aws-cft-tools 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.
- checksums.yaml +7 -0
- data/.editorconfig +10 -0
- data/.gitignore +52 -0
- data/.rspec +2 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/BEST-PRACTICES.md +136 -0
- data/CONTRIBUTING.md +38 -0
- data/Gemfile +8 -0
- data/LICENSE +15 -0
- data/README.md +118 -0
- data/Rakefile +17 -0
- data/USAGE.adoc +404 -0
- data/aws-cft-tools.gemspec +53 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/code.json +24 -0
- data/exe/aws-cft +176 -0
- data/lib/aws-cft-tools.rb +3 -0
- data/lib/aws_cft_tools.rb +31 -0
- data/lib/aws_cft_tools/aws_enumerator.rb +55 -0
- data/lib/aws_cft_tools/change.rb +66 -0
- data/lib/aws_cft_tools/client.rb +84 -0
- data/lib/aws_cft_tools/client/base.rb +40 -0
- data/lib/aws_cft_tools/client/cft.rb +93 -0
- data/lib/aws_cft_tools/client/cft/changeset_management.rb +109 -0
- data/lib/aws_cft_tools/client/cft/stack_management.rb +85 -0
- data/lib/aws_cft_tools/client/ec2.rb +136 -0
- data/lib/aws_cft_tools/client/templates.rb +84 -0
- data/lib/aws_cft_tools/deletion_change.rb +43 -0
- data/lib/aws_cft_tools/dependency_tree.rb +109 -0
- data/lib/aws_cft_tools/dependency_tree/nodes.rb +71 -0
- data/lib/aws_cft_tools/dependency_tree/variables.rb +37 -0
- data/lib/aws_cft_tools/errors.rb +25 -0
- data/lib/aws_cft_tools/runbook.rb +166 -0
- data/lib/aws_cft_tools/runbook/report.rb +30 -0
- data/lib/aws_cft_tools/runbooks.rb +16 -0
- data/lib/aws_cft_tools/runbooks/common/changesets.rb +30 -0
- data/lib/aws_cft_tools/runbooks/common/templates.rb +38 -0
- data/lib/aws_cft_tools/runbooks/deploy.rb +107 -0
- data/lib/aws_cft_tools/runbooks/deploy/reporting.rb +50 -0
- data/lib/aws_cft_tools/runbooks/deploy/stacks.rb +109 -0
- data/lib/aws_cft_tools/runbooks/deploy/templates.rb +37 -0
- data/lib/aws_cft_tools/runbooks/deploy/threading.rb +37 -0
- data/lib/aws_cft_tools/runbooks/diff.rb +28 -0
- data/lib/aws_cft_tools/runbooks/diff/context.rb +86 -0
- data/lib/aws_cft_tools/runbooks/diff/context/reporting.rb +87 -0
- data/lib/aws_cft_tools/runbooks/hosts.rb +43 -0
- data/lib/aws_cft_tools/runbooks/images.rb +43 -0
- data/lib/aws_cft_tools/runbooks/init.rb +86 -0
- data/lib/aws_cft_tools/runbooks/retract.rb +69 -0
- data/lib/aws_cft_tools/runbooks/retract/templates.rb +44 -0
- data/lib/aws_cft_tools/runbooks/stacks.rb +43 -0
- data/lib/aws_cft_tools/stack.rb +83 -0
- data/lib/aws_cft_tools/template.rb +177 -0
- data/lib/aws_cft_tools/template/dsl_context.rb +14 -0
- data/lib/aws_cft_tools/template/file_system.rb +62 -0
- data/lib/aws_cft_tools/template/metadata.rb +144 -0
- data/lib/aws_cft_tools/template/properties.rb +129 -0
- data/lib/aws_cft_tools/template_set.rb +120 -0
- data/lib/aws_cft_tools/template_set/array_methods.rb +63 -0
- data/lib/aws_cft_tools/template_set/closure.rb +77 -0
- data/lib/aws_cft_tools/template_set/dependencies.rb +55 -0
- data/lib/aws_cft_tools/template_set/each_slice_state.rb +58 -0
- data/lib/aws_cft_tools/version.rb +8 -0
- data/rubycritic.reek +3 -0
- metadata +321 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module AwsCftTools
|
6
|
+
class Client
|
7
|
+
##
|
8
|
+
# All of the business logic behind direct interaction with the AWS Template sources.
|
9
|
+
#
|
10
|
+
class Templates < Base
|
11
|
+
##
|
12
|
+
# Default template directory in the project.
|
13
|
+
DEFAULT_TEMPLATE_DIR = 'cloudformation/templates/'
|
14
|
+
|
15
|
+
##
|
16
|
+
# Default parameters directory in the project.
|
17
|
+
DEFAULT_PARAMETER_DIR = 'cloudformation/parameters/'
|
18
|
+
|
19
|
+
##
|
20
|
+
# Default set of file extensions that might contain templates.
|
21
|
+
#
|
22
|
+
TEMPLATE_FILE_EXTENSIONS = %w[.yaml .yml .json .rb].freeze
|
23
|
+
|
24
|
+
##
|
25
|
+
#
|
26
|
+
# @param options [Hash] client configuration
|
27
|
+
# @option options [String] :environment the operational environment in which to act
|
28
|
+
# @option options [String] :parameter_dir
|
29
|
+
# @option options [String] :region the AWS region in which to act
|
30
|
+
# @option options [Pathname] :root
|
31
|
+
# @option options [String] :template_dir
|
32
|
+
#
|
33
|
+
def initialize(options)
|
34
|
+
super({
|
35
|
+
template_dir: DEFAULT_TEMPLATE_DIR,
|
36
|
+
parameter_dir: DEFAULT_PARAMETER_DIR
|
37
|
+
}.merge(options))
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Lists all templates.
|
42
|
+
#
|
43
|
+
# @return AwsCftTools::TemplateSet
|
44
|
+
#
|
45
|
+
def templates
|
46
|
+
template_file_root = (options[:root] + options[:template_dir]).cleanpath
|
47
|
+
filtered_by_region(
|
48
|
+
filtered_by_environment(
|
49
|
+
all_templates(
|
50
|
+
template_file_root
|
51
|
+
)
|
52
|
+
)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def filtered_by_environment(set)
|
59
|
+
set.select { |template| template.environment?(options[:environment]) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def filtered_by_region(set)
|
63
|
+
set.select { |template| template.region?(options[:region]) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def all_templates(root)
|
67
|
+
AwsCftTools::TemplateSet.new(glob_templates(root)).tap do |set|
|
68
|
+
set.known_exports = options[:client].exports.map(&:name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def glob_templates(root)
|
73
|
+
Pathname.glob(root + '**/*')
|
74
|
+
.select { |file| TEMPLATE_FILE_EXTENSIONS.include?(file.extname) }
|
75
|
+
.map { |file| file_to_template(root, file) }
|
76
|
+
.select(&:template?)
|
77
|
+
end
|
78
|
+
|
79
|
+
def file_to_template(root, file)
|
80
|
+
AwsCftTools::Template.new(file.relative_path_from(root), options)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AwsCftTools
|
4
|
+
# Represents a change in a changeset.
|
5
|
+
class DeletionChange < Change
|
6
|
+
attr_reader :resource
|
7
|
+
|
8
|
+
###
|
9
|
+
#
|
10
|
+
# @param resource
|
11
|
+
#
|
12
|
+
def initialize(resource)
|
13
|
+
@resource = resource
|
14
|
+
end
|
15
|
+
|
16
|
+
###
|
17
|
+
# Return the action taken. For deletion, this is always +DELETE+.
|
18
|
+
#
|
19
|
+
# @return [String] +'DELETE'+ to indicate a deletion
|
20
|
+
#
|
21
|
+
def action
|
22
|
+
'DELETE'
|
23
|
+
end
|
24
|
+
|
25
|
+
###
|
26
|
+
# Return the status of this change as a replacement. For deletion, this is always +nil+.
|
27
|
+
#
|
28
|
+
# @return [nil]
|
29
|
+
#
|
30
|
+
def replacement
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
###
|
35
|
+
# Return the scopes of the change. For deletions, this is always +Resource+.
|
36
|
+
#
|
37
|
+
# @return [String] +'Resource'+
|
38
|
+
#
|
39
|
+
def scopes
|
40
|
+
'Resource'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AwsCftTools
|
4
|
+
##
|
5
|
+
# = Dependency Tree
|
6
|
+
#
|
7
|
+
# Manage dependencies between CloudFormation templates based on exported and imported variables.
|
8
|
+
#
|
9
|
+
class DependencyTree
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
require_relative 'dependency_tree/nodes'
|
13
|
+
require_relative 'dependency_tree/variables'
|
14
|
+
|
15
|
+
attr_reader :filenames, :nodes, :variables
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@nodes = Nodes.new
|
19
|
+
@variables = Variables.new
|
20
|
+
@filenames = []
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!method undefined_variables
|
24
|
+
# @see AwsCftTools::DependencyTree::Variables#undefined_variables
|
25
|
+
# @!method defined_variables
|
26
|
+
# @see AwsCftTools::DependencyTree::Variables#defined_variables
|
27
|
+
def_delegators :variables, :undefined_variables, :defined_variables
|
28
|
+
# @!method exported
|
29
|
+
# @see AwsCftTools::DependencyTree::Variables#defined
|
30
|
+
def_delegator :variables, :defined, :exported
|
31
|
+
|
32
|
+
# @!method dependencies_for
|
33
|
+
# @see AwsCftTools::DependencyTree::Nodes#dependencies_for
|
34
|
+
# @!method dependents_for
|
35
|
+
# @see AwsCftTools::DependencyTree::Nodes#dependents_for
|
36
|
+
def_delegators :nodes, :dependencies_for, :dependents_for
|
37
|
+
|
38
|
+
##
|
39
|
+
# computes a topological sort and returns the filenames in that sort order
|
40
|
+
#
|
41
|
+
# @return [Array<String>]
|
42
|
+
#
|
43
|
+
def sort
|
44
|
+
nodes.tsort & filenames
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# notes that the given filename defines the given variable name
|
49
|
+
#
|
50
|
+
# @param filename [#to_s]
|
51
|
+
# @param variable [String]
|
52
|
+
#
|
53
|
+
def provided(filename, variable)
|
54
|
+
filename = filename.to_s
|
55
|
+
nodes.make_link(variable, filename)
|
56
|
+
@filenames |= [filename]
|
57
|
+
exported(variable)
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# notes that the given filename requires the given variable name to be defined before deployment
|
62
|
+
#
|
63
|
+
# @param filename [#to_s]
|
64
|
+
# @param variable [String]
|
65
|
+
#
|
66
|
+
def required(filename, variable)
|
67
|
+
filename = filename.to_s
|
68
|
+
nodes.make_link(filename, variable)
|
69
|
+
@filenames |= [filename]
|
70
|
+
variables.referenced(variable)
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# links two nodes in a directed fashion
|
75
|
+
#
|
76
|
+
# The template named by _from_ provides resources required by the template named by _to_.
|
77
|
+
#
|
78
|
+
# @param from [#to_s]
|
79
|
+
# @param to [#to_s]
|
80
|
+
#
|
81
|
+
def linked(from, to)
|
82
|
+
linker = "#{from}$$#{to}"
|
83
|
+
provided(from, linker)
|
84
|
+
required(to, linker)
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# finds a subset of the given set that has no dependencies outside the set
|
89
|
+
#
|
90
|
+
# @param set [Array<T>]
|
91
|
+
# @return [Array<T>]
|
92
|
+
#
|
93
|
+
def closed_subset(set)
|
94
|
+
# list all nodes that have no dependents outside the set
|
95
|
+
close_subset(set, &method(:dependents_for))
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def close_subset(set, &block)
|
101
|
+
return [] unless block_given?
|
102
|
+
set - items_outside_subset(set, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
def items_outside_subset(set)
|
106
|
+
set.select { |node| (yield(node) - set).any? }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AwsCftTools
|
4
|
+
class DependencyTree
|
5
|
+
##
|
6
|
+
# Manages a list of nodes or vertices. Edges pass from a filename node to a variable node or from
|
7
|
+
# a variable node to a filename node, but never from a filename node to a filename node or from
|
8
|
+
# a variable node to a variable node.
|
9
|
+
#
|
10
|
+
class Nodes
|
11
|
+
include TSort
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@nodes = default_hash
|
15
|
+
@inverse_nodes = default_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Computes the direct dependencies of a node that are of the same type as the node. If the node
|
20
|
+
# is a filename, then the returned nodes will be filenames. Likewise with variable names.
|
21
|
+
#
|
22
|
+
# @param node [String]
|
23
|
+
# @return [Array<String>]
|
24
|
+
#
|
25
|
+
def dependencies_for(node)
|
26
|
+
double_hop(@nodes, node.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Computes the things dependent on the given node. If the node is a filename, then the returned
|
31
|
+
# nodes will be filenames. Likewise with variable names.
|
32
|
+
#
|
33
|
+
# @param node [String]
|
34
|
+
# @return [Array<String>]
|
35
|
+
#
|
36
|
+
def dependents_for(node)
|
37
|
+
double_hop(@inverse_nodes, node.to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Draws a directed link from +from+ to +to+.
|
42
|
+
#
|
43
|
+
# @param from [String]
|
44
|
+
# @param to [String]
|
45
|
+
def make_link(from, to)
|
46
|
+
@nodes[from] << to
|
47
|
+
@inverse_nodes[to] << from
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def double_hop(set, node)
|
53
|
+
# we hop from a filename to a variable child to a filename child
|
54
|
+
# or from a variable to a filename child to a variable child
|
55
|
+
set[node].flat_map { |neighbor| set[neighbor] }.uniq
|
56
|
+
end
|
57
|
+
|
58
|
+
def tsort_each_node(&block)
|
59
|
+
@nodes.each_key(&block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def tsort_each_child(node, &block)
|
63
|
+
@nodes[node].each(&block) if @nodes.include?(node)
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_hash
|
67
|
+
Hash.new { |hash, key| hash[key] = [] }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AwsCftTools
|
4
|
+
class DependencyTree
|
5
|
+
##
|
6
|
+
# Manage list of defined/undefined variables
|
7
|
+
#
|
8
|
+
class Variables
|
9
|
+
attr_reader :undefined_variables, :defined_variables
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@undefined_variables = []
|
13
|
+
@defined_variables = []
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Notes that the given variable name is provided either by the CloudFormation environment or by
|
18
|
+
# another template.
|
19
|
+
#
|
20
|
+
# @param name [String]
|
21
|
+
#
|
22
|
+
def defined(name)
|
23
|
+
@undefined_variables -= [name]
|
24
|
+
@defined_variables |= [name]
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Notes that the given variable name is used as an input into a template.
|
29
|
+
#
|
30
|
+
# @param name [String]
|
31
|
+
#
|
32
|
+
def referenced(name)
|
33
|
+
@undefined_variables |= [name] unless @defined_variables.include?(name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AwsCftTools
|
4
|
+
##
|
5
|
+
# Provides a root class for matching any internal exceptions.
|
6
|
+
#
|
7
|
+
class ToolingException < StandardError; end
|
8
|
+
|
9
|
+
##
|
10
|
+
# Exception when a needed environment variable is not provided. Intended for use in
|
11
|
+
# parameter files.
|
12
|
+
class IncompleteEnvironmentError < ToolingException; end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Exception raised when the template or parameter source file can not be read and parsed.
|
16
|
+
class ParseException < ToolingException; end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Exception raised when the AWS SDK raises an error interacting with the AWS API.
|
20
|
+
class CloudFormationError < ToolingException; end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Exception raised when an expected resolvable template dependency can not be satisfied.
|
24
|
+
class UnsatisfiedDependencyError < ToolingException; end
|
25
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AwsCftTools
|
4
|
+
##
|
5
|
+
# This is the base class for runbooks.
|
6
|
+
#
|
7
|
+
# A runbook is a command that can be accessed via the `aws-cft` script. A runbook is implemented as a
|
8
|
+
# subclass of this class.
|
9
|
+
#
|
10
|
+
# == Callbacks
|
11
|
+
# The AwsCftTools::CLI uses callbacks to manage runbook-specific options and behaviors.
|
12
|
+
#
|
13
|
+
# == Helpers
|
14
|
+
#
|
15
|
+
# The various helpers make managing logic flow much easier. Rather than having to be aware of how the
|
16
|
+
# different modes (+verbose+, +change+, +noop+) interplay and are configured, you can use the
|
17
|
+
# methods in this section to annotate the flow with your intent.
|
18
|
+
#
|
19
|
+
# @abstract Subclass and override {.default_options}, {.options}, and {#run} to implement
|
20
|
+
# a custom Runbook class.
|
21
|
+
class Runbook
|
22
|
+
require_relative 'runbook/report'
|
23
|
+
|
24
|
+
##
|
25
|
+
# The AwsCftTools::Client instance used by the runbook.
|
26
|
+
#
|
27
|
+
attr_reader :client
|
28
|
+
|
29
|
+
##
|
30
|
+
# The configuration options passed in to the runbook.
|
31
|
+
#
|
32
|
+
attr_reader :options
|
33
|
+
|
34
|
+
##
|
35
|
+
# Recognized configuration options depend on the runbook but include any options valid for
|
36
|
+
# AwsCftTools::Client.
|
37
|
+
#
|
38
|
+
# Modes are selected by various configuration options:
|
39
|
+
#
|
40
|
+
# +:verbose+:: provide more narrative as the runbook executes
|
41
|
+
# +:noop+:: do nothing that could modify state
|
42
|
+
# +:change+:: do nothing that could permanently modify state, though some modifications are permitted
|
43
|
+
# in order to examine what might change if permanent changes were made
|
44
|
+
#
|
45
|
+
# @param configuration [Hash] Various options passed to the AwsCftTools::Client.
|
46
|
+
# @return AwsCftTools::Runbook
|
47
|
+
#
|
48
|
+
def initialize(configuration = {})
|
49
|
+
@options = configuration
|
50
|
+
@client = AwsCftTools::Client.new(options)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!group Callbacks
|
54
|
+
|
55
|
+
##
|
56
|
+
# A callback to implement the runbook actions. Nothing is passed in or returned.
|
57
|
+
#
|
58
|
+
# @return void
|
59
|
+
#
|
60
|
+
def run; end
|
61
|
+
|
62
|
+
##
|
63
|
+
# An internal wrapper around +#run+ to catch credential errors and print a useful message.
|
64
|
+
#
|
65
|
+
def _run
|
66
|
+
run
|
67
|
+
rescue Aws::Errors::MissingCredentialsError
|
68
|
+
puts 'Unable to access AWS without valid credentials. Either define a default credential' \
|
69
|
+
' profile or use the -p option to specify an AWS credential profile.'
|
70
|
+
end
|
71
|
+
|
72
|
+
# @!group Helpers
|
73
|
+
|
74
|
+
##
|
75
|
+
# @param description [String] an optional description of the operation
|
76
|
+
# @yield runs the block if not in +noop+ mode
|
77
|
+
# @return void
|
78
|
+
#
|
79
|
+
# Defines an operation that may or may not be narrated and that should not be run if in +noop+ mode.
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
# operation("considering the change" ) do
|
83
|
+
# checking("seeing what changes") { check_what_changes }
|
84
|
+
# doing("committing the change") { make_the_change }
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
def operation(description = nil)
|
88
|
+
narrative(description)
|
89
|
+
return if options[:noop]
|
90
|
+
|
91
|
+
yield if block_given?
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# @param description [String] an optional description of the check
|
96
|
+
# @yield runs the block if in +check+ mode and not in +noop+ mode
|
97
|
+
# @return void
|
98
|
+
#
|
99
|
+
# Runs the given block when in +check+ mode and not in +noop+ mode. Outputs the description if the
|
100
|
+
# block is run.
|
101
|
+
#
|
102
|
+
# @example (see #operation)
|
103
|
+
#
|
104
|
+
def checking(description = nil)
|
105
|
+
return if options[:noop] || !options[:check]
|
106
|
+
output(description)
|
107
|
+
yield if block_given?
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# @param description [String] an optional description of the action
|
112
|
+
# @yield runs the block if not in +check+ or +noop+ mode
|
113
|
+
# @return void
|
114
|
+
#
|
115
|
+
# Runs the given block when not in +check+ or +noop+ mode. Outputs the description if the block is run.
|
116
|
+
#
|
117
|
+
# @example (see #operation)
|
118
|
+
#
|
119
|
+
def doing(description = nil)
|
120
|
+
return if options[:noop] || options[:check]
|
121
|
+
output(description)
|
122
|
+
yield if block_given?
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# @param description [String] an optional narrative string
|
127
|
+
# @return void
|
128
|
+
#
|
129
|
+
# Prints out the given description to stdout. If in +noop+ mode, +" (noop)"+ is appended.
|
130
|
+
#
|
131
|
+
def narrative(description = nil)
|
132
|
+
return unless description
|
133
|
+
if options[:noop]
|
134
|
+
puts "#{description} (noop)"
|
135
|
+
else
|
136
|
+
puts description
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# @param description [String] an optional verbose description
|
142
|
+
# @yield runs the block if in +verbose+ mode
|
143
|
+
# @return void
|
144
|
+
#
|
145
|
+
# Prints out the given description and runs the block if in +verbose+ mode. This is useful if the
|
146
|
+
# verbose narrative might be computationally expensive.
|
147
|
+
#
|
148
|
+
# @example
|
149
|
+
# detail do
|
150
|
+
# ... run some extensive processing to summarize stuff
|
151
|
+
# puts "The results are #{interesting}."
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
def detail(description = nil)
|
155
|
+
return unless options[:verbose]
|
156
|
+
output(description)
|
157
|
+
yield if block_given?
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def output(description)
|
163
|
+
puts description if description
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|