nocode 0.0.5 → 0.0.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8726547b06744c6a7870a8634901b344e41a65bb1ede23c7e68e99d2218d482f
4
- data.tar.gz: 3462f703d7639662c0da4f96ec9913749698c7f7a08a58387671db909a88785f
3
+ metadata.gz: f199fcae3341fccc2e54c1c139ba2c7e65a20dda27d2be31c79de02f9ec2558a
4
+ data.tar.gz: c10d64f895d34e567b846c52727205d1394641adcd68b1b1d2707c509577700c
5
5
  SHA512:
6
- metadata.gz: f7bcfd37d152798a753801944a6a4787e3ad83a08fcfca0b40d9a0957cd7c5142a298adbb64215ed2e161c9e7f842b89ab460f6374fb8ec552a3482aa0b712eb
7
- data.tar.gz: 6e024f679e2973888d5bb67c20988c8e93b296a6b6c4dbc66f7cb1f55d8638db48842834fd00c2a6d24050d9aa61950666107ff285a469e3f63835f74e7a5e7d
6
+ metadata.gz: 1a53bcec0412b9b8be57d9cb61601db583aa210542f9372c05d4cdee0e693861d05e017a697103253a2d522a2253e609481ded8e0524362ccaf6640a9d13f9d3
7
+ data.tar.gz: 131855e439116d1718cf46c53cc32597187aeae36156d71fff508fa4a0a20739a9965b055874aadc93cae4a5dd3fbf3f4b0201d5f7c75a9d1d63ed60e9150044
data/CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
+ #### 0.0.9 - February 18th, 2022
2
+
3
+ * Replace StringTemplate with Nay library.
4
+ #### 0.0.8 - February 14th, 2022
5
+
6
+ * Add `map` step to iterate and collect a dataset result.
7
+ * Add `record/map` step to iterate over a hash.
8
+ * Add `io/list` to populate a register with the contents of a directory.
9
+ * Add `io/delete` to delete a file specified in the path option.
10
+ #### 0.0.7 - February 13th, 2022
11
+
12
+ * Move shared logging logic to context (until a first class log writer emerges).
13
+ * Provide type_prefix option for class_registry
14
+
15
+ #### 0.0.6 - February 13th, 2022
16
+
17
+ * Expose registers to main YAML configuration.
18
+ * Add `each` step to serve as an example of an iterator.
19
+
1
20
  #### 0.0.5 - February 13th, 2022
2
21
 
3
- * Added initial Dataset steps.
22
+ * Added initial `dataset` steps.
4
23
 
5
24
  #### 0.0.4 - February 13th, 2022
6
25
 
data/exe/nocode CHANGED
@@ -4,6 +4,8 @@
4
4
  require 'bundler/setup'
5
5
  require 'nocode'
6
6
 
7
+ require 'pry'
8
+
7
9
  path = ARGV[0]
8
10
 
9
11
  if path.to_s.empty?
@@ -4,7 +4,12 @@ module Nocode
4
4
  # Describes the environment for each running step. An instance is initialized when a job
5
5
  # kicks off and then is passed from step to step.
6
6
  class Context
7
- attr_reader :io, :parameters, :registers
7
+ PARAMETERS_KEY = 'parameters'
8
+ REGISTERS_KEY = 'registers'
9
+
10
+ attr_reader :io,
11
+ :parameters,
12
+ :registers
8
13
 
9
14
  def initialize(io: $stdout, parameters: {}, registers: {})
10
15
  @io = io || $stdout
@@ -24,9 +29,17 @@ module Nocode
24
29
 
25
30
  def to_h
26
31
  {
27
- 'registers' => registers,
28
- 'parameters' => parameters
32
+ REGISTERS_KEY => registers,
33
+ PARAMETERS_KEY => parameters
29
34
  }
30
35
  end
36
+
37
+ def log_line
38
+ log('-' * 50)
39
+ end
40
+
41
+ def log(msg)
42
+ io.puts(msg)
43
+ end
31
44
  end
32
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
@@ -10,8 +10,8 @@ module Nocode
10
10
  class StepRegistry < Util::ClassRegistry
11
11
  include Singleton
12
12
 
13
- PREFIX = 'Nocode::Steps::'
14
- DIR = File.join(__dir__, 'steps')
13
+ CLASS_PREFIX = 'Nocode::Steps::'
14
+ DIR = File.join(__dir__, 'steps')
15
15
 
16
16
  class << self
17
17
  extend Forwardable
@@ -27,7 +27,7 @@ module Nocode
27
27
  files_loaded = Util::ClassLoader.new(DIR).load!
28
28
 
29
29
  # Class the parent to load up the registry with the files we found.
30
- load(files_loaded, PREFIX)
30
+ load(files_loaded, class_prefix: CLASS_PREFIX)
31
31
  end
32
32
  end
33
33
 
@@ -0,0 +1,47 @@
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
+ def perform
16
+ entries.each_with_index do |entry, index|
17
+ registers[element_key] = entry
18
+ registers[index_key] = index
19
+
20
+ execute_steps
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def execute_steps
27
+ StepsExecutor.new(context: context, steps: steps).execute
28
+ end
29
+
30
+ def entries
31
+ array(registers[register_option])
32
+ end
33
+
34
+ def steps
35
+ array(steps_option)
36
+ end
37
+
38
+ def element_key
39
+ "#{element_register_prefix_option}_element"
40
+ end
41
+
42
+ def index_key
43
+ "#{element_register_prefix_option}_index"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nocode
4
+ module Steps
5
+ module Io
6
+ # Delete the specified path. Does nothing if the file does not exist.
7
+ class Delete < Step
8
+ option :path
9
+
10
+ def perform
11
+ return if path.to_s.empty?
12
+
13
+ FileUtils.rm_f(path) if File.exist?(path)
14
+ end
15
+
16
+ private
17
+
18
+ def path
19
+ File.join(*array(path_option))
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nocode
4
+ module Steps
5
+ module Io
6
+ # List all files in the path option. Wildcards can be used.
7
+ #
8
+ # Mechanic: https://ruby-doc.org/core-2.5.0/Dir.html#method-c-glob
9
+ class List < Step
10
+ option :path,
11
+ :register
12
+
13
+ def perform
14
+ registers[register_option] = Dir[path].reject { |p| File.directory?(p) }
15
+ end
16
+
17
+ private
18
+
19
+ def path
20
+ File.join(*array(path_option))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,52 @@
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
+ #
9
+ # The main difference between this and 'each' is that this will collect the iterator
10
+ # element register and set the register to this new collection.
11
+ class Map < Step
12
+ option :element_register_prefix,
13
+ :register,
14
+ :steps
15
+
16
+ skip_options_evaluation!
17
+
18
+ def perform
19
+ registers[register_option] = entries.map.with_index do |entry, index|
20
+ registers[element_key] = entry
21
+ registers[index_key] = index
22
+
23
+ execute_steps
24
+
25
+ registers[element_key]
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def execute_steps
32
+ StepsExecutor.new(context: context, steps: steps).execute
33
+ end
34
+
35
+ def entries
36
+ array(registers[register_option])
37
+ end
38
+
39
+ def steps
40
+ array(steps_option)
41
+ end
42
+
43
+ def element_key
44
+ "#{element_register_prefix_option}_element"
45
+ end
46
+
47
+ def index_key
48
+ "#{element_register_prefix_option}_index"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nocode
4
+ module Steps
5
+ module Record
6
+ # Create a new hash from an existing hash mapping each key as configured by the
7
+ # key_mappings option. The key_mappings option should be in the form of:
8
+ # new_key => old_key
9
+ class Map < Step
10
+ option :key_mappings, :register
11
+
12
+ def perform
13
+ input = registers[register_option] || {}
14
+ output = {}
15
+
16
+ (key_mappings_option || {}).each do |to, from|
17
+ output[to.to_s] = input[from.to_s]
18
+ end
19
+
20
+ registers[register_option] = output
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -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
@@ -20,13 +20,13 @@ module Nocode
20
20
  freeze
21
21
  end
22
22
 
23
- def load(types, prefix = '')
23
+ def load(types, class_prefix: '', type_prefix: '')
24
24
  types.each do |type|
25
25
  pascal_cased = type.split(File::SEPARATOR).map do |part|
26
26
  part.split('_').collect(&:capitalize).join
27
27
  end.join('::')
28
28
 
29
- register(type, "#{prefix}#{pascal_cased}")
29
+ register("#{type_prefix}#{type}", "#{class_prefix}#{pascal_cased}")
30
30
  end
31
31
 
32
32
  self
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'string_template'
4
-
5
3
  module Nocode
6
4
  module Util
7
5
  # Built on top of StringTemplate but instead of only working for a string, this will
@@ -34,7 +32,7 @@ module Nocode
34
32
  [recursive_evaluate(k, values), recursive_evaluate(v, values)]
35
33
  end
36
34
  when String
37
- Util::StringTemplate.new(expression).evaluate(values)
35
+ Nay.evaluate(expression, values)
38
36
  else
39
37
  expression
40
38
  end
@@ -23,6 +23,14 @@ module Nocode
23
23
 
24
24
  # Class-level DSL Methods
25
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
+
26
34
  def option(*values)
27
35
  values.each { |v| options << v.to_s }
28
36
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nocode
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.9'
5
5
  end
data/lib/nocode.rb CHANGED
@@ -4,6 +4,7 @@ require 'benchmark'
4
4
  require 'csv'
5
5
  require 'fileutils'
6
6
  require 'json'
7
+ require 'nay'
7
8
  require 'singleton'
8
9
  require 'time'
9
10
  require 'yaml'
@@ -12,14 +13,14 @@ require 'yaml'
12
13
  require 'nocode/util'
13
14
 
14
15
  # Core
15
- require 'nocode/executor'
16
+ require 'nocode/job_executor'
16
17
 
17
18
  # Establish main top-level namespace
18
19
  module Nocode
19
20
  # Default consumer entrypoint into the library.
20
21
  class << self
21
22
  def execute(yaml, io: $stdout)
22
- Executor.new(yaml, io: io).execute
23
+ JobExecutor.new(yaml, io: io).execute
23
24
  end
24
25
  end
25
26
  end
data/nocode.gemspec CHANGED
@@ -29,6 +29,8 @@ Gem::Specification.new do |s|
29
29
 
30
30
  s.required_ruby_version = '>= 2.6'
31
31
 
32
+ s.add_dependency('nay', '~>0.0', '>=0.0.2')
33
+
32
34
  s.add_development_dependency('bundler-audit')
33
35
  s.add_development_dependency('guard-rspec')
34
36
  s.add_development_dependency('pry')
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nocode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.9
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-14 00:00:00.000000000 Z
11
+ date: 2022-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nay
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.0.2
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: bundler-audit
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -175,7 +195,7 @@ files:
175
195
  - exe/nocode
176
196
  - lib/nocode.rb
177
197
  - lib/nocode/context.rb
178
- - lib/nocode/executor.rb
198
+ - lib/nocode/job_executor.rb
179
199
  - lib/nocode/step.rb
180
200
  - lib/nocode/step_registry.rb
181
201
  - lib/nocode/steps/copy.rb
@@ -188,14 +208,20 @@ files:
188
208
  - lib/nocode/steps/deserialize/csv.rb
189
209
  - lib/nocode/steps/deserialize/json.rb
190
210
  - lib/nocode/steps/deserialize/yaml.rb
211
+ - lib/nocode/steps/each.rb
212
+ - lib/nocode/steps/io/delete.rb
213
+ - lib/nocode/steps/io/list.rb
191
214
  - lib/nocode/steps/io/read.rb
192
215
  - lib/nocode/steps/io/write.rb
193
216
  - lib/nocode/steps/log.rb
217
+ - lib/nocode/steps/map.rb
218
+ - lib/nocode/steps/record/map.rb
194
219
  - lib/nocode/steps/serialize/csv.rb
195
220
  - lib/nocode/steps/serialize/json.rb
196
221
  - lib/nocode/steps/serialize/yaml.rb
197
222
  - lib/nocode/steps/set.rb
198
223
  - lib/nocode/steps/sleep.rb
224
+ - lib/nocode/steps_executor.rb
199
225
  - lib/nocode/util.rb
200
226
  - lib/nocode/util/arrayable.rb
201
227
  - lib/nocode/util/class_loader.rb
@@ -203,7 +229,6 @@ files:
203
229
  - lib/nocode/util/dictionary.rb
204
230
  - lib/nocode/util/object_template.rb
205
231
  - lib/nocode/util/optionable.rb
206
- - lib/nocode/util/string_template.rb
207
232
  - lib/nocode/version.rb
208
233
  - nocode.gemspec
209
234
  homepage: https://github.com/mattruggio/nocode
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'context'
4
- require_relative 'step_registry'
5
-
6
- module Nocode
7
- # Manages the lifecycle and executes a job.
8
- class Executor
9
- NAME_KEY = 'name'
10
- OPTIONS_KEY = 'options'
11
- PARAMETERS_KEY = 'parameters'
12
- STEPS_KEY = 'steps'
13
- TYPE_KEY = 'type'
14
-
15
- attr_reader :yaml, :io
16
-
17
- def initialize(yaml, io: $stdout)
18
- @yaml = yaml.respond_to?(:read) ? yaml.read : yaml
19
- @yaml = YAML.safe_load(@yaml) || {}
20
- @io = io
21
-
22
- freeze
23
- end
24
-
25
- def execute
26
- steps = yaml[STEPS_KEY] || []
27
- parameters = yaml[PARAMETERS_KEY] || {}
28
- context = Context.new(io: io, parameters: parameters)
29
-
30
- log_title
31
-
32
- steps.each do |step|
33
- step_instance = make_step(step, context)
34
-
35
- execute_step(step_instance)
36
- end
37
-
38
- log("Ended: #{DateTime.now}")
39
- log_line
40
-
41
- context
42
- end
43
-
44
- private
45
-
46
- def make_step(step, context)
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)
52
-
53
- step_class.new(
54
- options: Util::Dictionary.new(options),
55
- context: context,
56
- name: name,
57
- type: type
58
- )
59
- end
60
-
61
- def execute_step(step)
62
- log(step.name) unless step.name.empty?
63
- log("Step: #{step.type}")
64
- log("Class: #{step.class}")
65
-
66
- time_in_seconds = Benchmark.measure { step.perform }.real
67
-
68
- log("Completed in #{time_in_seconds.round(3)} second(s)")
69
-
70
- log_line
71
- end
72
-
73
- def log_title
74
- log_line
75
-
76
- log('Nocode Execution')
77
- log("Started: #{DateTime.now}")
78
-
79
- log_line
80
- end
81
-
82
- def log_line
83
- log('-' * 50)
84
- end
85
-
86
- def log(msg)
87
- io.puts(msg)
88
- end
89
- end
90
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nocode
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!"
10
- class StringTemplate
11
- LEFT_TOKEN = '<<'
12
- RIGHT_TOKEN = '>>'
13
- SEPARATOR = '.'
14
- REG_EXPR = /#{Regexp.quote(LEFT_TOKEN)}(.*?)#{Regexp.quote(RIGHT_TOKEN)}/.freeze
15
-
16
- attr_reader :expression
17
-
18
- def initialize(expression)
19
- @expression = expression.to_s
20
-
21
- freeze
22
- end
23
-
24
- def evaluate(values = {})
25
- resolved = tokens_to_values(tokens, values)
26
-
27
- tokens.inject(expression) do |memo, token|
28
- memo.gsub("#{LEFT_TOKEN}#{token}#{RIGHT_TOKEN}", resolved[token].to_s)
29
- end
30
- end
31
-
32
- private
33
-
34
- def tokens
35
- expression.to_s.scan(REG_EXPR).flatten
36
- end
37
-
38
- def tokens_to_values(tokens, values)
39
- tokens.each_with_object({}) do |token, memo|
40
- cleansed = token.strip
41
- parts = cleansed.split(SEPARATOR)
42
- value = values.dig(*parts)
43
-
44
- memo[token] = value
45
- end
46
- end
47
- end
48
- end
49
- end