samovar 1.10.0 → 2.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1e5553e013cfe19724bdbdb1cb1c989025216e737e6b7591068f97fc4059fb3
4
- data.tar.gz: 7430857e0d0d389b041056d2cac1f532d6215014ed06b4f60fc2a8e0fdc4161f
3
+ metadata.gz: 3d7ea0c2f3e68eea317420d35531661fcca9c912d034eb26eae71572ba7977cf
4
+ data.tar.gz: 25406ee8967a8a5854145683cdca839c62ba1999f8f8077582705423ca0613ed
5
5
  SHA512:
6
- metadata.gz: 9a47b68e6b5c28c2539d9089d51752320a289056f762693edef1b7ec84dff3f8befc3f5196700f95243ca3bb1461413e1fc74c3d6d6619c816eeb873f5491800
7
- data.tar.gz: b083e569e8f23de9da31c302dbd1526b32cbfbb9d6087430f0103ff68d3ca3de204d87bf4f541179f3c2b56f3f71c10b4ac0c32331a1261d740f5f3d7a88445e
6
+ metadata.gz: b48456ba224e7025fa4e16d3dc5a472795d6261f2861f50daf8b948dc28eb1936c56b23f3e0dc046260d59a933153a8b78ab7e66d80ccef265f1c73b89bc1d48
7
+ data.tar.gz: f1a65ab649c96805d5addc23f73dee070f5d505be81842f5f0944a0d52a600024e8ad12d4d18d1eadb91786c349688797f31a16853ee0af5708367287e345ed9
data/Gemfile CHANGED
@@ -4,6 +4,5 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem 'simplecov'
8
- gem 'coveralls', require: false
7
+ gem 'pry'
9
8
  end
@@ -27,36 +27,23 @@ require_relative 'split'
27
27
 
28
28
  require_relative 'output'
29
29
 
30
- require_relative 'command/system'
31
- require_relative 'command/track_time'
30
+ require_relative 'error'
32
31
 
33
32
  module Samovar
34
- class IncompleteParse < StandardError
35
- end
36
-
37
33
  class Command
38
- def self.parse(input)
39
- command = self.new(input)
40
-
41
- raise IncompleteParse.new("Could not parse #{input}") unless input.empty?
34
+ # The top level entry point for parsing ARGV.
35
+ def self.parse(input = ARGV)
36
+ self.new(input)
37
+ rescue Error => error
38
+ error.command.print_usage(output: $stderr) do |formatter|
39
+ formatter.map(error)
40
+ end
42
41
 
43
- return command
44
- end
45
-
46
- def self.[](*input)
47
- self.parse(input)
48
- end
49
-
50
- def [](*input)
51
- self.dup.tap{|command| command.parse(input)}
42
+ return nil
52
43
  end
53
44
 
54
- def parse(input)
55
- self.class.table.parse(input, self)
56
- end
57
-
58
- def initialize(input = nil)
59
- parse(input) if input
45
+ def self.[](*input, **options)
46
+ self.new(input, **options)
60
47
  end
61
48
 
62
49
  class << self
@@ -64,7 +51,7 @@ module Samovar
64
51
  end
65
52
 
66
53
  def self.table
67
- @table ||= Table.new
54
+ @table ||= Table.nested(self)
68
55
  end
69
56
 
70
57
  def self.append(row)
@@ -95,9 +82,9 @@ module Samovar
95
82
 
96
83
  def self.usage(rows, name)
97
84
  rows.nested(name, self) do |rows|
98
- return unless @table
85
+ return if @table.nil?
99
86
 
100
- @table.rows.each do |row|
87
+ @table.merged.each do |row|
101
88
  if row.respond_to?(:usage)
102
89
  row.usage(rows)
103
90
  else
@@ -109,18 +96,42 @@ module Samovar
109
96
 
110
97
  def self.command_line(name)
111
98
  if @table
112
- "#{name} #{@table.usage}"
99
+ "#{name} #{@table.merged.usage}"
113
100
  else
114
101
  name
115
102
  end
116
103
  end
117
104
 
118
- def print_usage(*args, output: $stderr, formatter: Output::DetailedFormatter)
105
+ def initialize(input = nil, name: File.basename($0), parent: nil)
106
+ @name = name
107
+ @parent = parent
108
+
109
+ parse(input) if input
110
+ end
111
+
112
+ attr :name
113
+ attr :parent
114
+
115
+ def [](*input)
116
+ self.dup.tap{|command| command.parse(input)}
117
+ end
118
+
119
+ def parse(input)
120
+ self.class.table.merged.parse(input, self)
121
+
122
+ if input.empty?
123
+ return self
124
+ else
125
+ raise IncompleteParse.new(self, input)
126
+ end
127
+ end
128
+
129
+ def print_usage(output: $stderr, formatter: Output::UsageFormatter, &block)
119
130
  rows = Output::Rows.new
120
131
 
121
- self.class.usage(rows, *args)
132
+ self.class.usage(rows, @name)
122
133
 
123
- formatter.print(rows, output)
134
+ formatter.print(rows, output, &block)
124
135
  end
125
136
  end
126
137
  end
@@ -1,4 +1,4 @@
1
- # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
1
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -18,39 +18,39 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require_relative '../failure'
22
-
23
- require_relative 'terminal'
24
-
25
- require 'shellwords'
26
-
27
21
  module Samovar
28
- class SystemError < Failure
22
+ class Error < StandardError
29
23
  end
30
-
31
- class Command
32
- def system(*args, **options)
33
- log_system(args, options)
24
+
25
+ class InvalidInputError < Error
26
+ def initialize(command, input)
27
+ @command = command
28
+ @input = input
34
29
 
35
- Kernel::system(*args, **options)
36
- rescue Errno::ENOENT
37
- return false
30
+ super "Could not parse token #{input.first.inspect}"
38
31
  end
39
32
 
40
- def system!(*args, **options)
41
- if system(*args, **options)
42
- return true
43
- else
44
- raise SystemError.new("Command #{args.first.inspect} failed: #{$?.to_s}")
45
- end
33
+ def token
34
+ @input.first
46
35
  end
47
36
 
48
- private
37
+ def help?
38
+ self.token == "--help"
39
+ end
49
40
 
50
- def log_system(args, options)
51
- # Print out something half-decent:
52
- command_line = Shellwords.join(args)
53
- terminal.puts command_line, style: :command
41
+ attr :command
42
+ attr :input
43
+ end
44
+
45
+ class MissingValueError < Error
46
+ def initialize(command, field)
47
+ @command = command
48
+ @field = field
49
+
50
+ super "#{field} is required"
54
51
  end
52
+
53
+ attr :command
54
+ attr :field
55
55
  end
56
56
  end
@@ -20,14 +20,19 @@
20
20
 
21
21
  module Samovar
22
22
  class Many
23
- def initialize(key, description, stop: /^-/, default: nil)
23
+ def initialize(key, description, stop: /^-/, default: nil, required: false)
24
24
  @key = key
25
25
  @description = description
26
26
  @stop = stop
27
27
  @default = default
28
+ @required = required
28
29
  end
29
30
 
30
31
  attr :key
32
+ attr :description
33
+ attr :stop
34
+ attr :default
35
+ attr :required
31
36
 
32
37
  def to_s
33
38
  "<#{key}...>"
@@ -37,18 +42,24 @@ module Samovar
37
42
  usage = [to_s, @description]
38
43
 
39
44
  if @default
40
- usage << "Default: #{@default.inspect}"
45
+ usage << "(default: #{@default.inspect})"
46
+ elsif @required
47
+ usage << "(required)"
41
48
  end
42
49
 
43
50
  return usage
44
51
  end
45
52
 
46
- def parse(input, default = @default)
53
+ def parse(input, parent = nil, default = nil)
47
54
  if @stop and stop_index = input.index{|item| @stop === item}
48
55
  input.shift(stop_index)
49
- else
56
+ elsif input.any?
50
57
  input.shift(input.size)
51
- end || default
58
+ elsif default ||= @default
59
+ return default
60
+ elsif @required
61
+ raise MissingValueError.new(parent, self)
62
+ end
52
63
  end
53
64
  end
54
65
  end
@@ -20,23 +20,27 @@
20
20
 
21
21
  module Samovar
22
22
  class Nested
23
- def initialize(name, commands, key: :command, default: nil)
24
- @name = name
25
- @commands = commands
23
+ def initialize(key, commands, default: nil, required: false)
26
24
  @key = key
25
+ @commands = commands
27
26
 
28
27
  # This is the default name [of a command], not the default command:
29
28
  @default = default
29
+
30
+ @required = required
30
31
  end
31
32
 
32
33
  attr :key
34
+ attr :commands
35
+ attr :default
36
+ attr :required
33
37
 
34
38
  def to_s
35
- @name
39
+ "<#{@key}>"
36
40
  end
37
41
 
38
42
  def to_a
39
- usage = [@name]
43
+ usage = [self.to_s]
40
44
 
41
45
  if @commands.size == 0
42
46
  usage << "No commands available."
@@ -47,20 +51,27 @@ module Samovar
47
51
  end
48
52
 
49
53
  if @default
50
- usage << "Default: #{@default}"
54
+ usage << "(default: #{@default})"
55
+ elsif @required
56
+ usage << "(required)"
51
57
  end
52
58
 
53
59
  return usage
54
60
  end
55
61
 
56
- def parse(input, default)
62
+ # @param default [Command] the default command if any.
63
+ def parse(input, parent = nil, default = nil)
57
64
  if command = @commands[input.first]
58
- input.shift
65
+ name = input.shift
59
66
 
60
67
  # puts "Instantiating #{command} with #{input}"
61
- command.new(input)
68
+ command.new(input, name: name, parent: parent)
69
+ elsif default
70
+ return default
62
71
  elsif @default
63
- default || @commands[@default].new(input)
72
+ @commands[@default].new(input, name: @default, parent: parent)
73
+ elsif @required
74
+ raise MissingValueError.new(parent, self)
64
75
  end
65
76
  end
66
77
 
@@ -20,14 +20,19 @@
20
20
 
21
21
  module Samovar
22
22
  class One
23
- def initialize(key, description, pattern: //, default: nil)
23
+ def initialize(key, description, pattern: //, default: nil, required: false)
24
24
  @key = key
25
25
  @description = description
26
26
  @pattern = pattern
27
27
  @default = default
28
+ @required = required
28
29
  end
29
30
 
30
31
  attr :key
32
+ attr :description
33
+ attr :pattern
34
+ attr :default
35
+ attr :required
31
36
 
32
37
  def to_s
33
38
  "<#{@key}>"
@@ -37,16 +42,22 @@ module Samovar
37
42
  usage = [to_s, @description]
38
43
 
39
44
  if @default
40
- usage << "Default: #{@default.inspect}"
45
+ usage << "(default: #{@default.inspect})"
46
+ elsif @required
47
+ usage << "(required)"
41
48
  end
42
49
 
43
50
  return usage
44
51
  end
45
52
 
46
- def parse(input, default = @default)
53
+ def parse(input, parent = nil, default = nil)
47
54
  if input.first =~ @pattern
48
55
  input.shift
49
- end || default
56
+ elsif default ||= @default
57
+ return default
58
+ elsif @required
59
+ raise MissingValueError.new(parent, self)
60
+ end
50
61
  end
51
62
  end
52
63
  end
@@ -0,0 +1,107 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'flags'
22
+
23
+ module Samovar
24
+ class Option
25
+ def initialize(flags, description, key: nil, default: nil, value: nil, type: nil, required: false, &block)
26
+ @flags = Flags.new(flags)
27
+ @description = description
28
+
29
+ if key
30
+ @key = key
31
+ else
32
+ @key = @flags.first.key
33
+ end
34
+
35
+ @default = default
36
+
37
+ # If the value is given, it overrides the user specified input.
38
+ @value = value
39
+ @value ||= true if @flags.boolean?
40
+
41
+ @type = type
42
+ @required = required
43
+ @block = block
44
+ end
45
+
46
+ attr :flags
47
+ attr :description
48
+ attr :key
49
+ attr :default
50
+
51
+ attr :value
52
+
53
+ attr :type
54
+ attr :required
55
+ attr :block
56
+
57
+ def coerce_type(result)
58
+ if @type == Integer
59
+ Integer(result)
60
+ elsif @type == Float
61
+ Float(result)
62
+ elsif @type == Symbol
63
+ result.to_sym
64
+ elsif @type.respond_to? :call
65
+ @type.call(result)
66
+ elsif @type.respond_to? :new
67
+ @type.new(result)
68
+ end
69
+ end
70
+
71
+ def coerce(result)
72
+ if @type
73
+ result = coerce_type(result)
74
+ end
75
+
76
+ if @block
77
+ result = @block.call(result)
78
+ end
79
+
80
+ return result
81
+ end
82
+
83
+ def parse(input, parent = nil, default = nil)
84
+ if result = @flags.parse(input)
85
+ @value.nil? ? coerce(result) : @value
86
+ elsif default ||= @default
87
+ return default
88
+ elsif @required
89
+ raise MissingValueError.new(parent, self)
90
+ end
91
+ end
92
+
93
+ def to_s
94
+ @flags
95
+ end
96
+
97
+ def to_a
98
+ if @default
99
+ [@flags, @description, "(default: #{@default})"]
100
+ elsif @required
101
+ [@flags, @description, "(required)"]
102
+ else
103
+ [@flags, @description]
104
+ end
105
+ end
106
+ end
107
+ end