cultome_player 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +325 -0
  9. data/Rakefile +8 -0
  10. data/bin/cultome_player +39 -0
  11. data/config/environment.yml +28 -0
  12. data/cultome_player.gemspec +35 -0
  13. data/db/001_create_schema.rb +58 -0
  14. data/lib/cultome_player.rb +107 -0
  15. data/lib/cultome_player/command.rb +11 -0
  16. data/lib/cultome_player/command/language.rb +61 -0
  17. data/lib/cultome_player/command/processor.rb +165 -0
  18. data/lib/cultome_player/command/reader.rb +86 -0
  19. data/lib/cultome_player/environment.rb +130 -0
  20. data/lib/cultome_player/events.rb +29 -0
  21. data/lib/cultome_player/media.rb +47 -0
  22. data/lib/cultome_player/objects.rb +15 -0
  23. data/lib/cultome_player/objects/album.rb +21 -0
  24. data/lib/cultome_player/objects/artist.rb +18 -0
  25. data/lib/cultome_player/objects/command.rb +37 -0
  26. data/lib/cultome_player/objects/drive.rb +26 -0
  27. data/lib/cultome_player/objects/genre.rb +16 -0
  28. data/lib/cultome_player/objects/parameter.rb +37 -0
  29. data/lib/cultome_player/objects/response.rb +42 -0
  30. data/lib/cultome_player/objects/song.rb +38 -0
  31. data/lib/cultome_player/player.rb +13 -0
  32. data/lib/cultome_player/player/adapter.rb +14 -0
  33. data/lib/cultome_player/player/adapter/mpg123.rb +143 -0
  34. data/lib/cultome_player/player/interactive.rb +56 -0
  35. data/lib/cultome_player/player/interface.rb +13 -0
  36. data/lib/cultome_player/player/interface/basic.rb +96 -0
  37. data/lib/cultome_player/player/interface/builtin_help.rb +368 -0
  38. data/lib/cultome_player/player/interface/extended.rb +199 -0
  39. data/lib/cultome_player/player/interface/helper.rb +300 -0
  40. data/lib/cultome_player/player/playlist.rb +280 -0
  41. data/lib/cultome_player/plugins.rb +23 -0
  42. data/lib/cultome_player/plugins/help.rb +58 -0
  43. data/lib/cultome_player/state_checker.rb +74 -0
  44. data/lib/cultome_player/utils.rb +95 -0
  45. data/lib/cultome_player/version.rb +3 -0
  46. data/spec/config.yml +0 -0
  47. data/spec/cultome_player/command/processor_spec.rb +168 -0
  48. data/spec/cultome_player/command/reader_spec.rb +45 -0
  49. data/spec/cultome_player/cultome_player_spec.rb +17 -0
  50. data/spec/cultome_player/environment_spec.rb +65 -0
  51. data/spec/cultome_player/events_spec.rb +22 -0
  52. data/spec/cultome_player/media_spec.rb +41 -0
  53. data/spec/cultome_player/player/adapter/mpg123_spec.rb +82 -0
  54. data/spec/cultome_player/player/interface/basic_spec.rb +168 -0
  55. data/spec/cultome_player/player/interface/extended/connect_spec.rb +117 -0
  56. data/spec/cultome_player/player/interface/extended/search_spec.rb +90 -0
  57. data/spec/cultome_player/player/interface/extended/show_spec.rb +36 -0
  58. data/spec/cultome_player/player/interface/extended/shuffle_spec.rb +26 -0
  59. data/spec/cultome_player/player/interface/extended_spec.rb +136 -0
  60. data/spec/cultome_player/player/interface/helper_spec.rb +63 -0
  61. data/spec/cultome_player/player/interface_spec.rb +17 -0
  62. data/spec/cultome_player/player/playlist_spec.rb +301 -0
  63. data/spec/cultome_player/plugins/help_spec.rb +21 -0
  64. data/spec/cultome_player/plugins_spec.rb +19 -0
  65. data/spec/cultome_player/utils_spec.rb +15 -0
  66. data/spec/spec_helper.rb +108 -0
  67. data/spec/test/uno/dos/dos.mp3 +0 -0
  68. data/spec/test/uno/dos/tres/tres.mp3 +0 -0
  69. data/spec/test/uno/uno.mp3 +0 -0
  70. data/tasks/console.rake +19 -0
  71. data/tasks/db.rake +19 -0
  72. data/tasks/run.rake +7 -0
  73. metadata +322 -0
@@ -0,0 +1,168 @@
1
+ require 'spec_helper'
2
+
3
+ describe CultomePlayer::Player::Interface::Basic do
4
+ let(:t){ TestClass.new }
5
+
6
+ describe '#pause' do
7
+ it 'calls the underliying player' do
8
+ t.should_receive(:pause_in_player)
9
+ t.execute "pause on"
10
+ end
11
+
12
+ it 'respond to description_help' do
13
+ t.should respond_to(:description_help)
14
+ end
15
+
16
+ it 'respond to usage_help' do
17
+ t.should respond_to(:usage_help)
18
+ end
19
+
20
+ it 'respond with Response object' do
21
+ t.execute("pause on").should be_instance_of Response
22
+ end
23
+ end
24
+
25
+ describe '#stop' do
26
+ before :each do
27
+ t.execute "connect '#{test_folder}' => test"
28
+ end
29
+
30
+ it 'calls the underliying player' do
31
+ t.should_receive(:stop_in_player)
32
+ t.execute "play"
33
+ t.execute "stop"
34
+ end
35
+
36
+ it 'respond to description_stop' do
37
+ t.should respond_to(:description_stop)
38
+ end
39
+
40
+ it 'respond to usage_stop' do
41
+ t.should respond_to(:usage_stop)
42
+ end
43
+
44
+ it 'respond with Response object' do
45
+ t.execute("stop").should be_instance_of Response
46
+ end
47
+ end
48
+
49
+ describe '#play' do
50
+
51
+ it 'respond to description_play' do
52
+ t.should respond_to(:description_play)
53
+ end
54
+
55
+ it 'respond to usage_play' do
56
+ t.should respond_to(:usage_play)
57
+ end
58
+
59
+ it 'respond with Response object' do
60
+ t.execute("play").should be_instance_of Response
61
+ end
62
+
63
+ context 'without music connected' do
64
+ it 'advise to connect music first if no music is connected' do
65
+ r = t.execute("play")
66
+ r.message.should eq "No music connected! You should try 'connect /home/yoo/music => main' first"
67
+ end
68
+ end
69
+
70
+ context 'with music connected' do
71
+ before :each do
72
+ t.execute "connect '#{test_folder}' => test"
73
+ end
74
+
75
+ it 'calls the underliying player to play' do
76
+ t.should_receive(:play_in_player)
77
+ t.execute 'play'
78
+ end
79
+
80
+ it 'calls the underliying player to resume playback' do
81
+ t.should_receive(:resume_in_player)
82
+ t.execute 'play'
83
+ t.execute "pause on"
84
+ t.execute 'play'
85
+ end
86
+
87
+ context 'without parameters' do
88
+ it 'in clean state should play all the library' do
89
+ r = t.execute 'play'
90
+ r.playlist.should have(3).items
91
+ r.should respond_to(:now_playing)
92
+ r.should respond_to(:playlist)
93
+ t.current_playlist.to_a.should have(3).items
94
+ t.current_song.should_not be_nil
95
+ end
96
+
97
+ it 'if paused should resume playback' do
98
+ t.execute "pause on"
99
+ t.should be_paused
100
+ t.execute 'play'
101
+ t.should_not be_paused
102
+ end
103
+
104
+ it 'if stopped should resume last playback from the begin' do
105
+ t.execute "play"
106
+ my_song = t.current_song
107
+ t.execute "stop"
108
+ t.should be_stopped
109
+ t.execute 'play'
110
+ t.should be_playing
111
+ my_song.should eq t.current_song
112
+ end
113
+
114
+ it 'get an error message if playing' do
115
+ t.execute 'play'
116
+ r = t.execute 'play'
117
+ r.message.should eq 'What you mean? Im already playing!'
118
+ end
119
+ end
120
+
121
+ context 'with number parameter' do
122
+ it 'play the nth element in focus list without altering the current playlist' do
123
+ t.execute 'search a'
124
+ t.execute 'play 2'
125
+ t.current_song.name.should eq t.playlists[:focus].at(1).name
126
+ end
127
+ end
128
+
129
+ context 'with criteria parameter' do
130
+ it 'different criterias create an AND filter' do
131
+ t.execute "play t:a"
132
+ t.current_playlist.should have(2).songs
133
+ t.execute "play t:a a:m"
134
+ t.current_playlist.should have(1).songs
135
+ end
136
+
137
+ it 'same criterias create an OR filter' do
138
+ t.execute "play t:a"
139
+ t.current_playlist.should have(2).songs
140
+ t.execute "play t:a t:d"
141
+ t.current_playlist.should have(3).songs
142
+ end
143
+ end
144
+
145
+ context 'with object parameter' do
146
+ it 'replace the current playlist with the object content' do
147
+ t.execute 'play'
148
+ t.current_playlist.should have(3).songs
149
+ t.execute 'next'
150
+ t.execute 'next'
151
+ t.execute 'play @history'
152
+ t.current_playlist.should have(2).songs
153
+ end
154
+ end
155
+
156
+ context 'with literal parameter' do
157
+ it 'create an OR filter with the fields trackname, artist and album' do
158
+ t.execute 'play way'
159
+ t.current_playlist.should have(1).songs
160
+ t.execute 'play sing'
161
+ t.current_playlist.should have(1).songs
162
+ t.execute 'play sing way'
163
+ t.current_playlist.should have(2).songs
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ include CultomePlayer::Objects
4
+
5
+ describe CultomePlayer::Player::Interface::Extended do
6
+ let(:t){ TestClass.new }
7
+
8
+ before :each do
9
+ Song.create!(id: 1, name: "song_uno", artist_id: 1, album_id: 1, drive_id: 1, relative_path: "uno/uno.mp3")
10
+ Song.create!(id: 2, name: "song_dos", artist_id: 1, album_id: 1, drive_id: 1, relative_path: "uno/dos/dos.mp3")
11
+
12
+ Artist.create!(id: 1, name: "artist_uno")
13
+
14
+ Album.create!(id: 1, name: "album_uno")
15
+ end
16
+
17
+ it 'detect all the files in the subdirectories' do
18
+ r = t.execute "connect '#{test_folder}' => test"
19
+ r.files_detected.should eq 3
20
+ end
21
+
22
+ it 'import the files not imported before and update the rest' do
23
+ r = t.execute "connect '#{test_folder}' => test"
24
+ r.files_detected.should eq 3
25
+ r.files_updated.should eq 2
26
+ r.files_imported.should eq 1
27
+ end
28
+
29
+ context 'with only a literal parameter' do
30
+ it 'connect the named drive if exist' do
31
+ Drive.create!(name: "test", path: test_folder, connected: false)
32
+ Drive.find_by(name: 'test').should_not be_connected
33
+ r = t.execute "connect test"
34
+ Drive.find_by(name: 'test').should be_connected
35
+ end
36
+
37
+ it 'raise an error if drive not exists' do
38
+ r = t.execute("connect ghost")
39
+ r.message.should eq 'invalid name'
40
+ r.details.should eq 'the named drive doesnt exists'
41
+ end
42
+ end
43
+
44
+ context 'with a path and a literal' do
45
+ it 'create the drive if not exists' do
46
+ Drive.find_by(name: 'new').should be_nil
47
+ r = t.execute "connect '#{test_folder}' => new"
48
+ Drive.find_by(name: 'new').should_not be_nil
49
+ r.drive_updated.should be_false
50
+ end
51
+
52
+ it 'update the drive if exists' do
53
+ Drive.create!(name: "test", path: test_folder)
54
+ r = t.execute "connect '#{test_folder}' => test"
55
+ r.drive_updated.should be_true
56
+ end
57
+
58
+ it 'raise an error if path is invalid' do
59
+ r = t.execute('connect /invalid => ghost')
60
+ r.message.should eq 'invalid path'
61
+ r.details.should eq 'the directory is invalid'
62
+ end
63
+ end
64
+
65
+ describe 'with mp3 files' do
66
+ let(:t2){ TestClass.new }
67
+
68
+ before :each do
69
+ Drive.create!(name: "library", path: test_folder)
70
+ end
71
+
72
+ describe 'create information from file' do
73
+ it 'create the song' do
74
+ expect{ t2.execute "connect '#{test_folder}' => test" }.to change{ Song.all.count }.by(1)
75
+ end
76
+
77
+ it 'create the artist' do
78
+ expect{ t2.execute "connect '#{test_folder}' => test" }.to change{ Artist.all.count }.by(2) # una de las rolas no tiene artist
79
+ end
80
+
81
+ it 'create the album' do
82
+ expect{ t2.execute "connect '#{test_folder}' => test" }.to change{ Album.all.count }.by(2) # una de las rolas no tiene album
83
+ end
84
+
85
+ context 'already imported into library' do
86
+ before :each do
87
+ DatabaseCleaner.clean_with(:truncation)
88
+ r = t2.execute "connect '#{test_folder}/uno/dos/tres' => test"
89
+ r.files_imported.should eq 1
90
+ r.files_updated.should eq 0
91
+ @track = Song.all.first
92
+ end
93
+
94
+ it 'insert the correct song information' do
95
+ @track.name.should eq "Sing For Absolution"
96
+ @track.year.should eq 2003
97
+ @track.track.should eq 4
98
+ @track.duration.should eq 295
99
+ @track.relative_path.should eq "tres.mp3"
100
+ # dependencias
101
+ @track.artist_id.should eq 1
102
+ @track.album_id.should eq 1
103
+ @track.drive_id.should eq 1
104
+ end
105
+
106
+ it 'insert the correct artist information' do
107
+ @track.artist.name.should eq "Muse"
108
+ end
109
+
110
+ it 'insert the correct album information' do
111
+ @track.album.name.should eq "Absolution"
112
+ @track.album.artists.first.name.should eq "Muse"
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ include CultomePlayer::Objects
4
+
5
+ describe CultomePlayer::Player::Interface::Extended do
6
+ let(:t){ TestClass.new }
7
+
8
+ before :each do
9
+ Drive.create!(id: 1, name: 'test', path: '/patito', connected: true)
10
+ Song.create!(id: 1, name: "song_uno", artist_id: 1, album_id: 1, drive_id: 1, relative_path: "uno/uno.mp3")
11
+ Song.create!(id: 2, name: "song_dos", artist_id: 1, album_id: 2, drive_id: 1, relative_path: "uno/dos/dos.mp3")
12
+ Song.create!(id: 3, name: "song_tres", artist_id: 1, album_id: 2, drive_id: 1, relative_path: "uno/dos/tres/tres.mp3")
13
+ Song.create!(id: 4, name: "song_cuatro", artist_id: 2, album_id: 3, drive_id: 1, relative_path: "fake/cuatro.mp3")
14
+ Song.create!(id: 5, name: "song_cinco", artist_id: 3, album_id: 4, drive_id: 1, relative_path: "fake/cinco.mp3")
15
+
16
+ Artist.create!(id: 1, name: "artist_uno")
17
+ Artist.create!(id: 2, name: "artist_dos")
18
+ Artist.create!(id: 3, name: "artist_tres")
19
+ Artist.create!(id: 4, name: "artist_cuatro")
20
+
21
+ Album.create!(id: 1, name: "album_uno")
22
+ Album.create!(id: 2, name: "album_dos")
23
+ Album.create!(id: 3, name: "album_tres")
24
+ Album.create!(id: 4, name: "album_cuatro")
25
+ end
26
+
27
+ it 'return a Response object' do
28
+ t.execute('search a:artist_uno').should be_instance_of Response
29
+ end
30
+
31
+ it 'respond success when there are results' do
32
+ r = t.execute('search a:artist_uno')
33
+ r.should be_success
34
+ r.should respond_to :songs
35
+ end
36
+
37
+ it 'respond failure when there are not results' do
38
+ r = t.execute('search a:nothing')
39
+ r.should be_failure
40
+ r.should respond_to :message
41
+ end
42
+
43
+ context 'with criteria parameters' do
44
+ it 'different criterias create an AND filter' do
45
+ r = t.execute 'search a:artist_uno b:album_dos'
46
+ songs = r.send(r.response_type)
47
+ songs.should have(2).items
48
+ songs.each{|s| s.artist.name.should match /uno/ }
49
+ songs.each{|s| s.album.name.should match /dos/ }
50
+ end
51
+
52
+ it 'same criterias create an OR filter' do
53
+ r = t.execute 'search a:artist_dos a:artist_tres'
54
+ songs = r.send(r.response_type)
55
+ songs.should have(2).songs
56
+ songs.each{|s| s.artist.name.should match /(dos|tres)/ }
57
+ end
58
+ end
59
+
60
+ context 'with object parameters' do
61
+ it 'search using an object as criteria' do
62
+ r = t.execute 'search @artist'
63
+ songs = r.send(r.response_type)
64
+ songs.should have(3).item
65
+ songs.each{|s| s.artist.name.should eq t.current_artist.name }
66
+ end
67
+
68
+ it 'create and OR filter more than one object' do
69
+ t.execute('search @artist @album').should have(4).songs
70
+ end
71
+ end
72
+
73
+ context 'with literal parameters' do
74
+ it 'create an OR filter with the fields trackname, artist and album' do
75
+ t.execute('search song_cuatro song_cinco').should have(2).songs
76
+ t.execute('search song_uno song_dos').should have(2).songs
77
+ t.execute('search album_dos cinco').should have(3).songs
78
+ t.execute('search cinco').should have(1).songs
79
+ end
80
+ end
81
+
82
+ context 'with mixed parameters' do
83
+ it 'create an OR filter between each type' do
84
+ t.execute('search t:song_tres artist_tres @album').should have(3).songs
85
+ t.execute('search t:song_cuatro t:song_dos @album').should have(2).songs
86
+ t.execute('search song_dos song_uno').should have(2).songs
87
+ t.execute('search @artist song_tres').should have(3).songs
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe CultomePlayer::Player::Interface::Extended do
4
+ let(:t){ TestClass.new }
5
+ context 'when no playlist is active and no parameters' do
6
+ it 'shows a message saying so, and giving instructions to play' do
7
+ r = t.execute 'show'
8
+ r.message.should eq "Nothing to show yet. Try with 'play' first."
9
+ end
10
+ end
11
+
12
+ context 'with and active playlist and playback' do
13
+ before :each do
14
+ t.execute "connect '#{test_folder}' => test"
15
+ t.execute 'play'
16
+ end
17
+
18
+ it 'without parameters shows the current song' do
19
+ r = t.execute 'show'
20
+ r.message.should match /^:::: Song: .+? (\\ Artist: .+? \\ Album: .+? )?::::$/
21
+ r.message.should match /[\d]{2}:[\d]{2} \|[#_]+?\| [\d]{2}:[\d]{2}/
22
+ end
23
+
24
+ context 'with object parameter' do
25
+ it 'library' do
26
+ r = t.execute 'show @library'
27
+ r.list.should_not be_empty
28
+ end
29
+
30
+ it 'song' do
31
+ r = t.execute 'show @song'
32
+ r.message.should match /:::: Song: .+? (\\ Artist: .+? )?(\\ Album: .+? )?::::\n?/
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe CultomePlayer::Player::Interface::Extended do
4
+ let(:t){ TestClass.new }
5
+
6
+ context '#shuffle' do
7
+ it 'check shuffle status' do
8
+ r = t.execute('shuffle')
9
+ r.message.should eq "No shuffling"
10
+ r.shuffling.should be_false
11
+ end
12
+
13
+ it 'turn on the shuffle' do
14
+ t.should_not be_shuffling
15
+ t.execute('shuffle on')
16
+ t.should be_shuffling
17
+ end
18
+
19
+ it 'turn off shuffle' do
20
+ t.execute('shuffle on')
21
+ t.should be_shuffling
22
+ t.execute('shuffle off')
23
+ t.should_not be_shuffling
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+
3
+ describe CultomePlayer::Player::Interface::Extended do
4
+ let(:t){ TestClass.new }
5
+
6
+ it 'repeats a song from the beginning' do
7
+ t.should_receive(:send_to_player).with(/^jump 0$/)
8
+ t.repeat(nil)
9
+ end
10
+
11
+ describe 'search' do
12
+ it 'respond to description_search' do
13
+ t.should respond_to(:description_search)
14
+ end
15
+
16
+ it 'respond to usage_search' do
17
+ t.should respond_to(:usage_search)
18
+ end
19
+
20
+ it 'respond with Response object' do
21
+ t.execute("search on").should be_instance_of Response
22
+ end
23
+ end
24
+
25
+ describe 'show' do
26
+ it 'respond to description_show' do
27
+ t.should respond_to(:description_show)
28
+ end
29
+
30
+ it 'respond to usage_show' do
31
+ t.should respond_to(:usage_show)
32
+ end
33
+
34
+ it 'respond with Response object' do
35
+ t.execute("show").should be_instance_of Response
36
+ end
37
+ end
38
+
39
+ describe 'enqueue' do
40
+ it 'respond to description_enqueue' do
41
+ t.should respond_to(:description_enqueue)
42
+ end
43
+
44
+ it 'respond to usage_enqueue' do
45
+ t.should respond_to(:usage_enqueue)
46
+ end
47
+
48
+ it 'respond with Response object' do
49
+ t.execute("enqueue on").should be_instance_of Response
50
+ end
51
+ end
52
+
53
+ describe 'shuffle' do
54
+ it 'respond to description_shuffle' do
55
+ t.should respond_to(:description_shuffle)
56
+ end
57
+
58
+ it 'respond to usage_shuffle' do
59
+ t.should respond_to(:usage_shuffle)
60
+ end
61
+
62
+ it 'respond with Response object' do
63
+ t.execute("shuffle on").should be_instance_of Response
64
+ end
65
+ end
66
+
67
+ describe 'connect' do
68
+ it 'respond to description_connect' do
69
+ t.should respond_to(:description_connect)
70
+ end
71
+
72
+ it 'respond to usage_connect' do
73
+ t.should respond_to(:usage_connect)
74
+ end
75
+
76
+ it 'respond with Response object' do
77
+ t.execute("connect drive").should be_instance_of Response
78
+ end
79
+ end
80
+
81
+ describe 'disconnect' do
82
+ it 'respond to description_disconnect' do
83
+ t.should respond_to(:description_disconnect)
84
+ end
85
+
86
+ it 'respond to usage_disconnect' do
87
+ t.should respond_to(:usage_disconnect)
88
+ end
89
+
90
+ it 'respond with Response object' do
91
+ t.execute("disconnect drive").should be_instance_of Response
92
+ end
93
+ end
94
+
95
+ describe 'ff' do
96
+ it 'respond to description_ff' do
97
+ t.should respond_to(:description_ff)
98
+ end
99
+
100
+ it 'respond to usage_ff' do
101
+ t.should respond_to(:usage_ff)
102
+ end
103
+
104
+ it 'respond with Response object' do
105
+ t.execute("ff").should be_instance_of Response
106
+ end
107
+ end
108
+
109
+ describe 'fb' do
110
+ it 'respond to description_fb' do
111
+ t.should respond_to(:description_fb)
112
+ end
113
+
114
+ it 'respond to usage_fb' do
115
+ t.should respond_to(:usage_fb)
116
+ end
117
+
118
+ it 'respond with Response object' do
119
+ t.execute("fb").should be_instance_of Response
120
+ end
121
+ end
122
+
123
+ describe 'repeat' do
124
+ it 'respond to description_repeat' do
125
+ t.should respond_to(:description_repeat)
126
+ end
127
+
128
+ it 'respond to usage_repeat' do
129
+ t.should respond_to(:usage_repeat)
130
+ end
131
+
132
+ it 'respond with Response object' do
133
+ t.execute("repeat").should be_instance_of Response
134
+ end
135
+ end
136
+ end