octocatalog-diff 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.version +1 -0
- data/LICENSE +20 -0
- data/README.md +82 -0
- data/bin/octocatalog-diff +75 -0
- data/doc/advanced-bootstrap.md +33 -0
- data/doc/advanced-cache-dir.md +24 -0
- data/doc/advanced-catalog-only.md +37 -0
- data/doc/advanced-ci.md +13 -0
- data/doc/advanced-dynamic-ignores.md +123 -0
- data/doc/advanced-future-parser.md +11 -0
- data/doc/advanced-ignores.md +224 -0
- data/doc/advanced-output-formats.md +96 -0
- data/doc/advanced-output-hacks.md +45 -0
- data/doc/advanced-override-facts.md +67 -0
- data/doc/advanced-pe-enc.md +52 -0
- data/doc/advanced-puppet-master.md +50 -0
- data/doc/advanced-puppet-versions.md +9 -0
- data/doc/advanced-storeconfigs.md +72 -0
- data/doc/advanced-using-without-git.md +15 -0
- data/doc/advanced.md +43 -0
- data/doc/basic.md +70 -0
- data/doc/configuration-enc.md +69 -0
- data/doc/configuration-hiera.md +103 -0
- data/doc/configuration-puppetdb.md +49 -0
- data/doc/configuration.md +51 -0
- data/doc/dev/README.md +1 -0
- data/doc/dev/coverage.md +34 -0
- data/doc/dev/how-to-add-options.md +83 -0
- data/doc/dev/integration-tests.md +63 -0
- data/doc/dev/releasing.md +19 -0
- data/doc/installation.md +49 -0
- data/doc/limitations.md +34 -0
- data/doc/optionsref.md +947 -0
- data/doc/requirements.md +16 -0
- data/doc/roadmap.md +26 -0
- data/doc/similar.md +17 -0
- data/doc/troubleshooting.md +54 -0
- data/lib/octocatalog-diff.rb +12 -0
- data/lib/octocatalog-diff/bootstrap.rb +53 -0
- data/lib/octocatalog-diff/catalog-diff/cli.rb +205 -0
- data/lib/octocatalog-diff/catalog-diff/cli/catalogs.rb +240 -0
- data/lib/octocatalog-diff/catalog-diff/cli/diffs.rb +145 -0
- data/lib/octocatalog-diff/catalog-diff/cli/helpers/fact_override.rb +99 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options.rb +173 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/basedir.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrap_environment.rb +18 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrap_script.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrap_then_exit.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrapped_dirs.rb +18 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/cached_master_dir.rb +21 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/catalog_only.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/color.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/compare_file_text.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/debug.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/display_datatype_changes.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/display_detail_add.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/display_source_file_line.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/enc.rb +31 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/existing_catalogs.rb +25 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/fact_file.rb +23 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/fact_override.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/facts_terminus.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/from_puppetdb.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/header.rb +24 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/hiera_config.rb +18 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/hiera_path_strip.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/hostname.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/ignore.rb +24 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/ignore_attr.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/ignore_tags.rb +23 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/include_tags.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/master_cache_branch.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/output_file.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/output_format.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/parallel.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/parser.rb +48 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pass_env_vars.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_ssl_ca.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_ssl_client_cert.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_ssl_client_key.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_token.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_token_file.rb +17 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_url.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_binary.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_api_version.rb +20 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_ssl_ca.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_ssl_client_cert.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_ssl_client_key.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_ca.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_cert.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_key.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_password.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_password_file.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_url.rb +18 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/quiet.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/retry_failed_catalog.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/safe_to_delete_cached_master_dir.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/storeconfigs.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/suppress_absent_file_details.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/to_from_branch.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/printer.rb +52 -0
- data/lib/octocatalog-diff/catalog-diff/differ.rb +615 -0
- data/lib/octocatalog-diff/catalog-diff/display.rb +125 -0
- data/lib/octocatalog-diff/catalog-diff/display/json.rb +25 -0
- data/lib/octocatalog-diff/catalog-diff/display/text.rb +452 -0
- data/lib/octocatalog-diff/catalog-util/bootstrap.rb +145 -0
- data/lib/octocatalog-diff/catalog-util/builddir.rb +289 -0
- data/lib/octocatalog-diff/catalog-util/cached_master_directory.rb +169 -0
- data/lib/octocatalog-diff/catalog-util/command.rb +96 -0
- data/lib/octocatalog-diff/catalog-util/enc.rb +77 -0
- data/lib/octocatalog-diff/catalog-util/enc/noop.rb +22 -0
- data/lib/octocatalog-diff/catalog-util/enc/pe.rb +99 -0
- data/lib/octocatalog-diff/catalog-util/enc/pe/v1.rb +61 -0
- data/lib/octocatalog-diff/catalog-util/enc/script.rb +88 -0
- data/lib/octocatalog-diff/catalog-util/facts.rb +89 -0
- data/lib/octocatalog-diff/catalog-util/fileresources.rb +83 -0
- data/lib/octocatalog-diff/catalog-util/git.rb +65 -0
- data/lib/octocatalog-diff/catalog.rb +209 -0
- data/lib/octocatalog-diff/catalog/computed.rb +205 -0
- data/lib/octocatalog-diff/catalog/json.rb +30 -0
- data/lib/octocatalog-diff/catalog/noop.rb +19 -0
- data/lib/octocatalog-diff/catalog/puppetdb.rb +82 -0
- data/lib/octocatalog-diff/catalog/puppetmaster.rb +121 -0
- data/lib/octocatalog-diff/external/pson/LICENSE +17 -0
- data/lib/octocatalog-diff/external/pson/README.md +20 -0
- data/lib/octocatalog-diff/external/pson/common.rb +370 -0
- data/lib/octocatalog-diff/external/pson/pure.rb +15 -0
- data/lib/octocatalog-diff/external/pson/pure/generator.rb +395 -0
- data/lib/octocatalog-diff/external/pson/pure/parser.rb +307 -0
- data/lib/octocatalog-diff/external/pson/version.rb +8 -0
- data/lib/octocatalog-diff/facts.rb +125 -0
- data/lib/octocatalog-diff/facts/json.rb +20 -0
- data/lib/octocatalog-diff/facts/puppetdb.rb +59 -0
- data/lib/octocatalog-diff/facts/yaml.rb +29 -0
- data/lib/octocatalog-diff/puppetdb.rb +163 -0
- data/lib/octocatalog-diff/util/colored.rb +20 -0
- data/lib/octocatalog-diff/util/httparty.rb +158 -0
- data/lib/octocatalog-diff/util/parallel.rb +170 -0
- data/lib/octocatalog-diff/util/puppetversion.rb +24 -0
- data/lib/octocatalog-diff/version.rb +7 -0
- metadata +386 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
require_relative 'differ'
|
2
|
+
require_relative 'display/json'
|
3
|
+
require_relative 'display/text'
|
4
|
+
|
5
|
+
module OctocatalogDiff
|
6
|
+
module CatalogDiff
|
7
|
+
# Prepare a display of the results from a catalog-diff. Intended that this will contain utility
|
8
|
+
# methods but call out to a OctocatalogDiff::CatalogDiff::Display::<something> class to display in
|
9
|
+
# the desired format.
|
10
|
+
class Display
|
11
|
+
# Display the diff in some specified format.
|
12
|
+
# @param diff_in [OctocatalogDiff::CatalogDiff::Differ | Array<Diff results>] Diff to display
|
13
|
+
# @param options [Hash] Consisting of:
|
14
|
+
# - :header [String] => Header (can be :default to construct header)
|
15
|
+
# - :display_source_file_line [Boolean] => Display manifest filename and line number where declared
|
16
|
+
# - :compilation_from_dir [String] => Directory where 'from' catalog was compiled
|
17
|
+
# - :compilation_to_dir [String] => Directory where 'to' catalog was compiled
|
18
|
+
# - :display_detail_add [Boolean] => Set true to display parameters of newly added resources
|
19
|
+
# @param logger [Logger] Logger object
|
20
|
+
# @return [String] Text output for provided diff
|
21
|
+
def self.output(diff_in, options = {}, logger = nil)
|
22
|
+
diff = diff_in.is_a?(OctocatalogDiff::CatalogDiff::Differ) ? diff_in.diff : diff_in
|
23
|
+
raise ArgumentError, "text_output requires Array<Diff results>; passed in #{diff_in.class}" unless diff.is_a?(Array)
|
24
|
+
|
25
|
+
# req_format means 'requested format' because 'format' has a built-in meaning to Ruby
|
26
|
+
req_format = options.fetch(:format, :color_text)
|
27
|
+
|
28
|
+
# Options hash to pass to display method
|
29
|
+
opts = {}
|
30
|
+
opts[:header] = header(options)
|
31
|
+
opts[:display_source_file_line] = options.fetch(:display_source_file_line, false)
|
32
|
+
opts[:compilation_from_dir] = options[:compilation_from_dir] || nil
|
33
|
+
opts[:compilation_to_dir] = options[:compilation_to_dir] || nil
|
34
|
+
opts[:display_detail_add] = options.fetch(:display_detail_add, false)
|
35
|
+
opts[:display_datatype_changes] = options.fetch(:display_datatype_changes, false)
|
36
|
+
|
37
|
+
# Call appropriate display method
|
38
|
+
case req_format
|
39
|
+
when :json
|
40
|
+
logger.debug 'Generating JSON output' if logger
|
41
|
+
OctocatalogDiff::CatalogDiff::Display::Json.generate(diff, opts, logger)
|
42
|
+
when :text
|
43
|
+
logger.debug 'Generating non-colored text output' if logger
|
44
|
+
OctocatalogDiff::CatalogDiff::Display::Text.generate(diff, opts.merge(color: false), logger)
|
45
|
+
when :color_text
|
46
|
+
logger.debug 'Generating colored text output' if logger
|
47
|
+
OctocatalogDiff::CatalogDiff::Display::Text.generate(diff, opts.merge(color: true), logger)
|
48
|
+
else
|
49
|
+
raise ArgumentError, "Unrecognized text format '#{req_format}'"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Utility method!
|
54
|
+
# Construct the header for diffs
|
55
|
+
# Default is diff <old_branch_name>/<node_name> <new_branch_name>/<node_name>
|
56
|
+
# @param opts [Hash] Options hash from CLI
|
57
|
+
# @return [String] Header in indicated format
|
58
|
+
def self.header(opts)
|
59
|
+
return nil if opts[:no_header]
|
60
|
+
return opts[:header] unless opts[:header] == :default
|
61
|
+
node = opts.fetch(:node, 'node')
|
62
|
+
from_br = opts.fetch(:from_env, 'a')
|
63
|
+
to_br = opts.fetch(:to_env, 'b')
|
64
|
+
from_br = 'current' if from_br == '.'
|
65
|
+
to_br = 'current' if to_br == '.'
|
66
|
+
"diff #{from_br}/#{node} #{to_br}/#{node}"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Utility method!
|
70
|
+
# Go through the 'diff' array, filtering out ignored items and classifying each change
|
71
|
+
# as an addition (+), subtraction (-), change (~), or nested change (!). This creates
|
72
|
+
# hashes for each type of change that are consumed later for ordering purposes.
|
73
|
+
# @param diff [Array<Diff results>] The diff which *must* be in this format
|
74
|
+
# @return [Array<Hash of adds, Hash of removes, Hash of changes, Hash of nested] Processed results
|
75
|
+
def self.parse_diff_array_into_categorized_hashes(diff)
|
76
|
+
only_in_old = {}
|
77
|
+
only_in_new = {}
|
78
|
+
changed = {}
|
79
|
+
diff.each do |diff_obj|
|
80
|
+
(type, title, the_rest) = diff_obj[1].split(/\f/, 3)
|
81
|
+
key = "#{type}[#{title}]"
|
82
|
+
if ['-', '+'].include?(diff_obj[0])
|
83
|
+
only_in_old[key] = { diff: diff_obj[2], loc: diff_obj[3] } if diff_obj[0] == '-'
|
84
|
+
only_in_new[key] = { diff: diff_obj[2], loc: diff_obj[3] } if diff_obj[0] == '+'
|
85
|
+
elsif ['~', '!'].include?(diff_obj[0])
|
86
|
+
# HashDiff reports these as diffs for some reason
|
87
|
+
next if diff_obj[2].nil? && diff_obj[3].nil?
|
88
|
+
|
89
|
+
# This turns "foo\fbar\fbaz" into hash['foo']['bar']['baz']
|
90
|
+
result = the_rest.split(/\f/).reverse.inject(old: diff_obj[2], new: diff_obj[3]) { |a, e| { e => a } }
|
91
|
+
|
92
|
+
# Assign to appropriate variable
|
93
|
+
diff = changed.key?(key) ? changed[key][:diff] : {}
|
94
|
+
simple_deep_merge!(diff, result)
|
95
|
+
changed[key] = { diff: diff, old_loc: diff_obj[4], new_loc: diff_obj[5] }
|
96
|
+
else
|
97
|
+
raise "Unrecognized diff symbol '#{diff_obj[0]}' in #{diff_obj.inspect}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
[only_in_new, only_in_old, changed]
|
101
|
+
end
|
102
|
+
|
103
|
+
# Utility Method!
|
104
|
+
# Deep merge two hashes. (The 'deep_merge' gem seems to de-duplicate arrays so this is a reinvention
|
105
|
+
# of the wheel, but a simpler wheel that does just exactly what we need.)
|
106
|
+
# @param hash1 [Hash] First object
|
107
|
+
# @param hash2 [Hash] Second object
|
108
|
+
def self.simple_deep_merge!(hash1, hash2)
|
109
|
+
raise ArgumentError, 'First argument to simple_deep_merge must be a hash' unless hash1.is_a?(Hash)
|
110
|
+
raise ArgumentError, 'Second argument to simple_deep_merge must be a hash' unless hash2.is_a?(Hash)
|
111
|
+
hash2.each do |k, v|
|
112
|
+
if v.is_a?(Hash) && hash1[k].is_a?(Hash)
|
113
|
+
# We can only merge a hash with a hash. If hash1[k] is something other than a hash, say for example
|
114
|
+
# a string, then the merging is NOT invoked and hash1[k] gets directly overwritten in the `else` clause.
|
115
|
+
# Also if hash1[k] is nil, it falls through to the `else` clause where it gets set directly to the result
|
116
|
+
# hash without needless iterations.
|
117
|
+
simple_deep_merge!(hash1[k], v)
|
118
|
+
else
|
119
|
+
hash1[k] = v
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative '../display'
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module OctocatalogDiff
|
6
|
+
module CatalogDiff
|
7
|
+
class Display
|
8
|
+
# Display the output from a diff in JSON format.
|
9
|
+
class Json < OctocatalogDiff::CatalogDiff::Display
|
10
|
+
# Generate JSON representation of the 'diff' suitable for further analysis.
|
11
|
+
# @param diff [Array<Diff results>] The diff which *must* be in this format
|
12
|
+
# @param options [Hash] Options which are:
|
13
|
+
# - :header => [String] Header to print; no header is printed if not specified
|
14
|
+
# @param _logger [Logger] Not used here
|
15
|
+
def self.generate(diff, options = {}, _logger = nil)
|
16
|
+
result = {
|
17
|
+
'diff' => diff
|
18
|
+
}
|
19
|
+
result['header'] = options[:header] unless options[:header].nil?
|
20
|
+
result.to_json
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,452 @@
|
|
1
|
+
require_relative '../display'
|
2
|
+
require_relative '../../util/colored.rb'
|
3
|
+
|
4
|
+
require 'diffy'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module OctocatalogDiff
|
8
|
+
module CatalogDiff
|
9
|
+
class Display
|
10
|
+
# Display the output from a diff in text format. Uses the 'diffy' gem to provide diffs in
|
11
|
+
# blocks of text. Formats results in a logical Puppet output.
|
12
|
+
class Text < OctocatalogDiff::CatalogDiff::Display
|
13
|
+
SEPARATOR = '*******************************************'.freeze
|
14
|
+
|
15
|
+
# Generate the text representation of the 'diff' suitable for rendering in a console or log.
|
16
|
+
# @param diff [Array<Diff results>] The diff which *must* be in this format
|
17
|
+
# @param options_in [Hash] Options which are:
|
18
|
+
# - :color => [Boolean] True or false, whether to use color codes
|
19
|
+
# - :header => [String] Header to print; no header is printed if not specified
|
20
|
+
# - :display_source_file_line [Boolean] True or false, print filename and line (if known)
|
21
|
+
# - :display_detail_add [Boolean] If true, print details of any added resources
|
22
|
+
# @param logger [Logger] Logger object
|
23
|
+
# @return [Array<String>] Results
|
24
|
+
def self.generate(diff, options_in = {}, logger = nil)
|
25
|
+
# Empty?
|
26
|
+
return [] if diff.empty?
|
27
|
+
|
28
|
+
# We may modify this for temporary local use, but don't want to pass these changes
|
29
|
+
# back to the rest of the program.
|
30
|
+
options = options_in.dup
|
31
|
+
|
32
|
+
# Enable color support if requested...
|
33
|
+
String.colors_enabled = options.fetch(:color, true)
|
34
|
+
|
35
|
+
previous_diffy_default_format = Diffy::Diff.default_format
|
36
|
+
Diffy::Diff.default_format = options.fetch(:color, true) ? :color : :text
|
37
|
+
|
38
|
+
# Strip out differences or update display where string matches but data type differs.
|
39
|
+
# For example, 28 (the integer) and "28" (the string) have identical string
|
40
|
+
# representations, but are different data types. Same for nil vs. "".
|
41
|
+
adjust_for_display_datatype_changes(diff, options[:display_datatype_changes], logger)
|
42
|
+
|
43
|
+
# Call the utility method to sort changes into their respective types
|
44
|
+
only_in_new, only_in_old, changed = parse_diff_array_into_categorized_hashes(diff)
|
45
|
+
sorted_list = only_in_old.keys | only_in_new.keys | changed.keys
|
46
|
+
sorted_list.sort!
|
47
|
+
unless logger.nil?
|
48
|
+
logger.debug "Added resources: #{only_in_new.keys.count}"
|
49
|
+
logger.debug "Removed resources: #{only_in_old.keys.count}"
|
50
|
+
logger.debug "Changed resources: #{changed.keys.count}"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Run through the list to build the result
|
54
|
+
result = []
|
55
|
+
sorted_list.each do |item|
|
56
|
+
# Print the header if needed
|
57
|
+
unless options[:header].nil?
|
58
|
+
result << options[:header] unless options[:header].empty?
|
59
|
+
result << SEPARATOR
|
60
|
+
options[:header] = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# A removed item appears only in the old hash.
|
64
|
+
if only_in_old.key?(item)
|
65
|
+
result.concat display_removed_item(
|
66
|
+
item: item,
|
67
|
+
old_loc: only_in_old[item][:loc],
|
68
|
+
options: options,
|
69
|
+
logger: logger
|
70
|
+
)
|
71
|
+
|
72
|
+
# An added item appears only in the new hash.
|
73
|
+
elsif only_in_new.key?(item)
|
74
|
+
result.concat display_added_item(
|
75
|
+
item: item,
|
76
|
+
new_loc: only_in_new[item][:loc],
|
77
|
+
diff: only_in_new[item][:diff],
|
78
|
+
options: options,
|
79
|
+
logger: logger
|
80
|
+
)
|
81
|
+
|
82
|
+
# A change can appear either in the change hash, the nested hash, or both.
|
83
|
+
# Therefore, changes and nested changes are combined for display.
|
84
|
+
elsif changed.key?(item)
|
85
|
+
result.concat display_changed_or_nested_item(
|
86
|
+
item: item,
|
87
|
+
old_loc: changed[item][:old_loc],
|
88
|
+
new_loc: changed[item][:new_loc],
|
89
|
+
diff: changed[item][:diff],
|
90
|
+
options: options,
|
91
|
+
logger: logger
|
92
|
+
)
|
93
|
+
|
94
|
+
# An unrecognized change throws an error. This indicates a bug.
|
95
|
+
else
|
96
|
+
# :nocov:
|
97
|
+
raise "BUG (please report): Unable to determine diff type of item: #{item.inspect}"
|
98
|
+
# :nocov:
|
99
|
+
end
|
100
|
+
result << SEPARATOR
|
101
|
+
end
|
102
|
+
|
103
|
+
# Reset the global color-related flags
|
104
|
+
String.colors_enabled = false
|
105
|
+
Diffy::Diff.default_format = previous_diffy_default_format
|
106
|
+
|
107
|
+
# The end
|
108
|
+
result
|
109
|
+
end
|
110
|
+
|
111
|
+
# Display a changed or nested item
|
112
|
+
# @param item [String] Item (type+title) that has changed
|
113
|
+
# @param old_loc [Hash] File and line number of location in "from" catalog
|
114
|
+
# @param new_loc [Hash] File and line number of location in "to" catalog
|
115
|
+
# @param diff [Hash] Difference hash
|
116
|
+
# @param options [Hash] Display options
|
117
|
+
# @param logger [Logger] Logger object
|
118
|
+
# @return [Array] Lines of text
|
119
|
+
def self.display_changed_or_nested_item(opts = {})
|
120
|
+
item = opts.fetch(:item)
|
121
|
+
old_loc = opts.fetch(:old_loc)
|
122
|
+
new_loc = opts.fetch(:new_loc)
|
123
|
+
diff = opts.fetch(:diff)
|
124
|
+
options = opts.fetch(:options)
|
125
|
+
logger = opts[:logger]
|
126
|
+
|
127
|
+
result = []
|
128
|
+
info_hash = { item: item, result: result, old_loc: old_loc, new_loc: new_loc, options: options, logger: logger }
|
129
|
+
add_source_file_line_info(info_hash)
|
130
|
+
result << " #{item} =>"
|
131
|
+
diff.keys.sort.each { |key| result.concat hash_diff(diff[key], 1, key, true) }
|
132
|
+
result
|
133
|
+
end
|
134
|
+
|
135
|
+
# Display a removed item
|
136
|
+
# @param item [String] Item (type+title) that has changed
|
137
|
+
# @param old_loc [Hash] File and line number of location in "from" catalog
|
138
|
+
# @param options [Hash] Display options
|
139
|
+
# @param logger [Logger] Logger object
|
140
|
+
# @return [Array] Lines of text
|
141
|
+
def self.display_removed_item(opts = {})
|
142
|
+
item = opts.fetch(:item)
|
143
|
+
old_loc = opts.fetch(:old_loc)
|
144
|
+
options = opts.fetch(:options)
|
145
|
+
logger = opts[:logger]
|
146
|
+
|
147
|
+
result = []
|
148
|
+
add_source_file_line_info(item: item, result: result, old_loc: old_loc, options: options, logger: logger)
|
149
|
+
result << "- #{item}".red
|
150
|
+
end
|
151
|
+
|
152
|
+
# Display an added item
|
153
|
+
# @param item [String] Item (type+title) that has changed
|
154
|
+
# @param new_loc [Hash] File and line number of location in "to" catalog
|
155
|
+
# @param diff [Hash] Difference hash
|
156
|
+
# @param options [Hash] Display options
|
157
|
+
# @param logger [Logger] Logger object
|
158
|
+
# @return [Array] Lines of text
|
159
|
+
def self.display_added_item(opts = {})
|
160
|
+
item = opts.fetch(:item)
|
161
|
+
new_loc = opts.fetch(:new_loc)
|
162
|
+
diff = opts.fetch(:diff)
|
163
|
+
options = opts.fetch(:options)
|
164
|
+
logger = opts[:logger]
|
165
|
+
|
166
|
+
result = []
|
167
|
+
add_source_file_line_info(item: item, result: result, new_loc: new_loc, options: options, logger: logger)
|
168
|
+
if options[:display_detail_add] && diff.key?('parameters')
|
169
|
+
result << "+ #{item} =>".green
|
170
|
+
result << ' parameters =>'.green
|
171
|
+
result.concat(
|
172
|
+
diff_two_hashes_with_diffy(
|
173
|
+
depth: 1,
|
174
|
+
hash2: Hash[diff['parameters'].sort], # Should work with somewhat older rubies too
|
175
|
+
limit: 80,
|
176
|
+
strip_diff: true
|
177
|
+
).map(&:green)
|
178
|
+
)
|
179
|
+
else
|
180
|
+
result << "+ #{item}".green
|
181
|
+
if diff.key?('parameters') && logger && !options[:display_detail_add_notice_printed]
|
182
|
+
logger.info 'Note: you can use --display-detail-add to view details of added resources'
|
183
|
+
options[:display_detail_add_notice_printed] = true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
result
|
188
|
+
end
|
189
|
+
|
190
|
+
# Generate info about the source of the change. Pass in parameters as a hash with indicated names.
|
191
|
+
# @param item [Hash] Item that is added/removed/changed
|
192
|
+
# @param result [Array] Result array (modified by this method)
|
193
|
+
# @param old_loc [Hash] Old location hash { file => ..., line => ... }
|
194
|
+
# @param new_loc [Hash] New location hash { file => ..., line => ... }
|
195
|
+
# @param options [Hash] Options hash
|
196
|
+
# @param logger [Logger] Logger object
|
197
|
+
def self.add_source_file_line_info(opts = {})
|
198
|
+
item = opts.fetch(:item)
|
199
|
+
result = opts.fetch(:result)
|
200
|
+
old_loc = opts[:old_loc]
|
201
|
+
new_loc = opts[:new_loc]
|
202
|
+
options = opts.fetch(:options, {})
|
203
|
+
logger = opts[:logger]
|
204
|
+
|
205
|
+
# Initialize any currently undefined settings
|
206
|
+
empty_hash = { 'file' => nil, 'line' => nil }
|
207
|
+
old_loc ||= empty_hash
|
208
|
+
new_loc ||= empty_hash
|
209
|
+
return if old_loc == empty_hash && new_loc == empty_hash
|
210
|
+
|
211
|
+
# Convert old_loc and new_loc to strings
|
212
|
+
old_loc_string = loc_string(old_loc, options[:compilation_from_dir], logger)
|
213
|
+
new_loc_string = loc_string(new_loc, options[:compilation_to_dir], logger)
|
214
|
+
|
215
|
+
# Debug log information and build up local_result with printable changes
|
216
|
+
local_result = []
|
217
|
+
if old_loc == new_loc || new_loc == empty_hash || old_loc_string == new_loc_string
|
218
|
+
logger.debug "#{item} @ #{old_loc_string || 'nil'}" if logger
|
219
|
+
local_result << " #{old_loc_string}".cyan unless old_loc_string.nil?
|
220
|
+
elsif old_loc == empty_hash
|
221
|
+
logger.debug "#{item} @ #{new_loc_string || 'nil'}" if logger
|
222
|
+
local_result << " #{new_loc_string}".cyan unless new_loc_string.nil?
|
223
|
+
else
|
224
|
+
logger.debug "#{item} -@ #{old_loc_string} +@ #{new_loc_string}" if logger
|
225
|
+
local_result << "- #{old_loc_string}".cyan
|
226
|
+
local_result << "+ #{new_loc_string}".cyan
|
227
|
+
end
|
228
|
+
|
229
|
+
# Only modify result if option to display source file and line is enabled
|
230
|
+
result.concat local_result if options[:display_source_file_line]
|
231
|
+
end
|
232
|
+
|
233
|
+
# Convert { file => ..., line => ... } to displayable string
|
234
|
+
# @param loc [Hash] file => ..., line => ... hash
|
235
|
+
# @param compilation_dir [String] Compilation directory
|
236
|
+
# @param logger [Logger] Logger object
|
237
|
+
# @return [String] Location string
|
238
|
+
def self.loc_string(loc, compilation_dir, logger)
|
239
|
+
return nil if loc.nil? || !loc.is_a?(Hash) || loc['file'].nil? || loc['line'].nil?
|
240
|
+
result = "#{loc['file']}:#{loc['line']}"
|
241
|
+
if compilation_dir
|
242
|
+
rex = Regexp.new('^' + Regexp.escape(compilation_dir + '/'))
|
243
|
+
result_new = result.sub(rex, '')
|
244
|
+
if result_new != result
|
245
|
+
logger.debug "Removed compilation directory in #{result} -> #{result_new}" if logger
|
246
|
+
result = result_new
|
247
|
+
end
|
248
|
+
end
|
249
|
+
result
|
250
|
+
end
|
251
|
+
|
252
|
+
# Get the diff of two long strings. Call the 'diffy' gem for this.
|
253
|
+
# @param string1 [String] First string (-)
|
254
|
+
# @param string2 [String] Second string (+)
|
255
|
+
# @param depth [Fixnum] Depth, for correct indentation
|
256
|
+
# @return Array<String> Displayable result
|
257
|
+
def self.diff_two_strings_with_diffy(string1, string2, depth)
|
258
|
+
# prevent 'No newline at end of file' for single line strings
|
259
|
+
string1 += "\n" unless string1 =~ /\n/
|
260
|
+
string2 += "\n" unless string2 =~ /\n/
|
261
|
+
diff = Diffy::Diff.new(string1, string2, context: 2, include_diff_info: true).to_s.split("\n")
|
262
|
+
diff.shift # Remove first line of diff info (filename that makes no sense)
|
263
|
+
diff.shift # Remove second line of diff info (filename that makes no sense)
|
264
|
+
diff.map { |x| left_pad(2 * depth + 2, x) }
|
265
|
+
end
|
266
|
+
|
267
|
+
# Get the diff of two hashes. Call the 'diffy' gem for this.
|
268
|
+
# @param hash1 [Hash] First hash (-)
|
269
|
+
# @param hash1 [Hash] Second hash (+)
|
270
|
+
# @param depth [Fixnum] Depth, for correct indentation
|
271
|
+
# @param limit [Fixnum] Maximum string length
|
272
|
+
# @param strip_diff [Boolean] Strip leading +/-/" "
|
273
|
+
# @return Array<String> Displayable result
|
274
|
+
def self.diff_two_hashes_with_diffy(opts = {})
|
275
|
+
depth = opts.fetch(:depth, 0)
|
276
|
+
hash1 = opts.fetch(:hash1, {})
|
277
|
+
hash2 = opts.fetch(:hash2, {})
|
278
|
+
limit = opts[:limit]
|
279
|
+
strip_diff = opts.fetch(:strip_diff, false)
|
280
|
+
|
281
|
+
json_old = stringify_for_diffy(hash1)
|
282
|
+
json_new = stringify_for_diffy(hash2)
|
283
|
+
|
284
|
+
# If stripping the diff, we need to make sure diffy does not colorize the output, so that
|
285
|
+
# there are not color codes in the output to deal with.
|
286
|
+
diff = if strip_diff
|
287
|
+
Diffy::Diff.new(json_old, json_new, context: 0).to_s(:text).split("\n")
|
288
|
+
else
|
289
|
+
Diffy::Diff.new(json_old, json_new, context: 0).to_s.split("\n")
|
290
|
+
end
|
291
|
+
raise "Diffy diff empty for string: #{json_old}" if diff.empty?
|
292
|
+
|
293
|
+
# This is the array that is returned
|
294
|
+
diff.map do |x|
|
295
|
+
x = x[2..-1] if strip_diff # Drop first 2 characters: '+ ', '- ', or ' '
|
296
|
+
truncate_string(left_pad(2 * depth + 2, x), limit)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Limit length of a string
|
301
|
+
# @param str [String] String
|
302
|
+
# @param limit [Fixnum] Limit (0=unlimited)
|
303
|
+
# @return [String] Truncated string
|
304
|
+
def self.truncate_string(str, limit)
|
305
|
+
return str if limit.nil? || str.length <= limit
|
306
|
+
"#{str[0..limit]}..."
|
307
|
+
end
|
308
|
+
|
309
|
+
# Get the diff between two hashes. This is recursive-aware.
|
310
|
+
# @param obj [diff object] diff object
|
311
|
+
# @param depth [Fixnum] Depth of nesting, used for indentation
|
312
|
+
# @return Array<String> Printable diff outputs
|
313
|
+
def self.hash_diff(obj, depth, key_in, nested = false)
|
314
|
+
result = []
|
315
|
+
result << left_pad(2 * depth, " #{key_in} =>")
|
316
|
+
if obj.key?(:old) && obj.key?(:new)
|
317
|
+
if nested && obj[:old].is_a?(Hash) && obj[:new].is_a?(Hash)
|
318
|
+
# Nested hashes will be stringified and then use 'diffy'
|
319
|
+
result.concat diff_two_hashes_with_diffy(depth: depth, hash1: obj[:old], hash2: obj[:new])
|
320
|
+
elsif obj[:old].is_a?(String) && obj[:new].is_a?(String) && (obj[:old] =~ /\n/ || obj[:new] =~ /\n/)
|
321
|
+
# Multi-line strings will be split and then use 'diffy' to mimic the
|
322
|
+
# output seen when using "diff" on the command line
|
323
|
+
result.concat diff_two_strings_with_diffy(obj[:old], obj[:new], depth)
|
324
|
+
else
|
325
|
+
# Stuff we don't recognize will be converted to a string and printed
|
326
|
+
# with '+' and '-' unless the object resolves to an empty string.
|
327
|
+
result.concat diff_at_depth(depth, obj[:old], obj[:new])
|
328
|
+
end
|
329
|
+
else
|
330
|
+
obj.keys.sort.each { |key| result.concat hash_diff(obj[key], 1 + depth, key, nested) }
|
331
|
+
end
|
332
|
+
result
|
333
|
+
end
|
334
|
+
|
335
|
+
# Get the diff between two arbitrary objects
|
336
|
+
# @param depth [Fixnum] Depth of nesting, used for indentation
|
337
|
+
# @param old_obj [?] Old object
|
338
|
+
# @param new_obj [?] New object
|
339
|
+
# @return Array<String> Diff output
|
340
|
+
def self.diff_at_depth(depth, old_obj, new_obj)
|
341
|
+
old_s = old_obj.to_s
|
342
|
+
new_s = new_obj.to_s
|
343
|
+
result = []
|
344
|
+
result << left_pad(2 * depth + 2, "- #{old_s}").red unless old_s == ''
|
345
|
+
result << left_pad(2 * depth + 2, "+ #{new_s}").green unless new_s == ''
|
346
|
+
result
|
347
|
+
end
|
348
|
+
|
349
|
+
# Utility Method!
|
350
|
+
# Indent a given text string with a certain number of spaces
|
351
|
+
# @param spaces [Fixnum] Number of spaces
|
352
|
+
# @param text [String] Text
|
353
|
+
def self.left_pad(spaces, text = '')
|
354
|
+
[' ' * spaces, text].join('')
|
355
|
+
end
|
356
|
+
|
357
|
+
# Utility Method!
|
358
|
+
# Given an arbitrary object, convert it into a string for use by 'diffy'.
|
359
|
+
# This basically exists so we can do something prettier than just calling .inspect or .to_s
|
360
|
+
# on object types we anticipate seeing, while not failing entirely on other object types.
|
361
|
+
# @param obj [?] Object to be stringified
|
362
|
+
# @return [String] String representation of object for diffy
|
363
|
+
def self.stringify_for_diffy(obj)
|
364
|
+
return JSON.pretty_generate(obj) if [Hash, Array].include?(obj.class)
|
365
|
+
return '""' if obj.is_a?(String) && obj == ''
|
366
|
+
return obj if [String, Fixnum, Float].include?(obj.class)
|
367
|
+
"#{obj.class}: #{obj.inspect}"
|
368
|
+
end
|
369
|
+
|
370
|
+
# Utility Method!
|
371
|
+
# Implement the --display-datatype-changes option by:
|
372
|
+
# - Removing string-equivalent differences when option == false
|
373
|
+
# - Updating display of string-equivalent differences when option == true
|
374
|
+
# @param diff [Array<Diff Objects>] Difference array
|
375
|
+
# @param option [Boolean] Selected behavior; see description
|
376
|
+
# @param logger [Logger] Logger object
|
377
|
+
def self.adjust_for_display_datatype_changes(diff, option, logger = nil)
|
378
|
+
diff.map! do |diff_obj|
|
379
|
+
if diff_obj[0] == '+' || diff_obj[0] == '-'
|
380
|
+
diff_obj[2] = 'undef' if diff_obj[2].nil?
|
381
|
+
diff_obj
|
382
|
+
else
|
383
|
+
x2, x3 = _adjust_for_display_datatype(diff_obj[2], diff_obj[3], option, logger)
|
384
|
+
if x2.nil? && x3.nil?
|
385
|
+
# Delete this! Return nil and compact! will get rid of them.
|
386
|
+
msg = "Adjust display for #{diff_obj[1].gsub(/\f/, '::')}: " \
|
387
|
+
"#{diff_obj[2].inspect} != #{diff_obj[3].inspect} DELETED"
|
388
|
+
logger.debug(msg) if logger
|
389
|
+
nil
|
390
|
+
elsif x2 == diff_obj[2] && x3 == diff_obj[3]
|
391
|
+
# Neither object changed
|
392
|
+
diff_obj
|
393
|
+
else
|
394
|
+
# Adjust the display and return modified object
|
395
|
+
msg = "Adjust display for #{diff_obj[1].gsub(/\f/, '::')}: " \
|
396
|
+
"#{diff_obj[2].inspect} -> #{x2}; "\
|
397
|
+
"#{diff_obj[3].inspect} -> #{x3}"
|
398
|
+
logger.debug(msg) if logger
|
399
|
+
diff_obj[2] = x2
|
400
|
+
diff_obj[3] = x3
|
401
|
+
diff_obj
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
diff.compact!
|
406
|
+
end
|
407
|
+
|
408
|
+
# Utility Method!
|
409
|
+
# Called by adjust_for_display_datatype_changes to compare an old value
|
410
|
+
# to a new value and adjust as appropriate.
|
411
|
+
# @param obj1 [?] First object
|
412
|
+
# @param obj2 [?] Second object
|
413
|
+
# @param option [Boolean] Selected behavior; see adjust_for_display_datatype_changes
|
414
|
+
# @return [<String, String> or <?, ?>] Updated values of objects
|
415
|
+
def self._adjust_for_display_datatype(obj1, obj2, option, logger)
|
416
|
+
# If not string-equal, return to leave untouched
|
417
|
+
return [obj1, obj2] unless obj1.to_s == obj2.to_s
|
418
|
+
|
419
|
+
# Delete if option to display these is false
|
420
|
+
return [nil, nil] unless option
|
421
|
+
|
422
|
+
# Delete if both objects are nil
|
423
|
+
return [nil, nil] if obj1.nil? && obj2.nil?
|
424
|
+
|
425
|
+
# If one is nil and the other is the empty string...
|
426
|
+
return ['undef', '""'] if obj1.nil?
|
427
|
+
return ['""', 'undef'] if obj2.nil?
|
428
|
+
|
429
|
+
# If one is an integer and the other is a string
|
430
|
+
return [obj1, "\"#{obj2}\""] if obj1.is_a?(Fixnum) && obj2.is_a?(String)
|
431
|
+
return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(Fixnum)
|
432
|
+
|
433
|
+
# True and false
|
434
|
+
return [obj1, "\"#{obj2}\""] if obj1.is_a?(TrueClass) && obj2.is_a?(String)
|
435
|
+
return [obj1, "\"#{obj2}\""] if obj1.is_a?(FalseClass) && obj2.is_a?(String)
|
436
|
+
return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(TrueClass)
|
437
|
+
return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(FalseClass)
|
438
|
+
|
439
|
+
# Unhandled case - warn about it and then return inputs untouched
|
440
|
+
# Note: If you encounter this, please report it so we can add a handler.
|
441
|
+
# :nocov:
|
442
|
+
msg = "In _adjust_for_display_datatype, objects '#{obj1.inspect}' (#{obj1.class}) and"\
|
443
|
+
" '#{obj2.inspect}' (#{obj2.class}) have identical string representations but"\
|
444
|
+
' formatting is not implemented to update display.'
|
445
|
+
logger.warn(msg) if logger
|
446
|
+
[obj1, obj2]
|
447
|
+
# :nocov:
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|