l43_xargs 0.1.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 +7 -0
- data/lib/l43/xargs/anon_flag.rb +23 -0
- data/lib/l43/xargs/anon_keyword.rb +23 -0
- data/lib/l43/xargs/check.rb +33 -0
- data/lib/l43/xargs/deconstruct.rb +16 -0
- data/lib/l43/xargs/defined_args.rb +114 -0
- data/lib/l43/xargs/descriptions.rb +57 -0
- data/lib/l43/xargs/flag.rb +55 -0
- data/lib/l43/xargs/keyword.rb +80 -0
- data/lib/l43/xargs/open_struct.rb +28 -0
- data/lib/l43/xargs/output.rb +42 -0
- data/lib/l43/xargs/parse.rb +161 -0
- data/lib/l43/xargs/version.rb +8 -0
- data/lib/l43/xargs.rb +168 -0
- metadata +97 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6cdd350e54ca66983f13d1e33b3e3e18894f353e4c3fe03a219d30ce1fd6a2d0
|
|
4
|
+
data.tar.gz: 49af947748b25d788161948d18d37025a27b638ff93c27c3d54dbb2e12ae5df3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: daba987a66ff981117e87d6f1d729e8d347e888b0d9bddeb4396a3c650da1fecbe7e78ce2feec6ca176852e099be512ddfd9448c2ef2647772c510ef854ecad6
|
|
7
|
+
data.tar.gz: 965d995dc98c035320aeb3cd4c91cd5bd4c71dff26ef081a44f9dc8b3ebb830dc38b8acb0cf27f9f4546a63f6839fba2f70dcc6d4220cb30ce3fe139a7434964
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module L43
|
|
4
|
+
class Xargs
|
|
5
|
+
class AnonFlag
|
|
6
|
+
attr_reader \
|
|
7
|
+
:desc,
|
|
8
|
+
:name,
|
|
9
|
+
:value
|
|
10
|
+
|
|
11
|
+
def defaulted = !value
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
def initialize(name)
|
|
15
|
+
@desc = [:yellow, "anonymous flag ", :bold, name.inspect]
|
|
16
|
+
@name = name
|
|
17
|
+
@value = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module L43
|
|
4
|
+
class Xargs
|
|
5
|
+
class AnonKeyword
|
|
6
|
+
|
|
7
|
+
attr_reader \
|
|
8
|
+
:defaulted, :desc,
|
|
9
|
+
:name,
|
|
10
|
+
:value
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def initialize(name, value)
|
|
14
|
+
@defaulted = false
|
|
15
|
+
@desc = [:yellow, "anonymous keyword ", :bold, name.inspect]
|
|
16
|
+
@name = name
|
|
17
|
+
@value = value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module L43
|
|
4
|
+
class Xargs
|
|
5
|
+
class Check
|
|
6
|
+
|
|
7
|
+
attr_reader :check, :desc, :name
|
|
8
|
+
|
|
9
|
+
def call(kwds)
|
|
10
|
+
return if check.(kwds)
|
|
11
|
+
errors << [:red, :bold, "ERROR: ", :reset, :yellow, "failed check: ", :cyan, _make_description, :reset]
|
|
12
|
+
errors.freeze
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def errors = @__errors__ ||= []
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
def initialize(name:, desc:, &blk)
|
|
19
|
+
@name = name.freeze
|
|
20
|
+
@desc = desc.freeze
|
|
21
|
+
@check = blk
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def _make_description
|
|
25
|
+
return desc unless name
|
|
26
|
+
"#{name} (#{desc})"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module L43
|
|
4
|
+
class Xargs
|
|
5
|
+
module Deconstruct
|
|
6
|
+
def deconstruct(*)
|
|
7
|
+
[ok?, args, errors, kwds]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def deconstruct_keys(*)
|
|
11
|
+
{ok: ok?, args:, errors:, kwds:}
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'output'
|
|
4
|
+
require_relative 'flag'
|
|
5
|
+
require_relative 'keyword'
|
|
6
|
+
require_relative 'descriptions'
|
|
7
|
+
|
|
8
|
+
module L43
|
|
9
|
+
class Xargs
|
|
10
|
+
class DefinedArgs
|
|
11
|
+
include Descriptions
|
|
12
|
+
BadDefinition = Class.new(RuntimeError)
|
|
13
|
+
DuplicateDefinition = Class.new(BadDefinition)
|
|
14
|
+
|
|
15
|
+
def add_desc(desc, element, indent:)
|
|
16
|
+
element.add_desc([indent || 4, *desc, :reset])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def def_flag(long, short=nil, desc:)
|
|
20
|
+
flag = Flag.new(long, short, desc:)
|
|
21
|
+
_def_short_flag(short, flag) if short
|
|
22
|
+
_def_flag(long, flag)
|
|
23
|
+
# p(defined_kwds: _defined_kwds)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def def_kwd(name, short=nil, desc:, default: None, &blk)
|
|
27
|
+
kwd = Keyword.new(name, short, desc:, default:, &blk)
|
|
28
|
+
_def_short_kwd(short, kwd) if short
|
|
29
|
+
_def_kwd(name, kwd)
|
|
30
|
+
# p(defined_kwds: _defined_kwds, defined_flags: _defined_flags)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_element(name, &blk)
|
|
34
|
+
_defined_flags[name] ||
|
|
35
|
+
_defined_kwds.fetch(name, &blk)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def get_flag(long)
|
|
39
|
+
_defined_flags[long]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_flags(&blk)
|
|
43
|
+
_defined_flags.each(&blk)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def get_kwd(long)
|
|
47
|
+
_defined_kwds[long]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def get_kwds(&blk)
|
|
51
|
+
_defined_kwds.each(&blk)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def get_missing_kwds(&blk)
|
|
55
|
+
missing = _defined_kwds.filter { |_, kwd| kwd.required? && kwd.defaulted }
|
|
56
|
+
missing.each { |_, kwd| blk.(kwd) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_short_flag(short)
|
|
60
|
+
_defined_short_flags[short]
|
|
61
|
+
end
|
|
62
|
+
def get_short_kwd(short)
|
|
63
|
+
_defined_short_kwds[short]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def print_help(indent: 1, newline: true, program_name: nil, sections: true, section_colors: [:bold, :green], usage: false, device: $stderr)
|
|
67
|
+
lines = help_description(indent:, newline:, program_name:, usage:, sections:, section_colors:)
|
|
68
|
+
lines.each do |line|
|
|
69
|
+
Output.putc(line, device:, indent:, colorize: @_colorize)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def args = @__args__ ||= []
|
|
74
|
+
|
|
75
|
+
def kwds = @__kwds__ ||= OpenStruct.new
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
def initialize(colorize:)
|
|
79
|
+
@_colorize = colorize
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def _colorize? = @_colorize
|
|
83
|
+
|
|
84
|
+
def _def_flag(name, flag)
|
|
85
|
+
raise DuplicateDefinition, "flag #{name.inspect} already defined" if get_flag(name)
|
|
86
|
+
_defined_flags[name] = flag
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def _def_kwd(name, kwd)
|
|
90
|
+
raise DuplicateDefinition, "keyword #{name.inspect} already defined" if get_kwd(name)
|
|
91
|
+
_defined_kwds.set!(name, kwd)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def _def_short_flag(short, flag)
|
|
95
|
+
raise DuplicateDefinition, "short flag #{short.inspect} already defined" if get_short_flag(short)
|
|
96
|
+
_defined_short_flags.set!(short, flag)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def _def_short_kwd(short, kwd)
|
|
100
|
+
raise DuplicateDefinition, "short keyword #{short.inspect} already defined" if get_short_kwd(short)
|
|
101
|
+
_defined_short_kwds[short] = kwd
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def _defined_flags = @__defined_flags__ ||= OpenStruct.new
|
|
105
|
+
|
|
106
|
+
def _defined_kwds = @__defined_kwds__ ||= OpenStruct.new
|
|
107
|
+
|
|
108
|
+
def _defined_short_flags = @__defined_short_flags__ ||= OpenStruct.new
|
|
109
|
+
|
|
110
|
+
def _defined_short_kwds = @__defined_short_kwds__ ||= OpenStruct.new
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module L43
|
|
4
|
+
class Xargs
|
|
5
|
+
module Descriptions
|
|
6
|
+
def help_description(indent: 1, newline: true, program_name: nil, sections: true, section_colors: [:bold, :green], usage: false)
|
|
7
|
+
flag_section =_section("Flags", indent:, newline:, sections:, section_colors:) unless _defined_flags.empty?
|
|
8
|
+
kwd_section = _section("Keywords", indent:, newline:, sections:, section_colors:) unless _defined_kwds.empty?
|
|
9
|
+
usage_section = [:bold, "usage: ", *_maybe_program_name(program_name), *_options(usage)] if usage
|
|
10
|
+
|
|
11
|
+
[
|
|
12
|
+
usage_section,
|
|
13
|
+
flag_section,
|
|
14
|
+
*_flag_descriptions,
|
|
15
|
+
kwd_section,
|
|
16
|
+
*_kwd_descriptions,
|
|
17
|
+
].compact
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
def _flag_descriptions
|
|
22
|
+
_defined_flags.inject([]) do |result, (_, flag)|
|
|
23
|
+
[*result, *flag.full_description]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def _kwd_descriptions
|
|
28
|
+
_defined_kwds.inject([]) do |result, (_, flag)|
|
|
29
|
+
[*result, *flag.full_description]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def _maybe_program_name(program_name)
|
|
34
|
+
return [] unless program_name
|
|
35
|
+
[:green, program_name + " "]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _options(*usage)
|
|
39
|
+
case usage.flatten
|
|
40
|
+
in options, args
|
|
41
|
+
[:cyan, "[#{options}] ", :blue, "#{args}..."]
|
|
42
|
+
in [options]
|
|
43
|
+
[:cyan, "[#{options}]"]
|
|
44
|
+
else
|
|
45
|
+
[]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def _section(title, indent:, newline:, sections:, section_colors:)
|
|
50
|
+
return unless sections
|
|
51
|
+
newline = newline ? [nil] : []
|
|
52
|
+
[*newline, indent, *Array(section_colors), title]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module L43
|
|
4
|
+
class Xargs
|
|
5
|
+
class Flag
|
|
6
|
+
attr_reader \
|
|
7
|
+
:additional_desc,
|
|
8
|
+
:desc,
|
|
9
|
+
:error,
|
|
10
|
+
:name,
|
|
11
|
+
:short,
|
|
12
|
+
:value
|
|
13
|
+
|
|
14
|
+
def add_desc(desc)
|
|
15
|
+
@additional_desc << desc
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def defaulted = !value
|
|
19
|
+
|
|
20
|
+
def error? = !!@error
|
|
21
|
+
|
|
22
|
+
def full_description(indent: 3, colors: :cyan)
|
|
23
|
+
[
|
|
24
|
+
[
|
|
25
|
+
indent, *name_description(colors:), *desc, :reset
|
|
26
|
+
],
|
|
27
|
+
*additional_desc
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def name_description(colors: :cyan)
|
|
32
|
+
formatted_name = ":#{name} "
|
|
33
|
+
formatted_name = ":#{short}|#{formatted_name}" if short
|
|
34
|
+
[*Array(colors), formatted_name, :reset]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def set_error!(error) = @error = error
|
|
38
|
+
|
|
39
|
+
def set_true!
|
|
40
|
+
@value = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
def initialize(name, short=nil, desc:)
|
|
45
|
+
@additional_desc = []
|
|
46
|
+
@desc = Array(desc)
|
|
47
|
+
@error = nil
|
|
48
|
+
@name = name
|
|
49
|
+
@short = short
|
|
50
|
+
@value = false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module L43
|
|
4
|
+
class Xargs
|
|
5
|
+
class Keyword
|
|
6
|
+
CallbackError = Class.new(RuntimeError)
|
|
7
|
+
|
|
8
|
+
attr_reader \
|
|
9
|
+
:additional_desc,
|
|
10
|
+
:callback,
|
|
11
|
+
:default, :defaulted, :desc,
|
|
12
|
+
:name,
|
|
13
|
+
:short,
|
|
14
|
+
:value
|
|
15
|
+
|
|
16
|
+
def add_desc(desc)
|
|
17
|
+
@additional_desc << desc
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def description_name
|
|
21
|
+
prefix = short ? "#{short}:|" : ""
|
|
22
|
+
"#{prefix}#{name}:"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def default? = @__default_p__ ||= default != None
|
|
26
|
+
|
|
27
|
+
def full_description(indent: 3, colors: :cyan, important: :bold, default_colors: :dim)
|
|
28
|
+
[
|
|
29
|
+
[
|
|
30
|
+
indent, *name_description(colors:), *desc, *_default_description(important:, default_colors:)
|
|
31
|
+
],
|
|
32
|
+
*additional_desc
|
|
33
|
+
]#.tap { p it }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def name_description(colors: :cyan)
|
|
37
|
+
formatted_name = "#{name}: "
|
|
38
|
+
formatted_name = "#{short}:|#{formatted_name}" if short
|
|
39
|
+
[*Array(colors), formatted_name, :bold, :blue, "<value> ", :reset]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def required? = default == None
|
|
43
|
+
|
|
44
|
+
def set_value!(value)
|
|
45
|
+
@defaulted = false
|
|
46
|
+
value = callback.(value) if callback
|
|
47
|
+
@value = value
|
|
48
|
+
:ok
|
|
49
|
+
rescue RuntimeError => re
|
|
50
|
+
[:error, re.class, re.message]
|
|
51
|
+
rescue ArgumentError => ae
|
|
52
|
+
[:error, ArgumentError, ae.message]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
def initialize(name, short=nil, desc:, default: None, &blk)
|
|
57
|
+
@callback = blk
|
|
58
|
+
@default = default
|
|
59
|
+
@defaulted = true
|
|
60
|
+
@desc = Array(desc)
|
|
61
|
+
@name = name
|
|
62
|
+
@short = short
|
|
63
|
+
@value = default
|
|
64
|
+
|
|
65
|
+
@additional_desc = []
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _default_description(important:, default_colors:)
|
|
70
|
+
if default?
|
|
71
|
+
[:reset, *Array(default_colors), " (defaults to: ", :reset, default.inspect, :dim, ")", :reset]
|
|
72
|
+
else
|
|
73
|
+
[:reset, *Array(important), " REQUIRED", :reset]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ostruct'
|
|
4
|
+
require 'l43/core/forwarder'
|
|
5
|
+
class OpenStruct
|
|
6
|
+
extend L43::Core::Forwarder
|
|
7
|
+
forward_all :deconstruc_keys,
|
|
8
|
+
:each, :empty?,
|
|
9
|
+
:fetch, :filter,
|
|
10
|
+
:inject,
|
|
11
|
+
:map, to: :to_h
|
|
12
|
+
|
|
13
|
+
# def deconstruct_keys(*)
|
|
14
|
+
# to_h
|
|
15
|
+
# end
|
|
16
|
+
|
|
17
|
+
# def each(&blk) = to_h.each(&blk)
|
|
18
|
+
# def empty? = to_h.empty?
|
|
19
|
+
# def filter(&blk) = to_h.filter(&blk)
|
|
20
|
+
# def map(&blk) = to_h.map(&blk)
|
|
21
|
+
|
|
22
|
+
def merge(**values)
|
|
23
|
+
self.class.new(to_h.merge(**values))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def set!(key, value) = self[key.to_sym] = value
|
|
27
|
+
end
|
|
28
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'l43/color/output'
|
|
4
|
+
|
|
5
|
+
module L43
|
|
6
|
+
class Xargs
|
|
7
|
+
module Output extend self
|
|
8
|
+
include Color::Output
|
|
9
|
+
|
|
10
|
+
def putc(*chunks, indent: 1, device: $stderr, colorize: true)
|
|
11
|
+
expanded = _expand_chunks(chunks, indent:, colorize:)
|
|
12
|
+
putcol(expanded, to: device)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def _expand_chunks(*chunks, indent:, colorize:)
|
|
18
|
+
chunks
|
|
19
|
+
.flatten
|
|
20
|
+
.inject([]) { |result, chunk| _expand_description(result, chunk, indent:, colorize:) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def _expand_description(result, element, indent:, colorize:)
|
|
24
|
+
case element
|
|
25
|
+
when nil
|
|
26
|
+
[*result, "\n"]
|
|
27
|
+
when Integer
|
|
28
|
+
[*result, " " * element * indent]
|
|
29
|
+
when Symbol
|
|
30
|
+
if colorize
|
|
31
|
+
[*result, element]
|
|
32
|
+
else
|
|
33
|
+
result
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
[*result, element]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module L43
|
|
4
|
+
class Xargs
|
|
5
|
+
module Parse
|
|
6
|
+
def _add_defaults
|
|
7
|
+
# require "debug"; binding.break
|
|
8
|
+
defined_args.get_flags do |_, flag|
|
|
9
|
+
_kwds.set!(flag.name, flag)
|
|
10
|
+
# p(name: flag.name, kwds: _kwds)
|
|
11
|
+
end
|
|
12
|
+
defined_args.get_kwds do |_, kwd|
|
|
13
|
+
if kwd.default != None
|
|
14
|
+
_kwds.set!(kwd.name, kwd)
|
|
15
|
+
# p(name: kwd.name, kwds: _kwds)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def _add_flag(flag)
|
|
21
|
+
flag_def = defined_args.get_flag(flag)
|
|
22
|
+
_errors << _colorize("undefined flag: ", :yellow, :bold, ":#{flag}", :reset) unless flag_def
|
|
23
|
+
if flag_def
|
|
24
|
+
_check_multiple_occurence(flag_def, name: flag_def.name)
|
|
25
|
+
flag_def.set_true!
|
|
26
|
+
_kwds.set!(flag, flag_def)
|
|
27
|
+
else
|
|
28
|
+
_kwds.set!(flag, AnonFlag.new(flag))
|
|
29
|
+
end
|
|
30
|
+
argv.shift
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def _add_kwd(name)
|
|
34
|
+
argv.shift
|
|
35
|
+
|
|
36
|
+
kwd_def = defined_args.get_kwd(name)
|
|
37
|
+
_errors << _colorize("undefined keyword: ", :yellow, :bold, name.inspect, :reset) unless kwd_def
|
|
38
|
+
|
|
39
|
+
if argv.empty?
|
|
40
|
+
_errors << _colorize(:yellow, :bold, "missing value ", :reset, "for keyword ", name.inspect)
|
|
41
|
+
raise StopIteration
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if argv.first == "::"
|
|
45
|
+
_errors << _colorize(:yellow, :bold, "missing value ", :reset, "for keyword ", name.inspect, " (caused by a following :: which forces the end of argument parsing)")
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
value = argv.shift
|
|
50
|
+
|
|
51
|
+
if kwd_def
|
|
52
|
+
set_kwd(kwd_def, value)
|
|
53
|
+
else
|
|
54
|
+
_kwds.set!(name, AnonKeyword.new(name, value))
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def set_kwd(kwd_def, value)
|
|
59
|
+
case kwd_def.set_value!(value)
|
|
60
|
+
in :ok
|
|
61
|
+
_kwds.set!(kwd_def.name, kwd_def)
|
|
62
|
+
in :error, klass, msg
|
|
63
|
+
_errors << _colorize(
|
|
64
|
+
"callback for keyword ", :bold, :cyan, kwd_def.name.to_s + ":",
|
|
65
|
+
:reset, :yellow, " raised error ", :bold, klass.to_s,
|
|
66
|
+
:reset, " with message: ", :bold, msg
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def _add_short_flag(flag, shift: true)
|
|
72
|
+
flag_def = defined_args.get_short_flag(flag)
|
|
73
|
+
_errors << _colorize("undefined short flag: ", :yellow, :bold, ":#{flag}", :reset) unless flag_def
|
|
74
|
+
|
|
75
|
+
if flag_def
|
|
76
|
+
_check_multiple_occurence(flag_def, name: flag_def.short, short: true)
|
|
77
|
+
flag_def.set_true!
|
|
78
|
+
_kwds.set!(flag_def.name, flag_def)
|
|
79
|
+
else
|
|
80
|
+
_kwds.set!(flag, AnonFlag.new(flag))
|
|
81
|
+
end
|
|
82
|
+
argv.shift if shift
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def _add_short_kwd(short)
|
|
86
|
+
argv.shift
|
|
87
|
+
|
|
88
|
+
kwd_def = defined_args.get_short_kwd(short)
|
|
89
|
+
_errors << _colorize("undefined short keyword: ", :yellow, :bold, short.inspect, :reset) unless kwd_def
|
|
90
|
+
|
|
91
|
+
if argv.empty?
|
|
92
|
+
_errors << _colorize(:yellow, :bold, "missing value ", :reset, "for short keyword ", short.inspect)
|
|
93
|
+
raise StopIteration
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
value = argv.shift
|
|
97
|
+
|
|
98
|
+
if kwd_def
|
|
99
|
+
kwd_def.set_value!(value)
|
|
100
|
+
_kwds.set!(kwd_def.name, kwd_def)
|
|
101
|
+
else
|
|
102
|
+
_kwds.set!(short, AnonKeyword.new(short, value))
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def _check_multiple_occurence(flag_def, name:, short: false)
|
|
107
|
+
# p(name:, value: flag_def.value, error: flag_def.error?)
|
|
108
|
+
return if !flag_def.value || flag_def.error?
|
|
109
|
+
error_msg = [:yellow, :bold, "multiple occurences of #{short ? "short " : ""}flag: ", :cyan, name.inspect]
|
|
110
|
+
_errors << _colorize(*error_msg)
|
|
111
|
+
flag_def.set_error!(error_msg)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def _checks!
|
|
115
|
+
checks.each do |check|
|
|
116
|
+
check.(_kwds)
|
|
117
|
+
check.errors.each do |error|
|
|
118
|
+
_errors << error
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def _parse
|
|
124
|
+
_add_defaults
|
|
125
|
+
loop do
|
|
126
|
+
_parse_argv
|
|
127
|
+
end
|
|
128
|
+
_check_for_missing!
|
|
129
|
+
_checks!
|
|
130
|
+
OpenStruct.new(args: _args, errors: _errors, kwds: _kwds)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def _parse_argv
|
|
134
|
+
raise StopIteration if argv.empty?
|
|
135
|
+
case argv.first
|
|
136
|
+
when LongFlagRgx
|
|
137
|
+
_add_flag($1.to_sym)
|
|
138
|
+
when LongKwdRgx
|
|
139
|
+
_add_kwd($1.to_sym)
|
|
140
|
+
when PosixShortFlagsRgx
|
|
141
|
+
$1.grapheme_clusters.each do
|
|
142
|
+
_add_short_flag(it.to_sym, shift: false)
|
|
143
|
+
end
|
|
144
|
+
argv.shift
|
|
145
|
+
when StopParsing
|
|
146
|
+
_args.concat(argv.drop(1))
|
|
147
|
+
raise StopIteration
|
|
148
|
+
when ShortFlagRgx
|
|
149
|
+
_add_short_flag($1.to_sym)
|
|
150
|
+
when ShortKwdRgx
|
|
151
|
+
_add_short_kwd($1.to_sym)
|
|
152
|
+
else
|
|
153
|
+
_args << argv.first
|
|
154
|
+
argv.shift
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
data/lib/l43/xargs.rb
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# require 'l43/core/forwarder'
|
|
4
|
+
module L43
|
|
5
|
+
class Xargs
|
|
6
|
+
None = Object.new.freeze
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative 'xargs/open_struct'
|
|
11
|
+
require_relative 'xargs/check'
|
|
12
|
+
require_relative 'xargs/deconstruct'
|
|
13
|
+
require_relative 'xargs/defined_args'
|
|
14
|
+
require_relative 'xargs/anon_flag'
|
|
15
|
+
require_relative 'xargs/anon_keyword'
|
|
16
|
+
require_relative 'xargs/parse'
|
|
17
|
+
|
|
18
|
+
module L43
|
|
19
|
+
class Xargs
|
|
20
|
+
include Deconstruct
|
|
21
|
+
include Parse
|
|
22
|
+
|
|
23
|
+
IllegalMonitorState = Class.new(RuntimeError)
|
|
24
|
+
ColorizeValues = %i[always auto never]
|
|
25
|
+
|
|
26
|
+
LongFlagRgx = %r{\A:(..+)}
|
|
27
|
+
LongKwdRgx = %r{\A(..+):\z}
|
|
28
|
+
PosixShortFlagsRgx = %r{\A-(.+)\z}
|
|
29
|
+
ShortFlagRgx = %r{\A:(.)\z}
|
|
30
|
+
ShortKwdRgx = %r{\A(.):\z}
|
|
31
|
+
StopParsing = "::"
|
|
32
|
+
|
|
33
|
+
attr_reader :argv, :autohelp, :defined_args, :last_added, :program_name, :usage
|
|
34
|
+
|
|
35
|
+
def add_desc(*desc, indent: nil, name: nil)
|
|
36
|
+
if name
|
|
37
|
+
element = defined_args.get_element(name) do
|
|
38
|
+
raise IllegalMonitorState, "cannot add description for undefined flag or keyword #{name.inspect}"
|
|
39
|
+
end
|
|
40
|
+
defined_args.add_desc(desc.flatten, element, indent:)
|
|
41
|
+
else
|
|
42
|
+
raise IllegalMonitorState, "cannot add description for last added flag or keyword before adding one" unless last_added
|
|
43
|
+
defined_args.add_desc(desc.flatten, last_added, indent:)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def args = result.args
|
|
50
|
+
|
|
51
|
+
def autohelp!
|
|
52
|
+
def_flag(:help, :h, desc: "Shows this help and exits") unless autohelp
|
|
53
|
+
@autohelp = true
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def def_check(name=nil, desc:, &blk)
|
|
58
|
+
checks << Check.new(name:, desc:, &blk)
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def def_flag(name, short=nil, desc:)
|
|
63
|
+
@last_added = defined_args.def_flag(name, short, desc:)
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def def_kwd(name, short=nil, desc:, default: None, &blk)
|
|
68
|
+
@last_added = defined_args.def_kwd(name, short, desc:, default:, &blk)
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def errors = result.errors
|
|
73
|
+
|
|
74
|
+
def help_description(indent: 1, newline: true, sections: true, section_colors: [:bold, :green])
|
|
75
|
+
defined_args.help_description(indent:, newline:, program_name:, sections:, section_colors:, usage:)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def full_result
|
|
79
|
+
raise IllegalMonitorState, "must not call full_result before calling parse" unless @__full_result__
|
|
80
|
+
@__full_result__
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def kwds = result.kwds
|
|
84
|
+
|
|
85
|
+
def ok? = errors.empty?
|
|
86
|
+
|
|
87
|
+
def parse(argv)
|
|
88
|
+
raise IllegalMonitorState, "must not call parse twice on the same instance" if @__full_result__
|
|
89
|
+
@argv = argv
|
|
90
|
+
@__full_result__ = _parse
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def print_errors
|
|
95
|
+
_errors.each do |error|
|
|
96
|
+
Output.putc(error, device: $stderr, indent: 0)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def print_help(indent: 1, newline: true, program_name: nil, sections: true, section_colors: [:bold, :green], usage: false, device: $stderr)
|
|
101
|
+
defined_args.print_help(indent:, newline:, program_name:, sections:, section_colors:, usage:, device:)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def result
|
|
105
|
+
raise IllegalMonitorState, "must not call result before calling parse" unless @__full_result__
|
|
106
|
+
@__result__ ||= _result
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def set_program_name!(program_name)
|
|
110
|
+
@program_name = program_name
|
|
111
|
+
self
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def set_usage!(*values)
|
|
115
|
+
@usage = values.flatten
|
|
116
|
+
self
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
def initialize(autohelp: false, colorize: :auto, program_name: nil, usage: false, &blk)
|
|
121
|
+
raise ArgumentError, "keyword argument colorize is #{colorize.inspect}, not in required set: #{ColorizeValues.inspect}" unless ColorizeValues.member? colorize
|
|
122
|
+
raise ArgumentError, "keyword argument autohelp is #{autohelp.inspect}, but a boolean is required" unless [false, true].member? autohelp
|
|
123
|
+
raise ArgumentError, "blocks are ignored and therefore not supported" if blk
|
|
124
|
+
@colorize = colorize
|
|
125
|
+
@usage = usage
|
|
126
|
+
|
|
127
|
+
@program_name = program_name
|
|
128
|
+
@defined_args = DefinedArgs.new(colorize: _colorize?)
|
|
129
|
+
def_flag(:help, :h, desc: "Shows this help and exits") if autohelp
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def _args = @__args__ ||= []
|
|
133
|
+
|
|
134
|
+
def _colorize(*chunks)
|
|
135
|
+
chunks.map do |chunk|
|
|
136
|
+
_use_chunk(chunk)
|
|
137
|
+
end.compact
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def _check_for_missing!
|
|
141
|
+
defined_args.get_missing_kwds do |kwd|
|
|
142
|
+
_errors << _colorize(:red, :bold, "ERROR: ", :yellow, "the required keyword ", :cyan, kwd.description_name, :yellow, " is missing")
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def checks = @__checks__ ||= []
|
|
147
|
+
|
|
148
|
+
def _colorize? = @___colorize_p__ ||= @colorize == :always || @colorize == :auto && ENV['NO_COLOR'].nil?
|
|
149
|
+
|
|
150
|
+
def _errors = @__errors__ ||= []
|
|
151
|
+
|
|
152
|
+
def _kwds = @__kwds__ ||= OpenStruct.new
|
|
153
|
+
|
|
154
|
+
def _result
|
|
155
|
+
kwds = full_result.kwds.map { |k, v| [k, v.value]}.to_h
|
|
156
|
+
full_result.merge(kwds:)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def _use_chunk(chunk)
|
|
160
|
+
if _colorize?
|
|
161
|
+
chunk
|
|
162
|
+
elsif String===chunk
|
|
163
|
+
chunk
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
metadata
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: l43_xargs
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Robert Dober
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-09-04 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ostruct
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.6.3
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.6.3
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: l43_color
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.3.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.3.0
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: l43_core
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 0.2.9
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 0.2.9
|
|
54
|
+
description: |
|
|
55
|
+
A Modular Tailor Made Command Line Parser
|
|
56
|
+
Yet another ClParser emerging from my needs for my Ruby Tools.
|
|
57
|
+
General but with a opinionated Featute Set.
|
|
58
|
+
email: robert.dober@gmail.com
|
|
59
|
+
executables: []
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- lib/l43/xargs.rb
|
|
64
|
+
- lib/l43/xargs/anon_flag.rb
|
|
65
|
+
- lib/l43/xargs/anon_keyword.rb
|
|
66
|
+
- lib/l43/xargs/check.rb
|
|
67
|
+
- lib/l43/xargs/deconstruct.rb
|
|
68
|
+
- lib/l43/xargs/defined_args.rb
|
|
69
|
+
- lib/l43/xargs/descriptions.rb
|
|
70
|
+
- lib/l43/xargs/flag.rb
|
|
71
|
+
- lib/l43/xargs/keyword.rb
|
|
72
|
+
- lib/l43/xargs/open_struct.rb
|
|
73
|
+
- lib/l43/xargs/output.rb
|
|
74
|
+
- lib/l43/xargs/parse.rb
|
|
75
|
+
- lib/l43/xargs/version.rb
|
|
76
|
+
homepage: https://codeberg.org/lab419/l43_opt_parser
|
|
77
|
+
licenses:
|
|
78
|
+
- AGPL-3.0-or-later
|
|
79
|
+
metadata: {}
|
|
80
|
+
rdoc_options: []
|
|
81
|
+
require_paths:
|
|
82
|
+
- lib
|
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: 4.0.1
|
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
requirements: []
|
|
94
|
+
rubygems_version: 4.0.19
|
|
95
|
+
specification_version: 4
|
|
96
|
+
summary: Modular Tailor Made Command Line Parser
|
|
97
|
+
test_files: []
|