gitara 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data/.gitignore +4 -0
  2. data/Guardfile +8 -0
  3. data/README.markdown +214 -0
  4. data/bin/gitara +4 -0
  5. data/examples/aimee-man-wise-up/aimee-man-wise-up.ly +186 -0
  6. data/examples/aimee-man-wise-up/aimee-man-wise-up.midi +0 -0
  7. data/examples/aimee-man-wise-up/aimee-man-wise-up.pdf +0 -0
  8. data/examples/aimee-man-wise-up/aimee-man-wise-up.rb +170 -0
  9. data/examples/tab-with-bar.rb +7 -0
  10. data/examples/tab-with-reused-bar.rb +9 -0
  11. data/examples/tab.ly +117 -0
  12. data/examples/tab.rb +43 -0
  13. data/gitara.gemspec +15 -5
  14. data/lib/gitara.rb +38 -1
  15. data/lib/gitara/app.rb +29 -0
  16. data/lib/gitara/dsl.rb +35 -0
  17. data/lib/gitara/id_as_word.rb +8 -0
  18. data/lib/gitara/node/bar.rb +9 -0
  19. data/lib/gitara/node/bar/voiced_node.rb +13 -0
  20. data/lib/gitara/node/base.rb +82 -0
  21. data/lib/gitara/node/base/voiced_node.rb +22 -0
  22. data/lib/gitara/node/line.rb +6 -0
  23. data/lib/gitara/node/note_set.rb +6 -0
  24. data/lib/gitara/node/score.rb +6 -0
  25. data/lib/gitara/node/tab.rb +21 -0
  26. data/lib/gitara/pow/base.rb +11 -0
  27. data/lib/gitara/template/bar.erb +3 -0
  28. data/lib/gitara/template/line.erb +3 -0
  29. data/lib/gitara/template/score.erb +3 -0
  30. data/lib/gitara/template/tab.erb +91 -0
  31. data/lib/gitara/template/voice.erb +1 -0
  32. data/lib/gitara/version.rb +1 -1
  33. data/lib/gitara/voice.rb +20 -0
  34. data/spec/lib/gitara/app_spec.rb +12 -0
  35. data/spec/lib/gitara/dsl_spec.rb +173 -0
  36. data/spec/lib/gitara/node/bar/voiced_node_spec.rb +17 -0
  37. data/spec/lib/gitara/node/bar_spec.rb +22 -0
  38. data/spec/lib/gitara/node/base/voiced_node_spec.rb +35 -0
  39. data/spec/lib/gitara/node/base_spec.rb +201 -0
  40. data/spec/lib/gitara/node/note_set_spec.rb +4 -0
  41. data/spec/lib/gitara/node/tab_spec.rb +57 -0
  42. data/spec/lib/gitara/voice_spec.rb +31 -0
  43. data/spec/lib/gitara_spec.rb +23 -0
  44. data/spec/spec_helper.rb +27 -0
  45. metadata +217 -10
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'VoicedNode' do
4
+ describe "call_name" do
5
+ it "should be the call name of the voiced node's definition name" do
6
+ voiced_node = Node::Base::VoicedNode.new(:node => Node::Base.new(:name => 'Intro'), :voice => Voice.new(:id => 1))
7
+ voiced_node.call_name.should == '\vOneBaseIntro'
8
+ end
9
+ end
10
+
11
+ describe "definition_name" do
12
+ it "should include the voice's id, the node's class, and the node's name" do
13
+ voiced_node = Node::Base::VoicedNode.new(:node => Node::Base.new(:name => 'Intro'), :voice => Voice.new(:id => 1))
14
+ voiced_node.definition_name.should == 'vOneBaseIntro'
15
+ end
16
+ end
17
+
18
+ describe "children" do
19
+ it "should be voiced versions of the node's children" do
20
+ parent = Node::Base.new(:name => 'parent')
21
+ child = Node::Base.new(:name => 'child')
22
+ parent.add child
23
+
24
+ voice = Voice.new(:id => 1)
25
+
26
+ voiced_node = Node::Base::VoicedNode.new(:node => parent, :voice => voice)
27
+
28
+ children = voiced_node.children
29
+ children.should have(1).voiced_node
30
+ children[0].node.should == child
31
+ children[0].voice.should == voice
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,201 @@
1
+ require 'spec_helper'
2
+
3
+ describe Node::Base do
4
+ describe "children" do
5
+ it "should be its own children if they exist" do
6
+ child = Node::Base.new(:id => nil)
7
+
8
+ parent = Node::Base.new
9
+ parent.children = [child]
10
+ parent.children.should == [child]
11
+ end
12
+
13
+ it "should be the children of its definition if the node is not a definition" do
14
+ child = Node::NoteSet.new
15
+ definition_bar = Node::Bar.new(:name => 'Intro').tap {|bar|
16
+ bar.children = [child]
17
+ }
18
+
19
+ call_bar = Node::Bar.new(:name => 'Intro')
20
+
21
+ tab = Node::Tab.new.tap {|tab|
22
+ tab.children = [definition_bar, call_bar]
23
+ }
24
+
25
+ call_bar.children.should == [child]
26
+ end
27
+ end
28
+
29
+ describe "children=(other)" do
30
+ it "should set the ids and parents of the children" do
31
+ parent = Node::Base.new
32
+ parent.children = [Node::Base.new(:id => nil)]
33
+ parent.children[0].id.should == 1
34
+ parent.children[0].parent.should == parent
35
+ end
36
+ end
37
+
38
+ describe "add(child)" do
39
+ it "should set the id and parent of the child" do
40
+ parent = Node::Base.new
41
+
42
+ parent.add Node::Base.new(:id => nil)
43
+ parent.children[0].id.should == 1
44
+ parent.children[0].parent.should == parent
45
+
46
+ parent.add Node::Base.new(:id => nil)
47
+ parent.children[1].id.should == 2
48
+ parent.children[1].parent.should == parent
49
+ end
50
+ end
51
+
52
+ describe "definition" do
53
+ it "should be the definition node which matches this node's name" do
54
+ definition_bar = Node::Bar.new(:name => 'Intro').tap {|bar|
55
+ bar.children = [
56
+ Node::NoteSet.new,
57
+ Node::NoteSet.new
58
+ ]
59
+ }
60
+
61
+ call_bar = Node::Bar.new(:name => 'Intro')
62
+
63
+ tab = Node::Tab.new.tap {|tab|
64
+ tab.children = [definition_bar, call_bar]
65
+ }
66
+
67
+ call_bar.send(:definition).should == definition_bar
68
+ end
69
+ end
70
+
71
+ describe "definition?" do
72
+ it "should be true if this node has children" do
73
+ definition_bar = Node::Bar.new(:name => 'Intro').tap {|bar|
74
+ bar.add Node::NoteSet.new
75
+ bar.add Node::NoteSet.new
76
+ }
77
+ definition_bar.should be_definition
78
+ end
79
+
80
+ it "should be false if this node does not have children" do
81
+ call_bar = Node::Bar.new(:name => 'Intro')
82
+ call_bar.should_not be_definition
83
+ end
84
+ end
85
+
86
+ describe "value" do
87
+ it "should convert slashes to backslashes" do
88
+ node = Node::Base.new(:value => %q|notes "<g'/1>8 <a/3>8 <g'/1>8 <a/3>16 <g'/1>8 <g/3>16 <e'/1>4 <g/3>8"|)
89
+ node.value.should == %q|notes "<g'\1>8 <a\3>8 <g'\1>8 <a\3>16 <g'\1>8 <g\3>16 <e'\1>4 <g\3>8"|
90
+ end
91
+ end
92
+
93
+ describe "voiced_as(arg)" do
94
+ it "should return the voiced version of the node, if arg is a voice" do
95
+ node = Node::Base.new
96
+ voice = Voice.new
97
+
98
+ node_voice_pair = node.voiced_as(voice)
99
+ node_voice_pair.should be_a(Node::Base::VoicedNode)
100
+ node_voice_pair.node.should == node
101
+ node_voice_pair.voice.should == voice
102
+ end
103
+
104
+ it "should return the voiced versions of the node, if arg are voices" do
105
+ node = Node::Base.new
106
+ voices = [Voice.new, Voice.new]
107
+
108
+ node_voice_pairs = node.voiced_as(voices)
109
+ node_voice_pairs.size.should == 2
110
+
111
+ node_voice_pairs.each do |pair|
112
+ pair.should be_a(Node::Base::VoicedNode)
113
+ pair.node.should == node
114
+ end
115
+
116
+ node_voice_pairs[0].voice.should == voices[0]
117
+ node_voice_pairs[1].voice.should == voices[1]
118
+ end
119
+ end
120
+
121
+ describe "definitions(klass)" do
122
+ it "should be itself if it is an instance of the klass" do
123
+ node = Node::Bar.new
124
+ node.add Node::NoteSet.new
125
+ node.should be_definition
126
+
127
+ node.definitions(Node::Bar).should == [node]
128
+ end
129
+
130
+ it "should include descendants which are instances of klass" do
131
+ node = Node::Base.new.tap do |base|
132
+ base.add(
133
+ Node::Bar.new.tap do |bar|
134
+ bar.add Node::NoteSet.new
135
+ end
136
+ )
137
+
138
+ base.add(
139
+ Node::Bar.new.tap do |bar|
140
+ bar.add Node::NoteSet.new
141
+ end
142
+ )
143
+ end
144
+
145
+ node.definitions(Node::Bar).should have(2).bars
146
+ end
147
+ end
148
+
149
+ describe "root" do
150
+ it "should be itself if it has no parent" do
151
+ node = Node::Base.new
152
+ node.root.should == node
153
+ end
154
+
155
+ it "should be the ancentor of the node which has no parent of its own" do
156
+ note_set = Node::NoteSet.new
157
+
158
+ bar = Node::Bar.new
159
+ bar.add note_set
160
+
161
+ root = Node::Base.new
162
+ root.add bar
163
+
164
+ note_set.root.should == root
165
+ end
166
+ end
167
+
168
+ describe "name" do
169
+ it "should be the given name, if available" do
170
+ Node::Bar.new(:name => :Intro).name.should == :Intro
171
+ end
172
+
173
+ it "should be based on the parent's name the node's class and id, if not available" do
174
+ child = Node::NoteSet.new
175
+ bar = Node::Bar.new(:name => :Intro).tap do |bar|
176
+ bar.add child
177
+ end
178
+
179
+ child.name.should == "IntroNoteSetOne"
180
+ end
181
+
182
+ it "should be based on the node's class and id, if there's no parent and the there's no given name" do
183
+ child = Node::NoteSet.new
184
+ child.name.should == "NoteSetOne"
185
+ end
186
+ end
187
+
188
+ describe "id" do
189
+ it "should be 1 by default" do
190
+ child = Node::NoteSet.new
191
+ child.id.should == 1
192
+ end
193
+ end
194
+
195
+ describe "id_as_word" do
196
+ it "should camelize if necessary" do
197
+ node = Node::Base.new(:id => 24)
198
+ node.id_as_word.should == "TwentyFour"
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Node::NoteSet do
4
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Tab' do
4
+ describe "voices" do
5
+ it "should create a <max_number_of_voices> voices" do
6
+ tab = Node::Tab.new.tap {|tab|
7
+ tab.children = [
8
+ Node::Bar.new.tap {|bar|
9
+ bar.children = [
10
+ Node::NoteSet.new,
11
+ Node::NoteSet.new
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ voices = tab.voices
17
+ voices.should have(2).values
18
+ voices[0].id.should == 1
19
+ voices[1].id.should == 2
20
+ voices.map(&:parent).uniq.should == [tab]
21
+ end
22
+ end
23
+
24
+ describe "max_number_of_voices" do
25
+ it "should be the max number of note_sets in a bar" do
26
+ tab = Node::Tab.new.tap {|tab|
27
+ tab.children = [
28
+ Node::Bar.new.tap {|bar|
29
+ bar.children = [
30
+ Node::NoteSet.new,
31
+ Node::NoteSet.new
32
+ ]
33
+ }
34
+ ]
35
+ }
36
+ tab.max_number_of_voices.should == 2
37
+ end
38
+ end
39
+
40
+ describe "playable_child" do
41
+ it "should be the last child of the tab" do
42
+ tab = Node::Tab.new.tap {|tab|
43
+ tab.children = [
44
+ Node::Bar.new(:name => 'Intro').tap {|bar|
45
+ bar.children = [
46
+ Node::NoteSet.new,
47
+ Node::NoteSet.new
48
+ ]
49
+ },
50
+ Node::Bar.new(:name => 'Intro')
51
+ ]
52
+ }
53
+
54
+ tab.playable_child.id.should == 2
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Voice do
4
+ describe "call_name" do
5
+ it "should be the variable name of the node when called inside lilypond" do
6
+ voice = Voice.new(:id => 1)
7
+ voice.call_name.should == '\vOne'
8
+ end
9
+ end
10
+
11
+ describe "definition_name" do
12
+ it "should be the name of the voice in a lilypond variable definition statement" do
13
+ voice = Voice.new(:id => 1)
14
+ voice.definition_name.should == "vOne"
15
+ end
16
+ end
17
+
18
+ describe "id_as_word" do
19
+ it "should convert the id to word" do
20
+ node = Voice.new(:id => 1)
21
+ node.id_as_word.should == "One"
22
+ end
23
+ end
24
+
25
+ describe "stem_type" do
26
+ it "should be the lilypond stem type for the voice" do
27
+ voice = Voice.new(:id => 1)
28
+ voice.stem_type.should == '\voiceOne'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Gitara do
4
+ describe "tab" do
5
+ it "should create a tab instance if it does not exist" do
6
+ Gitara.tab.should be_nil
7
+ Gitara.define
8
+ Gitara.tab.should be_a(Node::Tab)
9
+ end
10
+
11
+ it "should instance evaluate a block if given" do
12
+ tab = Gitara.define do
13
+ bar "Intro" do
14
+ notes "c"
15
+ end
16
+ end
17
+
18
+ bars = tab.definitions(Node::Bar)
19
+ bars.size.should == 1
20
+ bars[0].name.should == 'Intro'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ require 'gitara'
2
+ require 'parslet/convenience'
3
+ require 'parslet/rig/rspec'
4
+ require 'pry'
5
+
6
+ RSpec.configure do |config|
7
+ include Gitara
8
+
9
+ config.before :each do
10
+ test_tmp_dir.delete! if test_tmp_dir.exists?
11
+ Gitara.instance_variable_set :@tab, nil
12
+ end
13
+
14
+ def debug?
15
+ $debug.tap do
16
+ $debug = false
17
+ end
18
+ end
19
+
20
+ def debug!
21
+ $debug = true
22
+ end
23
+
24
+ def test_tmp_dir
25
+ Pow('tmp/test')
26
+ end
27
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitara
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 0
9
8
  - 1
10
- version: 0.0.1
9
+ - 0
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - George Mendoza
@@ -15,14 +15,181 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-12-08 00:00:00 Z
19
- dependencies: []
20
-
21
- description: This will be a work in progress. Ultimately the goal is to have a good basic tablature DSL that is easily convertible to lilypond.
18
+ date: 2012-02-17 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: active_support
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: erubis
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: linguistics
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :runtime
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ name: parslet
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ type: :runtime
75
+ version_requirements: *id004
76
+ - !ruby/object:Gem::Dependency
77
+ name: pow
78
+ prerelease: false
79
+ requirement: &id005 !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ type: :runtime
89
+ version_requirements: *id005
90
+ - !ruby/object:Gem::Dependency
91
+ name: thor
92
+ prerelease: false
93
+ requirement: &id006 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ type: :runtime
103
+ version_requirements: *id006
104
+ - !ruby/object:Gem::Dependency
105
+ name: valuable
106
+ prerelease: false
107
+ requirement: &id007 !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ type: :runtime
117
+ version_requirements: *id007
118
+ - !ruby/object:Gem::Dependency
119
+ name: guard
120
+ prerelease: false
121
+ requirement: &id008 !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ type: :development
131
+ version_requirements: *id008
132
+ - !ruby/object:Gem::Dependency
133
+ name: guard-rspec
134
+ prerelease: false
135
+ requirement: &id009 !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ hash: 3
141
+ segments:
142
+ - 0
143
+ version: "0"
144
+ type: :development
145
+ version_requirements: *id009
146
+ - !ruby/object:Gem::Dependency
147
+ name: pry
148
+ prerelease: false
149
+ requirement: &id010 !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ hash: 3
155
+ segments:
156
+ - 0
157
+ version: "0"
158
+ type: :development
159
+ version_requirements: *id010
160
+ - !ruby/object:Gem::Dependency
161
+ name: rb-inotify
162
+ prerelease: false
163
+ requirement: &id011 !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ hash: 3
169
+ segments:
170
+ - 0
171
+ version: "0"
172
+ type: :development
173
+ version_requirements: *id011
174
+ - !ruby/object:Gem::Dependency
175
+ name: rspec
176
+ prerelease: false
177
+ requirement: &id012 !ruby/object:Gem::Requirement
178
+ none: false
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ hash: 3
183
+ segments:
184
+ - 0
185
+ version: "0"
186
+ type: :development
187
+ version_requirements: *id012
188
+ description: A Ruby DSL for generating Lilypond guitar tablatures.
22
189
  email:
23
190
  - gsmendoza@gmail.com
24
- executables: []
25
-
191
+ executables:
192
+ - gitara
26
193
  extensions: []
27
194
 
28
195
  extra_rdoc_files: []
@@ -30,10 +197,50 @@ extra_rdoc_files: []
30
197
  files:
31
198
  - .gitignore
32
199
  - Gemfile
200
+ - Guardfile
201
+ - README.markdown
33
202
  - Rakefile
203
+ - bin/gitara
204
+ - examples/aimee-man-wise-up/aimee-man-wise-up.ly
205
+ - examples/aimee-man-wise-up/aimee-man-wise-up.midi
206
+ - examples/aimee-man-wise-up/aimee-man-wise-up.pdf
207
+ - examples/aimee-man-wise-up/aimee-man-wise-up.rb
208
+ - examples/tab-with-bar.rb
209
+ - examples/tab-with-reused-bar.rb
210
+ - examples/tab.ly
211
+ - examples/tab.rb
34
212
  - gitara.gemspec
35
213
  - lib/gitara.rb
214
+ - lib/gitara/app.rb
215
+ - lib/gitara/dsl.rb
216
+ - lib/gitara/id_as_word.rb
217
+ - lib/gitara/node/bar.rb
218
+ - lib/gitara/node/bar/voiced_node.rb
219
+ - lib/gitara/node/base.rb
220
+ - lib/gitara/node/base/voiced_node.rb
221
+ - lib/gitara/node/line.rb
222
+ - lib/gitara/node/note_set.rb
223
+ - lib/gitara/node/score.rb
224
+ - lib/gitara/node/tab.rb
225
+ - lib/gitara/pow/base.rb
226
+ - lib/gitara/template/bar.erb
227
+ - lib/gitara/template/line.erb
228
+ - lib/gitara/template/score.erb
229
+ - lib/gitara/template/tab.erb
230
+ - lib/gitara/template/voice.erb
36
231
  - lib/gitara/version.rb
232
+ - lib/gitara/voice.rb
233
+ - spec/lib/gitara/app_spec.rb
234
+ - spec/lib/gitara/dsl_spec.rb
235
+ - spec/lib/gitara/node/bar/voiced_node_spec.rb
236
+ - spec/lib/gitara/node/bar_spec.rb
237
+ - spec/lib/gitara/node/base/voiced_node_spec.rb
238
+ - spec/lib/gitara/node/base_spec.rb
239
+ - spec/lib/gitara/node/note_set_spec.rb
240
+ - spec/lib/gitara/node/tab_spec.rb
241
+ - spec/lib/gitara/voice_spec.rb
242
+ - spec/lib/gitara_spec.rb
243
+ - spec/spec_helper.rb
37
244
  homepage: ""
38
245
  licenses: []
39
246
 
@@ -66,6 +273,6 @@ rubyforge_project: gitara
66
273
  rubygems_version: 1.8.10
67
274
  signing_key:
68
275
  specification_version: 3
69
- summary: A Ruby DSL for tablatures
276
+ summary: Ruby + Lilypond = pretty guitar tablatures.
70
277
  test_files: []
71
278