lyp-win 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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