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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a6165b74687b54296ee9b273eac4465746de73b9774eb5466fd550031897cba
4
- data.tar.gz: 915728a74d0050b45fec8167e51e2b08c3657defb5fc3187b25b4aca2ff18fb4
3
+ metadata.gz: 5c577f08df5d0dd378a335824250d97008d3eb24896d101ecc1ed51a2281a1f0
4
+ data.tar.gz: f35b4fd99afb4870ddb44991b776ef8e5621739d7d83d7ba4d9ae851b2713df1
5
5
  SHA512:
6
- metadata.gz: f1c013bdb8158298fec2a204063d142a328840e9f3bea214036932ddc59f696aca94549190df72b95b3110dbea295f7a2e240bd69dbb3ed7264911a201517297
7
- data.tar.gz: 4cdffbe87293046d6752bd64a0e7e17224b16786d39cb5ff4f4537a5ed3da6722060acf44475774c4bc788f4b09abbb66cf85833e8027cc990fb79fc862f001c
6
+ metadata.gz: a889f20d3499a98ddc6da50f8cc14201881cf695271b250730ebc26024e657f2af2f7764f5e078979029559dd1dea3745a60a9df9c40ae8d5fc64f7903948a1e
7
+ data.tar.gz: 1a95a1661230282f9339258e1b6a241bd4fe2e503fbb9f43ea20c220e220a093903356cd8e78fd36e62899189b919a5a4ee50a3261951fdfde1bceebb91cbae1
@@ -1,10 +1,9 @@
1
1
  ---
2
2
  language: ruby
3
3
  rvm:
4
- - 2.4.0
5
- - 2.3.3
6
- - 2.2.6
7
- - 2.1.10
8
- - ruby-head
9
- - ruby-head-clang
4
+ - 2.5.1
5
+ - 2.4.4
6
+ - 2.3.7
7
+ #- ruby-head
8
+ #- ruby-head-clang
10
9
  script: rake test
@@ -1,4 +1,4 @@
1
- Copyright © 2015–2017 Damien Robert
1
+ Copyright © 2015–2018 Damien Robert
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -23,7 +23,7 @@ The Api is far from stable yet, so use at your own risk!
23
23
 
24
24
  ## Copyright
25
25
 
26
- Copyright © 2015–2017 Damien Robert
26
+ Copyright © 2015–2018 Damien Robert
27
27
 
28
28
  MIT License. See [LICENSE.txt](./LICENSE.txt) for details.
29
29
 
@@ -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
- when Fixnum
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
@@ -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
- def to_graph(indent_level: 0, show_attr: true)
87
- sout = ""
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
- sout += margin + "#{to_s(show_attr: show_attr)}\n"
91
+ out << margin + "#{to_s(show_attr: show_attr)}"
92
92
  @children.each do |child|
93
- sout += child.to_graph(indent_level: indent_level+STEP, show_attr: show_attr)
93
+ child.to_graph(indent_level: indent_level+STEP, show_attr: show_attr, out: out)
94
94
  end
95
- return sout
95
+ return out
96
96
  end
97
- def to_dot
98
- sout=["\""+name+"\""]
97
+ def to_dot(out: [])
98
+ out << "\""+name+"\""
99
99
  @children.each do |child|
100
- sout.push "\"#{@name}\" -> \"#{child.name}\""
101
- sout += child.to_dot
100
+ out << "\"#{@name}\" -> \"#{child.name}\""
101
+ child.to_dot(out: out)
102
102
  end
103
- return sout
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, **unused)
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| sout+=node.to_graph(show_attr: show_attr) end
234
- when :list; n.each do |i| sout+="- #{i}\n" end
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
- sout+="digraph gems {\n"
237
- sout+=n.map {|node| node.to_dot}.inject(:+).uniq!.join("\n")
238
- sout+="}\n"
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 sout
243
+ return out
241
244
  end
242
245
 
243
246
  def to_nodes(*nodes)
@@ -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
@@ -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},
@@ -30,7 +30,7 @@ module DR
30
30
  t=-t if s[0]=='-'
31
31
  end
32
32
  case t
33
- when Time
33
+ when Time
34
34
  else
35
35
  t=Time.now+t
36
36
  end
@@ -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
@@ -1,4 +1,4 @@
1
1
  module DR
2
2
  # drain version
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
@@ -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.2.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-02-01 00:00:00.000000000 Z
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.3
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!