eco-helpers 2.7.16 → 2.7.17

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: 5c94232f6124e63e5b9df716e6e4550b10e3853e2cd2ad9ee5dc6289bf4a2a56
4
- data.tar.gz: 224908e975dff3cf04d03c5c6cc55fde4472a622010a0548beb731cfb1e834d0
3
+ metadata.gz: 35f96561e2b58978c8949c744ac79c87888f067def10f63e00e3393e94cb992c
4
+ data.tar.gz: 7dba34b74428c53f7167eb1075b11bbd306432b3687c05986c66610da5b69f5e
5
5
  SHA512:
6
- metadata.gz: 2521d794cfa6f6d47c938df024f48540d4bf6eb2401a4c5a2e805d556814e1bdfef3949a03f492b7ab00ce98f56b752438e06a0860aebdb788c5eda056b242de
7
- data.tar.gz: 245b52b949c7c8141821ce61c0b58beed8242d5b0a6efd493892e1f9c96f58198db6fa4b2c0da0c08b275ff8a6eb7c6444e099dd1c1898a532bd8310a9201c8a
6
+ metadata.gz: 35223b9e52a315cf78b65393b509675a5ab515976e36535906b1a1e4588ccab178272ce209ab964b4f0365f74adccae9536db778edc5b524fa0cc70b327f0ccf
7
+ data.tar.gz: 0b356c4f3ca79f4ae57cae1db37f97455199c0772a165f294577bf7cfe3b4f1249a3c6cbee34c8903cb2d637a3b4b28658a3897680d61b2494aeffdd026efb8f
data/CHANGELOG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [2.7.16] - 2024-06-18
5
+ ## [2.7.18] - 2024-06-xx
6
6
 
7
7
  ### Added
8
8
 
@@ -10,14 +10,36 @@ All notable changes to this project will be documented in this file.
10
10
 
11
11
  ### Fixed
12
12
 
13
- - `RegisterUpdateCase` filters
14
-
15
- ## [2.7.15] - 2024-06-18
13
+ ## [2.7.17] - 2024-06-22
16
14
 
17
15
  ### Added
18
16
 
17
+ - **Stop on uknown** options will try to offer suggestions from now on
18
+ - `Eco::CSV::count` class method to stream-count the number of rows
19
+ - Option `-start-at` allows to start the count at certain row idx
20
+
19
21
  ### Changed
20
22
 
23
+ - Added options to case `-split-csv`
24
+ - `-start-at`
25
+ - `-simulate` a dry-run will return the count and won't generate the files.
26
+ - `Eco::CSV::split`
27
+ - the `block` allows to filter what rows should be included
28
+
29
+ ### Fixed
30
+
31
+ - `Workflow::Mailer`: shouldn't send notification when there is an **error** that doesn't really need to be notified.
32
+ - `to-csv` case should accepte target folder as an argument
33
+ - `options_set` sort namespaces
34
+
35
+ ## [2.7.16] - 2024-06-18
36
+
37
+ ### Fixed
38
+
39
+ - `RegisterUpdateCase` filters
40
+
41
+ ## [2.7.15] - 2024-06-18
42
+
21
43
  ### Fixed
22
44
 
23
45
  - `Eco::API::Common::People::EntryFactor` super must be called before
@@ -17,7 +17,10 @@ class Eco::API::Common::Loaders::Workflow::Mailer < Eco::API::Common::Loaders::W
17
17
  next unless session.mailer?
18
18
  next if session.config.dry_run?
19
19
  next unless session.config.run_mode_remote?
20
- next unless some_update?(io) || error?
20
+
21
+ # temporary contingency
22
+ maybe_error_pages_or_tree_updates = other_case?(io) && error?
23
+ next unless some_update?(io) || maybe_error_pages_or_tree_updates
21
24
 
22
25
  subject = base_subject
23
26
 
@@ -53,6 +56,12 @@ class Eco::API::Common::Loaders::Workflow::Mailer < Eco::API::Common::Loaders::W
53
56
  end
54
57
  end
55
58
 
59
+ def other_case?(io)
60
+ cli.config.usecases.active(io: io).any? do |usecase, _data|
61
+ %i[other].any? { |type| usecase.type == type }
62
+ end
63
+ end
64
+
56
65
  def error?
57
66
  !!error
58
67
  end
@@ -7,18 +7,17 @@ module Eco
7
7
  def people_cache(filename = enviro.config.people.cache)
8
8
  logger.info("Going to get all the people via API")
9
9
 
10
- start = Time.now
11
- people = session.batch.get_people
10
+ start = Time.now
11
+ people = session.batch.get_people
12
12
  secs = (Time.now - start).round(3)
13
13
  cnt = people.count
14
14
  per_sec = (cnt.to_f / secs).round(2)
15
15
  logger.info("Loaded #{cnt} people in #{secs} seconds (#{per_sec} people/sec)")
16
16
 
17
- file = file_manager.save_json(people, filename, :timestamp)
17
+ file = file_manager.save_json(people, filename, :timestamp)
18
18
  logger.info("#{people.length} people loaded and saved locally to #{file}.")
19
19
  Eco::API::Organization::People.new(people)
20
20
  end
21
-
22
21
  end
23
22
  end
24
23
  end
@@ -15,19 +15,23 @@ module Eco
15
15
  # - `:file` if it is supposed to load people from a file.
16
16
  # - `:save` if it is supposed to cache/save the data locally once obtained people from the server (`:api`)
17
17
  # @return [Eco::API::Organization::People] the `People` object with the data.
18
- def people_load(filename = enviro.config.people.cache, modifier: [:newest, :api])
18
+ def people_load(filename = enviro.config.people.cache, modifier: %i[newest api]) # rubocop:disable Metrics/AbcSize
19
19
  modifier = [modifier].flatten
20
- load_file = [:file, :newest].any? {|flag| modifier.include?(flag)}
20
+ load_file = %i[file newest].any? {|flag| modifier.include?(flag)}
21
+
21
22
  case
22
23
  when filename && load_file
23
- if file = people_load_filename(filename, newest: modifier.include?(:newest))
24
+ file = people_load_filename(filename, newest: modifier.include?(:newest))
25
+
26
+ if file
24
27
  file_manager.load_json(file).tap do |people|
25
28
  logger.info("#{people&.length} people loaded from file #{file}") if people.is_a?(Array)
26
29
  end
27
30
  else
28
31
  logger.error("could not find the file #{file_manager.dir.file(filename)}")
29
32
  exit unless modifier.include?(:api)
30
- people_load(modifier: modifier - [:newest, :file])
33
+
34
+ people_load(modifier: modifier - %i[newest file])
31
35
  end
32
36
  when modifier.include?(:api)
33
37
  logger.info("Going to get all the people via API (load)")
@@ -39,12 +43,12 @@ module Eco
39
43
  per_sec = (cnt.to_f / secs).round(2)
40
44
  logger.info("Loaded #{cnt} people in #{secs} seconds (#{per_sec} people/sec)")
41
45
 
42
- if modifier.include?(:save) && people && people.length > 0
46
+ if modifier.include?(:save) && people && people.length.positive?
43
47
  file = file_manager.save_json(people, filename, :timestamp)
44
- logger.info("#{people.length } people saved to file #{file}.")
48
+ logger.info("#{people.length} people saved to file #{file}.")
45
49
  end
46
50
  end
47
- end.yield_self do |people|
51
+ end.then do |people|
48
52
  Eco::API::Organization::People.new(people)
49
53
  end
50
54
  end
@@ -61,7 +65,6 @@ module Eco
61
65
  file_manager.dir.file(filename, should_exist: true)
62
66
  end
63
67
  end
64
-
65
68
  end
66
69
  end
67
70
  end
@@ -9,24 +9,26 @@ module Eco
9
9
  # @param people [Eco::API::Organization::People] the people that needs refresh.
10
10
  # @param include_created [Boolean] include people created during this session? (will check `:create` batch jobs).
11
11
  # @return [Eco::API::Organization::People] the `People` object with the data.
12
- def people_refresh(people:, include_created: true)
12
+ def people_refresh(people:, include_created: true) # rubocop:disable Metrics/AbcSize
13
13
  people = people.newFrom people.select do |person|
14
14
  !person.new? || !person.dirty?
15
15
  end
16
+
16
17
  ini = people.length
18
+
17
19
  if include_created
18
20
  session.job_groups.find_jobs(type: :create).map do |job|
19
- to_add = job.people.select {|person| !person.dirty?}
21
+ to_add = job.people.reject(&:dirty?)
20
22
  people = people.merge(to_add)
21
23
  end
22
24
  end
23
25
 
24
26
  created = people.length - ini
25
- msg = "Going to refresh #{people.length} people with server data"
26
- msg += " (including #{created} that were created)" if created > 0
27
+ msg = "Going to refresh #{people.length} people with server data"
28
+ msg += " (including #{created} that were created)" if created.positive?
27
29
  logger.info(msg)
28
30
 
29
- start = Time.now
31
+ start = Time.now
30
32
  entries = session.batch.get_people(people, silent: true)
31
33
  secs = (Time.now - start).round(3)
32
34
  cnt = entries.count
@@ -34,11 +36,10 @@ module Eco
34
36
  logger.info("Re-loaded #{cnt} people (out of #{people.length}) in #{secs} seconds (#{per_sec} people/sec)")
35
37
 
36
38
  missing = people.length - entries.length
37
- logger.error("Missed to obtain #{missing} people during the refresh") if missing > 0
39
+ logger.error("Missed to obtain #{missing} people during the refresh") if missing.positive?
38
40
 
39
41
  Eco::API::Organization::People.new(entries)
40
42
  end
41
-
42
43
  end
43
44
  end
44
45
  end
@@ -5,52 +5,57 @@ module Eco
5
5
  # @note
6
6
  # - this helper is normally used to **get partial** part of the people manager.
7
7
  # - therefore, normally used with _**delta** input files_ (files with only the differences).
8
- # @param data [Eco::API::Organization::People, Enumerable<Person>, Enumerable<Hash>] `People` to search against the server.
8
+ # @param data [Eco::API::Organization::People, Enumerable<Person>, Enumerable<Hash>]
9
+ # `People` to search against the server.
9
10
  # @param options [Hash] the options.
10
11
  # @param silent [Boolean] `false` if low level search messages should be shown.
11
12
  # @return [Eco::API::Organization::People] the `People` object with the found persons.
12
- def people_search(data, options: {}, silent: true)
13
+ def people_search(data, options: {}, silent: true) # rubocop:disable Metrics/AbcSize
13
14
  session.logger.info("Going to api get #{data.length} entries...")
14
15
 
15
16
  start = Time.now
16
- people = session.batch.search(data, silent: silent).yield_self do |status|
17
- secs = (Time.now - start).round(3)
18
- Eco::API::Organization::People.new(status.people).tap do |people|
17
+ people = session.batch.search(data, silent: silent).then do |status|
18
+ secs = (Time.now - start).round(3)
19
+ Eco::API::Organization::People.new(status.people).tap do |people| # rubocop:disable Lint/ShadowingOuterLocalVariable
19
20
  cnt = people.count
20
21
  per_sec = (cnt.to_f / secs).round(2)
21
- msg = "... could get #{cnt} people (out of #{data.length} entries) in #{secs} seconds (#{per_sec} people/sec)"
22
+ msg = "... could get #{cnt} people "
23
+ msg << "(out of #{data.length} entries) in #{secs} seconds (#{per_sec} people/sec)"
22
24
  session.logger.info(msg)
23
25
  end
24
26
  end
25
27
 
26
28
  # get the supervisors of found people (current supervisors)
27
29
  supers = people_search_prepare_supers_request(people)
28
- if supers.length > 0
30
+ if supers.length.positive?
29
31
  session.logger.info(" Going to api get #{supers.length} current supervisors...")
30
32
  start = Time.now
31
- people = session.batch.search(supers, silent: silent).yield_self do |status|
33
+ people = session.batch.search(supers, silent: silent).then do |status|
32
34
  secs = (Time.now - start).round(3)
33
35
  found = status.people
34
36
  cnt = found.count
35
37
  per_sec = (cnt.to_f / secs).round(2)
36
- msg = "... could find #{cnt} current supers (out of #{supers.length}) in #{secs} seconds (#{per_sec} people/sec)"
38
+ msg = "... could find #{cnt} current supers "
39
+ msg << "(out of #{supers.length}) in #{secs} seconds (#{per_sec} people/sec)"
37
40
  session.logger.info(msg)
41
+
38
42
  people.merge(found, strict: micro.strict_search?(options))
39
43
  end
40
44
  end
41
45
 
42
46
  # get the supervisors referred in the input data (future supervisors)
43
47
  supers = people_search_prepare_supers_request(data, people)
44
- if supers.length > 0
48
+ if supers.length.positive?
45
49
  session.logger.info(" Going to api get #{supers.length} supervisors as per input entries...")
46
50
  start = Time.now
47
51
 
48
- people = session.batch.search(supers, silent: silent).yield_self do |status|
52
+ people = session.batch.search(supers, silent: silent).then do |status|
49
53
  secs = (Time.now - start).round(3)
50
54
  found = status.people
51
55
  cnt = found.count
52
56
  per_sec = (cnt.to_f / secs).round(2)
53
- msg = "... could find #{cnt} input supers (out of #{supers.length}) in #{secs} seconds (#{per_sec} people/sec)"
57
+ msg = "... could find #{cnt} input supers "
58
+ msg << "(out of #{supers.length}) in #{secs} seconds (#{per_sec} people/sec)"
54
59
  session.logger.info(msg)
55
60
  people.merge(found, strict: micro.strict_search?(options))
56
61
  end
@@ -66,25 +71,26 @@ module Eco
66
71
  def people_search_prepare_supers_request(data, people = data)
67
72
  data.each_with_object([]) do |entry, request|
68
73
  spr = {"id" => (sup_id = people_search_super_id(entry))}
69
- unless !sup_id || request.include?(spr)
70
- micro.with_supervisor(sup_id, people) do |supervisor|
71
- request.push(spr) unless supervisor
72
- end
74
+ next if !sup_id || request.include?(spr)
75
+
76
+ micro.with_supervisor(sup_id, people) do |supervisor|
77
+ request.push(spr) unless supervisor
73
78
  end
74
79
  end
75
80
  end
76
81
 
77
82
  # Gets the `supervisor_id` from `value`
78
83
  def people_search_super_id(value)
79
- sup_id = if value.respond_to?(:supervisor_id)
80
- value.supervisor_id
81
- elsif value.is_a?(Hash) && value.key("supervisor_id")
82
- value["supervisor_id"]
83
- end
84
+ sup_id =
85
+ if value.respond_to?(:supervisor_id)
86
+ value.supervisor_id
87
+ elsif value.is_a?(Hash) && value.key("supervisor_id")
88
+ value["supervisor_id"]
89
+ end
90
+
84
91
  sup_id = nil if sup_id.to_s.strip.empty?
85
92
  sup_id
86
93
  end
87
-
88
94
  end
89
95
  end
90
96
  end
@@ -7,9 +7,10 @@ module Eco
7
7
  def apply!(arg_case = cli_name)
8
8
  #puts "DEFINING CLI for '#{arg_case}' via #{self}"
9
9
  if applied?(arg_case)
10
- puts "Warning: (#{self}) Tried to call again cli.apply! on '#{arg_case}'"
10
+ puts "Warning: (#{self}) Tried to call again cli.apply! on '#{arg_case}'"
11
11
  return self
12
12
  end
13
+
13
14
  cli_config_case(arg_case)
14
15
  apply_options(arg_case)
15
16
  applied!(arg_case)
@@ -29,10 +30,11 @@ module Eco
29
30
  end
30
31
 
31
32
  attr_writer :usecase
33
+
32
34
  # Unless specified, assume Cli class hangs from its case namespace
33
35
  def usecase
34
- raise "#{self} is to use to extend a class" unless self.is_a?(Class)
35
- @usecase ||= Kernel.const_get(self.to_s.split('::')[0..-2].join('::'))
36
+ raise "#{self} is to use to extend a class" unless is_a?(Class)
37
+ @usecase ||= Kernel.const_get(to_s.split('::')[0..-2].join('::'))
36
38
  end
37
39
 
38
40
  def description(value = nil)
@@ -50,7 +52,7 @@ module Eco
50
52
 
51
53
  # It defaults to the use case preceded by dash
52
54
  def cli_name(arg_name = nil)
53
- @cli_name = (arg_name.nil? ? @cli_name : arg_name).yield_self do |value|
55
+ @cli_name = (arg_name.nil? ? @cli_name : arg_name).then do |value|
54
56
  value = "-#{name}" if value.nil?
55
57
  value
56
58
  end
@@ -66,7 +68,7 @@ module Eco
66
68
  end
67
69
 
68
70
  def add_option(arg, desc = nil, &block)
69
- self.tap do
71
+ tap do
70
72
  "Overriding option '#{arg}' on case '#{name}'" if options.key?(arg)
71
73
  @options[arg] = Eco::API::UseCases::Cli::Option.new(arg, desc, &block)
72
74
  end
@@ -75,7 +77,7 @@ module Eco
75
77
  private
76
78
 
77
79
  def apply_options(arg_case)
78
- options.each do |_key, option|
80
+ options.each_value do |option|
79
81
  option.link_case(cli_config_case(arg_case))
80
82
  end
81
83
  end
@@ -13,6 +13,7 @@ class Eco::API::UseCases::Cli
13
13
 
14
14
  def link_case(cli_config_case)
15
15
  raise ArgumentError, "cli_config_case must have an 'add_option' method. Given: #{cli_config_case.class}" unless cli_config_case.respond_to?(:add_option)
16
+
16
17
  cli_config_case.add_option(name, desc, &callback)
17
18
  end
18
19
  end
@@ -11,5 +11,10 @@ class Eco::API::UseCases::Default::People::Utils::SplitCsv
11
11
  count = SCR.get_arg("-max-rows", with_param: true)
12
12
  options.deep_merge!(output: {file: {max_rows: count}})
13
13
  end
14
+
15
+ add_option("-start-at", "Get only the last N-start_at rows") do |options|
16
+ count = SCR.get_arg("-start-at", with_param: true)
17
+ options.deep_merge!(output: {file: {start_at: count}})
18
+ end
14
19
  end
15
20
  end
@@ -7,15 +7,27 @@ class Eco::API::UseCases::Default::People::Utils::SplitCsv < Eco::API::Common::L
7
7
  type :other
8
8
 
9
9
  def main(*_args)
10
- Eco::CSV.split(input_file, max_rows: max_rows).each do |file|
11
- log(:info) {
12
- "Generated file '#{file}'"
13
- }
10
+ if simulate?
11
+ count = Eco::CSV.count(input_file, start_at: start_at)
12
+ log(:info) { "CSV '#{input_file}' has #{count} rows." }
13
+ else
14
+ Eco::CSV.split(
15
+ input_file,
16
+ max_rows: max_rows,
17
+ start_at: start_at,
18
+ &filter
19
+ ).each do |file|
20
+ log(:info) { "Generated file '#{file}'" }
21
+ end
14
22
  end
15
23
  end
16
24
 
17
25
  private
18
26
 
27
+ def filter
28
+ nil
29
+ end
30
+
19
31
  def input_file
20
32
  options.dig(:source, :file)
21
33
  end
@@ -31,4 +43,12 @@ class Eco::API::UseCases::Default::People::Utils::SplitCsv < Eco::API::Common::L
31
43
  num = nil if num.zero?
32
44
  num
33
45
  end
46
+
47
+ def start_at
48
+ return nil unless (num = options.dig(:output, :file, :start_at))
49
+
50
+ num = num.to_i
51
+ num = nil if num.zero?
52
+ num
53
+ end
34
54
  end
@@ -2,6 +2,8 @@ class Eco::API::UseCases::DefaultCases::ToCsvCase < Eco::API::Common::Loaders::U
2
2
  name "to-csv"
3
3
  type :export
4
4
 
5
+ OUT_FILENAME = 'pm'
6
+
5
7
  attr_reader :people
6
8
 
7
9
  def main(people, _session, options, _usecase)
@@ -16,8 +18,9 @@ class Eco::API::UseCases::DefaultCases::ToCsvCase < Eco::API::Common::Loaders::U
16
18
  if options.dig(:export, :options, :split_schemas)
17
19
  by_schema.each do |id, people|
18
20
  sch_name = schemas.to_name(id)
19
- prefix = sch_name ? sch_name.gsub(" ", "_") : "No_Schema"
20
- create_file!("#{prefix}_#{file}", people)
21
+ prefix = sch_name ? sch_name.gsub(" ", "_").downcase : "no_schema"
22
+ filename = in_folder("#{prefix}_#{File.basename(file)}")
23
+ create_file!(filename, people)
21
24
  end
22
25
  else
23
26
  create_file!(file, people)
@@ -61,15 +64,13 @@ class Eco::API::UseCases::DefaultCases::ToCsvCase < Eco::API::Common::Loaders::U
61
64
  end
62
65
 
63
66
  def nice_header_names(header, schema: nil)
64
- schema ||= session.schema
65
- name_maps = schema.fields_by_alt_id.each_with_object({}) do |(alt_id, fld), mappings|
66
- mappings[alt_id] = fld.name
67
- end.merge(nice_header_maps)
67
+ schema ||= session.schema
68
+ name_maps = schema.fields_by_alt_id.transform_values(&:name).merge(nice_header_maps)
68
69
  header.map {|name| name_maps[name] || name}
69
70
  end
70
71
 
71
72
  def to_entry_type(person)
72
- session.new_entry(person, dependencies: deps).yield_self do |person_entry|
73
+ session.new_entry(person, dependencies: deps).then do |person_entry|
73
74
  options.dig(:export, :options, :internal_names) ? person_entry.mapped_entry : person_entry.external_entry
74
75
  end
75
76
  end
@@ -79,14 +80,37 @@ class Eco::API::UseCases::DefaultCases::ToCsvCase < Eco::API::Common::Loaders::U
79
80
  end
80
81
 
81
82
  def file
82
- @file ||= (options[:file] || options.dig(:export, :file, :name)).tap do |filename|
83
- unless filename
84
- session.logger.error("Destination file not specified")
85
- return false
86
- end
83
+ @file ||= out_filename.tap do |filename|
84
+ next if filename
85
+
86
+ log(:error) { "Destination file not specified" }
87
+ return false
87
88
  end
88
89
  end
89
90
 
91
+ def out_filename
92
+ return options_file unless options_folder?
93
+
94
+ File.join(options_file, "#{config.active_enviro}_#{OUT_FILENAME}.csv")
95
+ end
96
+
97
+ def in_folder(filename)
98
+ basename = File.basename(filename)
99
+ return basename unless options_folder?
100
+
101
+ File.join(options_file, basename)
102
+ end
103
+
104
+ def options_folder?
105
+ return false unless (value = options_file)
106
+
107
+ File.directory?(value)
108
+ end
109
+
110
+ def options_file
111
+ options[:file] || options.dig(:export, :file, :name)
112
+ end
113
+
90
114
  def by_schema
91
115
  people.group_by do |person|
92
116
  if (details = person.details)
@@ -15,7 +15,8 @@ module Eco
15
15
  def help(msg = nil, refine: nil)
16
16
  refinement = refine.is_a?(String)? " (containing: '#{refine}')" : ""
17
17
  msg ||= "The following are the available filters#{refinement}:"
18
- [msg].yield_self do |lines|
18
+
19
+ [msg].then do |lines|
19
20
  max_len = keys_max_len(@filters.keys)
20
21
  @filters.keys.sort.select do |key|
21
22
  !refine.is_a?(String) || key.include?(refine)
@@ -26,10 +27,19 @@ module Eco
26
27
  end.join("\n")
27
28
  end
28
29
 
30
+ def available(keys: false)
31
+ return @filters.keys if keys
32
+
33
+ @filters.keys.map do |key|
34
+ [key, @filters[key], @description[key]]
35
+ end
36
+ end
37
+
29
38
  # @param option [String] the command line option that activates this filter.
30
39
  # @param desc [String] description of the filter.
31
40
  def add(option, desc = nil, &block)
32
- raise "Missing block to define the filters builder" unless block_given?
41
+ raise ArgumentError, "Missing block to define the filters builder" unless block_given?
42
+
33
43
  callback = block
34
44
  [option].flatten.compact.each do |opt|
35
45
  @filters[opt] = callback
@@ -38,7 +48,7 @@ module Eco
38
48
  self
39
49
  end
40
50
 
41
- def process(io:)
51
+ def process(*)
42
52
  raise "You need to override this method in child classes"
43
53
  end
44
54
  end
@@ -2,8 +2,7 @@ module Eco
2
2
  class CLI
3
3
  class Config
4
4
  module Help
5
-
6
- def help(*args)
5
+ def help(*_args)
7
6
  raise "this needs to be reimplemented in the child class and return a string"
8
7
  end
9
8
 
@@ -16,11 +15,12 @@ module Eco
16
15
  # Creatas a well aligned line
17
16
  def help_line(key, desc, keys_max_len = key.length, line_len = 100)
18
17
  blanks = keys_max_len + 3 - key.length
19
- blanks = blanks < 0 ? 0 : blanks
18
+ blanks = [blanks, 0].max
20
19
  top_line = " #{key}#{" "*blanks} "
21
20
  indent = top_line.length
22
21
  first = true
23
- each_slice_words(desc, line_len - indent).each_with_object([]) do |line, lines|
22
+
23
+ each_slice_words(desc, line_len - indent).each_with_object([]) do |line, lines| # rubocop:disable Style/RedundantEach
24
24
  lines << (first ? "#{top_line}#{line}" : "#{" " * indent}#{line}")
25
25
  first = false
26
26
  end.join("\n")
@@ -33,14 +33,16 @@ module Eco
33
33
  liner << part
34
34
  else
35
35
  yield(liner) if block_given?
36
- out << liner
36
+
37
+ out << liner
37
38
  liner = part.strip
38
39
  end
39
40
  end.tap do |out|
40
- if out.empty? || !liner.empty?
41
- yield(liner) if block_given?
42
- out << liner if liner
43
- end
41
+ next unless out.empty? || !liner.empty?
42
+
43
+ yield(liner) if block_given?
44
+
45
+ out << liner if liner
44
46
  end
45
47
  end
46
48
  end
@@ -2,14 +2,13 @@ module Eco
2
2
  class CLI
3
3
  class Config
4
4
  class Input
5
-
6
5
  attr_reader :core_config
7
6
  attr_reader :default_option
8
7
 
9
8
  def initialize(core_config:, default_option: nil)
10
9
  @core_config = core_config
11
10
  @default_option = default_option
12
- @callbacks = {}
11
+ @callbacks = {}
13
12
  end
14
13
 
15
14
  def default_option?
@@ -20,16 +19,17 @@ module Eco
20
19
  option ||= default_option
21
20
  raise "Missing option to identify the input (no default defined)" unless !!option
22
21
  raise "Missing block to define the input obtention process" unless block
22
+
23
23
  @callbacks[option] = block
24
24
  end
25
25
 
26
26
  def get(io:, option: nil)
27
- unless io && io.is_a?(Eco::API::UseCases::BaseIO)
28
- raise "You need to provide Eco::API::UseCases::BaseIO object. Given: #{io.class}"
29
- end
27
+ msg = "You need to provide Eco::API::UseCases::BaseIO object. Given: #{io.class}"
28
+ raise ArgumentError, msg unless io.is_a?(Eco::API::UseCases::BaseIO)
30
29
 
31
30
  option ||= default_option
32
- raise "Missing option to identify the input (no default defined)" unless !!option
31
+ raise "Missing option to identify the input (no default defined)" unless option
32
+
33
33
  callback = @callbacks[option] || @callbacks[default_option]
34
34
  callback.call(io.session, option, io.options)
35
35
  end
@@ -3,10 +3,10 @@ module Eco
3
3
  class Config
4
4
  class OptionsSet
5
5
  include Eco::CLI::Config::Help
6
+
6
7
  attr_reader :core_config
7
8
 
8
- class OptConfig < Struct.new(:name, :namespace, :description, :callback)
9
- end
9
+ OptConfig = Struct.new(:name, :namespace, :description, :callback)
10
10
 
11
11
  def initialize(core_config:)
12
12
  @core_config = core_config
@@ -15,19 +15,21 @@ module Eco
15
15
 
16
16
  # @return [String] summary of the options.
17
17
  def help(refine: nil)
18
- indent = 2
19
- spaces = any_non_general_space_active? ? active_namespaces : namespaces
18
+ indent = 2
19
+ spaces = any_non_general_space_active? ? active_namespaces : namespaces
20
20
  refinement = refine.is_a?(String)? " (containing: '#{refine}')" : ""
21
- ["The following are the available options#{refinement}:"].yield_self do |lines|
21
+
22
+ ["The following are the available options#{refinement}:"].then do |lines|
22
23
  max_len = keys_max_len(options_args(spaces)) + indent
23
24
  spaces.each do |namespace|
24
25
  is_general = (namespace == :general)
25
26
  str_indent = is_general ? "" : " " * indent
26
27
  lines << help_line(namespace, "", max_len) unless is_general
27
- options_set(namespace).select do |arg, option|
28
+
29
+ options_set(namespace).select do |_arg, option| # rubocop:disable Style/HashEachMethods
28
30
  !refine.is_a?(String) || option.name.include?(refine)
29
- end.each do |arg, option|
30
- lines << help_line(" " * indent + "#{option.name}", option.description, max_len)
31
+ end.each do |_arg, option|
32
+ lines << help_line("#{str_indent}#{option.name}", option.description, max_len)
31
33
  end
32
34
  end
33
35
  lines
@@ -41,6 +43,19 @@ module Eco
41
43
  end.uniq
42
44
  end
43
45
 
46
+ # Options that are known for active namespaces (CLI-present)
47
+ def available(keys: false)
48
+ sets.select do |namespace, _opts_set|
49
+ active_namespace?(namespace)
50
+ end.each_value.with_object([]) do |opts_set, options|
51
+ options.concat(opts_set.values)
52
+ end.then do |options|
53
+ next options unless keys
54
+
55
+ options.map(&:name)
56
+ end
57
+ end
58
+
44
59
  # @param option [String, Array<String>] the command line option(s).
45
60
  # @param namespace [String] preceding command(s) argument that enables this option.
46
61
  # @param desc [String] description of the option.
@@ -51,17 +66,18 @@ module Eco
51
66
  unless opts.empty?
52
67
  callback = block
53
68
  opts.each do |opt|
54
- puts "Overriding CLI option '#{option}' in '#{namespace}' CLI case / namespace" if option_exists?(opt, namespace)
69
+ msg = "Overriding CLI option '#{option}' in '#{namespace}' CLI case / namespace"
70
+ puts msg if option_exists?(opt, namespace)
55
71
  options_set(namespace)[opt] = OptConfig.new(opt, namespace, desc, callback)
56
72
  end
57
73
  end
74
+
58
75
  self
59
76
  end
60
77
 
61
78
  def process(io:)
62
- unless io && io.is_a?(Eco::API::UseCases::BaseIO)
63
- raise "You need to provide Eco::API::UseCases::BaseIO object. Given: #{io.class}"
64
- end
79
+ msg = "You need to provide Eco::API::UseCases::BaseIO object. Given: #{io.class}"
80
+ raise ArgumentError, msg unless io.is_a?(Eco::API::UseCases::BaseIO)
65
81
 
66
82
  active_options.each do |option|
67
83
  option.callback.call(io.options, io.session)
@@ -70,8 +86,9 @@ module Eco
70
86
  io.options
71
87
  end
72
88
 
89
+ # Options that have been invoked via CLI
73
90
  def active_options
74
- @active_options ||= sets.select do |namespace, opts_set|
91
+ @active_options ||= sets.select do |namespace, _opts_set|
75
92
  active_namespace?(namespace)
76
93
  end.each_with_object([]) do |(namespace, opts_set), options|
77
94
  opts_set.each do |arg, option|
@@ -81,19 +98,20 @@ module Eco
81
98
  end
82
99
 
83
100
  def all_options
84
- sets.each_with_object([]) do |(namespace, opts_set), options|
101
+ sets.each_with_object([]) do |(_namespace, opts_set), options|
85
102
  options << opts_set.values
86
103
  end
87
104
  end
88
105
 
89
106
  def namespaces
90
107
  sets.keys.sort_by do |key|
91
- key == :general
108
+ next 1 if key == :general
109
+ 0
92
110
  end
93
111
  end
94
112
 
95
113
  def any_non_general_space_active?
96
- (active_namespaces - [:general]).length > 0
114
+ (active_namespaces - [:general]).length.positive?
97
115
  end
98
116
 
99
117
  def active_namespaces
@@ -104,7 +122,6 @@ module Eco
104
122
  end
105
123
  end
106
124
 
107
-
108
125
  private
109
126
 
110
127
  def active_namespace?(namespace)
@@ -132,10 +149,6 @@ module Eco
132
149
  }
133
150
  end
134
151
 
135
- def namespaces
136
- @sets.keys
137
- end
138
-
139
152
  def options_set(namespace = :general)
140
153
  @sets[namespace] ||= {}
141
154
  end
@@ -39,6 +39,15 @@ module Eco
39
39
  end.join("\n")
40
40
  end
41
41
 
42
+ # The linked cases
43
+ def available(keys: false)
44
+ @linked_cases.values.then do |cases|
45
+ next cases unless keys
46
+
47
+ cases.map(&:option)
48
+ end
49
+ end
50
+
42
51
  # Integrates a usecase to the command line.
43
52
  # @param option_case [String] the command line option to invoke the usecase.
44
53
  # @param type [Symbol] the type of usecase.
@@ -46,10 +55,12 @@ module Eco
46
55
  # @param case_name [String, nil] the name of the usecase as defined.
47
56
  def add(option_case, type, desc = nil, case_name: nil, &callback)
48
57
  Eco::API::UseCases::UseCase.validate_type(type)
58
+
49
59
  unless block_given?
50
60
  raise "You must specify a valid 'case_name' when no block is provided" unless case_name
51
61
  raise "'case_name' expected to be a String. Given: #{case_name.class}" unless case_name.is_a?(String)
52
62
  end
63
+
53
64
  puts "Overriding CLI case '#{option_case}'" if @linked_cases.key?(option_case)
54
65
  @linked_cases[option_case] = CaseConfig.new(self, option_case, type, desc, case_name, callback)
55
66
  end
@@ -60,6 +71,7 @@ module Eco
60
71
  io.session.usecases.each do |usecase|
61
72
  next unless usecase.respond_to?(:classed_definition)
62
73
  next unless (original_case = usecase.classed_definition)
74
+
63
75
  original_case.cli_apply!
64
76
  end
65
77
  end
@@ -84,9 +96,12 @@ module Eco
84
96
 
85
97
  def process(io:)
86
98
  validate_io!(io)
99
+
87
100
  processed = false
101
+
88
102
  active(io: io).each do |usecase, data|
89
103
  raise "Something went wrong when scoping active cases" unless data
104
+
90
105
  processed = true
91
106
  io = case_io(io: io, usecase: usecase)
92
107
  # some usecases have a callback to collect the parameters
@@ -101,6 +116,7 @@ module Eco
101
116
  # Gets a `UseCaseIO`
102
117
  def case_io(io:, usecase:)
103
118
  validate_io!(io)
119
+
104
120
  case io
105
121
  when Eco::API::UseCases::UseCaseIO
106
122
  io.chain(usecase: usecase)
@@ -122,12 +138,12 @@ module Eco
122
138
  params = io.params(keyed: true).merge(type: data.type)
123
139
  io = io.new(**params, validate: false)
124
140
  callback.call(*io.params).tap do |use|
125
- unless use.is_a?(Eco::API::UseCases::UseCase)
126
- msg = "When adding a usecase, without specifying 'case_name:', "
127
- msg += "the block that integrates usecase for cli option '#{data.option}'"
128
- msg += " must return an Eco::API::UseCases::UseCase object. It returns #{use.class}"
129
- raise msg
130
- end
141
+ next if use.is_a?(Eco::API::UseCases::UseCase)
142
+
143
+ msg = "When adding a usecase, without specifying 'case_name:', "
144
+ msg << "the block that integrates usecase for cli option '#{data.option}'"
145
+ msg << "must return an Eco::API::UseCases::UseCase object. It returns #{use.class}"
146
+ raise msg
131
147
  end
132
148
  end
133
149
  end
@@ -1,7 +1,6 @@
1
1
  module Eco
2
2
  class CLI
3
3
  class Config
4
-
5
4
  attr_reader :cli
6
5
 
7
6
  def initialize(cli:)
@@ -16,6 +15,16 @@ module Eco
16
15
  cli.args
17
16
  end
18
17
 
18
+ # All the available known option args that can be used in the CLI
19
+ def available_option_args
20
+ [
21
+ usecases.available(keys: true),
22
+ options_set.available(keys: true),
23
+ people_filters.available(keys: true),
24
+ input_filters.available(keys: true)
25
+ ].flatten.uniq
26
+ end
27
+
19
28
  def options_set
20
29
  @opions_set ||= Eco::CLI::Config::OptionsSet.new(core_config: self)
21
30
  @opions_set.tap do |opts_set|
@@ -25,12 +34,10 @@ module Eco
25
34
 
26
35
  def input(default_option: nil, &block)
27
36
  @input ||= Eco::CLI::Config::Input.new(core_config: self, default_option: default_option)
28
- if block_given?
29
- @input.define(&block)
30
- self
31
- else
32
- @input
33
- end
37
+ return @input unless block_given?
38
+
39
+ @input.define(&block)
40
+ self
34
41
  end
35
42
 
36
43
  def people(io: nil, &block)
@@ -38,10 +45,11 @@ module Eco
38
45
  @people_load = block
39
46
  self
40
47
  else
41
- raise "There is no definition on how to load people" unless instance_variable_defined?(:@people_load) && @people_load
42
- unless io && io.is_a?(Eco::API::UseCases::BaseIO)
43
- raise "You need to provide Eco::API::UseCases::BaseIO object. Given: #{io.class}"
44
- end
48
+ msg = "There is no definition on how to load people"
49
+ raise msg unless instance_variable_defined?(:@people_load) && @people_load
50
+
51
+ msg = "You need to provide Eco::API::UseCases::BaseIO object. Given: #{io.class}"
52
+ raise msg unless io.is_a?(Eco::API::UseCases::BaseIO)
45
53
 
46
54
  io = io.new(type: :import, input: io.input || [])
47
55
  @people_load.call(*io.params)
@@ -7,7 +7,7 @@ module Eco
7
7
  @argv || ARGV
8
8
  end
9
9
 
10
- def is_modifier?(value)
10
+ def is_modifier?(value) # rubocop:disable Naming/PredicateName
11
11
  Argument.is_modifier?(value)
12
12
  end
13
13
 
@@ -21,22 +21,34 @@ module Eco
21
21
  arguments.add(key, with_param: with_param)
22
22
  end
23
23
 
24
-
25
24
  # Validation to stop the `script` if among `argv` there's any **unknown** argument.
26
- def stop_on_unknown!(exclude: [], only_options: false)
25
+ def stop_on_unknown!(exclude: [], only_options: false, all_available: nil)
27
26
  # validate only those that are options
28
- unknown = arguments.unknown(exclude: exclude)
29
- if only_options
30
- unknown = unknown..select {|arg| is_modifier?(arg)}
27
+ suggestions = {}
28
+ args = {
29
+ exclude: exclude,
30
+ all_available: all_available
31
+ }
32
+ unknown = arguments.unknown(**args) do |key, correct|
33
+ suggestions[key] = correct unless correct.empty?
31
34
  end
35
+ unknown = unknown.select {|arg| is_modifier?(arg)} if only_options
32
36
 
33
- unless unknown.empty?
34
- msg = "There are unknown options in your command line arguments:\n"
35
- msg += "#{unknown}\n"
36
- msg += "Please, remember that use case specific options should come after the use case in the command line.\n"
37
- msg += "Use 'ruby main.rb -org [-usecase] --help -options' for more information"
38
- raise msg
39
- end
37
+ return if unknown.empty?
38
+
39
+ suggestions_str = suggestions.slice(*unknown).map do |key, correct|
40
+ str = "Unknown option '#{key}'."
41
+ str_corr = correct.map {|key| "'#{key}'"}
42
+ str << " Did you mean: #{str_corr.join(', ')}" unless correct.empty?
43
+ str
44
+ end.join("\n * ")
45
+
46
+ msg = "\nThere are UNKNOWN OPTIONS in your command line arguments !!"
47
+ msg << "\n * #{suggestions_str}\n"
48
+ msg << "\nUse 'ruby main.rb -org [-usecase] --help -options' for more information"
49
+ msg << "\n - Please, remember that use case specific options "
50
+ msg << "should come after the use case in the command line.\n"
51
+ raise msg
40
52
  end
41
53
 
42
54
  # @return [Boolean] if `key` is in the command line.
@@ -46,28 +58,31 @@ module Eco
46
58
 
47
59
  # @return [Integer, nil] the position of `key` in the command line.
48
60
  def get_arg_index(key)
49
- return nil if !arg?(key)
61
+ return unless arg?(key)
62
+
50
63
  argv.index(key)
51
64
  end
52
65
 
53
66
  # @return [Boolean] if `key1` precedes `key2` in the command line.
54
- def arg_order?(key1, key2)
55
- return false unless (k1 = get_arg_index(key1)) && k2 = get_arg_index(key2)
56
- k1 < k2
67
+ def arg_order?(key_1, key_2)
68
+ k_1 = get_arg_index(key_1)
69
+ k_2 = get_arg_index(key_2)
70
+ return false unless k_1 && k_2
71
+
72
+ k_1 < k_2
57
73
  end
58
74
 
59
75
  # @return [String, Boolean] the argument value if `with_param` or a `Boolean` if not.
60
76
  def get_arg(key, with_param: false, valid: true)
61
77
  # track what a known option looks like
62
78
  known_argument(key, with_param: with_param)
63
- return nil unless index = get_arg_index(key)
64
- value = true
65
- if with_param
66
- value = argv[index + 1]
67
- #puts "modifier argument: #{value}"
68
- value = nil if valid && is_modifier?(value)
69
- end
70
- return value
79
+ return nil unless (index = get_arg_index(key))
80
+ return true unless with_param
81
+
82
+ value = argv[index + 1]
83
+ #puts "modifier argument: #{value}"
84
+ value = nil if valid && is_modifier?(value)
85
+ value
71
86
  end
72
87
 
73
88
  # @return [String] the filename.
@@ -13,6 +13,7 @@ module Eco
13
13
 
14
14
  def each(&block)
15
15
  return to_enum(:each) unless block
16
+
16
17
  @known.values.each(&block)
17
18
  end
18
19
 
@@ -22,7 +23,8 @@ module Eco
22
23
 
23
24
  def <<(arg)
24
25
  raise "Expected Argument. Given #{arg.class}" unless arg.is_a?(Argument)
25
- if karg = @known[arg.key]
26
+
27
+ if (karg = @known[arg.key])
26
28
  #puts "Found already existent option #{arg.key} (with_param: arg.with_param?)"
27
29
  karg.with_param! if arg.with_param?
28
30
  else
@@ -40,14 +42,33 @@ module Eco
40
42
  @known.keys
41
43
  end
42
44
 
43
- def unknown(exclude: [])
45
+ def unknown(exclude: [], all_available: nil)
44
46
  reduce(args.dup - exclude) do |not_known, arg|
45
47
  arg.args_slice(*not_known)
48
+ end.compact.tap do |list|
49
+ list.each do |key|
50
+ next unless block_given?
51
+
52
+ yield(key, unknown_suggestions(key, all_available: all_available))
53
+ end
46
54
  end
47
55
  end
48
56
 
57
+ def unknown_suggestions(str, all_available: nil)
58
+ return [] if str.nil?
59
+
60
+ str = str.to_s.strip
61
+ return [] if str.empty?
62
+
63
+ all_available ||= @known.keys
64
+ spell_checker = DidYouMean::SpellChecker.new(
65
+ dictionary: all_available
66
+ )
67
+ spell_checker.correct(str)
68
+ end
69
+
49
70
  def any_unknown?(exclude: [])
50
- unknown(exclude: exclude).length > 0
71
+ unknown(exclude: exclude).length.positive?
51
72
  end
52
73
 
53
74
  private
data/lib/eco/cli.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  module Eco
2
2
  class CLI
3
-
4
3
  def initialize
5
4
  @config = nil
6
5
  end
@@ -74,8 +74,9 @@ ASSETS.cli do |cli| # rubocop:disable Metrics/BlockLength
74
74
  end
75
75
  end
76
76
 
77
- wf.before(:launch_jobs) do
78
- SCR.stop_on_unknown!
77
+ wf.before(:launch_jobs) do |_wf_jobs, io|
78
+ available_args = cli.config.available_option_args
79
+ SCR.stop_on_unknown!(all_available: available_args)
79
80
  end
80
81
 
81
82
  wf.on(:launch_jobs) do
data/lib/eco/csv/split.rb CHANGED
@@ -5,10 +5,13 @@ module Eco
5
5
 
6
6
  attr_reader :filename
7
7
 
8
- def initialize(filename, max_rows:, **kargs)
9
- raise ArgumentError, "File '#{filename}' does not exist" unless ::File.exist?(filename)
8
+ def initialize(filename, max_rows:, start_at: nil, **kargs)
9
+ msg = "File '#{filename}' does not exist"
10
+ raise ArgumentError, msg unless ::File.exist?(filename)
11
+
10
12
  @filename = filename
11
13
  @max_rows = max_rows
14
+ @start_at = start_at
12
15
  @params = kargs
13
16
  init
14
17
  end
@@ -20,7 +23,7 @@ module Eco
20
23
  # - If `nil` it will create its own filename convention
21
24
  # @return [Array<String>] names of the generated files
22
25
  def call(&block)
23
- stream.for_each do |row, ridx|
26
+ stream.for_each(start_at_idx: start_at) do |row, ridx|
24
27
  copy_row(row, ridx, &block)
25
28
  end
26
29
  out_files
@@ -32,7 +35,7 @@ module Eco
32
35
  private
33
36
 
34
37
  attr_reader :params
35
- attr_reader :idx, :max_rows
38
+ attr_reader :idx, :max_rows, :start_at
36
39
  attr_reader :headers, :row_idx
37
40
 
38
41
  attr_accessor :exception
@@ -40,19 +43,29 @@ module Eco
40
43
  def copy_row(row, ridx, &block)
41
44
  @headers ||= row.headers
42
45
  @row_idx = ridx
43
- current_csv(ridx, &block) << row.fields
46
+
47
+ current_csv(ridx) do |csv, fidx, file_out|
48
+ included = true
49
+ included &&= !block || yield(row, ridx, fidx, file_out)
50
+ next unless included
51
+
52
+ csv << row.fields
53
+ end
44
54
  end
45
55
 
46
56
  def current_csv(ridx)
47
57
  if split?(ridx) || @csv.nil?
48
58
  puts "Split at row #{row_idx}"
49
59
  @csv&.close
50
- out_filename = generate_name(nidx = next_idx)
51
- out_filename = yield(nidx, out_filename) if block_given?
52
- @csv = ::CSV.open(out_filename, "w")
53
- @csv << headers
54
- out_files << out_filename
60
+
61
+ out_filename = generate_name(next_idx)
62
+ @csv = ::CSV.open(out_filename, "w")
63
+ @csv << headers
64
+ out_files << out_filename
55
65
  end
66
+
67
+ yield(@csv, idx, out_files.last) if block_given?
68
+
56
69
  @csv
57
70
  end
58
71
 
@@ -37,6 +37,7 @@ module Eco
37
37
  end
38
38
 
39
39
  def move_to_idx(start_at_idx)
40
+ start_at_idx ||= 0
40
41
  next_idx while (idx < start_at_idx) && (self.row = csv.shift)
41
42
  end
42
43
 
data/lib/eco/csv.rb CHANGED
@@ -18,16 +18,38 @@ module Eco
18
18
  parse(get_file_content(file, **params), **kargs)
19
19
  end
20
20
 
21
- # @yield [idx, file] a block to spot the filename
22
- # @yieldparam idx [Integer] the number of the file
21
+ # Splits the csv `filename` into `max_rows`
22
+ # @yield [row, ridx, fidx, file]
23
+ # @yieldparam row [Integer] the row
24
+ # @yieldparam ridx [Integer] the index of the row in the source file
25
+ # @yieldparam fidx [Integer] the number of the file
23
26
  # @yieldparam file [String] the default name of the file
24
- # @yieldreturn [String] the filename of the file `idx`.
25
- # - If `nil` it will create its own filename convention
27
+ # @yieldreturn [Bollean] whether the row should be included
26
28
  # @param filename [String] the orignal file
27
29
  # @param max_rows [Integer] number of rows per file
30
+ # @param start_at [Integer] row that sets the starting point.
31
+ # Leave empty for the full set of rows.
28
32
  # @see Eco::CSV::Split#call
29
- def split(filename, max_rows:, **kargs, &block)
30
- Eco::CSV::Split.new(filename, max_rows: max_rows, **kargs).call(&block)
33
+ def split(filename, max_rows:, start_at: nil, **kargs, &block)
34
+ Eco::CSV::Split.new(
35
+ filename,
36
+ max_rows: max_rows,
37
+ start_at: start_at,
38
+ **kargs
39
+ ).call(&block)
40
+ end
41
+
42
+ # @note it excludes headers by default
43
+ # @return [Integer] the number of rows of the file
44
+ def count(filename, start_at: nil, **kargs)
45
+ count = 0
46
+ streamer = Eco::CSV::Stream.new(filename, **kargs)
47
+ streamer.for_each(start_at_idx: start_at) do |row|
48
+ included = true
49
+ included = yield(row) if block_given?
50
+ count += 1 if included
51
+ end
52
+ count
31
53
  end
32
54
  end
33
55
  end
data/lib/eco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eco
2
- VERSION = '2.7.16'.freeze
2
+ VERSION = '2.7.17'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eco-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.16
4
+ version: 2.7.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Segura