Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Ruby CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest

name: Build and Test

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2.1'
bundler-cache: true

- name: Run RuboCop
run: bundle exec rubocop --parallel
5 changes: 5 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
AllCops:
NewCops: enable
Exclude:
- 'Gemfile.lock'
- 'bin/*'
21 changes: 11 additions & 10 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# frozen_string_literal: true

source "https://rubygems.org"
source 'https://rubygems.org'

ruby "3.2.1"
ruby '3.2.1'

gem "sinatra"
gem "sinatra-contrib"
gem "puma"
gem "dotenv"
gem "activesupport"
gem 'activesupport'
gem 'dotenv'
gem 'puma'
gem 'sinatra'
gem 'sinatra-contrib'

group :development do
gem "better_errors"
gem "binding_of_caller"
gem "pry"
gem 'better_errors'
gem 'binding_of_caller'
gem 'pry'
gem 'rubocop'
end
31 changes: 31 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ GEM
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
ast (2.4.3)
base64 (0.3.0)
benchmark (0.4.1)
better_errors (2.10.1)
Expand All @@ -32,18 +33,27 @@ GEM
erubi (1.13.1)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
json (2.12.2)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
method_source (1.1.0)
minitest (5.25.5)
multi_json (1.15.0)
mustermann (3.0.3)
ruby2_keywords (~> 0.0.1)
nio4r (2.7.4)
parallel (1.27.0)
parser (3.3.8.0)
ast (~> 2.4.1)
racc
prism (1.4.0)
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
puma (6.6.0)
nio4r (~> 2.0)
racc (1.8.1)
rack (3.1.16)
rack-protection (4.1.1)
base64 (>= 0.1.0)
Expand All @@ -52,7 +62,24 @@ GEM
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rainbow (3.1.1)
regexp_parser (2.10.0)
rouge (4.5.2)
rubocop (1.78.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.45.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.45.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
securerandom (0.4.1)
sinatra (4.1.1)
Expand All @@ -71,6 +98,9 @@ GEM
tilt (2.6.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
uri (1.0.3)

PLATFORMS
Expand All @@ -84,6 +114,7 @@ DEPENDENCIES
dotenv
pry
puma
rubocop
sinatra
sinatra-contrib

Expand Down
36 changes: 19 additions & 17 deletions app.rb
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
require "sinatra"
require "sinatra/reloader"
require "erb"
require "active_support/core_ext/string/inflections"
require_relative "lib/chord_transposer"
# frozen_string_literal: true

require 'sinatra'
require 'sinatra/reloader'
require 'erb'
require 'active_support/core_ext/string/inflections'
require_relative 'lib/chord_transposer'

enable :sessions

def songs_dir_path
File.join(File.dirname(__FILE__), "songs")
File.join(File.dirname(__FILE__), 'songs')
end

def get_available_songs
Dir.glob(File.join(songs_dir_path, "*.txt")).map { |f| File.basename(f) }.sort
def available_songs
Dir.glob(File.join(songs_dir_path, '*.txt')).map { |f| File.basename(f) }.sort
rescue Errno::ENOENT
[]
end

before do
session[:song_transpositions] ||= {}
@available_songs = get_available_songs
@available_songs = available_songs

@selected_song_filename = params[:song] || session[:current_song] || @available_songs.first
session[:current_song] = @selected_song_filename
end

get "/" do
get '/' do
if @available_songs.empty?
status 404
return "<h1>Error: No song files found!</h1><p>Please create a 'songs' directory and place .txt files inside it.</p>"
return "<h1>Error: No song files found!</h1><p>Please add .txt files inside the 'songs' directory .</p>"
end

unless @selected_song_filename && @available_songs.include?(@selected_song_filename)
Expand All @@ -47,7 +49,7 @@ def get_available_songs
original_lines = File.readlines(song_file_path)
transposed_lines = original_lines.map do |line|
line_to_process = line.chomp
if ChordTransposer.is_chord_line?(line_to_process)
if ChordTransposer.chord_line?(line_to_process)
ChordTransposer.transpose_line(line_to_process, @current_total_semitones)
else
line_to_process
Expand All @@ -59,19 +61,19 @@ def get_available_songs
erb :index
end

post "/transpose" do
redirect "/" unless session[:current_song]
post '/transpose' do
redirect '/' unless session[:current_song]

current_song = session[:current_song]

session[:song_transpositions][current_song] ||= 0

case params[:direction]
when "up"
when 'up'
session[:song_transpositions][current_song] += 1
when "down"
when 'down'
session[:song_transpositions][current_song] -= 1
when "reset"
when 'reset'
session[:song_transpositions][current_song] = 0
end

Expand Down
4 changes: 3 additions & 1 deletion config.ru
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
require_relative "app"
# frozen_string_literal: true

require_relative 'app'
run Sinatra::Application
15 changes: 9 additions & 6 deletions lib/chord_transposer.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# frozen_string_literal: true

# provides functionality for transposing musical chords within a line of text.
module ChordTransposer
NOTES = %w[A A# B C C# D D# E F F# G G#].freeze
CHORD_REGEX = /\b([A-G][b#]?(m|maj|min|sus|dim|aug|add)?(\d+)?(M)?)\b/.freeze
CHORD_REGEX = /\b([A-G][b#]?(m|maj|min|sus|dim|aug|add)?(\d+)?(M)?)\b/

FLAT_TO_SHARP = {
"Ab" => "G#", "Bb" => "A#", "Cb" => "B",
"Db" => "C#", "Eb" => "D#", "Fb" => "E",
"Gb" => "F#",
'Ab' => 'G#', 'Bb' => 'A#', 'Cb' => 'B',
'Db' => 'C#', 'Eb' => 'D#', 'Fb' => 'E',
'Gb' => 'F#'
}.freeze

def self.transpose_chord(chord, semitones)
Expand All @@ -15,7 +18,7 @@ def self.transpose_chord(chord, semitones)
return chord unless match

base_note = match[1]
suffix = chord[base_note.length..-1]
suffix = chord[base_note.length..]

normalized_note = FLAT_TO_SHARP[base_note] || base_note

Expand All @@ -33,7 +36,7 @@ def self.transpose_line(line, semitones)
end
end

def self.is_chord_line?(line)
def self.chord_line?(line)
words = line.strip.split(/\s+/)
return false if words.empty?

Expand Down
Loading