miniparse 0.3.0

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,67 @@
1
+ module Miniparse
2
+
3
+
4
+ DEFAULT_CONTROLS = {
5
+ # gives an error if there is an unrecognized option either short or long
6
+ # (if not then passes them as arguments)
7
+ raise_on_unrecognized: true,
8
+
9
+ # intercepts .parse ArgumentError (i.e. the commandline user introduced
10
+ # wrong or invalid options) and exits with a helpful msg
11
+ rescue_argument_error: true,
12
+
13
+ # gives usage help and exits if commandline is empty
14
+ # useful if your app always needs args or options to work
15
+ help_cmdline_empty: false,
16
+
17
+ # raises an ArgumentError if there are global args
18
+ # (after parsing options and commands)
19
+ # useful if you don't expect any args
20
+ raise_global_args: false,
21
+
22
+ # formats help output with the width_... controls
23
+ formatted_help: true,
24
+
25
+ width_display: 79,
26
+ width_indent: 3,
27
+ width_left: 18,
28
+
29
+ # use a detailed options help usage msg or a generic one
30
+ detailed_usage: true,
31
+
32
+ # uses --no-... options for all options
33
+ # useful if you want all your options to be negatable by default
34
+ autonegatable: false,
35
+
36
+ # uses short options (besides long ones) for all options
37
+ # useful if you want all your options to be shortable by default
38
+ autoshortable: false,
39
+
40
+ # TODO: consider adding this option
41
+ # admits -h besides --help in help predefined option
42
+ # shortable_help: false,
43
+ }
44
+
45
+ def self.reset_controls
46
+ @behaviour_controls = {}
47
+ @behaviour_controls.merge! DEFAULT_CONTROLS
48
+ end
49
+
50
+ self.reset_controls
51
+
52
+ # raises a KeyError if key is not a recognized control
53
+ # TODO consider raising SyntaxError with a custom msg instead of KeyError
54
+ def self.control(key)
55
+ @behaviour_controls.fetch(key)
56
+ end
57
+
58
+ # raises a KeyError if any key is not a recognized control
59
+ def self.set_control(opts = {})
60
+ opts.keys.each { |key| @behaviour_controls.fetch key }
61
+ @behaviour_controls.merge! opts
62
+ nil
63
+ end
64
+
65
+
66
+
67
+ end
@@ -0,0 +1,113 @@
1
+ module Miniparse
2
+
3
+
4
+
5
+ class OptionBroker
6
+
7
+ attr_reader :parsed_values
8
+
9
+ def initialize(&block)
10
+ @parsed_values = {}
11
+ @added_options = {}
12
+ if block
13
+ add_option(spec: "--help", negatable: false, &block)
14
+ end
15
+ end
16
+
17
+ def add_option(args, &block)
18
+ opt = new_option(args, &block)
19
+ # FEATURE duplicate option overwrite the old one
20
+ # NOTE defining a switch option and a flag option with the same name doesn't work (the first one gets overwritten)
21
+ if opt.shortable
22
+ duplicate = added_options.values.collect { |o|
23
+ (o.shortable && o.name.to_s[0] == opt.name.to_s[0])? o.name : nil }
24
+ duplicate.compact!
25
+ unless duplicate.empty? || duplicate.include?(opt.name)
26
+ raise SyntaxError, "shortable option '#{opt.name}' conflicts with previously defined '" + duplicate.join(', ') + "'"
27
+ end
28
+ end
29
+ @added_options[opt.name] = opt
30
+ end
31
+
32
+ # @param argv is like ARGV but just for this broker
33
+ # @return unprocessed arguments
34
+ def parse_argv(argv)
35
+ av = argv.dup
36
+ rest_argv = []
37
+ while av.size > 0
38
+ arg = av.shift
39
+ opt = check_arg(arg)
40
+ if opt
41
+ val = opt.parse_value(arg)
42
+ if val.nil? && (av.size > 0) && (av[0][0] != '-')
43
+ new_arg = arg + "=" + av.shift
44
+ val = opt.parse_value(new_arg)
45
+ end
46
+ if val.nil?
47
+ raise ArgumentError, "#{opt.class} invalid invocation format '#{arg}'"
48
+ end
49
+ else
50
+ if Miniparse.control(:raise_on_unrecognized) && (arg[0] == '-')
51
+ raise ArgumentError, "unrecognized option '#{arg}'"
52
+ end
53
+ rest_argv << arg
54
+ end
55
+ end
56
+ update_parsed_values
57
+ rest_argv
58
+ end
59
+
60
+ def help_usage
61
+ helps = added_options.values.collect do |opt|
62
+ opt.help_usage
63
+ end
64
+ helps.compact.join(" ")
65
+ end
66
+
67
+ def help_desc
68
+ helps = added_options.values.collect { |opt| opt.help_desc }
69
+ helps.compact.join("\n")
70
+ end
71
+
72
+ protected
73
+
74
+ attr_reader :added_options
75
+
76
+ def new_option(args, &block)
77
+ if SwitchOption.valid_spec args[:spec]
78
+ SwitchOption.new(args, &block)
79
+ elsif FlagOption.valid_spec args[:spec]
80
+ FlagOption.new(args, &block)
81
+ else
82
+ raise SyntaxError,
83
+ "unknown or invalid option specification '#{args[:spec]}'"
84
+ end
85
+ end
86
+
87
+ def check_arg(arg)
88
+ match = added_options.values.collect { |opt| opt.check(arg) ? opt : nil }
89
+ match.compact!
90
+ if match.size > 1
91
+ # NOTE this shouldn't be happening, just a sanity check
92
+ names = match.collect { |opt| opt.name }
93
+ raise "Ambiguous options: '" + names.join(', ') + "'"
94
+ elsif match.size > 0
95
+ match[0]
96
+ else
97
+ nil
98
+ end
99
+ end
100
+
101
+ def update_parsed_values
102
+ @parsed_values = {}
103
+ added_options.values.each do |opt|
104
+ @parsed_values[opt.name] = opt.value if opt.value != nil
105
+ end
106
+ parsed_values
107
+ end
108
+
109
+ end
110
+
111
+
112
+
113
+ end
@@ -0,0 +1,136 @@
1
+ module Miniparse
2
+
3
+ # TODO create external documentation, maybe auto
4
+
5
+
6
+ class Parser
7
+
8
+ # @return after parsing (i.e. specified) rest of arguments
9
+ attr_reader :args, :program_desc
10
+ # @return parsed (i.e. specified) global options
11
+ def options; global_broker.parsed_values; end
12
+
13
+ # @return parsed (i.e. specified) command (nil if none), options and arguments
14
+ def command; commander.parsed_command; end
15
+ def command_options; commander.parsed_values; end
16
+ def command_args; commander.parsed_args; end
17
+
18
+ # @return the command the next add_option will apply to
19
+ def current_command; commander.current_command; end
20
+
21
+
22
+ def initialize(program_description = '')
23
+ @global_broker = OptionBroker.new do
24
+ puts program_desc
25
+ puts help_usage
26
+ puts help_desc
27
+ exit ERR_HELP_REQ
28
+ end
29
+ @commander = Commander.new
30
+ @program_desc = program_description
31
+ end
32
+
33
+ # @param spec is the option specification, similar to the option invocation
34
+ # in the command line (ex. "--debug" or "--verbose LEVEL")
35
+ #
36
+ # @param desc is a short description of the option
37
+ #
38
+ # @param opts are the options to apply to the option
39
+ # :default
40
+ # :negatable (used only for switches)
41
+ # :shortable
42
+ def add_option(spec, desc, opts={}, &block)
43
+ args = opts.merge(spec: spec, desc: desc)
44
+ current_broker.add_option(args, &block)
45
+ end
46
+
47
+ # @param name is the command name (ex. either "kill" or :kill)
48
+ #
49
+ # @param desc is a short description of the command
50
+ #
51
+ # @param opts are the options to apply to the command
52
+ # :no_options indicates the command has no command line options
53
+ def add_command(name, desc, opts={}, &block)
54
+ args = opts.merge(spec: name, desc: desc)
55
+ commander.add_command(args, &block)
56
+ end
57
+
58
+ # @param argv is like ARGV but just for this parser
59
+ # @return unprocessed arguments
60
+ def parse(argv)
61
+ if Miniparse.control(:help_cmdline_empty) && argv.empty?
62
+ puts help_usage
63
+ exit ERR_HELP_REQ
64
+ end
65
+ try_argument do
66
+ global_argv, cmd_name, cmd_argv = commander.split_argv(argv)
67
+ @args = global_broker.parse_argv(global_argv)
68
+ if cmd_name
69
+ commander.parse_argv(cmd_name, cmd_argv)
70
+ end
71
+ if Miniparse.control(:raise_global_args) && (! args.empty?)
72
+ # FIXME review this logic later
73
+ error = current_command ? "unrecognized command" : "extra arguments"
74
+ raise ArgumentError, "#{error} '#{args[0]}'"
75
+ end
76
+ args
77
+ end
78
+ end
79
+
80
+ # @return a help message with the short descriptions
81
+ def help_desc
82
+ #FIXME
83
+ text = ""
84
+ if (global = global_broker.help_desc).size > 0
85
+ text += "\nOptions:\n"
86
+ text += global
87
+ end
88
+ text += commander.help_desc
89
+ text
90
+ end
91
+
92
+ # @return a usage message
93
+ def help_usage
94
+ #FIXME
95
+ if Miniparse.control(:detailed_usage)
96
+ right_text = @global_broker.help_usage
97
+ elsif current_command
98
+ right_text = "[global_options]"
99
+ else
100
+ right_text = "[options]"
101
+ end
102
+ if current_command
103
+ right_text += " <command> [command_options]"
104
+ end
105
+ right_text += " <args>"
106
+ Miniparse.help_usage_format(right_text)
107
+ end
108
+
109
+ protected
110
+
111
+ attr_reader :commander, :global_broker
112
+
113
+ def current_broker
114
+ commander.current_broker || global_broker
115
+ end
116
+
117
+ def try_argument
118
+ #FIXME
119
+ begin
120
+ yield
121
+ rescue ArgumentError => except
122
+ raise unless Miniparse.control(:rescue_argument_error)
123
+ prg = File.basename($PROGRAM_NAME)
124
+ $stderr.puts "#{prg}: error: #{except.message}"
125
+ $stderr.puts
126
+ $stderr.puts help_usage
127
+ # (#{except.backtrace[-1]})")
128
+ exit ERR_ARGUMENT
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+
135
+
136
+ end
@@ -0,0 +1,9 @@
1
+ module Miniparse
2
+
3
+
4
+
5
+ VERSION = "0.3.0"
6
+
7
+
8
+
9
+ end
@@ -0,0 +1,59 @@
1
+ module WordWrap
2
+
3
+
4
+
5
+ # wrap a text at word boundaries
6
+ #
7
+ # @param text: the text to wrap
8
+ # @param width: the maximum width allowed for on line before inserting line breaks
9
+ # @param reformat: if true then the previous line breaks are removed and then the text wrappeed
10
+ # @return text with line breaks inserted as necessasry
11
+ def self.word_wrap(text, width, reformat:false)
12
+ text = text.gsub(/\s*\n/, ' ') if reformat
13
+
14
+ clean = (text[-1] != "\n")
15
+ res = text.gsub(/(.{1,#{width-1}}\S)(\s+|$)/, "\\1\n")
16
+ (clean)? res.chomp : res
17
+ end
18
+
19
+
20
+ # same as word_wrap(...) but returns an array of lines
21
+ #
22
+ # @return an array of lines containing the rearranged text
23
+ def self.word_wrap_lines(*args)
24
+ word_wrap(*args).split("\n")
25
+ end
26
+
27
+
28
+ # same as two_cols_word_wrap(...) but returns an array of lines
29
+ #
30
+ # @return an array of lines containing the merged and rearranged texts
31
+ def self.two_cols_word_wrap_lines(text_left, separator, text_right,
32
+ width_left, width_right, reformat:false)
33
+ left = word_wrap_lines(text_left, width_left, reformat:reformat)
34
+ right = word_wrap_lines(text_right, width_right, reformat:reformat)
35
+
36
+ top = [left.size, right.size].max
37
+ lines = []
38
+ i = 0
39
+ while i < top
40
+ l_part = left[i] || ''
41
+ r_part = right[i] || ''
42
+ lines << "%-*s%s%s" % [width_left, l_part, separator, r_part]
43
+ i += 1
44
+ end
45
+ lines
46
+ end
47
+
48
+
49
+ # wrap two texts in two separate columms
50
+ #
51
+ # @param separator: a string of characters inserted at every line between the two collums, for example ' ' or ' | '
52
+ # @return text with line breaks inserted as necessary
53
+ def self.two_cols_word_wrap(*args)
54
+ two_cols_word_wrap_lines(*args).join("\n")
55
+ end
56
+
57
+
58
+
59
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'miniparse/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "miniparse"
8
+ spec.version = Miniparse::VERSION
9
+ spec.authors = ["Juanma Rodriguez"]
10
+ spec.email = ["jmrod4@gmail.com"]
11
+
12
+ spec.summary = %q{Miniparse is a easy to use yet flexible and powerful ruby library for parsing command-line options.}
13
+ # spec.description = %q{TODO: Write a longer description or delete this line.}
14
+ spec.homepage = "https://github.com/jmrod4/miniparse"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.11"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "minitest", "~> 5.0"
33
+ spec.add_development_dependency "rspec", "~> 3.0"
34
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: miniparse
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Juanma Rodriguez
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description:
70
+ email:
71
+ - jmrod4@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".travis.yml"
79
+ - FAQ.md
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - bin/console
85
+ - bin/setup
86
+ - examples/ex01_simple.rb
87
+ - examples/ex02_options.rb
88
+ - examples/ex03_commands.rb
89
+ - examples/ex04_mixed.rb
90
+ - examples/ex05_block.rb
91
+ - examples/ex06_task_app.rb
92
+ - examples/ex07_controls.rb
93
+ - lib/miniparse.rb
94
+ - lib/miniparse/app.rb
95
+ - lib/miniparse/command.rb
96
+ - lib/miniparse/commander.rb
97
+ - lib/miniparse/control.rb
98
+ - lib/miniparse/option_broker.rb
99
+ - lib/miniparse/parser.rb
100
+ - lib/miniparse/version.rb
101
+ - lib/miniparse/word_wrap.rb
102
+ - miniparse.gemspec
103
+ homepage: https://github.com/jmrod4/miniparse
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.4.8
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Miniparse is a easy to use yet flexible and powerful ruby library for parsing
127
+ command-line options.
128
+ test_files: []
129
+ has_rdoc: