diff --git a/Gemfile b/Gemfile index 5026f76..75b364e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source "https://rubygems.org" -# Specify your gem's dependencies in alda-rb-rb.gemspec gemspec gem "rake", "~> 12.0" gem "minitest", "~> 5.0" +gem "colorize" diff --git a/alda-rb.gemspec b/alda-rb.gemspec index 18542e6..7d95bb6 100644 --- a/alda-rb.gemspec +++ b/alda-rb.gemspec @@ -6,7 +6,7 @@ Gem::Specification.new do |spec| spec.name = "alda-rb" spec.version = Alda::VERSION spec.authors = ["Ulysses Zhan"] - spec.email = ["2938747508@qq.com"] + spec.email = ["UlyssesZhan@gmail.com"] spec.summary = %q{A Ruby library for live-coding music with Alda.} # spec.description = %q{TODO: Write a longer description or delete this line.} @@ -18,7 +18,7 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + spec.metadata["changelog_uri"] = "https://github.com/UlyssesZh/alda-rb/releases" # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. diff --git a/lib/alda-rb.rb b/lib/alda-rb.rb index c93b3f8..a02b97f 100644 --- a/lib/alda-rb.rb +++ b/lib/alda-rb.rb @@ -3,6 +3,7 @@ require 'stringio' require 'irb/ruby-lex' require 'alda-rb/version' +require 'colorize' { Array => -> { "[#{map(&:to_alda_code).join ' '}]" }, @@ -34,6 +35,18 @@ def to_s end end +module Kernel + # Runs the alda command. + # Does not capture output. + # @example + # alda 'version' + # alda 'play', '-c', 'piano: a' + # alda 'repl' + def alda *args + system Alda.executable, *args + end +end + # The module serving as a namespace. module Alda @@ -60,15 +73,25 @@ module Alda ].freeze COMMANDS.each do |command| - define_method command do |options = {}, **command_options| - args = [] - block = ->key, val { args.push "--#{key.to_s.tr ?_, ?-}", val.to_s } - options.each &block + define_method command do |*args, **opts| + block = ->key, val do + next unless val + args.push "--#{key.to_s.tr ?_, ?-}" + args.push val.to_s unless val == true + end + # executable + args.unshift Alda.executable + args.map! &:to_s + # options + Alda.options.each &block + # subcommand args.push command.to_s - command_options.each &block - output = IO.popen [Alda.executable, *args], &:read - raise CommandLineError.new $?, output if $?.exitstatus.nonzero? - output + # subcommand options + opts.each &block + # subprocess + IO.popen(args, &:read).tap do + raise CommandLineError.new $?, _1 if $?.exitstatus.nonzero? + end end end @@ -79,6 +102,9 @@ module Alda singleton_class.attr_accessor :executable @executable = 'alda' + singleton_class.attr_reader :options + @options = {} + # @return Whether the alda server is up. def up? status.include? 'up' @@ -96,6 +122,18 @@ def self.repl REPL.new.run end + # Sets the options of alda command. + # Not the subcommand options. + def self.[] **opts + @options.merge! opts + self + end + + # Clears the command line options. + def self.clear_options + @options.clear + end + # Including this module can make your class have the ability # to have an event list. # See docs below to get an overview of its functions. @@ -348,6 +386,10 @@ def history @session.history.to_s end + def clear_history + @session.clear_history + end + def get_binding binding end @@ -384,7 +426,7 @@ def start def rb_code result = '' begin - buf = Readline.readline '> ', true + buf = Readline.readline '> '.green, true return unless buf result.concat buf, ?\n ltype, indent, continue, block_open = @lex.check_state result @@ -412,7 +454,7 @@ def process_rb_code code end code = @score.events_alda_codes unless code.empty? - $stdout.puts code + $stdout.puts code.yellow play_score code end true @@ -423,7 +465,7 @@ def try_command # :block: begin yield rescue CommandLineError => e - puts e + puts e.message.red end end @@ -448,17 +490,32 @@ def clear_history # The error is raised when one tries to # run a non-existing subcommand of +alda+. - class CommandLineError < Exception + class CommandLineError < StandardError # The Process::Status object representing the status of # the process that runs +alda+ command. attr_reader :status + # The port on which the problematic Alda server runs. + # @example + # begin + # Alda.play({port: 1108}, code: "y") + # rescue CommandLineError => e + # e.port # => 1108 + # end + attr_reader :port + # Create a CommandLineError# object. # @param status The status of the process running +alda+ command. # @param msg The exception message. def initialize status, msg = nil - super /ERROR\s*(?.*)$/ =~ msg ? message : msg&.lines(chomp: true).first + if match = msg&.match(/^\[(?\d+)\]\sERROR\s(?.*)$/) + super match[:message] + @port = match[:port].to_i + else + super msg + @port = nil + end @status = status end end @@ -469,9 +526,9 @@ def initialize status, msg = nil # Alda::Score.new do # motif = f4 f e e d d c2 # g4 f e d c2 # It commented out, error will not occur - # c4 c g g a a g2 motif # OrderError + # c4 c g g a a g2 motif # (OrderError) # end - class OrderError < Exception + class OrderError < StandardError # The expected element gotten if it is of the correct order. # @see #got diff --git a/test/alda-rb/test.rb b/test/alda-rb/to_alda_code_test.rb similarity index 100% rename from test/alda-rb/test.rb rename to test/alda-rb/to_alda_code_test.rb