ex_aequo 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +127 -4
- data/lib/ex_aequo/args.rb +60 -0
- data/lib/ex_aequo/args_parser.rb +71 -0
- data/lib/ex_aequo/my_struct/class_methods.rb +19 -0
- data/lib/ex_aequo/my_struct.rb +32 -0
- data/lib/ex_aequo/version.rb +1 -1
- data/lib/ex_aequo.rb +1 -0
- metadata +22 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2aaf57df3f78e7ecdd11fdf189a6dbd0942a68d9ec41213713583bebd533c5dd
|
4
|
+
data.tar.gz: 799c05c0d0b4e4f20d736de98017c960e40fc370fabbfce0d3fe5827499cdafa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4ee1ab2d221d3fe055f3e9a4d27c76fef3237af11718a3fef986702f5a08511cb43a30a439fd3e896b8a86d785691d3aab01327dbbca8d54026f6c87292b390
|
7
|
+
data.tar.gz: e038d29057424cf6f2f45139562c060b76221eca5fe6c55eaf3ae0bb0bf58b9b68666122e11d5eedf8aff396c241b1e11f1fa87892ee710e85dd02cdfd883812
|
data/README.md
CHANGED
@@ -3,15 +3,138 @@
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/ex_aequo.svg)](http://badge.fury.io/rb/ex_aequo)
|
4
4
|
[![Gem Downloads](https://img.shields.io/gem/dt/ex_aequo.svg)](https://rubygems.org/gems/ex_aequo)
|
5
5
|
|
6
|
-
# ExAequo
|
7
6
|
|
8
|
-
|
7
|
+
# Some Tools
|
8
|
+
|
9
|
+
... which tools?
|
10
|
+
|
11
|
+
Let us [speculate about](https://github.com/RobertDober/speculate_about) that:
|
12
|
+
|
13
|
+
|
14
|
+
## Context: MyStruct
|
15
|
+
|
16
|
+
All the goodies of `OpenStruct` without the badies:
|
17
|
+
|
18
|
+
- Immutable
|
19
|
+
- Deconstructable (→ _Patternmatchable_)
|
20
|
+
- _Hashy_ Interface (`fetch`, `merge`, `slice`...)
|
21
|
+
|
22
|
+
Given an instance of `MyStruct` constructed from a `Hash`
|
23
|
+
```ruby
|
24
|
+
require 'ex_aequo/my_struct'
|
25
|
+
let(:from_hash) { MyStruct.new(a: 1, b: 2) }
|
26
|
+
```
|
27
|
+
And an instance constructed by a reducer
|
28
|
+
```ruby
|
29
|
+
let(:incremented) { MyStruct.from_reduce(%i[a b c d e], 0) { _1.succ } }
|
30
|
+
```
|
31
|
+
Then they can be accessed like a `Hash` or `OpenStruct`
|
32
|
+
```ruby
|
33
|
+
expect(from_hash[:a]).to eq(1)
|
34
|
+
expect(from_hash.fetch(:b)).to eq(2)
|
35
|
+
expect(incremented[:e]).to eq(5)
|
36
|
+
```
|
37
|
+
|
38
|
+
And they are immutable
|
39
|
+
```ruby
|
40
|
+
expect { from_hash[:c] = 3 }
|
41
|
+
.to raise_error(MyStruct::ImmutableError, 'cannot change values with []= in immutable instance of MyStruct')
|
42
|
+
```
|
43
|
+
|
44
|
+
And _mutable_ operations just create new objects
|
45
|
+
```ruby
|
46
|
+
expect(from_hash.merge(c: 3)).to eq(MyStruct.new(a: 1, b: 2, c: 3))
|
47
|
+
expect(from_hash.to_h).to eq(a: 1, b: 2)
|
48
|
+
```
|
49
|
+
|
50
|
+
And the _hashy_ methods observer the same interface
|
51
|
+
```ruby
|
52
|
+
expect { from_hash.fetch(:c) }
|
53
|
+
.to raise_error(KeyError, 'key :c not found in #<MyStruct a=1, b=2> and no default given to #fetch')
|
54
|
+
|
55
|
+
expect(from_hash.fetch(:c, 42)).to eq(42)
|
56
|
+
expect(from_hash.fetch(:c) { 43 }).to eq(43)
|
57
|
+
|
58
|
+
expect(incremented.slice(:a, :c, :e)).to eq(MyStruct.new(a: 1, c: 3, e: 5))
|
59
|
+
```
|
60
|
+
|
61
|
+
## Context: ArgsParser and Args
|
62
|
+
|
63
|
+
An expressive, yet simple argument parser that takes full advantage of Ruby 3
|
64
|
+
and returns a modern Args struct (with modern I mean MyStruct) as a result.
|
65
|
+
|
66
|
+
Given a simple example like this on
|
67
|
+
```ruby
|
68
|
+
let(:simple_parser) do
|
69
|
+
ExAequo::ArgsParser.new(
|
70
|
+
allowed: %w[alpha: beta: :help :verbose],
|
71
|
+
aliases: {h: :help, v: :verbose, a: :alpha}
|
72
|
+
)
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
Then, as northing is required we can successfully parse empty arguments
|
77
|
+
```ruby
|
78
|
+
result = simple_parser.parse([])
|
79
|
+
|
80
|
+
expect(result).to be_ok
|
81
|
+
expect(result.missing).to be_empty
|
82
|
+
expect(result.superflous).to be_empty
|
83
|
+
expect(result.positionals).to be_empty
|
84
|
+
expect(result.keywords).to eq(MyStruct.new)
|
85
|
+
```
|
86
|
+
|
87
|
+
And, we can also provide allowed values (N.B. ruby sytnax is default)
|
88
|
+
```ruby
|
89
|
+
result = simple_parser.parse(%w[alpha: 42 43])
|
90
|
+
|
91
|
+
expect(result).to be_ok
|
92
|
+
expect(result.missing).to be_empty
|
93
|
+
expect(result.superflous).to be_empty
|
94
|
+
expect(result.positionals).to eq(%w[43])
|
95
|
+
expect(result.keywords).to eq(MyStruct.new(alpha: '42'))
|
96
|
+
```
|
97
|
+
|
98
|
+
## Context: Colors
|
99
|
+
|
100
|
+
Given the includion of the `Color` module
|
101
|
+
```ruby
|
102
|
+
include ExAequo::Color
|
103
|
+
```
|
104
|
+
|
105
|
+
|
106
|
+
Then we can create ansi colored strings
|
107
|
+
```ruby
|
108
|
+
expect(ansi(:red)).to eq("\e[31m")
|
109
|
+
```
|
110
|
+
|
111
|
+
Or we can create 256 color strings
|
112
|
+
```ruby
|
113
|
+
expect(ansi256(100)).to eq("\e[38;5;100m")
|
114
|
+
```
|
115
|
+
|
116
|
+
Or we can use RGB colors
|
117
|
+
```ruby
|
118
|
+
expect(rgb(96,32,16)).to eq("\e[38;2;96;32;16m")
|
119
|
+
expect(rgb('#602010')).to eq("\e[38;2;96;32;16m")
|
120
|
+
```
|
121
|
+
|
122
|
+
### Context: Colorize Helper
|
123
|
+
|
124
|
+
And
|
125
|
+
```ruby
|
126
|
+
expect(colorize('red', ansi: :green)).to eq("\e[32mred\e[0m")
|
127
|
+
```
|
128
|
+
|
129
|
+
Or we can omit the reset
|
130
|
+
```ruby
|
131
|
+
expect(colorize('red', ansi: :green, reset: false)).to eq("\e[32mred")
|
132
|
+
```
|
133
|
+
|
9
134
|
|
10
|
-
Some Tools
|
11
135
|
|
12
136
|
|
13
137
|
|
14
|
-
##
|
15
138
|
|
16
139
|
## LICENSE
|
17
140
|
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'my_struct'
|
4
|
+
|
5
|
+
module ExAequo
|
6
|
+
class Args
|
7
|
+
|
8
|
+
attr_reader :missing, :positionals, :superflous
|
9
|
+
|
10
|
+
def add_illegal_kwd(kwd)
|
11
|
+
errors << ["Illegal kwd #{kwd}"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_kwd(kwd, value)
|
15
|
+
# Constraint Checks would go here
|
16
|
+
@keywords[kwd.to_sym] = value
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_positional(arg)
|
21
|
+
positionals << arg
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def errors
|
26
|
+
@__errors__ ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def keywords
|
30
|
+
MyStruct.new(@keywords)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ok?
|
34
|
+
missing.empty? && errors.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_flag(flag)
|
38
|
+
@keywords[flag.to_sym] = true
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :aliases, :allowed, :required
|
45
|
+
|
46
|
+
def initialize(aliases: [], allowed: [], required: [])
|
47
|
+
@required = required
|
48
|
+
@allowed = allowed
|
49
|
+
@required = required
|
50
|
+
|
51
|
+
@bad_syntax = false
|
52
|
+
@keywords = {}
|
53
|
+
@missing = []
|
54
|
+
@positionals = []
|
55
|
+
@superflous = []
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
# SPDX-License-Identifier: Apache-2.0
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'args'
|
4
|
+
|
5
|
+
module ExAequo
|
6
|
+
class ArgsParser
|
7
|
+
|
8
|
+
FlagRgx = %r{\A : .*}x.freeze
|
9
|
+
KwdRgx = %r{.* : \z}x.freeze
|
10
|
+
def parse(args)
|
11
|
+
result = Args.new(aliases:, allowed:, required:)
|
12
|
+
args.inject([state, result], &_parse).last
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :aliases, :allowed, :posix, :required, :state
|
18
|
+
|
19
|
+
def initialize(aliases: [], allowed: [], posix: false, required: [])
|
20
|
+
@aliases = aliases
|
21
|
+
@allowed = allowed
|
22
|
+
@posix = posix
|
23
|
+
@required = required
|
24
|
+
|
25
|
+
@state = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def _parse
|
29
|
+
->((state, result), arg) do
|
30
|
+
case state
|
31
|
+
in nil
|
32
|
+
_parse_nil(result, arg)
|
33
|
+
in kwd
|
34
|
+
_parse_kwd(kwd, result, arg)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def _parse_new_flag(flag, result)
|
40
|
+
if allowed.include?(flag)
|
41
|
+
[nil, result.set_flag(flag[1..-1])]
|
42
|
+
else
|
43
|
+
[nil, result.add_illegal_kwd(flag)]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def _parse_new_kwd(kwd, result)
|
48
|
+
if allowed.include?(kwd)
|
49
|
+
[kwd[0...-1], result]
|
50
|
+
else
|
51
|
+
[nil, result.add_illegal_kwd(kwd)]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def _parse_kwd(kwd, result, arg)
|
56
|
+
[nil, result.add_kwd(kwd, arg)]
|
57
|
+
end
|
58
|
+
|
59
|
+
def _parse_nil(result, arg)
|
60
|
+
case arg
|
61
|
+
when FlagRgx
|
62
|
+
_parse_new_flag(arg, result)
|
63
|
+
when KwdRgx
|
64
|
+
_parse_new_kwd(arg, result)
|
65
|
+
else
|
66
|
+
[nil, result.add_positional(arg)]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
# SPDX-License-Identifier: Apache-2.0
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MyStruct < OpenStruct
|
4
|
+
module ClassMethods
|
5
|
+
def from_reduce(keys, acc, &reducer)
|
6
|
+
new(**_from_reduce(keys, acc, &reducer))
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def _from_reduce(keys, acc, &reducer)
|
12
|
+
keys.map do |key|
|
13
|
+
reducer.(acc, key) => acc
|
14
|
+
[key, acc]
|
15
|
+
end.to_h
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
# SPDX-License-Identifier: Apache-2.0
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'my_struct/class_methods'
|
4
|
+
require 'forwarder'
|
5
|
+
class MyStruct < OpenStruct
|
6
|
+
class ImmutableError < RuntimeError; end
|
7
|
+
|
8
|
+
extend ClassMethods
|
9
|
+
extend Forwarder
|
10
|
+
|
11
|
+
# forward :fetch, to: :to_h
|
12
|
+
|
13
|
+
def[]=(*)
|
14
|
+
raise ImmutableError, "cannot change values with []= in immutable instance of #{self.class}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch(*args, &blk)
|
18
|
+
to_h.fetch(*args, &blk)
|
19
|
+
rescue KeyError
|
20
|
+
raise KeyError,
|
21
|
+
"key #{args.first.inspect} not found in #{self} and no default given to #fetch"
|
22
|
+
end
|
23
|
+
|
24
|
+
def merge(with)
|
25
|
+
self.class.new(to_h.merge(with.to_h))
|
26
|
+
end
|
27
|
+
|
28
|
+
def slice(*args)
|
29
|
+
self.class.new(to_h.slice(*args.flatten))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
# SPDX-License-Identifier: Apache-2.0
|
data/lib/ex_aequo/version.rb
CHANGED
data/lib/ex_aequo.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ex_aequo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Dober
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
11
|
+
date: 2023-03-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: forwarder3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.0
|
13
27
|
description: 'Some tools
|
14
28
|
|
15
29
|
'
|
@@ -21,6 +35,8 @@ files:
|
|
21
35
|
- LICENSE
|
22
36
|
- README.md
|
23
37
|
- lib/ex_aequo.rb
|
38
|
+
- lib/ex_aequo/args.rb
|
39
|
+
- lib/ex_aequo/args_parser.rb
|
24
40
|
- lib/ex_aequo/color.rb
|
25
41
|
- lib/ex_aequo/color/ansi.rb
|
26
42
|
- lib/ex_aequo/color/ansi256.rb
|
@@ -32,6 +48,8 @@ files:
|
|
32
48
|
- lib/ex_aequo/kernel.rb
|
33
49
|
- lib/ex_aequo/kernel/access.rb
|
34
50
|
- lib/ex_aequo/kernel/fn.rb
|
51
|
+
- lib/ex_aequo/my_struct.rb
|
52
|
+
- lib/ex_aequo/my_struct/class_methods.rb
|
35
53
|
- lib/ex_aequo/version.rb
|
36
54
|
homepage: https://gitlab.com/robert_dober/rb_ex_aequo
|
37
55
|
licenses:
|
@@ -52,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
70
|
- !ruby/object:Gem::Version
|
53
71
|
version: '0'
|
54
72
|
requirements: []
|
55
|
-
rubygems_version: 3.3.
|
73
|
+
rubygems_version: 3.3.26
|
56
74
|
signing_key:
|
57
75
|
specification_version: 4
|
58
76
|
summary: Some tools
|