cure 0.1.1 → 0.4.0

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +16 -3
  3. data/.tool-versions +1 -0
  4. data/Dockerfile +1 -1
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +25 -6
  7. data/README.md +59 -81
  8. data/docs/README.md +33 -0
  9. data/docs/about.md +219 -0
  10. data/docs/builder/add.md +52 -0
  11. data/docs/builder/black_white_list.md +83 -0
  12. data/docs/builder/copy.md +48 -0
  13. data/docs/builder/explode.md +70 -0
  14. data/docs/builder/main.md +43 -0
  15. data/docs/builder/remove.md +46 -0
  16. data/docs/examples/examples.md +164 -0
  17. data/docs/export/main.md +37 -0
  18. data/docs/extract/main.md +89 -0
  19. data/docs/metadata/main.md +29 -0
  20. data/docs/query/main.md +45 -0
  21. data/docs/sources/main.md +36 -0
  22. data/docs/transform/main.md +53 -0
  23. data/docs/validate/main.md +42 -0
  24. data/exe/cure +12 -37
  25. data/exe/cure.old +59 -0
  26. data/lib/cure/builder/base_builder.rb +151 -0
  27. data/lib/cure/builder/candidate.rb +56 -0
  28. data/lib/cure/cli/command.rb +105 -0
  29. data/lib/cure/cli/generate_command.rb +54 -0
  30. data/lib/cure/cli/new_command.rb +52 -0
  31. data/lib/cure/cli/run_command.rb +19 -0
  32. data/lib/cure/cli/templates/README.md.erb +1 -0
  33. data/lib/cure/cli/templates/gemfile.erb +5 -0
  34. data/lib/cure/cli/templates/gitignore.erb +181 -0
  35. data/lib/cure/cli/templates/new_template.rb.erb +31 -0
  36. data/lib/cure/cli/templates/tool-versions.erb +1 -0
  37. data/lib/cure/config.rb +151 -13
  38. data/lib/cure/coordinator.rb +108 -0
  39. data/lib/cure/database.rb +191 -0
  40. data/lib/cure/dsl/builder.rb +26 -0
  41. data/lib/cure/dsl/exporters.rb +45 -0
  42. data/lib/cure/dsl/extraction.rb +60 -0
  43. data/lib/cure/dsl/metadata.rb +33 -0
  44. data/lib/cure/dsl/queries.rb +36 -0
  45. data/lib/cure/dsl/source_files.rb +36 -0
  46. data/lib/cure/dsl/template.rb +131 -0
  47. data/lib/cure/dsl/transformations.rb +95 -0
  48. data/lib/cure/dsl/validator.rb +22 -0
  49. data/lib/cure/export/base_processor.rb +194 -0
  50. data/lib/cure/export/manager.rb +24 -0
  51. data/lib/cure/extract/base_processor.rb +47 -0
  52. data/lib/cure/extract/csv_lookup.rb +43 -0
  53. data/lib/cure/extract/extractor.rb +80 -0
  54. data/lib/cure/extract/filter.rb +118 -0
  55. data/lib/cure/extract/named_range.rb +94 -0
  56. data/lib/cure/extract/named_range_processor.rb +128 -0
  57. data/lib/cure/extract/variable.rb +25 -0
  58. data/lib/cure/extract/variable_processor.rb +57 -0
  59. data/lib/cure/generator/base_generator.rb +61 -0
  60. data/lib/cure/generator/case_generator.rb +32 -0
  61. data/lib/cure/generator/character_generator.rb +41 -0
  62. data/lib/cure/generator/erb_generator.rb +21 -0
  63. data/lib/cure/generator/eval_generator.rb +34 -0
  64. data/lib/cure/generator/faker_generator.rb +31 -0
  65. data/lib/cure/generator/guid_generator.rb +21 -0
  66. data/lib/cure/generator/hex_generator.rb +21 -0
  67. data/lib/cure/generator/imports.rb +16 -0
  68. data/lib/cure/generator/number_generator.rb +21 -0
  69. data/lib/cure/generator/placeholder_generator.rb +26 -0
  70. data/lib/cure/generator/proc_generator.rb +21 -0
  71. data/lib/cure/generator/redact_generator.rb +22 -0
  72. data/lib/cure/generator/static_generator.rb +21 -0
  73. data/lib/cure/generator/variable_generator.rb +26 -0
  74. data/lib/cure/helpers/file_helpers.rb +50 -0
  75. data/lib/cure/helpers/object_helpers.rb +17 -0
  76. data/lib/cure/helpers/perf_helpers.rb +30 -0
  77. data/lib/cure/helpers/string.rb +54 -0
  78. data/lib/cure/launcher.rb +125 -0
  79. data/lib/cure/log.rb +10 -3
  80. data/lib/cure/planner.rb +136 -0
  81. data/lib/cure/strategy/append_strategy.rb +28 -0
  82. data/lib/cure/strategy/base_strategy.rb +98 -0
  83. data/lib/cure/strategy/contain_strategy.rb +51 -0
  84. data/lib/cure/strategy/end_with_strategy.rb +52 -0
  85. data/lib/cure/strategy/full_strategy.rb +28 -0
  86. data/lib/cure/strategy/history/history_cache.rb +82 -0
  87. data/lib/cure/strategy/imports.rb +12 -0
  88. data/lib/cure/strategy/match_strategy.rb +48 -0
  89. data/lib/cure/strategy/prepend_strategy.rb +28 -0
  90. data/lib/cure/strategy/regex_strategy.rb +55 -0
  91. data/lib/cure/strategy/split_strategy.rb +58 -0
  92. data/lib/cure/strategy/start_with_strategy.rb +53 -0
  93. data/lib/cure/transformation/candidate.rb +47 -36
  94. data/lib/cure/transformation/transform.rb +29 -71
  95. data/lib/cure/validator/base_rule.rb +78 -0
  96. data/lib/cure/validator/candidate.rb +54 -0
  97. data/lib/cure/validator/manager.rb +21 -0
  98. data/lib/cure/validators.rb +71 -0
  99. data/lib/cure/version.rb +1 -1
  100. data/lib/cure.rb +19 -6
  101. data/templates/dsl_example.rb +48 -0
  102. data/templates/empty_template.rb +31 -0
  103. metadata +161 -23
  104. data/lib/cure/csv_helpers.rb +0 -6
  105. data/lib/cure/export/exporter.rb +0 -49
  106. data/lib/cure/file_helpers.rb +0 -38
  107. data/lib/cure/generator/base.rb +0 -148
  108. data/lib/cure/main.rb +0 -63
  109. data/lib/cure/object_helpers.rb +0 -27
  110. data/lib/cure/strategy/base.rb +0 -223
  111. data/templates/aws_cur_template.json +0 -143
  112. data/templates/example_template.json +0 -38
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cure/builder/base_builder"
4
+ require "cure/builder/candidate"
5
+
6
+ module Cure
7
+ module Dsl
8
+ class Builder
9
+
10
+ attr_reader :candidates
11
+
12
+ def initialize
13
+ @candidates = []
14
+ end
15
+
16
+ # @param [String,nil] column
17
+ # @param [String] named_range
18
+ # @param [Proc] block
19
+ def candidate(column: nil, named_range: "_default", &block)
20
+ candidate = Cure::Builder::Candidate.new(column, named_range)
21
+ @candidates << candidate
22
+ candidate.instance_exec(&block)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cure/export/base_processor"
4
+ require "cure/extract/named_range"
5
+
6
+ module Cure
7
+ module Dsl
8
+ class Exporters
9
+
10
+ attr_reader :processors
11
+
12
+ def initialize
13
+ @processors = []
14
+ end
15
+
16
+ # @param [String,Symbol] _method_name
17
+ # @param [TrueClass,FalseClass] _include_private
18
+ # @return [TrueClass]
19
+ def respond_to_missing?(_method_name, _include_private=false)
20
+ true
21
+ end
22
+
23
+ # @param [Symbol] method_name
24
+ # @return [Cure::Export::BaseBuilder]
25
+ def method_missing(method_name, **args)
26
+ klass_name = "Cure::Export::#{method_name.to_s.split("_").map(&:capitalize).join("")}Processor"
27
+ raise "#{method_name} is not valid" unless class_exists?(klass_name)
28
+
29
+ @processors << Kernel.const_get(klass_name).new(
30
+ args[:named_range] || "_default",
31
+ args || {}
32
+ )
33
+ end
34
+
35
+ # @param [String] klass_name
36
+ # @return [TrueClass,FalseClass]
37
+ def class_exists?(klass_name)
38
+ klass = Module.const_get(klass_name)
39
+ klass.is_a?(Class)
40
+ rescue NameError
41
+ false
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cure/extract/named_range"
4
+ require "cure/extract/variable"
5
+
6
+ module Cure
7
+ module Dsl
8
+ class Extraction
9
+
10
+ attr_reader :named_ranges, :variables, :sample_rows
11
+
12
+ def initialize
13
+ @named_ranges = []
14
+ @variables = []
15
+ end
16
+
17
+ def named_range(name:, at: -1, headers: nil, ref_name: nil, placeholder: false, &block)
18
+ named_range = Extract::NamedRange.new(name, at,
19
+ headers: headers,
20
+ ref_name: ref_name,
21
+ placeholder: placeholder
22
+ )
23
+
24
+ if block_given?
25
+ named_range.filter.instance_eval(&block)
26
+ end
27
+
28
+ @named_ranges << named_range
29
+ end
30
+
31
+ def variable(name:, at:, ref_name: nil)
32
+ @variables << Cure::Extract::Variable.new(name, at, ref_name: ref_name)
33
+ end
34
+
35
+ def sample(rows: nil)
36
+ @sample_rows = rows
37
+ end
38
+
39
+ # @param [String] ref_name
40
+ # @return [Array]
41
+ def required_named_ranges(ref_name: "_default")
42
+ # This now needs to take support multiple files. We don't want named ranges
43
+ # for different files
44
+ return @named_ranges if ref_name == "default"
45
+
46
+ @named_ranges.select { |nr| nr.ref_name == ref_name && nr.placeholder == false }
47
+ end
48
+
49
+ # @param [String] ref_name
50
+ # @return [Array]
51
+ def required_variables(ref_name: "_default")
52
+ # This now needs to take support multiple files. We don't want named ranges
53
+ # for different files
54
+ return @variables if ref_name == "_default"
55
+
56
+ @variables.select { |v| v.ref_name == ref_name }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cure
4
+ module Dsl
5
+ class Metadata
6
+
7
+ attr_reader :_name, :_version, :_comments, :_additional
8
+
9
+ def initialize
10
+ @_name = nil
11
+ @_version = nil
12
+ @_comments = nil
13
+ @_additional = {}
14
+ end
15
+
16
+ def name(name)
17
+ @_name = name
18
+ end
19
+
20
+ def version(version)
21
+ @_version = version
22
+ end
23
+
24
+ def comments(comments)
25
+ @_comments = comments
26
+ end
27
+
28
+ def additional(data: {})
29
+ @_additional = data
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cure/builder/base_builder"
4
+ require "cure/builder/candidate"
5
+
6
+ module Cure
7
+ module Dsl
8
+ class Queries
9
+
10
+ attr_reader :candidates
11
+
12
+ def initialize
13
+ @candidates = []
14
+ end
15
+
16
+ def with(query:, named_range: "_default")
17
+ candidate = Query.new(named_range.to_sym, query)
18
+ @candidates << candidate
19
+ end
20
+
21
+ def find(named_range)
22
+ @candidates.find { |candidate| candidate.named_range.to_sym == named_range.to_sym }
23
+ end
24
+
25
+ class Query
26
+ attr_reader :named_range, :query
27
+
28
+ def initialize(named_range, query)
29
+ @named_range = named_range
30
+ @query = query
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cure/builder/base_builder"
4
+ require "cure/builder/candidate"
5
+
6
+ module Cure
7
+ module Dsl
8
+ class SourceFiles
9
+
10
+ attr_reader :candidates
11
+
12
+ def initialize
13
+ @candidates = []
14
+ end
15
+
16
+ def csv(type, value, ref_name: nil)
17
+ candidate = SourceFile.new(type, value, ref_name)
18
+ @candidates << candidate
19
+ end
20
+
21
+ def has_candidates?
22
+ @candidates.length.positive?
23
+ end
24
+
25
+ class SourceFile
26
+ attr_accessor :type, :value, :ref_name
27
+
28
+ def initialize(type, value, ref_name)
29
+ @type = type
30
+ @value = value
31
+ @ref_name = ref_name
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cure/log"
4
+ require "cure/dsl/extraction"
5
+ require "cure/dsl/builder"
6
+ require "cure/dsl/validator"
7
+ require "cure/dsl/transformations"
8
+ require "cure/dsl/exporters"
9
+ require "cure/dsl/queries"
10
+ require "cure/dsl/metadata"
11
+ require "cure/dsl/source_files"
12
+
13
+ module Cure
14
+ module Dsl
15
+ class DslHandler
16
+ include Log
17
+ def self.init(&block)
18
+ DslHandler.new(block)
19
+ end
20
+
21
+ def self.init_from_content(dsl_source, identifier, line_number=1)
22
+ proc = Binding.get.eval(<<-SOURCE, identifier, line_number)
23
+ Proc.new do
24
+ #{dsl_source}
25
+ end
26
+ SOURCE
27
+
28
+ DslHandler.new(proc)
29
+ end
30
+
31
+ def initialize(proc)
32
+ @proc = proc
33
+ end
34
+
35
+ def generate(instance_variables={})
36
+ dsl = Template.new
37
+ instance_variables.each do |name, value|
38
+ dsl.instance_variable_set("@#{name}", value)
39
+ end
40
+
41
+ dsl.instance_eval(&@proc)
42
+ dsl
43
+
44
+ rescue StandardError => e
45
+ log_error "Error parsing DSL: #{e.message}"
46
+
47
+ # Raise specific error in future.
48
+ raise e
49
+ end
50
+ end
51
+
52
+ module Binding
53
+ # @return [Object] Empty object for binding purposes
54
+ def self.get
55
+ binding
56
+ end
57
+ end
58
+
59
+ class Template
60
+
61
+ # @return [Dsl::Extraction]
62
+ attr_reader :extraction
63
+
64
+ # @return [Dsl::Builder]
65
+ attr_reader :builder
66
+
67
+ # @return [Dsl::Validator]
68
+ attr_reader :validator
69
+
70
+ # @return [Dsl::Transformations]
71
+ attr_reader :transformations
72
+
73
+ # @return [Dsl::Exporters]
74
+ attr_reader :exporters
75
+
76
+ # @return [Dsl::Queries]
77
+ attr_reader :queries
78
+
79
+ # @return [Dsl::Metadata]
80
+ attr_reader :meta_data
81
+
82
+ # @return [Dsl::SourceFiles]
83
+ attr_reader :source_files
84
+
85
+ def initialize
86
+ @source_files = SourceFiles.new
87
+ @extraction = Extraction.new
88
+ @builder = Builder.new
89
+ @validator = Validator.new
90
+ @transformations = Transformations.new
91
+ @exporters = Exporters.new
92
+ @queries = Queries.new
93
+ @meta_data = Metadata.new
94
+ end
95
+
96
+ private
97
+
98
+ def sources(&block)
99
+ @source_files.instance_exec(&block)
100
+ end
101
+
102
+ def extract(&block)
103
+ @extraction.instance_exec(&block)
104
+ end
105
+
106
+ def query(&block)
107
+ @queries.instance_exec(&block)
108
+ end
109
+
110
+ def build(&block)
111
+ @builder.instance_exec(&block)
112
+ end
113
+
114
+ def validate(&block)
115
+ @validator.instance_exec(&block)
116
+ end
117
+
118
+ def transform(&block)
119
+ @transformations.instance_exec(&block)
120
+ end
121
+
122
+ def metadata(&block)
123
+ @meta_data.instance_exec(&block)
124
+ end
125
+
126
+ def export(&block)
127
+ @exporters.instance_exec(&block)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cure/strategy/imports"
4
+ require "cure/generator/imports"
5
+ require "cure/transformation/candidate"
6
+
7
+ module Cure
8
+ module Dsl
9
+ class Transformations
10
+
11
+ attr_reader :candidates, :placeholders
12
+
13
+ def initialize
14
+ @candidates = []
15
+ @placeholders = []
16
+ end
17
+
18
+ def candidate(column:, named_range: "_default", options: {}, &block)
19
+ candidate = Candidate.new(column, named_range: named_range)
20
+ candidate.instance_exec(&block)
21
+
22
+ @candidates << Transformation::Candidate
23
+ .new(candidate.column, named_range: candidate.named_range, options: options)
24
+ .with_translations(candidate.translations)
25
+ .with_no_match_translation(candidate.no_match_translation)
26
+ end
27
+
28
+ def place_holders(hash)
29
+ @placeholders = hash
30
+ end
31
+
32
+ class Candidate
33
+
34
+ attr_reader :column, :named_range, :ref, :translations, :no_match_translation
35
+
36
+ def initialize(column, named_range:, ref: "_default")
37
+ @column = column
38
+ @named_range = named_range
39
+ @ref = ref
40
+
41
+ @translations = []
42
+ @no_match_translation = nil
43
+ end
44
+
45
+ def with_translation(&block)
46
+ translation = Translation.new
47
+ translation.instance_exec(&block)
48
+
49
+ @translations << Transformation::Translation.new(
50
+ translation.strategy,
51
+ translation.generator
52
+ )
53
+ end
54
+
55
+ def if_no_match(&block)
56
+ no_match_translation = Translation.new
57
+ no_match_translation.instance_exec(&block)
58
+
59
+ @no_match_translation = Transformation::Translation.new(
60
+ no_match_translation.strategy,
61
+ no_match_translation.generator
62
+ )
63
+ end
64
+ end
65
+
66
+ class Translation
67
+
68
+ attr_reader :strategy, :generator
69
+
70
+ def replace(name, **options)
71
+ klass_name = "Cure::Strategy::#{name.to_s.capitalize}Strategy"
72
+ raise "#{name} is not valid" unless class_exists?(klass_name)
73
+
74
+ @strategy = Kernel.const_get(klass_name).new(options)
75
+ self
76
+ end
77
+
78
+ def with(name, **options)
79
+ klass_name = "Cure::Generator::#{name.to_s.capitalize}Generator"
80
+ raise "#{name} is not valid" unless class_exists?(klass_name)
81
+
82
+ @generator = Kernel.const_get(klass_name).new(options)
83
+ self
84
+ end
85
+
86
+ def class_exists?(klass_name)
87
+ klass = Module.const_get(klass_name)
88
+ klass.is_a?(Class)
89
+ rescue NameError
90
+ false
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cure/validator/candidate"
4
+
5
+ module Cure
6
+ module Dsl
7
+ class Validator
8
+
9
+ attr_reader :candidates
10
+
11
+ def initialize
12
+ @candidates = []
13
+ end
14
+
15
+ def candidate(column: nil, named_range: "_default", options: {}, &block)
16
+ candidate = Cure::Validator::Candidate.new(column, named_range, options)
17
+ @candidates << candidate
18
+ candidate.instance_exec(&block)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+ require "cure/log"
5
+ require "cure/helpers/file_helpers"
6
+
7
+ module Cure
8
+ module Export
9
+ class BaseProcessor
10
+ include Log
11
+
12
+ attr_reader :named_range
13
+
14
+ def initialize(named_range, opts)
15
+ @named_range = named_range
16
+ @opts = opts
17
+ @limit_rows = opts.fetch(:limit_rows, nil)
18
+
19
+ @processed = 0
20
+ end
21
+
22
+ # @param [Hash]
23
+ def process_row(row)
24
+ process(row) unless @limit_rows && @limit_rows <= @processed
25
+
26
+ @processed += 1
27
+ end
28
+
29
+ # @param [Hash]
30
+ def process(_row)
31
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
32
+ end
33
+
34
+ def setup
35
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
36
+ end
37
+
38
+ def cleanup
39
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
40
+ end
41
+ end
42
+
43
+ require "terminal-table"
44
+
45
+ class TerminalProcessor < BaseProcessor
46
+
47
+ attr_reader :table, :limit_rows, :processed
48
+
49
+ def process(row)
50
+ @table.headings = row.keys if @processed.zero?
51
+ @table.add_row(row.values)
52
+ end
53
+
54
+ def setup
55
+ # Markdown mode
56
+ Terminal::Table::Style.defaults = {
57
+ border_top: false,
58
+ border_bottom: false,
59
+ border_x: "-",
60
+ border_y: "|",
61
+ border_i: "|"
62
+ }
63
+
64
+ log_info "Exporting [#{@named_range}] to terminal."
65
+ @table = Terminal::Table.new(title: @opts[:title] || "<No Title Set>")
66
+ end
67
+
68
+ def cleanup
69
+ puts @table
70
+ end
71
+ end
72
+
73
+ class CsvProcessor < BaseProcessor
74
+ include Helpers::FileHelpers
75
+
76
+ attr_reader :csv_file
77
+
78
+ def process(row)
79
+ @csv_file.write(row.keys.to_csv) if @processed.zero?
80
+ @csv_file.write(row.values.to_csv)
81
+ end
82
+
83
+ def setup
84
+ log_info "Exporting [#{@named_range}] to CSV..."
85
+
86
+ output_dir = @opts[:directory]
87
+ file_name = @opts[:file_name]
88
+
89
+ log_info("Exporting file to [#{output_dir}/#{file_name}]")
90
+ # file_name = "#{file_name}-#{Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S%-z")}"
91
+
92
+ path = "#{output_dir}/#{file_name}"
93
+
94
+ # clean_dir(output_dir)
95
+
96
+ dir = File.dirname(path)
97
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
98
+
99
+ path = "#{path}.csv"
100
+ @csv_file = File.open(path, "w")
101
+ @processed = 0
102
+ end
103
+
104
+ def cleanup
105
+ ensure
106
+ log_info File.basename(@csv_file)
107
+ @csv_file.close
108
+ end
109
+ end
110
+
111
+ class ChunkCsvProcessor < BaseProcessor
112
+ include Helpers::FileHelpers
113
+
114
+ attr_reader :current_csv_file,
115
+ :file_name_prefix,
116
+ :directory,
117
+ :chunk_size,
118
+ :include_headers,
119
+ :row_count
120
+
121
+ def process(row)
122
+ chunked_file_handler do |csv_file|
123
+ if @processed.zero? || (@processed % @chunk_size).zero? || (@processed % @chunk_size).zero?
124
+ csv_file.write(row.keys.to_csv)
125
+ end
126
+
127
+ csv_file.write(row.values.to_csv)
128
+ end
129
+ end
130
+
131
+ def setup
132
+ log_info "Exporting [#{@named_range}] to CSV..."
133
+
134
+ extract_opts
135
+
136
+ log_info("Exporting file to [#{@output_dir}/#{@file_name_prefix}]")
137
+
138
+ clean_dir(@output_dir)
139
+
140
+ dir = File.dirname("#{@output_dir}/#{@file_name_prefix}")
141
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
142
+
143
+ @processed = 0
144
+ @current_chunk = 0
145
+ end
146
+
147
+ def cleanup
148
+ ensure
149
+ @current_csv_file.close
150
+ end
151
+
152
+ def extract_opts
153
+ # TODO: Add offset? pick a slice?
154
+ @output_dir = @opts[:directory]
155
+ @file_name_prefix = @opts[:file_name_prefix]
156
+ @directory = @opts[:directory]
157
+ @chunk_size = @opts[:chunk_size]
158
+ @include_headers = @opts.fetch(:include_headers, true)
159
+ end
160
+
161
+ def chunked_file_handler(&block)
162
+ raise "No block" unless block
163
+
164
+ if @processed.zero? || (@processed % @chunk_size).zero?
165
+ @current_csv_file&.close
166
+
167
+ @current_chunk += 1
168
+ log_info "Writing file to #{current_file_path}"
169
+ @current_csv_file = File.open(current_file_path, "w")
170
+ end
171
+
172
+ yield @current_csv_file
173
+ end
174
+
175
+ def current_file_path
176
+ "#{@output_dir}/#{@current_chunk}-#{@file_name_prefix}.csv"
177
+ end
178
+ end
179
+
180
+ class YieldRowProcessor < BaseProcessor
181
+ attr_reader :proc
182
+
183
+ def process_row(row)
184
+ @proc.call(row)
185
+ end
186
+
187
+ def setup
188
+ @proc = @opts.fetch(:proc)
189
+ end
190
+
191
+ def cleanup; end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cure
4
+ module Export
5
+ class Manager
6
+
7
+ # @param [Array<Cure::Export::BaseProcessor>] candidates
8
+ attr_reader :processors
9
+
10
+ def initialize(named_range, processors)
11
+ @named_range = named_range
12
+ @processors = processors
13
+ end
14
+
15
+ def with_processors
16
+ @processors.each(&:setup)
17
+
18
+ yield @processors
19
+
20
+ @processors.each(&:cleanup)
21
+ end
22
+ end
23
+ end
24
+ end