eco-helpers 3.0.14 → 3.0.16

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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -2
  3. data/eco-helpers.gemspec +15 -14
  4. data/lib/eco/api/common/people/default_parsers/date_parser.rb +6 -0
  5. data/lib/eco/api/common/session/mailer/aws_provider.rb +85 -0
  6. data/lib/eco/api/common/session/mailer/provider_base.rb +61 -0
  7. data/lib/eco/api/common/session/mailer/sendgrid_provider.rb +117 -0
  8. data/lib/eco/api/common/session/mailer.rb +42 -71
  9. data/lib/eco/api/microcases/people_refresh.rb +1 -1
  10. data/lib/eco/api/session/batch/errors.rb +2 -2
  11. data/lib/eco/api/session/batch.rb +83 -37
  12. data/lib/eco/api/session/config/api.rb +96 -37
  13. data/lib/eco/api/session/config/apis/enviro_spaces.rb +106 -0
  14. data/lib/eco/api/session/config/apis/one_off.rb +94 -0
  15. data/lib/eco/api/session/config/apis/service_up.rb +37 -0
  16. data/lib/eco/api/session/config/apis/space_helpers.rb +41 -0
  17. data/lib/eco/api/session/config/apis.rb +81 -132
  18. data/lib/eco/api/session/config.rb +21 -3
  19. data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +1 -1
  20. data/lib/eco/api/usecases/graphql/helpers/base/error_handling.rb +19 -8
  21. data/lib/eco/api/usecases/graphql/helpers/base/graphql_env.rb +1 -0
  22. data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +3 -7
  23. data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +6 -2
  24. data/lib/eco/cli/config/options_set.rb +10 -7
  25. data/lib/eco/cli/scripting/args_helpers.rb +18 -9
  26. data/lib/eco/cli_default/options.rb +8 -0
  27. data/lib/eco/cli_default/people.rb +3 -3
  28. data/lib/eco/language/basic_logger.rb +4 -2
  29. data/lib/eco/version.rb +1 -1
  30. metadata +37 -16
@@ -2,8 +2,9 @@ module Eco
2
2
  module API
3
3
  class Session
4
4
  class Batch < Common::Session::BaseSession
5
- DEFAULT_BATCH_BLOCK = 50
6
- VALID_METHODS = %i[get create update upsert delete].freeze
5
+ DEFAULT_BATCH_SIZE = 50
6
+ DEFAULT_JOB_SIZE = 100
7
+ VALID_METHODS = %i[get create update upsert delete].freeze
7
8
 
8
9
  class << self
9
10
  # @return [Boolean] `true` if the method is supported, `false` otherwise.
@@ -12,13 +13,19 @@ module Eco
12
13
  end
13
14
  end
14
15
 
16
+ def batch_size(opts = options)
17
+ return self.class::DEFAULT_JOB_SIZE if job_mode?(opts)
18
+
19
+ self.class::DEFAULT_BATCH_SIZE
20
+ end
21
+
15
22
  # @return [Symbol] the batch mode to run
16
- def batch_mode(opts = self.options)
23
+ def batch_mode(opts = options)
17
24
  opts.dig(:workflow, :batch, :mode) || :batch
18
25
  end
19
26
 
20
27
  # @return [Boolean] are we running in `:job` mode?
21
- def job_mode?(opts = self.options)
28
+ def job_mode?(opts = options)
22
29
  batch_mode(opts) == :job
23
30
  end
24
31
 
@@ -32,10 +39,16 @@ module Eco
32
39
  # @option params [String] :per_page the number of people included per each batch api request.
33
40
  # @option params [String] :q some text to search. Omit this parameter to target all the people.
34
41
  # @return [Array<People>] all the people based on `params`
35
- def get_people(people = nil, params: {}, silent: false)
36
- return launch(people, method: :get, params: params, silent: silent).people if people.is_a?(Enumerable)
37
-
38
- get(params: params, silent: silent)
42
+ def get_people(people = nil, params: {}, silent: false, options: self.options)
43
+ return get(params: params, silent: silent, options: options) unless people.is_a?(Enumerable)
44
+
45
+ launch(
46
+ people,
47
+ method: :get,
48
+ params: params,
49
+ silent: silent,
50
+ options: options
51
+ ).people
39
52
  end
40
53
 
41
54
  # launches a batch of `method` type using `people` and the specified `params`
@@ -47,14 +60,26 @@ module Eco
47
60
  # @param params [Hash] api request options.
48
61
  # @option params [String] :per_page the number of people included per each batch api request.
49
62
  # @return [Batch::Status] the `status` of this batch launch.
50
- def launch(people, method:, params: {}, silent: false)
51
- batch_from(people, method: method, params: params, silent: silent)
63
+ def launch(people, method:, params: {}, silent: false, options: self.options)
64
+ batch_from(
65
+ people,
66
+ method: method,
67
+ params: params,
68
+ silent: silent,
69
+ options: options
70
+ )
52
71
  end
53
72
 
54
- def search(data, silent: false, params: {}) # rubocop:disable Metrics/AbcSize
55
- params = {per_page: DEFAULT_BATCH_BLOCK}.merge(params)
73
+ def search(data, silent: false, params: {}, options: self.options) # rubocop:disable Metrics/AbcSize
74
+ params = {per_page: batch_size(options)}.merge(params)
56
75
 
57
- launch(data, method: :get, params: params, silent: silent).tap do |status|
76
+ launch(
77
+ data,
78
+ method: :get,
79
+ params: params,
80
+ silent: silent,
81
+ options: options
82
+ ).tap do |status|
58
83
  status.mode = :search
59
84
 
60
85
  entries = status.queue
@@ -78,7 +103,11 @@ module Eco
78
103
  people_matching = []
79
104
  email = email.to_s.strip.downcase
80
105
  unless email.empty?
81
- people_matching = get(params: params.merge(q: email), silent: silent).select do |person|
106
+ people_matching = get(
107
+ params: params.merge(q: email),
108
+ silent: silent,
109
+ options: options
110
+ ).select do |person|
82
111
  person.email == email
83
112
  end
84
113
  end
@@ -95,15 +124,21 @@ module Eco
95
124
 
96
125
  private
97
126
 
98
- def get(params: {}, silent: false)
127
+ def get(params: {}, silent: false, options: self.options)
99
128
  msg = "cannot batch get without api connnection, please provide a valid api connection!"
100
129
  fatal msg unless (people_api = api&.people)
101
130
 
102
- params = {per_page: DEFAULT_BATCH_BLOCK}.merge(params)
131
+ params = {per_page: batch_size(options)}.merge(params)
103
132
  people_api.get_all(params: params, silent: silent)
104
133
  end
105
134
 
106
- def batch_from(data, method:, params: {}, silent: false)
135
+ def batch_from(
136
+ data,
137
+ method:,
138
+ params: {},
139
+ silent: false,
140
+ options: self.options
141
+ )
107
142
  fatal "Invalid batch method: #{method}." unless self.class.valid_method?(method)
108
143
  return nil if !data || !data.is_a?(Enumerable)
109
144
 
@@ -111,15 +146,16 @@ module Eco
111
146
  fatal msg unless (people_api = api&.people)
112
147
 
113
148
  # param q does not make sense here, even for GET method
114
- params = {per_page: DEFAULT_BATCH_BLOCK}.merge(params)
115
- per_page = params[:per_page] || DEFAULT_BATCH_BLOCK
149
+ params = {per_page: batch_size(options)}.merge(params)
150
+ per_page = params[:per_page] || batch_size(options)
116
151
 
117
152
  launch_batch(
118
153
  data,
119
154
  method: method,
120
155
  per_page: per_page,
121
156
  people_api: people_api,
122
- silent: silent
157
+ silent: silent,
158
+ options: options
123
159
  )
124
160
  end
125
161
 
@@ -128,15 +164,15 @@ module Eco
128
164
  ASSETS.cli.options
129
165
  end
130
166
 
131
- def launch_batch( # rubocop:disable Metrics/AbcSize
167
+ def launch_batch( # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
132
168
  data,
133
169
  method:,
134
170
  status: nil,
135
171
  job_mode: true, # rubocop:disable Lint/UnusedMethodArgument
136
- per_page: DEFAULT_BATCH_BLOCK,
172
+ options: self.options,
173
+ per_page: batch_size(options),
137
174
  people_api: api&.people,
138
- silent: false,
139
- options: self.options
175
+ silent: false
140
176
  )
141
177
  iteration = 1
142
178
  done = 0
@@ -149,17 +185,18 @@ module Eco
149
185
  )
150
186
 
151
187
  status.tap do
152
- start_time = Time.now
153
- start_slice = Time.now
154
-
155
188
  pending_for_server_error = data.to_a[0..]
189
+
190
+ start_time = Time.now
191
+
156
192
  data.each_slice(per_page) do |slice|
157
- msg = "starting batch '#{method}' iteration #{iteration}/#{iterations},"
158
- msg << " with #{slice.length} entries of #{data.length} -- #{done} done"
159
- msg << " (last: #{str_stats(start_slice, slice.length)}; total: #{str_stats(start_time, done)})"
193
+ msg = "starting batch '#{method}' iteration #{iteration}/#{iterations}, "
194
+ msg << "with #{slice.length} entries of #{data.length} -- #{done} done"
195
+ msg << (" " * 20)
160
196
  log(:info) { msg } unless silent
161
197
 
162
198
  start_slice = Time.now
199
+
163
200
  offer_retry_on(Ecoportal::API::Errors::TimeOut) do
164
201
  people_api.batch(job_mode: job_mode?(options)) do |batch|
165
202
  slice.each do |person|
@@ -175,8 +212,15 @@ module Eco
175
212
  end # end batch
176
213
  end
177
214
 
215
+ done += slice.length
216
+
217
+ msg = " ... iteration #{iteration}/#{iterations} done "
218
+ msg << "in #{str_stats(start_slice, slice.length)} "
219
+ msg << "(average: #{str_stats(start_time, done)})"
220
+ msg << (" " * 20)
221
+ log(:info) { msg } unless silent
222
+
178
223
  iteration += 1
179
- done += slice.length
180
224
  end # next slice
181
225
 
182
226
  # temporary working around (due to back-end problems with batch/jobs)
@@ -191,7 +235,8 @@ module Eco
191
235
  job_mode: false,
192
236
  per_page: per_page,
193
237
  people_api: people_api,
194
- silent: silent
238
+ silent: silent,
239
+ options: options
195
240
  )
196
241
  end
197
242
  end
@@ -206,11 +251,12 @@ module Eco
206
251
  end
207
252
 
208
253
  def offer_retry_on(error_type, retries_left = 3, &block)
209
- block.call
210
- rescue error_type
211
- raise unless retries_left.positive?
254
+ yield
255
+ rescue error_type => err
256
+ raise err.class, err.message, cause: nil unless retries_left.positive?
212
257
 
213
- explanation = "Batch TimeOut. You have #{retries_left} retries left."
258
+ explanation = "#{err}\n"
259
+ explanation << "You have #{retries_left} retries left."
214
260
  question = " Do you want to retry (y/N)?"
215
261
 
216
262
  prompt_user(question, default: "Y", explanation: explanation, timeout: 10) do |response|
@@ -223,7 +269,7 @@ module Eco
223
269
 
224
270
  def str_stats(start, count)
225
271
  now = Time.now
226
- secs = (now - start).round(3)
272
+ secs = (now - start).round(2)
227
273
  if secs > 0.0
228
274
  per_sec = (count.to_f / secs).round(2)
229
275
  "#{secs}s -> #{per_sec} people/s"
@@ -4,6 +4,27 @@ module Eco
4
4
  class Config
5
5
  class Api < Hash
6
6
  class << self
7
+ def to_space(value)
8
+ value = value.to_s.strip.gsub(/[- ]/, '_').downcase
9
+ return :default if value.empty?
10
+
11
+ value.to_sym
12
+ end
13
+
14
+ def description(api)
15
+ msg = "Expecting #{self}. Given: #{api.class}"
16
+ raise ArgumentError, msg unless api.is_a?(self)
17
+
18
+ full_name(api.name, space: api.space)
19
+ end
20
+
21
+ def full_name(name, space: :default)
22
+ space = to_space(space)
23
+
24
+ str_space = space == :default ? '' : " (space: :#{space})"
25
+ "'#{name}'#{str_space}"
26
+ end
27
+
7
28
  def to_version(str)
8
29
  case str.to_sym
9
30
  when :external, :v1
@@ -35,22 +56,34 @@ module Eco
35
56
  end
36
57
  end
37
58
 
38
- def initialize(name, root:, key:, host:, version:,
39
- mode: :local, user_key: nil, external_key: nil,
40
- email: nil, pass: nil, org_id: nil)
59
+ def initialize(
60
+ name,
61
+ root:,
62
+ key:,
63
+ host:,
64
+ version:,
65
+ space: :default,
66
+ mode: :local,
67
+ user_key: nil,
68
+ external_key: nil,
69
+ email: nil,
70
+ pass: nil,
71
+ org_id: nil
72
+ )
41
73
  super(nil)
42
- @root = root
43
- @apis = {}
44
- self["name"] = name
45
- self["key"] = key
46
- self["host"] = host
47
- self["version"] = version
48
- self["mode"] = mode
49
- self["user_key"] = user_key
50
- self["external_key"] = external_key
51
- self["email"] = email || ENV['USER_EMAIL']
52
- self["pass"] = pass || ENV['USER_PASS']
53
- self["org_id"] = org_id
74
+ @root = root
75
+ @apis = {}
76
+ self['name'] = name
77
+ self['space'] = to_space(space)
78
+ self['key'] = key
79
+ self['host'] = host
80
+ self['version'] = version
81
+ self['mode'] = mode
82
+ self['user_key'] = user_key
83
+ self['external_key'] = external_key
84
+ self['email'] = email || ENV['USER_EMAIL']
85
+ self['pass'] = pass || ENV['USER_PASS']
86
+ self['org_id'] = org_id
54
87
  end
55
88
 
56
89
  # @return [Eco::API::Session::Config] the `root` config
@@ -67,12 +100,13 @@ module Eco
67
100
  switch_logger = (logger != @logger)
68
101
  @logger = logger if logger
69
102
 
70
- if (current = get(version)) && !switch_logger
71
- return current
72
- end
103
+ current = get(version)
104
+ return current if current && !switch_logger
73
105
 
74
106
  unless api_params?(version)
75
- raise "The api configuration for '#{name}' is missing data for the api version '#{self.version(version)}'"
107
+ msg = "The api configuration for #{description} "
108
+ msg << "is missing data for the api version '#{self.version(version)}'"
109
+ raise ArgumentError, msg
76
110
  end
77
111
 
78
112
  new_api(version).tap do |pi|
@@ -89,7 +123,16 @@ module Eco
89
123
  end
90
124
 
91
125
  def name
92
- self["name"]
126
+ self['name']
127
+ end
128
+
129
+ def space
130
+ self['space'] ||= :default
131
+ to_space(self['space'])
132
+ end
133
+
134
+ def description
135
+ self.class.description(self)
93
136
  end
94
137
 
95
138
  def one_off?
@@ -97,45 +140,45 @@ module Eco
97
140
  end
98
141
 
99
142
  def key
100
- self["key"]
143
+ self['key']
101
144
  end
102
145
 
103
146
  def user_key
104
- self["user_key"] || @root.default_user_key
147
+ self['user_key'] || @root.default_user_key
105
148
  end
106
149
 
107
150
  def external_key
108
- self["external_key"] || (%i[v1 v2].include?(version) && key)
151
+ self['external_key'] || (%i[v1 v2].include?(version) && key)
109
152
  end
110
153
 
111
154
  def internal_key
112
- (version == :v0) && self["key"]
155
+ (version == :v0) && self['key']
113
156
  end
114
157
 
115
158
  def org_id
116
- self["org_id"]
159
+ self['org_id']
117
160
  end
118
161
 
119
162
  def email
120
- self["email"] || @root.default_email
163
+ self['email'] || @root.default_email
121
164
  end
122
165
 
123
166
  def pass
124
- self["pass"] || @root.default_pass
167
+ self['pass'] || @root.default_pass
125
168
  end
126
169
 
127
170
  def host
128
- self["host"]
171
+ self['host']
129
172
  end
130
173
 
131
174
  # @param mode [Symbol] to define if running on `:remote` or `:local`
132
175
  def mode=(mode)
133
- self["mode"] = mode == :remote ? :remote : :local
176
+ self['mode'] = mode == :remote ? :remote : :local
134
177
  end
135
178
 
136
179
  # @return [Symbol] if running on `:remote` or `:local`
137
180
  def mode
138
- self["mode"]
181
+ self['mode']
139
182
  end
140
183
 
141
184
  def local?
@@ -152,7 +195,7 @@ module Eco
152
195
  end
153
196
 
154
197
  def version(value = nil)
155
- self.class.to_version(value || self["version"])
198
+ self.class.to_version(value || self['version'])
156
199
  end
157
200
 
158
201
  # if no low level connection messages: use `IO::NULL`
@@ -163,9 +206,14 @@ module Eco
163
206
 
164
207
  private
165
208
 
209
+ def to_space(...)
210
+ self.class.to_space(...)
211
+ end
212
+
166
213
  # Generates a **new** `API` object of version `version`.
167
214
  def new_api(version) # rubocop:disable Metrics/AbcSize
168
215
  klass = self.class.api_class(version)
216
+
169
217
  case self.version(version)
170
218
  when :v0
171
219
  klass.new(internal_key, host: host, logger: logger)
@@ -174,14 +222,25 @@ module Eco
174
222
  when :v2
175
223
  klass.new(user_key: user_key, org_key: external_key, host: host, logger: logger)
176
224
  when :graphql
177
- kargs = {host: host, org_id: org_id, email: email, pass: pass}
225
+ kargs = {
226
+ host: host,
227
+ org_id: org_id,
228
+ email: email,
229
+ pass: pass
230
+ }
231
+
178
232
  kargs.merge!({no_schema: true}) if ENV['GRAPHQL_FETCH_SCHEMA'] == "false"
179
- klass.new(host: host, org_id: org_id, email: email, pass: pass)
233
+ klass.new(**kargs)
180
234
  end.tap do |api|
181
- unless !api || log_connection?
182
- @logger.info("Created api#{self.version(version)} connection on '#{name}' enviro, pointing to '#{host}' in '#{mode}' mode")
183
- api.logger.level = ::Logger::UNKNOWN if api.respond_to?(:logger)
184
- end
235
+ next unless api
236
+ next if log_connection? # prevent over-logging
237
+
238
+ msg = "Created api#{self.version(version)} connection "
239
+ msg << "on enviro #{description}, "
240
+ msg << "pointing to '#{host}' in '#{mode}' mode"
241
+
242
+ @logger.info(msg)
243
+ api.logger.level = ::Logger::UNKNOWN if api.respond_to?(:logger)
185
244
  end
186
245
  end
187
246
 
@@ -0,0 +1,106 @@
1
+ module Eco
2
+ module API
3
+ class Session
4
+ class Config
5
+ class Apis
6
+ module EnviroSpaces
7
+ class << self
8
+ def included(base)
9
+ target_class = Eco::API::Session::Config::Apis
10
+ msg = "To be included in #{target_class}. "
11
+ msg << "Included in #{base}"
12
+ raise msg unless base <= target_class
13
+
14
+ super
15
+ end
16
+ end
17
+
18
+ include Session::Config::Apis::SpaceHelpers
19
+
20
+ def active_space
21
+ active_api&.space || space_option
22
+ end
23
+
24
+ def enviros(space)
25
+ apis(space).keys
26
+ end
27
+
28
+ # APIs of a particualr `space`
29
+ def apis(space = space_option)
30
+ api_space(space)
31
+ end
32
+
33
+ # Any APIs (of any space or in a particular `space`)
34
+ def apis?(space = nil)
35
+ return apis(space).any? unless space.nil?
36
+
37
+ spaces.each_key.any? do |space|
38
+ apis?(space)
39
+ end
40
+ end
41
+
42
+ def api?(name, space: space_option)
43
+ apis(space).key?(name)
44
+ end
45
+
46
+ # Deprecated
47
+ def defined?(name, space: space_option)
48
+ apis(space).key?(name)
49
+ end
50
+
51
+ def any_defined?(*names, space: space_option)
52
+ [names].flatten.any? do |name|
53
+ api?(name, space: space)
54
+ end
55
+ end
56
+
57
+ # @return [Array<Symbol>] the spaces where the enviro `name`
58
+ # has an api defined
59
+ def enviro_spaces(name)
60
+ spaces.keys.select do |space|
61
+ api?(name, space: space)
62
+ end
63
+ end
64
+
65
+ def enviro_spaces_str(name)
66
+ return '' unless enviro_spaces?(name)
67
+
68
+ ":#{enviro_spaces(name).join(', :')}"
69
+ end
70
+
71
+ def enviro_spaces?(name)
72
+ enviro_spaces(name).any?
73
+ end
74
+
75
+ def missing_api_message(name, space: space_option)
76
+ space ||= space_option
77
+
78
+ msg = "Missing credentials for "
79
+ msg << "#{full_name(name, space: space)} Api env."
80
+
81
+ if enviro_spaces?(name)
82
+ msg << "\n Available space(s) for '#{name}' enviro: "
83
+ msg << enviro_spaces_str(name)
84
+ msg << "\n"
85
+ end
86
+
87
+ msg
88
+ end
89
+
90
+ private
91
+
92
+ def spaces
93
+ self['spaces'] ||= {}
94
+ end
95
+
96
+ def api_space(space = space_option)
97
+ space ||= space_option
98
+
99
+ spaces[space.to_sym] ||= {}
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,94 @@
1
+ module Eco
2
+ module API
3
+ class Session
4
+ class Config
5
+ class Apis
6
+ module OneOff
7
+ private
8
+
9
+ def one_off?
10
+ @is_one_off ||=
11
+ SCR.get_arg('-api-key') ||
12
+ SCR.get_arg('-one-off')
13
+ end
14
+
15
+ def one_off_key
16
+ return @one_off_key if instance_variable_defined?(:@one_off_key)
17
+
18
+ return unless one_off?
19
+
20
+ Dotenv.load('./.env_one_off')
21
+ SCR.get_arg('-api-key', with_param: true).then do |key|
22
+ one_off_key_env(key)
23
+ end
24
+ end
25
+
26
+ def one_off_key_env(key)
27
+ return unless one_off?
28
+
29
+ if key
30
+ env_file_set_var('./.env_one_off', one_off_key_env_var, key)
31
+ key
32
+ else
33
+ Dotenv.load('./.env_one_off')
34
+
35
+ ENV[one_off_key_env_var].tap do |k|
36
+ msg = "At least the first time, "
37
+ msg << "you should provide the -api-key"
38
+ raise msg unless k
39
+ end
40
+ end
41
+ end
42
+
43
+ def one_off_key_env_var
44
+ @one_off_key_env_var ||= "#{one_off_org}_KEY"
45
+ end
46
+
47
+ def one_off_org
48
+ return @one_off_org if instance_variable_defined?(:@one_off_org)
49
+
50
+ msg = "You should specify -org NAME when using -api-key or -one-off"
51
+ raise msg unless org = SCR.get_arg('-org', with_param: true)
52
+
53
+ str_org = "#{org.downcase.split(/[^a-z]+/).join('_')}_#{one_off_enviro.gsub('.', '_')}"
54
+ @one_off_org ||= str_org.to_sym
55
+ end
56
+
57
+ def one_off_enviro
58
+ return @one_off_enviro if instance_variable_defined?(:@one_off_enviro)
59
+
60
+ enviro = 'live'
61
+ enviro = SCR.get_arg('-enviro', with_param: true) if SCR.get_arg('-enviro')
62
+
63
+ @one_off_enviro ||= enviro.downcase
64
+ end
65
+
66
+ def env_file_set_var(file, var, value)
67
+ pattern = /"#{var}=(?<value>[^ \r\n]+)"/
68
+
69
+ File.open(file, "w+") do |fd|
70
+ found = false
71
+
72
+ fd.each_line do |line|
73
+ next unless (match = line.match(pattern))
74
+
75
+ found = true
76
+ # IO::SEEK_CUR => Seeks to _amount_ plus current position
77
+ fd.seek(-(line.length + 1), IO::SEEK_CUR)
78
+ fd.write line.gsub(match[:value], value)
79
+ end
80
+
81
+ fd << "#{var}=#{value}" unless found
82
+ end
83
+
84
+ true
85
+ rescue StandardError => err
86
+ puts "#{err}"
87
+ false
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end