songbooks 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +29 -0
  7. data/LICENSE +22 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +41 -0
  10. data/Rakefile +6 -0
  11. data/app/assets/javascripts/application.coffee +4 -0
  12. data/app/assets/javascripts/components/filters/convertWhitespace.filter.coffee +3 -0
  13. data/app/assets/javascripts/components/song_component/chord.directive.coffee +9 -0
  14. data/app/assets/javascripts/components/song_component/literal.directive.coffee +6 -0
  15. data/app/assets/javascripts/components/song_component/song_component.directive.coffee +6 -0
  16. data/app/assets/javascripts/components/songbook/songbook.controller.coffee +29 -0
  17. data/app/assets/javascripts/components/songs/song.controller.coffee +10 -0
  18. data/app/assets/javascripts/components/songs/song.directive.coffee +7 -0
  19. data/app/assets/javascripts/components/songs/song.service.coffee +98 -0
  20. data/app/assets/javascripts/components/songs/songs.controller.coffee +21 -0
  21. data/app/assets/javascripts/routes.coffee +11 -0
  22. data/app/assets/stylesheets/application.scss +34 -0
  23. data/app/fonts/DejaVuSans-Bold.ttf +0 -0
  24. data/app/fonts/DejaVuSans-BoldOblique.ttf +0 -0
  25. data/app/fonts/DejaVuSans-ExtraLight.ttf +0 -0
  26. data/app/fonts/DejaVuSans-Oblique.ttf +0 -0
  27. data/app/fonts/DejaVuSans.ttf +0 -0
  28. data/app/fonts/DejaVuSansCondensed-Bold.ttf +0 -0
  29. data/app/fonts/DejaVuSansCondensed-BoldOblique.ttf +0 -0
  30. data/app/fonts/DejaVuSansCondensed-Oblique.ttf +0 -0
  31. data/app/fonts/DejaVuSansCondensed.ttf +0 -0
  32. data/app/fonts/DejaVuSansMono-Bold.ttf +0 -0
  33. data/app/fonts/DejaVuSansMono-BoldOblique.ttf +0 -0
  34. data/app/fonts/DejaVuSansMono-Oblique.ttf +0 -0
  35. data/app/fonts/DejaVuSansMono.ttf +0 -0
  36. data/app/fonts/DejaVuSerif-Bold.ttf +0 -0
  37. data/app/fonts/DejaVuSerif-BoldItalic.ttf +0 -0
  38. data/app/fonts/DejaVuSerif-Italic.ttf +0 -0
  39. data/app/fonts/DejaVuSerif.ttf +0 -0
  40. data/app/fonts/DejaVuSerifCondensed-Bold.ttf +0 -0
  41. data/app/fonts/DejaVuSerifCondensed-BoldItalic.ttf +0 -0
  42. data/app/fonts/DejaVuSerifCondensed-Italic.ttf +0 -0
  43. data/app/fonts/DejaVuSerifCondensed.ttf +0 -0
  44. data/app/tex/songbook.tex +12 -0
  45. data/app/views/application.haml +54 -0
  46. data/app/views/layouts/application.haml +40 -0
  47. data/app/views/songbook.prawn +80 -0
  48. data/app/views/songs/component.haml +2 -0
  49. data/app/views/songs/index.jbuilder +5 -0
  50. data/app/views/songs/show.haml +23 -0
  51. data/app/views/songs/show.jbuilder +24 -0
  52. data/app/views/songs/song.haml +4 -0
  53. data/exe/sb_server +6 -0
  54. data/lib/prawn_ext/font/afm.rb +20 -0
  55. data/lib/songbooks.rb +32 -0
  56. data/lib/songbooks/CLI.rb +13 -0
  57. data/lib/songbooks/components/chord.rb +98 -0
  58. data/lib/songbooks/components/literal.rb +57 -0
  59. data/lib/songbooks/components/metadata.rb +69 -0
  60. data/lib/songbooks/components/section.rb +73 -0
  61. data/lib/songbooks/controllers/controller.rb +18 -0
  62. data/lib/songbooks/controllers/songs_controller.rb +30 -0
  63. data/lib/songbooks/folder.rb +32 -0
  64. data/lib/songbooks/server.rb +65 -0
  65. data/lib/songbooks/song.rb +53 -0
  66. data/lib/songbooks/version.rb +3 -0
  67. data/lib/songbooks/view_helper.rb +9 -0
  68. data/public/javascripts/angular-local-storage.js +449 -0
  69. data/public/javascripts/angular-route.js +991 -0
  70. data/public/javascripts/angular.js +28904 -0
  71. data/public/javascripts/application.js +8 -0
  72. data/public/javascripts/components/filters/convertWhitespace.filter.js +8 -0
  73. data/public/javascripts/components/song_component/chord.directive.js +13 -0
  74. data/public/javascripts/components/song_component/literal.directive.js +9 -0
  75. data/public/javascripts/components/song_component/song_component.directive.js +9 -0
  76. data/public/javascripts/components/songbook/songbook.controller.js +33 -0
  77. data/public/javascripts/components/songs/song.controller.js +12 -0
  78. data/public/javascripts/components/songs/song.directive.js +9 -0
  79. data/public/javascripts/components/songs/song.service.js +107 -0
  80. data/public/javascripts/components/songs/songs.controller.js +22 -0
  81. data/public/javascripts/jquery-2.1.4.min.js +4 -0
  82. data/public/javascripts/routes.js +16 -0
  83. data/public/stylesheets/application.css +24 -0
  84. data/public/stylesheets/bootstrap-theme.min.css +5 -0
  85. data/public/stylesheets/bootstrap.min.css +5 -0
  86. data/songbooks.gemspec +51 -0
  87. metadata +369 -0
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,12 @@
1
+ \documentclass[10pt,a4paper,twoside]{article}
2
+ \usepackage[latin1]{inputenc}
3
+ \usepackage{amsmath}
4
+ \usepackage{amsfonts}
5
+ \usepackage{amssymb}
6
+ \usepackage{graphicx}
7
+ \begin{document}
8
+ \begin{tabular}{|r|l|}
9
+ asdf & fdsa \\
10
+ ss & gg \\
11
+ \end{tabular}
12
+ \end{document}
@@ -0,0 +1,54 @@
1
+ !!! 5
2
+ %html{'ng-app' => 'songbooks', 'ng-controller' => 'SongsController as songsCtrl'}
3
+ %head
4
+ %link{:href => '/stylesheets/bootstrap.min.css', :rel => 'stylesheet'}/
5
+ %link{:href => '/stylesheets/bootstrap-theme.min.css', :rel => 'stylesheet'}/
6
+ %link{:href => '/stylesheets/application.css', :rel => 'stylesheet'}/
7
+
8
+ - %w(jquery-2.1.4.min angular angular-route angular-local-storage application routes).each do |file|
9
+ %script{:src => "/javascripts/#{file}.js", :type => 'text/javascript'}
10
+ - Songbooks::ViewHelper.javascripts.each do |file|
11
+ %script{:src => "/javascripts/#{file}", :type => 'text/javascript'}
12
+
13
+ %title
14
+ Songbooks
15
+
16
+ %body{:ng => {:controller => 'SongbookController as songbook'}}
17
+ .container-fluid
18
+ .row
19
+ .col-sm-3.hidden-print
20
+ .panel.panel-default
21
+ .panel-heading
22
+ %h3.panel-title
23
+ Available Songs
24
+ %a.btn.btn-default.btn-sm.pull-right{:ng => {:click => 'songsCtrl.addAllToBook()'}} +
25
+ %br/
26
+ %small= Songbooks.folder.path
27
+ .list-group#songs
28
+ .list-group-item
29
+ %input.form-control{:type => 'text', :ng => {:model => 'songsCtrl.filterQuery'}}
30
+ .list-group-item{:ng => {:click => 'songsCtrl.setActive(song)',
31
+ :repeat => 'song in songsCtrl.songs() | filter: songsCtrl.filterQuery',
32
+ :class => "{ 'active': song.active }"},
33
+ :style => 'cursor: default'}
34
+ %span.pull-right
35
+ %a.btn.btn-sm.btn-default{:ng => {:click => 'songbook.addSong(song)'}} +
36
+ %song
37
+ .col-sm-6
38
+ .panel.panel-default
39
+ .panel-body#main-content{:ng => {:view => true}}
40
+ .col-sm-3.hidden-print
41
+ .panel.panel-default
42
+ .panel-heading
43
+ %h3.panel-title
44
+ {{ songbook.title() }}
45
+ %a.btn.btn-default.btn-sm.pull-right{:ng => {:click => 'songbook.sendToServer()'}} PDF
46
+ %a.btn.btn-default.btn-sm.pull-right{:ng => {:click => 'songbook.clear()'}} Clear
47
+ .clearfix
48
+ .list-group#songs
49
+ .list-group-item
50
+ %input.form-control{:type => 'text', :ng => {:model => 'songbook.filterQuery'}}
51
+ .list-group-item{:ng => {:repeat => 'song in songbook.songs() | filter: songbook.filterQuery'}}
52
+ %span.pull-right
53
+ %a.btn.btn-sm.btn-default{:ng => {:click => 'songbook.removeSong(song)'}} -
54
+ %song
@@ -0,0 +1,40 @@
1
+ !!! 5
2
+
3
+ %html
4
+ %head
5
+ %link{:href => '/stylesheets/bootstrap.min.css', :rel => 'stylesheet'}/
6
+ %link{:href => '/stylesheets/bootstrap-theme.min.css', :rel => 'stylesheet'}/
7
+ %link{:href => '/stylesheets/application.css', :rel => 'stylesheet'}/
8
+ %script{:src => '/javascripts/jquery-2.1.4.min.js', :type => 'text/javascript'}
9
+ %script{:src => '/javascripts/angular.min.js', :type => 'text/javascript'}
10
+
11
+ %body
12
+ .container-fluid
13
+ .row
14
+ .col-sm-3
15
+ .panel.panel-default
16
+ .panel-heading
17
+ %h3.panel-title
18
+ Verfügbare Lieder
19
+ %br/
20
+ %small= Songbooks.chords_directory.path
21
+ .list-group#file-list
22
+ - Songbooks.chords_directory.song_list.songs.each do |song|
23
+ %a.list-group-item{:href => "/songs/#{song.identifier}", :data => {:remote => true}}
24
+ = song.title
25
+ = ' - '
26
+ %i= song.artist
27
+ .col-sm-6
28
+ .panel.panel-default
29
+ .panel-heading
30
+ %h3.panel-title Inhalt
31
+ .panel-body#main-content
32
+ = yield
33
+ .col-sm-3
34
+ .panel.panel-default
35
+ .panel-heading
36
+ %h3.panel-title
37
+ Ausgewählte Lieder
38
+ .panel-body
39
+ ll
40
+
@@ -0,0 +1,80 @@
1
+ #----------------------------------------------------------------
2
+ # Fonts
3
+ #----------------------------------------------------------------
4
+
5
+ pdf.font_families.update('DejaVuSansMono' => {
6
+ :normal => Songbooks.font_file('DejaVuSansMono'),
7
+ :italic => Songbooks.font_file('DejaVuSansMono-Oblique'),
8
+ :bold => Songbooks.font_file('DejaVuSansMono-Bold'),
9
+ :bold_italic => Songbooks.font_file('DejaVuSansMono-BoldOblique')
10
+ })
11
+
12
+ @toc = {}
13
+
14
+ pdf.start_new_page
15
+
16
+ @songs.each do |song|
17
+ pdf.text song.title, :size => 18
18
+ pdf.text song.artist
19
+
20
+ @toc["#{song.title} - #{song.artist}"] = pdf.page_count
21
+
22
+ song_y = pdf.cursor
23
+ text_x = pdf.width_of(song.longest_section_caption) + 10
24
+
25
+ pdf.font 'DejaVuSansMono', :size => 9 do
26
+
27
+ song.sections.each do |section|
28
+ pdf.draw_text section.name, :at => [text_x - pdf.width_of(section.name) - 10, song_y], :style => :bold
29
+
30
+ section.lines.each do |line|
31
+ line_x = text_x
32
+
33
+ line.each do |component|
34
+ case component
35
+ when Songbooks::Components::Literal
36
+ pdf.draw_text component.to_s, :at => [line_x, song_y]
37
+ when Songbooks::Components::Chord
38
+ pdf.fill_color '8B0000'
39
+ pdf.draw_text component.to_s, :at => [line_x, song_y]
40
+ pdf.fill_color '000000'
41
+ end
42
+
43
+ line_x += pdf.width_of(component.to_s)
44
+ end
45
+ song_y -= 12
46
+
47
+ if song_y < 10
48
+ pdf.start_new_page
49
+ song_y = 700
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ pdf.start_new_page unless song == @songs.last
56
+ end
57
+
58
+ pdf.go_to_page 1
59
+
60
+ #----------------------------------------------------------------
61
+ # Table of Contents
62
+ #----------------------------------------------------------------
63
+
64
+ pdf.font 'DejaVuSansMono', :size => 11 do
65
+ @toc.keys.sort.each do |k|
66
+ min_width = pdf.width_of("#{k} #{@toc[k]}")
67
+ dots = ''
68
+ dots << '.' while pdf.width_of(dots) < (530 - min_width)
69
+
70
+ pdf.text "#{k} #{dots} #{@toc[k]}"
71
+ end
72
+ end
73
+
74
+ #----------------------------------------------------------------
75
+ # Page Numbers
76
+ #----------------------------------------------------------------
77
+
78
+ pdf.number_pages '<page>',
79
+ :page_filter => lambda{ |pg| pg != 1 },
80
+ :at => [pdf.bounds.right - 50, 0]
@@ -0,0 +1,2 @@
1
+ %chord{:ng => {:if => 'component.component_type == "chord"'}}<>
2
+ %literal{:ng => {:if => 'component.component_type == "literal"'}}<>
@@ -0,0 +1,5 @@
1
+ json.data do
2
+ json.array!(@songs) do |song|
3
+ json.(song, :identifier, :title, :artist)
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ .pull-right
2
+ %a{:ng => {:click => 'songbook.addSong(songCtrl.song())'}} Add
3
+
4
+ %h3
5
+ %small
6
+ {{ songCtrl.song().artist }}
7
+ %br/
8
+ {{ songCtrl.song().title }}
9
+
10
+ %p
11
+ %span{ng: {repeat: 'i in [-6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6]'}}
12
+ %input{type: 'radio', name: 'transposed_by', ng: {model: 'songCtrl.song().transposedBy'}} {{ i }}
13
+
14
+
15
+ %table
16
+ %tbody
17
+ %tr{:ng => {:repeat => 'section in songCtrl.song().sections'}}
18
+ %td.text-right{:style => 'vertical-align: top; padding: 10px; padding-top: 0'}
19
+ %strong
20
+ {{ section.name }}
21
+ %td
22
+ %div{:ng => {:repeat => 'line in section.lines'}}
23
+ %song-component{:ng => {:repeat => 'component in line'}}
@@ -0,0 +1,24 @@
1
+ json.data do
2
+ json.(@song, :identifier, :title, :artist)
3
+
4
+ json.sections @song.sections do |section|
5
+ json.(section, :name)
6
+
7
+ json.lines section.lines do |line|
8
+ json.array! line do |component|
9
+ case component
10
+ when Songbooks::Components::Literal
11
+ json.component_type 'literal'
12
+ json.content component.to_s
13
+ when Songbooks::Components::Chord
14
+ json.component_type 'chord'
15
+ json.name component.name
16
+ json.transposed_by component.transposed_by.to_s
17
+ else
18
+ json.type 'unknown'
19
+ json.content 'unknown component: '
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ %small
2
+ {{ song.artist }}
3
+ %br/
4
+ {{ song.title }}
data/exe/sb_server ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'songbooks'
5
+ require 'songbooks/CLI'
6
+ Songbooks::CLI.start
@@ -0,0 +1,20 @@
1
+ # class Prawn::Font::AFM
2
+ # # Patch normalize_encoding method to handle conversion more gracefully.
3
+ # #
4
+ # # Any valid utf-8 characters that cannot be encoded to windows-1252 are
5
+ # # replaced with the logic "not" symbol and a warning is issued identifying
6
+ # # the text that cannot be converted.
7
+ # def normalize_encoding(text)
8
+ # text.encode('utf-8')
9
+ # #text.encode 'windows-1252'
10
+ # rescue ::Encoding::UndefinedConversionError
11
+ # warn 'The following text could not be fully converted to the Windows-1252 character set:'
12
+ # warn %(#{text.gsub(/^/, '| ').rstrip})
13
+ # warn ''
14
+ # text.encode 'windows-1252', undef: :replace, invalid: :replace, replace: 'x'
15
+ # rescue ::Encoding::InvalidByteSequenceError
16
+ # raise Prawn::Errors::IncompatibleStringEncoding,
17
+ # %(Your document includes text that's not compatible with the Windows-1252 character set.
18
+ # If you need full UTF-8 support, use TTF fonts instead of PDF's built-in (AFM) fonts\n.)
19
+ # end
20
+ # end
data/lib/songbooks.rb ADDED
@@ -0,0 +1,32 @@
1
+ module Songbooks
2
+ # Gem requires
3
+ require 'haml'
4
+ require 'sinatra'
5
+ require 'active_support/all'
6
+ require 'prawn'
7
+ require 'sinatra/prawn'
8
+ require 'sinatra/jbuilder'
9
+ require 'json'
10
+
11
+ # Ruby requires
12
+ require 'pathname'
13
+
14
+ # Internal requires
15
+ require 'songbooks/folder'
16
+
17
+ def self.root
18
+ Pathname.new(File.dirname(__dir__))
19
+ end
20
+
21
+ def self.font_file(name)
22
+ root.join('app', 'fonts', "#{name}.ttf").to_s
23
+ end
24
+
25
+ def self.initialize_folder(directory_path)
26
+ @folder = Songbooks::Folder.new(directory_path)
27
+ end
28
+
29
+ def self.folder
30
+ @folder
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ require 'thor'
2
+ require 'songbooks/server'
3
+
4
+ module Songbooks
5
+ class CLI < Thor
6
+ desc :start, 'Starts a songbook server'
7
+ def start(directory = '.')
8
+ Songbooks.initialize_folder(File.expand_path(directory))
9
+ puts Songbooks.folder.inspect
10
+ Songbooks::Server.run!
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,98 @@
1
+ module Songbooks
2
+ module Components
3
+ class Chord
4
+
5
+ #----------------------------------------------------------------
6
+ # CONSTANTS
7
+ #----------------------------------------------------------------
8
+
9
+ NOTES = %w(C Db D Eb E F Gb G Ab A Bb B)
10
+
11
+ NOTE_MAPPING = [
12
+ %w(C# Db),
13
+ %w(D# Eb),
14
+ %w(E# F),
15
+ %w(Fb, E),
16
+ %w(F# Gb),
17
+ %w(G# Ab),
18
+ %w(A# Bb),
19
+ %w(B#, C)
20
+ ].each_with_object({}) do |(n1, n2), h|
21
+ h[n1] = n2
22
+ h[n2] = n1
23
+ end
24
+
25
+ CHORD_REGEXP = /[A-G](?:b|#)?(?:(?:m(?:aj)?|M|aug|dim|sus)?(?:[2-7]|9|13)?)?(?:\/[A-G](?:b|#)?)?/
26
+ NOTE_REGEXP = /\A[A-G](b|#)*/
27
+
28
+ # A chord in a line has to appear either right before a space or punctuation symbol
29
+ # or be the only thing left in a string
30
+ CHORD_OCCURRENCE_REGEXP = /\A(#{CHORD_REGEXP})(?:\s+|[\.,\|])|\A(#{CHORD_REGEXP})\z/
31
+
32
+ #----------------------------------------------------------------
33
+ # Parsing
34
+ #----------------------------------------------------------------
35
+
36
+ #
37
+ # Tries to parse the beginning of the given text as a chord
38
+ #
39
+ # If the text starts with a chord, this part of the string is automatically deleted.
40
+ #
41
+ # @return [Chord, NilClass] a chord object if the given text started with a valid chord
42
+ # or +nil+ otherwise.
43
+ #
44
+ def self.munch_chord(text)
45
+ if (match = text.match(CHORD_OCCURRENCE_REGEXP))
46
+ chord_name = match[1..-1].compact.first
47
+ text[0, chord_name.length] = ''
48
+ return Chord.new(chord_name)
49
+ end
50
+ nil
51
+ end
52
+
53
+ def self.starts_with_chord?(text)
54
+ CHORD_OCCURRENCE_REGEXP === text
55
+ end
56
+
57
+ def initialize(chord_name, transposed_by: 0)
58
+ @chord_name = chord_name
59
+ @transposed_by = transposed_by
60
+ end
61
+
62
+ attr_reader :transposed_by
63
+
64
+ def name
65
+ @chord_name
66
+ end
67
+
68
+ def base_note
69
+ name.match(NOTE_REGEXP)[0]
70
+ end
71
+
72
+ def transpose!(count)
73
+ note = base_note
74
+ note = NOTE_MAPPING[base_note] unless NOTES.include?(base_note)
75
+ index = NOTES.index(note)
76
+ transposed_note = NOTES[(index + number) % NOTES.size]
77
+ @transposed_by = count
78
+ @chord_name.sub!(note, transposed_note)
79
+ end
80
+
81
+ def transpose(count)
82
+ dup.transpose!(count)
83
+ end
84
+
85
+ def ==(other_chord)
86
+ name == other_chord.name
87
+ end
88
+
89
+ def to_s
90
+ name
91
+ end
92
+
93
+ def to_str
94
+ name
95
+ end
96
+ end
97
+ end
98
+ end