drain 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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!