lyp-win 0.2.2

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.
@@ -0,0 +1,457 @@
1
+ require 'fileutils'
2
+ require 'open-uri'
3
+ require 'yaml'
4
+
5
+ module Lyp::Package
6
+ class << self
7
+
8
+ def list(pattern = nil)
9
+ packages = Dir["#{Lyp.packages_dir}/**/package.ly"].map do |path|
10
+ File.dirname(path).gsub("#{Lyp.packages_dir}/", '')
11
+ end
12
+
13
+ if pattern
14
+ if (pattern =~ /[@\>\<\=\~]/) && (pattern =~ Lyp::PACKAGE_RE)
15
+ package, version = $1, $2
16
+ req = Gem::Requirement.new(version) rescue nil
17
+ packages.select! do |p|
18
+ p =~ Lyp::PACKAGE_RE
19
+ p_pack, p_ver = $1, $2
20
+
21
+ next false unless p_pack == package
22
+
23
+ if req && (p_gemver = Gem::Version.new(p_ver) rescue nil)
24
+ req =~ p_gemver
25
+ else
26
+ p_ver == version
27
+ end
28
+ end
29
+ else
30
+ packages.select! do |p|
31
+ p =~ Lyp::PACKAGE_RE
32
+ $1 =~ /#{pattern}/
33
+ end
34
+ end
35
+ end
36
+
37
+ packages.sort do |x, y|
38
+ x =~ Lyp::PACKAGE_RE; x_package, x_version = $1, $2
39
+ y =~ Lyp::PACKAGE_RE; y_package, y_version = $1, $2
40
+
41
+ x_version = (x_version && Gem::Version.new(x_version) rescue x)
42
+ y_version = (y_version && Gem::Version.new(y_version) rescue y)
43
+
44
+ if (x_package == y_package) && (x_version.class == y_version.class)
45
+ x_version <=> y_version
46
+ else
47
+ x <=> y
48
+ end
49
+ end
50
+ end
51
+
52
+ def which(pattern = nil)
53
+ list(pattern).map {|p| "#{Lyp.packages_dir}/#{p}" }
54
+ end
55
+
56
+ def install(package_specifier, opts = {})
57
+ unless package_specifier =~ Lyp::PACKAGE_RE
58
+ raise "Invalid package specifier #{package_specifier}"
59
+ end
60
+ package, version = $1, $2
61
+
62
+ if version =~ /\:/
63
+ info = install_from_local_files(package, version, opts)
64
+ else
65
+ info = install_from_repository(package, version, opts)
66
+ end
67
+
68
+ install_package_dependencies(info[:path], opts)
69
+
70
+ if File.directory?(File.join(info[:path], 'fonts'))
71
+ puts "Installing package fonts..." unless opts[:silent]
72
+ install_package_fonts(info[:path], opts)
73
+ end
74
+
75
+ puts "\nInstalled #{package}@#{info[:version]}\n\n" unless opts[:silent]
76
+
77
+ if opts[:test]
78
+ FileUtils.cd(info[:path]) do
79
+ run_tests(info[:path])
80
+ end
81
+ end
82
+
83
+ # important: return the installed version
84
+ info[:version]
85
+ end
86
+
87
+ LOCAL_PACKAGE_WRAPPER =
88
+ "#(set! lyp:current-package-dir \"%s\")\n\\include \"%s\"\n"
89
+
90
+ def install_from_local_files(package, version, opts)
91
+ version =~ /^([^\:]+)\:(.+)$/
92
+ version, local_path = $1, $2
93
+
94
+ entry_point_path = nil
95
+ local_path = File.expand_path(local_path)
96
+ if File.directory?(local_path)
97
+ ly_path = File.join(local_path, "package.ly")
98
+ if File.file?(ly_path)
99
+ entry_point_path = ly_path
100
+ else
101
+ raise "Could not find #{ly_path}. Please specify a valid lilypond file."
102
+ end
103
+ elsif File.file?(local_path)
104
+ entry_point_path = local_path
105
+ else
106
+ raise "Could not find #{local_path}"
107
+ end
108
+
109
+ entry_point_dirname = File.dirname(entry_point_path)
110
+ package_path = "#{Lyp.packages_dir}/#{package}@#{version}"
111
+ package_ly_path = "#{package_path}/package.ly"
112
+
113
+ FileUtils.rm_rf(package_path)
114
+ FileUtils.mkdir_p(package_path)
115
+ File.open(package_ly_path, 'w+') do |f|
116
+ f << LOCAL_PACKAGE_WRAPPER % [entry_point_dirname, entry_point_path]
117
+ end
118
+
119
+ prepare_local_package_fonts(local_path, package_path)
120
+
121
+ {version: version, path: package_path}
122
+ end
123
+
124
+ def prepare_local_package_fonts(local_path, package_path)
125
+ # create fonts directory symlink if needed
126
+ fonts_path = File.join(local_path, 'fonts')
127
+ if File.directory?(fonts_path)
128
+ FileUtils.ln_sf(fonts_path, File.join(package_path, 'fonts'))
129
+ end
130
+ end
131
+
132
+ def install_from_repository(package, version, opts)
133
+ url = package_git_url(package)
134
+ tmp_path = git_url_to_temp_path(url)
135
+
136
+ repo = package_repository(url, tmp_path, opts)
137
+ version = checkout_package_version(repo, version, opts)
138
+
139
+ # Copy files
140
+ package_path = git_url_to_package_path(
141
+ package !~ /\// ? package : url, version
142
+ )
143
+
144
+ FileUtils.mkdir_p(File.dirname(package_path))
145
+ FileUtils.rm_rf(package_path)
146
+ FileUtils.cp_r(tmp_path, package_path)
147
+
148
+ {version: version, path: package_path}
149
+ end
150
+
151
+ def uninstall(package, opts = {})
152
+ unless package =~ Lyp::PACKAGE_RE
153
+ raise "Invalid package specifier #{package}"
154
+ end
155
+ package, version = $1, $2
156
+ package_path = git_url_to_package_path(
157
+ package !~ /\// ? package : package_git_url(package), nil
158
+ )
159
+
160
+ if opts[:all]
161
+ Dir["#{package_path}@*"].each do |path|
162
+ name = path.gsub("#{Lyp.packages_dir}/", '')
163
+ puts "Uninstalling #{name}" unless opts[:silent]
164
+ FileUtils.rm_rf(path)
165
+ end
166
+ else
167
+ if version
168
+ package_path += "@#{version}"
169
+ else
170
+ packages = Dir["#{package_path}@*"] + Dir["#{package_path}"]
171
+ case packages.size
172
+ when 0
173
+ raise "Could not find package #{package}"
174
+ when 1
175
+ package_path = packages[0]
176
+ else
177
+ packages.each do |path|
178
+ name = path.gsub("#{Lyp.packages_dir}/", '')
179
+ puts "Uninstalling #{name}" unless opts[:silent]
180
+ FileUtils.rm_rf(path)
181
+ end
182
+ return
183
+ end
184
+ end
185
+
186
+ if File.directory?(package_path)
187
+ name = package_path.gsub("#{Lyp.packages_dir}/", '')
188
+ puts "Uninstalling #{name}" unless opts[:silent]
189
+ FileUtils.rm_rf(package_path)
190
+ else
191
+ raise "Could not find package #{package}"
192
+ end
193
+ end
194
+ end
195
+
196
+ def package_repository(url, tmp_path, opts = {})
197
+ # Create repository
198
+ if File.directory?(tmp_path)
199
+ begin
200
+ repo = Rugged::Repository.new(tmp_path)
201
+ repo.fetch('origin', [repo.head.name])
202
+ return repo
203
+ rescue
204
+ # ignore and try to clone
205
+ end
206
+ end
207
+
208
+ FileUtils.rm_rf(File.dirname(tmp_path))
209
+ FileUtils.mkdir_p(File.dirname(tmp_path))
210
+ puts "Cloning #{url}..." unless opts[:silent]
211
+ Rugged::Repository.clone_at(url, tmp_path)
212
+ rescue => e
213
+ raise "Could not clone repository (please check that the package URL is correct.)"
214
+ end
215
+
216
+ def checkout_package_version(repo, version, opts = {})
217
+ # Select commit to checkout
218
+ checkout_ref = select_checkout_ref(repo, version)
219
+ unless checkout_ref
220
+ raise "Could not find tag matching #{version}"
221
+ end
222
+
223
+ begin
224
+ repo.checkout(checkout_ref, strategy: :force)
225
+ rescue
226
+ raise "Invalid version specified (#{version})"
227
+ end
228
+
229
+ tag_version(checkout_ref) || version
230
+ end
231
+
232
+ def install_package_dependencies(package_path, opts = {})
233
+ # Install any missing sub-dependencies
234
+ sub_deps = []
235
+
236
+ resolver = Lyp::Resolver.new("#{package_path}/package.ly")
237
+ deps_tree = resolver.get_dependency_tree(ignore_missing: true)
238
+ deps_tree[:dependencies].each do |package_name, leaf|
239
+ sub_deps << leaf[:clause] if leaf[:versions].empty?
240
+ end
241
+ sub_deps.each {|d| install(d, opts)}
242
+ end
243
+
244
+ def install_package_fonts(package_path, opts = {})
245
+ req = Lyp::FONT_COPY_REQ
246
+
247
+ Lyp::Lilypond.list.each do |lilypond|
248
+ next unless req =~ Gem::Version.new(lilypond[:version])
249
+
250
+ ly_fonts_dir = File.join(lilypond[:root_path], lilypond_fonts_path)
251
+ package_fonts_dir = File.join(package_path, 'fonts')
252
+
253
+ Dir["#{package_fonts_dir}/*.otf"].each do |fn|
254
+ target_fn = File.join(ly_fonts_dir, 'otf', File.basename(fn))
255
+ FileUtils.cp(fn, target_fn)
256
+ end
257
+
258
+ Dir["#{package_fonts_dir}/*.svg"].each do |fn|
259
+ target_fn = File.join(ly_fonts_dir, 'svg', File.basename(fn))
260
+ FileUtils.cp(fn, target_fn)
261
+ end
262
+
263
+ Dir["#{package_fonts_dir}/*.woff"].each do |fn|
264
+ target_fn = File.join(ly_fonts_dir, 'svg', File.basename(fn))
265
+ FileUtils.cp(fn, target_fn)
266
+ end
267
+ end
268
+ end
269
+
270
+ def lilypond_fonts_path
271
+ 'share/lilypond/current/fonts'
272
+ end
273
+
274
+ def package_git_url(package, search_index = true)
275
+ case package
276
+ when /^(?:(?:[^\:]+)|http|https)\:/
277
+ package
278
+ when /^([^\.]+\..+)\/[^\/]+\/.+(?<!\.git)$/ # .git missing from end of URL
279
+ "https://#{package}.git"
280
+ when /^([^\.]+\..+)\/.+/
281
+ "https://#{package}"
282
+ when /^[^\/]+\/[^\/]+$/
283
+ "https://github.com/#{package}.git"
284
+ else
285
+ if search_index && (url = search_lyp_index(package))
286
+ package_git_url(url, false) # make sure url is qualified
287
+ else
288
+ raise "Could not find package '#{package}' in lyp-index"
289
+ end
290
+ end
291
+ end
292
+
293
+ LYP_INDEX_URL = "https://raw.githubusercontent.com/noteflakes/lyp-index/master/index.yaml"
294
+
295
+ def search_lyp_index(package)
296
+ entry = lyp_index['packages'][package]
297
+ entry && entry['url']
298
+ end
299
+
300
+ def list_lyp_index(pattern = nil)
301
+ list = lyp_index['packages'].inject([]) do |m, kv|
302
+ m << kv[1].merge(name: kv[0])
303
+ end
304
+
305
+ if pattern
306
+ list.select! {|p| p[:name] =~ /#{pattern}/}
307
+ end
308
+
309
+ list.sort_by {|p| p[:name]}
310
+ end
311
+
312
+ def lyp_index
313
+ @lyp_index ||= YAML.load(open(LYP_INDEX_URL))
314
+ end
315
+
316
+ TEMP_REPO_ROOT_PATH = "#{$TMP_ROOT}/repos"
317
+
318
+ def git_url_to_temp_path(url)
319
+ case url
320
+ when /^(?:http|https)\:(?:\/\/)?(.+)$/
321
+ path = $1.gsub(/\.git$/, '')
322
+ "#{TEMP_REPO_ROOT_PATH}/#{path}"
323
+ when /^(?:.+@)([^\:]+)\:(?:\/\/)?(.+)$/
324
+ domain, path = $1, $2.gsub(/\.git$/, '')
325
+ "#{TEMP_REPO_ROOT_PATH}/#{domain}/#{path}"
326
+ else
327
+ raise "Invalid URL #{url}"
328
+ end
329
+ end
330
+
331
+ def git_url_to_package_path(url, version)
332
+ # version = 'head' if version.nil? || (version == '')
333
+
334
+ package_path = case url
335
+ when /^(?:http|https)\:(?:\/\/)?(.+)$/
336
+ path = $1.gsub(/\.git$/, '')
337
+ "#{Lyp::packages_dir}/#{path}"
338
+ when /^(?:.+@)([^\:]+)\:(?:\/\/)?(.+)$/
339
+ domain, path = $1, $2.gsub(/\.git$/, '')
340
+ "#{Lyp::packages_dir}/#{domain}/#{path}"
341
+ else
342
+ if url !~ /\//
343
+ "#{Lyp::packages_dir}/#{url}"
344
+ else
345
+ raise "Invalid URL #{url}"
346
+ end
347
+ end
348
+
349
+ package_path += "@#{version}" if version
350
+ package_path
351
+ end
352
+
353
+ TAG_VERSION_RE = /^v?(\d.*)$/
354
+
355
+ def select_checkout_ref(repo, version_specifier)
356
+ case version_specifier
357
+ when nil, '', 'latest'
358
+ highest_versioned_tag(repo) || 'master'
359
+ when /^(\>=|~\>|\d)/
360
+ req = Gem::Requirement.new(version_specifier)
361
+ tag = repo_tags(repo).reverse.find do |t|
362
+ (v = tag_version(t.name)) && (req =~ Gem::Version.new(v))
363
+ end
364
+ unless tag
365
+ raise "Could not find a version matching #{version_specifier}"
366
+ else
367
+ tag.name
368
+ end
369
+ else
370
+ version_specifier
371
+ end
372
+ end
373
+
374
+ def highest_versioned_tag(repo)
375
+ tag = repo_tags(repo).select {|t| Gem::Version.new(tag_version(t.name)) rescue nil}.last
376
+ tag && tag.name
377
+ end
378
+
379
+ # Returns a list of tags sorted by version
380
+ def repo_tags(repo)
381
+ tags = []
382
+ repo.tags.each {|t| tags << t}
383
+
384
+ tags.sort do |x, y|
385
+ x_version, y_version = tag_version(x.name), tag_version(y.name)
386
+ if x_version && y_version
387
+ Gem::Version.new(x_version) <=> Gem::Version.new(y_version)
388
+ else
389
+ x.name <=> y.name
390
+ end
391
+ end
392
+ end
393
+
394
+ def tag_version(tag)
395
+ (tag =~ TAG_VERSION_RE) ? $1 : nil
396
+ end
397
+
398
+ # Runs all tests found in local directory
399
+ def run_local_tests(dir, opts = {})
400
+ package_dir = File.expand_path(dir)
401
+ test_files = Dir["#{package_dir}/**/*_test.ly"]
402
+ run_tests(opts) do |stats|
403
+ test_files.each do |f|
404
+ perform_test(f, stats)
405
+ end
406
+ end
407
+ end
408
+
409
+ # This method runs tests by yielding the test statistics.
410
+ # The caller should then call #perform_test to run each test file.
411
+ def run_tests(opts = {})
412
+ stats = {
413
+ start: Time.now,
414
+ test_count: 0,
415
+ fail_count: 0
416
+ }
417
+
418
+ yield stats
419
+
420
+ if stats[:test_count] == 0
421
+ STDERR.puts "No test files found" unless opts[:silent]
422
+ else
423
+ puts "\nFinished in %.2g seconds\n%d files, %d failures" % [
424
+ Time.now - stats[:start], stats[:test_count], stats[:fail_count]
425
+ ] unless opts[:silent]
426
+ exit(stats[:fail_count] > 0 ? 1 : 0) unless opts[:dont_exit]
427
+ end
428
+
429
+ stats
430
+ end
431
+
432
+ def perform_test(fn, stats)
433
+ stats[:test_count] += 1
434
+ unless Lyp::Lilypond.compile([fn], mode: :system, force_wrap: true)
435
+ stats[:fail_count] += 1
436
+ end
437
+ end
438
+
439
+ def run_package_tests(patterns, opts = {})
440
+ patterns = [''] if patterns.empty?
441
+ packages = patterns.inject([]) do |m, pat|
442
+ m += Dir["#{Lyp.packages_dir}/#{pat}*"]
443
+ end.uniq
444
+
445
+ run_tests(opts) do |stats|
446
+ packages.each do |path|
447
+ files = Dir["#{path}/**/*_test.ly"]
448
+ next if files.empty?
449
+
450
+ FileUtils.cd(path) do
451
+ files.each {|fn| perform_test(fn, stats)}
452
+ end
453
+ end
454
+ end
455
+ end
456
+ end
457
+ end