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,296 @@
1
+ ;;;; This file is part of LilyPond, the GNU music typesetter.
2
+ ;;;;
3
+ ;;;; Copyright (C) 2004--2014 Han-Wen Nienhuys <hanwen@xs4all.nl>
4
+ ;;;;
5
+ ;;;; LilyPond is free software: you can redistribute it and/or modify
6
+ ;;;; it under the terms of the GNU General Public License as published by
7
+ ;;;; the Free Software Foundation, either version 3 of the License, or
8
+ ;;;; (at your option) any later version.
9
+ ;;;;
10
+ ;;;; LilyPond is distributed in the hope that it will be useful,
11
+ ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ;;;; GNU General Public License for more details.
14
+ ;;;;
15
+ ;;;; You should have received a copy of the GNU General Public License
16
+ ;;;; along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ ;; This file supplied by Abraham Leigh and used by lyp to patch lilypond
19
+ ;; versions from 2.18.2 to 2.19.11 in order to support custom music fonts.
20
+
21
+ ;; TODO:
22
+ ;;
23
+ ;; lookup-font should be written in C.
24
+ ;;
25
+
26
+ ;; We have a tree, where each level of the tree is a qualifier
27
+ ;; (eg. encoding, family, shape, series etc.) this defines the levels
28
+ ;; in the tree. The first one is encoding, so we can directly select
29
+ ;; between text or music in the first step of the selection.
30
+ (define default-qualifier-order
31
+ '(font-encoding font-family font-shape font-series))
32
+
33
+ (define-class <Font-tree-element>
34
+ ())
35
+
36
+ (define-class <Font-tree-leaf> (<Font-tree-element>)
37
+ (default-size #:init-keyword #:default-size)
38
+ (size-vector #:init-keyword #:size-vector))
39
+
40
+ (define-class <Font-tree-node> (<Font-tree-element>)
41
+ (qualifier #:init-keyword #:qualifier #:accessor font-qualifier)
42
+ (default #:init-keyword #:default #:accessor font-default)
43
+ (children #:init-keyword #:children #:accessor font-children))
44
+
45
+ (define (make-font-tree-leaf size size-font-vector)
46
+ (make <Font-tree-leaf> #:default-size size #:size-vector size-font-vector))
47
+
48
+ (define (make-font-tree-node
49
+ qualifier default)
50
+ (make <Font-tree-node>
51
+ #:qualifier qualifier
52
+ #:default default
53
+ #:children (make-hash-table 11)))
54
+
55
+ (define-method (display (leaf <Font-tree-leaf>) port)
56
+ (for-each (lambda (x) (display x port))
57
+ (list
58
+ "#<Font-size-family:\n"
59
+ (slot-ref leaf 'default-size)
60
+ (slot-ref leaf 'size-vector)
61
+ "#>"
62
+ )))
63
+
64
+ (define-method (display (node <Font-tree-node>) port)
65
+ (for-each
66
+ (lambda (x)
67
+ (display x port))
68
+ (list
69
+ "Font_node {\nqual: "
70
+ (font-qualifier node)
71
+ "(def: "
72
+ (font-default node)
73
+ ") {\n"))
74
+ (for-each
75
+ (lambda (x)
76
+ (display "\n")
77
+ (display (car x) port)
78
+ (display "=" port)
79
+ (display (cdr x) port))
80
+ (hash-table->alist (font-children node)))
81
+ (display "} }\n"))
82
+
83
+
84
+ (define-method (add-font (node <Font-tree-node>) fprops size-family)
85
+ (define (assoc-delete key alist)
86
+ (assoc-remove! (list-copy alist) key))
87
+
88
+ (define (make-node fprops size-family)
89
+ (if (null? fprops)
90
+ (make-font-tree-leaf (car size-family) (cdr size-family))
91
+ (let* ((qual (next-qualifier default-qualifier-order fprops)))
92
+ (make-font-tree-node qual
93
+ (assoc-get qual fprops)))))
94
+
95
+ (define (next-qualifier order props)
96
+ (cond
97
+ ((and (null? props) (null? order))
98
+ #f)
99
+ ((null? props) (car order))
100
+ ((null? order) (caar props))
101
+ (else
102
+ (if (assoc-get (car order) props)
103
+ (car order)
104
+ (next-qualifier (cdr order) props)))))
105
+
106
+ (let* ((q (font-qualifier node))
107
+ (d (font-default node))
108
+ (v (assoc-get q fprops d))
109
+ (new-fprops (assoc-delete q fprops))
110
+ (child (hashq-ref (slot-ref node 'children)
111
+ v #f)))
112
+ (if (not child)
113
+ (begin
114
+ (set! child (make-node new-fprops size-family))
115
+ (hashq-set! (slot-ref node 'children) v child)))
116
+ (if (pair? new-fprops)
117
+ (add-font child new-fprops size-family))))
118
+
119
+ (define-method (add-font (node <Font-tree-leaf>) fprops size-family)
120
+ (throw "must add to node, not leaf"))
121
+
122
+ (define-method (g-lookup-font (node <Font-tree-node>) alist-chain)
123
+ (let* ((qual (font-qualifier node))
124
+ (def (font-default node))
125
+ (val (chain-assoc-get qual alist-chain def))
126
+ (desired-child (hashq-ref (font-children node) val)))
127
+
128
+ (if desired-child
129
+ (g-lookup-font desired-child alist-chain)
130
+ (g-lookup-font (hashq-ref (font-children node) def) alist-chain))))
131
+
132
+ (define-method (g-lookup-font (node <Font-tree-leaf>) alist-chain)
133
+ node)
134
+
135
+ ;; two step call is handy for debugging.
136
+ (define (lookup-font node alist-chain)
137
+ (g-lookup-font node alist-chain))
138
+
139
+ ;; TODO - we could actually construct this by loading all OTFs and
140
+ ;; inspecting their design size fields.
141
+ (define-public feta-design-size-mapping
142
+ '((11 . 11.22)
143
+ (13 . 12.60)
144
+ (14 . 14.14)
145
+ (16 . 15.87)
146
+ (18 . 17.82)
147
+ (20 . 20)
148
+ (23 . 22.45)
149
+ (26 . 25.20)))
150
+
151
+ ;; Each size family is a vector of fonts, loaded with a delay. The
152
+ ;; vector should be sorted according to ascending design size.
153
+ (define-public (add-music-fonts node family name brace design-size-alist factor)
154
+ "Set up music fonts.
155
+
156
+ Arguments:
157
+ @itemize
158
+ @item
159
+ @var{node} is the font tree to modify.
160
+
161
+ @item
162
+ @var{family} is the family name of the music font.
163
+
164
+ @item
165
+ @var{name} is the basename for the music font.
166
+ @file{@var{name}-<designsize>.otf} should be the music font,
167
+
168
+ @item
169
+ @var{brace} is the basename for the brace font.
170
+ @file{@var{brace}-brace.otf} should have piano braces.
171
+
172
+ @item
173
+ @var{design-size-alist} is a list of @code{(rounded . designsize)}.
174
+ @code{rounded} is a suffix for font filenames, while @code{designsize}
175
+ should be the actual design size. The latter is used for text fonts
176
+ loaded through pango/@/fontconfig.
177
+
178
+ @item
179
+ @var{factor} is a size factor relative to the default size that is being
180
+ used. This is used to select the proper design size for the text fonts.
181
+ @end itemize"
182
+ (for-each
183
+ (lambda (x)
184
+ (add-font node
185
+ (list (cons 'font-encoding (car x))
186
+ (cons 'font-family family))
187
+ (cons (* factor (cadr x))
188
+ (caddr x))))
189
+
190
+ `((fetaText ,(ly:pt 20.0)
191
+ ,(list->vector
192
+ (map (lambda (tup)
193
+ (cons (ly:pt (cdr tup))
194
+ (format #f "~a-~a ~a"
195
+ name
196
+ (car tup)
197
+ (ly:pt (cdr tup)))))
198
+ design-size-alist)))
199
+ (fetaMusic ,(ly:pt 20.0)
200
+ ,(list->vector
201
+ (map (lambda (size-tup)
202
+ (delay (ly:system-font-load
203
+ (format #f "~a-~a" name (car size-tup)))))
204
+ design-size-alist
205
+ )))
206
+ (fetaBraces ,(ly:pt 20.0)
207
+ #(,(delay (ly:system-font-load
208
+ (format #f "~a-brace" brace)))))
209
+ )))
210
+
211
+ (define-public (add-pango-fonts node lily-family family factor)
212
+ ;; Synchronized with the `text-font-size' variable in
213
+ ;; layout-set-absolute-staff-size-in-module (see paper.scm).
214
+ (define text-font-size (ly:pt (* factor 11.0)))
215
+
216
+ (define (add-node shape series)
217
+ (add-font node
218
+ `((font-family . ,lily-family)
219
+ (font-shape . ,shape)
220
+ (font-series . ,series)
221
+ (font-encoding . latin1) ;; ugh.
222
+ )
223
+ `(,text-font-size
224
+ . #(,(cons
225
+ (ly:pt 12)
226
+ (ly:make-pango-description-string
227
+ `(((font-family . ,family)
228
+ (font-series . ,series)
229
+ (font-shape . ,shape)))
230
+ (ly:pt 12)))))))
231
+
232
+ (add-node 'upright 'normal)
233
+ (add-node 'caps 'normal)
234
+ (add-node 'upright 'bold)
235
+ (add-node 'italic 'normal)
236
+ (add-node 'italic 'bold))
237
+
238
+ ; This function allows the user to change the specific fonts, leaving others
239
+ ; to the default values. This way, "make-pango-font-tree"'s syntax doesn't
240
+ ; have to change from the user's perspective.
241
+ ;
242
+ ; Usage:
243
+ ; \paper {
244
+ ; #(define fonts
245
+ ; (set-global-fonts
246
+ ; #:music "gonville" ; (the main notation font)
247
+ ; #:roman "FreeSerif" ; (the main/serif text font)
248
+ ; ))
249
+ ; }
250
+ ;
251
+ ; Leaving out "#:brace", "#:sans", and "#:typewriter" leave them at
252
+ ; "emmentaler", "sans-serif", and "monospace", respectively. All fonts are
253
+ ; still accesible through the usual scheme symbols: 'feta, 'roman, 'sans, and
254
+ ; 'typewriter.
255
+ (define*-public (set-global-fonts #:key
256
+ (music "emmentaler")
257
+ (brace "emmentaler")
258
+ (roman "Century Schoolbook L")
259
+ (sans "sans-serif")
260
+ (typewriter "monospace")
261
+ (factor 1))
262
+ (let ((n (make-font-tree-node 'font-encoding 'fetaMusic)))
263
+ (add-music-fonts n 'feta music brace feta-design-size-mapping factor)
264
+ (add-pango-fonts n 'roman roman factor)
265
+ (add-pango-fonts n 'sans sans factor)
266
+ (add-pango-fonts n 'typewriter typewriter factor)
267
+ n))
268
+
269
+ (define-public (make-pango-font-tree roman-str sans-str typewrite-str factor)
270
+ (let ((n (make-font-tree-node 'font-encoding 'fetaMusic)))
271
+ (add-music-fonts n 'feta "emmentaler" "emmentaler" feta-design-size-mapping factor)
272
+ (add-pango-fonts n 'roman roman-str factor)
273
+ (add-pango-fonts n 'sans sans-str factor)
274
+ (add-pango-fonts n 'typewriter typewrite-str factor)
275
+ n))
276
+
277
+ (define-public (make-century-schoolbook-tree factor)
278
+ (make-pango-font-tree
279
+ "Century Schoolbook L"
280
+ "sans-serif"
281
+ "monospace"
282
+ factor))
283
+
284
+ (define-public all-text-font-encodings
285
+ '(latin1))
286
+
287
+ (define-public all-music-font-encodings
288
+ '(fetaBraces
289
+ fetaMusic
290
+ fetaText))
291
+
292
+ (define-public (magstep s)
293
+ (exp (* (/ s 6) (log 2))))
294
+
295
+ (define-public (magnification->font-size m)
296
+ (* 6 (/ (log m) (log 2))))
@@ -0,0 +1,103 @@
1
+ #(begin
2
+ (define lyp:path-separator "/")
3
+ ;(define lyp:path-separator (list->string (list
4
+ ; (if (eq? PLATFORM 'windows) #\\ #\/ ))))
5
+
6
+ (define (lyp:file-join . ls) (string-join ls lyp:path-separator))
7
+
8
+ ; hash table mapping package refs to package names
9
+ (define lyp:package-refs (make-hash-table))
10
+
11
+ ; hash table mapping package names to package directories
12
+ (define lyp:package-dirs (make-hash-table))
13
+
14
+ ; hash table mapping package names to loaded state
15
+ (define lyp:package-loaded? (make-hash-table))
16
+
17
+ ; hash table mapping file paths to included state
18
+ (define lyp:file-included? (make-hash-table))
19
+
20
+ ; convert package ref to package name
21
+ (define (lyp:ref->name ref) (let* (
22
+ (clean-ref (car (string-split ref #\:)))
23
+ (name (hash-ref lyp:package-refs clean-ref))
24
+ )
25
+ (or name (throw 'lyp:failure "lyp:ref->name"
26
+ (format "Invalid package ref ~a" ref) #f))
27
+ ))
28
+
29
+ ; convert package reef to directory
30
+ (define (lyp:name->dir name) (let (
31
+ (dir (hash-ref lyp:package-dirs name))
32
+ )
33
+ (or dir (throw 'lyp-failure "lyp:name->dir"
34
+ (format "Invalid package name ~a" ref) #f))
35
+ ))
36
+
37
+ ; converts a package-relative path to absolute path. If the package is null,
38
+ ; uses lyp:current-package-dir (which value is valid only on package loading)
39
+ (define (lyp:package-file-path package path) (let* (
40
+ (base-path (if (null? package)
41
+ lyp:current-package-dir (lyp:name->dir package)))
42
+ )
43
+ (lyp:file-join base-path path)
44
+ ))
45
+
46
+ ; converts a package file reference to absolute path
47
+ (define (lyp:fileref->path ref) (let* (
48
+ (split (string-split ref #\:))
49
+ (qualified? (eq? (length split) 2))
50
+ (package (if qualified? (list-ref split 0) %nil))
51
+ (path (if qualified? (list-ref split 1) (list-ref split 0)))
52
+ )
53
+ (lyp:package-file-path package path)
54
+ ))
55
+
56
+ (define (lyp:load ref) (load (lyp:fileref->path ref)))
57
+
58
+ (define (lyp:include-ly-file path once-only?) (let* (
59
+ (included? (and once-only? (hash-ref lyp:file-included? path)))
60
+ )
61
+ (if (not (file-exists? path))
62
+ (throw 'lyp:failure "lyp:include-ly-file"
63
+ (format "File not found ~a" path) #f)
64
+ )
65
+ (if (not included?) (begin
66
+ (hash-set! lyp:file-included? path #t)
67
+ #{ \include #path #}
68
+ ))
69
+ ))
70
+
71
+ (define (lyp:include ref)
72
+ (lyp:include-ly-file (lyp:fileref->path ref) #f))
73
+ (define (lyp:include-once ref)
74
+ (lyp:include-ly-file (lyp:fileref->path ref) #t))
75
+
76
+ (define (lyp:require ref) (let* (
77
+ (name (lyp:ref->name ref))
78
+ (package-dir (lyp:name->dir name))
79
+ (entry-point-path (lyp:file-join package-dir "package.ly"))
80
+ (loaded? (hash-ref lyp:package-loaded? name))
81
+ (prev-package-dir lyp:current-package-dir)
82
+ )
83
+ (if (not loaded?) (begin
84
+ (ly:debug "Loading package ~a at ~a" name package-dir)
85
+ (set! lyp:current-package-dir package-dir)
86
+ (hash-set! lyp:package-loaded? name #t)
87
+ #{ \include #entry-point-path #}
88
+ (set! lyp:current-package-dir prev-package-dir)
89
+ ))
90
+ ))
91
+ )
92
+
93
+ % command form
94
+ require = #(define-void-function (parser location ref)(string?)
95
+ (lyp:require ref))
96
+
97
+
98
+ pinclude = #(define-void-function (parser location ref)(string?)
99
+ (lyp:include ref))
100
+
101
+ pincludeOnce = #(define-void-function (parser location ref)(string?)
102
+ (lyp:include-once ref))
103
+
@@ -0,0 +1,44 @@
1
+ # A quick-n-dirty rugged swap-in. Since the rugged gem includes a native
2
+ # extension (libgit2), and since traveling-ruby does not yet include an updated
3
+ # version of it (the latest is 0.22.0b5 and we need >=0.23.0), we make a
4
+ # compromise, and make a traveling-ruby-based standalone release without
5
+ # rugged, but using plain git in order to install packages. So, users will have
6
+ # to have git installed on their machines.
7
+ #
8
+ # So here's an absolutely minimal replacement for rugged (just for the
9
+ # functionality we need) wrapping the git command.
10
+
11
+ module Rugged
12
+ class Repository
13
+ def self.clone_at(url, path)
14
+ `git clone -q \"#{url}\" \"#{path}\"`
15
+ new(path)
16
+ end
17
+
18
+ def initialize(path)
19
+ @path = path
20
+ exec('status')
21
+ end
22
+
23
+ Ref = Struct.new(:name)
24
+
25
+ def head
26
+ h = exec("show-ref --head").lines.map {|r| r =~ /^(\S+)\sHEAD$/ && $1}[0]
27
+ Ref.new(h)
28
+ end
29
+
30
+
31
+ def checkout(ref, opts)
32
+ # strategy: :force
33
+ exec("checkout -qf #{ref}")
34
+ end
35
+
36
+ def tags
37
+ exec("tag").lines.map {|l| Ref.new(l.chomp)}
38
+ end
39
+
40
+ def exec(cmd)
41
+ `cd #{@path} && git #{cmd}`
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,570 @@
1
+ require 'uri'
2
+ require 'httpclient'
3
+ require 'open3'
4
+ require 'ruby-progressbar'
5
+
6
+ module Lyp::Lilypond
7
+ class << self
8
+ def compile(argv, opts = {})
9
+ fn = Lyp.wrap(argv.pop, opts)
10
+ argv << fn
11
+
12
+ invoke(argv, opts)
13
+ end
14
+
15
+ def invoke(argv, opts = {})
16
+ lilypond = detect_use_version_argument(argv) || current_lilypond
17
+
18
+ case opts[:mode]
19
+ when :system
20
+ system("#{lilypond} #{argv.join(" ")}")
21
+ else
22
+ Kernel.exec(lilypond, *argv)
23
+ end
24
+ end
25
+
26
+ def detect_use_version_argument(argv)
27
+ nil
28
+ end
29
+
30
+ def default_lilypond
31
+ Lyp::Settings['lilypond/default']
32
+ end
33
+
34
+ def set_default_lilypond(path)
35
+ Lyp::Settings['lilypond/default'] = path
36
+ end
37
+
38
+ # The current lilypond path is stored in a temporary file named by the
39
+ # session id. Thus we can persist the version selected by the user
40
+ def current_lilypond
41
+ return forced_lilypond if @forced_version
42
+
43
+ settings = get_session_settings
44
+
45
+ if !settings[:current]
46
+ settings[:current] = default_lilypond
47
+ set_session_settings(settings)
48
+ end
49
+
50
+ settings[:current]
51
+ end
52
+
53
+ def set_current_lilypond(path)
54
+ settings = get_session_settings
55
+ settings[:current] = path
56
+ set_session_settings(settings)
57
+ end
58
+
59
+ def forced_lilypond
60
+ lilypond = filter_installed_list(@forced_version)[0]
61
+ if lilypond
62
+ lilypond[:path]
63
+ else
64
+ raise "No installed version found matching '#{@forced_version}'"
65
+ end
66
+ end
67
+
68
+ def force_env_version!
69
+ @forced_version = ENV['LILYPOND_VERSION']
70
+ unless @forced_version
71
+ raise "LILYPOND_VERSION not set"
72
+ end
73
+ end
74
+
75
+ def force_version!(version)
76
+ @forced_version = version
77
+ end
78
+
79
+ attr_reader :forced_version
80
+
81
+ def check_lilypond!
82
+ # check default
83
+ select_default_lilypond! unless valid_lilypond?(default_lilypond)
84
+
85
+ set_current_lilypond(default_lilypond) unless valid_lilypond?(current_lilypond)
86
+ end
87
+
88
+ def valid_lilypond?(path)
89
+ (File.file?(path) rescue nil) && (`#{path} -v` =~ /^GNU LilyPond/)
90
+ end
91
+
92
+ def select_default_lilypond!
93
+ latest = system_lilyponds.sort(&CMP_VERSION).last || lyp_lilyponds.sort(&CMP_VERSION).last
94
+ if latest
95
+ default = latest[:path]
96
+ set_default_lilypond(default)
97
+ else
98
+ raise Lyp::LILYPOND_NOT_FOUND_MSG
99
+ end
100
+ end
101
+
102
+ def get_session_settings
103
+ YAML.load(IO.read(session_settings_filename)) rescue {}
104
+ end
105
+
106
+ def set_session_settings(settings)
107
+ File.open(session_settings_filename, 'w+') do |f|
108
+ f << YAML.dump(settings)
109
+ end
110
+ end
111
+
112
+ def session_settings_filename
113
+ "#{$TMP_ROOT}/session.#{Process.getsid}.yml"
114
+ end
115
+
116
+ CMP_VERSION = proc do |x, y|
117
+ Gem::Version.new(x[:version]) <=> Gem::Version.new(y[:version])
118
+ end
119
+
120
+ def filter_installed_list(version_specifier)
121
+ list = (system_lilyponds + lyp_lilyponds).sort!(&CMP_VERSION)
122
+ list.select {|l| version_match(l[:version], version_specifier, list)}
123
+ end
124
+
125
+ def list(opts = {})
126
+ system_list = opts[:lyp_only] ? [] : system_lilyponds
127
+ lyp_list = opts[:system_only] ? [] : lyp_lilyponds
128
+
129
+ default = default_lilypond
130
+ unless default
131
+ latest = system_list.sort(&CMP_VERSION).last || lyp_list.sort(&CMP_VERSION).last
132
+ if latest
133
+ default = latest[:path]
134
+ set_default_lilypond(default)
135
+ end
136
+ end
137
+ current = current_lilypond
138
+
139
+ lilyponds = system_list + lyp_list
140
+
141
+ lilyponds.each do |l|
142
+ l[:default] = l[:path] == default
143
+ l[:current] = l[:path] == current
144
+ end
145
+
146
+ # sort by version
147
+ lilyponds.sort!(&CMP_VERSION)
148
+ end
149
+
150
+ def lyp_lilyponds
151
+ list = []
152
+
153
+ Dir["#{Lyp.lilyponds_dir}/*"].each do |path|
154
+ next unless File.directory?(path) && File.basename(path) =~ /^[\d\.]+$/
155
+
156
+ root_path = path
157
+ version = File.basename(path)
158
+ path = File.join(path, "bin/lilypond")
159
+ list << {
160
+ root_path: root_path,
161
+ path: path,
162
+ version: version
163
+ }
164
+ end
165
+
166
+ list
167
+ end
168
+
169
+ def system_lilyponds
170
+ list = get_system_lilyponds_paths
171
+ return list if list.empty?
172
+
173
+ list.inject([]) do |m, path|
174
+ begin
175
+ resp = `#{path} -v`
176
+ if resp.lines.first =~ /LilyPond ([0-9\.]+)/i
177
+ m << {
178
+ root_path: File.expand_path(File.join(File.dirname(path), '..')),
179
+ path: path,
180
+ version: $1,
181
+ system: true
182
+ }
183
+ end
184
+ rescue
185
+ # ignore error
186
+ end
187
+ m
188
+ end
189
+ end
190
+
191
+ def get_system_lilyponds_paths
192
+ self_bin_dir = File.dirname(File.expand_path($0))
193
+
194
+ list = `which -a lilypond`
195
+ list = list.lines.map {|f| f.chomp}.reject do |l|
196
+ dir = File.dirname(l)
197
+ (dir == Gem.bindir) || (dir == Lyp::LYP_BIN_DIRECTORY) || (dir == self_bin_dir)
198
+ end
199
+ end
200
+
201
+ BASE_URL = "http://download.linuxaudio.org/lilypond/binaries"
202
+
203
+ # Returns a list of versions of lilyponds available for download
204
+ def search(version_specifier = nil)
205
+ require 'open-uri'
206
+
207
+ platform = detect_lilypond_platform
208
+ url = "#{BASE_URL}/#{platform}/"
209
+
210
+ versions = []
211
+
212
+ open(url).read.scan(/a href=\"lilypond-([0-9\.]+)[^>]+\"/) do |m|
213
+ versions << $1
214
+ end
215
+
216
+ installed_versions = list.map {|l| l[:version]}
217
+ versions.select! {|v| version_match(v, version_specifier, versions)}
218
+ versions.map do |v|
219
+ {
220
+ version: v,
221
+ installed: installed_versions.include?(v)
222
+ }
223
+ end
224
+ end
225
+
226
+ def version_match(version, specifier, all_versions)
227
+ case specifier
228
+ when 'latest'
229
+ version == all_versions.last
230
+ when 'stable'
231
+ Gem::Version.new(version).segments[1].even?
232
+ when 'unstable'
233
+ Gem::Version.new(version).segments[1].odd?
234
+ else
235
+ Gem::Requirement.new(specifier) =~ Gem::Version.new(version)
236
+ end
237
+ end
238
+
239
+ def latest_stable_version
240
+ search.reverse.find {|l| Gem::Version.new(l[:version]).segments[1].even?}[:version]
241
+ end
242
+
243
+ def latest_unstable_version
244
+ search.reverse.find {|l| Gem::Version.new(l[:version]).segments[1].odd?}[:version]
245
+ end
246
+
247
+ def latest_version
248
+ search.last[:version]
249
+ end
250
+
251
+ def install_if_missing(version_specifier, opts = {})
252
+ if filter_installed_list(version_specifier).empty?
253
+ install(version_specifier, opts)
254
+ end
255
+ end
256
+
257
+ def install(version_specifier, opts = {})
258
+ version = detect_version_from_specifier(version_specifier)
259
+ raise "No version found matching specifier #{version_specifier}" unless version
260
+
261
+ STDERR.puts "Installing version #{version}" unless opts[:silent]
262
+ install_version(version, opts)
263
+
264
+ lilypond_path = lyp_lilypond_path(version)
265
+ set_current_lilypond(lilypond_path)
266
+ set_default_lilypond(lilypond_path) if opts[:default]
267
+ end
268
+
269
+ def lyp_lilypond_path(version)
270
+ "#{Lyp.lilyponds_dir}/#{version}/bin/lilypond"
271
+ end
272
+
273
+ def detect_version_from_specifier(version_specifier)
274
+ case version_specifier
275
+ when /^\d/
276
+ version_specifier
277
+ when nil, 'stable'
278
+ latest_stable_version
279
+ when 'unstable'
280
+ latest_unstable_version
281
+ when 'latest'
282
+ latest_version
283
+ else
284
+ req = Gem::Requirement.new(version_specifier)
285
+ lilypond = search.reverse.find {|l| req =~ Gem::Version.new(l[:version])}
286
+ if lilypond
287
+ lilypond[:version]
288
+ else
289
+ raise "Could not find version matching #{version_specifier}"
290
+ end
291
+ end
292
+ end
293
+
294
+ def detect_lilypond_platform
295
+ case RUBY_PLATFORM
296
+ when /x86_64-darwin/
297
+ "darwin-x86"
298
+ when /ppc-darwin/
299
+ "darwin-ppc"
300
+ when "i686-linux"
301
+ "linux-x86"
302
+ when "x86_64-linux"
303
+ "linux-64"
304
+ when "ppc-linux"
305
+ "linux-ppc"
306
+ when "x64-mingw32"
307
+ "mingw"
308
+ end
309
+ end
310
+
311
+ def install_version(version, opts)
312
+ platform = detect_lilypond_platform
313
+ url = lilypond_install_url(platform, version, opts)
314
+ fn = temp_install_filename(url)
315
+
316
+ download_lilypond(url, fn, opts) unless File.file?(fn)
317
+ install_lilypond_files(fn, platform, version, opts)
318
+
319
+ patch_font_scm(version)
320
+ copy_fonts_from_all_packages(version, opts)
321
+ end
322
+
323
+ def lilypond_install_url(platform, version, opts)
324
+ ext = case platform
325
+ when /darwin/
326
+ ".tar.bz2"
327
+ when /linux/
328
+ ".sh"
329
+ when /mingw/
330
+ ".exe"
331
+ end
332
+ filename = "lilypond-#{version}-1.#{platform}"
333
+
334
+ "#{BASE_URL}/#{platform}/#{filename}#{ext}"
335
+ end
336
+
337
+ def temp_install_filename(url)
338
+ u = URI(url)
339
+ "#{$TMP_ROOT}/#{File.basename(u.path)}"
340
+ end
341
+
342
+ def download_lilypond(url, fn, opts)
343
+ STDERR.puts "Downloading #{url}" unless opts[:silent]
344
+
345
+ download_count = 0
346
+ client = HTTPClient.new
347
+ conn = client.get_async(url)
348
+ msg = conn.pop
349
+ total_size = msg.header['Content-Length'].first.to_i
350
+ io = msg.content
351
+
352
+ unless opts[:silent]
353
+ pbar = ProgressBar.create(title: 'Download', total: total_size)
354
+ end
355
+ File.open(fn, 'w+') do |f|
356
+ while data = io.read(10000)
357
+ download_count += data.bytesize
358
+ f << data
359
+ unless opts[:silent]
360
+ pbar.progress = download_count if download_count <= total_size
361
+ end
362
+ end
363
+ end
364
+ pbar.finish unless opts[:silent]
365
+ end
366
+
367
+ def install_lilypond_files(fn, platform, version, opts)
368
+ tmp_target = "#{$TMP_ROOT}/lilypond-#{version}"
369
+ FileUtils.mkdir_p(tmp_target)
370
+
371
+ case platform
372
+ when /darwin/
373
+ install_lilypond_files_osx(fn, tmp_target, platform, version, opts)
374
+ when /linux/
375
+ install_lilypond_files_linux(fn, tmp_target, platform, version, opts)
376
+ when /mingw/
377
+ install_lilypond_files_windows(fn, tmp_target, platform, version, opts)
378
+ end
379
+
380
+ ensure
381
+ FileUtils.rm_rf(tmp_target)
382
+ end
383
+
384
+ def install_lilypond_files_osx(fn, target, platform, version, opts)
385
+ STDERR.puts "Extracting..." unless opts[:silent]
386
+ exec "tar -xjf #{fn} -C #{target}"
387
+
388
+ copy_lilypond_files("#{target}/LilyPond.app/Contents/Resources", version, opts)
389
+ end
390
+
391
+ # Since linux versions are distributed as sh archives, we need first to
392
+ # extract the sh archive, then extract the resulting tar file
393
+ def install_lilypond_files_linux(fn, target, platform, version, opts)
394
+ STDERR.puts "Extracting..." unless opts[:silent]
395
+
396
+ # create temp directory in which to extract .sh file
397
+ tmp_dir = "#{$TMP_ROOT}/#{Time.now.to_f}"
398
+ FileUtils.mkdir_p(tmp_dir)
399
+
400
+ FileUtils.cd(tmp_dir) do
401
+ exec "sh #{fn} --tarball >/dev/null"
402
+ end
403
+
404
+ tmp_fn = "#{tmp_dir}/lilypond-#{version}-1.#{platform}.tar.bz2"
405
+
406
+ exec "tar -xjf #{tmp_fn} -C #{target}"
407
+
408
+ copy_lilypond_files("#{target}/usr", version, opts)
409
+ ensure
410
+ FileUtils.rm_rf(tmp_dir)
411
+ end
412
+
413
+ def install_lilypond_files_windows(fn, target, platform, version, opts)
414
+ STDERR.puts "Running NSIS Installer..." unless opts[:silent]
415
+
416
+ target_dir = File.join(Lyp.lilyponds_dir, version)
417
+ FileUtils.mkdir_p(target_dir)
418
+
419
+ # run installer
420
+ cmd = "#{fn} /S /D=#{target_dir.gsub('/', '\\')}"
421
+ `#{cmd}`
422
+
423
+ # wait for installer to finish
424
+ t1 = Time.now
425
+ while !File.file?("#{target_dir}/usr/bin/lilypond.exe")
426
+ sleep 0.5
427
+ raise "Windows installation failed" if Time.now - t1 >= 60
428
+ end
429
+
430
+ # Show lilypond versions
431
+ STDERR.puts `#{target_dir}/usr/bin/lilypond -v` unless opts[:silent] || opts[:no_version_test]
432
+ end
433
+
434
+ def copy_lilypond_files(base_path, version, opts)
435
+ target_dir = File.join(Lyp.lilyponds_dir, version)
436
+
437
+ FileUtils.rm_rf(target_dir) if File.exists?(target_dir)
438
+
439
+ # create directory for lilypond files
440
+ FileUtils.mkdir_p(target_dir)
441
+
442
+ # copy files
443
+ STDERR.puts "Copying..." unless opts[:silent]
444
+ %w{bin etc lib lib64 share var}.each do |entry|
445
+ dir = File.join(base_path, entry)
446
+ FileUtils.cp_r(dir, target_dir, remove_destination: true) if File.directory?(dir)
447
+ end
448
+
449
+ # Show lilypond versions
450
+ STDERR.puts `#{target_dir}/bin/lilypond -v` unless opts[:silent] || opts[:no_version_test]
451
+ rescue => e
452
+ puts e.message
453
+ end
454
+
455
+ def patch_font_scm(version)
456
+ return unless Lyp::FONT_PATCH_REQ =~ Gem::Version.new(version)
457
+
458
+ target_fn = File.join(lyp_lilypond_share_dir(version), 'lilypond/current/scm/font.scm')
459
+ FileUtils.cp(Lyp::FONT_PATCH_FILENAME, target_fn)
460
+ end
461
+
462
+ def copy_fonts_from_all_packages(version, opts)
463
+ return unless Lyp::FONT_COPY_REQ =~ Gem::Version.new(version)
464
+
465
+ ly_fonts_dir = File.join(lyp_lilypond_share_dir(version), 'lilypond/current/fonts')
466
+
467
+ Dir["#{Lyp.packages_dir}/**/fonts"].each do |package_fonts_dir|
468
+
469
+ Dir["#{package_fonts_dir}/*.otf"].each do |fn|
470
+ target_fn = File.join(ly_fonts_dir, 'otf', File.basename(fn))
471
+ FileUtils.cp(fn, target_fn)
472
+ end
473
+
474
+ Dir["#{package_fonts_dir}/*.svg"].each do |fn|
475
+ target_fn = File.join(ly_fonts_dir, 'svg', File.basename(fn))
476
+ FileUtils.cp(fn, target_fn)
477
+ end
478
+
479
+ Dir["#{package_fonts_dir}/*.woff"].each do |fn|
480
+ target_fn = File.join(ly_fonts_dir, 'svg', File.basename(fn))
481
+ FileUtils.cp(fn, target_fn)
482
+ end
483
+ end
484
+ end
485
+
486
+ def lyp_lilypond_share_dir(version)
487
+ File.join(Lyp.lilyponds_dir, version, 'share')
488
+ end
489
+
490
+ def use(version, opts)
491
+ lilypond_list = list.reverse
492
+
493
+ case version
494
+ when 'system'
495
+ lilypond = lilypond_list.find {|v| v[:system] }
496
+ unless lilypond
497
+ raise "Could not find a system installed version of lilypond"
498
+ end
499
+ when 'latest'
500
+ lilypond = lilypond_list.first
501
+ when 'stable'
502
+ lilypond = lilypond_list.find do |v|
503
+ Gem::Version.new(v[:version]).segments[1].even?
504
+ end
505
+ when 'unstable'
506
+ lilypond = lilypond_list.find do |v|
507
+ Gem::Version.new(v[:version]).segments[1].odd?
508
+ end
509
+ else
510
+ version = "~>#{version}.0" if version =~ /^\d+\.\d+$/
511
+ req = Gem::Requirement.new(version)
512
+ lilypond = lilypond_list.find {|v| req =~ Gem::Version.new(v[:version])}
513
+ end
514
+
515
+ unless lilypond
516
+ raise "Could not find a lilypond matching \"#{version}\""
517
+ end
518
+
519
+ set_current_lilypond(lilypond[:path])
520
+ set_default_lilypond(lilypond[:path]) if opts[:default]
521
+
522
+ lilypond
523
+ end
524
+
525
+ def uninstall(version_specifier, opts = {})
526
+ list = list(lyp_only: true)
527
+ if version_specifier
528
+ list.select! {|l| version_match(l[:version], version_specifier, list)}
529
+ elsif !opts[:all]
530
+ # if no version is specified
531
+ raise "No version specifier given.\nTo uninstall all versions run 'lyp uninstall lilypond -a'.\n"
532
+ end
533
+
534
+ if list.empty?
535
+ if version_specifier
536
+ raise "No lilypond found matching #{version_specifier}"
537
+ else
538
+ raise "No lilypond found"
539
+ end
540
+ end
541
+
542
+ list.each do |l|
543
+ puts "Uninstalling lilypond #{l[:version]}" unless opts[:silent]
544
+ set_current_lilypond(nil) if l[:current]
545
+ set_default_lilypond(nil) if l[:default]
546
+ uninstall_lilypond_version(l[:root_path])
547
+ end
548
+ end
549
+
550
+ def uninstall_lilypond_version(path)
551
+ FileUtils.rm_rf(path)
552
+ end
553
+
554
+ def exec(cmd, raise_on_failure = true)
555
+ $_out = ""
556
+ $_err = ""
557
+ success = nil
558
+ Open3.popen3(cmd) do |_in, _out, _err, wait_thr|
559
+ exit_value = wait_thr.value
560
+ $_out = _out.read
561
+ $_err = _err.read
562
+ success = exit_value == 0
563
+ end
564
+ if !success && raise_on_failure
565
+ raise "Error executing cmd #{cmd}: #{$_err}"
566
+ end
567
+ success
568
+ end
569
+ end
570
+ end