eco-helpers 2.7.16 → 2.7.17

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: 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