samovar 1.10.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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