assemblr 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a56be997b4579f72b563f67e40f81a2aaaec974dc2568a345528e9171b0813c1
4
+ data.tar.gz: 2d08815cb4b1c2d2245c75a9337a7586dfa2ef7e646ed726d8ed6527c90166c6
5
+ SHA512:
6
+ metadata.gz: 7571588a2718e3ed241d328b5d25d9fd9efb75054bee707c27d65b549e3dd944e909dc715d93958a0eeb18386cffead01d92306a800a3957d6b4dae5aeac9fa9
7
+ data.tar.gz: cb10006737feca4ee02d42af22b6d06e9fc4fea7ae258d374f27e3363c37bcc03abf3877280cf29812d52ff9ae2fc2d4f5f5677752468dbca6d5f29b82f03a70
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /.idea/
10
+ /examples/
11
+
12
+ /Gemfile.lock
13
+ /.rakeTasks
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in assemblr.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Ryan Burmeister-Morrison
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,84 @@
1
+ # Assemblr
2
+
3
+ A small DSL for the construction of quick automation tasks.
4
+
5
+ **NOTE:** This version is just a prototype. It will be completely re-written.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'assemblr'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install assemblr
22
+
23
+ ## Usage
24
+
25
+ This is an example of a simple assemblr script:
26
+
27
+ ```ruby
28
+ # frozen_string_literal: true
29
+
30
+ require 'assemblr'
31
+
32
+ # Configuration options. The values listed below are the defaults for assemblr.
33
+ set :default_user, 'root' # set the default user nodes are assigned to
34
+ set :default_group, 'default' # set the default group nodes are assigned to
35
+ set :log, true # turn on or off built-in logging.
36
+ set :quit_on_error, true # quit if anything goes wrong.
37
+
38
+ group :dev_servers do
39
+ # Define nodes. All nodes defined in this block will be added to the
40
+ # specified group (:dev_servers). After the block ends, the default user and
41
+ # group will be restored for any other top-level node definitions.
42
+ node '10.0.0.173'
43
+ node '10.0.0.332'
44
+ node '10.0.1.22'
45
+ end
46
+
47
+ node '10.0.0.33' # assigned to group :default and user 'root' as per the
48
+ # defaults
49
+
50
+ group :dev_servers do
51
+ upload 'Gemfile', '/tmp/Gemmm' # upload a file to all nodes in a group
52
+ upload 'lib', '/tmp',
53
+ recursive: true # upload a directory to all nodes in a group
54
+
55
+ remote_exec 'echo "Hello, world!"'
56
+ end
57
+
58
+ upload_to '10.0.2.43', 'root', 'message.txt', '/home/user/message.txt'
59
+ result = remote_exec_on 'ruby', inject: ['puts "Hello, world!"', 'exit']
60
+
61
+ puts result
62
+ ```
63
+
64
+ ## Development
65
+
66
+ After checking out the repo, run `bin/setup` to install dependencies. You can
67
+ also run `bin/console` for an interactive prompt that will allow you to
68
+ experiment.
69
+
70
+ To install this gem onto your local machine, run `bundle exec rake install`. To
71
+ release a new version, update the version number in `version.rb`, and then run
72
+ `bundle exec rake release`, which will create a git tag for the version, push
73
+ git commits and tags, and push the `.gem` file to
74
+ [rubygems.org](https://rubygems.org).
75
+
76
+ ## Contributing
77
+
78
+ Bug reports and pull requests are welcome on GitHub at
79
+ https://github.com/rburmorrison/assemblr.
80
+
81
+ ## License
82
+
83
+ The gem is available as open source under the terms of the [MIT
84
+ License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ task default: :spec
5
+
6
+ task 'rubocop' do
7
+ sh 'rubocop *'
8
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/assemblr/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'assemblr'
7
+ spec.version = Assemblr::VERSION
8
+ spec.authors = ['Ryan Burmeister-Morrison']
9
+ spec.email = ['rburmeistermorrison@gmail.com']
10
+
11
+ spec.summary = 'A small DSL for the construction of quick automation tasks.'
12
+ spec.homepage = 'https://github.com/rburmorrison/assemblr'
13
+ spec.license = 'MIT'
14
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/rburmorrison/assemblr'
18
+
19
+ # Specify which files should be added to the gem when it is released. The `git
20
+ # ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match(%r{^(test|spec|features)/})
24
+ end
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_development_dependency 'rake', '~> 12.0'
31
+ spec.add_development_dependency 'rubocop', '~> 0.80'
32
+
33
+ spec.add_runtime_dependency 'net-ping', '~> 2.0'
34
+ spec.add_runtime_dependency 'net-scp', '~> 2.0'
35
+ spec.add_runtime_dependency 'net-ssh', '~> 5.2'
36
+ spec.add_runtime_dependency 'tty-logger', '~> 0.3'
37
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'assemblr'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'assemblr/version'
4
+ require 'assemblr/logging'
5
+ require 'assemblr/utilities'
6
+
7
+ require 'open3'
8
+ require 'net/ssh'
9
+ require 'net/scp'
10
+
11
+ module Assemblr
12
+ $VERBOSE = nil # turn off warnings from external libraries
13
+
14
+ # Local variables are prefixed with an 'm' for 'Module' as to not clash with
15
+ # method names.
16
+ m_nodes = []
17
+ m_current_group = 'default'
18
+ m_current_user = 'root'
19
+
20
+ ##
21
+ # Set a configuration option.
22
+ #
23
+ # Available options are:
24
+ #
25
+ # - *default_user* - the default user to assign nodes to
26
+ # - *default_group* - the default group to assign nodes to
27
+ # - *log* - turn on or off logging
28
+ # - *quit_on_error* - quit if any error occur
29
+ #
30
+ # @return [void]
31
+ define_method :set do |key, value|
32
+ options = %i[log quit_on_error default_user default_group]
33
+ unless options.include?(key)
34
+ raise ArgumentError, "#{key} is not a valid option"
35
+ end
36
+
37
+ key = :current_user if key == :default_user
38
+ key = :current_group if key == :default_group
39
+
40
+ info %(config: set "#{key}" to "#{value}")
41
+ eval "m_#{key} = value", binding, __FILE__, __LINE__
42
+ end
43
+
44
+ ##
45
+ # @!method local_exec(cmd, inject: [])
46
+ # Execute a command locally.
47
+ #
48
+ # @param cmd [String] the command to run locally
49
+ # @param inject [Array<String>] strings to inject into stdin
50
+ #
51
+ # @return [Array(String, Process::Status)]
52
+ define_method :local_exec do |cmd, inject: []|
53
+ log_string = "running `#{cmd}` locally"
54
+ log_string += " with these inputs: #{inject}" unless inject.empty?
55
+ info log_string
56
+
57
+ result = ''
58
+ status = nil
59
+ Open3.popen2e(cmd) do |i, o, wait|
60
+ inject.each do |line|
61
+ line = line.strip
62
+ i.write line + "\n"
63
+ end
64
+ i.close
65
+ result = o.read
66
+ status = wait.value
67
+ end
68
+
69
+ if status.exitstatus != 0
70
+ code = status.exitstatus
71
+ error "local command `#{cmd}` exited with a status of #{code}"
72
+ else
73
+ success "local command `#{cmd}` executed successfully"
74
+ end
75
+
76
+ return result, status
77
+ end
78
+
79
+ ##
80
+ # Execute a command on a remote machine using SSH. It returns the combined
81
+ # stdout and stderr data.
82
+ #
83
+ # @param ip [String] the ip of the node to run the command on
84
+ # @param user [String] the user to log in as
85
+ # @param cmd [String] the command to execute remotely
86
+ # @param inject [Array<String>] strings to inject into stdin
87
+ #
88
+ # @return [String]
89
+ # @return [Array<String>]
90
+ def remote_exec_on(ip, user, cmd, inject: [])
91
+ info "attempting to execute `#{cmd}` on #{user}@#{ip}"
92
+
93
+ result = ''
94
+ Net::SSH.start(ip, user) do |ssh|
95
+ ch = ssh.open_channel do |ch|
96
+ ch.exec(cmd) do |ch, success|
97
+ error "unable to execute `#{cmd}` on #{user}@#{ip}" unless success
98
+
99
+ # Collect stdout data.
100
+ ch.on_data do |_, data|
101
+ result += data if data.is_a?(String)
102
+ end
103
+
104
+ # Collect stderr data.
105
+ ch.on_extended_data do |_, _, data|
106
+ result += data if data.is_a?(String)
107
+ end
108
+
109
+ inject.each do |line|
110
+ ch.send_data(line + "\n")
111
+ end
112
+
113
+ ch.on_request 'exit-status' do |_, data|
114
+ code = data.read_long
115
+ if code.zero?
116
+ success "`#{cmd}` executed successfully on #{user}@#{ip}"
117
+ else
118
+ error "`#{cmd}` returned status code #{code} on #{user}@#{ip}"
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ ch.wait
125
+ end
126
+ result
127
+ end
128
+
129
+ ##
130
+ # Upload a file to a node.
131
+ #
132
+ # @param ip [String] the node to upload to
133
+ # @param user [String] the user to authenticate as
134
+ # @param src [String] the file to upload
135
+ # @param dest [String] the location to upload to
136
+ # @param recursive [Bool] recursively upload files in a directory
137
+ # @param timeout [Integer] how many seconds to wait before throwing an error
138
+ #
139
+ # @return [void]
140
+ def upload_to(ip, user, src, dest, recursive: false, timeout: 5)
141
+ info %(attempting to upload "#{src}" to #{user}@#{ip}:#{dest})
142
+ Timeout.timeout timeout do
143
+ Net::SCP.upload!(ip, user, src, dest, recursive: recursive)
144
+ end
145
+ rescue StandardError
146
+ error %(failed to upload "#{src}" to #{user}@#{ip}:#{dest})
147
+ rescue Timeout::Error
148
+ error %(failed to upload "#{src}" to #{user}@#{ip}:#{dest} within #{timeout} seconds)
149
+ else
150
+ success %(uploaded "#{src}" to #{user}@#{ip}:#{dest})
151
+ end
152
+
153
+ ##
154
+ # @!method current_nodes()
155
+ # Get all nodes in the current group.
156
+ #
157
+ # @return [void]
158
+ define_method :current_nodes do
159
+ return nodes if m_current_group == 'all'
160
+
161
+ nodes.select { |n| n[:group] == m_current_group }
162
+ end
163
+
164
+ ##
165
+ # Execute a command on all machines within the current group.
166
+ #
167
+ # @param cmd [String] the command to execute
168
+ # @param inject [Array<String>] strings to inject into stdin
169
+ #
170
+ # @return [String] the combined output of the command
171
+ def remote_exec(cmd, inject: [])
172
+ results = []
173
+ current_nodes.each do |n|
174
+ result = remote_exec_on(n[:ip], n[:user], cmd, inject: inject)
175
+ results << result
176
+ end
177
+ results
178
+ end
179
+
180
+ ##
181
+ # Upload a file or directory to all machines within the current group.
182
+ #
183
+ # @param src [String] the file to upload
184
+ # @param dest [String] the location to upload the file to
185
+ # @param recursive [Bool] recursively upload files in a directory
186
+ # @param timeout [Integer] how many seconds to wait before throwing an error
187
+ #
188
+ # @return [void]
189
+ def upload(src, dest, recursive: false, timeout: 5)
190
+ current_nodes.each do |n|
191
+ upload_to(n[:ip], n[:user], src, dest, recursive: recursive)
192
+ end
193
+ end
194
+
195
+ ##
196
+ # @!method group(group, &block)
197
+ # Temporarily change current_group to refer to the specified group while
198
+ # within the passed block.
199
+ #
200
+ # @param group [Symbol] the group to temporarily switch to
201
+ #
202
+ # @return [void]
203
+ define_method :group do |group, &block|
204
+ info %(entered group block with group "#{group}")
205
+
206
+ # Temporarily switch to the target group.
207
+ old_current_group = m_current_group
208
+ m_current_group = group.to_s
209
+
210
+ block.call
211
+
212
+ m_current_group = old_current_group
213
+
214
+ info %(exited group block with group "#{group}")
215
+ end
216
+
217
+ ##
218
+ # @!method current_group
219
+ # Get the current default group.
220
+ #
221
+ # @return [String]
222
+
223
+ ##
224
+ # @!method current_user
225
+ # Get the current default user.
226
+ #
227
+ # @return [String]
228
+ %w[group user].each do |item|
229
+ define_method "current_#{item}" do
230
+ eval "m_current_#{item}"
231
+ end
232
+ end
233
+
234
+ ##
235
+ # @!method node(ip, group: current_group, user: current_user)
236
+ # Add a node to the list of nodes.
237
+ #
238
+ # @param ip [String] the ip address of the node
239
+ # @param group [Symbol] the group to assign the node to
240
+ # @param user [String] the user to associate with the node
241
+ #
242
+ # @return [Hash{ip=>String, group=>String, user=>String}] the added node
243
+ define_method :node do |ip, group: m_current_group, user: m_current_user|
244
+ group = group.to_s
245
+ if group == 'all'
246
+ error = ArgumentError.new('nodes can not be assigned to the "all" group')
247
+ raise error
248
+ end
249
+
250
+ l_node = { ip: ip, group: group, user: user }
251
+ m_nodes << l_node
252
+ info "added node: #{l_node}"
253
+
254
+ l_node
255
+ end
256
+
257
+ ##
258
+ # @!method nodes()
259
+ # Get all registered nodes.
260
+ #
261
+ # @return [Array<Hash{ip=>String, group=>String, user=>String}>]
262
+ define_method :nodes do
263
+ m_nodes
264
+ end
265
+
266
+ define_method :nodes_reachable? do
267
+ reachable = true
268
+ m_nodes.each do |node|
269
+ reachable &&= reachable?(node[:ip])
270
+ end
271
+ reachable
272
+ end
273
+ end
274
+
275
+ extend Assemblr
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-logger'
4
+
5
+ module Assemblr
6
+ Logger = TTY::Logger.new do |config|
7
+ config.metadata = %i[time date]
8
+ end
9
+
10
+ m_log = true
11
+ m_quit_on_error = true
12
+
13
+ ##
14
+ # @!method info(message)
15
+ # Logs an info message.
16
+ #
17
+ # @return [void]
18
+
19
+ ##
20
+ # @!method success(message)
21
+ # Logs a success message.
22
+ #
23
+ # @return [void]
24
+
25
+ ##
26
+ # @!method warn(message)
27
+ # Logs a warning message.
28
+ #
29
+ # @return [void]
30
+ %i[info success warn].each do |level|
31
+ define_method level do |*args, &block|
32
+ return unless m_log
33
+
34
+ Logger.send(level, *args, &block)
35
+ end
36
+ end
37
+
38
+ ##
39
+ # @!method error(message)
40
+ # Logs a error message. If configured, this will end the task with error code
41
+ # 1.
42
+ #
43
+ # @return [void]
44
+ define_method :error do |*args, &block|
45
+ Logger.send(:error, *args, &block) if m_log
46
+ exit 1 if m_quit_on_error
47
+ end
48
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'net/ping'
5
+
6
+ ##
7
+ # A collection of utility functions for assemblr.
8
+ module Assemblr
9
+ ##
10
+ # Retrieve the first ip address that is not '127.0.0.1' on the local machine.
11
+ #
12
+ # @param pattern [Regexp] a pattern to test the ips against
13
+ def get_local_ip(pattern = //)
14
+ Socket.ip_address_list.each do |ip|
15
+ address = ip.ip_address
16
+ next if address == '127.0.0.1'
17
+ return address if address.match?(pattern)
18
+ end
19
+ ''
20
+ end
21
+
22
+ ##
23
+ # Check if a remote machine can be contacted.
24
+ #
25
+ # @return [Bool]
26
+ def reachable?(ip)
27
+ external = Net::Ping::External.new(ip)
28
+
29
+ info %(attempting to contact host "#{ip}"...)
30
+ reachable = external.ping || external.ping6
31
+ if reachable
32
+ success %(host "#{ip}" is reachable)
33
+ else
34
+ error %(unable to contact host "#{ip}")
35
+ end
36
+
37
+ reachable
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Assemblr
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: assemblr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Burmeister-Morrison
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '12.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '12.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.80'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.80'
41
+ - !ruby/object:Gem::Dependency
42
+ name: net-ping
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: net-scp
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: net-ssh
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-logger
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.3'
97
+ description:
98
+ email:
99
+ - rburmeistermorrison@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - assemblr.gemspec
110
+ - bin/console
111
+ - bin/setup
112
+ - lib/assemblr.rb
113
+ - lib/assemblr/logging.rb
114
+ - lib/assemblr/utilities.rb
115
+ - lib/assemblr/version.rb
116
+ homepage: https://github.com/rburmorrison/assemblr
117
+ licenses:
118
+ - MIT
119
+ metadata:
120
+ homepage_uri: https://github.com/rburmorrison/assemblr
121
+ source_code_uri: https://github.com/rburmorrison/assemblr
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 2.3.0
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.1.2
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: A small DSL for the construction of quick automation tasks.
141
+ test_files: []