nocode 0.0.4 → 0.0.8

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: 884ab5737629463437ef0991555e1ea30faa8db0cd6d8c79da712942aa69011b
4
- data.tar.gz: d8562fdf11b2a204e9ced685b434d6959bafe2dca605dd3795355aeed1ed5238
3
+ metadata.gz: f42cd4574b36e4098fbb2c3de7263d8530ee2c935ec1d86da30950c51f45998e
4
+ data.tar.gz: a1259cd1075a946a056db27b2a3da5c7acebee2002c0f1e01b2f6a5fef203fa9
5
5
  SHA512:
6
- metadata.gz: 677aa00aba3a998a4cc616002420c9366ac945fabdfc336d948b5158c24eae6c9bfc1b8a7ee658718c5c55af4148c4f83656fddf8b45995002471ef7224870da
7
- data.tar.gz: e4de53676eaebb1d9bd3738dbbbb6df777ce0fdf1674e9aa370176c2eca9d42d4672d6edd682a313b943f24d149566491d1d7322d9c30ed0cc352db7730e0f3f
6
+ metadata.gz: 7be2f97365a7d2494e956362074d47c07cf39a741af34299b2bd30e70c2a74f8bf7566a24de816b4af692deede762c4e03ba9b2e1f8ddd81cf161a263e6e6f1b
7
+ data.tar.gz: '0099f90f5b0a569f2fe95b93d16aa5e737f957830bcfcb84d598f362c21edec9c759e113f0d01049a5ecfa05bb17d5e70ec49436b5c558a66f0e194d8c32ff28'
data/CHANGELOG.md CHANGED
@@ -1,7 +1,28 @@
1
+
2
+ #### 0.0.8 - February 14th, 2022
3
+
4
+ * Add `map` step to iterate and collect a dataset result.
5
+ * Add `record/map` step to iterate over a hash.
6
+ * Add `io/list` to populate a register with the contents of a directory.
7
+ * Add `io/delete` to delete a file specified in the path option.
8
+ #### 0.0.7 - February 13th, 2022
9
+
10
+ * Move shared logging logic to context (until a first class log writer emerges).
11
+ * Provide type_prefix option for class_registry
12
+
13
+ #### 0.0.6 - February 13th, 2022
14
+
15
+ * Expose registers to main YAML configuration.
16
+ * Add `each` step to serve as an example of an iterator.
17
+
18
+ #### 0.0.5 - February 13th, 2022
19
+
20
+ * Added initial `dataset` steps.
21
+
1
22
  #### 0.0.4 - February 13th, 2022
2
23
 
3
24
  * Increase class documentation
4
- * Add YAML serialization/deserializationß
25
+ * Add YAML serialization/deserialization
5
26
 
6
27
  #### 0.0.3 - February 12th, 2022
7
28
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  #### Execute Ruby code through YAML
4
4
 
5
- [![Gem Version](https://badge.fury.io/rb/nocode.svg)](https://badge.fury.io/rb/nocode) [![Ruby Gem CI](https://github.com/mattruggio/nocode/actions/workflows/rubygem.yml/badge.svg)](https://github.com/mattruggio/nocode/actions/workflows/rubygem.yml)
5
+ [![Gem Version](https://badge.fury.io/rb/nocode.svg)](https://badge.fury.io/rb/nocode) [![Ruby Gem CI](https://github.com/mattruggio/nocode/actions/workflows/rubygem.yml/badge.svg)](https://github.com/mattruggio/nocode/actions/workflows/rubygem.yml) [![Maintainability](https://api.codeclimate.com/v1/badges/66479dae44129c87dc88/maintainability)](https://codeclimate.com/github/mattruggio/nocode/maintainability) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
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
8
 
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,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
@@ -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
@@ -27,14 +27,8 @@ module Nocode
27
27
 
28
28
  if object.is_a?(Array)
29
29
  csv << object
30
-
31
- true
32
30
  elsif object.respond_to?(:values)
33
31
  csv << object.values
34
-
35
- true
36
- else
37
- false
38
32
  end
39
33
  end
40
34
  end
@@ -3,6 +3,8 @@
3
3
  module Nocode
4
4
  module Steps
5
5
  # Sleep for an arbitrary number of seconds.
6
+ #
7
+ # Mechanic: https://apidock.com/ruby/Kernel/sleep
6
8
  class Sleep < Step
7
9
  option :seconds
8
10
 
@@ -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
@@ -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.4'
4
+ VERSION = '0.0.8'
5
5
  end
data/lib/nocode.rb CHANGED
@@ -12,14 +12,14 @@ require 'yaml'
12
12
  require 'nocode/util'
13
13
 
14
14
  # Core
15
- require 'nocode/executor'
15
+ require 'nocode/job_executor'
16
16
 
17
17
  # Establish main top-level namespace
18
18
  module Nocode
19
19
  # Default consumer entrypoint into the library.
20
20
  class << self
21
21
  def execute(yaml, io: $stdout)
22
- Executor.new(yaml, io: io).execute
22
+ JobExecutor.new(yaml, io: io).execute
23
23
  end
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nocode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.8
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-13 00:00:00.000000000 Z
11
+ date: 2022-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler-audit
@@ -175,22 +175,33 @@ files:
175
175
  - exe/nocode
176
176
  - lib/nocode.rb
177
177
  - lib/nocode/context.rb
178
- - lib/nocode/executor.rb
178
+ - lib/nocode/job_executor.rb
179
179
  - lib/nocode/step.rb
180
180
  - lib/nocode/step_registry.rb
181
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
182
187
  - lib/nocode/steps/delete.rb
183
188
  - lib/nocode/steps/deserialize/csv.rb
184
189
  - lib/nocode/steps/deserialize/json.rb
185
190
  - lib/nocode/steps/deserialize/yaml.rb
191
+ - lib/nocode/steps/each.rb
192
+ - lib/nocode/steps/io/delete.rb
193
+ - lib/nocode/steps/io/list.rb
186
194
  - lib/nocode/steps/io/read.rb
187
195
  - lib/nocode/steps/io/write.rb
188
196
  - lib/nocode/steps/log.rb
197
+ - lib/nocode/steps/map.rb
198
+ - lib/nocode/steps/record/map.rb
189
199
  - lib/nocode/steps/serialize/csv.rb
190
200
  - lib/nocode/steps/serialize/json.rb
191
201
  - lib/nocode/steps/serialize/yaml.rb
192
202
  - lib/nocode/steps/set.rb
193
203
  - lib/nocode/steps/sleep.rb
204
+ - lib/nocode/steps_executor.rb
194
205
  - lib/nocode/util.rb
195
206
  - lib/nocode/util/arrayable.rb
196
207
  - lib/nocode/util/class_loader.rb
@@ -1,84 +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
- 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 = Util::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