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,36 @@
|
|
1
|
+
**Sources** > Extract > Validate > Build > **Query** > Transform > Export
|
2
|
+
|
3
|
+
Sources
|
4
|
+
=======
|
5
|
+
|
6
|
+
### About
|
7
|
+
|
8
|
+
The sources step allows you to define the files that will be extracted. You can supply it in the template,
|
9
|
+
or you can provide it when you are initializing `Cure::Launcher`.
|
10
|
+
|
11
|
+
As Cure templates are just Ruby, you can define the files dynamically, and provide them to the template DSL.
|
12
|
+
|
13
|
+
---
|
14
|
+
|
15
|
+
**When you should use this**: You want to load a CSV file, but you don't want to do it at initialize.
|
16
|
+
|
17
|
+
See below an example configuration block:
|
18
|
+
|
19
|
+
### Example
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
sources do
|
23
|
+
csv :pathname, Pathname.new("loc/to/my/file_1.csv"), ref_name: "file_1"
|
24
|
+
csv :pathname, Pathname.new("loc/to/my/file_2.csv"), ref_name: "file_2"
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
or using Ruby (this is a contrived example)
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
[Pathname.new("loc/to/my/file_1.csv"), Pathname.new("loc/to/my/file_2.csv")].each_with_index do |file_path, idx|
|
32
|
+
sources do
|
33
|
+
csv :pathname, file_path, ref_name: "file_#{idx}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
@@ -0,0 +1,53 @@
|
|
1
|
+
Source > Extract > Validate > Build > Query > **Transform** > Export
|
2
|
+
|
3
|
+
Transform
|
4
|
+
=======
|
5
|
+
|
6
|
+
### About
|
7
|
+
|
8
|
+
The transform step is the arguably the most powerful step in the chain. It is used when you want to *change* a value.
|
9
|
+
The process can be undertaken in multiple steps, performing transforms as you go. It is quite sophisticated, and does
|
10
|
+
not require homologous data for each value.
|
11
|
+
|
12
|
+
**When you should use this**: You have a value in a row, and you wish to change it.
|
13
|
+
|
14
|
+
Transform is made up of many translations. There are two main parts that make up a translations step, the
|
15
|
+
**strategy** and the **generator** which are defined on a specific column.
|
16
|
+
|
17
|
+
**Strategy** is the means in which a value will be transformed. It is responsible for how and what part of the value
|
18
|
+
will be transformed. The strategy will first look to extract the value that you wish to transform. This can either
|
19
|
+
be simple, like a full replacement, or complex using regex. Once it has the value extracted, it will attempt to find
|
20
|
+
any previously translated mapping before running it through the generator step.
|
21
|
+
|
22
|
+
**Generator** is the process of creating that transformed value. It could be as simple as replace all characters with
|
23
|
+
the character "x", fill it with Faker data, set it to a random guid, modify the date format, inject a value from
|
24
|
+
elsewhere on the spreadsheet (see [Build](../builder/main.md)).
|
25
|
+
|
26
|
+
Once this value has been generated, it is stored against the existing value so any other translation will get the same.
|
27
|
+
This is particularly useful if you want to keep data integrity. For example, if you had two columns, one was `account
|
28
|
+
number`, and the other was `reference_id` (which was constructed by the format `account_number/identifier`). Once you
|
29
|
+
had set a replacement value for the account number (`abcde => defgh`), you wouldn't want a different value for the
|
30
|
+
account number part of the `reference_id` column.
|
31
|
+
|
32
|
+
```
|
33
|
+
+----------------+--------------+
|
34
|
+
| account_number | reference_id |
|
35
|
+
+----------------+--------------+
|
36
|
+
| abcde | abcde/123456 |
|
37
|
+
+----------------+--------------+
|
38
|
+
```
|
39
|
+
changes to
|
40
|
+
```
|
41
|
+
+----------------+--------------+
|
42
|
+
| account_number | reference_id |
|
43
|
+
+----------------+--------------+
|
44
|
+
| defgh | defgh/123456 |
|
45
|
+
+----------------+--------------+
|
46
|
+
```
|
47
|
+
|
48
|
+
---
|
49
|
+
|
50
|
+
See below an example configuration block:
|
51
|
+
|
52
|
+
### Example
|
53
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
Source > Extract > **Validate** > Build > Query > Transform > Export
|
2
|
+
|
3
|
+
Validate
|
4
|
+
=======
|
5
|
+
|
6
|
+
### About
|
7
|
+
|
8
|
+
The validator step checks that data in the spreadsheet is what is expected. This may involve checking
|
9
|
+
that data is of a certain format, and if it isn't, it can either fail or warn.
|
10
|
+
|
11
|
+
---
|
12
|
+
|
13
|
+
**When you should use this**: You have a spreadsheet that has data that needs to be in a certain format
|
14
|
+
before transforming or exporting.
|
15
|
+
|
16
|
+
See below an example configuration block:
|
17
|
+
|
18
|
+
### Example
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
validate do
|
22
|
+
candidate column: "new_column", named_range: "section_1", options: { fail_on_error: false } do
|
23
|
+
with_rule :not_null
|
24
|
+
with_rule :length, { min: 0, max: 5 }
|
25
|
+
with_rule :custom, { proc: Proc.new { |x| x.size >= 0 && x.size <= 5 } } # Proc version of above.
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
Original input:
|
31
|
+
```
|
32
|
+
+------------+
|
33
|
+
| new_column |
|
34
|
+
+------------+
|
35
|
+
| test | <- Is valid
|
36
|
+
| test value | <- Is invalid (too long)
|
37
|
+
| | <- Is invalid (null)
|
38
|
+
+------------+
|
39
|
+
```
|
40
|
+
|
41
|
+
As `fail_on_error` is set to false, this would warn that 2 values are invalid.
|
42
|
+
|
data/exe/cure
CHANGED
@@ -1,42 +1,17 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require "cure"
|
5
|
-
require "
|
3
|
+
require "cure/cli/command"
|
4
|
+
require "cure/cli/new_command"
|
5
|
+
require "cure/cli/run_command"
|
6
|
+
require "cure/cli/generate_command"
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
conf = {}
|
14
|
-
|
15
|
-
OptionParser.new do |opts|
|
16
|
-
opts.banner = "Usage: cure_cli [options]"
|
17
|
-
|
18
|
-
opts.on("-t", "--template_file=template_file", "Template definition file") do |t|
|
19
|
-
conf[:template_file_location] = t
|
20
|
-
end
|
21
|
-
|
22
|
-
opts.on("-s", "--source_file=source_file", "Source file") do |s|
|
23
|
-
conf[:source_file_location] = s
|
24
|
-
end
|
25
|
-
|
26
|
-
opts.on("-o", "--output_dir=output_dir", "Output directory") do |o|
|
27
|
-
conf[:output_dir] = o
|
28
|
-
end
|
29
|
-
|
30
|
-
opts.on("-h", "--help", "Prints this help") do
|
31
|
-
puts opts
|
32
|
-
exit
|
33
|
-
end
|
34
|
-
end.parse!
|
35
|
-
|
36
|
-
log_info "Config loaded successfully, initialising environment ..."
|
37
|
-
main = Cure::Main.init(conf[:template_file_location], conf[:source_file_location], conf[:output_dir])
|
38
|
-
|
39
|
-
log_info "... set up complete. Beginning process"
|
40
|
-
main.process
|
8
|
+
short_hand = {
|
9
|
+
"n" => "new",
|
10
|
+
"r" => "run",
|
11
|
+
"g" => "generate"
|
12
|
+
}
|
41
13
|
|
14
|
+
command = ARGV.shift
|
15
|
+
command = short_hand[command] || command
|
42
16
|
|
17
|
+
Cure::Cli::Command.invoke(command, ARGV)
|
data/exe/cure.old
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
# rubocop:disable Style/MixinUsage
|
6
|
+
|
7
|
+
require "cure"
|
8
|
+
require "optparse"
|
9
|
+
|
10
|
+
include Cure::Log
|
11
|
+
|
12
|
+
class CureConfig
|
13
|
+
include Cure::Configuration
|
14
|
+
end
|
15
|
+
|
16
|
+
conf = {}
|
17
|
+
|
18
|
+
opts_parser = OptionParser.new do |opts|
|
19
|
+
opts.banner = "Usage: cure [options]"
|
20
|
+
|
21
|
+
opts.on("-t", "--template_file=template_file", "Template definition file") do |t|
|
22
|
+
conf[:template_file_location] = t
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("-s", "--source_file=source_file", "Source file") do |s|
|
26
|
+
conf[:source_file_location] = s
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on("-l", "--list_plugins=list_plugins", "List plugins") do
|
30
|
+
ObjectSpace.each_object(Class) do |klass|
|
31
|
+
log_info "Generator: #{klass.name}" if klass.ancestors.include?(Cure::Generator::BaseGenerator)
|
32
|
+
log_info "Strategy: #{klass.name}" if klass.ancestors.include?(Cure::Strategy::BaseStrategy)
|
33
|
+
end
|
34
|
+
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-h", "--help", "Prints this help") do
|
39
|
+
puts opts
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
opts_parser.parse!
|
45
|
+
|
46
|
+
if conf.empty?
|
47
|
+
puts opts_parser
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
log_info "Config loaded successfully, initialising environment ..."
|
52
|
+
main = Cure.init_from_file(conf[:template_file_location])
|
53
|
+
.with_csv_file(:pathname, Pathname.new(conf[:source_file_location]))
|
54
|
+
.setup
|
55
|
+
|
56
|
+
log_info "... set up complete. Beginning process"
|
57
|
+
main.run_export
|
58
|
+
|
59
|
+
# rubocop:enable Style/MixinUsage
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cure/log"
|
4
|
+
require "cure/config"
|
5
|
+
require "cure/database"
|
6
|
+
require "cure/helpers/file_helpers"
|
7
|
+
require "cure/extract/extractor"
|
8
|
+
|
9
|
+
require "rcsv"
|
10
|
+
|
11
|
+
module Cure
|
12
|
+
module Builder
|
13
|
+
include Database
|
14
|
+
|
15
|
+
class BaseBuilder
|
16
|
+
include Database
|
17
|
+
|
18
|
+
# @param [String] named_range
|
19
|
+
# @param [String] column
|
20
|
+
# @param [Hash] opts
|
21
|
+
def initialize(named_range, column, opts)
|
22
|
+
@named_range = named_range
|
23
|
+
@column = column
|
24
|
+
@opts = opts
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [void]
|
28
|
+
def call
|
29
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String (frozen)]
|
33
|
+
def to_s
|
34
|
+
"Base Builder"
|
35
|
+
end
|
36
|
+
|
37
|
+
# @yield [DatabaseService]
|
38
|
+
# @raise StandardError
|
39
|
+
def with_database(&block)
|
40
|
+
raise "Missing block" unless block
|
41
|
+
|
42
|
+
yield database_service
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class AddBuilder < BaseBuilder
|
47
|
+
|
48
|
+
# @return [void]
|
49
|
+
def call
|
50
|
+
with_database do |db_svc|
|
51
|
+
db_svc.add_column(@named_range.to_sym, @column.to_sym, default: @opts.fetch(:default_value, nil))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [String (frozen)]
|
56
|
+
def to_s
|
57
|
+
"Add Builder"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class RemoveBuilder < BaseBuilder
|
62
|
+
|
63
|
+
# @return [void]
|
64
|
+
def call
|
65
|
+
with_database do |db_svc|
|
66
|
+
db_svc.remove_column(@named_range.to_sym, @column.to_sym)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [String (frozen)]
|
71
|
+
def to_s
|
72
|
+
"Remove Builder"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class RenameBuilder < BaseBuilder
|
77
|
+
|
78
|
+
# @return [void]
|
79
|
+
def call
|
80
|
+
with_database do |db_svc|
|
81
|
+
db_svc.rename_column(@named_range.to_sym, @column.to_sym, @opts.fetch("new_name"))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [String (frozen)]
|
86
|
+
def to_s
|
87
|
+
"Rename Builder"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class CopyBuilder < BaseBuilder
|
92
|
+
|
93
|
+
# @return [void]
|
94
|
+
def call
|
95
|
+
with_database do |db_svc|
|
96
|
+
to_column = @opts.fetch(:to_column)
|
97
|
+
db_svc.copy_column(
|
98
|
+
@named_range.to_sym,
|
99
|
+
@column.to_sym,
|
100
|
+
to_column
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [String (frozen)]
|
106
|
+
def to_s
|
107
|
+
"Copy Builder"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class BlacklistBuilder < BaseBuilder
|
112
|
+
|
113
|
+
# @return [void]
|
114
|
+
def call
|
115
|
+
@opts[:columns].each do |column|
|
116
|
+
with_database do |db_svc|
|
117
|
+
db_svc.remove_column(@named_range.to_sym, column.to_sym)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [String (frozen)]
|
123
|
+
def to_s
|
124
|
+
"Blacklist Builder"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class WhitelistBuilder < BaseBuilder
|
129
|
+
|
130
|
+
# @return [void]
|
131
|
+
def call
|
132
|
+
with_database do |db_svc|
|
133
|
+
whitelist_columns = (@opts[:columns]).map(&:to_sym)
|
134
|
+
all_columns = db_svc.list_columns(@named_range.to_sym)
|
135
|
+
|
136
|
+
# Remove cols that aren't defined in white list or sys columns
|
137
|
+
candidate_cols = all_columns - whitelist_columns - [:_id]
|
138
|
+
|
139
|
+
candidate_cols.each do |column|
|
140
|
+
db_svc.remove_column(@named_range.to_sym, column.to_sym)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# @return [String (frozen)]
|
146
|
+
def to_s
|
147
|
+
"Whitelist Builder"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cure/helpers/object_helpers"
|
4
|
+
require "cure/builder/base_builder"
|
5
|
+
require "cure/extract/extractor"
|
6
|
+
require "cure/log"
|
7
|
+
|
8
|
+
module Cure
|
9
|
+
module Builder
|
10
|
+
# Per row, we will have a candidate for each transformation that needs to be made
|
11
|
+
class Candidate
|
12
|
+
include Helpers::ObjectHelpers
|
13
|
+
include Log
|
14
|
+
|
15
|
+
# Named range that column exists in
|
16
|
+
# @return [String]
|
17
|
+
attr_reader :named_range
|
18
|
+
|
19
|
+
# Lookup column name for CSV.
|
20
|
+
# @return [String,nil]
|
21
|
+
attr_reader :column
|
22
|
+
|
23
|
+
# What sort of data needs to be generated.
|
24
|
+
# @return [Cure::Builder::BaseBuilder]
|
25
|
+
attr_reader :action
|
26
|
+
|
27
|
+
# @param [String,nil] column
|
28
|
+
# @param [String] named_range
|
29
|
+
def initialize(column, named_range)
|
30
|
+
@column = column
|
31
|
+
@named_range = named_range || "_default"
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [void]
|
35
|
+
def perform
|
36
|
+
@action.call
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [String,Symbol] _method_name
|
40
|
+
# @param [TrueClass,FalseClass] _include_private
|
41
|
+
# @return [TrueClass]
|
42
|
+
def respond_to_missing?(_method_name, _include_private=false)
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [Symbol] method_name
|
47
|
+
# @return [Cure::Builder::BaseBuilder]
|
48
|
+
def method_missing(method_name, args)
|
49
|
+
klass_name = "Cure::Builder::#{method_name.to_s.capitalize}Builder"
|
50
|
+
raise "#{method_name} is not valid" unless class_exists?(klass_name)
|
51
|
+
|
52
|
+
@action = Kernel.const_get(klass_name).new(@named_range, @column, args[:options] || {})
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cure"
|
4
|
+
require "cure/log"
|
5
|
+
|
6
|
+
module Cure
|
7
|
+
module Cli
|
8
|
+
class Command
|
9
|
+
include Log
|
10
|
+
|
11
|
+
# @param [String] command
|
12
|
+
# @param [Array] argv
|
13
|
+
def self.invoke(command, argv)
|
14
|
+
extend Log
|
15
|
+
|
16
|
+
handler = nil
|
17
|
+
|
18
|
+
case command
|
19
|
+
when "new"
|
20
|
+
handler = NewCommand.new(argv)
|
21
|
+
when "generate"
|
22
|
+
handler = GenerateCommand.new(argv)
|
23
|
+
when "run"
|
24
|
+
handler = RunCommand.new(argv)
|
25
|
+
else
|
26
|
+
new.help
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
handler.call
|
31
|
+
rescue ArgumentError => aex
|
32
|
+
handler ? handler.help(ex: aex) : new.help
|
33
|
+
rescue StandardError => ex
|
34
|
+
log_error(ex.message)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Array] argv
|
38
|
+
def initialize(argv = [])
|
39
|
+
@argv = argv
|
40
|
+
end
|
41
|
+
|
42
|
+
def call
|
43
|
+
validate
|
44
|
+
execute
|
45
|
+
end
|
46
|
+
|
47
|
+
def help(ex: nil)
|
48
|
+
log_error ex.message if ex && ex.is_a?(StandardError)
|
49
|
+
log_error "Error: unknown request"
|
50
|
+
log_info "\nUsage: cure <command> [options]"
|
51
|
+
log_info "\tRun: cure run -t [template] -f [file]"
|
52
|
+
log_info "\tNew Project: cure new [name]"
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @return [void]
|
58
|
+
def execute
|
59
|
+
raise NotImplementedError
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [void]
|
63
|
+
# @raise [ArgumentError, NotImplementedError]
|
64
|
+
def validate
|
65
|
+
raise NotImplementedError
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
|
71
|
+
def make_directory(dir_name)
|
72
|
+
log_info "Creating directory #{dir_name}"
|
73
|
+
|
74
|
+
FileUtils.mkdir_p(dir_name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def make_file(dir_name, file_name, template: nil, binding: nil)
|
78
|
+
file = File.join(dir_name, file_name)
|
79
|
+
|
80
|
+
log_info "Creating file #{file}"
|
81
|
+
|
82
|
+
unless template
|
83
|
+
FileUtils.touch(file)
|
84
|
+
return
|
85
|
+
end
|
86
|
+
|
87
|
+
content = retrieve_template(template)
|
88
|
+
if binding
|
89
|
+
erb = ERB.new(content)
|
90
|
+
content = erb.result_with_hash(binding)
|
91
|
+
end
|
92
|
+
|
93
|
+
File.open(file, "w") do |f|
|
94
|
+
f.write(content)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def retrieve_template(template)
|
99
|
+
File.read(
|
100
|
+
File.join(File.dirname(__FILE__), "templates", "#{template}.erb")
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cure/cli/command"
|
4
|
+
|
5
|
+
# TODO: Generate script - inc File and init and Run export.
|
6
|
+
|
7
|
+
module Cure
|
8
|
+
module Cli
|
9
|
+
class GenerateCommand < Command
|
10
|
+
def initialize(args)
|
11
|
+
super(args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def help(ex: nil)
|
15
|
+
log_error ex.message if ex && ex.is_a?(StandardError)
|
16
|
+
log_info "\nUsage: cure generate template [name]"
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def execute
|
22
|
+
raise "You are not in a Cure project! Cannot create template." unless cure_project?
|
23
|
+
|
24
|
+
log_info "Creating new template: #{params[:name]}"
|
25
|
+
|
26
|
+
root_dir = File.join(Dir.pwd, "templates")
|
27
|
+
make_file(root_dir, "#{params[:name]}_template.rb", template: "new_template.rb")
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate
|
31
|
+
# Theres a nicer way to do this, I'll do it later.
|
32
|
+
raise ArgumentError, "Missing arguments" if @argv.empty?
|
33
|
+
raise ArgumentError, "Invalid operation #{@argv.first}" if @argv.first != "template"
|
34
|
+
raise ArgumentError, "Invalid name #{@argv.last}" if @argv.last.nil? || @argv.last.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :params
|
38
|
+
|
39
|
+
def params
|
40
|
+
{
|
41
|
+
operation: @argv.first,
|
42
|
+
name: @argv.last
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
# Naive implementation, could do better. Who cares for now.
|
47
|
+
# @return [TrueClass, FalseClass]
|
48
|
+
def cure_project?
|
49
|
+
gf = File.join(Dir.pwd, 'Gemfile')
|
50
|
+
File.exist?(gf) && File.read(gf).include?('cure')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cure/version"
|
4
|
+
require "cure/cli/command"
|
5
|
+
|
6
|
+
require "fileutils"
|
7
|
+
require "erb"
|
8
|
+
|
9
|
+
module Cure
|
10
|
+
module Cli
|
11
|
+
class NewCommand < Command
|
12
|
+
def initialize(argv)
|
13
|
+
super(argv)
|
14
|
+
end
|
15
|
+
|
16
|
+
def help(ex: nil)
|
17
|
+
log_error ex.message if ex && ex.is_a?(StandardError)
|
18
|
+
log_info "\nUsage: cure new [name]"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def execute
|
24
|
+
root_dir = File.join(Dir.pwd, @argv[0])
|
25
|
+
|
26
|
+
raise "Project already exists in directory!" if File.exist?(root_dir)
|
27
|
+
|
28
|
+
log_info "Creating new project: #{@argv[0]}"
|
29
|
+
|
30
|
+
make_directory(root_dir)
|
31
|
+
make_directory(File.join(root_dir, "input"))
|
32
|
+
make_directory(File.join(root_dir, "output"))
|
33
|
+
make_directory(File.join(root_dir, "scripts"))
|
34
|
+
make_directory(File.join(root_dir, "templates"))
|
35
|
+
make_directory(File.join(root_dir, "utilities"))
|
36
|
+
|
37
|
+
make_file(root_dir, ".gitignore", template: "gitignore")
|
38
|
+
make_file(root_dir, ".tool-versions", template: "tool-versions")
|
39
|
+
make_file(root_dir, "README.md", template: "README.md")
|
40
|
+
make_file(root_dir, "Gemfile", template: "gemfile", binding: { version: Cure::VERSION })
|
41
|
+
make_file("#{root_dir}/input", ".gitkeep")
|
42
|
+
make_file("#{root_dir}/output", ".gitkeep")
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [void]
|
46
|
+
# @raise [ArgumentError]
|
47
|
+
def validate
|
48
|
+
raise ArgumentError, "Error: No project name given" if @argv.empty?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cure/cli/command"
|
4
|
+
|
5
|
+
module Cure
|
6
|
+
module Cli
|
7
|
+
class RunCommand < Command
|
8
|
+
def initialize(args)
|
9
|
+
super(args)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def execute
|
15
|
+
log_info "Not implemented yet!"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
## New Cure Project
|