musicality 0.10.1 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +13 -3
- data/README.md +8 -8
- data/bin/auditions +241 -0
- data/bin/collidify +4 -2
- data/bin/musicality +13 -2
- data/examples/composition/auto_counterpoint.rb +4 -4
- data/examples/composition/part_generator.rb +6 -6
- data/examples/composition/scale_exercise.rb +5 -5
- data/examples/notation/scores.rb +2 -2
- data/examples/notation/twinkle.rb +6 -6
- data/examples/notation/twinkle.score +3 -3
- data/lib/musicality.rb +6 -4
- data/lib/musicality/composition/dsl/part_methods.rb +12 -0
- data/lib/musicality/composition/dsl/score_dsl.rb +4 -4
- data/lib/musicality/composition/dsl/score_methods.rb +8 -2
- data/lib/musicality/notation/model/audition.rb +16 -0
- data/lib/musicality/notation/model/score.rb +31 -30
- data/lib/musicality/performance/supercollider/conductor.rb +2 -2
- data/lib/musicality/performance/supercollider/synthdefs.rb +30 -29
- data/lib/musicality/project/auditions_task.rb +28 -0
- data/lib/musicality/project/create_tasks.rb +15 -9
- data/lib/musicality/project/file_cleaner.rb +22 -5
- data/lib/musicality/project/file_raker.rb +29 -21
- data/lib/musicality/project/project.rb +218 -32
- data/lib/musicality/version.rb +1 -1
- data/musicality.gemspec +6 -4
- data/spec/composition/dsl/part_methods_spec.rb +24 -0
- data/spec/notation/conversion/score_conversion_spec.rb +2 -1
- data/spec/notation/conversion/score_converter_spec.rb +23 -23
- data/spec/notation/model/score_spec.rb +66 -46
- data/spec/performance/conversion/score_collator_spec.rb +29 -29
- data/spec/performance/midi/score_sequencing_spec.rb +5 -5
- metadata +10 -8
- data/lib/musicality/project/load_config.rb +0 -58
@@ -0,0 +1,28 @@
|
|
1
|
+
module Musicality
|
2
|
+
module Tasks
|
3
|
+
|
4
|
+
class Auditions < Rake::TaskLib
|
5
|
+
attr_reader :auditions_dirs
|
6
|
+
|
7
|
+
TEMPO_SAMPLE_RATE = 200
|
8
|
+
AUDITIONS_DIR = "auditions"
|
9
|
+
AUDITIONS_EXT = ".auditions"
|
10
|
+
|
11
|
+
def initialize yaml_filelist, audio_format = nil
|
12
|
+
@auditions_dirs = yaml_filelist.pathmap("%d/#{AUDITIONS_DIR}")
|
13
|
+
@auditions_dirs.each { |auditions_dir| directory auditions_dir }
|
14
|
+
|
15
|
+
format_flag = audio_format.nil? ? "" : "--format=#{audio_format}"
|
16
|
+
subtask = audio_format.nil? ? "" : ":#{audio_format}"
|
17
|
+
|
18
|
+
task "auditions#{subtask}" => yaml_filelist + @auditions_dirs do
|
19
|
+
yaml_filelist.each_with_index do |yaml_fname,i|
|
20
|
+
auditions_dir = @auditions_dirs[i]
|
21
|
+
`auditions #{yaml_fname} --outdir="#{auditions_dir}" #{format_flag}`
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -1,31 +1,37 @@
|
|
1
1
|
module Musicality
|
2
|
+
class Project
|
2
3
|
|
3
|
-
class Project
|
4
4
|
def self.create_tasks config
|
5
|
-
score_files = Rake::FileList[
|
6
|
-
|
5
|
+
score_files = Rake::FileList[File.join(SCORES_DIR,"*"+SCORE_EXT)]
|
6
|
+
|
7
7
|
yaml_task = Tasks::FileRaker::YAML.new(score_files)
|
8
|
-
|
8
|
+
|
9
9
|
tempo_sample_rate = config[:tempo_sample_rate]
|
10
10
|
lilypond_task = Tasks::FileRaker::LilyPond.new(yaml_task.files)
|
11
11
|
midi_task = Tasks::FileRaker::MIDI.new(yaml_task.files, tempo_sample_rate)
|
12
12
|
supercollider_task = Tasks::FileRaker::SuperCollider.new(yaml_task.files, tempo_sample_rate)
|
13
|
-
|
13
|
+
|
14
14
|
sample_rate, sample_format = config[:audio_sample_rate], config[:audio_sample_format]
|
15
15
|
wav_task = Tasks::FileRaker::Audio.new(supercollider_task.files, :wav, sample_rate, sample_format)
|
16
16
|
aiff_task = Tasks::FileRaker::Audio.new(supercollider_task.files, :aiff, sample_rate, sample_format)
|
17
17
|
flac_task = Tasks::FileRaker::Audio.new(supercollider_task.files, :flac, sample_rate, sample_format)
|
18
|
-
|
18
|
+
|
19
19
|
pdf_task = Tasks::FileRaker::Visual.new(lilypond_task.files, :pdf)
|
20
20
|
png_task = Tasks::FileRaker::Visual.new(lilypond_task.files, :png)
|
21
21
|
ps_task = Tasks::FileRaker::Visual.new(lilypond_task.files, :ps)
|
22
22
|
|
23
|
+
auditions_default_task = Tasks::Auditions.new yaml_task.files
|
24
|
+
auditions_flac_task = Tasks::Auditions.new yaml_task.files, "flac"
|
25
|
+
auditions_wav_task = Tasks::Auditions.new yaml_task.files, "wav"
|
26
|
+
auditions_aiff_task = Tasks::Auditions.new yaml_task.files, "aiff"
|
27
|
+
|
23
28
|
outfiles = (
|
24
|
-
yaml_task.files + lilypond_task.files + midi_task.files + supercollider_task.files +
|
25
|
-
pdf_task.files + png_task.files + ps_task.files +
|
29
|
+
yaml_task.files + lilypond_task.files + midi_task.files + supercollider_task.files +
|
30
|
+
pdf_task.files + png_task.files + ps_task.files +
|
26
31
|
wav_task.files + aiff_task.files + flac_task.files
|
27
32
|
).select {|fname| File.exists? fname }
|
28
|
-
|
33
|
+
outdirs = auditions_default_task.auditions_dirs + yaml_task.subdirs + [Project::OUT_DIR]
|
34
|
+
clean_task = Tasks::FileCleaner.new(outfiles, outdirs)
|
29
35
|
end
|
30
36
|
end
|
31
37
|
|
@@ -1,19 +1,36 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
1
3
|
module Musicality
|
2
4
|
module Tasks
|
3
5
|
|
4
6
|
class FileCleaner < Rake::TaskLib
|
5
|
-
def initialize
|
7
|
+
def initialize files, dirs
|
6
8
|
task :clean do
|
7
|
-
if
|
8
|
-
puts "Deleting
|
9
|
-
|
9
|
+
if files.any?
|
10
|
+
puts "Deleting files:"
|
11
|
+
files.each do |fname|
|
10
12
|
puts " " + fname
|
11
13
|
File.delete fname
|
12
14
|
end
|
13
15
|
end
|
16
|
+
|
17
|
+
existing_dirs = dirs.select {|dir| Dir.exist?(dir) }
|
18
|
+
|
19
|
+
if existing_dirs.any?
|
20
|
+
puts "Deleting dirs:"
|
21
|
+
existing_dirs.each do |dirname|
|
22
|
+
puts " " + dirname
|
23
|
+
begin
|
24
|
+
FileUtils::rm Dir.glob(File.join(dirname, "*"))
|
25
|
+
FileUtils::rmdir dirname
|
26
|
+
rescue => e
|
27
|
+
puts "Error while trying to delete #{dirname}: #{e}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
14
31
|
end
|
15
32
|
end
|
16
33
|
end
|
17
34
|
|
18
35
|
end
|
19
|
-
end
|
36
|
+
end
|
@@ -2,26 +2,34 @@ module Musicality
|
|
2
2
|
module Tasks
|
3
3
|
|
4
4
|
class FileRaker < Rake::TaskLib
|
5
|
-
attr_reader :files, :task_name, :file_ext
|
6
|
-
|
5
|
+
attr_reader :files, :task_name, :file_ext, :subdirs
|
6
|
+
|
7
7
|
def initialize parent_filelist, task_name, file_ext, &rule_block
|
8
8
|
raise ArgumentError, "parent filelist is empty" if parent_filelist.empty?
|
9
9
|
raise ArgumentError, "no rule block given" unless block_given?
|
10
|
-
|
11
|
-
@task_name, @file_ext = task_name, file_ext
|
12
|
-
@files = parent_filelist.ext(file_ext)
|
13
|
-
|
14
|
-
task task_name => @files
|
15
|
-
|
10
|
+
|
16
11
|
parent_exts = parent_filelist.map {|str| File.extname(str) }.uniq
|
17
12
|
raise ArgumentError, "multiple file extensions in parent filelist: #{parent_filelist}" unless parent_exts.one?
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
|
14
|
+
@task_name, @file_ext = task_name, file_ext
|
15
|
+
@subdirs = parent_filelist.pathmap("#{Project::OUT_DIR}/%n")
|
16
|
+
@files = parent_filelist.pathmap("#{Project::OUT_DIR}/%n/%n#{file_ext}")
|
17
|
+
|
18
|
+
directory Project::OUT_DIR
|
19
|
+
@subdirs.each { |subdir| directory subdir }
|
20
|
+
task task_name => [Project::OUT_DIR] + @subdirs + @files
|
21
|
+
|
22
|
+
find_parent_file = lambda do |f|
|
23
|
+
parent_filelist.detect do |f2|
|
24
|
+
File.basename(f2.ext("")) == File.basename(f.ext(""))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
rule file_ext => find_parent_file do |t|
|
21
29
|
rule_block.call(t)
|
22
30
|
end
|
23
31
|
end
|
24
|
-
|
32
|
+
|
25
33
|
class YAML < FileRaker
|
26
34
|
def initialize score_files
|
27
35
|
super(score_files, :yaml, ".yml") do |t|
|
@@ -31,7 +39,7 @@ class FileRaker < Rake::TaskLib
|
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
34
|
-
|
42
|
+
|
35
43
|
class LilyPond < FileRaker
|
36
44
|
def initialize yaml_files
|
37
45
|
super(yaml_files, :lilypond, ".ly") do |t|
|
@@ -39,7 +47,7 @@ class FileRaker < Rake::TaskLib
|
|
39
47
|
end
|
40
48
|
end
|
41
49
|
end
|
42
|
-
|
50
|
+
|
43
51
|
class MIDI < FileRaker
|
44
52
|
def initialize yaml_files, tempo_sample_rate
|
45
53
|
super(yaml_files, :midi, ".mid") do |t|
|
@@ -47,7 +55,7 @@ class FileRaker < Rake::TaskLib
|
|
47
55
|
end
|
48
56
|
end
|
49
57
|
end
|
50
|
-
|
58
|
+
|
51
59
|
class SuperCollider < FileRaker
|
52
60
|
def initialize yaml_files, tempo_sample_rate
|
53
61
|
super(yaml_files, :supercollider, ".osc") do |t|
|
@@ -55,7 +63,7 @@ class FileRaker < Rake::TaskLib
|
|
55
63
|
end
|
56
64
|
end
|
57
65
|
end
|
58
|
-
|
66
|
+
|
59
67
|
class Audio < FileRaker
|
60
68
|
def check_sample_format audio_file_type, sample_format
|
61
69
|
combination_okay = case audio_file_type
|
@@ -64,7 +72,7 @@ class FileRaker < Rake::TaskLib
|
|
64
72
|
when :flac
|
65
73
|
!["int32","float","mulaw","alaw"].include?(sample_format)
|
66
74
|
else
|
67
|
-
true
|
75
|
+
true
|
68
76
|
end
|
69
77
|
|
70
78
|
unless combination_okay
|
@@ -78,7 +86,7 @@ class FileRaker < Rake::TaskLib
|
|
78
86
|
|
79
87
|
osc_fpath = t.sources[0]
|
80
88
|
out_fpath = File.join(File.dirname(osc_fpath), File.basename(osc_fpath, File.extname(osc_fpath)) + ".#{audio_file_type}")
|
81
|
-
|
89
|
+
|
82
90
|
cmd_line = "scsynth -N \"#{osc_fpath}\" _ \"#{out_fpath}\" #{sample_rate} #{audio_file_type} #{sample_format}"
|
83
91
|
IO.popen(cmd_line) do |pipe|
|
84
92
|
while response = pipe.gets
|
@@ -93,13 +101,13 @@ class FileRaker < Rake::TaskLib
|
|
93
101
|
end
|
94
102
|
end
|
95
103
|
end
|
96
|
-
|
104
|
+
|
97
105
|
class Visual < FileRaker
|
98
106
|
def initialize lilypond_files, visual_file_type
|
99
107
|
super(lilypond_files, visual_file_type, ".#{visual_file_type}") do |t|
|
100
108
|
ly_fpath = t.sources[0]
|
101
109
|
out_dir = File.dirname(ly_fpath)
|
102
|
-
|
110
|
+
|
103
111
|
sh "lilypond --output=\"#{out_dir}\" \"#{ly_fpath}\" --#{visual_file_type}"
|
104
112
|
end
|
105
113
|
end
|
@@ -107,4 +115,4 @@ class FileRaker < Rake::TaskLib
|
|
107
115
|
end
|
108
116
|
|
109
117
|
end
|
110
|
-
end
|
118
|
+
end
|
@@ -1,25 +1,71 @@
|
|
1
1
|
module Musicality
|
2
2
|
|
3
3
|
class Project
|
4
|
+
CONFIG_FILE_NAME = "config.yml"
|
5
|
+
SCORES_DIR = "scores"
|
6
|
+
SCORE_EXT = ".score"
|
7
|
+
OUT_DIR = "output"
|
8
|
+
SAMPLE_FORMATS = ["int8", "int16", "int24", "int32", "mulaw", "alaw", "float"]
|
9
|
+
DEFAULT_CONFIG = {
|
10
|
+
:tempo_sample_rate => 200,
|
11
|
+
:audio_sample_rate => 44100,
|
12
|
+
:audio_sample_format => "int16"
|
13
|
+
}
|
14
|
+
GEM_MUSICALITY = "gem 'musicality', '~> #{VERSION}'"
|
4
15
|
USEFUL_MODULES = ['Musicality','Pitches','Meters','Keys','Articulations','Dynamics']
|
5
16
|
|
6
|
-
|
17
|
+
class ConfigError < RuntimeError
|
18
|
+
end
|
19
|
+
|
7
20
|
def initialize dest_dir
|
8
|
-
|
21
|
+
Project.create_project_dir_if_needed(dest_dir)
|
22
|
+
Project.create_scores_dir_if_needed(dest_dir)
|
23
|
+
Project.update(dest_dir)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.update dest_dir
|
27
|
+
if File.exists?(gemfile_path(dest_dir))
|
28
|
+
puts "Updating Gemfile"
|
29
|
+
update_gemfile(dest_dir)
|
30
|
+
else
|
31
|
+
puts "Creating Gemfile"
|
32
|
+
create_gemfile(dest_dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
if File.exists?(rakefile_path(dest_dir))
|
36
|
+
puts "Updating Rakefile"
|
37
|
+
update_rakefile(dest_dir)
|
38
|
+
else
|
39
|
+
puts "Creating Rakefile"
|
40
|
+
create_rakefile(dest_dir)
|
41
|
+
end
|
9
42
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
43
|
+
if File.exists?(config_path(dest_dir))
|
44
|
+
puts "Updating config.yml"
|
45
|
+
update_config(dest_dir)
|
46
|
+
else
|
47
|
+
puts "Creating config.yml"
|
48
|
+
create_config(dest_dir)
|
49
|
+
end
|
15
50
|
end
|
16
51
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
52
|
+
def self.config_path(dest_dir)
|
53
|
+
File.join(dest_dir,"config.yml")
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.gemfile_path(dest_dir)
|
57
|
+
File.join(dest_dir,"Gemfile")
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.rakefile_path(dest_dir)
|
61
|
+
File.join(dest_dir,"Rakefile")
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.create_project_dir_if_needed(dest_dir)
|
65
|
+
if Dir.exists?(dest_dir)
|
66
|
+
puts "Project directory already exists"
|
22
67
|
else
|
68
|
+
puts "Creating project directory #{dest_dir}"
|
23
69
|
Dir.mkdir(dest_dir)
|
24
70
|
unless Dir.exists?(dest_dir)
|
25
71
|
raise "directory #{dest_dir} could not be created"
|
@@ -27,38 +73,178 @@ class Project
|
|
27
73
|
end
|
28
74
|
end
|
29
75
|
|
30
|
-
def
|
76
|
+
def self.create_scores_dir_if_needed(dest_dir, scores_dir = Project::SCORES_DIR)
|
77
|
+
scores_dir = File.join(dest_dir, scores_dir)
|
78
|
+
if Dir.exists? scores_dir
|
79
|
+
puts "Scores directory already exists"
|
80
|
+
else
|
81
|
+
puts "Creating scores directory #{scores_dir}"
|
82
|
+
Dir.mkdir(scores_dir)
|
83
|
+
unless Dir.exists?(scores_dir)
|
84
|
+
raise "directory #{scores_dir} could not be created"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Gemfile
|
91
|
+
#
|
92
|
+
|
93
|
+
def self.create_gemfile(dest_dir)
|
31
94
|
gemfile_path = File.join(dest_dir,"Gemfile")
|
32
|
-
File.
|
33
|
-
|
34
|
-
|
95
|
+
File.new(gemfile_path(dest_dir),"w")
|
96
|
+
update_gemfile(dest_dir)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.update_gemfile(dest_dir)
|
100
|
+
pre_lines = []
|
101
|
+
lines = File.readlines(gemfile_path(dest_dir)).map {|l| l.chomp }
|
102
|
+
|
103
|
+
if line = lines.find {|x| x =~ /source/ }
|
104
|
+
delete_empty_lines_around lines, line
|
105
|
+
pre_lines.push lines.delete(line)
|
106
|
+
else
|
107
|
+
pre_lines.push("source :rubygems")
|
108
|
+
end
|
109
|
+
|
110
|
+
if line = lines.find {|x| x =~ /gem/ && x =~ /musicality/ }
|
111
|
+
delete_empty_lines_around lines, line
|
112
|
+
lines.delete(line)
|
113
|
+
end
|
114
|
+
pre_lines.push GEM_MUSICALITY
|
115
|
+
|
116
|
+
File.open(gemfile_path(dest_dir),"w") do |f|
|
117
|
+
f.puts pre_lines
|
118
|
+
if lines.any?
|
119
|
+
f.puts [""] + lines
|
120
|
+
end
|
35
121
|
end
|
36
122
|
end
|
37
123
|
|
38
|
-
|
124
|
+
#
|
125
|
+
# Rakefile
|
126
|
+
#
|
127
|
+
|
128
|
+
def self.create_rakefile(dest_dir)
|
39
129
|
rakefile_path = File.join(dest_dir,"Rakefile")
|
40
|
-
File.
|
41
|
-
|
42
|
-
|
43
|
-
|
130
|
+
File.new(rakefile_path(dest_dir),"w")
|
131
|
+
update_rakefile(dest_dir)
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.update_rakefile(dest_dir)
|
135
|
+
pre_lines = []
|
136
|
+
lines = File.readlines(rakefile_path(dest_dir)).map {|l| l.chomp }
|
137
|
+
|
138
|
+
if line = lines.find {|x| x =~ /^[\s]*require[\s]+[\'\"]musicality[\'\"]/}
|
139
|
+
delete_empty_lines_around lines, line
|
140
|
+
pre_lines.push lines.delete(line)
|
141
|
+
else
|
142
|
+
pre_lines.push "require 'musicality'"
|
143
|
+
end
|
144
|
+
pre_lines.push ""
|
145
|
+
|
146
|
+
USEFUL_MODULES.each do |module_name|
|
147
|
+
if line = lines.find {|x| x =~ /^[\s]*include[\s]+#{module_name}/}
|
148
|
+
delete_empty_lines_around lines, line
|
149
|
+
pre_lines.push lines.delete(line)
|
150
|
+
else
|
151
|
+
pre_lines.push "include #{module_name}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
pre_lines.push ""
|
156
|
+
if line = lines.find {|x| x =~ /Project\.load_config/ }
|
157
|
+
delete_empty_lines_around lines, line
|
158
|
+
pre_lines.push lines.delete(line)
|
159
|
+
else
|
160
|
+
pre_lines.push "config = Project.load_config(File.dirname(__FILE__))"
|
161
|
+
end
|
162
|
+
|
163
|
+
if line = lines.find {|x| x =~ /Project\.create_tasks/ }
|
164
|
+
delete_empty_lines_around lines, line
|
165
|
+
pre_lines.push lines.delete(line)
|
166
|
+
else
|
167
|
+
pre_lines.push "Project.create_tasks(config)"
|
168
|
+
end
|
169
|
+
|
170
|
+
File.open(rakefile_path(dest_dir),"w") do |f|
|
171
|
+
f.puts pre_lines
|
172
|
+
if lines.any?
|
173
|
+
f.puts [""] + lines
|
44
174
|
end
|
45
|
-
f.puts
|
46
|
-
f.puts("config = Project.load_config(File.dirname(__FILE__))")
|
47
|
-
f.puts("Project.create_tasks(config)")
|
48
175
|
end
|
49
176
|
end
|
50
177
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
178
|
+
#
|
179
|
+
# config.yml
|
180
|
+
#
|
181
|
+
|
182
|
+
def self.check_config config
|
183
|
+
config.each do |k,v|
|
184
|
+
case k
|
185
|
+
when :audio_sample_format
|
186
|
+
raise ConfigError, "#{k} => #{v} is not allowed" unless SAMPLE_FORMATS.include?(v)
|
187
|
+
when :tempo_sample_rate, :audio_sample_rate
|
188
|
+
raise ConfigError, "#{k} => #{v} is not positive" unless v > 0
|
189
|
+
end
|
55
190
|
end
|
56
191
|
end
|
57
192
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
193
|
+
def self.load_config project_root_dir
|
194
|
+
globabl_config_path = File.join(project_root_dir,CONFIG_FILE_NAME)
|
195
|
+
|
196
|
+
config = if File.exists? globabl_config_path
|
197
|
+
global_config = YAML.load(File.read(globabl_config_path))
|
198
|
+
DEFAULT_CONFIG.merge global_config
|
199
|
+
else
|
200
|
+
DEFAULT_CONFIG
|
201
|
+
end
|
202
|
+
|
203
|
+
# overrides from ENV
|
204
|
+
config.keys.each do |k|
|
205
|
+
k_str = k.to_s
|
206
|
+
if ENV.has_key? k_str
|
207
|
+
case k
|
208
|
+
when :tempo_sample_rate, :audio_sample_rate
|
209
|
+
config[k] = ENV[k_str].to_i
|
210
|
+
else
|
211
|
+
config[k] = ENV[k_str]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
check_config config
|
217
|
+
return config
|
218
|
+
end
|
219
|
+
|
220
|
+
def self.create_config(dest_dir, config = Project::DEFAULT_CONFIG)
|
221
|
+
File.open(config_path(dest_dir),"w") do |f|
|
222
|
+
f.write(config.to_yaml)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.update_config(dest_dir)
|
227
|
+
config = Project.load_config(dest_dir)
|
228
|
+
config = Project::DEFAULT_CONFIG.merge(config)
|
229
|
+
create_config(dest_dir, config)
|
230
|
+
end
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
def self.delete_empty_lines_around lines, line
|
235
|
+
# delete lines before
|
236
|
+
i = lines.index(line)-1
|
237
|
+
while (i >= 0) && lines[i].empty?
|
238
|
+
lines.delete_at i
|
239
|
+
i -= 1
|
240
|
+
end
|
241
|
+
|
242
|
+
# delete lines after
|
243
|
+
i = lines.index(line)+1
|
244
|
+
while (i < lines.size) && lines[i].empty?
|
245
|
+
lines.delete_at i
|
246
|
+
end
|
61
247
|
end
|
62
248
|
end
|
63
249
|
|
64
|
-
end
|
250
|
+
end
|