nocode 0.0.1 → 0.0.5
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 +14 -1
- data/CODE_OF_CONDUCT.md +73 -0
- data/README.md +121 -3
- data/lib/nocode/context.rb +3 -1
- data/lib/nocode/executor.rb +15 -9
- data/lib/nocode/step.rb +10 -2
- data/lib/nocode/step_registry.rb +6 -0
- 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 +18 -0
- data/lib/nocode/steps/deserialize/json.rb +18 -0
- data/lib/nocode/steps/deserialize/yaml.rb +22 -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 +37 -0
- data/lib/nocode/steps/serialize/json.rb +19 -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/util/arrayable.rb +1 -0
- data/lib/nocode/util/class_loader.rb +1 -0
- data/lib/nocode/util/class_registry.rb +3 -0
- data/lib/nocode/util/dictionary.rb +1 -0
- data/lib/nocode/util/object_template.rb +44 -0
- data/lib/nocode/util/optionable.rb +14 -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 -0
- data/nocode.gemspec +1 -0
- metadata +29 -3
- data/lib/nocode/options_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: 8726547b06744c6a7870a8634901b344e41a65bb1ede23c7e68e99d2218d482f
|
4
|
+
data.tar.gz: 3462f703d7639662c0da4f96ec9913749698c7f7a08a58387671db909a88785f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7bcfd37d152798a753801944a6a4787e3ad83a08fcfca0b40d9a0957cd7c5142a298adbb64215ed2e161c9e7f842b89ab460f6374fb8ec552a3482aa0b712eb
|
7
|
+
data.tar.gz: 6e024f679e2973888d5bb67c20988c8e93b296a6b6c4dbc66f7cb1f55d8638db48842834fd00c2a6d24050d9aa61950666107ff285a469e3f63835f74e7a5e7d
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
-
#### February
|
1
|
+
#### 0.0.5 - February 13th, 2022
|
2
|
+
|
3
|
+
* Added initial Dataset steps.
|
4
|
+
|
5
|
+
#### 0.0.4 - February 13th, 2022
|
6
|
+
|
7
|
+
* Increase class documentation
|
8
|
+
* Add YAML serialization/deserialization
|
9
|
+
|
10
|
+
#### 0.0.3 - February 12th, 2022
|
11
|
+
|
12
|
+
* Add initial implementation.
|
13
|
+
|
14
|
+
#### 0.0.0 - February 9th, 2022
|
2
15
|
|
3
16
|
* Establish initial repository and gem infrastructure.
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported. All complaints will be reviewed and investigated and will result in a response that
|
59
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
60
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
61
|
+
Further details of specific enforcement policies may be posted separately.
|
62
|
+
|
63
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
64
|
+
faith may face temporary or permanent repercussions as determined by other
|
65
|
+
members of the project's leadership.
|
66
|
+
|
67
|
+
## Attribution
|
68
|
+
|
69
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
70
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
71
|
+
|
72
|
+
[homepage]: http://contributor-covenant.org
|
73
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/README.md
CHANGED
@@ -2,8 +2,126 @@
|
|
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.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
To install through Rubygems:
|
12
|
+
|
13
|
+
````
|
14
|
+
gem install nocode
|
15
|
+
````
|
16
|
+
|
17
|
+
You can also add this to your Gemfile using:
|
18
|
+
|
19
|
+
````
|
20
|
+
bundle add nocode
|
21
|
+
````
|
22
|
+
|
23
|
+
## Examples
|
24
|
+
|
25
|
+
Create a file called `nocode-csv-to-json.yaml`:
|
26
|
+
|
27
|
+
### CSV-to-JSON File Converter
|
28
|
+
|
29
|
+
````yaml
|
30
|
+
parameters:
|
31
|
+
input_filename: input.csv
|
32
|
+
output_filename: output.json
|
33
|
+
|
34
|
+
steps:
|
35
|
+
- type: io/read
|
36
|
+
name: READ FILE
|
37
|
+
options:
|
38
|
+
path:
|
39
|
+
- files
|
40
|
+
- << parameters.input_filename >>
|
41
|
+
- type: deserialize/csv
|
42
|
+
- type: serialize/json
|
43
|
+
- type: io/write
|
44
|
+
options:
|
45
|
+
path:
|
46
|
+
- files
|
47
|
+
- << parameters.output_filename >>
|
48
|
+
````
|
49
|
+
|
50
|
+
Create csv file at: `files/input.csv`
|
51
|
+
|
52
|
+
Execute in Ruby:
|
53
|
+
|
54
|
+
````ruby
|
55
|
+
path = Pathname.new('nocode-csv-to-json.yaml')
|
56
|
+
|
57
|
+
Nocode.execute(path)
|
58
|
+
````
|
59
|
+
|
60
|
+
Or use bundler:
|
61
|
+
|
62
|
+
````zsh
|
63
|
+
bundle exec nocode `nocode-csv-to-json.yaml
|
64
|
+
````
|
65
|
+
|
66
|
+
A file should have been created at: `files/output.json`.
|
67
|
+
|
68
|
+
Notes:
|
69
|
+
|
70
|
+
* Path can be an array or a string. If its an array then the environment's path separator will be used.
|
71
|
+
* The `name` job key is optional. If present then it will print out on the log.
|
72
|
+
* Parameter values can be interpolated with keys and values using `<< parameters.key >>` syntax
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
### Development Environment Configuration
|
79
|
+
|
80
|
+
Basic steps to take to get this repository compiling:
|
81
|
+
|
82
|
+
1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check nocode.gemspec for versions supported)
|
83
|
+
2. Install bundler (gem install bundler)
|
84
|
+
3. Clone the repository (git clone git@github.com:mattruggio/nocode.git)
|
85
|
+
4. Navigate to the root folder (cd nocode)
|
86
|
+
5. Install dependencies (bundle)
|
87
|
+
|
88
|
+
### Running Tests
|
89
|
+
|
90
|
+
To execute the test suite run:
|
91
|
+
|
92
|
+
````bash
|
93
|
+
bundle exec rspec spec --format documentation
|
94
|
+
````
|
95
|
+
|
96
|
+
Alternatively, you can have Guard watch for changes:
|
97
|
+
|
98
|
+
````bash
|
99
|
+
bundle exec guard
|
100
|
+
````
|
101
|
+
|
102
|
+
Also, do not forget to run Rubocop:
|
103
|
+
|
104
|
+
````bash
|
105
|
+
bundle exec rubocop
|
106
|
+
````
|
107
|
+
|
108
|
+
### Publishing
|
109
|
+
|
110
|
+
Note: ensure you have proper authorization before trying to publish new versions.
|
111
|
+
|
112
|
+
After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
|
113
|
+
|
114
|
+
1. Merge Pull Request into main
|
115
|
+
2. Update `version.rb` using [semantic versioning](https://semver.org/)
|
116
|
+
3. Install dependencies: `bundle`
|
117
|
+
4. Update `CHANGELOG.md` with release notes
|
118
|
+
5. Commit & push main to remote and ensure CI builds main successfully
|
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).
|
120
|
+
|
121
|
+
## Code of Conduct
|
122
|
+
|
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).
|
124
|
+
|
125
|
+
## License
|
126
|
+
|
127
|
+
This project is MIT Licensed.
|
data/lib/nocode/context.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
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
|
attr_reader :io, :parameters, :registers
|
6
8
|
|
@@ -17,7 +19,7 @@ module Nocode
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def parameter(key)
|
20
|
-
parameters
|
22
|
+
parameters[key]
|
21
23
|
end
|
22
24
|
|
23
25
|
def to_h
|
data/lib/nocode/executor.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'context'
|
4
|
-
require_relative 'options_template'
|
5
4
|
require_relative 'step_registry'
|
6
5
|
|
7
6
|
module Nocode
|
7
|
+
# Manages the lifecycle and executes a job.
|
8
8
|
class Executor
|
9
|
+
NAME_KEY = 'name'
|
10
|
+
OPTIONS_KEY = 'options'
|
11
|
+
PARAMETERS_KEY = 'parameters'
|
12
|
+
STEPS_KEY = 'steps'
|
13
|
+
TYPE_KEY = 'type'
|
14
|
+
|
9
15
|
attr_reader :yaml, :io
|
10
16
|
|
11
17
|
def initialize(yaml, io: $stdout)
|
@@ -17,8 +23,8 @@ module Nocode
|
|
17
23
|
end
|
18
24
|
|
19
25
|
def execute
|
20
|
-
steps = yaml[
|
21
|
-
parameters = yaml[
|
26
|
+
steps = yaml[STEPS_KEY] || []
|
27
|
+
parameters = yaml[PARAMETERS_KEY] || {}
|
22
28
|
context = Context.new(io: io, parameters: parameters)
|
23
29
|
|
24
30
|
log_title
|
@@ -38,14 +44,14 @@ module Nocode
|
|
38
44
|
private
|
39
45
|
|
40
46
|
def make_step(step, context)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
step_class
|
47
|
+
step = Util::ObjectTemplate.new(step).evaluate(context.to_h)
|
48
|
+
type = step[TYPE_KEY].to_s
|
49
|
+
name = step[NAME_KEY].to_s
|
50
|
+
options = step[OPTIONS_KEY] || {}
|
51
|
+
step_class = StepRegistry.constant!(type)
|
46
52
|
|
47
53
|
step_class.new(
|
48
|
-
options: Util::Dictionary.new(
|
54
|
+
options: Util::Dictionary.new(options),
|
49
55
|
context: context,
|
50
56
|
name: name,
|
51
57
|
type: type
|
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,7 +2,11 @@
|
|
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
|
|
@@ -22,9 +26,11 @@ module Nocode
|
|
22
26
|
def load!
|
23
27
|
files_loaded = Util::ClassLoader.new(DIR).load!
|
24
28
|
|
29
|
+
# Class the parent to load up the registry with the files we found.
|
25
30
|
load(files_loaded, 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,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
module Deserialize
|
6
|
+
# take a specified register and parse it as a CSV to produce an array of hashes.
|
7
|
+
class Csv < Step
|
8
|
+
option :register
|
9
|
+
|
10
|
+
def perform
|
11
|
+
input = registers[register_option].to_s
|
12
|
+
|
13
|
+
registers[register_option] = CSV.new(input, headers: true).map(&:to_h)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
5
|
+
module Deserialize
|
6
|
+
# take a specified register and parse it as JSON to produce Ruby object(s).
|
7
|
+
class Json < Step
|
8
|
+
option :register
|
9
|
+
|
10
|
+
def perform
|
11
|
+
input = registers[register_option]
|
12
|
+
|
13
|
+
registers[register_option] = JSON.parse(input)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -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
|
data/lib/nocode/steps/io/read.rb
CHANGED
data/lib/nocode/steps/log.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nocode
|
4
|
+
module Steps
|
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.
|
8
|
+
class Csv < Step
|
9
|
+
option :register
|
10
|
+
|
11
|
+
def perform
|
12
|
+
input = registers[register_option]
|
13
|
+
|
14
|
+
registers[register_option] = CSV.generate do |csv|
|
15
|
+
array(input).each_with_index do |object, index|
|
16
|
+
csv << object.keys if index.zero? && object.respond_to?(:keys)
|
17
|
+
|
18
|
+
add_object(object, csv)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def add_object(object, csv)
|
26
|
+
object ||= {}
|
27
|
+
|
28
|
+
if object.is_a?(Array)
|
29
|
+
csv << object
|
30
|
+
elsif object.respond_to?(:values)
|
31
|
+
csv << object.values
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
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 JSON. The serialized JSON
|
7
|
+
# will override the register specified.
|
8
|
+
class Json < Step
|
9
|
+
option :register
|
10
|
+
|
11
|
+
def perform
|
12
|
+
input = registers[register_option]
|
13
|
+
|
14
|
+
registers[register_option] = input.to_json
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
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
@@ -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
|
|
@@ -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)
|
@@ -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
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'benchmark'
|
4
|
+
require 'csv'
|
4
5
|
require 'fileutils'
|
6
|
+
require 'json'
|
5
7
|
require 'singleton'
|
6
8
|
require 'time'
|
7
9
|
require 'yaml'
|
@@ -12,7 +14,9 @@ require 'nocode/util'
|
|
12
14
|
# Core
|
13
15
|
require 'nocode/executor'
|
14
16
|
|
17
|
+
# Establish main top-level namespace
|
15
18
|
module Nocode
|
19
|
+
# Default consumer entrypoint into the library.
|
16
20
|
class << self
|
17
21
|
def execute(yaml, io: $stdout)
|
18
22
|
Executor.new(yaml, io: io).execute
|
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.5
|
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
|
@@ -151,6 +165,7 @@ files:
|
|
151
165
|
- ".rubocop.yml"
|
152
166
|
- ".tool-versions"
|
153
167
|
- CHANGELOG.md
|
168
|
+
- CODE_OF_CONDUCT.md
|
154
169
|
- Gemfile
|
155
170
|
- Guardfile
|
156
171
|
- LICENSE
|
@@ -161,14 +176,24 @@ files:
|
|
161
176
|
- lib/nocode.rb
|
162
177
|
- lib/nocode/context.rb
|
163
178
|
- lib/nocode/executor.rb
|
164
|
-
- lib/nocode/options_template.rb
|
165
179
|
- lib/nocode/step.rb
|
166
180
|
- lib/nocode/step_registry.rb
|
167
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
|
168
187
|
- lib/nocode/steps/delete.rb
|
188
|
+
- lib/nocode/steps/deserialize/csv.rb
|
189
|
+
- lib/nocode/steps/deserialize/json.rb
|
190
|
+
- lib/nocode/steps/deserialize/yaml.rb
|
169
191
|
- lib/nocode/steps/io/read.rb
|
170
192
|
- lib/nocode/steps/io/write.rb
|
171
193
|
- lib/nocode/steps/log.rb
|
194
|
+
- lib/nocode/steps/serialize/csv.rb
|
195
|
+
- lib/nocode/steps/serialize/json.rb
|
196
|
+
- lib/nocode/steps/serialize/yaml.rb
|
172
197
|
- lib/nocode/steps/set.rb
|
173
198
|
- lib/nocode/steps/sleep.rb
|
174
199
|
- lib/nocode/util.rb
|
@@ -176,6 +201,7 @@ files:
|
|
176
201
|
- lib/nocode/util/class_loader.rb
|
177
202
|
- lib/nocode/util/class_registry.rb
|
178
203
|
- lib/nocode/util/dictionary.rb
|
204
|
+
- lib/nocode/util/object_template.rb
|
179
205
|
- lib/nocode/util/optionable.rb
|
180
206
|
- lib/nocode/util/string_template.rb
|
181
207
|
- lib/nocode/version.rb
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Nocode
|
4
|
-
class OptionsTemplate
|
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
|