drain 0.2.0 → 0.3.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 +4 -4
- data/.travis.yml +5 -6
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/lib/dr/base/bool.rb +6 -3
- data/lib/dr/base/graph.rb +21 -18
- data/lib/dr/base/uri.rb +201 -0
- data/lib/dr/base/utils.rb +26 -2
- data/lib/dr/formatter/simple_formatter.rb +154 -0
- data/lib/dr/parse/date_parse.rb +157 -0
- data/lib/dr/parse/simple_keywords.rb +56 -0
- data/lib/dr/parse/simple_parser.rb +18 -0
- data/lib/dr/parse/time_parse.rb +1 -1
- data/lib/dr/ruby_ext/core_modules.rb +86 -5
- data/lib/dr/version.rb +1 -1
- data/test/helper.rb +2 -2
- data/test/test_date_parse.rb +25 -0
- data/test/test_simple_keywords.rb +36 -0
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c577f08df5d0dd378a335824250d97008d3eb24896d101ecc1ed51a2281a1f0
|
|
4
|
+
data.tar.gz: f35b4fd99afb4870ddb44991b776ef8e5621739d7d83d7ba4d9ae851b2713df1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a889f20d3499a98ddc6da50f8cc14201881cf695271b250730ebc26024e657f2af2f7764f5e078979029559dd1dea3745a60a9df9c40ae8d5fc64f7903948a1e
|
|
7
|
+
data.tar.gz: 1a95a1661230282f9339258e1b6a241bd4fe2e503fbb9f43ea20c220e220a093903356cd8e78fd36e62899189b919a5a4ee50a3261951fdfde1bceebb91cbae1
|
data/.travis.yml
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
data/lib/dr/base/bool.rb
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
module DR
|
|
2
2
|
module Bool
|
|
3
3
|
extend(self)
|
|
4
|
-
def to_bool(el, default=nil)
|
|
4
|
+
def to_bool(el, default=nil, allow_nil: true, string_fallback: true)
|
|
5
5
|
case el
|
|
6
6
|
when String
|
|
7
7
|
string=el.chomp
|
|
8
8
|
return true if string =~ (/(true|t|yes|y|1)$/i)
|
|
9
9
|
return false if string.empty? || string =~ (/(false|f|no|n|0)$/i)
|
|
10
|
-
|
|
10
|
+
return el if string_fallback
|
|
11
|
+
when Integer
|
|
11
12
|
return ! (el == 0)
|
|
12
|
-
when Process::Status
|
|
13
|
+
when ::Process::Status
|
|
13
14
|
exitstatus=el.exitstatus
|
|
14
15
|
return exitstatus == 0
|
|
15
16
|
else
|
|
@@ -18,7 +19,9 @@ module DR
|
|
|
18
19
|
#we don't return !!el because we don't want nil to be false but to
|
|
19
20
|
#give an error
|
|
20
21
|
end
|
|
22
|
+
return el if string_fallback and el.is_a?(Symbol)
|
|
21
23
|
return default unless default.nil?
|
|
24
|
+
return nil if el.nil? and allow_nil
|
|
22
25
|
raise ArgumentError.new("Invalid value for Boolean: \"#{el}\"")
|
|
23
26
|
end
|
|
24
27
|
end
|
data/lib/dr/base/graph.rb
CHANGED
|
@@ -83,24 +83,24 @@ module DR
|
|
|
83
83
|
def inspect
|
|
84
84
|
"#{self.class}: #{to_s(show_attr: true)}"+(graph.nil? ? "" : " (#{graph})")
|
|
85
85
|
end
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
# output like a graph
|
|
87
|
+
def to_graph(indent_level: 0, show_attr: true, out: [])
|
|
88
88
|
margin = ''
|
|
89
89
|
0.upto(indent_level/STEP-1) { |p| margin += (p==0 ? ' ' : '|') + ' '*(STEP - 1) }
|
|
90
90
|
margin += '|' + '-'*(STEP - 2)
|
|
91
|
-
|
|
91
|
+
out << margin + "#{to_s(show_attr: show_attr)}"
|
|
92
92
|
@children.each do |child|
|
|
93
|
-
|
|
93
|
+
child.to_graph(indent_level: indent_level+STEP, show_attr: show_attr, out: out)
|
|
94
94
|
end
|
|
95
|
-
return
|
|
95
|
+
return out
|
|
96
96
|
end
|
|
97
|
-
def to_dot
|
|
98
|
-
|
|
97
|
+
def to_dot(out: [])
|
|
98
|
+
out << "\""+name+"\""
|
|
99
99
|
@children.each do |child|
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
out << "\"#{@name}\" -> \"#{child.name}\""
|
|
101
|
+
child.to_dot(out: out)
|
|
102
102
|
end
|
|
103
|
-
return
|
|
103
|
+
return out
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
|
|
@@ -109,6 +109,7 @@ module DR
|
|
|
109
109
|
include Enumerable
|
|
110
110
|
def initialize(*nodes, attributes: {}, infos: nil)
|
|
111
111
|
@nodes=[]
|
|
112
|
+
# a node can be a Hash or a Node
|
|
112
113
|
build(*nodes, attributes: {}, infos: infos)
|
|
113
114
|
end
|
|
114
115
|
def each(&b)
|
|
@@ -214,30 +215,32 @@ module DR
|
|
|
214
215
|
@nodes.select{ |n| n.children.length == 0}.sort
|
|
215
216
|
end
|
|
216
217
|
|
|
218
|
+
# allow a hash too
|
|
217
219
|
def |(graph)
|
|
220
|
+
graph=Graph.new(graph) unless Graph===graph
|
|
218
221
|
build(*graph.all, recursive: false)
|
|
219
222
|
end
|
|
220
223
|
def +(graph)
|
|
221
224
|
clone.|(graph)
|
|
222
225
|
end
|
|
223
226
|
|
|
224
|
-
def dump(mode: :graph, nodes_list: :roots, show_attr: true, **
|
|
227
|
+
def dump(mode: :graph, nodes_list: :roots, show_attr: true, out: [], **_opts)
|
|
225
228
|
n=case nodes_list
|
|
226
229
|
when :roots; roots
|
|
227
230
|
when :all; all
|
|
228
231
|
when Symbol; nodes.select {|n| n.attributes[:nodes_list]}
|
|
229
232
|
else nodes_list.to_a
|
|
230
233
|
end
|
|
231
|
-
sout = ""
|
|
232
234
|
case mode
|
|
233
|
-
when :graph; n.each do |node|
|
|
234
|
-
when :list; n.each do |i|
|
|
235
|
+
when :graph; n.each do |node| node.to_graph(show_attr: show_attr, out: out) end
|
|
236
|
+
when :list; n.each do |i| out << "- #{i}" end
|
|
235
237
|
when :dot;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
238
|
+
out << "digraph gems {"
|
|
239
|
+
#out << n.map {|node| node.to_dot}.inject(:+).uniq!.join("\n")
|
|
240
|
+
n.map {|node| node.to_dot(out: out)}
|
|
241
|
+
out << "}"
|
|
239
242
|
end
|
|
240
|
-
return
|
|
243
|
+
return out
|
|
241
244
|
end
|
|
242
245
|
|
|
243
246
|
def to_nodes(*nodes)
|
data/lib/dr/base/uri.rb
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "delegate"
|
|
3
|
+
|
|
4
|
+
module URI
|
|
5
|
+
# From https://github.com/packsaddle/ruby-uri-ssh_git
|
|
6
|
+
module Ssh
|
|
7
|
+
extend self
|
|
8
|
+
# @example
|
|
9
|
+
# url = URI::SshGit.parse('git@github.com:packsaddle/ruby-uri-ssh_git.git')
|
|
10
|
+
# #=> #<URI::SshGit::Generic git@github.com:packsaddle/ruby-uri-ssh_git.git>
|
|
11
|
+
# url.scheme #=> nil
|
|
12
|
+
# url.userinfo #=> 'git'
|
|
13
|
+
# url.user #=> 'git'
|
|
14
|
+
# url.password #=> nil
|
|
15
|
+
# url.host #=> 'github.com'
|
|
16
|
+
# url.port #=> nil
|
|
17
|
+
# url.registry #=> nil
|
|
18
|
+
# url.path #=> 'packsaddle/ruby-uri-ssh_git.git'
|
|
19
|
+
# url.opaque #=> nil
|
|
20
|
+
# url.query #=> nil
|
|
21
|
+
# url.fragment #=> nil
|
|
22
|
+
# @see http://docs.ruby-lang.org/en/2.2.0/URI/Generic.html
|
|
23
|
+
# @param url [String] git repository url via ssh protocol
|
|
24
|
+
# @return [Generic] parsed object
|
|
25
|
+
protected def internal_parse(uri_string)
|
|
26
|
+
host_part, path_part = uri_string&.split(':', 2)
|
|
27
|
+
# There may be no user, so reverse the split to make sure host always
|
|
28
|
+
# is !nil if host_part was !nil.
|
|
29
|
+
host, userinfo = host_part&.split('@', 2)&.reverse
|
|
30
|
+
Generic.build(userinfo: userinfo, host: host || uri_string, path: path_part)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param url [String] git repository-ish url
|
|
34
|
+
# @return [URI::Generic] if url starts ssh
|
|
35
|
+
# @return [URI::HTTPS] if url starts https
|
|
36
|
+
# @return [URI::SshGit] if url is ssh+git e.g git@example.com:schacon/ticgit.git
|
|
37
|
+
def parse(url, force: false)
|
|
38
|
+
(ssh_git_url?(url) || force)? URI::Ssh.internal_parse(url) : URI.parse(url)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
## From: https://github.com/packsaddle/ruby-git_clone_url
|
|
42
|
+
# @param url [String] git repository-ish url
|
|
43
|
+
# @return [Boolean] true if url is git via ssh protocol
|
|
44
|
+
def ssh_git_url?(url)
|
|
45
|
+
!generic_url?(url)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param url [String] git repository-ish url
|
|
49
|
+
# @return [Boolean] true if url is https, ssh protocol
|
|
50
|
+
def generic_url?(url)
|
|
51
|
+
match = %r{\A(\w*)://}.match(url)
|
|
52
|
+
!match.nil?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Generic < ::URI::Generic
|
|
56
|
+
# check_host returns `false` for 'foo_bar'
|
|
57
|
+
# but in ssh config this can be a valid host
|
|
58
|
+
def check_host(v)
|
|
59
|
+
return true
|
|
60
|
+
end
|
|
61
|
+
# @example
|
|
62
|
+
# Generic.build(
|
|
63
|
+
# userinfo: 'git',
|
|
64
|
+
# host: 'github.com',
|
|
65
|
+
# path: 'packsaddle/ruby-uri-ssh_git.git'
|
|
66
|
+
# ).to_ssh
|
|
67
|
+
# #=> 'git@github.com:packsaddle/ruby-uri-ssh_git.git'
|
|
68
|
+
#
|
|
69
|
+
# @return [String] git repository url via ssh protocol
|
|
70
|
+
def to_ssh(show_path: true)
|
|
71
|
+
str = ''
|
|
72
|
+
str << "#{user}@" if user && !user.empty?
|
|
73
|
+
str << "#{host}"
|
|
74
|
+
str << ":#{path}" if path and show_path
|
|
75
|
+
str
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
module DR
|
|
82
|
+
module MailToHelper
|
|
83
|
+
# TODO: wrap to= to add user= and host=
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
module URIlikeWrapper
|
|
87
|
+
def to_h
|
|
88
|
+
h = { uri: uri }
|
|
89
|
+
components = uri.component
|
|
90
|
+
components += %i[user password] if components.include?(:userinfo)
|
|
91
|
+
components.each do |m|
|
|
92
|
+
v = uri.public_send(m)
|
|
93
|
+
v && h[m] = v
|
|
94
|
+
end
|
|
95
|
+
h
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_json(_state = nil)
|
|
99
|
+
to_h.to_json
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def to_public
|
|
103
|
+
pub = dup
|
|
104
|
+
pub.password = nil
|
|
105
|
+
pub.to_s
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# uri=u2.merge(uri) does not work if uri is absolute
|
|
109
|
+
def reverse_merge(u2)
|
|
110
|
+
# return self unless uri.scheme
|
|
111
|
+
u2 = u2.clone
|
|
112
|
+
u2 = self.class.new(u2) unless u2.is_a?(self.class)
|
|
113
|
+
if opaque.nil? == u2.opaque.nil?
|
|
114
|
+
u2.soft_merge(self)
|
|
115
|
+
else
|
|
116
|
+
self
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# merge(u2) replace self by u2 if u2 is aboslute
|
|
121
|
+
# soft_merge looks at each u2 components
|
|
122
|
+
def soft_merge(u2)
|
|
123
|
+
# we want automatic unescaping of u2 components
|
|
124
|
+
u2 = self.class.new(u2) unless u2.is_a?(self.class)
|
|
125
|
+
# only merge if we are both opaque or path like
|
|
126
|
+
if opaque.nil? == u2.opaque.nil?
|
|
127
|
+
components = uri.component
|
|
128
|
+
if components.include?(:userinfo)
|
|
129
|
+
components += %i[user password]
|
|
130
|
+
components.delete(:userinfo)
|
|
131
|
+
end
|
|
132
|
+
components.each do |m|
|
|
133
|
+
# path returns "" by default but we don't want to merge in this case
|
|
134
|
+
if u2.respond_to?(m) && (v = u2.public_send(m)) && !((v == "") && (m == :path))
|
|
135
|
+
uri.public_send(:"#{m}=", v)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
self
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class URIWrapper < SimpleDelegator
|
|
144
|
+
def uri
|
|
145
|
+
__getobj__
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def uri=(uri)
|
|
149
|
+
__setobj__(transform_uri(uri))
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
include URIlikeWrapper
|
|
153
|
+
|
|
154
|
+
def self.parse(s)
|
|
155
|
+
new(URI.parse(s))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def self.get_uri_object(uri)
|
|
159
|
+
uri = self.parse(uri.to_s) unless uri.is_a?(URI)
|
|
160
|
+
uri
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private def transform_uri(uri)
|
|
164
|
+
# wrap the components around escape/unescape
|
|
165
|
+
uri = self.class.get_uri_object(uri)
|
|
166
|
+
if uri.is_a?(URI)
|
|
167
|
+
components = uri.component
|
|
168
|
+
components += %i[user password] if components.include?(:userinfo)
|
|
169
|
+
components.each do |m|
|
|
170
|
+
uri.define_singleton_method(m) do
|
|
171
|
+
r = super()
|
|
172
|
+
r && r.is_a?(String) ? URI.unescape(r) : r
|
|
173
|
+
end
|
|
174
|
+
uri.define_singleton_method(:"#{m}=") do |v|
|
|
175
|
+
begin
|
|
176
|
+
super(v && v.is_a?(String) ? URI.escape(v) : v)
|
|
177
|
+
rescue URI::InvalidURIError => e
|
|
178
|
+
warn "#{e} in (#{self}).#{m}=#{v}"
|
|
179
|
+
# require 'pry'; binding.pry
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
uri.extend(MailToHelper) if uri.is_a?(URI::MailTo)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
uri
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# recall that '//user@server' is an uri while 'user@server' is just a path
|
|
189
|
+
def initialize(uri)
|
|
190
|
+
super
|
|
191
|
+
self.uri = uri
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
class Ssh < URIWrapper
|
|
195
|
+
def self.parse(s)
|
|
196
|
+
new(URI::Ssh.parse(s))
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
end
|
data/lib/dr/base/utils.rb
CHANGED
|
@@ -5,10 +5,10 @@ module DR
|
|
|
5
5
|
case format.to_s
|
|
6
6
|
when "json"
|
|
7
7
|
require 'json'
|
|
8
|
-
return pretty_print(string.to_json)
|
|
8
|
+
return pretty_print(string.to_json, pretty: pretty)
|
|
9
9
|
when "yaml"
|
|
10
10
|
require "yaml"
|
|
11
|
-
return pretty_print(string.to_yaml)
|
|
11
|
+
return pretty_print(string.to_yaml, pretty: pretty)
|
|
12
12
|
end
|
|
13
13
|
if pretty.to_s=="color"
|
|
14
14
|
begin
|
|
@@ -24,5 +24,29 @@ module DR
|
|
|
24
24
|
puts string
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
# stolen from active support
|
|
29
|
+
def to_camel_case(s)
|
|
30
|
+
s.sub(/^[a-z\d]*/) { |match| match.capitalize }.
|
|
31
|
+
gsub(/(?:_|(\/))([a-z\d]*)/i) {"#{$1}#{$2.capitalize}"}.
|
|
32
|
+
gsub("/", "::")
|
|
33
|
+
end
|
|
34
|
+
def to_snake_case(s)
|
|
35
|
+
# convert from caml case to snake_case
|
|
36
|
+
s.gsub(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2').
|
|
37
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def rsplit(s, sep, num=nil)
|
|
41
|
+
if num.nil? or num==0
|
|
42
|
+
s.split(sep)
|
|
43
|
+
else
|
|
44
|
+
components=s.split(sep)
|
|
45
|
+
components+=[nil]*[(num-components.length), 0].max
|
|
46
|
+
a=components[0..(components.length-num)]
|
|
47
|
+
b=components[(components.length-num+1)..(components.length-1)]
|
|
48
|
+
return [a.join(sep), *b]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
27
51
|
end
|
|
28
52
|
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
module DR
|
|
2
|
+
class Formatter
|
|
3
|
+
module Helpers
|
|
4
|
+
def localize(msg, lang: :en, **_opts)
|
|
5
|
+
case msg
|
|
6
|
+
when Hash
|
|
7
|
+
Array(lang).each do |l|
|
|
8
|
+
if msg.key?(l)
|
|
9
|
+
yield(msg[l]) if block_given?
|
|
10
|
+
return msg[l]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
else
|
|
14
|
+
msg
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def wrap(content, pre:nil, post:nil)
|
|
19
|
+
return content if content.nil? or content.empty?
|
|
20
|
+
pre.to_s+content.to_s+post.to_s
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def join(*args, pre: "", post: "", pre_item: "", post_item: "", join: :auto, **_opts)
|
|
24
|
+
args=Array(args)
|
|
25
|
+
list=args.compact.map {|i| wrap(i, pre: pre_item, post: post_item)}.delete_if {|i| i.empty?}
|
|
26
|
+
r=list.shift
|
|
27
|
+
list.each do |s|
|
|
28
|
+
if join==:auto
|
|
29
|
+
if r[-1]=="\n" or s[1]=="\n"
|
|
30
|
+
r+=s
|
|
31
|
+
else
|
|
32
|
+
r+=" "+s
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
r+=join+s
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
r=pre+r+post unless r.nil? or r.empty?
|
|
39
|
+
r
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
extend Helpers
|
|
43
|
+
|
|
44
|
+
attr_accessor :opts, :meta
|
|
45
|
+
def initialize(meta={}, **opts)
|
|
46
|
+
@meta=meta
|
|
47
|
+
@opts=opts
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def localize(msg, **opts)
|
|
51
|
+
self.class.localize(msg, **@opts.merge(opts))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def join(*args, **opts)
|
|
55
|
+
opts=@opts.merge(opts)
|
|
56
|
+
args=Array(args).map {|i| try_expand_symbol(i,**opts)}
|
|
57
|
+
self.class.localize(*args, **opts)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private def metainfo_from_symbol(sym, meta: @meta, **opts)
|
|
61
|
+
return sym if opts[:meta_symbol]==:never
|
|
62
|
+
content=case meta
|
|
63
|
+
when Hash
|
|
64
|
+
warn "#{sym} not found in #{meta}" unless meta.key?(sym)
|
|
65
|
+
meta[sym]
|
|
66
|
+
when Proc
|
|
67
|
+
meta.call(sym, **opts)
|
|
68
|
+
else
|
|
69
|
+
sym
|
|
70
|
+
end
|
|
71
|
+
if block_given?
|
|
72
|
+
yield content, **opts
|
|
73
|
+
else
|
|
74
|
+
content
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private def get_symbol(sym)
|
|
79
|
+
case sym
|
|
80
|
+
when Symbol
|
|
81
|
+
return sym
|
|
82
|
+
when String
|
|
83
|
+
return sym[1...sym.length].to_sym if sym[0] == ':'
|
|
84
|
+
end
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
private def try_get_symbol(sym,**opts)
|
|
88
|
+
if (key=get_symbol(sym))
|
|
89
|
+
expand_symbol(key,**opts)
|
|
90
|
+
else
|
|
91
|
+
sym
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def expand(msg, **opts)
|
|
96
|
+
recursive=opts[:recursive]
|
|
97
|
+
#if recursive is :first, then we only expand once ore
|
|
98
|
+
if recursive.is_a?(Integer)
|
|
99
|
+
opts[:recursive]=recursive-1
|
|
100
|
+
recursive=false if recursive <= 0
|
|
101
|
+
end
|
|
102
|
+
case msg
|
|
103
|
+
when Hash
|
|
104
|
+
Array(opts[:merge]).each do |key|
|
|
105
|
+
if msg.key?(key)
|
|
106
|
+
msg=msg.merge(msg[key])
|
|
107
|
+
msg.delete(key)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
# we localize after merging potential out types
|
|
111
|
+
localize(msg, **opts) do |lmsg|
|
|
112
|
+
# localization do not count as a recursive step
|
|
113
|
+
return expand(lmsg, **opts)
|
|
114
|
+
end
|
|
115
|
+
if recursive
|
|
116
|
+
msg_exp={}
|
|
117
|
+
msg.each do |k,v|
|
|
118
|
+
msg_exp[k]=expand(v,**opts)
|
|
119
|
+
end
|
|
120
|
+
#expand may have introduced nil values
|
|
121
|
+
clean_nil=opts.fetch(:clean_nil,true)
|
|
122
|
+
msg_exp.delete_if {|_k,v| v==nil} if clean_nil
|
|
123
|
+
msg_exp
|
|
124
|
+
else
|
|
125
|
+
msg
|
|
126
|
+
end
|
|
127
|
+
when Symbol
|
|
128
|
+
opts[:symbol]||=:never
|
|
129
|
+
msg=metainfo_from_symbol(msg,**opts)
|
|
130
|
+
recursive ? expand(msg, **opts) : msg
|
|
131
|
+
when Array
|
|
132
|
+
msg=msg.map {|i| expand(i,**opts)} if recursive
|
|
133
|
+
opts[:join] ? join(msg, **opts) : msg
|
|
134
|
+
when String
|
|
135
|
+
(nmsg=try_get_symbol(msg,**opts)) and return nmsg
|
|
136
|
+
if block_given?
|
|
137
|
+
yield(msg, **opts)
|
|
138
|
+
else
|
|
139
|
+
msg
|
|
140
|
+
end
|
|
141
|
+
when nil
|
|
142
|
+
nil
|
|
143
|
+
else
|
|
144
|
+
if block_given?
|
|
145
|
+
yield(msg, **opts)
|
|
146
|
+
else
|
|
147
|
+
#expand(msg.to_s,**opts)
|
|
148
|
+
msg
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
require 'dr/formatter/simple_formatter'
|
|
2
|
+
|
|
3
|
+
module DR
|
|
4
|
+
module DateRangeParser
|
|
5
|
+
extend self
|
|
6
|
+
#in: 2014-01-02 -> 2014-01-03, 2014-01-05, 2014-02 -> :now
|
|
7
|
+
#out: [[2014-01-02,2014-01-03],[2014-01-05],[2014-02,:now]]
|
|
8
|
+
def parse(date)
|
|
9
|
+
return date if date.kind_of?(self)
|
|
10
|
+
r=[]
|
|
11
|
+
dates = date.to_s.chomp.split(/,\s*/)
|
|
12
|
+
dates.each do |d|
|
|
13
|
+
r << d.split(/\s*->\s*/).map {|i| i == ":now" ? :now : i }
|
|
14
|
+
end
|
|
15
|
+
return DateRange.new(r)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module DateOutput
|
|
20
|
+
extend self
|
|
21
|
+
#BUG: années bissextiles...
|
|
22
|
+
Months_end={1 => 31, 2 => 28, 3 => 31, 4 => 30,
|
|
23
|
+
5 => 31, 6 => 30, 7 => 31, 8 => 31,
|
|
24
|
+
9 => 30, 10 => 31, 11 => 30, 12 => 31}
|
|
25
|
+
|
|
26
|
+
# Convert a Date/string into a Time
|
|
27
|
+
def to_time(datetime, complete_date: :first, **opts)
|
|
28
|
+
require 'time'
|
|
29
|
+
return Time.now if datetime == :now
|
|
30
|
+
begin
|
|
31
|
+
fallback=Time.new(0) #supply the missing components
|
|
32
|
+
return Time.parse(datetime,fallback)
|
|
33
|
+
rescue ArgumentError
|
|
34
|
+
year,month,day,time=split_date(datetime)
|
|
35
|
+
case complete_date
|
|
36
|
+
when :first
|
|
37
|
+
month="01" if month == nil
|
|
38
|
+
day="01" if day == nil
|
|
39
|
+
time="00:00:00" if day == nil
|
|
40
|
+
when :last
|
|
41
|
+
month="12" if month == nil
|
|
42
|
+
day=Months_end[month.to_i].to_s if day == nil
|
|
43
|
+
time="23:59:59" if day == nil
|
|
44
|
+
end
|
|
45
|
+
return Time.parse("#{year}-#{month}-#{day}T#{time}",fallback)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
#ex: split 2014-07-28T19:26:20+0200 into year,month,day,time
|
|
50
|
+
def split_date(datetime)
|
|
51
|
+
datetime=Time.now.iso8601 if datetime == :now
|
|
52
|
+
date,time=datetime.to_s.split("T")
|
|
53
|
+
year,month,day=date.split("-")
|
|
54
|
+
return year,month,day,time
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
Months_names={en: {
|
|
58
|
+
1 => 'January', 2 => 'February', 3 => 'March',
|
|
59
|
+
4 => 'April', 5 => 'May', 6 => 'June',
|
|
60
|
+
7 => 'July', 8 => 'August', 9 => 'September',
|
|
61
|
+
10 => 'October', 11 => 'November', 12 => 'December'},
|
|
62
|
+
fr: {
|
|
63
|
+
1 => 'Janvier', 2 => 'Février', 3 => 'Mars',
|
|
64
|
+
4 => 'Avril', 5 => 'Mai', 6 => 'Juin',
|
|
65
|
+
7 => 'Juillet', 8 => 'Août', 9 => 'Septembre',
|
|
66
|
+
10 => 'Octobre', 11 => 'Novembre', 12 => 'Décembre'}}
|
|
67
|
+
|
|
68
|
+
private def abbr_month(month, lang: :en, **_opts)
|
|
69
|
+
return month if month.length <= 4
|
|
70
|
+
return month[0..2]+(lang==:en ? '.' : '')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# output_date_length: granularity :year/:month/:day/:all
|
|
74
|
+
# output_date: :num, :string, :abbr
|
|
75
|
+
def output_date(datetime, output_date: :abbr, output_date_length: :month,
|
|
76
|
+
**opts)
|
|
77
|
+
lang=opts[:lang]||:en
|
|
78
|
+
year,month,day,time=split_date(datetime)
|
|
79
|
+
month=nil if output_date_length==:year
|
|
80
|
+
day=nil if output_date_length==:month
|
|
81
|
+
time=nil if output_date_length==:day
|
|
82
|
+
return Formatter.localize({en: 'Present', fr: 'Présent'},**opts) if datetime==:now
|
|
83
|
+
r=year
|
|
84
|
+
case output_date
|
|
85
|
+
when :num
|
|
86
|
+
month.nil? ? (return r) : r+="-"+month
|
|
87
|
+
day.nil? ? (return r) : r+="-"+day
|
|
88
|
+
time.nil? ? (return r) : r+="T"+time
|
|
89
|
+
when :abbr,:string
|
|
90
|
+
return r if month.nil?
|
|
91
|
+
month_name=Months_names[lang][month.to_i]
|
|
92
|
+
month_name=abbr_month(month_name) if output_date==:abbr
|
|
93
|
+
r=month_name+" "+r
|
|
94
|
+
return r if day.nil?
|
|
95
|
+
r=day+" "+r
|
|
96
|
+
return r if time.nil?
|
|
97
|
+
r+=" "+time
|
|
98
|
+
end
|
|
99
|
+
r
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class DateRange
|
|
104
|
+
extend DateRangeParser
|
|
105
|
+
extend DateOutput
|
|
106
|
+
|
|
107
|
+
attr_accessor :d, :t
|
|
108
|
+
def initialize(d)
|
|
109
|
+
@d=d
|
|
110
|
+
@t=d.map do |range|
|
|
111
|
+
case range.length
|
|
112
|
+
when 1
|
|
113
|
+
[DateRange.to_time(range[0], complete_date: :first),
|
|
114
|
+
DateRange.to_time(range[0], complete_date: :last)]
|
|
115
|
+
when 2
|
|
116
|
+
[DateRange.to_time(range[0], complete_date: :first),
|
|
117
|
+
DateRange.to_time(range[1], complete_date: :last)]
|
|
118
|
+
else
|
|
119
|
+
range.map {|i| DateRange.to_time(i)}
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
#sort_date_by :first or :last
|
|
125
|
+
def <=>(d2, sort_date_by: :last,**_opts)
|
|
126
|
+
d1=@t; d2=d2.t
|
|
127
|
+
sel=lambda do |d|
|
|
128
|
+
case sort_date_by
|
|
129
|
+
when :last
|
|
130
|
+
return d.map {|i| i.last}
|
|
131
|
+
when :first
|
|
132
|
+
return d.map {|i| i.first}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
best=lambda do |d|
|
|
136
|
+
case sort_date_by
|
|
137
|
+
when :last
|
|
138
|
+
return d.max
|
|
139
|
+
when :first
|
|
140
|
+
return d.min
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
b1=best.call(sel.call(d1))
|
|
144
|
+
b2=best.call(sel.call(d2))
|
|
145
|
+
return b1 <=> b2
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def to_s(join: ", ", range_join: " – ", **opts)
|
|
149
|
+
r=@d.map do |range|
|
|
150
|
+
range.map do |d|
|
|
151
|
+
DateRange.output_date(d,**opts)
|
|
152
|
+
end.join(range_join)
|
|
153
|
+
end.join(join)
|
|
154
|
+
r.empty? ? nil : r
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module DR
|
|
2
|
+
class SimpleKeywordsParser
|
|
3
|
+
attr_accessor :opts, :keywords
|
|
4
|
+
|
|
5
|
+
def initialize(hash, **opts)
|
|
6
|
+
@opts=opts
|
|
7
|
+
@keywords=hash
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def keyword(name, &b)
|
|
11
|
+
@keywords[name]=b
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def parse(msg, **opts)
|
|
15
|
+
opts=@opts.merge(opts)
|
|
16
|
+
sep=opts[:sep] || ','
|
|
17
|
+
# Warning: the delims must take only one char
|
|
18
|
+
delims= opts[:delims] || '()'
|
|
19
|
+
bdelim= delims[0]
|
|
20
|
+
edelim= delims[1] || bdelim
|
|
21
|
+
keywords=@keywords.keys
|
|
22
|
+
keywords_r="(?:"+keywords.map {|k| "(?:"+k+")"}.join("|")+")"
|
|
23
|
+
reg = %r{(?<kw>#{keywords_r})(?<re>#{'\\'+bdelim}(?:(?>[^#{'\\'+bdelim}#{'\\'+edelim}]+)|\g<re>)*#{'\\'+edelim})}
|
|
24
|
+
if (m=reg.match(msg))
|
|
25
|
+
arg=m[:re][1...m[:re].length-1]
|
|
26
|
+
arg=parse(arg, **opts)
|
|
27
|
+
args=arg.split(sep)
|
|
28
|
+
args=args.map {|a| a.strip} unless opts[:space]
|
|
29
|
+
key=keywords.find {|k| /#{k}/ =~ m[:kw]}
|
|
30
|
+
r=@keywords[key].call(*args).to_s
|
|
31
|
+
msg=m.pre_match+r+parse(m.post_match,**opts)
|
|
32
|
+
msg=keywords(msg,@keywords,**opts) if opts[:recursive]
|
|
33
|
+
end
|
|
34
|
+
return msg
|
|
35
|
+
end
|
|
36
|
+
# re = %r{
|
|
37
|
+
# (?<re>
|
|
38
|
+
# \(
|
|
39
|
+
# (?:
|
|
40
|
+
# (?> [^()]+ )
|
|
41
|
+
# |
|
|
42
|
+
# \g<re>
|
|
43
|
+
# )*
|
|
44
|
+
# \)
|
|
45
|
+
# )
|
|
46
|
+
# }x
|
|
47
|
+
#(?<re> name regexp/match
|
|
48
|
+
#\g<re> reuse regexp
|
|
49
|
+
#\k<re> reuse match
|
|
50
|
+
#(?: grouping without capturing
|
|
51
|
+
#(?> atomic grouping
|
|
52
|
+
#x whitespace does not count
|
|
53
|
+
# -> match balanced groups of parentheses
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -24,6 +24,24 @@ module DR
|
|
|
24
24
|
return name,value
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# parse opt1=value1:opt2=value2...
|
|
28
|
+
def parse_options(options, arg_split:':', valuesep: '=', opt_default: true, keyed_sep: "/")
|
|
29
|
+
return {} unless options
|
|
30
|
+
parsed_options={}
|
|
31
|
+
options=options.split(arg_split) unless options.is_a?(Enumerable)
|
|
32
|
+
options.each do |optvalue|
|
|
33
|
+
opt,value=DR::SimpleParser.parse_namevalue(optvalue,sep: valuesep, default: opt_default)
|
|
34
|
+
parsed_options.set_keyed_value(opt,value, sep: keyed_sep)
|
|
35
|
+
end
|
|
36
|
+
return parsed_options
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# parse name:opt1=value1:opt2=value2...
|
|
40
|
+
def parse_name_options(name, arg_split:':', **keywords)
|
|
41
|
+
name,*options=name.split(arg_split)
|
|
42
|
+
return name, parse_options(options, arg_split: arg_split, **keywords)
|
|
43
|
+
end
|
|
44
|
+
|
|
27
45
|
#takes a string as "name:value!option1=ploum!option2=plam,name2:value2!!globalopt=plim,globalopt2=plam!!globalopt3=plom,globalopt4=plim"
|
|
28
46
|
#and return the hash
|
|
29
47
|
#{values: {name: value, name2: value2},
|
data/lib/dr/parse/time_parse.rb
CHANGED
|
@@ -30,12 +30,12 @@ module DR
|
|
|
30
30
|
# #=> {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}
|
|
31
31
|
#
|
|
32
32
|
# Adapted from active support
|
|
33
|
-
def deep_merge(other_hash, &block)
|
|
34
|
-
dup.deep_merge!(other_hash, &block)
|
|
33
|
+
def deep_merge(other_hash, **opts, &block)
|
|
34
|
+
dup.deep_merge!(other_hash, **opts, &block)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Same as +deep_merge+, but modifies +self+.
|
|
38
|
-
def deep_merge!(other_hash, &block)
|
|
38
|
+
def deep_merge!(other_hash, append: :auto, &block)
|
|
39
39
|
return unless other_hash
|
|
40
40
|
other_hash.each_pair do |k,v|
|
|
41
41
|
tv = self[k]
|
|
@@ -43,17 +43,19 @@ module DR
|
|
|
43
43
|
when tv.is_a?(Hash) && v.is_a?(Hash)
|
|
44
44
|
self[k] = tv.deep_merge(v, &block)
|
|
45
45
|
when tv.is_a?(Array) && v.is_a?(Array)
|
|
46
|
-
if v.length > 0 && v.first.nil? then
|
|
46
|
+
if append==:auto and v.length > 0 && v.first.nil? then
|
|
47
47
|
#hack: if the array begins with nil, we append the new
|
|
48
48
|
#value rather than overwrite it
|
|
49
49
|
v.shift
|
|
50
50
|
self[k] += v
|
|
51
|
+
elsif append && append != :auto
|
|
52
|
+
self[k] += v
|
|
51
53
|
else
|
|
52
54
|
self[k] = block && tv ? block.call(k, tv, v) : v
|
|
53
55
|
end
|
|
54
56
|
when tv.nil? && v.is_a?(Array)
|
|
55
57
|
#here we still need to remove nil (see above)
|
|
56
|
-
if v.length > 0 && v.first.nil? then
|
|
58
|
+
if append==:auto and v.length > 0 && v.first.nil? then
|
|
57
59
|
v.shift
|
|
58
60
|
self[k]=v
|
|
59
61
|
else
|
|
@@ -66,6 +68,13 @@ module DR
|
|
|
66
68
|
self
|
|
67
69
|
end
|
|
68
70
|
|
|
71
|
+
def reverse_merge(other_hash)
|
|
72
|
+
other_hash.merge(self)
|
|
73
|
+
end
|
|
74
|
+
def reverse_deep_merge(other_hash)
|
|
75
|
+
other_hash.deep_merge(self)
|
|
76
|
+
end
|
|
77
|
+
|
|
69
78
|
#from a hash {key: [values]} produce a hash {value: [keys]}
|
|
70
79
|
#there is already Hash#invert using Hash#key which does that, but the difference here is that we flatten Enumerable values
|
|
71
80
|
#h={ploum: 2, plim: 2, plam: 3}
|
|
@@ -137,6 +146,78 @@ module DR
|
|
|
137
146
|
end until s==r
|
|
138
147
|
r
|
|
139
148
|
end
|
|
149
|
+
|
|
150
|
+
# Adapted from File activesupport/lib/active_support/core_ext/hash/slice.rb, line 22
|
|
151
|
+
# Note that ruby has Hash#slice, but if the key does not exist, we
|
|
152
|
+
# cannot configure a default
|
|
153
|
+
def slice_with_default(*keys, default: nil)
|
|
154
|
+
keys.each_with_object(::Hash.new) do |k, hash|
|
|
155
|
+
if has_key?(k) || default == :default_proc
|
|
156
|
+
hash[k] = self[k]
|
|
157
|
+
else
|
|
158
|
+
hash[k] = default
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def dig_with_default(*args, default: nil)
|
|
164
|
+
r=dig(*args)
|
|
165
|
+
return default if r.nil?
|
|
166
|
+
r
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def has_keys?(*keys, key)
|
|
170
|
+
i=self
|
|
171
|
+
keys.each do |k|
|
|
172
|
+
i.key?(k) or return false
|
|
173
|
+
i=i[k]
|
|
174
|
+
end
|
|
175
|
+
i.key?(key)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def set_key(*keys, key, value)
|
|
179
|
+
i=self
|
|
180
|
+
keys.each do |k|
|
|
181
|
+
i.key?(k) or i[k]={}
|
|
182
|
+
i=i[k]
|
|
183
|
+
end
|
|
184
|
+
i[key]=value
|
|
185
|
+
# self
|
|
186
|
+
end
|
|
187
|
+
# like set_key, but only set the value if it does not exist
|
|
188
|
+
def add_key(*keys, key, value)
|
|
189
|
+
i=self
|
|
190
|
+
keys.each do |k|
|
|
191
|
+
i.key?(k) or i[k]={}
|
|
192
|
+
i=i[k]
|
|
193
|
+
end
|
|
194
|
+
i.key?(key) or i[key]=value
|
|
195
|
+
# self
|
|
196
|
+
i[key]
|
|
197
|
+
end
|
|
198
|
+
#like add_key, but consider the value is an Array and add to it
|
|
199
|
+
def add_to_key(*keys, key, value, overwrite: false, uniq: true, deep: false)
|
|
200
|
+
i=self
|
|
201
|
+
keys.each do |k|
|
|
202
|
+
i.key?(k) or i[k]={}
|
|
203
|
+
i=i[k]
|
|
204
|
+
end
|
|
205
|
+
if value.is_a?(Hash)
|
|
206
|
+
v=i[key] || {}
|
|
207
|
+
if deep
|
|
208
|
+
overwrite ? v.deep_merge!(value) : v=value.deep_merge(v)
|
|
209
|
+
else
|
|
210
|
+
overwrite ? v.merge!(value) : v=value.merge(v)
|
|
211
|
+
end
|
|
212
|
+
else
|
|
213
|
+
v=i[key] || []
|
|
214
|
+
v += Array(value)
|
|
215
|
+
v.uniq! if uniq
|
|
216
|
+
end
|
|
217
|
+
i[key]=v
|
|
218
|
+
# self
|
|
219
|
+
end
|
|
220
|
+
|
|
140
221
|
end
|
|
141
222
|
|
|
142
223
|
module UnboundMethod
|
data/lib/dr/version.rb
CHANGED
data/test/helper.rb
CHANGED
|
@@ -4,8 +4,8 @@ require 'minitest/autorun'
|
|
|
4
4
|
#require 'pry-rescue/minitest'
|
|
5
5
|
|
|
6
6
|
begin
|
|
7
|
-
require 'minitest/reporters'
|
|
8
|
-
Minitest::Reporters.use! Minitest::Reporters::DefaultReporter.new
|
|
7
|
+
# require 'minitest/reporters'
|
|
8
|
+
# Minitest::Reporters.use! Minitest::Reporters::DefaultReporter.new
|
|
9
9
|
#Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
|
10
10
|
#Minitest::Reporters.use! Minitest::Reporters::ProgressReporter.new
|
|
11
11
|
rescue LoadError => error
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
require 'dr/parse/date_parse'
|
|
3
|
+
|
|
4
|
+
describe DR::DateRange do
|
|
5
|
+
before do
|
|
6
|
+
ENV['TZ']='GMT'
|
|
7
|
+
@daterange=DR::DateRange.parse("2014-01-02 -> 2014-01-03, 2014-01-05, 2014-02 -> :now")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "Can parse dates" do
|
|
11
|
+
@daterange.d.must_equal [["2014-01-02", "2014-01-03"], ["2014-01-05"], ["2014-02", :now]]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "Can output a date range" do
|
|
15
|
+
@daterange.to_s.must_equal "Jan. 2014 – Jan. 2014, Jan. 2014, Feb. 2014 – Present"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "Can output a date range with full time information" do
|
|
19
|
+
@daterange.to_s(output_date_length: :all).must_equal "02 Jan. 2014 – 03 Jan. 2014, 05 Jan. 2014, Feb. 2014 – Present"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "Has time information" do
|
|
23
|
+
@daterange.t[0].to_s.must_equal "[2014-01-02 00:00:00 +0000, 2014-01-03 00:00:00 +0000]".encode('US-ASCII')
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
require 'dr/parse/simple_keywords'
|
|
3
|
+
|
|
4
|
+
describe DR::SimpleKeywordsParser do
|
|
5
|
+
before do
|
|
6
|
+
@parser=DR::SimpleKeywordsParser.new({
|
|
7
|
+
'FOO' => lambda { |*args| "FOO: #{args}" },
|
|
8
|
+
'BAR' => lambda { |*args| "BAR: #{args}" },
|
|
9
|
+
})
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "Can parse keywords" do
|
|
13
|
+
@parser.parse("FOO(ploum, plam)").must_equal 'FOO: ["ploum", "plam"]'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "Can preserver spaces" do
|
|
17
|
+
@parser.parse("FOO( ploum , plam )", space: true).must_equal "FOO: [\" ploum \", \" plam \"]"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "Can change delimiters" do
|
|
21
|
+
@parser.parse("FOO[ ploum , plam ]", delims: '[]').must_equal "FOO: [\"ploum\", \"plam\"]"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "Can have a one caracter delimiter" do
|
|
25
|
+
@parser.parse("FOO! ploum , plam !", delims: '!').must_equal "FOO: [\"ploum\", \"plam\"]"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "Can parse keywords inside keywords" do
|
|
29
|
+
@parser.parse("FOO(ploum, BAR( foo, bar ))").must_equal "FOO: [\"ploum\", \"BAR: [\\\"foo\\\"\", \"\\\"bar\\\"]\"]"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "Can add a keyword" do
|
|
33
|
+
@parser.keyword("PLOUM") { |a,b| a.to_i+b.to_i}
|
|
34
|
+
@parser.parse("Hello PLOUM(2,3)").must_equal 'Hello 5'
|
|
35
|
+
end
|
|
36
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: drain
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Damien Robert
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-
|
|
11
|
+
date: 2018-09-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -95,8 +95,12 @@ files:
|
|
|
95
95
|
- lib/dr/base/eruby.rb
|
|
96
96
|
- lib/dr/base/functional.rb
|
|
97
97
|
- lib/dr/base/graph.rb
|
|
98
|
+
- lib/dr/base/uri.rb
|
|
98
99
|
- lib/dr/base/utils.rb
|
|
100
|
+
- lib/dr/formatter/simple_formatter.rb
|
|
99
101
|
- lib/dr/parse.rb
|
|
102
|
+
- lib/dr/parse/date_parse.rb
|
|
103
|
+
- lib/dr/parse/simple_keywords.rb
|
|
100
104
|
- lib/dr/parse/simple_parser.rb
|
|
101
105
|
- lib/dr/parse/time_parse.rb
|
|
102
106
|
- lib/dr/ruby_ext.rb
|
|
@@ -110,9 +114,11 @@ files:
|
|
|
110
114
|
- test/helper.rb
|
|
111
115
|
- test/test_converter.rb
|
|
112
116
|
- test/test_core_ext.rb
|
|
117
|
+
- test/test_date_parse.rb
|
|
113
118
|
- test/test_drain.rb
|
|
114
119
|
- test/test_graph.rb
|
|
115
120
|
- test/test_meta.rb
|
|
121
|
+
- test/test_simple_keywords.rb
|
|
116
122
|
- test/test_simple_parser.rb
|
|
117
123
|
homepage: https://github.com/DamienRobert/drain#readme
|
|
118
124
|
licenses:
|
|
@@ -135,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
135
141
|
version: '0'
|
|
136
142
|
requirements: []
|
|
137
143
|
rubyforge_project:
|
|
138
|
-
rubygems_version: 2.7.
|
|
144
|
+
rubygems_version: 2.7.7
|
|
139
145
|
signing_key:
|
|
140
146
|
specification_version: 4
|
|
141
147
|
summary: Use a drain for a dryer ruby!
|