retrospec 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +90 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/available_plugins.yaml +5 -0
- data/bin/retrospec +25 -0
- data/config.yaml.sample +3 -0
- data/lib/retrospec.rb +50 -0
- data/lib/retrospec/cli.rb +22 -0
- data/lib/retrospec/config.rb +54 -0
- data/lib/retrospec/exceptions.rb +8 -0
- data/lib/retrospec/plugin_loader.rb +53 -0
- data/lib/retrospec/plugins.rb +62 -0
- data/lib/retrospec/plugins/v1.rb +1 -0
- data/lib/retrospec/plugins/v1/instance.rb +44 -0
- data/lib/retrospec/plugins/v1/module_helpers.rb +121 -0
- data/retrospec.gemspec +88 -0
- data/spec/cli_spec.rb +5 -0
- data/spec/config_spec.rb +21 -0
- data/spec/plugin_loader_spec.rb +5 -0
- data/spec/plugins_spec.rb +5 -0
- data/spec/retrospec_spec.rb +6 -0
- data/spec/spec_helper.rb +31 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 08b7e319301663802a82b062d861645c924ee032
|
4
|
+
data.tar.gz: bb0d02ff8279e82049534b87f33612fb4b0d8b81
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 48db65f55ba4eee36885cade5bfda62b0e41b65be3a2caacd8104f41b54b57b15e05e474bc24c94b24901e51b4277d141cf15452361d344ab30dffb43a0a69b1
|
7
|
+
data.tar.gz: 5f94d2b8aaf93a880ebc028c09609a241d0af629530874c573e99ac458d2d0e6268e79b13c048c2ffee53f13444441220786d3139acc30e8a46d81b08017ff70
|
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
gem 'trollop'
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 3.2"
|
10
|
+
gem "rdoc", "~> 3.12"
|
11
|
+
gem "bundler", "~> 1.0"
|
12
|
+
gem "jeweler", "~> 2.0.1"
|
13
|
+
gem "simplecov", ">= 0"
|
14
|
+
gem 'pry'
|
15
|
+
gem "fakefs", :require => "fakefs/safe"
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.3.8)
|
5
|
+
builder (3.2.2)
|
6
|
+
coderay (1.1.0)
|
7
|
+
descendants_tracker (0.0.4)
|
8
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
9
|
+
diff-lcs (1.2.5)
|
10
|
+
docile (1.1.5)
|
11
|
+
fakefs (0.6.7)
|
12
|
+
faraday (0.9.1)
|
13
|
+
multipart-post (>= 1.2, < 3)
|
14
|
+
git (1.2.9.1)
|
15
|
+
github_api (0.12.4)
|
16
|
+
addressable (~> 2.3)
|
17
|
+
descendants_tracker (~> 0.0.4)
|
18
|
+
faraday (~> 0.8, < 0.10)
|
19
|
+
hashie (>= 3.4)
|
20
|
+
multi_json (>= 1.7.5, < 2.0)
|
21
|
+
nokogiri (~> 1.6.6)
|
22
|
+
oauth2
|
23
|
+
hashie (3.4.2)
|
24
|
+
highline (1.7.7)
|
25
|
+
jeweler (2.0.1)
|
26
|
+
builder
|
27
|
+
bundler (>= 1.0)
|
28
|
+
git (>= 1.2.5)
|
29
|
+
github_api
|
30
|
+
highline (>= 1.6.15)
|
31
|
+
nokogiri (>= 1.5.10)
|
32
|
+
rake
|
33
|
+
rdoc
|
34
|
+
json (1.8.3)
|
35
|
+
jwt (1.5.1)
|
36
|
+
method_source (0.8.2)
|
37
|
+
mini_portile (0.6.2)
|
38
|
+
multi_json (1.11.2)
|
39
|
+
multi_xml (0.5.5)
|
40
|
+
multipart-post (2.0.0)
|
41
|
+
nokogiri (1.6.6.2)
|
42
|
+
mini_portile (~> 0.6.0)
|
43
|
+
oauth2 (1.0.0)
|
44
|
+
faraday (>= 0.8, < 0.10)
|
45
|
+
jwt (~> 1.0)
|
46
|
+
multi_json (~> 1.3)
|
47
|
+
multi_xml (~> 0.5)
|
48
|
+
rack (~> 1.2)
|
49
|
+
pry (0.10.1)
|
50
|
+
coderay (~> 1.1.0)
|
51
|
+
method_source (~> 0.8.1)
|
52
|
+
slop (~> 3.4)
|
53
|
+
rack (1.6.4)
|
54
|
+
rake (10.4.2)
|
55
|
+
rdoc (3.12.2)
|
56
|
+
json (~> 1.4)
|
57
|
+
rspec (3.3.0)
|
58
|
+
rspec-core (~> 3.3.0)
|
59
|
+
rspec-expectations (~> 3.3.0)
|
60
|
+
rspec-mocks (~> 3.3.0)
|
61
|
+
rspec-core (3.3.2)
|
62
|
+
rspec-support (~> 3.3.0)
|
63
|
+
rspec-expectations (3.3.1)
|
64
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
65
|
+
rspec-support (~> 3.3.0)
|
66
|
+
rspec-mocks (3.3.2)
|
67
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
68
|
+
rspec-support (~> 3.3.0)
|
69
|
+
rspec-support (3.3.0)
|
70
|
+
simplecov (0.10.0)
|
71
|
+
docile (~> 1.1.0)
|
72
|
+
json (~> 1.8)
|
73
|
+
simplecov-html (~> 0.10.0)
|
74
|
+
simplecov-html (0.10.0)
|
75
|
+
slop (3.6.0)
|
76
|
+
thread_safe (0.3.5)
|
77
|
+
trollop (2.1.2)
|
78
|
+
|
79
|
+
PLATFORMS
|
80
|
+
ruby
|
81
|
+
|
82
|
+
DEPENDENCIES
|
83
|
+
bundler (~> 1.0)
|
84
|
+
fakefs
|
85
|
+
jeweler (~> 2.0.1)
|
86
|
+
pry
|
87
|
+
rdoc (~> 3.12)
|
88
|
+
rspec (~> 3.2)
|
89
|
+
simplecov
|
90
|
+
trollop
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Corey Osman
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= retrospec
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to retrospec
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
9
|
+
* Fork the project.
|
10
|
+
* Start a feature/bugfix branch.
|
11
|
+
* Commit and push until you are happy with your contribution.
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2015 Corey Osman. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
17
|
+
gem.name = "retrospec"
|
18
|
+
gem.homepage = "http://github.com/nwops/retrospec"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{A devops framework for automating your development workflow}
|
21
|
+
gem.description = %Q{Retrospec is a framework that allows the automation of repetitive file creation with just about any kind of language through the use of a pluggable architecture.}
|
22
|
+
gem.email = "corey@logicminds.biz"
|
23
|
+
gem.authors = ["Corey Osman"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Code coverage detail"
|
35
|
+
task :simplecov do
|
36
|
+
ENV['COVERAGE'] = "true"
|
37
|
+
Rake::Task['spec'].execute
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rdoc/task'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "retrospec #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/retrospec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative '../lib/retrospec/cli'
|
3
|
+
require 'trollop'
|
4
|
+
|
5
|
+
opts = Trollop::options do
|
6
|
+
version "0.1.0 (c) Corey Osman"
|
7
|
+
banner <<-EOS
|
8
|
+
A framework to automate your development workflow by generating common files and test patterns.
|
9
|
+
|
10
|
+
|
11
|
+
EOS
|
12
|
+
opt :module_path, "The path (relative or absolute) to the module directory (Defaults to current directory) " ,
|
13
|
+
:type => :string, :required => false, :default => nil
|
14
|
+
opt :installed_plugins, "List the installed plugins", :type => :boolean, :require => false
|
15
|
+
opt :available_plugins, "Show an online list of available plugins", :type => :boolean, :require => false
|
16
|
+
|
17
|
+
end
|
18
|
+
if opts[:installed_plugins]
|
19
|
+
Retrospec::Cli.list_installed_plugins
|
20
|
+
end
|
21
|
+
|
22
|
+
if opts[:available_plugins]
|
23
|
+
Retrospec::Cli.list_available_plugins
|
24
|
+
end
|
25
|
+
|
data/config.yaml.sample
ADDED
data/lib/retrospec.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'retrospec/plugins'
|
2
|
+
require 'retrospec/exceptions'
|
3
|
+
require 'retrospec/config'
|
4
|
+
|
5
|
+
module Retrospec
|
6
|
+
class Module
|
7
|
+
include Retrospec::Plugins
|
8
|
+
# module path is the relative or absolute path to the module that we should retrofit
|
9
|
+
# opts hash contains additional flags and options that can be user to control the creation of the tests
|
10
|
+
# opts[:config_map]
|
11
|
+
def initialize(supplied_module_path, opts={})
|
12
|
+
# locates the plugin class that can be used with this module directory
|
13
|
+
begin
|
14
|
+
opts[:name] ||= File.basename(supplied_module_path) # use the name or derive it from the dir name
|
15
|
+
plugin_class = find_plugin_type(supplied_module_path, opts[:name])
|
16
|
+
# load any save data in the config file
|
17
|
+
config_data = Retrospec::Config.config_data(opts[:config_map])
|
18
|
+
plugin_data = Retrospec::Config.plugin_context(config_data, plugin_class.send(:plugin_name))
|
19
|
+
# merge the passed in options
|
20
|
+
plugin_data.merge!(opts)
|
21
|
+
# create the instance of the plugin
|
22
|
+
plugin = plugin_class.send(:new, supplied_module_path, plugin_data)
|
23
|
+
plugin.run
|
24
|
+
rescue NoSuitablePluginFoundException
|
25
|
+
puts "No gem was found to support this code type, please install a gem that supports this module.".fatal
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# finds a suitable plugin given the name of the plugin or via a supported discovery method
|
30
|
+
def find_plugin_type(module_path, name=nil)
|
31
|
+
if name
|
32
|
+
# when the user wants to create a module give the module type
|
33
|
+
discover_plugin_by_name(name)
|
34
|
+
else
|
35
|
+
discover_plugin(module_path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class String
|
42
|
+
def red; "\033[31m#{self}\033[0m" end
|
43
|
+
def green; "\033[32m#{self}\033[0m" end
|
44
|
+
def cyan; "\033[36m#{self}\033[0m" end
|
45
|
+
def yellow; "\033[33m#{self}\033[0m" end
|
46
|
+
def warning; yellow end
|
47
|
+
def fatal; red end
|
48
|
+
def info; green end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'plugins'
|
2
|
+
|
3
|
+
module Retrospec
|
4
|
+
class Cli
|
5
|
+
include Retrospec::Plugins
|
6
|
+
|
7
|
+
def self.list_available_plugins
|
8
|
+
Retrospec::Cli.new.available_plugins.each do |name, plugin_data|
|
9
|
+
puts "#{name}: #{plugin_data['project_url']}"
|
10
|
+
puts "\tDescription: #{plugin_data['description']}"
|
11
|
+
puts "\tInstallation: gem install #{plugin_data['gem']} --no-rdoc --no-ri"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.list_installed_plugins
|
16
|
+
Retrospec::Cli.new.installed_plugins do |spec|
|
17
|
+
puts "#{spec.name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'retrospec/plugins/v1/module_helpers'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Retrospec
|
6
|
+
class Config
|
7
|
+
include Retrospec::Plugins::V1::ModuleHelpers
|
8
|
+
include Retrospec::Plugins::V1
|
9
|
+
attr_accessor :config_file
|
10
|
+
|
11
|
+
# we should be able to lookup where the user stores the config map
|
12
|
+
# so the user doesn't have to pass this info each time
|
13
|
+
def initialize(file=nil, opts={})
|
14
|
+
setup_config_file(file)
|
15
|
+
end
|
16
|
+
|
17
|
+
# create a blank yaml config file it file does not exist
|
18
|
+
def setup_config_file(file=nil)
|
19
|
+
if file.nil? or ! File.exists?(file)
|
20
|
+
# config does not exist
|
21
|
+
setup_config_dir
|
22
|
+
dst_file = File.join(default_retrospec_dir, 'config.yaml.sample')
|
23
|
+
src_file = File.join(gem_dir,'config.yaml.sample')
|
24
|
+
safe_copy_file(src_file, dst_file)
|
25
|
+
file = dst_file
|
26
|
+
end
|
27
|
+
@config_file = file
|
28
|
+
end
|
29
|
+
|
30
|
+
# loads the config data into a ruby object
|
31
|
+
def config_data
|
32
|
+
@config_data ||= YAML.load_file(config_file)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.config_data(file)
|
36
|
+
new(file).config_data
|
37
|
+
end
|
38
|
+
|
39
|
+
# returns the configs that are only related to the plugin name
|
40
|
+
def self.plugin_context(config, plugin_name)
|
41
|
+
context = config.select {|k,v| k.downcase =~ /#{plugin_name}/ }
|
42
|
+
end
|
43
|
+
|
44
|
+
def gem_dir
|
45
|
+
File.expand_path("../../../", __FILE__)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def setup_config_dir
|
51
|
+
FileUtils.mkdir_p(File.expand_path(default_retrospec_dir)) unless File.directory?(default_retrospec_dir)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Retrospec
|
4
|
+
module PluginLoader
|
5
|
+
# Internal: Find any gems containing retrospec plugins and load the main file in them.
|
6
|
+
#
|
7
|
+
# Returns nothing.
|
8
|
+
def self.load_from_gems(version='v1')
|
9
|
+
retorspec_plugin_paths = gem_directories.select { |path| (path + File.join('retrospec','plugins')).directory? }
|
10
|
+
retorspec_plugin_paths.each do |gem_path|
|
11
|
+
Dir[File.join(gem_path,'*.rb')].each do |file|
|
12
|
+
load file
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Internal: Retrieve a list of available gem paths from RubyGems.
|
18
|
+
#
|
19
|
+
# Returns an Array of Pathname objects.
|
20
|
+
def self.gem_directories
|
21
|
+
if has_rubygems?
|
22
|
+
gemspecs.reject { |spec| spec.name == 'retrospec' }.map do |spec|
|
23
|
+
Pathname.new(spec.full_gem_path) + 'lib'
|
24
|
+
end
|
25
|
+
else
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# returns a list of retrospec gem plugin specs
|
31
|
+
def self.retrospec_gem_list
|
32
|
+
gemspecs.reject { |spec| spec.name == 'retrospec' or ! File.directory?(File.join(spec.full_gem_path,'lib','retrospec','plugins')) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Internal: Check if RubyGems is loaded and available.
|
36
|
+
#
|
37
|
+
# Returns true if RubyGems is available, false if not.
|
38
|
+
def self.has_rubygems?
|
39
|
+
defined? ::Gem
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Retrieve a list of available gemspecs.
|
43
|
+
#
|
44
|
+
# Returns an Array of Gem::Specification objects.
|
45
|
+
def self.gemspecs
|
46
|
+
@gemspecs ||= if Gem::Specification.respond_to?(:latest_specs)
|
47
|
+
Gem::Specification.latest_specs
|
48
|
+
else
|
49
|
+
Gem.searcher.init_gemspecs
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative 'plugin_loader'
|
2
|
+
require_relative 'plugins/v1'
|
3
|
+
require 'yaml'
|
4
|
+
require_relative 'exceptions'
|
5
|
+
|
6
|
+
module Retrospec
|
7
|
+
module Plugins
|
8
|
+
# loads the plugins (all of them)
|
9
|
+
def load_plugins
|
10
|
+
Retrospec::PluginLoader.load_from_gems('v1')
|
11
|
+
end
|
12
|
+
|
13
|
+
# returns an array of plugin classes by looking in the object space for all loaded classes
|
14
|
+
# that start with Retrospec::Plugins::V1
|
15
|
+
def plugin_classes
|
16
|
+
unless @plugin_classes
|
17
|
+
load_plugins
|
18
|
+
@plugin_classes = ObjectSpace.each_object(Class).find_all {|c| c.name =~ /Retrospec::Plugins/}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def installed_plugins
|
23
|
+
Retrospec::PluginLoader.retrospec_gem_list
|
24
|
+
end
|
25
|
+
|
26
|
+
# returns the first plugin class that supports this module directory
|
27
|
+
# not sure what to do when we find multiple plugins
|
28
|
+
# would need additional criteria
|
29
|
+
def discover_plugin(module_path)
|
30
|
+
plugin = plugin_classes.find {|c| c.send(:valid_module_dir?, module_path) }
|
31
|
+
raise NoSuitablePluginFoundException unless plugin
|
32
|
+
plugin
|
33
|
+
end
|
34
|
+
|
35
|
+
def discover_plugin_by_name(name)
|
36
|
+
plugin = plugin_classes.find {|c| c.send(:plugin_name, name) }
|
37
|
+
raise NoSuitablePluginFoundException unless plugin
|
38
|
+
plugin
|
39
|
+
end
|
40
|
+
|
41
|
+
def available_plugins
|
42
|
+
get_remote_data('https://raw.githubusercontent.com/nwops/retrospec/master/available_plugins.yaml')
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_remote_data(url)
|
46
|
+
require "net/https"
|
47
|
+
require "uri"
|
48
|
+
uri = URI.parse(url)
|
49
|
+
if uri.kind_of?(URI::HTTP) or uri.kind_of?(URI::HTTPS)
|
50
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
51
|
+
http.use_ssl = true
|
52
|
+
#http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
53
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
54
|
+
response = http.request(request)
|
55
|
+
YAML.load(response.body)
|
56
|
+
else
|
57
|
+
{}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'v1/instance'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Retrospec
|
2
|
+
module Plugin
|
3
|
+
module V1
|
4
|
+
attr_accessor :module_path, :module_name, :module_dir_name, :config
|
5
|
+
attr_reader :files, :plugin_name
|
6
|
+
|
7
|
+
# validates that the module meets the plugins criteria
|
8
|
+
# returns boolean true if module files are valid, false otherwise
|
9
|
+
# validates module directory fits the description of this plugin
|
10
|
+
def self.valid_module_dir?(dir)
|
11
|
+
if ! File.exist?(dir)
|
12
|
+
false
|
13
|
+
else
|
14
|
+
module_files ||= Dir.glob("#{dir}/**/*#{file_type}")
|
15
|
+
if module_files.length < 1
|
16
|
+
false
|
17
|
+
else
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# sets the config which should be a hash
|
24
|
+
def config=(config_map)
|
25
|
+
@config = config_map
|
26
|
+
end
|
27
|
+
|
28
|
+
# the name of the plugin, defaults to the name of the class
|
29
|
+
def self.plugin_name
|
30
|
+
self.class.downcase
|
31
|
+
end
|
32
|
+
|
33
|
+
# the main file type that is used to help discover what the module is
|
34
|
+
def self.file_type
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
|
38
|
+
def run
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'erb'
|
3
|
+
require 'find'
|
4
|
+
module Retrospec
|
5
|
+
module Plugins
|
6
|
+
module V1
|
7
|
+
module ModuleHelpers
|
8
|
+
# only creates a directory if the directory doesn't already exist
|
9
|
+
def safe_mkdir(dir)
|
10
|
+
if File.exists? dir
|
11
|
+
unless File.directory? dir
|
12
|
+
$stderr.puts "!! #{dir} already exists and is not a directory".fatal
|
13
|
+
end
|
14
|
+
else
|
15
|
+
FileUtils.mkdir_p dir
|
16
|
+
puts " + #{dir}/".info
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# copy the symlink and preserve the link
|
21
|
+
def safe_create_symlink(src,dest)
|
22
|
+
if File.exists? dest
|
23
|
+
$stderr.puts "!! #{dest} already exists and differs from template".warning
|
24
|
+
else
|
25
|
+
FileUtils.copy_entry(src,dest)
|
26
|
+
puts " + #{dest}".info
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# safely copy and existing file to another dest
|
31
|
+
def safe_copy_file(src, dest)
|
32
|
+
if File.exists?(dest) and not File.zero?(dest)
|
33
|
+
$stderr.puts "!! #{dest} already exists".warning
|
34
|
+
else
|
35
|
+
if not File.exists?(src)
|
36
|
+
safe_touch(src)
|
37
|
+
else
|
38
|
+
safe_mkdir(File.dirname(dest))
|
39
|
+
FileUtils.cp(src,dest)
|
40
|
+
end
|
41
|
+
puts " + #{dest}".info
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# touch a file, this is useful for setting up trigger files
|
46
|
+
def safe_touch(file)
|
47
|
+
if File.exists? file
|
48
|
+
unless File.file? file
|
49
|
+
$stderr.puts "!! #{file} already exists and is not a regular file".fatal
|
50
|
+
end
|
51
|
+
else
|
52
|
+
FileUtils.touch file
|
53
|
+
puts " + #{file}".info
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# safely creates a file and does not override the existing file
|
58
|
+
def safe_create_file(filepath, content)
|
59
|
+
if File.exists? filepath
|
60
|
+
old_content = File.read(filepath)
|
61
|
+
# if we did a better comparison of content we could be smarter about when we create files
|
62
|
+
if old_content != content or not File.zero?(filepath)
|
63
|
+
$stderr.puts "!! #{filepath} already exists and differs from template".warning
|
64
|
+
end
|
65
|
+
else
|
66
|
+
safe_mkdir(File.dirname(filepath)) unless File.exists? File.dirname(filepath)
|
67
|
+
File.open(filepath, 'w') do |f|
|
68
|
+
f.puts content
|
69
|
+
end
|
70
|
+
puts " + #{filepath}".info
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# the directory where the config, repos, and other info are saved
|
75
|
+
def default_retrospec_dir
|
76
|
+
File.expand_path(File.join(ENV['HOME'], '.retrospec' ))
|
77
|
+
end
|
78
|
+
|
79
|
+
def retrospec_repos_dir
|
80
|
+
File.join(default_retrospec_dir, 'repos')
|
81
|
+
end
|
82
|
+
|
83
|
+
# path is the full path of the file to create
|
84
|
+
# template is the full path to the template file
|
85
|
+
# spec_object is any bindable object which the templates uses for context
|
86
|
+
def safe_create_template_file(path, template, spec_object)
|
87
|
+
# check to ensure parent directory exists
|
88
|
+
file_dir_path = File.expand_path(File.dirname(path))
|
89
|
+
if ! File.exists?(file_dir_path)
|
90
|
+
safe_mkdir(file_dir_path)
|
91
|
+
end
|
92
|
+
File.open(template) do |file|
|
93
|
+
renderer = ERB.new(file.read, 0, '-')
|
94
|
+
content = renderer.result spec_object.get_binding
|
95
|
+
dest_path = File.expand_path(path)
|
96
|
+
safe_create_file(dest_path, content)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# creates any file that is contained in the templates/modules_files directory structure
|
101
|
+
# loops through the directory looking for erb files or other files.
|
102
|
+
# strips the erb extension and renders the template to the current module path
|
103
|
+
# filenames must named how they would appear in the normal module path. The directory
|
104
|
+
# structure where the file is contained
|
105
|
+
def safe_create_module_files(template_dir, module_path, spec_object)
|
106
|
+
templates = Find.find(File.join(template_dir,'module_files')).find_all.sort
|
107
|
+
templates.each do |template|
|
108
|
+
dest = template.gsub(File.join(template_dir,'module_files'), module_path).gsub('.erb', '')
|
109
|
+
if File.symlink?(template)
|
110
|
+
safe_create_symlink(template, dest)
|
111
|
+
elsif File.directory?(template)
|
112
|
+
safe_mkdir(dest)
|
113
|
+
else
|
114
|
+
safe_create_template_file(dest, template, spec_object)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/retrospec.gemspec
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "retrospec"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Corey Osman"]
|
12
|
+
s.date = "2015-09-25"
|
13
|
+
s.description = "Retrospec is a framework that allows the automation of repetitive file creation with just about any kind of language through the use of a pluggable architecture."
|
14
|
+
s.email = "corey@logicminds.biz"
|
15
|
+
s.executables = ["retrospec"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE.txt",
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".document",
|
22
|
+
".rspec",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"available_plugins.yaml",
|
30
|
+
"bin/retrospec",
|
31
|
+
"config.yaml.sample",
|
32
|
+
"lib/retrospec.rb",
|
33
|
+
"lib/retrospec/cli.rb",
|
34
|
+
"lib/retrospec/config.rb",
|
35
|
+
"lib/retrospec/exceptions.rb",
|
36
|
+
"lib/retrospec/plugin_loader.rb",
|
37
|
+
"lib/retrospec/plugins.rb",
|
38
|
+
"lib/retrospec/plugins/v1.rb",
|
39
|
+
"lib/retrospec/plugins/v1/instance.rb",
|
40
|
+
"lib/retrospec/plugins/v1/module_helpers.rb",
|
41
|
+
"retrospec.gemspec",
|
42
|
+
"spec/cli_spec.rb",
|
43
|
+
"spec/config_spec.rb",
|
44
|
+
"spec/plugin_loader_spec.rb",
|
45
|
+
"spec/plugins_spec.rb",
|
46
|
+
"spec/retrospec_spec.rb",
|
47
|
+
"spec/spec_helper.rb"
|
48
|
+
]
|
49
|
+
s.homepage = "http://github.com/nwops/retrospec"
|
50
|
+
s.licenses = ["MIT"]
|
51
|
+
s.require_paths = ["lib"]
|
52
|
+
s.rubygems_version = "2.0.14"
|
53
|
+
s.summary = "A devops framework for automating your development workflow"
|
54
|
+
|
55
|
+
if s.respond_to? :specification_version then
|
56
|
+
s.specification_version = 4
|
57
|
+
|
58
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
59
|
+
s.add_runtime_dependency(%q<trollop>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<rspec>, ["~> 3.2"])
|
61
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
62
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
63
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
64
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<pry>, [">= 0"])
|
66
|
+
s.add_development_dependency(%q<fakefs>, [">= 0"])
|
67
|
+
else
|
68
|
+
s.add_dependency(%q<trollop>, [">= 0"])
|
69
|
+
s.add_dependency(%q<rspec>, ["~> 3.2"])
|
70
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
71
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
72
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
73
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
74
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
75
|
+
s.add_dependency(%q<fakefs>, [">= 0"])
|
76
|
+
end
|
77
|
+
else
|
78
|
+
s.add_dependency(%q<trollop>, [">= 0"])
|
79
|
+
s.add_dependency(%q<rspec>, ["~> 3.2"])
|
80
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
81
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
82
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
83
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
84
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
85
|
+
s.add_dependency(%q<fakefs>, [">= 0"])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
data/spec/cli_spec.rb
ADDED
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'retrospec/config'
|
3
|
+
|
4
|
+
|
5
|
+
describe 'config' do
|
6
|
+
let(:config_obj) do
|
7
|
+
Retrospec::Config.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it do
|
11
|
+
expect(config_obj.config_data['plugins::puppet::templates::ref']).to eq('master')
|
12
|
+
expect(config_obj.config_data).to be_a Hash
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns the correct context' do
|
16
|
+
context = Retrospec::Config.plugin_context(config_obj.config_data, 'puppet')
|
17
|
+
expect(context.keys.count).to eq(2)
|
18
|
+
expect(context['plugins::puppet::templates::ref']).to eq('master')
|
19
|
+
expect(context).to be_a Hash
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
#require 'fakefs'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module SimpleCov::Configuration
|
6
|
+
def clean_filters
|
7
|
+
@filters = []
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
SimpleCov.configure do
|
12
|
+
clean_filters
|
13
|
+
load_adapter 'test_frameworks'
|
14
|
+
end
|
15
|
+
|
16
|
+
ENV["COVERAGE"] && SimpleCov.start do
|
17
|
+
add_filter "/.rvm/"
|
18
|
+
end
|
19
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
20
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
21
|
+
|
22
|
+
require 'rspec'
|
23
|
+
require 'retrospec'
|
24
|
+
|
25
|
+
# Requires supporting files with custom matchers and macros, etc,
|
26
|
+
# in ./support/ and its subdirectories.
|
27
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
28
|
+
|
29
|
+
RSpec.configure do |config|
|
30
|
+
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: retrospec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Corey Osman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: trollop
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rdoc
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.12'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: jeweler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.0.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.0.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: fakefs
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Retrospec is a framework that allows the automation of repetitive file
|
126
|
+
creation with just about any kind of language through the use of a pluggable architecture.
|
127
|
+
email: corey@logicminds.biz
|
128
|
+
executables:
|
129
|
+
- retrospec
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files:
|
132
|
+
- LICENSE.txt
|
133
|
+
- README.rdoc
|
134
|
+
files:
|
135
|
+
- .document
|
136
|
+
- .rspec
|
137
|
+
- Gemfile
|
138
|
+
- Gemfile.lock
|
139
|
+
- LICENSE.txt
|
140
|
+
- README.rdoc
|
141
|
+
- Rakefile
|
142
|
+
- VERSION
|
143
|
+
- available_plugins.yaml
|
144
|
+
- bin/retrospec
|
145
|
+
- config.yaml.sample
|
146
|
+
- lib/retrospec.rb
|
147
|
+
- lib/retrospec/cli.rb
|
148
|
+
- lib/retrospec/config.rb
|
149
|
+
- lib/retrospec/exceptions.rb
|
150
|
+
- lib/retrospec/plugin_loader.rb
|
151
|
+
- lib/retrospec/plugins.rb
|
152
|
+
- lib/retrospec/plugins/v1.rb
|
153
|
+
- lib/retrospec/plugins/v1/instance.rb
|
154
|
+
- lib/retrospec/plugins/v1/module_helpers.rb
|
155
|
+
- retrospec.gemspec
|
156
|
+
- spec/cli_spec.rb
|
157
|
+
- spec/config_spec.rb
|
158
|
+
- spec/plugin_loader_spec.rb
|
159
|
+
- spec/plugins_spec.rb
|
160
|
+
- spec/retrospec_spec.rb
|
161
|
+
- spec/spec_helper.rb
|
162
|
+
homepage: http://github.com/nwops/retrospec
|
163
|
+
licenses:
|
164
|
+
- MIT
|
165
|
+
metadata: {}
|
166
|
+
post_install_message:
|
167
|
+
rdoc_options: []
|
168
|
+
require_paths:
|
169
|
+
- lib
|
170
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - '>='
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
requirements: []
|
181
|
+
rubyforge_project:
|
182
|
+
rubygems_version: 2.0.14
|
183
|
+
signing_key:
|
184
|
+
specification_version: 4
|
185
|
+
summary: A devops framework for automating your development workflow
|
186
|
+
test_files: []
|