nocode 0.0.3 → 0.0.7
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/.github/workflows/rubygem.yml +2 -0
- data/.rubocop.yml +0 -3
- data/CHANGELOG.md +19 -0
- data/README.md +4 -6
- data/exe/nocode +2 -0
- data/lib/nocode/context.rb +18 -3
- data/lib/nocode/job_executor.rb +55 -0
- data/lib/nocode/step.rb +10 -2
- data/lib/nocode/step_registry.rb +9 -3
- data/lib/nocode/steps/copy.rb +2 -1
- data/lib/nocode/steps/dataset/append.rb +21 -0
- data/lib/nocode/steps/dataset/coalesce.rb +22 -0
- data/lib/nocode/steps/dataset/insert.rb +29 -0
- data/lib/nocode/steps/dataset/prepend.rb +21 -0
- data/lib/nocode/steps/dataset/range.rb +32 -0
- data/lib/nocode/steps/delete.rb +1 -0
- data/lib/nocode/steps/deserialize/csv.rb +1 -0
- data/lib/nocode/steps/deserialize/json.rb +1 -0
- data/lib/nocode/steps/deserialize/yaml.rb +22 -0
- data/lib/nocode/steps/each.rb +31 -0
- data/lib/nocode/steps/io/read.rb +1 -0
- data/lib/nocode/steps/io/write.rb +1 -0
- data/lib/nocode/steps/log.rb +1 -0
- data/lib/nocode/steps/serialize/csv.rb +2 -6
- data/lib/nocode/steps/serialize/json.rb +2 -0
- data/lib/nocode/steps/serialize/yaml.rb +19 -0
- data/lib/nocode/steps/set.rb +1 -0
- data/lib/nocode/steps/sleep.rb +3 -0
- data/lib/nocode/steps_executor.rb +68 -0
- data/lib/nocode/util/arrayable.rb +1 -0
- data/lib/nocode/util/class_loader.rb +1 -0
- data/lib/nocode/util/class_registry.rb +5 -2
- data/lib/nocode/util/dictionary.rb +1 -0
- data/lib/nocode/util/object_template.rb +44 -0
- data/lib/nocode/util/optionable.rb +22 -0
- data/lib/nocode/util/string_template.rb +6 -1
- data/lib/nocode/util.rb +1 -1
- data/lib/nocode/version.rb +1 -1
- data/lib/nocode.rb +4 -2
- data/nocode.gemspec +1 -0
- metadata +27 -4
- data/lib/nocode/executor.rb +0 -84
- data/lib/nocode/object_template.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73aa9b620a2823a96b00c4b38f5a8e0fc8f5f040b5100e255e5f3df546263544
|
4
|
+
data.tar.gz: d5dbacadf0759e59ddd279c7446f679052ff89b4f2d3ea8d49378173368bd28f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fe2d5363d14bb6d035072629e280d6e87717a3a16e3d28d69e520ea6a8aa21d1ad330f554c94c6d6c5113c8acbb665fc3d4b9f8bde74d7652e8b14d3c1b42c8
|
7
|
+
data.tar.gz: c8f2c04683439abecfc3de0c63a909cc1b52686f2741f1936f03ea152bef263a41c4613d26e2ae46c732c6f879a6cfe40e62eed6d8922cb7b862a87329624b45
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
#### 0.0.7 - February 13th, 2022
|
2
|
+
|
3
|
+
* Move shared logging logic to context (until a first class log writer emerges).
|
4
|
+
* Provide type_prefix option for class_registry
|
5
|
+
|
6
|
+
#### 0.0.6 - February 13th, 2022
|
7
|
+
|
8
|
+
* Expose registers to main YAML configuration.
|
9
|
+
* Add Each step to serve as an example of an iterator.
|
10
|
+
|
11
|
+
#### 0.0.5 - February 13th, 2022
|
12
|
+
|
13
|
+
* Added initial Dataset steps.
|
14
|
+
|
15
|
+
#### 0.0.4 - February 13th, 2022
|
16
|
+
|
17
|
+
* Increase class documentation
|
18
|
+
* Add YAML serialization/deserialization
|
19
|
+
|
1
20
|
#### 0.0.3 - February 12th, 2022
|
2
21
|
|
3
22
|
* Add initial implementation.
|
data/README.md
CHANGED
@@ -2,9 +2,7 @@
|
|
2
2
|
|
3
3
|
#### Execute Ruby code through YAML
|
4
4
|
|
5
|
-
[](https://github.com/mattruggio/nocode/actions/workflows/rubygem.yml)
|
6
|
-
|
7
|
-
**Warning**: This library is currently experimental.
|
5
|
+
[](https://badge.fury.io/rb/nocode) [](https://github.com/mattruggio/nocode/actions/workflows/rubygem.yml) [](https://codeclimate.com/github/mattruggio/nocode/maintainability) [](https://opensource.org/licenses/MIT)
|
8
6
|
|
9
7
|
This is a proof of concept showing how a YAML interface could be draped over arbitrary Ruby code. The YAML contains a series of steps with each step mapping to a specific Ruby class. The Ruby classes just have one responsibility: to implement #perform.
|
10
8
|
|
@@ -113,16 +111,16 @@ Note: ensure you have proper authorization before trying to publish new versions
|
|
113
111
|
|
114
112
|
After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
|
115
113
|
|
116
|
-
1. Merge Pull Request into
|
114
|
+
1. Merge Pull Request into main
|
117
115
|
2. Update `version.rb` using [semantic versioning](https://semver.org/)
|
118
116
|
3. Install dependencies: `bundle`
|
119
117
|
4. Update `CHANGELOG.md` with release notes
|
120
|
-
5. Commit & push
|
118
|
+
5. Commit & push main to remote and ensure CI builds main successfully
|
121
119
|
6. Run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
122
120
|
|
123
121
|
## Code of Conduct
|
124
122
|
|
125
|
-
Everyone interacting in this codebase, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/mattruggio/nocode/blob/
|
123
|
+
Everyone interacting in this codebase, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/mattruggio/nocode/blob/main/CODE_OF_CONDUCT.md).
|
126
124
|
|
127
125
|
## License
|
128
126
|
|
data/exe/nocode
CHANGED
data/lib/nocode/context.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Nocode
|
4
|
+
# Describes the environment for each running step. An instance is initialized when a job
|
5
|
+
# kicks off and then is passed from step to step.
|
4
6
|
class Context
|
5
|
-
|
7
|
+
PARAMETERS_KEY = 'parameters'
|
8
|
+
REGISTERS_KEY = 'registers'
|
9
|
+
|
10
|
+
attr_reader :io,
|
11
|
+
:parameters,
|
12
|
+
:registers
|
6
13
|
|
7
14
|
def initialize(io: $stdout, parameters: {}, registers: {})
|
8
15
|
@io = io || $stdout
|
@@ -22,9 +29,17 @@ module Nocode
|
|
22
29
|
|
23
30
|
def to_h
|
24
31
|
{
|
25
|
-
|
26
|
-
|
32
|
+
REGISTERS_KEY => registers,
|
33
|
+
PARAMETERS_KEY => parameters
|
27
34
|
}
|
28
35
|
end
|
36
|
+
|
37
|
+
def log_line
|
38
|
+
log('-' * 50)
|
39
|
+
end
|
40
|
+
|
41
|
+
def log(msg)
|
42
|
+
io.puts(msg)
|
43
|
+
end
|
29
44
|
end
|
30
45
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'context'
|
4
|
+
require_relative 'steps_executor'
|
5
|
+
|
6
|
+
module Nocode
|
7
|
+
# Manages the lifecycle and executes a job.
|
8
|
+
class JobExecutor
|
9
|
+
PARAMETERS_KEY = 'parameters'
|
10
|
+
REGISTERS_KEY = 'registers'
|
11
|
+
STEPS_KEY = 'steps'
|
12
|
+
|
13
|
+
attr_reader :yaml, :io
|
14
|
+
|
15
|
+
def initialize(yaml, io: $stdout)
|
16
|
+
@yaml = yaml.respond_to?(:read) ? yaml.read : yaml
|
17
|
+
@yaml = YAML.safe_load(@yaml) || {}
|
18
|
+
@io = io
|
19
|
+
|
20
|
+
freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute
|
24
|
+
steps = yaml[STEPS_KEY] || []
|
25
|
+
parameters = yaml[PARAMETERS_KEY] || {}
|
26
|
+
registers = yaml[REGISTERS_KEY] || {}
|
27
|
+
|
28
|
+
context = Context.new(
|
29
|
+
io: io,
|
30
|
+
parameters: parameters,
|
31
|
+
registers: registers
|
32
|
+
)
|
33
|
+
|
34
|
+
log_title(context)
|
35
|
+
|
36
|
+
StepsExecutor.new(context: context, steps: steps).execute
|
37
|
+
|
38
|
+
context.log("Ended: #{DateTime.now}")
|
39
|
+
context.log_line
|
40
|
+
|
41
|
+
context
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def log_title(context)
|
47
|
+
context.log_line
|
48
|
+
|
49
|
+
context.log('Nocode Execution')
|
50
|
+
context.log("Started: #{DateTime.now}")
|
51
|
+
|
52
|
+
context.log_line
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/nocode/step.rb
CHANGED
@@ -1,14 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Nocode
|
4
|
+
# Defines a running step. Steps should be sub-classes of this class as well as to implement
|
5
|
+
# #perform.
|
4
6
|
class Step
|
5
7
|
extend Forwardable
|
6
8
|
include Util::Arrayable
|
7
9
|
include Util::Optionable
|
8
10
|
|
9
|
-
attr_reader :
|
11
|
+
attr_reader :context,
|
12
|
+
:name,
|
13
|
+
:options,
|
14
|
+
:type
|
10
15
|
|
11
|
-
def_delegators :context,
|
16
|
+
def_delegators :context,
|
17
|
+
:io,
|
18
|
+
:parameters,
|
19
|
+
:registers
|
12
20
|
|
13
21
|
def initialize(
|
14
22
|
context: Context.new,
|
data/lib/nocode/step_registry.rb
CHANGED
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
require_relative 'step'
|
4
4
|
|
5
|
+
# This will execute the StepRegisty's load! method upon script evaluation.
|
5
6
|
module Nocode
|
7
|
+
# Provides a global place to register all valid steps by their types. By default the
|
8
|
+
# steps directory will be autoloaded and their paths will be used as their types. For example:
|
9
|
+
# for the class: steps/io/write, it would register as "io/write" type.
|
6
10
|
class StepRegistry < Util::ClassRegistry
|
7
11
|
include Singleton
|
8
12
|
|
9
|
-
|
10
|
-
DIR
|
13
|
+
CLASS_PREFIX = 'Nocode::Steps::'
|
14
|
+
DIR = File.join(__dir__, 'steps')
|
11
15
|
|
12
16
|
class << self
|
13
17
|
extend Forwardable
|
@@ -22,9 +26,11 @@ module Nocode
|
|
22
26
|
def load!
|
23
27
|
files_loaded = Util::ClassLoader.new(DIR).load!
|
24
28
|
|
25
|
-
load
|
29
|
+
# Class the parent to load up the registry with the files we found.
|
30
|
+
load(files_loaded, class_prefix: CLASS_PREFIX)
|
26
31
|
end
|
27
32
|
end
|
28
33
|
|
34
|
+
# Call upon class evaluation to autoload all classes.
|
29
35
|
StepRegistry.load!
|
30
36
|
end
|
data/lib/nocode/steps/copy.rb
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
module Nocode
|
4
4
|
module Steps
|
5
|
+
# Shallow-copy one register to another.
|
5
6
|
class Copy < Step
|
6
7
|
option :from_register, :to_register
|
7
8
|
|
8
9
|
def perform
|
9
|
-
registers[to_register_option] = registers[from_register_option]
|
10
|
+
registers[to_register_option] = registers[from_register_option].dup
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
module Dataset
|
6
|
+
# Add entries specified in the options to the end of the specified register's
|
7
|
+
# existing entries.
|
8
|
+
class Append < Step
|
9
|
+
option :entries, :register
|
10
|
+
|
11
|
+
def perform
|
12
|
+
registers[register_option] = array(registers[register_option])
|
13
|
+
|
14
|
+
array(entries_option).each do |entry|
|
15
|
+
registers[register_option].append(entry)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
module Dataset
|
6
|
+
# Combine all specified from_registers into one dataset and place in the specified
|
7
|
+
# to_register. If anything currently exists in the to_register then it will be coerced
|
8
|
+
# to an array and prepended to the beginning.
|
9
|
+
class Coalesce < Step
|
10
|
+
option :from_registers, :to_register
|
11
|
+
|
12
|
+
def perform
|
13
|
+
registers[to_register_option] = array(registers[to_register_option])
|
14
|
+
|
15
|
+
array(from_registers_option).each do |from_register|
|
16
|
+
registers[to_register_option] += array(registers[from_register])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
module Dataset
|
6
|
+
# Insert the entries to the at_index of the specified register.
|
7
|
+
# If at_index is nil then they will appended to the end.
|
8
|
+
class Insert < Step
|
9
|
+
option :at_index, :entries, :register
|
10
|
+
|
11
|
+
def perform
|
12
|
+
registers[register_option] = array(registers[register_option])
|
13
|
+
|
14
|
+
registers[register_option].insert(at_index, *entries)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def entries
|
20
|
+
array(entries_option)
|
21
|
+
end
|
22
|
+
|
23
|
+
def at_index
|
24
|
+
at_index_option.nil? ? -1 : at_index_option.to_i
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
module Dataset
|
6
|
+
# Add entries specified in the options to the beginning of the specified register's
|
7
|
+
# existing entries.
|
8
|
+
class Prepend < Step
|
9
|
+
option :entries, :register
|
10
|
+
|
11
|
+
def perform
|
12
|
+
registers[register_option] = array(registers[register_option])
|
13
|
+
|
14
|
+
array(entries_option).reverse_each do |entry|
|
15
|
+
registers[register_option].prepend(entry)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
module Dataset
|
6
|
+
# Slice a dataset and keep on the entries between start_index and end_index, inclusively.
|
7
|
+
# If start_index is not provided then it defaults to 0.
|
8
|
+
# If end_index is not provided then it defaults to the end of the dataset.
|
9
|
+
class Range < Step
|
10
|
+
option :end_index,
|
11
|
+
:register,
|
12
|
+
:start_index
|
13
|
+
|
14
|
+
def perform
|
15
|
+
registers[register_option] = array(registers[register_option])
|
16
|
+
|
17
|
+
registers[register_option] = registers[register_option][start_index..end_index]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def start_index
|
23
|
+
start_index_option.nil? ? 0 : start_index_option.to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
def end_index
|
27
|
+
end_index_option.nil? ? -1 : end_index_option.to_i
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/nocode/steps/delete.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
module Deserialize
|
6
|
+
# Take a specified register and parse it as YAML to produce Ruby object(s).
|
7
|
+
#
|
8
|
+
# NOTE: This will throw an error if unsafe YAML types are used. The only allowed types are
|
9
|
+
# array, hash, strings, numbers, booleans, nil. See:
|
10
|
+
# https://ruby-doc.org/stdlib-2.6.1/libdoc/psych/rdoc/Psych.html#method-c-safe_load
|
11
|
+
class Yaml < Step
|
12
|
+
option :register
|
13
|
+
|
14
|
+
def perform
|
15
|
+
input = registers[register_option]
|
16
|
+
|
17
|
+
registers[register_option] = YAML.safe_load(input)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
# Iterate over a register. Each iteration will store the current element and index in
|
6
|
+
# special registers called: _element and _index. You can prefix these registers by setting
|
7
|
+
# the element_register_prefix option.
|
8
|
+
class Each < Step
|
9
|
+
option :element_register_prefix,
|
10
|
+
:register,
|
11
|
+
:steps
|
12
|
+
|
13
|
+
skip_options_evaluation!
|
14
|
+
|
15
|
+
# rubocop:disable Metrics/AbcSize
|
16
|
+
def perform
|
17
|
+
entries = array(registers[register_option])
|
18
|
+
|
19
|
+
entries.each_with_index do |entry, index|
|
20
|
+
steps = array(steps_option)
|
21
|
+
|
22
|
+
registers["#{element_register_prefix_option}_element"] = entry
|
23
|
+
registers["#{element_register_prefix_option}_index"] = index
|
24
|
+
|
25
|
+
StepsExecutor.new(context: context, steps: steps).execute
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# rubocop:enable Metrics/AbcSize
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/nocode/steps/io/read.rb
CHANGED
data/lib/nocode/steps/log.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module Nocode
|
4
4
|
module Steps
|
5
5
|
module Serialize
|
6
|
+
# Take the contents of a register and create a CSV out of its contents. The CSV contents
|
7
|
+
# will override the register specified.
|
6
8
|
class Csv < Step
|
7
9
|
option :register
|
8
10
|
|
@@ -25,14 +27,8 @@ module Nocode
|
|
25
27
|
|
26
28
|
if object.is_a?(Array)
|
27
29
|
csv << object
|
28
|
-
|
29
|
-
true
|
30
30
|
elsif object.respond_to?(:values)
|
31
31
|
csv << object.values
|
32
|
-
|
33
|
-
true
|
34
|
-
else
|
35
|
-
false
|
36
32
|
end
|
37
33
|
end
|
38
34
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
module Serialize
|
6
|
+
# Take the contents of a register and serialize it as YAML. The serialized YAML
|
7
|
+
# will override the register specified.
|
8
|
+
class Yaml < Step
|
9
|
+
option :register
|
10
|
+
|
11
|
+
def perform
|
12
|
+
input = registers[register_option]
|
13
|
+
|
14
|
+
registers[register_option] = input.to_yaml
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/nocode/steps/set.rb
CHANGED
data/lib/nocode/steps/sleep.rb
CHANGED
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'step_registry'
|
4
|
+
|
5
|
+
module Nocode
|
6
|
+
# Class that knows how to execute a series of steps given a context.
|
7
|
+
class StepsExecutor
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
NAME_KEY = 'name'
|
11
|
+
OPTIONS_KEY = 'options'
|
12
|
+
TYPE_KEY = 'type'
|
13
|
+
|
14
|
+
attr_reader :context, :steps
|
15
|
+
|
16
|
+
def_delegators :context, :log_line, :log
|
17
|
+
|
18
|
+
def initialize(context:, steps:)
|
19
|
+
@context = context
|
20
|
+
@steps = steps
|
21
|
+
|
22
|
+
freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute
|
26
|
+
steps.each do |step|
|
27
|
+
step_instance = make_step(step)
|
28
|
+
|
29
|
+
execute_step(step_instance)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def make_step(step)
|
36
|
+
evaluated_step = Util::ObjectTemplate.new(step).evaluate(context.to_h)
|
37
|
+
type = evaluated_step[TYPE_KEY].to_s
|
38
|
+
name = evaluated_step[NAME_KEY].to_s
|
39
|
+
step_class = StepRegistry.constant!(type)
|
40
|
+
|
41
|
+
options =
|
42
|
+
if step_class.skip_options_evaluation?
|
43
|
+
step[OPTIONS_KEY]
|
44
|
+
else
|
45
|
+
evaluated_step[OPTIONS_KEY]
|
46
|
+
end
|
47
|
+
|
48
|
+
step_class.new(
|
49
|
+
options: Util::Dictionary.new(options),
|
50
|
+
context: context,
|
51
|
+
name: name,
|
52
|
+
type: type
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def execute_step(step)
|
57
|
+
log(step.name) unless step.name.empty?
|
58
|
+
log("Step: #{step.type}")
|
59
|
+
log("Class: #{step.class}")
|
60
|
+
|
61
|
+
time_in_seconds = Benchmark.measure { step.perform }.real
|
62
|
+
|
63
|
+
log("Completed in #{time_in_seconds.round(3)} second(s)")
|
64
|
+
|
65
|
+
log_line
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
module Nocode
|
4
4
|
module Util
|
5
|
+
# Create a type -> class constant interface. Classes can be registered as types. Types
|
6
|
+
# are snake-cased while class names are stored as pascal-cased. Then constant! can be called
|
7
|
+
# to retrieve the class constant by type.
|
5
8
|
class ClassRegistry
|
6
9
|
extend Forwardable
|
7
10
|
|
@@ -17,13 +20,13 @@ module Nocode
|
|
17
20
|
freeze
|
18
21
|
end
|
19
22
|
|
20
|
-
def load(types,
|
23
|
+
def load(types, class_prefix: '', type_prefix: '')
|
21
24
|
types.each do |type|
|
22
25
|
pascal_cased = type.split(File::SEPARATOR).map do |part|
|
23
26
|
part.split('_').collect(&:capitalize).join
|
24
27
|
end.join('::')
|
25
28
|
|
26
|
-
register(type, "#{
|
29
|
+
register("#{type_prefix}#{type}", "#{class_prefix}#{pascal_cased}")
|
27
30
|
end
|
28
31
|
|
29
32
|
self
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'string_template'
|
4
|
+
|
5
|
+
module Nocode
|
6
|
+
module Util
|
7
|
+
# Built on top of StringTemplate but instead of only working for a string, this will
|
8
|
+
# recursively evaluate all strings within an object. Heuristics:
|
9
|
+
# - Strings evaluate using StringTemplate
|
10
|
+
# - Hashes will have their keys and values traversed
|
11
|
+
# - Arrays will have their entries traversed
|
12
|
+
# - All other types will simply return themselves
|
13
|
+
class ObjectTemplate
|
14
|
+
attr_reader :object
|
15
|
+
|
16
|
+
def initialize(object)
|
17
|
+
@object = object
|
18
|
+
|
19
|
+
freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def evaluate(values = {})
|
23
|
+
recursive_evaluate(object, values)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def recursive_evaluate(expression, values)
|
29
|
+
case expression
|
30
|
+
when Array
|
31
|
+
expression.map { |o| recursive_evaluate(o, values) }
|
32
|
+
when Hash
|
33
|
+
expression.to_h do |k, v|
|
34
|
+
[recursive_evaluate(k, values), recursive_evaluate(v, values)]
|
35
|
+
end
|
36
|
+
when String
|
37
|
+
Util::StringTemplate.new(expression).evaluate(values)
|
38
|
+
else
|
39
|
+
expression
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -2,6 +2,20 @@
|
|
2
2
|
|
3
3
|
module Nocode
|
4
4
|
module Util
|
5
|
+
# Add on a DSL for classes. The DSL allows for a new class-level keyword called 'option'
|
6
|
+
# which can be used to describe what metadata values are important. Then instances
|
7
|
+
# can reference those option's values using magic _option methods. For example:
|
8
|
+
#
|
9
|
+
# class Animal
|
10
|
+
# include Optionable
|
11
|
+
# option :type
|
12
|
+
# attr_writer :options
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# animal = Animal.new
|
16
|
+
# animal.options = { 'type' => 'dog' }
|
17
|
+
#
|
18
|
+
# animal.type_option # -> should return 'dog'
|
5
19
|
module Optionable
|
6
20
|
def self.included(klass)
|
7
21
|
klass.extend(ClassMethods)
|
@@ -9,6 +23,14 @@ module Nocode
|
|
9
23
|
|
10
24
|
# Class-level DSL Methods
|
11
25
|
module ClassMethods
|
26
|
+
def skip_options_evaluation?
|
27
|
+
@skip_options_evaluation || false
|
28
|
+
end
|
29
|
+
|
30
|
+
def skip_options_evaluation!
|
31
|
+
@skip_options_evaluation = true
|
32
|
+
end
|
33
|
+
|
12
34
|
def option(*values)
|
13
35
|
values.each { |v| options << v.to_s }
|
14
36
|
end
|
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
module Nocode
|
4
4
|
module Util
|
5
|
+
# Takes in an expression and interpolates in any parameters using << >> notation.
|
6
|
+
# For example:
|
7
|
+
# input = { 'person' => 'hops' }
|
8
|
+
# Nocode::Util::StringTemplate.new("Hello, << person.name >>!").evaluate(input)
|
9
|
+
# Should produce: "Hello, hops!"
|
5
10
|
class StringTemplate
|
6
11
|
LEFT_TOKEN = '<<'
|
7
12
|
RIGHT_TOKEN = '>>'
|
@@ -11,7 +16,7 @@ module Nocode
|
|
11
16
|
attr_reader :expression
|
12
17
|
|
13
18
|
def initialize(expression)
|
14
|
-
@expression = expression
|
19
|
+
@expression = expression.to_s
|
15
20
|
|
16
21
|
freeze
|
17
22
|
end
|
data/lib/nocode/util.rb
CHANGED
data/lib/nocode/version.rb
CHANGED
data/lib/nocode.rb
CHANGED
@@ -12,12 +12,14 @@ require 'yaml'
|
|
12
12
|
require 'nocode/util'
|
13
13
|
|
14
14
|
# Core
|
15
|
-
require 'nocode/
|
15
|
+
require 'nocode/job_executor'
|
16
16
|
|
17
|
+
# Establish main top-level namespace
|
17
18
|
module Nocode
|
19
|
+
# Default consumer entrypoint into the library.
|
18
20
|
class << self
|
19
21
|
def execute(yaml, io: $stdout)
|
20
|
-
|
22
|
+
JobExecutor.new(yaml, io: io).execute
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
data/nocode.gemspec
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nocode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Ruggio
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-02-
|
11
|
+
date: 2022-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler-audit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: guard-rspec
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,26 +175,35 @@ files:
|
|
161
175
|
- exe/nocode
|
162
176
|
- lib/nocode.rb
|
163
177
|
- lib/nocode/context.rb
|
164
|
-
- lib/nocode/
|
165
|
-
- lib/nocode/object_template.rb
|
178
|
+
- lib/nocode/job_executor.rb
|
166
179
|
- lib/nocode/step.rb
|
167
180
|
- lib/nocode/step_registry.rb
|
168
181
|
- lib/nocode/steps/copy.rb
|
182
|
+
- lib/nocode/steps/dataset/append.rb
|
183
|
+
- lib/nocode/steps/dataset/coalesce.rb
|
184
|
+
- lib/nocode/steps/dataset/insert.rb
|
185
|
+
- lib/nocode/steps/dataset/prepend.rb
|
186
|
+
- lib/nocode/steps/dataset/range.rb
|
169
187
|
- lib/nocode/steps/delete.rb
|
170
188
|
- lib/nocode/steps/deserialize/csv.rb
|
171
189
|
- lib/nocode/steps/deserialize/json.rb
|
190
|
+
- lib/nocode/steps/deserialize/yaml.rb
|
191
|
+
- lib/nocode/steps/each.rb
|
172
192
|
- lib/nocode/steps/io/read.rb
|
173
193
|
- lib/nocode/steps/io/write.rb
|
174
194
|
- lib/nocode/steps/log.rb
|
175
195
|
- lib/nocode/steps/serialize/csv.rb
|
176
196
|
- lib/nocode/steps/serialize/json.rb
|
197
|
+
- lib/nocode/steps/serialize/yaml.rb
|
177
198
|
- lib/nocode/steps/set.rb
|
178
199
|
- lib/nocode/steps/sleep.rb
|
200
|
+
- lib/nocode/steps_executor.rb
|
179
201
|
- lib/nocode/util.rb
|
180
202
|
- lib/nocode/util/arrayable.rb
|
181
203
|
- lib/nocode/util/class_loader.rb
|
182
204
|
- lib/nocode/util/class_registry.rb
|
183
205
|
- lib/nocode/util/dictionary.rb
|
206
|
+
- lib/nocode/util/object_template.rb
|
184
207
|
- lib/nocode/util/optionable.rb
|
185
208
|
- lib/nocode/util/string_template.rb
|
186
209
|
- lib/nocode/version.rb
|
data/lib/nocode/executor.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'context'
|
4
|
-
require_relative 'object_template'
|
5
|
-
require_relative 'step_registry'
|
6
|
-
|
7
|
-
module Nocode
|
8
|
-
class Executor
|
9
|
-
attr_reader :yaml, :io
|
10
|
-
|
11
|
-
def initialize(yaml, io: $stdout)
|
12
|
-
@yaml = yaml.respond_to?(:read) ? yaml.read : yaml
|
13
|
-
@yaml = YAML.safe_load(@yaml) || {}
|
14
|
-
@io = io
|
15
|
-
|
16
|
-
freeze
|
17
|
-
end
|
18
|
-
|
19
|
-
def execute
|
20
|
-
steps = yaml['steps'] || []
|
21
|
-
parameters = yaml['parameters'] || {}
|
22
|
-
context = Context.new(io: io, parameters: parameters)
|
23
|
-
|
24
|
-
log_title
|
25
|
-
|
26
|
-
steps.each do |step|
|
27
|
-
step_instance = make_step(step, context)
|
28
|
-
|
29
|
-
execute_step(step_instance)
|
30
|
-
end
|
31
|
-
|
32
|
-
log("Ended: #{DateTime.now}")
|
33
|
-
log_line
|
34
|
-
|
35
|
-
context
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def make_step(step, context)
|
41
|
-
step = ObjectTemplate.new(step).evaluate(context.to_h)
|
42
|
-
type = step['type'].to_s
|
43
|
-
name = step['name'].to_s
|
44
|
-
options = step['options'] || {}
|
45
|
-
step_class = StepRegistry.constant!(type)
|
46
|
-
|
47
|
-
step_class.new(
|
48
|
-
options: Util::Dictionary.new(options),
|
49
|
-
context: context,
|
50
|
-
name: name,
|
51
|
-
type: type
|
52
|
-
)
|
53
|
-
end
|
54
|
-
|
55
|
-
def execute_step(step)
|
56
|
-
log(step.name) unless step.name.empty?
|
57
|
-
log("Step: #{step.type}")
|
58
|
-
log("Class: #{step.class}")
|
59
|
-
|
60
|
-
time_in_seconds = Benchmark.measure { step.perform }.real
|
61
|
-
|
62
|
-
log("Completed in #{time_in_seconds.round(3)} second(s)")
|
63
|
-
|
64
|
-
log_line
|
65
|
-
end
|
66
|
-
|
67
|
-
def log_title
|
68
|
-
log_line
|
69
|
-
|
70
|
-
log('Nocode Execution')
|
71
|
-
log("Started: #{DateTime.now}")
|
72
|
-
|
73
|
-
log_line
|
74
|
-
end
|
75
|
-
|
76
|
-
def log_line
|
77
|
-
log('-' * 50)
|
78
|
-
end
|
79
|
-
|
80
|
-
def log(msg)
|
81
|
-
io.puts(msg)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Nocode
|
4
|
-
class ObjectTemplate
|
5
|
-
attr_reader :object
|
6
|
-
|
7
|
-
def initialize(object)
|
8
|
-
@object = object
|
9
|
-
|
10
|
-
freeze
|
11
|
-
end
|
12
|
-
|
13
|
-
def evaluate(values = {})
|
14
|
-
recursive_evaluate(object, values)
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def recursive_evaluate(expression, values)
|
20
|
-
case expression
|
21
|
-
when Array
|
22
|
-
expression.map { |o| recursive_evaluate(o, values) }
|
23
|
-
when Hash
|
24
|
-
expression.to_h do |k, v|
|
25
|
-
[recursive_evaluate(k, values), recursive_evaluate(v, values)]
|
26
|
-
end
|
27
|
-
when String
|
28
|
-
Util::StringTemplate.new(expression).evaluate(values)
|
29
|
-
else
|
30
|
-
expression
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|