puppetfile_editor 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f3364e07c1a49414ed25fecced5c31afd13a15be
4
+ data.tar.gz: 3377a602b97e701d1f21a8dae430d9da65e418e4
5
+ SHA512:
6
+ metadata.gz: 4768b06d6ea7345a02cdd938716fc61a44cbb156dd2fcf056eaff6bb79adc98670adb09c85b5090bfc86911642e941df02a4a447a47815cff93cdc2481b8f14d
7
+ data.tar.gz: a3f6515c3d4ae5cd647e6fe8b25311d49e06de5b63abff8d44418e578cdc1769a90eb8e76a3d44353c17c3acf88438b6a5d4f7098767707b5dacd4b23a7c726a
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # PuppetfileEditor
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/puppetfile_editor.svg)](https://badge.fury.io/rb/puppetfile_editor)
4
+ [![Build Status](https://travis-ci.org/pegasd/puppetfile_editor.svg?branch=master)](https://travis-ci.org/pegasd/puppetfile_editor)
5
+ [![Coverage Status](https://coveralls.io/repos/github/pegasd/puppetfile_editor/badge.svg)](https://coveralls.io/github/pegasd/puppetfile_editor)
6
+
7
+ This gem provides CLI and Rake interfaces for maintaining Puppetfile.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'puppetfile_editor'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ $ bundle
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```bash
26
+ $ gem install puppetfile_editor
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ TODO
32
+
33
+ ## Development
34
+
35
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
36
+
37
+ To install this gem onto your local machine, run `bundle exec rake install`.
38
+
39
+ ## Contributing
40
+
41
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/pegasd/puppetfile_editor).
42
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the
43
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
44
+
45
+ ## License
46
+
47
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
48
+
49
+ ## Code of Conduct
50
+
51
+ Everyone interacting in the PuppetfileEditor project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the
52
+ [code of conduct](https://github.com/pegasd/puppetfile_editor/blob/master/CODE_OF_CONDUCT.md).
data/bin/pfile ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'puppetfile_editor/cli'
4
+ require 'optparse'
5
+
6
+ filename = File.basename(__FILE__)
7
+
8
+ # Display help if no arguments are specified
9
+ ARGV.push('-h') if ARGV.empty?
10
+
11
+ options = { puppetfile: 'Puppetfile' }
12
+ OptionParser.new do |parser|
13
+ parser.banner = "Usage: #{filename} [options] [command] [command options]"
14
+ parser.on('-v', '--[no-]verbose', 'Run verbosely') { |v| options[:verbose] = v }
15
+ parser.on('-f', '--filename PUPPETFILE', 'Path to your Puppetfile.') do |puppetfile|
16
+ options[:puppetfile] = puppetfile
17
+ end
18
+ end.order!
19
+
20
+ subcommands = {
21
+ 'edit' => OptionParser.new do |parser|
22
+ parser.banner = "Usage: #{filename} edit [options]"
23
+ parser.on('-m', '--module-name NAME', 'Module name') do |setting|
24
+ options[:name] = setting
25
+ end
26
+ parser.on('-u', '--update PARAM=VALUE', 'What to update') do |setting|
27
+ if (match = setting.match(/^(\w+)=([^=]+)$/))
28
+ options[:param], options[:value] = match[1], match[2]
29
+ else
30
+ warn 'Version must match PARAM=VALUE pattern'
31
+ exit 1
32
+ end
33
+ end
34
+ end,
35
+ 'format' => OptionParser.new do |parser|
36
+ parser.banner = "Usage: #{filename} format"
37
+ end,
38
+ 'add' => OptionParser.new do |parser|
39
+ parser.banner = "Usage: #{filename} add [options]"
40
+ parser.on('-m', '--module-name NAME', 'Module name') do |setting|
41
+ options[:name] = setting
42
+ end
43
+ parser.on('-t', '--type TYPE', [:hg, :forge, :local, :git], 'Type of module to add') do |setting|
44
+ options[:type] = setting
45
+ end
46
+ parser.on('-l', '--url [URL]', 'URL of module (applicable for hg and git modules)') do |setting|
47
+ options[:url] = setting
48
+ end
49
+ parser.on('-u', '--version [PARAM=VALUE]', 'Version of module to add in the form of PARAM=VALUE') do |setting|
50
+ if (match = setting.match(/^(\w+)=([^=]+)$/))
51
+ options[:param], options[:value] = match[1], match[2]
52
+ elsif (match = setting.match(/^\d+(\.\d)*$/))
53
+ options[:version] = match[0]
54
+ else
55
+ warn 'Version must match PARAM=VALUE pattern or be a proper Forge version.'
56
+ exit 1
57
+ end
58
+ end
59
+ end,
60
+ 'merge' => OptionParser.new do |parser|
61
+ parser.banner = "Usage: #{filename} merge [options]"
62
+ parser.separator ' Merge existing Puppetfile with another one from STDIN.'
63
+ parser.separator ' Dump the result to STDOUT.'
64
+ parser.separator ''
65
+
66
+ parser.on('-f', '--force', 'Force merge module versions') do |setting|
67
+ options[:force] = setting
68
+ end
69
+ end,
70
+ }
71
+
72
+ command = ARGV.shift
73
+ subcommands[command].order!
74
+
75
+ pf = PuppetfileEditor::CLI.new(options[:puppetfile])
76
+ pf.public_send(command, options) if pf.respond_to?(command)
@@ -0,0 +1,114 @@
1
+ require 'puppetfile_editor'
2
+
3
+ module PuppetfileEditor
4
+ # CLI methods
5
+ class CLI
6
+ def initialize(pfile_path)
7
+ @pfile = PuppetfileEditor::Puppetfile.new(path: pfile_path)
8
+ @logger = PuppetfileEditor::Logging.new
9
+ begin
10
+ @pfile.load
11
+ rescue IOError, NoMethodError => e
12
+ warn_and_exit(e.message)
13
+ end
14
+ end
15
+
16
+ def edit(opts)
17
+ opts[:value] = :latest if opts[:value] == 'latest'
18
+ @pfile.update_module(
19
+ opts[:name],
20
+ opts[:param],
21
+ opts[:value],
22
+ opts[:verbose],
23
+ )
24
+ @pfile.dump
25
+ end
26
+
27
+ def format(_)
28
+ @pfile.dump
29
+ end
30
+
31
+ def add(opts)
32
+ warn_and_exit "Module #{opts[:name]} is already present in your Puppetfile." if @pfile.modules.key? opts[:name]
33
+
34
+ case opts[:type]
35
+ when :hg, :git
36
+ warn_and_exit 'URL must be provided for Git and Hg modules' unless opts.key? :url
37
+ warn_and_exit 'Version must be provided for Git and Hg modules' unless opts.key? :param
38
+ opts[:value] = :latest if opts[:value] == 'latest'
39
+ @pfile.add_module(opts[:name], opts[:type] => opts[:url], opts[:param] => opts[:value])
40
+ when :local
41
+ @pfile.add_module(opts[:name], :local)
42
+ when :forge
43
+ warn_and_exit 'Version must be provided for Forge modules' unless opts.key? :version
44
+ @pfile.add_module(opts[:name], opts[:version])
45
+ else
46
+ warn_and_exit 'Only hg, git, local, and forge modules are supported at the moment.'
47
+ end
48
+ @pfile.dump
49
+ end
50
+
51
+ def merge(opts)
52
+ @pfdata = PuppetfileEditor::Puppetfile.new(from_stdin: true)
53
+ @pfdata.load
54
+ new_mod_types = @pfdata.modules.values.group_by(&:type)
55
+ new_mod_types.each do |mod_type, mods|
56
+ puts "\n #{@pfile.module_sections[mod_type]}\n\n"
57
+ unless [:hg, :git, :forge].include? mod_type
58
+ puts " Skipping #{mod_type} section."
59
+ next
60
+ end
61
+ indent = mods.map(&:name).max_by(&:length).length
62
+ mods.each do |mod|
63
+ if @pfile.modules.key? mod.name
64
+ begin
65
+ @pfile.modules[mod.name].merge_with(mod, opts[:force])
66
+ @logger.module_log(mod.name, indent, @pfile.modules[mod.name].message, @pfile.modules[mod.name].status)
67
+ rescue StandardError => e
68
+ @logger.module_log(mod.name, indent, e.message, @pfile.modules[mod.name].status)
69
+ end
70
+ else
71
+ @logger.module_log(mod.name, indent, 'does not exist in source Puppetfile', :not_found)
72
+ end
73
+ end
74
+ end
75
+ if opts[:stdout]
76
+ $stdout.puts(@pfile.generate_puppetfile)
77
+ else
78
+ @pfile.dump
79
+ end
80
+ end
81
+
82
+ def warn_and_exit(message)
83
+ warn message
84
+ exit 1
85
+ end
86
+ end
87
+
88
+ class Logging
89
+ def initialize
90
+ @statuses = {
91
+ updated: "[ \e[32;1m+\e[0m ]",
92
+ matched: "[ \e[33;1m=\e[0m ]",
93
+ skipped: "[ \e[33;1m~\e[0m ]",
94
+ not_found: "[ \e[31;1mx\e[0m ]",
95
+ type_mismatched: "[ \e[31;1mx\e[0m ]",
96
+ wont_upgrade: "[ \e[33;1m!\e[0m ]",
97
+ undef: '',
98
+ }
99
+ end
100
+
101
+ def log(message, message_type = :undef)
102
+ status = if @statuses.key? message_type
103
+ @statuses[message_type]
104
+ else
105
+ @statuses[:undef]
106
+ end
107
+ puts "#{status} #{message}"
108
+ end
109
+
110
+ def module_log(mod_name, indent, message, message_type = :undef)
111
+ log("#{mod_name.ljust(indent)} => #{message}", message_type)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,155 @@
1
+ module PuppetfileEditor
2
+ class Module
3
+ attr_reader :type
4
+ attr_reader :params
5
+ attr_reader :name
6
+ attr_reader :message
7
+ attr_reader :status
8
+
9
+ def initialize(title, args = nil)
10
+ @type = :undef
11
+ @params = nil
12
+ @message = nil
13
+ @status = nil
14
+ if args == :local
15
+ @type = :local
16
+ elsif args.nil? || args.is_a?(String) || args.is_a?(Symbol)
17
+ @type = :forge
18
+ @params = { version: args } unless args.nil?
19
+ elsif args.is_a? Hash
20
+ if args.key? :hg
21
+ @type = :hg
22
+ elsif args.key? :git
23
+ @type = :git
24
+ end
25
+ @params = args
26
+ calculate_indent
27
+ end
28
+ @author, @name = parse_title title
29
+ end
30
+
31
+ def set(param, newvalue, force = false)
32
+ case @type
33
+ when :hg, :git
34
+ if %w[branch tag ref].include? param
35
+ @params.delete :branch
36
+ @params.delete :tag
37
+ @params.delete :ref
38
+ @params[param.to_sym] = newvalue
39
+ calculate_indent
40
+ else
41
+ raise StandardError, "Only 'branch', 'tag', and 'ref' are supported for '#{@type}' modules."
42
+ end
43
+ when :forge
44
+ if param == 'version'
45
+ @params[:version] = newvalue
46
+ else
47
+ raise StandardError, "Only 'version' is supported for forge modules."
48
+ end
49
+ else
50
+ raise StandardError, "Editing params for '#{@type}' modules is not supported."
51
+ end
52
+ end
53
+
54
+ def merge_with(mod, force = false)
55
+ unless mod.type == @type
56
+ @status = :type_mismatched
57
+ raise(StandardError, "type mismatch ('#{@type}' vs '#{mod.type}')")
58
+ end
59
+ case @type
60
+ when :hg, :git
61
+ new = mod.params.reject { |param, _| param.eql? @type }
62
+ if !force && new.keys == [:tag] && (@params.key?(:branch) || @params.key?(:ref))
63
+ raise(StandardError, "kept at #{full_version}")
64
+ end
65
+ if full_version == mod.full_version
66
+ @message = "versions match (#{full_version})"
67
+ @status = :matched
68
+ else
69
+ @message = "updated (#{full_version} to #{mod.full_version})"
70
+ @status = :updated
71
+ end
72
+ @params.delete_if { |param, _| [:branch, :tag, :ref].include? param }
73
+ @params.merge!(new)
74
+ calculate_indent
75
+ when :forge
76
+ unless force
77
+ if mod.params.nil? or mod.params.is_a? Symbol
78
+ @status = :wont_upgrade
79
+ raise(StandardError, "won't upgrade to #{mod.full_version}")
80
+ end
81
+ end
82
+ if full_version == mod.full_version
83
+ @message = "versions match (#{full_version})"
84
+ @status = :matched
85
+ else
86
+ @message = "updated (#{full_version} to #{mod.full_version})"
87
+ @status = :updated
88
+ end
89
+ @params = mod.params
90
+ else
91
+ @status = :skipped
92
+ raise(StandardError, 'only git, forge, and hg modules are supported for merging')
93
+ end
94
+ end
95
+
96
+ def dump(old_hashes = false)
97
+ output = []
98
+ case @type
99
+ when :hg, :git
100
+ output.push "mod '#{full_title}'"
101
+ @params.each do |param_name, param_value|
102
+ value = if param_value == :latest
103
+ ':latest'
104
+ else
105
+ "'#{param_value}'"
106
+ end
107
+ param = old_hashes ? ":#{param_name.to_s.ljust(@indent - 1)} =>" : "#{param_name}:".ljust(@indent)
108
+ output.push " #{param} #{value}"
109
+ end
110
+ when :local
111
+ output.push("mod '#{full_title}', :local")
112
+ else
113
+ if @params.nil?
114
+ output.push("mod '#{full_title}'")
115
+ else
116
+ output.push("mod '#{full_title}', '#{@params[:version]}'")
117
+ end
118
+ end
119
+ output.join(",\n")
120
+ end
121
+
122
+ def full_title
123
+ return "#{@author}/#{@name}" if @author
124
+ @name
125
+ end
126
+
127
+ def full_version
128
+ case @type
129
+ when :hg, :git
130
+ @params.reject { |param, _| param.eql? @type }.map { |param, value| "#{param}: #{value}" }.sort.join(', ')
131
+ when :forge
132
+ return @params[:version] if @params.key? :version
133
+ nil
134
+ else
135
+ nil
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def parse_title(title)
142
+ if (match = title.match(/^(\w+)$/))
143
+ [nil, match[1]]
144
+ elsif (match = title.match(%r{^(\w+)[/-](\w[\w-]*\w)$}))
145
+ [match[1], match[2]]
146
+ else
147
+ raise ArgumentError, "Module name (#{title}) must match either 'modulename' or 'owner/modulename'"
148
+ end
149
+ end
150
+
151
+ def calculate_indent
152
+ @indent = @params.keys.max_by(&:length).length + 1
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,119 @@
1
+ require 'puppetfile_editor/module'
2
+
3
+ module PuppetfileEditor
4
+ # Puppetfile implementation
5
+ class Puppetfile
6
+ # @!attribute [r] modules
7
+ # @return [Hash<String, PuppetfileEditor::Module>]
8
+ attr_reader :modules
9
+
10
+ # @!attribute [r] puppetfile_path
11
+ # @return [String] The path to the Puppetfile
12
+ attr_reader :puppetfile_path
13
+
14
+ attr_reader :module_sections
15
+
16
+ # @param [String] path path to Puppetfile
17
+ def initialize(path: 'Puppetfile', from_stdin: false, old_hashes: false)
18
+ @puppetfile_path = path
19
+ @from_stdin = from_stdin
20
+ @old_hashes = old_hashes
21
+ @modules = {}
22
+ @loaded = false
23
+ @forge = nil
24
+
25
+ @module_sections = {
26
+ local: 'Local modules',
27
+ forge: 'Modules from the Puppet Forge',
28
+ hg: 'Mercurial modules',
29
+ git: 'Git modules',
30
+ undef: 'Other modules',
31
+ }
32
+ end
33
+
34
+ def load
35
+ puppetfile_contents = if @from_stdin
36
+ $stdin.gets(nil).chomp
37
+ else
38
+ raise(IOError, "Puppetfile #{@puppetfile_path} missing or unreadable") unless File.readable?(@puppetfile_path)
39
+
40
+ File.read @puppetfile_path
41
+ end
42
+
43
+ dsl = PuppetfileEditor::DSL.new(self)
44
+ dsl.instance_eval(puppetfile_contents)
45
+ @loaded = true
46
+ end
47
+
48
+ def generate_puppetfile
49
+ raise StandardError, 'File is not loaded' unless @loaded
50
+
51
+ contents = []
52
+
53
+ contents.push "forge '#{@forge}'\n" if @forge
54
+
55
+ @module_sections.each do |module_type, module_comment|
56
+ module_list = modules.select { |_, mod| mod.type == module_type }
57
+ next unless module_list.any?
58
+ contents.push "# #{module_comment}"
59
+ module_list.values.sort_by(&:name).each do |mod|
60
+ contents.push mod.dump(@old_hashes)
61
+ end
62
+ contents.push ''
63
+ end
64
+
65
+ contents.join("\n")
66
+ end
67
+
68
+ def dump
69
+ File.write(@puppetfile_path, generate_puppetfile) if @loaded
70
+ end
71
+
72
+ def update_module(name, param, value, verbose = false)
73
+ if @modules.key? name
74
+ begin
75
+ @modules[name].set(param, value)
76
+ puts "Successfully set #{param} to #{value} for #{name}." if verbose
77
+ rescue StandardError => e
78
+ warn e.message
79
+ exit 1
80
+ end
81
+ else
82
+ warn "Module #{name} does not exist in your Puppetfile"
83
+ end
84
+ end
85
+
86
+ # @param [String] name Module name
87
+ # @param [String, Hash] args Module arguments
88
+ def add_module(name, args)
89
+ mod = PuppetfileEditor::Module.new(name, args)
90
+ @modules[mod.name] = mod
91
+ end
92
+
93
+ def update_forge_url(url)
94
+ raise StandardError, "Forge URL must be a String, but it is a #{url.class}" unless url.is_a? String
95
+ @forge = url
96
+ end
97
+ end
98
+
99
+ # A barebones implementation of the Puppetfile DSL
100
+ #
101
+ # @api private
102
+ class DSL
103
+ def initialize(librarian)
104
+ @librarian = librarian
105
+ end
106
+
107
+ def mod(name, args = nil)
108
+ @librarian.add_module(name, args)
109
+ end
110
+
111
+ def forge(url)
112
+ @librarian.update_forge_url(url)
113
+ end
114
+
115
+ def method_missing(method, *_)
116
+ raise NoMethodError, "Unrecognized declaration: '#{method}'"
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,3 @@
1
+ module PuppetfileEditor
2
+ VERSION = '0.2.0'.freeze
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'puppetfile_editor/version'
2
+ require 'puppetfile_editor/puppetfile'
3
+ require 'puppetfile_editor/module'
4
+ require 'puppetfile_editor/cli'
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puppetfile_editor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Eugene Piven
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: |2
56
+ PuppetfileEditor provides an easy-to-use interface to check Puppetfile for validity,
57
+ update module versions, add, and remove modules.
58
+ email: epiven@gmail.com
59
+ executables:
60
+ - pfile
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - README.md
65
+ - bin/pfile
66
+ - lib/puppetfile_editor.rb
67
+ - lib/puppetfile_editor/cli.rb
68
+ - lib/puppetfile_editor/module.rb
69
+ - lib/puppetfile_editor/puppetfile.rb
70
+ - lib/puppetfile_editor/version.rb
71
+ homepage: https://github.com/pegasd/puppetfile_editor
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 2.6.12
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Parse and edit Puppetfile
95
+ test_files: []