shanty 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/README.md +52 -0
- data/bin/shanty +3 -0
- data/lib/shanty/cli.rb +83 -0
- data/lib/shanty/discoverer.rb +31 -0
- data/lib/shanty/discoverers/gradle.rb +18 -0
- data/lib/shanty/discoverers/shantyfile.rb +15 -0
- data/lib/shanty/global.rb +49 -0
- data/lib/shanty/graph.rb +122 -0
- data/lib/shanty/mixins/acts_as_link_graph_node.rb +81 -0
- data/lib/shanty/mixins/attr_combined_accessor.rb +24 -0
- data/lib/shanty/mixins/callbacks.rb +39 -0
- data/lib/shanty/mutator.rb +23 -0
- data/lib/shanty/mutators/git.rb +24 -0
- data/lib/shanty/plugin.rb +26 -0
- data/lib/shanty/plugins/bundler.rb +15 -0
- data/lib/shanty/plugins/rspec.rb +14 -0
- data/lib/shanty/plugins/rubocop.rb +14 -0
- data/lib/shanty/project.rb +71 -0
- data/lib/shanty/project_template.rb +53 -0
- data/lib/shanty/projects/ruby.rb +12 -0
- data/lib/shanty/projects/static.rb +21 -0
- data/lib/shanty/task.rb +45 -0
- data/lib/shanty/tasks/basic.rb +41 -0
- data/lib/shanty/util.rb +12 -0
- data/lib/shanty/vcs_range.rb +31 -0
- data/lib/shanty/vcs_ranges/local_git.rb +20 -0
- data/lib/shanty.rb +47 -0
- metadata +315 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 472f2d824a07db1b5c47ec13ddca184cad7478db
|
4
|
+
data.tar.gz: 6a45c1b9a654584efbd06d87d2c7202667095b43
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 92abe04b4e6d65338f17a637c03ff0079297b3777c3e23726d4d8f85438121ece70a02806feac64e149e24103816f734518247e6f07ee53c460b0778a802b31a
|
7
|
+
data.tar.gz: 7b7e75add8f40176c1510ffeb96453678194a8e4f7f6a6fafb9f266d1e3a211d00d5d144285ee50d72ebd0e63d8c3276d9931d6017121705ca2ab8b2feecf3f0
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Shanty [![Build Status](https://travis-ci.org/shantytown/shanty.svg?branch=master)](https://travis-ci.org/shantytown/shanty)[![Coverage Status](https://coveralls.io/repos/shantytown/shanty/badge.png?branch=master)](https://coveralls.io/r/shantytown/shanty?branch=master)
|
2
|
+
|
3
|
+
**Shanty** is a project orchestration tool. It is designed to make it easy for you to build dependencies between projects, and execute tasks across this tree of relationships, regardless of what language or technology these projects use. The aim is consistency in the way you manage, build, test and deploy your projects.
|
4
|
+
|
5
|
+
* Shanty is **language agnostic**. Shanty config files must be written in Ruby, but can be written for any type of project written in any language.
|
6
|
+
* Shanty **is not a build tool**. It is designed to work with your existing build tool, instead living a layer up where it manages tasks to call to your build tool.
|
7
|
+
* Shanty **is great for people with single repository, many subproject setups**. It makes it trivial to use your repository with many well known Continuous Integration tools and get benefits like building only changed projects, and depedency management.
|
8
|
+
* Shanty **supports plugins**. You can hook into the lifecycle of Shanty at any point to achieve the task you want. Plugins can discover projects, add support for a new VCS, add actions that will be executed when a command is run, and work out whether a project has changed or not based on more complex dependencies.
|
9
|
+
|
10
|
+
## Why Would I Want To Use Shanty Instead Of `<Insert Build Tool Here>`?
|
11
|
+
|
12
|
+
Shanty is designed to make it easy to run tasks consistently across projects of many different types or written in many different languages.
|
13
|
+
|
14
|
+
A great example use case of Shanty is integrating with Docker:
|
15
|
+
|
16
|
+
```
|
17
|
+
Foo (Java) -> Bar (Java) -> Bar Container (Docker)
|
18
|
+
```
|
19
|
+
|
20
|
+
Lets say you have a Java project `Bar` that builds an artifact. This Java project has a dependency on the artifact of another Java project called `Foo`. In order to dockerise the `Bar` project, you need to build the `Foo` dependency, then build the `Bar` project, and then build the Docker container.
|
21
|
+
|
22
|
+
Managing this in existing setups often requires cobbling together shell scripts and the build tool of your choice. Shanty is designed to manage these relationships for you, abstracting the tasks of building and linking these projects into plugins that get auto-triggered based on these relationships (think `make`). Projects like `Gradle` go a long way towards supporting this model with their multi-project support, but the expressiveness of Ruby is missed as soon as you need to do something menial like read in a JSON options file for a project.
|
23
|
+
|
24
|
+
So, with Shanty, building the Docker container would be as simple as `shanty build` in the Docker container project folder, and the rest is taken care of for you. This is because Shanty is designed to give you the freedom to choose any tool for any project, written in any language, by allowing you to plug in functionality where you need it.
|
25
|
+
|
26
|
+
## Contibuting
|
27
|
+
|
28
|
+
We welcome any contribution, whether big or small! We're busy importing our planned work as GitHub issues so people can join in the fun, we also have a [Huboard](https://huboard.com/shantytown/shanty) to make following the progress easier. If you have any questions, please hit us up on our freenode IRC channel `#shantytown`.
|
29
|
+
|
30
|
+
## License
|
31
|
+
|
32
|
+
The MIT License (MIT)
|
33
|
+
|
34
|
+
Copyright (c) 2014 Chris Jansen, Nathan Kleyn
|
35
|
+
|
36
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
37
|
+
of this software and associated documentation files (the "Software"), to deal
|
38
|
+
in the Software without restriction, including without limitation the rights
|
39
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
40
|
+
copies of the Software, and to permit persons to whom the Software is
|
41
|
+
furnished to do so, subject to the following conditions:
|
42
|
+
|
43
|
+
The above copyright notice and this permission notice shall be included in
|
44
|
+
all copies or substantial portions of the Software.
|
45
|
+
|
46
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
47
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
48
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
49
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
50
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
51
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
52
|
+
THE SOFTWARE.
|
data/bin/shanty
ADDED
data/lib/shanty/cli.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'commander'
|
2
|
+
require 'i18n'
|
3
|
+
require 'shanty/task'
|
4
|
+
|
5
|
+
module Shanty
|
6
|
+
# Public: Handle the CLI interface between the user and the registered tasks
|
7
|
+
# and plugins.
|
8
|
+
class Cli
|
9
|
+
include Commander::Methods
|
10
|
+
|
11
|
+
def initialize(graph)
|
12
|
+
@graph = graph
|
13
|
+
end
|
14
|
+
|
15
|
+
def tasks
|
16
|
+
Task.tasks.reduce({}) do |acc, task|
|
17
|
+
acc.merge(task.task_definitions)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
program :name, 'Shanty'
|
23
|
+
program :version, '0.1.0'
|
24
|
+
program :description, 'Something'
|
25
|
+
|
26
|
+
setup_tasks
|
27
|
+
run!
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def setup_tasks
|
33
|
+
tasks.each do |name, task|
|
34
|
+
setup_task(name, task)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def setup_task(name, task)
|
39
|
+
command name do |c|
|
40
|
+
c.description = I18n.t(task[:desc], default: task[:desc])
|
41
|
+
|
42
|
+
task[:options].each do |option_name, option|
|
43
|
+
c.option(syntax_for_option(option_name, option), I18n.t(option[:desc], default: option[:desc]))
|
44
|
+
end
|
45
|
+
|
46
|
+
c.action do |args, options|
|
47
|
+
execute_task(name, args, options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def execute_task(name, args, options)
|
53
|
+
task = tasks[name]
|
54
|
+
|
55
|
+
default_option_pairs = task[:options].map do |option_name, option|
|
56
|
+
[option_name, default_for_type(option)]
|
57
|
+
end
|
58
|
+
options.default(Hash[default_option_pairs])
|
59
|
+
|
60
|
+
task[:klass].new.send(name, options, @graph, *args)
|
61
|
+
end
|
62
|
+
|
63
|
+
def syntax_for_option(name, option)
|
64
|
+
syntax = case option[:type]
|
65
|
+
when :boolean
|
66
|
+
"--#{name}"
|
67
|
+
else
|
68
|
+
"--#{name} #{option[:type].upcase}"
|
69
|
+
end
|
70
|
+
|
71
|
+
option[:required] ? "[#{syntax}]" : syntax
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_for_type(option)
|
75
|
+
case option[:type]
|
76
|
+
when :boolean
|
77
|
+
option[:default] || false
|
78
|
+
else
|
79
|
+
option[:default]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'shanty/util'
|
2
|
+
require 'shanty/project_template'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
# Public: Enables discovery of different types of project
|
6
|
+
# utilises inherited class method to find all implementing
|
7
|
+
# classes
|
8
|
+
class Discoverer
|
9
|
+
class << self
|
10
|
+
attr_reader :discoverers
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.inherited(discoverer)
|
14
|
+
Util.logger.debug("Detected project discoverer #{discoverer}")
|
15
|
+
@discoverers ||= []
|
16
|
+
@discoverers << discoverer
|
17
|
+
end
|
18
|
+
|
19
|
+
def discover_all
|
20
|
+
self.class.discoverers.flat_map do |discoverer|
|
21
|
+
discoverer.new.discover
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def create_project(*args)
|
28
|
+
ProjectTemplate.new(*args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'shanty/discoverer'
|
2
|
+
require 'shanty/projects/static'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
# Gradle discoverer
|
6
|
+
class ShantyfileDiscoverer < Discoverer
|
7
|
+
def discover
|
8
|
+
Dir['**/build.gradle'].map do |path|
|
9
|
+
create_project(
|
10
|
+
File.dirname(path),
|
11
|
+
type: GradleProject,
|
12
|
+
options: { foo: 'bar' },
|
13
|
+
plugins: [GradlePlugin], parents: ['']
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'shanty/discoverer'
|
2
|
+
require 'shanty/projects/static'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
# Public: Discoverer for Shantyfiles
|
6
|
+
# will create a a project for every Shantyfile it finds in
|
7
|
+
# a directory
|
8
|
+
class ShantyfileDiscoverer < Discoverer
|
9
|
+
def discover
|
10
|
+
Dir['**/Shantyfile'].map do |path|
|
11
|
+
create_project(File.absolute_path(File.dirname(path)))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'deep_merge'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
# Global configuration variables for shanty
|
6
|
+
module Global
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def config_file
|
10
|
+
'.shanty.yml'
|
11
|
+
end
|
12
|
+
|
13
|
+
def environment
|
14
|
+
@environment = ENV['SHANTY_ENV'] || 'local'
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_config
|
18
|
+
@default_config ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_default_config(new_config)
|
22
|
+
@default_config.merge!(new_config)
|
23
|
+
end
|
24
|
+
|
25
|
+
def root
|
26
|
+
@root ||= find_root
|
27
|
+
end
|
28
|
+
|
29
|
+
def config
|
30
|
+
file_config = YAML.load_file("#{root}/#{config_file}") || {}
|
31
|
+
@config ||= default_config.deep_merge!(file_config[environment]) || default_config
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_root
|
35
|
+
if root_dir.nil?
|
36
|
+
fail "Could not find a #{Global.config_file} file in this or any parent directories. \
|
37
|
+
Please run `shanty init` in the directory you want to be the root of your project structure."
|
38
|
+
end
|
39
|
+
|
40
|
+
root_dir
|
41
|
+
end
|
42
|
+
|
43
|
+
def root_dir
|
44
|
+
Pathname.new(Dir.pwd).ascend do |d|
|
45
|
+
return d if d.join(Global.config_file).exist?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/shanty/graph.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'algorithms'
|
2
|
+
|
3
|
+
module Shanty
|
4
|
+
# Public: Represents the link graph of projects in the repository. This class is
|
5
|
+
# responsible for collecting up all the information the projects we have,
|
6
|
+
# making parent/child dependency links between them, and then calculating which
|
7
|
+
# projects need to be build by combining a Git diff with the dependency graph
|
8
|
+
# to resolve to a list of projects required to be built.
|
9
|
+
class Graph
|
10
|
+
attr_reader :projects
|
11
|
+
|
12
|
+
# Public: Initialise a ProjectLinkGraph.
|
13
|
+
#
|
14
|
+
# projects - An array of project instances to take and link together into
|
15
|
+
# a graph structure of dependencies.
|
16
|
+
def initialize(projects)
|
17
|
+
@projects = sort_projects(link_projects(projects))
|
18
|
+
|
19
|
+
@project_path_trie = Containers::Trie.new
|
20
|
+
|
21
|
+
@projects.each do |project|
|
22
|
+
@project_path_trie[project.path] = project
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: All the changed projects in the current repository.
|
27
|
+
#
|
28
|
+
# Returns an Array of Project subclasses, one for each project in the
|
29
|
+
# repository.
|
30
|
+
def changed
|
31
|
+
@projects.select { |project| project.changed? }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Returns a project, if any, with the given name.
|
35
|
+
#
|
36
|
+
# name - The name to filter by.
|
37
|
+
#
|
38
|
+
# Returns an instance of a Project subclass if found, otherwise nil.
|
39
|
+
def by_name(name)
|
40
|
+
@projects.find { |project| project.name == name }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Returns all projects of the given types.
|
44
|
+
#
|
45
|
+
# *types - One or more types to filter by.
|
46
|
+
#
|
47
|
+
# Returns an Array of Project subclasses, one for each project in the
|
48
|
+
# repository.
|
49
|
+
def all_of_type(*types)
|
50
|
+
@projects.select { |project| types.include?(project.class) }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Returns all the changed projects of the given types.
|
54
|
+
#
|
55
|
+
# *types - One or more types to filter by.
|
56
|
+
#
|
57
|
+
# Returns an Array of Project subclasses, one for each project in the
|
58
|
+
# repository.
|
59
|
+
def changed_of_type(*types)
|
60
|
+
changed.select { |project| types.include?(project.class) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Public: Returns the project, if any, that the current working directory
|
64
|
+
# belongs to.
|
65
|
+
#
|
66
|
+
# Returns an instance of a Project subclass if found, otherwise nil.
|
67
|
+
def current
|
68
|
+
owner_of_file(Dir.pwd)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: Given a path to a file or directory (normally a path obtained
|
72
|
+
# while looking at a Git diff), find the project that owns this file. This
|
73
|
+
# works by finding the project with the longest path in common with the
|
74
|
+
# file, and is very efficient because this search is backed using a Trie
|
75
|
+
# data structure. This data structure allows worst case matching of O(m)
|
76
|
+
# complexity.
|
77
|
+
#
|
78
|
+
# path - The path to the file to find the owner project.
|
79
|
+
#
|
80
|
+
# Returns a Project subclass if any found, otherwise nil.
|
81
|
+
def owner_of_file(path)
|
82
|
+
key = @project_path_trie.longest_prefix(path)
|
83
|
+
@project_path_trie[key]
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Private: Given a list of projects, construct the parent/child
|
89
|
+
# relationships between them given a list of their parents/children by name
|
90
|
+
# as defined on the project instances.
|
91
|
+
#
|
92
|
+
# projects - An array of Project subclasses to link together.
|
93
|
+
#
|
94
|
+
# Returns an Array of linked projects.
|
95
|
+
def link_projects(projects)
|
96
|
+
projects_by_name = projects.each_with_object({}) { |project, acc| acc[project.name] = project }
|
97
|
+
|
98
|
+
projects.each do |project|
|
99
|
+
parent_dependencies = project.parents_by_name.map { |parent_name| projects_by_name[parent_name] }.compact
|
100
|
+
|
101
|
+
parent_dependencies.each do |parent_dependency|
|
102
|
+
project.add_parent(parent_dependency)
|
103
|
+
parent_dependency.add_child(project)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
projects
|
108
|
+
end
|
109
|
+
|
110
|
+
# Private: Given a list of Project subclasses, sort them by their distance
|
111
|
+
# from the root node (that is, the topmost node). This order is important
|
112
|
+
# because it matches the order in which things should be build to avoid
|
113
|
+
# missing dependencies.
|
114
|
+
#
|
115
|
+
# projects - An array of Project subclasses to sort.
|
116
|
+
#
|
117
|
+
# Returns a sorted Array of Project subclasses.
|
118
|
+
def sort_projects(projects)
|
119
|
+
projects.sort { |a, b| a.distance_from_root <=> b.distance_from_root }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Shanty
|
2
|
+
module Mixins
|
3
|
+
# A mixin module enabling classes to have parents and children. It provides
|
4
|
+
# convenience methods for determining dependencies, depdenants, and a distance
|
5
|
+
# from the root node. Note that in contrast to a tree, this link graph module
|
6
|
+
# allows multiple parents.
|
7
|
+
module ActsAsLinkGraphNode
|
8
|
+
# The self.included idiom. This is described in great detail in a
|
9
|
+
# fantastic blog post here:
|
10
|
+
#
|
11
|
+
# http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/
|
12
|
+
#
|
13
|
+
# Basically, this idiom allows us to add both instance *and* class methods
|
14
|
+
# to the class that is mixing this module into itself without forcing them
|
15
|
+
# to call extend and include for this mixin. You'll see this idiom everywhere
|
16
|
+
# in the Ruby/Rails world, so we use it too.
|
17
|
+
def self.included(cls)
|
18
|
+
cls.extend(ClassMethods)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Common methods inherited by all classes
|
22
|
+
module ClassMethods
|
23
|
+
attr_writer :parents, :children
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: The parents linked to this instance.
|
27
|
+
#
|
28
|
+
# Returns an Array of Objects.
|
29
|
+
def parents
|
30
|
+
@parents ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: The children linked to this instance.
|
34
|
+
#
|
35
|
+
# Returns an Array of Objects.
|
36
|
+
def children
|
37
|
+
@children ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: Add a node to the parents linked to this instance.
|
41
|
+
#
|
42
|
+
# node - The Object to add.
|
43
|
+
def add_parent(node)
|
44
|
+
parents << node
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Add a node to the children linked to this instance.
|
48
|
+
#
|
49
|
+
# node - The Object to add.
|
50
|
+
def add_child(node)
|
51
|
+
children << node
|
52
|
+
end
|
53
|
+
|
54
|
+
# Public: Convenience method to return all of the children from this node in
|
55
|
+
# the tree downwards to the leaves of the tree.
|
56
|
+
#
|
57
|
+
# Returns an Array of child Objects.
|
58
|
+
def all_children
|
59
|
+
children + children.map(&:all_children).flatten
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public: Convenience method to return all of the parents from this node in
|
63
|
+
# the tree upwards to the root of the tree.
|
64
|
+
#
|
65
|
+
# Returns an Array of parent Objects.
|
66
|
+
def all_parents
|
67
|
+
parents + parents.map(&:all_parents).flatten
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: Calculate the maximum number of traverses that need to be made to
|
71
|
+
# reach the root from this node.
|
72
|
+
#
|
73
|
+
# Returns a Fixnum representing the traverses to the root. Note that a return
|
74
|
+
# value of 0 means this is the root (ie. it has no parents).
|
75
|
+
def distance_from_root
|
76
|
+
return 0 if parents.empty?
|
77
|
+
parents.map(&:distance_from_root).max + 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Shanty
|
2
|
+
module Mixins
|
3
|
+
# Additional accessor utility
|
4
|
+
module AttrCombinedAccessor
|
5
|
+
# Creates an invariant accessor that allows getting and setting from the
|
6
|
+
# same endpoint. It will operate in getter mode if you don't pass any
|
7
|
+
# arguments when calling it, otherwise it will work in setter mode. Useful
|
8
|
+
# when needing to chain methods (you can't chain standard attr_writer
|
9
|
+
# methods because of the `= something` part) or when trying to create a
|
10
|
+
# nice looking DSL.
|
11
|
+
def attr_combined_accessor(*syms)
|
12
|
+
syms.each do |sym|
|
13
|
+
define_method(sym) do |*args|
|
14
|
+
if args.empty?
|
15
|
+
instance_variable_get(:"@#{sym}")
|
16
|
+
else
|
17
|
+
instance_variable_set(:"@#{sym}", *args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'hooks'
|
2
|
+
|
3
|
+
module Shanty
|
4
|
+
module Mixins
|
5
|
+
# A mixin to implement publish/subscribe style callbacks in the class that
|
6
|
+
# includes this.
|
7
|
+
module Callbacks
|
8
|
+
# The self.included idiom. This is described in great detail in a
|
9
|
+
# fantastic blog post here:
|
10
|
+
#
|
11
|
+
# http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/
|
12
|
+
#
|
13
|
+
# Basically, this idiom allows us to add both instance *and* class methods
|
14
|
+
# to the class that is mixing this module into itself without forcing them
|
15
|
+
# to call extend and include for this mixin. You'll see this idiom everywhere
|
16
|
+
# in the Ruby/Rails world, so we use it too.
|
17
|
+
def self.included(cls)
|
18
|
+
cls.include(Hooks)
|
19
|
+
cls.extend(ClassMethods)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Common methods inherited by all classes
|
23
|
+
module ClassMethods
|
24
|
+
def subscribe(*names, sym)
|
25
|
+
names.each do |name|
|
26
|
+
define_hook(name, halts_on_falsey: true) unless respond_to?(name)
|
27
|
+
send(name, sym)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def publish(name, *args)
|
33
|
+
return true if self.class.callbacks_for_hook(name).nil?
|
34
|
+
results = run_hook(name, *args)
|
35
|
+
!results.halted?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'shanty/util'
|
2
|
+
|
3
|
+
module Shanty
|
4
|
+
# Public: enables mutation of the project graph
|
5
|
+
# Common usage would be to set changed flags on projects
|
6
|
+
class Mutator
|
7
|
+
class << self
|
8
|
+
attr_reader :mutators
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.inherited(mutator)
|
12
|
+
Util.logger.debug("Detected mutator #{mutator}")
|
13
|
+
@mutators ||= []
|
14
|
+
@mutators << mutator
|
15
|
+
end
|
16
|
+
|
17
|
+
def apply_mutations(graph)
|
18
|
+
self.class.mutators.reduce(graph) do |acc, mutator|
|
19
|
+
mutator.new.mutate(acc)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'shanty/mutator'
|
2
|
+
require 'shanty/vcs_ranges/local_git'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
# Git VCS mutator
|
6
|
+
class Git < Mutator
|
7
|
+
def initialize
|
8
|
+
@vcs_range = VCSRange.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def mutate(graph)
|
12
|
+
git_root = `git rev-parse --show-toplevel`.strip
|
13
|
+
diff_files = `git diff --name-only #{@vcs_range.from_commit} #{@vcs_range.to_commit}`.split("\n")
|
14
|
+
diff_files.each do |path|
|
15
|
+
next if path.nil?
|
16
|
+
path = File.join(git_root, path)
|
17
|
+
project = graph.owner_of_file(path)
|
18
|
+
project.changed = true unless project.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
graph
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'shanty/util'
|
2
|
+
require 'shanty/project_template'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
# Some basic functionality for every plugin.
|
6
|
+
module Plugin
|
7
|
+
def self.extended(mod)
|
8
|
+
mod.module_eval do
|
9
|
+
def self.included(cls)
|
10
|
+
copy_subscribes_to_class(cls)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def copy_subscribes_to_class(cls)
|
16
|
+
@subscriptions.each do |args|
|
17
|
+
cls.subscribe(*args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def subscribe(*args)
|
22
|
+
@subscriptions ||= []
|
23
|
+
@subscriptions.push(args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'shanty/plugin'
|
2
|
+
|
3
|
+
module Shanty
|
4
|
+
# Public: Bundler plugin for building ruby projects.
|
5
|
+
module BundlerPlugin
|
6
|
+
extend Plugin
|
7
|
+
|
8
|
+
subscribe :build, :bundle_install
|
9
|
+
|
10
|
+
def bundle_install
|
11
|
+
# FIXME: Add support for the --jobs argument to parallelise the bundler run.
|
12
|
+
system 'bundle install --quiet'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'shanty/mixins/acts_as_link_graph_node'
|
2
|
+
require 'shanty/mixins/callbacks'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
# Public: Represents a project in the current repository.
|
6
|
+
class Project
|
7
|
+
include Mixins::ActsAsLinkGraphNode
|
8
|
+
include Mixins::Callbacks
|
9
|
+
|
10
|
+
attr_accessor :name, :path, :options, :parents_by_name, :changed
|
11
|
+
attr_reader :changed
|
12
|
+
alias_method :changed?, :changed
|
13
|
+
|
14
|
+
# Public: Initialise the Project instance.
|
15
|
+
#
|
16
|
+
# project_template - An instance of ProjectTemplate from which to
|
17
|
+
# instantiate this project.
|
18
|
+
def initialize(project_template)
|
19
|
+
@name = project_template.name
|
20
|
+
@path = project_template.path
|
21
|
+
@options = project_template.options
|
22
|
+
@parents_by_name = project_template.parents
|
23
|
+
@changed = false
|
24
|
+
|
25
|
+
project_template.plugins.each do |plugin|
|
26
|
+
self.class.include plugin
|
27
|
+
end
|
28
|
+
|
29
|
+
instance_eval(&project_template.after_create) unless project_template.after_create.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: A list of the external dependencies this project has by name
|
33
|
+
# and version. This is used in dependency tree generation.
|
34
|
+
#
|
35
|
+
# Returns an Array of Strings representing external dependencies by name
|
36
|
+
# and version.
|
37
|
+
def externals_by_name
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: The absolute path to the artifact that would be created by this
|
42
|
+
# project when built, if any. This is expected to be overriden in subclasses.
|
43
|
+
#
|
44
|
+
# Returns a String representing the absolute path to the artifact.
|
45
|
+
def artifact_path
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Overriden String conversion method to return a simplified
|
50
|
+
# representation of this instance that doesn't include the cyclic
|
51
|
+
# parent/children attributes as defined by the ActsAsLinkGraphNode mixin.
|
52
|
+
#
|
53
|
+
# Returns a simple String representation of this instance.
|
54
|
+
def to_s
|
55
|
+
"Name: #{name}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Public: Overriden String conversion method to return a more detailed
|
59
|
+
# representation of this instance that doesn't include the cyclic
|
60
|
+
# parent/children attributes as defined by the ActsAsLinkGraphNode mixin.
|
61
|
+
#
|
62
|
+
# Returns more detailed String representation of this instance.
|
63
|
+
def inspect
|
64
|
+
{
|
65
|
+
name: name,
|
66
|
+
path: path,
|
67
|
+
options: options
|
68
|
+
}.inspect
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'shanty/mixins/attr_combined_accessor'
|
2
|
+
require 'shanty/projects/static'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
# Public: Allows creation of a project using a discoverer
|
6
|
+
class ProjectTemplate
|
7
|
+
extend Mixins::AttrCombinedAccessor
|
8
|
+
|
9
|
+
attr_combined_accessor :name, :type, :plugins, :parents, :options
|
10
|
+
attr_reader :path
|
11
|
+
|
12
|
+
def initialize(path, args = {})
|
13
|
+
fail 'Path to project must be a directory.' unless File.directory?(path)
|
14
|
+
|
15
|
+
@path = path
|
16
|
+
@name = File.basename(path)
|
17
|
+
@type = args[:type] || StaticProject
|
18
|
+
@plugins = args[:plugins] || []
|
19
|
+
@parents = args[:parents] || []
|
20
|
+
@options = args[:options] || {}
|
21
|
+
|
22
|
+
execute_shantyfile
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute_shantyfile
|
26
|
+
shantyfile_path = File.join(@path, 'Shantyfile')
|
27
|
+
|
28
|
+
return unless File.exist?(shantyfile_path)
|
29
|
+
|
30
|
+
eval(File.read(shantyfile_path))
|
31
|
+
end
|
32
|
+
|
33
|
+
def plugin(plugin)
|
34
|
+
@plugins << plugin
|
35
|
+
end
|
36
|
+
|
37
|
+
def parent(parent)
|
38
|
+
@parents << parent
|
39
|
+
end
|
40
|
+
|
41
|
+
def option(key, value)
|
42
|
+
@options[key] = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def after_create(&block)
|
46
|
+
if block.nil?
|
47
|
+
@after_create
|
48
|
+
else
|
49
|
+
@after_create = block
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'shanty/project'
|
2
|
+
|
3
|
+
module Shanty
|
4
|
+
# Public: Base type of project, simply creates a tarball of the directory
|
5
|
+
class StaticProject < Project
|
6
|
+
subscribe :build, :on_build
|
7
|
+
|
8
|
+
def on_build
|
9
|
+
# FIXME: Create a tarball of the current project.
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: The absolute path to the artifact that would be created by this
|
14
|
+
# project when built.
|
15
|
+
#
|
16
|
+
# Returns a String representing the absolute path to the artifact.
|
17
|
+
def artifact_path
|
18
|
+
"#{@root_dir}/build/#{@options['artifact_name'] || name}-#{@build_number}.tgz"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/shanty/task.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Shanty
|
2
|
+
# Public: Discover shanty tasks
|
3
|
+
class Task
|
4
|
+
class << self
|
5
|
+
attr_reader :tasks
|
6
|
+
attr_reader :task_definitions, :task_definiton
|
7
|
+
end
|
8
|
+
|
9
|
+
# This method is auto-triggred by Ruby whenever a class inherits from
|
10
|
+
# Shanty::Task. This means we can build up a list of all the tasks
|
11
|
+
# without requiring them to register with us - neat!
|
12
|
+
def self.inherited(task)
|
13
|
+
@tasks ||= []
|
14
|
+
@tasks << task
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.desc(desc)
|
18
|
+
task_definition[:desc] = desc
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.param(name, options = {})
|
22
|
+
task_definition[:params][name] = options
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.option(name, options = {})
|
26
|
+
task_definition[:options][name] = options
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.method_added(name)
|
30
|
+
@task_definitions ||= {}
|
31
|
+
@task_definitions[name] = task_definition.merge(klass: self)
|
32
|
+
|
33
|
+
# Now reset the task definition.
|
34
|
+
@task_definition = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.task_definition
|
38
|
+
@task_definition ||= {}
|
39
|
+
@task_definition[:params] ||= {}
|
40
|
+
@task_definition[:options] ||= {}
|
41
|
+
|
42
|
+
@task_definition
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'shanty/task'
|
3
|
+
require 'shanty/util'
|
4
|
+
|
5
|
+
module Shanty
|
6
|
+
# Public: A set of basic tasks that can be applied to all projects and that
|
7
|
+
# ship with the core of Shanty.
|
8
|
+
class BasicTasks < Shanty::Task
|
9
|
+
desc 'tasks.projects.desc'
|
10
|
+
option :changed, type: :boolean, desc: 'tasks.common.options.changed'
|
11
|
+
option :types, type: :array, desc: 'tasks.common.options.types'
|
12
|
+
def projects(options, graph)
|
13
|
+
graph.projects.each do |project|
|
14
|
+
next if options.changed? && !project.changed?
|
15
|
+
puts project
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'tasks.build.desc'
|
20
|
+
option :changed, type: :boolean, desc: 'tasks.common.options.changed'
|
21
|
+
option :watch, type: :boolean, desc: 'tasks.common.options.watch'
|
22
|
+
option :types, type: :array, desc: 'tasks.common.options.types'
|
23
|
+
def build(options, graph)
|
24
|
+
graph.projects.each do |project|
|
25
|
+
next if options.changed? && !project.changed?
|
26
|
+
fail I18n.t('tasks.build.failed', project: project) unless project.publish(:build)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'tasks.test.desc'
|
31
|
+
option :changed, type: :boolean, desc: 'tasks.common.options.changed'
|
32
|
+
option :watch, type: :boolean, desc: 'tasks.common.options.watch'
|
33
|
+
option :types, type: :array, desc: 'tasks.common.options.types'
|
34
|
+
def test(options, graph)
|
35
|
+
graph.projects.each do |project|
|
36
|
+
next if options.changed? && !project.changed?
|
37
|
+
fail I18n.t('tasks.test.failed', project: project) unless project.publish(:test)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/shanty/util.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'shanty/util'
|
2
|
+
|
3
|
+
module Shanty
|
4
|
+
# Public: Enables discovery of rangeerent types of CI provider
|
5
|
+
# utilises inherited class method to find all implementing
|
6
|
+
# classes
|
7
|
+
class VCSRange
|
8
|
+
class << self
|
9
|
+
attr_reader :vcs_range
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.inherited(vcs_range)
|
13
|
+
Util.logger.debug("Detected VCS range #{vcs_range}")
|
14
|
+
@vcs_range = vcs_range
|
15
|
+
end
|
16
|
+
|
17
|
+
def from_commit
|
18
|
+
vcs_range.from_commit
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_commit
|
22
|
+
vcs_range.to_commit
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def vcs_range
|
28
|
+
@vcs_range ||= self.class.vcs_range.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'shanty/vcs_range'
|
2
|
+
|
3
|
+
module Shanty
|
4
|
+
# Use range of local vs remote changes
|
5
|
+
class LocalGit < VCSRange
|
6
|
+
def from_commit
|
7
|
+
"origin/#{branch}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_commit
|
11
|
+
'HEAD'
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def branch
|
17
|
+
`git rev-parse --abbrev-ref HEAD`.strip
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/shanty.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'pathname'
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
require 'shanty/cli'
|
6
|
+
require 'shanty/discoverers/shantyfile'
|
7
|
+
require 'shanty/graph'
|
8
|
+
require 'shanty/global'
|
9
|
+
require 'shanty/mutators/git'
|
10
|
+
require 'shanty/tasks/basic'
|
11
|
+
|
12
|
+
module Shanty
|
13
|
+
# Main shanty class
|
14
|
+
class Shanty
|
15
|
+
GEM_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
16
|
+
|
17
|
+
def start!
|
18
|
+
setup_i18n
|
19
|
+
Cli.new(graph).run
|
20
|
+
end
|
21
|
+
|
22
|
+
def graph
|
23
|
+
@graph ||= construct_project_graph
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def setup_i18n
|
29
|
+
I18n.enforce_available_locales = true
|
30
|
+
I18n.load_path = Dir[File.join(GEM_ROOT, 'translations', '*.yml')]
|
31
|
+
end
|
32
|
+
|
33
|
+
def construct_project_graph
|
34
|
+
project_templates = Dir.chdir(Global.root) do
|
35
|
+
Discoverer.new.discover_all
|
36
|
+
end
|
37
|
+
|
38
|
+
projects = project_templates.map do |project_template|
|
39
|
+
project_template.type.new(project_template)
|
40
|
+
end
|
41
|
+
|
42
|
+
graph = Graph.new(projects)
|
43
|
+
|
44
|
+
Mutator.new.apply_mutations(graph)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shanty
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Jansen
|
8
|
+
- Nathan Kleyn
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-10-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: algorithms
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0.6'
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.6.1
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - "~>"
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0.6'
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.1
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: commander
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.2'
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 4.2.1
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - "~>"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '4.2'
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 4.2.1
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: deep_merge
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.0'
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 1.0.1
|
64
|
+
type: :runtime
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '1.0'
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 1.0.1
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: graph
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '2.6'
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 2.6.0
|
84
|
+
type: :runtime
|
85
|
+
prerelease: false
|
86
|
+
version_requirements: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '2.6'
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 2.6.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: hooks
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0.4'
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.4.0
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.4'
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 0.4.0
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: i18n
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - "~>"
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0.6'
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 0.6.11
|
124
|
+
type: :runtime
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0.6'
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 0.6.11
|
134
|
+
- !ruby/object:Gem::Dependency
|
135
|
+
name: pry-byebug
|
136
|
+
requirement: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - "~>"
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '1.3'
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: 1.3.3
|
144
|
+
type: :development
|
145
|
+
prerelease: false
|
146
|
+
version_requirements: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - "~>"
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '1.3'
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: 1.3.3
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: rspec
|
156
|
+
requirement: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - "~>"
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '3.0'
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: 3.0.0
|
164
|
+
type: :development
|
165
|
+
prerelease: false
|
166
|
+
version_requirements: !ruby/object:Gem::Requirement
|
167
|
+
requirements:
|
168
|
+
- - "~>"
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '3.0'
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 3.0.0
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: rubocop
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0.24'
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: 0.24.1
|
184
|
+
type: :development
|
185
|
+
prerelease: false
|
186
|
+
version_requirements: !ruby/object:Gem::Requirement
|
187
|
+
requirements:
|
188
|
+
- - "~>"
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '0.24'
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: 0.24.1
|
194
|
+
- !ruby/object:Gem::Dependency
|
195
|
+
name: rubocop-rspec
|
196
|
+
requirement: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - "~>"
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '1.1'
|
201
|
+
- - ">="
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: 1.1.0
|
204
|
+
type: :development
|
205
|
+
prerelease: false
|
206
|
+
version_requirements: !ruby/object:Gem::Requirement
|
207
|
+
requirements:
|
208
|
+
- - "~>"
|
209
|
+
- !ruby/object:Gem::Version
|
210
|
+
version: '1.1'
|
211
|
+
- - ">="
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: 1.1.0
|
214
|
+
- !ruby/object:Gem::Dependency
|
215
|
+
name: rake
|
216
|
+
requirement: !ruby/object:Gem::Requirement
|
217
|
+
requirements:
|
218
|
+
- - "~>"
|
219
|
+
- !ruby/object:Gem::Version
|
220
|
+
version: '10.3'
|
221
|
+
- - ">="
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: 10.3.2
|
224
|
+
type: :development
|
225
|
+
prerelease: false
|
226
|
+
version_requirements: !ruby/object:Gem::Requirement
|
227
|
+
requirements:
|
228
|
+
- - "~>"
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: '10.3'
|
231
|
+
- - ">="
|
232
|
+
- !ruby/object:Gem::Version
|
233
|
+
version: 10.3.2
|
234
|
+
- !ruby/object:Gem::Dependency
|
235
|
+
name: coveralls
|
236
|
+
requirement: !ruby/object:Gem::Requirement
|
237
|
+
requirements:
|
238
|
+
- - "~>"
|
239
|
+
- !ruby/object:Gem::Version
|
240
|
+
version: '0.7'
|
241
|
+
- - ">="
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: 0.7.1
|
244
|
+
type: :development
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - "~>"
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '0.7'
|
251
|
+
- - ">="
|
252
|
+
- !ruby/object:Gem::Version
|
253
|
+
version: 0.7.1
|
254
|
+
description: Pluggable, destributed continous delivery framework. Works with your
|
255
|
+
existing build, test, CI, deployment, or indeed any tools to provide a cohesive
|
256
|
+
way to do continuous delivery.
|
257
|
+
email:
|
258
|
+
- nathan@nathankleyn.com
|
259
|
+
executables:
|
260
|
+
- shanty
|
261
|
+
extensions: []
|
262
|
+
extra_rdoc_files: []
|
263
|
+
files:
|
264
|
+
- README.md
|
265
|
+
- bin/shanty
|
266
|
+
- lib/shanty.rb
|
267
|
+
- lib/shanty/cli.rb
|
268
|
+
- lib/shanty/discoverer.rb
|
269
|
+
- lib/shanty/discoverers/gradle.rb
|
270
|
+
- lib/shanty/discoverers/shantyfile.rb
|
271
|
+
- lib/shanty/global.rb
|
272
|
+
- lib/shanty/graph.rb
|
273
|
+
- lib/shanty/mixins/acts_as_link_graph_node.rb
|
274
|
+
- lib/shanty/mixins/attr_combined_accessor.rb
|
275
|
+
- lib/shanty/mixins/callbacks.rb
|
276
|
+
- lib/shanty/mutator.rb
|
277
|
+
- lib/shanty/mutators/git.rb
|
278
|
+
- lib/shanty/plugin.rb
|
279
|
+
- lib/shanty/plugins/bundler.rb
|
280
|
+
- lib/shanty/plugins/rspec.rb
|
281
|
+
- lib/shanty/plugins/rubocop.rb
|
282
|
+
- lib/shanty/project.rb
|
283
|
+
- lib/shanty/project_template.rb
|
284
|
+
- lib/shanty/projects/ruby.rb
|
285
|
+
- lib/shanty/projects/static.rb
|
286
|
+
- lib/shanty/task.rb
|
287
|
+
- lib/shanty/tasks/basic.rb
|
288
|
+
- lib/shanty/util.rb
|
289
|
+
- lib/shanty/vcs_range.rb
|
290
|
+
- lib/shanty/vcs_ranges/local_git.rb
|
291
|
+
homepage: https://github.com/shantytown/shanty
|
292
|
+
licenses:
|
293
|
+
- MIT
|
294
|
+
metadata: {}
|
295
|
+
post_install_message:
|
296
|
+
rdoc_options: []
|
297
|
+
require_paths:
|
298
|
+
- lib
|
299
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
300
|
+
requirements:
|
301
|
+
- - ">="
|
302
|
+
- !ruby/object:Gem::Version
|
303
|
+
version: '0'
|
304
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
305
|
+
requirements:
|
306
|
+
- - ">="
|
307
|
+
- !ruby/object:Gem::Version
|
308
|
+
version: '0'
|
309
|
+
requirements: []
|
310
|
+
rubyforge_project:
|
311
|
+
rubygems_version: 2.2.2
|
312
|
+
signing_key:
|
313
|
+
specification_version: 4
|
314
|
+
summary: Pluggable, destributed continous delivery framework.
|
315
|
+
test_files: []
|