lab42_options 0.4.0 → 0.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7cc89febbade4730d1c7112704d5655af6d9cd5
4
- data.tar.gz: 5de219eff28bf69199cdb5c1c59ef157eed0efd9
3
+ metadata.gz: 591fe82bbd4733ef27af5cbf07d0dce041b4a40c
4
+ data.tar.gz: 2de9e059429417fe2dabe852aa306d318df8d721
5
5
  SHA512:
6
- metadata.gz: 1dc5104a71fbe2ca6572fdebb57b88d22215086fb2f8d69b020f49fc9d52f75aeae2c012d590ba20096dc48da311b69eb6478600f079b40b6992fcb9e5c3ce67
7
- data.tar.gz: 3fdaf3fcd7413022e98b1e322c4ded53296bc0005547a49aa492dac0a12ad9bf41e91f27a9cb922633ec60b9ff547c6b2ddab9995dcae2de7e3e0c0952396bdd
6
+ metadata.gz: 56f563ae9f2a64a2f9644143708f3048dc9bd3caa53347e7681217a07e5a967fb8a8e0c482de5de44adb40216bd6be7fac5a0feef0566babf53b20903c9199b5
7
+ data.tar.gz: 94b1247f81b98ab1dfaf6dfed7274c68b3b8e181368ab9ae42d8b120cfd996079a3abee26532c5119a1a90af67afee31e5fab730ddcb4652c66e482afecb13a7
data/README.md CHANGED
@@ -2,82 +2,4 @@
2
2
 
3
3
  ## Options
4
4
 
5
- Lets us specify command line options with the same syntax as ruby parameters
6
-
7
- ```
8
- my_shiny_gem "hello" "world" :verbose answer: 42
9
- ```
10
-
11
- Will yield
12
-
13
- ```ruby
14
- require 'lab42/options'
15
-
16
- options = Lab42::Options.new.parse ARGV
17
- options.args # --> %W{ hello world }
18
- options.first # --> "hello"
19
- options[:verbose] # --> true
20
- options.verbose # --> true
21
- options[:answer] # --> "42"
22
- options.answer # --> "42"
23
- ```
24
-
25
- ## Multiple values
26
-
27
- When providing the same key many times the options object will become an array
28
-
29
- ```
30
- my_even_shiner_gem 42 tag: cool :mixed tag: hot mixed: pickels
31
- ```
32
-
33
- Will yield
34
-
35
- ```ruby
36
-
37
- options.args # --> %W{42}
38
- options[:tag] # --> %W{cool hot}
39
- options.mixed # --> [true, "pickels"]
40
- ```
41
-
42
- ## Required and Default Arguments
43
-
44
- ```ruby
45
- Lab42::Options.new greeting: "hello", target: :required
46
- ```
47
-
48
- will parse as follows:
49
-
50
- ```sh
51
- greet target: "world" # target = "world", greeting = "hello"
52
- greet target: "world" greeting: "cheerio" # target = "world", greeting = "cheerio"
53
- greet greeting: "howdy" # error missing required argument :target"
54
- ```
55
-
56
- ## Reading from yaml files
57
-
58
- ```ruby
59
- options = Lab42::Options.new greeting: "hello", target: :required
60
- options.read_from "./options.yml"
61
- # or
62
- options.read_from :load # read from file indicated by load: <file>
63
- # or
64
- options.read_from load: "./.default_options.yml" # read from file indicated by load: <file>
65
- # defaulting to "./.default_options.yml"
66
- ```
67
-
68
- Although this allows for nested parameters, defaults and requirements for deeper levels are not implemented right now (and are maybe not in the scope of a parameter parser).
69
-
70
- Existence of the yaml file is not reenforced either but that might be a good enhancement for the near
71
- future.
72
-
73
-
74
- ## Missing Features
75
-
76
- * Help Message Generation
77
-
78
- https://github.com/RobertDober/lab42_options/issues/3
79
-
80
- * Typed Arguments
81
-
82
- https://github.com/RobertDober/lab42_options/issues/4
83
-
5
+ Please see the demos for a description [here](https://github.com/RobertDober/lab42_options/tree/master/demo)
data/lib/lab42/options.rb CHANGED
@@ -1,19 +1,55 @@
1
1
  require 'ostruct'
2
+ require 'lab42/core/fn'
3
+ require 'lab42/core/kernel'
4
+
2
5
  require_relative './options/parser'
3
6
  require_relative './options/forwarder'
7
+ require_relative './options/validator'
8
+ require_relative './options/error_issuer'
4
9
 
5
10
  module Lab42
6
11
  class Options
7
- attr_reader :yaml_file
12
+ attr_reader :args, :strict_mode, :yaml_file
13
+
14
+ def define_help txt
15
+ @defined_help_text = txt
16
+ self
17
+ end
18
+
19
+ def defaults
20
+ @__defaults__ ||=
21
+ @registered.select{|_,v| v != :required}
22
+ end
23
+
24
+ def define_help_for opt, txt=nil, &blk
25
+ @help_text_for_option[opt] = txt||blk.(defaults.fetch(opt))
26
+ self
27
+ end
28
+
29
+ def get_help_text
30
+ (
31
+ [ @defined_help_text ] +
32
+ required_options.map do | ro |
33
+ "#{ro}: #{@help_text_for_option.fetch(ro, :required)}"
34
+ end +
35
+ defaults.map do | d,v |
36
+ "#{d}: defaults to #{@help_text_for_option.fetch(d,v.inspect)}"
37
+ end
38
+ ).compact.join("\n")
39
+ end
8
40
 
9
41
  def parse *args
10
42
  args = args.first if Array === args.first
43
+ @args = args
11
44
  @parsed = Lab42::Options::Parser.new.parse( self, args )
45
+
46
+ return if help_asked?
47
+
48
+ validate!
49
+
12
50
  set_defaults
13
- check_required
14
- issue_errors!
15
- result = OpenStruct.new @parsed
16
- result.forwarding_to :kwds
51
+
52
+ OpenStruct.new( @parsed ).forwarding_to :kwds
17
53
  end
18
54
 
19
55
  def read_from file_sym_or_hash
@@ -27,30 +63,65 @@ module Lab42
27
63
  end
28
64
  end
29
65
 
66
+ def strict(new_mode=:errors)
67
+ @strict_mode = new_mode
68
+ self
69
+ end
70
+
71
+ def strict?; !!strict_mode end
72
+
73
+ def warning_mode?
74
+ strict? && /warnings\z/ === strict_mode
75
+ end
76
+
77
+ def error_mode?
78
+ strict? && !warning_mode?
79
+ end
30
80
  private
31
81
  def initialize options={}
32
82
  @registered = {}
33
- @errors = []
83
+ @help_text_for_option = {}
84
+ @strict_mode = :errors
34
85
  options.each do | k, v |
35
86
  register_option k, v
36
87
  end
37
88
  end
38
89
 
39
- def check_required
40
- required_options.each do |ro|
41
- @errors << "Required option #{ro} was not provided" unless @parsed[:kwds].to_h.has_key? ro
42
- end
43
- end
90
+ # @spurious.each do | err |
91
+ # $stderr.puts "invalid parameter #{err.inspect}"
92
+ # end
93
+ # else
94
+ # raise ArgumentError, "invalid parameters: #{@spurious.map(&sendmsg(:inspect)).join(", ")}"
95
+ # end
44
96
 
45
- def defaults
46
- @__defaults__ =
47
- @registered.select{|_,v| v != :required}
97
+
98
+ def help_asked?
99
+ %w{-h --help :help}.any?( &@parsed[:to_a].fn.include? )
48
100
  end
49
101
 
50
- def issue_errors!
51
- return if @errors.empty?
102
+ # @spurious.each do | err |
103
+ # $stderr.puts "invalid parameter #{err.inspect}"
104
+ # end
105
+ # else
106
+ # raise ArgumentError, "invalid parameters: #{@spurious.map(&sendmsg(:inspect)).join(", ")}"
107
+ # end
108
+
109
+ def issue_errors validator
110
+ errors = "validator.missing"
52
111
  raise ArgumentError, @errors.join("\n")
53
112
  end
113
+
114
+ # @spurious.each do | err |
115
+ # $stderr.puts "invalid parameter #{err.inspect}"
116
+ # end
117
+ # else
118
+ # raise ArgumentError, "invalid parameters: #{@spurious.map(&sendmsg(:inspect)).join(", ")}"
119
+ # end
120
+
121
+ def issue_warnings validator
122
+ get_spurious
123
+
124
+ end
54
125
  def register_option k, v
55
126
  @registered[k] = v
56
127
  end
@@ -74,5 +145,23 @@ module Lab42
74
145
  @parsed[:kwds][k] = dv unless @parsed[:kwds].to_h.has_key? k
75
146
  end
76
147
  end
148
+ def validate!
149
+ validator = Validator.new( @registered )
150
+ validator.validate @parsed[:kwds].to_h
151
+ return if validator.valid?
152
+ issuer = ErrorIssuer.new self, validator
153
+ issuer.handle_errors!
154
+ end
155
+ end
156
+
157
+ class PermissiveOptions < Options
158
+ def strict
159
+ raise NoMethodError, "strict message not understood in this object #{self}"
160
+ end
161
+ private
162
+ def initialize *args, **kwds
163
+ super(*args, **kwds)
164
+ @strict_mode = false
165
+ end
77
166
  end
78
167
  end
@@ -0,0 +1,18 @@
1
+ require_relative 'hash_helper'
2
+ module Lab42
3
+ class Options
4
+ Identity = ->(x){x}
5
+
6
+ module ArrayHelpers
7
+
8
+ def counts
9
+ group_by(&Identity).map_values(&:size)
10
+ end
11
+
12
+ def flag_count
13
+ count{|x| x==true}
14
+ end
15
+
16
+ end # module ArrayHelpers
17
+ end # class Options
18
+ end # module Lab42
@@ -0,0 +1,5 @@
1
+ require_relative '../options'
2
+
3
+ Options = Lab42::Options
4
+ PermissiveOptions = Lab42::PermissiveOptions
5
+ ParameterError = Lab42::Options::ParameterError
@@ -0,0 +1,4 @@
1
+ # This will die as soon as it goes into lab42_core
2
+ module Lab42
3
+ Negate = -> (x){!x}
4
+ end # module Lab42
@@ -0,0 +1,14 @@
1
+ module Lab42
2
+ class Options
3
+ module DefaultHelpers
4
+
5
+ def counts
6
+ { self => 1 }
7
+ end
8
+ def flag_count
9
+ self == true ? 1 : 0
10
+ end
11
+
12
+ end # module DefaultHelpers
13
+ end # class Options
14
+ end # module Lab42
@@ -0,0 +1,60 @@
1
+ module Lab42
2
+ class Options
3
+ ParameterError = Class.new ArgumentError
4
+ class ErrorIssuer
5
+ Symbolize = -> (s){ s.to_sym.inspect }
6
+
7
+ attr_reader :options, :validator
8
+
9
+ def handle_errors!
10
+
11
+ if options.error_mode?
12
+ raise_errors_for_missing true
13
+ elsif options.warning_mode?
14
+ warn_for_spurious
15
+ raise_errors_for_missing
16
+ else
17
+ raise_errors_for_missing
18
+ end
19
+ end
20
+
21
+ private
22
+ def format_error_message sp, missing
23
+ spm = if sp.empty?
24
+ nil
25
+ else
26
+ "unspecified parameters: #{sp.map(Symbolize).join(", ")}"
27
+ end
28
+ mm = if missing.empty?
29
+ nil
30
+ else
31
+ "missing required parameters: #{missing.map(Symbolize).join(", ")}"
32
+ end
33
+ [spm, mm].compact.join "\n"
34
+ end
35
+
36
+ def get_spurious_errors
37
+ validator.spurious.keys
38
+ end
39
+
40
+ def initialize options, validator
41
+ @options = options
42
+ @validator = validator
43
+ end
44
+
45
+ def raise_errors_for_missing with_spurious_errors=false
46
+ spurious_errors = with_spurious_errors ? get_spurious_errors : []
47
+ missing_errors = validator.missing.keys
48
+
49
+ return if spurious_errors.empty? && missing_errors.empty?
50
+ raise ParameterError, format_error_message( spurious_errors, missing_errors )
51
+ end
52
+
53
+ def warn_for_spurious
54
+ validator.spurious.each do | k, v |
55
+ $stderr.puts "unspecified parameter passed: #{k}: #{v.inspect}"
56
+ end
57
+ end
58
+ end # class ErrorIssuer
59
+ end # class Options
60
+ end # module Lab42
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ def map_values bhv=nil, &blk
3
+ bhv ||= blk
4
+ inject self.class.new do | h, (k,v) |
5
+ h.merge( k => bhv.(v) )
6
+ end
7
+ end
8
+ end
@@ -1,20 +1,39 @@
1
1
  require 'yaml'
2
+
3
+ require_relative 'array_helpers'
4
+ require_relative 'default_helpers'
5
+
2
6
  module Lab42
3
7
  class Options
4
8
  class Parser
5
9
  attr_accessor :data, :defaults, :kwds, :positionals, :yaml_file
6
- def parse options, args
7
- self.yaml_file = options.yaml_file
10
+
11
+ def parse option, args
12
+ self.yaml_file = option.yaml_file
8
13
  self.data = {to_a: args}
9
- self.kwds = {}
10
- self.positionals = []
11
14
  parse_all args
12
15
  # read_yaml file might need the args parsed
13
- defaults = read_yaml_file
14
- data.merge kwds: OpenStruct.new(defaults.merge(kwds)), args: positionals
16
+ defaults = read_yaml_file || option.defaults
17
+ merged = defaults.merge kwds
18
+ merged = extend_values merged
19
+ result = data.merge kwds: OpenStruct.new( merged ), args: positionals
20
+ check_for_errors option, args if option.strict_mode
21
+ result
15
22
  end
16
23
 
24
+ def errors; @errors.dup end
25
+
17
26
  private
27
+ def initialize
28
+ @errors = []
29
+ self.kwds = {}
30
+ self.positionals = []
31
+ end
32
+
33
+ def check_for_errors options, args
34
+
35
+ end
36
+
18
37
  def convert_hash hs
19
38
  return hs unless Hash === hs
20
39
  hs.keys.inject Hash.new do |h, k|
@@ -26,6 +45,16 @@ module Lab42
26
45
  end
27
46
  end
28
47
 
48
+ def extend_values kwds
49
+ kwds.map_values do | val|
50
+ if Array === val
51
+ val.extend Lab42::Options::ArrayHelpers
52
+ else
53
+ val.extend Lab42::Options::DefaultHelpers rescue val
54
+ end
55
+ end
56
+ end
57
+
29
58
  def parse_all args
30
59
  e = (args[0..-1] || []).enum_for :each
31
60
  loop do
@@ -43,7 +72,7 @@ module Lab42
43
72
  def read_yaml_file
44
73
  read_yaml_file!
45
74
  rescue Errno::ENOENT
46
- {}
75
+ nil
47
76
  end
48
77
 
49
78
  def read_yaml_file!
@@ -54,7 +83,7 @@ module Lab42
54
83
  when String
55
84
  convert_hash YAML.load File.read yaml_file
56
85
  else
57
- {}
86
+ nil
58
87
  end
59
88
  end
60
89
 
@@ -0,0 +1,35 @@
1
+ require 'set'
2
+ require 'forwarder'
3
+ require_relative './core_extension'
4
+
5
+ module Lab42
6
+ class Options
7
+ class Validator
8
+ attr_reader :missing, :spurious
9
+ extend Forwarder
10
+
11
+ forward :missing?, to: :missing, as: :empty?, after: Lab42::Negate
12
+ forward :spurious?, to: :spurious, as: :empty?, after: Lab42::Negate
13
+
14
+ def valid?
15
+ missing.empty? && spurious.empty?
16
+ end
17
+
18
+ def validate parsed
19
+ parsed.each do | key, value |
20
+ @spurious.update key => value unless @allowed.include? key
21
+ @missing.delete key if @missing.include? key
22
+ end
23
+ end
24
+
25
+ private
26
+ def initialize registered
27
+ @spurious = {}
28
+ @missing = Hash[
29
+ *registered.select{ |_,v| v == :required }.map{ |k,| [k => true] }.flatten
30
+ ]
31
+ @allowed = Set.new registered.keys
32
+ end
33
+ end # class Validator
34
+ end # class Options
35
+ end # module Lab42
@@ -1,5 +1,5 @@
1
1
  module Lab42
2
2
  class Options
3
- VERSION = "0.4.0"
3
+ VERSION = "0.5.3"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,55 +1,174 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lab42_options
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-23 00:00:00.000000000 Z
11
+ date: 2014-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lab42_core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.7
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.7
27
+ - !ruby/object:Gem::Dependency
28
+ name: forwarder2
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.0
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: pry
15
43
  requirement: !ruby/object:Gem::Requirement
16
44
  requirements:
17
- - - ~>
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-nav
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
18
60
  - !ruby/object:Gem::Version
19
- version: 0.9.12
61
+ version: '0.2'
20
62
  type: :development
21
63
  prerelease: false
22
64
  version_requirements: !ruby/object:Gem::Requirement
23
65
  requirements:
24
- - - ~>
66
+ - - "~>"
25
67
  - !ruby/object:Gem::Version
26
- version: 0.9.12
68
+ version: '0.2'
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: rspec
29
71
  requirement: !ruby/object:Gem::Requirement
30
72
  requirements:
31
- - - ~>
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: qed
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.9'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.9'
97
+ - !ruby/object:Gem::Dependency
98
+ name: ae
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.8'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.8'
111
+ - !ruby/object:Gem::Dependency
112
+ name: byebug
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.1'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.1'
125
+ - !ruby/object:Gem::Dependency
126
+ name: mocha
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
32
130
  - !ruby/object:Gem::Version
33
- version: 2.13.0
131
+ version: '1.1'
34
132
  type: :development
35
133
  prerelease: false
36
134
  version_requirements: !ruby/object:Gem::Requirement
37
135
  requirements:
38
- - - ~>
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.1'
139
+ - !ruby/object:Gem::Dependency
140
+ name: test-unit
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
39
144
  - !ruby/object:Gem::Version
40
- version: 2.13.0
145
+ version: '2.1'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.1'
41
153
  description: Specify command line arguments with Ruby Syntax
42
154
  email: robert.dober@gmail.com
43
155
  executables: []
44
156
  extensions: []
45
157
  extra_rdoc_files: []
46
158
  files:
159
+ - LICENSE
160
+ - README.md
47
161
  - lib/lab42/options.rb
48
- - lib/lab42/options/parser.rb
162
+ - lib/lab42/options/array_helpers.rb
163
+ - lib/lab42/options/auto_import.rb
164
+ - lib/lab42/options/core_extension.rb
165
+ - lib/lab42/options/default_helpers.rb
166
+ - lib/lab42/options/error_issuer.rb
49
167
  - lib/lab42/options/forwarder.rb
168
+ - lib/lab42/options/hash_helper.rb
169
+ - lib/lab42/options/parser.rb
170
+ - lib/lab42/options/validator.rb
50
171
  - lib/lab42/options/version.rb
51
- - LICENSE
52
- - README.md
53
172
  homepage: https://github.com/RobertDober/lab42_options
54
173
  licenses:
55
174
  - MIT
@@ -60,17 +179,17 @@ require_paths:
60
179
  - lib
61
180
  required_ruby_version: !ruby/object:Gem::Requirement
62
181
  requirements:
63
- - - '>='
182
+ - - ">="
64
183
  - !ruby/object:Gem::Version
65
184
  version: 2.0.0
66
185
  required_rubygems_version: !ruby/object:Gem::Requirement
67
186
  requirements:
68
- - - '>='
187
+ - - ">="
69
188
  - !ruby/object:Gem::Version
70
189
  version: '0'
71
190
  requirements: []
72
191
  rubyforge_project:
73
- rubygems_version: 2.0.3
192
+ rubygems_version: 2.2.2
74
193
  signing_key:
75
194
  specification_version: 4
76
195
  summary: Command Lines the Ruby Way