nocode 0.0.5 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
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