eco-helpers 3.0.14 → 3.0.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -2
- data/eco-helpers.gemspec +15 -14
- data/lib/eco/api/common/people/default_parsers/date_parser.rb +6 -0
- data/lib/eco/api/common/session/mailer/aws_provider.rb +85 -0
- data/lib/eco/api/common/session/mailer/provider_base.rb +61 -0
- data/lib/eco/api/common/session/mailer/sendgrid_provider.rb +117 -0
- data/lib/eco/api/common/session/mailer.rb +42 -71
- data/lib/eco/api/session/batch/errors.rb +2 -2
- data/lib/eco/api/session/batch.rb +66 -28
- data/lib/eco/api/session/config/api.rb +96 -37
- data/lib/eco/api/session/config/apis/enviro_spaces.rb +106 -0
- data/lib/eco/api/session/config/apis/one_off.rb +94 -0
- data/lib/eco/api/session/config/apis/service_up.rb +37 -0
- data/lib/eco/api/session/config/apis/space_helpers.rb +41 -0
- data/lib/eco/api/session/config/apis.rb +81 -132
- data/lib/eco/api/session/config.rb +21 -3
- data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +1 -1
- data/lib/eco/api/usecases/graphql/helpers/base/error_handling.rb +19 -8
- data/lib/eco/api/usecases/graphql/helpers/base/graphql_env.rb +1 -0
- data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +3 -7
- data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +6 -2
- data/lib/eco/cli/config/options_set.rb +10 -7
- data/lib/eco/cli/scripting/args_helpers.rb +18 -9
- data/lib/eco/cli_default/options.rb +8 -0
- data/lib/eco/cli_default/people.rb +3 -3
- data/lib/eco/language/basic_logger.rb +4 -2
- data/lib/eco/version.rb +1 -1
- metadata +37 -16
@@ -3,182 +3,131 @@ module Eco
|
|
3
3
|
class Session
|
4
4
|
class Config
|
5
5
|
class Apis < BaseConfig
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
class UndefinedApi < ArgumentError; end
|
7
|
+
|
8
|
+
require_relative 'apis/space_helpers'
|
9
|
+
require_relative 'apis/enviro_spaces'
|
10
|
+
require_relative 'apis/one_off'
|
11
|
+
require_relative 'apis/service_up'
|
12
|
+
|
13
|
+
include EnviroSpaces
|
14
|
+
include OneOff
|
15
|
+
include ServiceUp
|
16
|
+
|
17
|
+
def add(
|
18
|
+
name,
|
19
|
+
key:,
|
20
|
+
host:,
|
21
|
+
space: nil,
|
22
|
+
version: :internal,
|
23
|
+
mode: :local,
|
24
|
+
user_key: nil,
|
25
|
+
external_key: nil,
|
26
|
+
email: nil,
|
27
|
+
pass: nil,
|
28
|
+
org_id: nil
|
29
|
+
)
|
30
|
+
# ensure that space_option isn't used
|
31
|
+
space = to_space(space)
|
32
|
+
|
33
|
+
msg = "WARNING: Overriding #{full_name(name, space: space)} API credentials"
|
34
|
+
puts msg if self.defined?(name, space: space)
|
35
|
+
|
36
|
+
apis(space)[name] = Session::Config::Api.new(
|
37
|
+
name,
|
38
|
+
space: space,
|
39
|
+
key: key,
|
40
|
+
host: host,
|
41
|
+
version: version,
|
42
|
+
mode: mode,
|
43
|
+
root: self,
|
44
|
+
user_key: user_key,
|
45
|
+
external_key: external_key,
|
46
|
+
email: email,
|
47
|
+
pass: pass,
|
48
|
+
org_id: org_id
|
49
|
+
)
|
17
50
|
|
18
|
-
|
19
|
-
apis.key?(name)
|
51
|
+
self
|
20
52
|
end
|
21
53
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
54
|
+
# the active environment
|
55
|
+
def active_root_name
|
56
|
+
active_name
|
26
57
|
end
|
27
58
|
|
28
|
-
def
|
29
|
-
|
30
|
-
email: nil, pass: nil, org_id: nil)
|
31
|
-
apis[name] = Session::Config::Api.new(
|
32
|
-
name,
|
33
|
-
key: key,
|
34
|
-
host: host,
|
35
|
-
version: version,
|
36
|
-
mode: mode,
|
37
|
-
root: self,
|
38
|
-
user_key: user_key,
|
39
|
-
external_key: external_key,
|
40
|
-
email: email,
|
41
|
-
pass: pass,
|
42
|
-
org_id: org_id
|
43
|
-
)
|
44
|
-
self
|
59
|
+
def active_name
|
60
|
+
self['active-name']
|
45
61
|
end
|
46
62
|
|
47
63
|
def active_api
|
48
|
-
self[
|
64
|
+
self['active-api']
|
49
65
|
end
|
50
66
|
|
51
67
|
def active_name=(name)
|
52
|
-
|
53
|
-
self["active-name"] = name
|
54
|
-
self["active-api"] = apis[name]
|
55
|
-
self
|
68
|
+
set_active_name(name)
|
56
69
|
end
|
57
70
|
|
58
|
-
def
|
59
|
-
|
60
|
-
end
|
71
|
+
def set_active_name(name, space: space_option)
|
72
|
+
space ||= space_option
|
61
73
|
|
62
|
-
|
63
|
-
|
64
|
-
active_name
|
65
|
-
end
|
74
|
+
msg = missing_api_message(name, space: space)
|
75
|
+
raise UndefinedApi, msg unless self.api?(name, space: space)
|
66
76
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
77
|
+
self['active-name'] = name
|
78
|
+
self['active-api'] = apis(space)[name]
|
79
|
+
set_options_space!(active_space)
|
80
|
+
|
81
|
+
self
|
72
82
|
end
|
73
83
|
|
74
84
|
def api(logger = ::Logger.new(IO::NULL), version: nil)
|
75
|
-
|
76
|
-
|
77
|
-
|
85
|
+
msg = "There's no 'active_api'. "
|
86
|
+
msg << "To set the target environment, please use either of:\n"
|
87
|
+
msg << " * apis.set_active_name('api_enviro_name', space: 'the-space')\n"
|
88
|
+
msg << " * apis.active_name='api_enviro_name'\n"
|
89
|
+
raise msg unless active_api
|
90
|
+
|
78
91
|
active_api.api(version: version, logger: logger)
|
79
92
|
end
|
80
93
|
|
81
94
|
def default_user_key=(key)
|
82
|
-
self[
|
95
|
+
self['user_key'] = key
|
83
96
|
end
|
84
97
|
|
85
98
|
def default_user_key
|
86
|
-
self[
|
99
|
+
self['user_key']
|
87
100
|
end
|
88
101
|
|
89
102
|
def default_email=(email)
|
90
|
-
self[
|
103
|
+
self['default_email'] = email
|
91
104
|
end
|
92
105
|
|
93
106
|
def default_email
|
94
|
-
self[
|
107
|
+
self['default_email'] || ENV['USER_EMAIL']
|
95
108
|
end
|
96
109
|
|
97
110
|
def default_pass=(pass)
|
98
|
-
self[
|
111
|
+
self['default_pass'] = pass
|
99
112
|
end
|
100
113
|
|
101
114
|
def default_pass
|
102
|
-
self[
|
115
|
+
self['default_pass'] || ENV['USER_PASS']
|
103
116
|
end
|
104
117
|
|
105
118
|
# Method to support CLI one-off API requests
|
106
119
|
def one_off
|
107
|
-
|
108
|
-
add(one_off_org, key: one_off_key, host: "#{one_off_enviro}.ecoportal.com")
|
109
|
-
return one_off_org
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
private
|
114
|
-
|
115
|
-
def one_off?
|
116
|
-
@is_one_off ||= SCR.get_arg("-api-key") || SCR.get_arg("-one-off")
|
117
|
-
end
|
120
|
+
return unless one_off?
|
118
121
|
|
119
|
-
|
120
|
-
return @one_off_key if instance_variable_defined?(:@one_off_key)
|
121
|
-
if one_off?
|
122
|
-
Dotenv.load("./.env_one_off")
|
123
|
-
SCR.get_arg("-api-key", with_param: true).yield_self do |key|
|
124
|
-
one_off_key_env(key)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def one_off_key_env(key)
|
130
|
-
if one_off?
|
131
|
-
if key
|
132
|
-
env_file_set_var("./.env_one_off", one_off_key_env_var, key)
|
133
|
-
key
|
134
|
-
else
|
135
|
-
Dotenv.load("./.env_one_off")
|
136
|
-
ENV[one_off_key_env_var].tap do |k|
|
137
|
-
raise "At least the first time, you should provide the -api-key" unless k
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def one_off_key_env_var
|
144
|
-
@one_off_key_env_var ||= "#{one_off_org}_KEY"
|
145
|
-
end
|
122
|
+
name = one_off_org
|
146
123
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
@one_off_org ||= "#{org.downcase.split(/[^a-z]+/).join("_")}_#{one_off_enviro.gsub(".", "_")}".to_sym
|
153
|
-
end
|
154
|
-
|
155
|
-
def one_off_enviro
|
156
|
-
return @one_off_enviro if instance_variable_defined?(:@one_off_enviro)
|
157
|
-
enviro = SCR.get_arg("-enviro") ? SCR.get_arg("-enviro", with_param: true) : "live"
|
158
|
-
@one_off_enviro ||= enviro.downcase
|
159
|
-
end
|
124
|
+
add(
|
125
|
+
name,
|
126
|
+
key: one_off_key,
|
127
|
+
host: "#{one_off_enviro}.ecoportal.com"
|
128
|
+
)
|
160
129
|
|
161
|
-
|
162
|
-
begin
|
163
|
-
pattern = /"#{var}=(?<value>[^ \r\n]+)"/
|
164
|
-
File.open(file, "w+") do |fd|
|
165
|
-
found = false
|
166
|
-
fd.each_line do |line|
|
167
|
-
if match = line.match(pattern)
|
168
|
-
found = true
|
169
|
-
# IO::SEEK_CUR => Seeks to _amount_ plus current position
|
170
|
-
fd.seek(-(line.length + 1), IO::SEEK_CUR)
|
171
|
-
fd.write line.gsub(match[:value], value)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
fd << "#{var}=#{value}" unless found
|
176
|
-
end
|
177
|
-
rescue StandardError => e
|
178
|
-
puts "#{e}"
|
179
|
-
return false
|
180
|
-
end
|
181
|
-
return true
|
130
|
+
name
|
182
131
|
end
|
183
132
|
end
|
184
133
|
end
|
@@ -97,18 +97,31 @@ module Eco
|
|
97
97
|
apis.apis?
|
98
98
|
end
|
99
99
|
|
100
|
+
ApiDefChain = Struct.new(:root, :name, :params) do
|
101
|
+
def add_space(space, **kargs, &block)
|
102
|
+
kargs = (params || {}).merge(kargs).merge(space: space)
|
103
|
+
root.add_api(name, **kargs, &block)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
100
107
|
# @param (see Eco::API::Session::Config::Apis#add)
|
101
108
|
# @return [Eco::API::Session::Config] this configuration
|
102
109
|
def add_api(name, **kargs)
|
103
110
|
apis.add(name, **kargs)
|
104
|
-
|
111
|
+
|
112
|
+
if block_given?
|
113
|
+
params = kargs.merge({space: :sub_other})
|
114
|
+
yield(ApiDefChain.new(self, name, params))
|
115
|
+
end
|
116
|
+
|
117
|
+
ApiDefChain.new(self, name, {space: :other})
|
105
118
|
end
|
106
119
|
|
107
120
|
# Set the active api by `name`
|
108
121
|
# @see Eco::API::Session::Config::Apis#active_api=
|
109
122
|
# @return [Eco::API::Session::Config] this configuration
|
110
|
-
def active_api(name)
|
111
|
-
apis.
|
123
|
+
def active_api(name, space: nil)
|
124
|
+
apis.set_active_name(name, space: space)
|
112
125
|
self
|
113
126
|
end
|
114
127
|
|
@@ -117,6 +130,11 @@ module Eco
|
|
117
130
|
apis.active_root_name
|
118
131
|
end
|
119
132
|
|
133
|
+
# @see Eco::API::Session::Config::Apis#active_space
|
134
|
+
def active_enviro_space
|
135
|
+
apis.active_space
|
136
|
+
end
|
137
|
+
|
120
138
|
# @see Eco::API::Session::Config::Apis#api
|
121
139
|
# @return [Eco::API::Session::Config::Api] the currently active api
|
122
140
|
def api(logger = ::Logger.new(IO::NULL), version: nil)
|
@@ -3,7 +3,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Base
|
|
3
3
|
module ErrorHandling
|
4
4
|
include Eco::Language::AuxiliarLogger
|
5
5
|
|
6
|
-
attr_reader :exception
|
6
|
+
attr_reader :exception, :exiting
|
7
7
|
|
8
8
|
private
|
9
9
|
|
@@ -19,6 +19,14 @@ module Eco::API::UseCases::GraphQL::Helpers::Base
|
|
19
19
|
exception && !interrupted?
|
20
20
|
end
|
21
21
|
|
22
|
+
def exiting?
|
23
|
+
@exiting
|
24
|
+
end
|
25
|
+
|
26
|
+
def exception?
|
27
|
+
error_raised? || exiting?
|
28
|
+
end
|
29
|
+
|
22
30
|
def rescued
|
23
31
|
yield
|
24
32
|
rescue StandardError => err
|
@@ -27,24 +35,27 @@ module Eco::API::UseCases::GraphQL::Helpers::Base
|
|
27
35
|
unless exception_already_captured?(err)
|
28
36
|
log(:error) { err.patch_full_message }
|
29
37
|
end
|
38
|
+
rescue SystemExit
|
39
|
+
@exiting = true
|
40
|
+
raise
|
30
41
|
end
|
31
42
|
|
32
43
|
def with_error_handling
|
33
44
|
@exception = nil
|
34
45
|
yield
|
35
|
-
rescue
|
36
|
-
@exception =
|
37
|
-
|
38
|
-
rescue
|
46
|
+
rescue StandardError, SignalException => err
|
47
|
+
@exception = err
|
48
|
+
raise
|
49
|
+
rescue SystemExit
|
50
|
+
@exiting = true
|
51
|
+
raise
|
52
|
+
rescue *interrupt_errors
|
39
53
|
@exception = int
|
40
54
|
raise
|
41
55
|
rescue SystemStackError
|
42
56
|
puts $! # rubocop:disable Style/SpecialGlobalVars
|
43
57
|
puts caller[0..100]
|
44
58
|
raise
|
45
|
-
rescue StandardError, SignalException => err
|
46
|
-
@exception = err
|
47
|
-
raise
|
48
59
|
end
|
49
60
|
|
50
61
|
def interrupt_errors
|
@@ -8,6 +8,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Base
|
|
8
8
|
def graphql
|
9
9
|
msg = "The credentials or basic graphql config are missing for the active environment"
|
10
10
|
raise msg unless session.api?(version: :graphql)
|
11
|
+
|
11
12
|
@graphql ||= session.api(version: :graphql)
|
12
13
|
end
|
13
14
|
end
|
@@ -49,15 +49,11 @@ class Eco::API::UseCases::GraphQL::Samples::Location
|
|
49
49
|
end
|
50
50
|
|
51
51
|
break if error
|
52
|
+
rescue StandardError => err
|
53
|
+
log(:error) { err.patch_full_message }
|
54
|
+
raise
|
52
55
|
end
|
53
56
|
end
|
54
|
-
rescue SystemStackError
|
55
|
-
puts $! # rubocop:disable Style/SpecialGlobalVars
|
56
|
-
puts caller[0..100]
|
57
|
-
raise
|
58
|
-
rescue StandardError => err
|
59
|
-
log(:error) { err.patch_full_message }
|
60
|
-
raise
|
61
57
|
ensure
|
62
58
|
rescued { self.tags_remap_csv_file = generate_tags_remap_csv }
|
63
59
|
rescued { close_handling_tags_remap_csv }
|
@@ -21,7 +21,7 @@ class Eco::API::UseCases::GraphQL::Samples::Location
|
|
21
21
|
super
|
22
22
|
end
|
23
23
|
ensure
|
24
|
-
rescued { re_archive } unless
|
24
|
+
rescued { re_archive } unless exception?
|
25
25
|
rescued { email_digest('TagTree Update') }
|
26
26
|
end
|
27
27
|
|
@@ -30,7 +30,7 @@ class Eco::API::UseCases::GraphQL::Samples::Location
|
|
30
30
|
# @note this is an additional necessary step
|
31
31
|
def re_archive
|
32
32
|
return if simulate?
|
33
|
-
return if
|
33
|
+
return if exception?
|
34
34
|
|
35
35
|
stage = :rearchive
|
36
36
|
|
@@ -90,8 +90,12 @@ class Eco::API::UseCases::GraphQL::Samples::Location
|
|
90
90
|
|
91
91
|
digest_msgs = logger.cache.logs(level: %i[info error warn])
|
92
92
|
str_exception = exception ? " - Exception!" : ''
|
93
|
+
str_exception = " - Aborted!" if exiting?
|
94
|
+
|
93
95
|
subject = "#{config.active_enviro} - #{title}#{str_exception}"
|
94
96
|
|
97
|
+
digest_msgs << "\n#{exception.patch_full_message(trace_count: 3)}" if exception
|
98
|
+
|
95
99
|
session.mail(subject: subject, body: digest_msgs.join).tap do
|
96
100
|
options.deep_merge!({worfklow: {no_email: true}})
|
97
101
|
end
|
@@ -21,9 +21,11 @@ module Eco
|
|
21
21
|
|
22
22
|
["The following are the available options#{refinement}:"].then do |lines|
|
23
23
|
max_len = keys_max_len(options_args(spaces)) + indent
|
24
|
+
|
24
25
|
spaces.each do |namespace|
|
25
26
|
is_general = (namespace == :general)
|
26
27
|
str_indent = is_general ? "" : " " * indent
|
28
|
+
|
27
29
|
lines << help_line(namespace, "", max_len) unless is_general
|
28
30
|
|
29
31
|
options_set(namespace).select do |_arg, option| # rubocop:disable Style/HashEachMethods
|
@@ -32,6 +34,7 @@ module Eco
|
|
32
34
|
lines << help_line("#{str_indent}#{option.name}", option.description, max_len)
|
33
35
|
end
|
34
36
|
end
|
37
|
+
|
35
38
|
lines
|
36
39
|
end.join("\n")
|
37
40
|
end
|
@@ -63,13 +66,13 @@ module Eco
|
|
63
66
|
raise "Missing block to define the options builder" unless block_given?
|
64
67
|
|
65
68
|
opts = [option].flatten.compact
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
return self if opts.empty?
|
70
|
+
|
71
|
+
callback = block
|
72
|
+
opts.each do |opt|
|
73
|
+
msg = "Overriding CLI option '#{option}' in '#{namespace}' CLI case / namespace"
|
74
|
+
puts msg if option_exists?(opt, namespace)
|
75
|
+
options_set(namespace)[opt] = OptConfig.new(opt, namespace, desc, callback)
|
73
76
|
end
|
74
77
|
|
75
78
|
self
|
@@ -78,15 +78,24 @@ module Eco
|
|
78
78
|
|
79
79
|
# @return [String, Boolean] the argument value if `with_param` or a `Boolean` if not.
|
80
80
|
def get_arg(key, with_param: false, valid: true)
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
81
|
+
case key
|
82
|
+
when Array
|
83
|
+
key.reduce(nil) do |value, k|
|
84
|
+
next value unless value.nil?
|
85
|
+
|
86
|
+
get_arg(k, with_param: with_param, valid: valid)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
# track what a known option looks like
|
90
|
+
known_argument(key, with_param: with_param)
|
91
|
+
return nil unless (index = get_arg_index(key))
|
92
|
+
return true unless with_param
|
93
|
+
|
94
|
+
value = argv[index + 1]
|
95
|
+
#puts "modifier argument: #{value}"
|
96
|
+
value = nil if valid && is_modifier?(value)
|
97
|
+
value
|
98
|
+
end
|
90
99
|
end
|
91
100
|
|
92
101
|
# @return [String] the filename.
|
@@ -153,6 +153,14 @@ ASSETS.cli.config do |cnf| # rubocop:disable Metrics/BlockLength
|
|
153
153
|
session.config.dry_run!
|
154
154
|
end
|
155
155
|
|
156
|
+
desc = "Specifies the target API key space (i.e. uat, dev, etc.). "
|
157
|
+
desc << "Use with CAUTION !!!"
|
158
|
+
options_set.add('-space', desc) do |options, session|
|
159
|
+
next unless space = SCR.get_arg('-space', with_param: true)
|
160
|
+
|
161
|
+
options.deep_merge!(api: {space: space.to_sym})
|
162
|
+
end
|
163
|
+
|
156
164
|
desc = "It specifies the type of batch to be used (default: ':batch')"
|
157
165
|
options_set.add("-batch-mode", desc) do |options, session|
|
158
166
|
mode_in = SCR.get_arg("-batch-mode", with_param: true)
|
@@ -23,7 +23,8 @@ ASSETS.cli.config do |cnf|
|
|
23
23
|
|
24
24
|
msg = "(Optimization) "
|
25
25
|
msg << "Switching from partial to full people download. "
|
26
|
-
msg << "Input (#{input.count}) surpases MAX_GET_PARTIAL
|
26
|
+
msg << "Input (#{input.count}) surpases MAX_GET_PARTIAL "
|
27
|
+
msg << "(#{MAX_GET_PARTIAL}) entries."
|
27
28
|
session.log(:info) { msg }
|
28
29
|
|
29
30
|
options.deep_merge!(people: {
|
@@ -46,8 +47,6 @@ ASSETS.cli.config do |cnf|
|
|
46
47
|
elsif get_by_file
|
47
48
|
# -people-from-backup
|
48
49
|
session.micro.people_load(get[:file], modifier: :file)
|
49
|
-
#people = JSON.parse(File.read(get[:file]))
|
50
|
-
#Eco::API::Organization::People.new(people)
|
51
50
|
else
|
52
51
|
options.deep_merge!(people: {
|
53
52
|
get: {
|
@@ -67,6 +66,7 @@ ASSETS.cli.config do |cnf|
|
|
67
66
|
})
|
68
67
|
people = session.micro.people_cache
|
69
68
|
end
|
69
|
+
|
70
70
|
people
|
71
71
|
end
|
72
72
|
end
|
@@ -24,6 +24,7 @@ module Eco
|
|
24
24
|
def initialize(level: ::Logger::INFO, timestamp: false)
|
25
25
|
@level = level
|
26
26
|
self.timestamp = timestamp
|
27
|
+
|
27
28
|
loggers[:console] = ::Logger.new($stdout).tap do |logger|
|
28
29
|
logger.formatter = format_proc(console: true)
|
29
30
|
logger.level = level
|
@@ -51,13 +52,14 @@ module Eco
|
|
51
52
|
end
|
52
53
|
|
53
54
|
def console_timestamp(datetime)
|
54
|
-
return
|
55
|
+
return unless timestamp?
|
55
56
|
|
56
57
|
timestamp(datetime)
|
57
58
|
end
|
58
59
|
|
59
60
|
def timestamp(datetime)
|
60
|
-
return
|
61
|
+
return unless datetime
|
62
|
+
|
61
63
|
str_date = datetime.strftime(self.class::TIMESTAMP_PATTERN)
|
62
64
|
"#{str_date} > "
|
63
65
|
end
|
data/lib/eco/version.rb
CHANGED