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.
- checksums.yaml +4 -4
- data/.rubocop.yml +16 -3
- data/.tool-versions +1 -0
- data/Dockerfile +1 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +25 -6
- data/README.md +59 -81
- data/docs/README.md +33 -0
- data/docs/about.md +219 -0
- data/docs/builder/add.md +52 -0
- data/docs/builder/black_white_list.md +83 -0
- data/docs/builder/copy.md +48 -0
- data/docs/builder/explode.md +70 -0
- data/docs/builder/main.md +43 -0
- data/docs/builder/remove.md +46 -0
- data/docs/examples/examples.md +164 -0
- data/docs/export/main.md +37 -0
- data/docs/extract/main.md +89 -0
- data/docs/metadata/main.md +29 -0
- data/docs/query/main.md +45 -0
- data/docs/sources/main.md +36 -0
- data/docs/transform/main.md +53 -0
- data/docs/validate/main.md +42 -0
- data/exe/cure +12 -37
- data/exe/cure.old +59 -0
- data/lib/cure/builder/base_builder.rb +151 -0
- data/lib/cure/builder/candidate.rb +56 -0
- data/lib/cure/cli/command.rb +105 -0
- data/lib/cure/cli/generate_command.rb +54 -0
- data/lib/cure/cli/new_command.rb +52 -0
- data/lib/cure/cli/run_command.rb +19 -0
- data/lib/cure/cli/templates/README.md.erb +1 -0
- data/lib/cure/cli/templates/gemfile.erb +5 -0
- data/lib/cure/cli/templates/gitignore.erb +181 -0
- data/lib/cure/cli/templates/new_template.rb.erb +31 -0
- data/lib/cure/cli/templates/tool-versions.erb +1 -0
- data/lib/cure/config.rb +151 -13
- data/lib/cure/coordinator.rb +108 -0
- data/lib/cure/database.rb +191 -0
- data/lib/cure/dsl/builder.rb +26 -0
- data/lib/cure/dsl/exporters.rb +45 -0
- data/lib/cure/dsl/extraction.rb +60 -0
- data/lib/cure/dsl/metadata.rb +33 -0
- data/lib/cure/dsl/queries.rb +36 -0
- data/lib/cure/dsl/source_files.rb +36 -0
- data/lib/cure/dsl/template.rb +131 -0
- data/lib/cure/dsl/transformations.rb +95 -0
- data/lib/cure/dsl/validator.rb +22 -0
- data/lib/cure/export/base_processor.rb +194 -0
- data/lib/cure/export/manager.rb +24 -0
- data/lib/cure/extract/base_processor.rb +47 -0
- data/lib/cure/extract/csv_lookup.rb +43 -0
- data/lib/cure/extract/extractor.rb +80 -0
- data/lib/cure/extract/filter.rb +118 -0
- data/lib/cure/extract/named_range.rb +94 -0
- data/lib/cure/extract/named_range_processor.rb +128 -0
- data/lib/cure/extract/variable.rb +25 -0
- data/lib/cure/extract/variable_processor.rb +57 -0
- data/lib/cure/generator/base_generator.rb +61 -0
- data/lib/cure/generator/case_generator.rb +32 -0
- data/lib/cure/generator/character_generator.rb +41 -0
- data/lib/cure/generator/erb_generator.rb +21 -0
- data/lib/cure/generator/eval_generator.rb +34 -0
- data/lib/cure/generator/faker_generator.rb +31 -0
- data/lib/cure/generator/guid_generator.rb +21 -0
- data/lib/cure/generator/hex_generator.rb +21 -0
- data/lib/cure/generator/imports.rb +16 -0
- data/lib/cure/generator/number_generator.rb +21 -0
- data/lib/cure/generator/placeholder_generator.rb +26 -0
- data/lib/cure/generator/proc_generator.rb +21 -0
- data/lib/cure/generator/redact_generator.rb +22 -0
- data/lib/cure/generator/static_generator.rb +21 -0
- data/lib/cure/generator/variable_generator.rb +26 -0
- data/lib/cure/helpers/file_helpers.rb +50 -0
- data/lib/cure/helpers/object_helpers.rb +17 -0
- data/lib/cure/helpers/perf_helpers.rb +30 -0
- data/lib/cure/helpers/string.rb +54 -0
- data/lib/cure/launcher.rb +125 -0
- data/lib/cure/log.rb +10 -3
- data/lib/cure/planner.rb +136 -0
- data/lib/cure/strategy/append_strategy.rb +28 -0
- data/lib/cure/strategy/base_strategy.rb +98 -0
- data/lib/cure/strategy/contain_strategy.rb +51 -0
- data/lib/cure/strategy/end_with_strategy.rb +52 -0
- data/lib/cure/strategy/full_strategy.rb +28 -0
- data/lib/cure/strategy/history/history_cache.rb +82 -0
- data/lib/cure/strategy/imports.rb +12 -0
- data/lib/cure/strategy/match_strategy.rb +48 -0
- data/lib/cure/strategy/prepend_strategy.rb +28 -0
- data/lib/cure/strategy/regex_strategy.rb +55 -0
- data/lib/cure/strategy/split_strategy.rb +58 -0
- data/lib/cure/strategy/start_with_strategy.rb +53 -0
- data/lib/cure/transformation/candidate.rb +47 -36
- data/lib/cure/transformation/transform.rb +29 -71
- data/lib/cure/validator/base_rule.rb +78 -0
- data/lib/cure/validator/candidate.rb +54 -0
- data/lib/cure/validator/manager.rb +21 -0
- data/lib/cure/validators.rb +71 -0
- data/lib/cure/version.rb +1 -1
- data/lib/cure.rb +19 -6
- data/templates/dsl_example.rb +48 -0
- data/templates/empty_template.rb +31 -0
- metadata +161 -23
- data/lib/cure/csv_helpers.rb +0 -6
- data/lib/cure/export/exporter.rb +0 -49
- data/lib/cure/file_helpers.rb +0 -38
- data/lib/cure/generator/base.rb +0 -148
- data/lib/cure/main.rb +0 -63
- data/lib/cure/object_helpers.rb +0 -27
- data/lib/cure/strategy/base.rb +0 -223
- data/templates/aws_cur_template.json +0 -143
- 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
|