diff --git a/lib/uplot/command.rb b/lib/uplot/command.rb index 05d137f..64d1b14 100644 --- a/lib/uplot/command.rb +++ b/lib/uplot/command.rb @@ -1,13 +1,14 @@ require 'optparse' require_relative 'preprocessing' require_relative 'command/params' +require_relative 'command/parser' module Uplot Data = Struct.new(:headers, :series) class Command - attr_accessor :params, :command - attr_reader :main_parser, :sub_parsers, :raw_inputs, :data, :fmt + attr_accessor :params + attr_reader :raw_inputs, :data, :fmt, :parser def initialize @params = Params.new @@ -22,224 +23,15 @@ module Uplot @raw_inputs = [] @debug = false + + @parser = Parser.new end - def create_default_parser - OptionParser.new do |opt| - opt.program_name = 'uplot' - opt.version = Uplot::VERSION - opt.on('-O', '--output', TrueClass) do |v| - @output = v - end - opt.on('-d', '--delimiter VAL', 'use DELIM instead of TAB for field delimiter', String) do |v| - @delimiter = v - end - opt.on('-H', '--headers', 'specify that the input has header row', TrueClass) do |v| - @headers = v - end - opt.on('-T', '--transpose', TrueClass) do |v| - @transpose = v - end - opt.on('-t', '--title VAL', 'print string on the top of plot', String) do |v| - params.title = v - end - opt.on('-x', '--xlabel VAL', 'print string on the bottom of the plot', String) do |v| - params.xlabel = v - end - opt.on('-y', '--ylabel VAL', 'print string on the far left of the plot', String) do |v| - params.ylabel = v - end - opt.on('-w', '--width VAL', 'number of characters per row', Integer) do |v| - params.width = v - end - opt.on('-h', '--height VAL', 'number of rows', Numeric) do |v| - params.height = v - end - opt.on('-b', '--border VAL', 'specify the style of the bounding box', String) do |v| - params.border = v.to_sym - end - opt.on('-m', '--margin VAL', 'number of spaces to the left of the plot', Numeric) do |v| - params.margin = v - end - opt.on('-p', '--padding VAL', 'space of the left and right of the plot', Numeric) do |v| - params.padding = v - end - opt.on('-c', '--color VAL', 'color of the drawing', String) do |v| - params.color = v =~ /\A[0-9]+\z/ ? v.to_i : v.to_sym - end - opt.on('--[no-]labels', 'hide the labels', TrueClass) do |v| - params.labels = v - end - opt.on('--fmt VAL', 'xyy, xyxy', String) do |v| - @fmt = v - end - opt.on('--debug', TrueClass) do |v| - @debug = v - end - yield opt if block_given? - end - end - - def create_sub_parsers - parsers = Hash.new do |h, k| - h[k] = create_default_parser do |parser| - parser.banner = <<~MSG - Usage: uplot #{k} [options] - - Options: - MSG - end - end - - parsers[:barplot] = \ - parsers[:bar] - .on('--symbol VAL', String) do |v| - params.symbol = v - end - .on('--xscale VAL', String) do |v| - params.xscale = v - end - .on('--count', TrueClass) do |v| - @count = v - end - - parsers[:count] = \ - parsers[:c] # barplot -c - .on('--symbol VAL', String) do |v| - params.symbol = v - end - - parsers[:histogram] = \ - parsers[:hist] - .on('-n', '--nbins VAL', Numeric) do |v| - params.nbins = v - end - .on('--closed VAL', String) do |v| - params.closed = v - end - .on('--symbol VAL', String) do |v| - params.symbol = v - end - - parsers[:lineplot] = \ - parsers[:line] - .on('--canvas VAL', String) do |v| - params.canvas = v - end - .on('--xlim VAL', String) do |v| - params.xlim = get_lim(v) - end - .on('--ylim VAL', String) do |v| - params.ylim = get_lim(v) - end - - parsers[:lineplots] = \ - parsers[:lines] - .on('--canvas VAL', String) do |v| - params.canvas = v - end - .on('--xlim VAL', String) do |v| - params.xlim = get_lim(v) - end - .on('--ylim VAL', String) do |v| - params.ylim = get_lim(v) - end - - parsers[:scatter] = \ - parsers[:s] - .on('--canvas VAL', String) do |v| - params.canvas = v - end - .on('--xlim VAL', String) do |v| - params.xlim = get_lim(v) - end - .on('--ylim VAL', String) do |v| - params.ylim = get_lim(v) - end - - parsers[:density] = \ - parsers[:d] - .on('--grid', TrueClass) do |v| - params.grid = v - end - .on('--xlim VAL', String) do |v| - params.xlim = get_lim(v) - end - .on('--ylim VAL', String) do |v| - params.ylim = get_lim(v) - end - - parsers[:boxplot] = \ - parsers[:box] - .on('--xlim VAL', String) do |v| - params.xlim = get_lim(v) - end - - parsers[:colors] - .on('-n', '--names', TrueClass) do |v| - @color_names = v - end - - # Preventing the generation of new sub-commands - parsers.default = nil - parsers - end - - def create_main_parser - create_default_parser do |main_parser| - # Usage and help messages - main_parser.banner = \ - <<~MSG - Program: uplot (Tools for plotting on the terminal) - Version: #{Uplot::VERSION} (using unicode_plot #{UnicodePlot::VERSION}) - - Usage: uplot [options] - - Command: - #{sub_parsers.keys.join("\n ")} - - Options: - MSG - end - end - - def parse_options(argv = ARGV) - @sub_parsers = create_sub_parsers - @main_parser = create_main_parser - - begin - main_parser.order!(argv) - rescue OptionParser::ParseError => e - warn "uplot: #{e.message}" - exit 1 - end - - @command = argv.shift&.to_sym - - unless sub_parsers.has_key?(command) - if command.nil? - warn main_parser.help - else - warn "uplot: unrecognized command '#{command}'" - end - exit 1 - end - parser = sub_parsers[command] - - begin - parser.parse!(argv) unless argv.empty? - rescue OptionParser::ParseError => e - warn "uplot: #{e.message}" - exit 1 - end - end - - def get_lim(str) - str.split(/-|:|\.\./)[0..1].map(&:to_f) - end def run - parse_options + parser.parse_options + command = parser.command + params = parser.params if command == :colors Plot.colors diff --git a/lib/uplot/command/parser.rb b/lib/uplot/command/parser.rb new file mode 100644 index 0000000..6b506fa --- /dev/null +++ b/lib/uplot/command/parser.rb @@ -0,0 +1,225 @@ +module Uplot + class Command + class Parser + attr_reader :command, :params, :main_parser, :sub_parsers + + def initialize + @sub_parsers = create_sub_parsers + @main_parser = create_main_parser + @command = nil + @params = Params.new + end + + def create_default_parser + OptionParser.new do |opt| + opt.program_name = 'uplot' + opt.version = Uplot::VERSION + opt.on('-O', '--output', TrueClass) do |v| + @output = v + end + opt.on('-d', '--delimiter VAL', 'use DELIM instead of TAB for field delimiter', String) do |v| + @delimiter = v + end + opt.on('-H', '--headers', 'specify that the input has header row', TrueClass) do |v| + @headers = v + end + opt.on('-T', '--transpose', TrueClass) do |v| + @transpose = v + end + opt.on('-t', '--title VAL', 'print string on the top of plot', String) do |v| + params.title = v + end + opt.on('-x', '--xlabel VAL', 'print string on the bottom of the plot', String) do |v| + params.xlabel = v + end + opt.on('-y', '--ylabel VAL', 'print string on the far left of the plot', String) do |v| + params.ylabel = v + end + opt.on('-w', '--width VAL', 'number of characters per row', Integer) do |v| + params.width = v + end + opt.on('-h', '--height VAL', 'number of rows', Numeric) do |v| + params.height = v + end + opt.on('-b', '--border VAL', 'specify the style of the bounding box', String) do |v| + params.border = v.to_sym + end + opt.on('-m', '--margin VAL', 'number of spaces to the left of the plot', Numeric) do |v| + params.margin = v + end + opt.on('-p', '--padding VAL', 'space of the left and right of the plot', Numeric) do |v| + params.padding = v + end + opt.on('-c', '--color VAL', 'color of the drawing', String) do |v| + params.color = v =~ /\A[0-9]+\z/ ? v.to_i : v.to_sym + end + opt.on('--[no-]labels', 'hide the labels', TrueClass) do |v| + params.labels = v + end + opt.on('--fmt VAL', 'xyy, xyxy', String) do |v| + @fmt = v + end + opt.on('--debug', TrueClass) do |v| + @debug = v + end + yield opt if block_given? + end + end + + def create_sub_parsers + parsers = Hash.new do |h, k| + h[k] = create_default_parser do |parser| + parser.banner = <<~MSG + Usage: uplot #{k} [options] + + Options: + MSG + end + end + + parsers[:barplot] = \ + parsers[:bar] + .on('--symbol VAL', String) do |v| + params.symbol = v + end + .on('--xscale VAL', String) do |v| + params.xscale = v + end + .on('--count', TrueClass) do |v| + @count = v + end + + parsers[:count] = \ + parsers[:c] # barplot -c + .on('--symbol VAL', String) do |v| + params.symbol = v + end + + parsers[:histogram] = \ + parsers[:hist] + .on('-n', '--nbins VAL', Numeric) do |v| + params.nbins = v + end + .on('--closed VAL', String) do |v| + params.closed = v + end + .on('--symbol VAL', String) do |v| + params.symbol = v + end + + parsers[:lineplot] = \ + parsers[:line] + .on('--canvas VAL', String) do |v| + params.canvas = v + end + .on('--xlim VAL', String) do |v| + params.xlim = get_lim(v) + end + .on('--ylim VAL', String) do |v| + params.ylim = get_lim(v) + end + + parsers[:lineplots] = \ + parsers[:lines] + .on('--canvas VAL', String) do |v| + params.canvas = v + end + .on('--xlim VAL', String) do |v| + params.xlim = get_lim(v) + end + .on('--ylim VAL', String) do |v| + params.ylim = get_lim(v) + end + + parsers[:scatter] = \ + parsers[:s] + .on('--canvas VAL', String) do |v| + params.canvas = v + end + .on('--xlim VAL', String) do |v| + params.xlim = get_lim(v) + end + .on('--ylim VAL', String) do |v| + params.ylim = get_lim(v) + end + + parsers[:density] = \ + parsers[:d] + .on('--grid', TrueClass) do |v| + params.grid = v + end + .on('--xlim VAL', String) do |v| + params.xlim = get_lim(v) + end + .on('--ylim VAL', String) do |v| + params.ylim = get_lim(v) + end + + parsers[:boxplot] = \ + parsers[:box] + .on('--xlim VAL', String) do |v| + params.xlim = get_lim(v) + end + + parsers[:colors] + .on('-n', '--names', TrueClass) do |v| + @color_names = v + end + + # Preventing the generation of new sub-commands + parsers.default = nil + parsers + end + + def create_main_parser + create_default_parser do |main_parser| + # Usage and help messages + main_parser.banner = \ + <<~MSG + Program: uplot (Tools for plotting on the terminal) + Version: #{Uplot::VERSION} (using unicode_plot #{UnicodePlot::VERSION}) + + Usage: uplot [options] + + Command: + #{sub_parsers.keys.join("\n ")} + + Options: + MSG + end + end + + def parse_options(argv = ARGV) + begin + main_parser.order!(argv) + rescue OptionParser::ParseError => e + warn "uplot: #{e.message}" + exit 1 + end + + @command = argv.shift&.to_sym + + unless sub_parsers.has_key?(command) + if command.nil? + warn main_parser.help + else + warn "uplot: unrecognized command '#{command}'" + end + exit 1 + end + parser = sub_parsers[command] + + begin + parser.parse!(argv) unless argv.empty? + rescue OptionParser::ParseError => e + warn "uplot: #{e.message}" + exit 1 + end + end + + def get_lim(str) + str.split(/-|:|\.\./)[0..1].map(&:to_f) + end + end + end +end