alda-rb 0.2.1 → 0.3.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +2 -2
- data/CHANGELOG.md +296 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile +3 -2
- data/Gemfile.lock +56 -0
- data/README.md +58 -7
- data/Rakefile +2 -2
- data/alda-rb.gemspec +30 -25
- data/examples/bwv846_prelude.rb +3 -3
- data/examples/clapping_music.rb +1 -1
- data/examples/dot_accessor.rb +1 -1
- data/examples/dynamics.rb +22 -0
- data/examples/entropy.rb +2 -2
- data/examples/hanon.rb +2 -2
- data/examples/marriage_d_amour.rb +114 -0
- data/examples/multi_poly.rb +2 -2
- data/examples/track-volume.rb +32 -0
- data/examples/variables-2.rb +16 -0
- data/exe/alda-irb +32 -0
- data/lib/alda-rb/commandline.rb +176 -33
- data/lib/alda-rb/error.rb +93 -9
- data/lib/alda-rb/event.rb +441 -49
- data/lib/alda-rb/event_list.rb +57 -10
- data/lib/alda-rb/patches.rb +88 -14
- data/lib/alda-rb/repl.rb +330 -61
- data/lib/alda-rb/utils.rb +47 -0
- data/lib/alda-rb/version.rb +1 -1
- data/lib/alda-rb.rb +1 -0
- metadata +74 -8
data/lib/alda-rb/repl.rb
CHANGED
@@ -1,17 +1,10 @@
|
|
1
1
|
require 'colorize'
|
2
2
|
require 'irb/ruby-lex'
|
3
3
|
require 'json'
|
4
|
-
require '
|
4
|
+
require 'reline'
|
5
5
|
require 'stringio'
|
6
|
-
|
7
|
-
|
8
|
-
# :call-seq:
|
9
|
-
# repl() -> nil
|
10
|
-
#
|
11
|
-
# Start a REPL session.
|
12
|
-
def Alda.repl
|
13
|
-
Alda::REPL.new.run
|
14
|
-
end
|
6
|
+
require 'bencode'
|
7
|
+
require 'socket'
|
15
8
|
|
16
9
|
##
|
17
10
|
# An instance of this class is an \REPL session.
|
@@ -32,101 +25,309 @@ end
|
|
32
25
|
# your previous input, run <tt>puts history</tt>.
|
33
26
|
#
|
34
27
|
# Unlike \IRB, this \REPL does not print the result of
|
35
|
-
# the executed codes. Use +p+ if you want.
|
28
|
+
# the executed codes. Use +p+ or +puts+ if you want.
|
36
29
|
#
|
37
30
|
# +Interrupt+ and +SystemExit+ exceptions are rescued and
|
38
31
|
# will not cause the process terminating.
|
39
32
|
# +exit+ terminates the \REPL session instead of the process.
|
40
33
|
#
|
41
|
-
# To start an \REPL session in a ruby program, use
|
34
|
+
# To start an \REPL session in a ruby program, use #run.
|
42
35
|
# To start an \REPL session conveniently from command line,
|
43
|
-
# run command <tt>
|
36
|
+
# run command <tt>alda-irb</tt>.
|
37
|
+
# For details about this command line tool, run <tt>alda-irb --help</tt>.
|
44
38
|
#
|
45
|
-
# $
|
46
|
-
# >
|
47
|
-
#
|
48
|
-
# > piano_ c d e f
|
49
|
-
#
|
39
|
+
# $ alda-irb
|
40
|
+
# > p processes.last
|
41
|
+
# {:id=>"dus", :port=>34317, :state=>nil, :expiry=>nil, :type=>:repl_server}
|
42
|
+
# > piano_; c d e f
|
43
|
+
# piano: [c d e f]
|
50
44
|
# > 5.times do
|
51
|
-
#
|
52
|
-
# >
|
45
|
+
# . c
|
46
|
+
# > end
|
53
47
|
# c c c c c
|
54
|
-
# >
|
55
|
-
#
|
48
|
+
# > score_text
|
49
|
+
# piano: [c d e f]
|
56
50
|
# c c c c c
|
57
51
|
# > play
|
52
|
+
# Playing...
|
58
53
|
# > save 'temp.alda'
|
59
54
|
# > puts `cat temp.alda`
|
60
|
-
#
|
55
|
+
# piano: [c d e f]
|
61
56
|
# c c c c c
|
62
57
|
# > system 'rm temp.alda'
|
63
58
|
# > exit
|
59
|
+
#
|
60
|
+
# Notice that there is a significant difference between \Alda 1 \REPL and \Alda 2 \REPL.
|
61
|
+
# In short, \Alda 2 has a much more powerful \REPL than \Alda 1,
|
62
|
+
# so it dropped the <tt>--history</tt> option in the <tt>alda play</tt> command line interface
|
63
|
+
# ({alda-lang/alda#367}[https://github.com/alda-lang/alda/issues/367]).
|
64
|
+
# It has an nREPL server, and this class simply functions by sending messages to the nREPL server.
|
65
|
+
# However, for \Alda 1, this class maintains necessary information
|
66
|
+
# in the memory of the Ruby program,
|
67
|
+
# and the \REPL is implemented by repeatedly running <tt>alda play</tt> in command line.
|
68
|
+
# Therefore, this class functions differently for \Alda 1 and \Alda 2
|
69
|
+
# and you thus should not modify Alda::generation during an \REPL session.
|
70
|
+
#
|
71
|
+
# It is also possible to use this class as a Ruby wrapper of APIs of the \Alda nREPL server
|
72
|
+
# in \Alda 2.
|
73
|
+
# In this usage, you never need to call #run, and you call #message or #raw_message instead.
|
74
|
+
#
|
75
|
+
# repl = Alda::REPL.new
|
76
|
+
# repl.message :eval_and_play, code: 'piano: c d e f' # => nil
|
77
|
+
# repl.message :eval_and_play, code: 'g a b > c' # => nil
|
78
|
+
# repl.message :score_text # => "piano: [c d e f]\ng a b > c\n"
|
79
|
+
# repl.message :eval_and_play, code: 'this will cause an error' # (raises Alda::NREPLServerError)
|
64
80
|
class Alda::REPL
|
65
81
|
|
66
82
|
##
|
67
83
|
# The score object used in Alda::REPL.
|
68
84
|
#
|
69
85
|
# Includes Alda, so it can refer to alda commandline.
|
86
|
+
# However, the methods Alda::Score#play, Alda::Score#parse and Alda::Score#export
|
87
|
+
# are still retained instead of being overridden by the included module.
|
70
88
|
#
|
71
89
|
# When you are in an \REPL session, you are actually
|
72
90
|
# in an instance of this class,
|
73
91
|
# so you can call the instance methods down here
|
74
92
|
# when you play with an \REPL.
|
75
|
-
class TempScore < Alda::Score
|
93
|
+
class TempScore < ::Alda::Score
|
76
94
|
include Alda
|
77
95
|
|
78
|
-
|
79
|
-
define_method meth, Score.instance_method(meth)
|
96
|
+
%i[play parse export].each do |meth|
|
97
|
+
define_method meth, Alda::Score.instance_method(meth)
|
80
98
|
end
|
81
99
|
|
100
|
+
##
|
101
|
+
# :call-seq:
|
102
|
+
# new(session) -> TempScore
|
103
|
+
#
|
104
|
+
# Creates a new TempScore for the given \REPL session specified by +session+.
|
105
|
+
# It is called in Alda::REPL::new.
|
82
106
|
def initialize session
|
83
107
|
super()
|
84
108
|
@session = session
|
85
109
|
end
|
86
110
|
|
111
|
+
##
|
112
|
+
# :call-seq:
|
113
|
+
# to_s -> String
|
114
|
+
#
|
115
|
+
# Overrides Alda::Score#to_s.
|
116
|
+
# Returns the history.
|
117
|
+
#
|
118
|
+
# $ alda-irb
|
119
|
+
# > harmonica_; a b c
|
120
|
+
# harmonica: [a b c]
|
121
|
+
# > guitar_; c g e
|
122
|
+
# guitar: [c g e]
|
123
|
+
# > p to_s
|
124
|
+
# "harmonica: [a b c]\nguitar: [c g e]\n"
|
87
125
|
def to_s
|
88
|
-
history
|
89
|
-
end
|
90
|
-
|
91
|
-
def history
|
92
|
-
@session.history.to_s
|
126
|
+
@session.history
|
93
127
|
end
|
94
128
|
|
129
|
+
##
|
130
|
+
# :call-seq:
|
131
|
+
# clear_history() -> nil
|
132
|
+
#
|
133
|
+
# Clears all the modifications that have been made to the score
|
134
|
+
# and start a new one.
|
135
|
+
# See #score for an example.
|
95
136
|
def clear_history
|
96
137
|
@session.clear_history
|
97
138
|
end
|
139
|
+
alias new clear_history
|
140
|
+
alias new_score clear_history
|
98
141
|
|
142
|
+
##
|
143
|
+
# :call-seq:
|
144
|
+
# get_binding() -> Binding
|
145
|
+
#
|
146
|
+
# Returns a Binding for the instance eval local environment of this score.
|
147
|
+
# Different callings of this method will return different bindings,
|
148
|
+
# and they do not share local variables.
|
149
|
+
# This method is called in Alda::REPL::new.
|
150
|
+
#
|
151
|
+
# $ alda-irb
|
152
|
+
# > p get_binding.receiver == self
|
153
|
+
# true
|
99
154
|
def get_binding
|
100
155
|
binding
|
101
156
|
end
|
102
157
|
|
158
|
+
##
|
159
|
+
# :call-seq:
|
160
|
+
# score() -> nil
|
161
|
+
#
|
162
|
+
# Print the history (all \Alda code of the score).
|
163
|
+
#
|
164
|
+
# $ alda-irb
|
165
|
+
# > violin_; a b
|
166
|
+
# violin: [a b]
|
167
|
+
# > score
|
168
|
+
# violin: [a b]
|
169
|
+
# > clear_history
|
170
|
+
# > score
|
171
|
+
# > viola_; c
|
172
|
+
# viola: c
|
173
|
+
# > score
|
174
|
+
# viola: c
|
103
175
|
def score
|
104
|
-
|
176
|
+
print @session.color ? @session.history.blue : @session.history
|
177
|
+
nil
|
105
178
|
end
|
179
|
+
alias score_text score
|
106
180
|
|
181
|
+
##
|
182
|
+
# :call-seq:
|
183
|
+
# map() -> nil
|
184
|
+
#
|
185
|
+
# Prints a data representation of the score.
|
186
|
+
# This is the output that you get when you call Alda::Score#parse.
|
107
187
|
def map
|
108
|
-
|
109
|
-
|
188
|
+
json = Alda.v1? ? parse : @session.message(:score_data)
|
189
|
+
json = JSON.generate JSON.parse(json), indent: ' ', space: ' ', object_nl: ?\n, array_nl: ?\n
|
190
|
+
puts @session.color ? json.blue : json
|
191
|
+
end
|
192
|
+
alias score_data map
|
193
|
+
|
194
|
+
##
|
195
|
+
# :call-seq:
|
196
|
+
# score_events() -> nil
|
197
|
+
#
|
198
|
+
# Prints the parsed events output of the score.
|
199
|
+
# This is the output that you get when you call Alda::Score#parse with <tt>output: :events</tt>.
|
200
|
+
def score_events
|
201
|
+
json = Alda.v1? ? parse(output: :events) : @session.message(:score_events)
|
202
|
+
json = JSON.generate JSON.parse(json), indent: ' ', space: ' ', object_nl: ?\n, array_nl: ?\n
|
203
|
+
puts @session.color ? json.blue : json
|
110
204
|
end
|
111
205
|
|
112
206
|
alias quit exit
|
113
|
-
alias new clear_history
|
114
207
|
end
|
115
208
|
|
116
209
|
##
|
117
|
-
# The
|
118
|
-
attr_reader :
|
210
|
+
# The host of the nREPL server. Only useful in \Alda 2.
|
211
|
+
attr_reader :host
|
212
|
+
|
213
|
+
##
|
214
|
+
# The port of the nREPL server. Only useful in \Alda 2.
|
215
|
+
attr_reader :port
|
216
|
+
|
217
|
+
##
|
218
|
+
# Whether the output should be colored.
|
219
|
+
attr_accessor :color
|
220
|
+
|
221
|
+
##
|
222
|
+
# Whether a preview of what \Alda code will be played everytime you input ruby codes.
|
223
|
+
attr_accessor :preview
|
224
|
+
|
225
|
+
##
|
226
|
+
# Whether to use Reline for input.
|
227
|
+
# When it is false, the \REPL session will be less buggy but less powerful.
|
228
|
+
attr_accessor :reline
|
119
229
|
|
120
230
|
##
|
121
231
|
# :call-seq:
|
122
|
-
# new() -> Alda::REPL
|
232
|
+
# new(**opts) -> Alda::REPL
|
123
233
|
#
|
124
234
|
# Creates a new Alda::REPL.
|
125
|
-
|
235
|
+
# The parameter +color+ specifies whether the output should be colored (sets #color).
|
236
|
+
# The parameter +preview+ specifies whether a preview of what \Alda code will be played
|
237
|
+
# everytime you input ruby codes (sets #preview).
|
238
|
+
# The parameter +reline+ specifies whether to use Reline for input.
|
239
|
+
#
|
240
|
+
# The +opts+ are passed to the command line of <tt>alda repl</tt>.
|
241
|
+
# Available options are +host+, +port+, etc.
|
242
|
+
# Run <tt>alda repl --help</tt> for more info.
|
243
|
+
# If +port+ is specified and +host+ is not or is specified to be <tt>"localhost"</tt>
|
244
|
+
# or <tt>"127.0.0.1"</tt>, then this method will try to connect to an existing
|
245
|
+
# \Alda REPL server.
|
246
|
+
# A new one will be started only if no existing server is found.
|
247
|
+
#
|
248
|
+
# The +opts+ are ignored in \Alda 1.
|
249
|
+
def initialize color: true, preview: true, reline: true, **opts
|
126
250
|
@score = TempScore.new self
|
127
251
|
@binding = @score.get_binding
|
128
|
-
|
129
|
-
@
|
252
|
+
# IRB once changed the API of RubyLex#initialize. Take care of that.
|
253
|
+
@lex = RubyLex.new *(RubyLex.instance_method(:initialize).arity == 0 ? [] : [@binding])
|
254
|
+
@color = color
|
255
|
+
@preview = preview
|
256
|
+
@reline = reline
|
257
|
+
setup_repl opts
|
258
|
+
end
|
259
|
+
|
260
|
+
##
|
261
|
+
# :call-seq:
|
262
|
+
# setup_repl(opts) -> nil
|
263
|
+
#
|
264
|
+
# Sets up the \REPL session.
|
265
|
+
# This method is called in ::new.
|
266
|
+
# After you #terminate the session,
|
267
|
+
# you cannot use the \REPL anymore unless you call this method again.
|
268
|
+
def setup_repl opts
|
269
|
+
if Alda.v1?
|
270
|
+
@history = StringIO.new
|
271
|
+
else
|
272
|
+
@port = (opts.fetch :port, -1).to_i
|
273
|
+
@host = opts.fetch :host, 'localhost'
|
274
|
+
unless @port.positive? && %w[localhost 127.0.0.1].include?(@host) &&
|
275
|
+
Alda.processes.any? { _1[:port] == @port && _1[:type] == :repl_server }
|
276
|
+
Alda.env(ALDA_DISABLE_SPAWNING: :no) { @nrepl_pipe = Alda.pipe :repl, **opts, server: true }
|
277
|
+
/nrepl:\/\/[a-zA-Z0-9._\-]+:(?<port>\d+)/ =~ @nrepl_pipe.gets
|
278
|
+
@port = port.to_i
|
279
|
+
Process.detach @nrepl_pipe.pid
|
280
|
+
end
|
281
|
+
@socket = TCPSocket.new @host, @port
|
282
|
+
@bencode_parser = BEncode::Parser.new @socket
|
283
|
+
end
|
284
|
+
nil
|
285
|
+
end
|
286
|
+
|
287
|
+
##
|
288
|
+
# :call-seq:
|
289
|
+
# raw_message(contents) -> Hash
|
290
|
+
#
|
291
|
+
# Sends a message to the nREPL server and returns the response.
|
292
|
+
# The parameter +contents+ is a Hash or a JSON string.
|
293
|
+
#
|
294
|
+
# repl = Alda::REPL.new
|
295
|
+
# repl.raw_message op: 'describe' # => {"ops"=>...}
|
296
|
+
def raw_message contents
|
297
|
+
Alda::GenerationError.assert_generation [:v2]
|
298
|
+
contents = JSON.parse contents if contents.is_a? String
|
299
|
+
@socket.write contents.bencode
|
300
|
+
@bencode_parser.parse!
|
301
|
+
end
|
302
|
+
|
303
|
+
##
|
304
|
+
# :call-seq:
|
305
|
+
# message(op, **params) -> String or Hash
|
306
|
+
#
|
307
|
+
# Sends a message to the nREPL server with the following format,
|
308
|
+
# with +op+ being the operation name (the +op+ field in the message),
|
309
|
+
# and +params+ being the parameters (other fields in the message).
|
310
|
+
# Then, this method analyzes the response.
|
311
|
+
# If there is an error, raises Alda::NREPLServerError.
|
312
|
+
# Otherwise, if the response contains only one field, return the content of that field (a String).
|
313
|
+
# Otherwise, return the whole response as a Hash.
|
314
|
+
#
|
315
|
+
# repl = Alda::REPL.new
|
316
|
+
# repl.message :eval_and_play, code: 'piano: c d e f' # => nil
|
317
|
+
# repl.message :eval_and_play, code: 'g a b > c' # => nil
|
318
|
+
# repl.message :score_text # => "piano: [c d e f]\ng a b > c\n"
|
319
|
+
# repl.message :eval_and_play, code: 'this will cause an error' # (raises Alda::NREPLServerError)
|
320
|
+
def message op, **params
|
321
|
+
result = raw_message op: Alda::Utils.snake_to_slug(op), **params
|
322
|
+
result.transform_keys! { Alda::Utils.slug_to_snake _1 }
|
323
|
+
if (status = result.delete :status).include? 'error'
|
324
|
+
raise Alda::NREPLServerError.new @host, @port, result.delete(:problems), status
|
325
|
+
end
|
326
|
+
case result.size
|
327
|
+
when 0 then nil
|
328
|
+
when 1 then result.values.first
|
329
|
+
else result
|
330
|
+
end
|
130
331
|
end
|
131
332
|
|
132
333
|
##
|
@@ -134,10 +335,11 @@ class Alda::REPL
|
|
134
335
|
# run() -> nil
|
135
336
|
#
|
136
337
|
# Runs the session.
|
137
|
-
# Includes the start, the main loop, and the termination.
|
338
|
+
# Includes the start (#start), the main loop, and the termination (#terminate).
|
138
339
|
def run
|
139
340
|
start
|
140
341
|
while code = rb_code
|
342
|
+
next if code.empty?
|
141
343
|
break unless process_rb_code code
|
142
344
|
end
|
143
345
|
terminate
|
@@ -159,18 +361,46 @@ class Alda::REPL
|
|
159
361
|
# It can intelligently continue reading if the code is not complete yet.
|
160
362
|
def rb_code
|
161
363
|
result = ''
|
364
|
+
indent = 0
|
162
365
|
begin
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
ltype, indent, continue, block_open = @lex.check_state result
|
366
|
+
result.concat readline(indent).tap { return unless _1 }, ?\n
|
367
|
+
# IRB once changed the API of RubyLex#check_state. Take care of that.
|
368
|
+
opts = @lex.method(:check_state).arity.positive? ? {} : { context: @binding }
|
369
|
+
ltype, indent, continue, block_open = @lex.check_state result, **opts
|
167
370
|
rescue Interrupt
|
168
371
|
$stdout.puts
|
169
|
-
|
372
|
+
return ''
|
170
373
|
end while ltype || indent.nonzero? || continue || block_open
|
171
374
|
result
|
172
375
|
end
|
173
376
|
|
377
|
+
##
|
378
|
+
# :call-seq:
|
379
|
+
# readline(indent = 0) -> String
|
380
|
+
#
|
381
|
+
# Prompts the user to input a line.
|
382
|
+
# The parameter +indent+ is the indentation level.
|
383
|
+
# Twice the number of spaces is already in the input field before the user fills in
|
384
|
+
# if #reline is true.
|
385
|
+
# The prompt hint is different for zero +indent+ and nonzero +indent+.
|
386
|
+
# Returns the user input.
|
387
|
+
def readline indent = 0
|
388
|
+
prompt = indent.nonzero? ? '. ' : '> '
|
389
|
+
prompt = prompt.green if @color
|
390
|
+
if @reline
|
391
|
+
Reline.pre_input_hook = -> do
|
392
|
+
Reline.insert_text ' ' * indent
|
393
|
+
Reline.redisplay
|
394
|
+
Reline.pre_input_hook = nil
|
395
|
+
end
|
396
|
+
Reline.readline prompt, true
|
397
|
+
else
|
398
|
+
$stdout.print prompt
|
399
|
+
$stdout.flush
|
400
|
+
$stdin.gets chomp: true
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
174
404
|
##
|
175
405
|
# :call-seq:
|
176
406
|
# process_rb_code(code) -> true or false
|
@@ -182,18 +412,16 @@ class Alda::REPL
|
|
182
412
|
@score.clear
|
183
413
|
begin
|
184
414
|
@binding.eval code
|
185
|
-
rescue StandardError, ScriptError => e
|
415
|
+
rescue StandardError, ScriptError, Interrupt => e
|
186
416
|
$stderr.print e.full_message
|
187
417
|
return true
|
188
|
-
rescue Interrupt
|
189
|
-
return true
|
190
418
|
rescue SystemExit
|
191
419
|
return false
|
192
420
|
end
|
193
421
|
code = @score.events_alda_codes
|
194
422
|
unless code.empty?
|
195
|
-
$stdout.puts code.yellow
|
196
|
-
play_score code
|
423
|
+
$stdout.puts @color ? code.yellow : code
|
424
|
+
try_command { play_score code }
|
197
425
|
end
|
198
426
|
true
|
199
427
|
end
|
@@ -202,12 +430,15 @@ class Alda::REPL
|
|
202
430
|
# :call-seq:
|
203
431
|
# try_command() { ... } -> obj
|
204
432
|
#
|
205
|
-
#
|
433
|
+
# Run the block.
|
434
|
+
# In \Alda 1, catches Alda::CommandLineError.
|
435
|
+
# In \Alda 2, catches Alda::NREPLServerError.
|
436
|
+
# If an error is caught, prints the error message (in red if #color is true).
|
206
437
|
def try_command
|
207
438
|
begin
|
208
439
|
yield
|
209
|
-
rescue Alda::CommandLineError => e
|
210
|
-
puts e.message.red
|
440
|
+
rescue Alda.v1? ? Alda::CommandLineError : Alda::NREPLServerError => e
|
441
|
+
puts @color ? e.message.red : e.message
|
211
442
|
end
|
212
443
|
end
|
213
444
|
|
@@ -215,11 +446,15 @@ class Alda::REPL
|
|
215
446
|
# :call-seq:
|
216
447
|
# play_score(code) -> nil
|
217
448
|
#
|
218
|
-
#
|
449
|
+
# Appends +code+ to the history and plays the +code+ as \Alda code.
|
450
|
+
# In \Alda 1, plays the score by sending +code+ to command line alda.
|
451
|
+
# In \Alda 2, sends +code+ to the nREPL server for evaluating and playing.
|
219
452
|
def play_score code
|
220
|
-
|
453
|
+
if Alda.v1?
|
221
454
|
Alda.play code: code, history: @history
|
222
455
|
@history.puts code
|
456
|
+
else
|
457
|
+
message :eval_and_play, code: code
|
223
458
|
end
|
224
459
|
end
|
225
460
|
|
@@ -228,18 +463,52 @@ class Alda::REPL
|
|
228
463
|
# terminate() -> nil
|
229
464
|
#
|
230
465
|
# Terminates the REPL session.
|
231
|
-
#
|
466
|
+
# In \Alda 1, just calls #clear_history.
|
467
|
+
# In \Alda 2, sends a SIGINT to the nREPL server if it was spawned by the Ruby program.
|
232
468
|
def terminate
|
233
|
-
|
469
|
+
if Alda.v1?
|
470
|
+
clear_history
|
471
|
+
else
|
472
|
+
if @nrepl_pipe
|
473
|
+
if Alda::Utils.win_platform?
|
474
|
+
unless IO.popen(['taskkill', '/f', '/pid', @nrepl_pipe.pid.to_s], &:read).include? 'SUCCESS'
|
475
|
+
Alda::Warning.warn 'failed to kill nREPL server; may become zombie process'
|
476
|
+
end
|
477
|
+
else
|
478
|
+
Process.kill :INT, @nrepl_pipe.pid
|
479
|
+
end
|
480
|
+
@nrepl_pipe.close
|
481
|
+
end
|
482
|
+
@socket.close
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
##
|
487
|
+
# :call-seq:
|
488
|
+
# history() -> String
|
489
|
+
#
|
490
|
+
# In \Alda 1, it is the same as an attribute reader.
|
491
|
+
# In \Alda 2, it asks the nREPL server for its score text and returns it.
|
492
|
+
def history
|
493
|
+
if Alda.v1?
|
494
|
+
@history
|
495
|
+
else
|
496
|
+
try_command { message :score_text }
|
497
|
+
end
|
234
498
|
end
|
235
499
|
|
236
500
|
##
|
237
501
|
# :call-seq:
|
238
502
|
# clear_history() -> nil
|
239
503
|
#
|
240
|
-
#
|
504
|
+
# In \Alda 1, clears #history.
|
505
|
+
# In \Alda 2, askes the nREPL server to clear its history (start a new score).
|
241
506
|
def clear_history
|
242
|
-
|
507
|
+
if Alda.v1?
|
508
|
+
@history = StringIO.new
|
509
|
+
else
|
510
|
+
try_command { message :new_score }
|
511
|
+
end
|
243
512
|
nil
|
244
513
|
end
|
245
514
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
##
|
2
|
+
# Some useful functions.
|
3
|
+
module Alda::Utils
|
4
|
+
|
5
|
+
##
|
6
|
+
# :call-seq:
|
7
|
+
# warn(message) -> nil
|
8
|
+
#
|
9
|
+
# Prints a warning message to standard error, appended by a newline.
|
10
|
+
# The message is prefixed with the filename and lineno of the caller
|
11
|
+
# (the lowest level where the file is not an alda-rb source file).
|
12
|
+
def warn message
|
13
|
+
location = caller_locations.find { !_1.path.start_with? __dir__ }
|
14
|
+
Warning.warn "#{location.path}:#{location.lineno}: #{message}\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# :call-seq:
|
19
|
+
# win_platform? -> true or false
|
20
|
+
#
|
21
|
+
# Returns whether the current platform is Windows.
|
22
|
+
def win_platform?
|
23
|
+
Gem.win_platform?
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# :call-seq:
|
28
|
+
# snake_to_slug(sym) -> String
|
29
|
+
#
|
30
|
+
# Converts a snake_case Symbol to a slug-case String.
|
31
|
+
# The inverse of ::slug_to_snake.
|
32
|
+
def snake_to_slug sym
|
33
|
+
sym.to_s.gsub ?_, ?-
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# :call-seq:
|
38
|
+
# slug_to_snake(str) -> Symbol
|
39
|
+
#
|
40
|
+
# Converts a slug-case String to a snake_case Symbol.
|
41
|
+
# The inverse of ::snake_to_slug.
|
42
|
+
def slug_to_snake str
|
43
|
+
str.to_s.gsub(?-, ?_).to_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
module_function :warn, :win_platform?, :snake_to_slug, :slug_to_snake
|
47
|
+
end
|
data/lib/alda-rb/version.rb
CHANGED