nugrant 2.0.0.dev2 → 2.0.0.pre1

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.
Files changed (54) hide show
  1. checksums.yaml +6 -14
  2. data/.gitignore +2 -1
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +148 -3
  5. data/Gemfile +8 -20
  6. data/README.md +266 -72
  7. data/Rakefile +1 -0
  8. data/lib/nugrant.rb +14 -6
  9. data/lib/nugrant/bag.rb +116 -62
  10. data/lib/nugrant/helper/bag.rb +19 -19
  11. data/lib/nugrant/helper/env/exporter.rb +208 -0
  12. data/lib/nugrant/helper/env/namer.rb +47 -0
  13. data/lib/nugrant/helper/parameters.rb +12 -0
  14. data/lib/nugrant/helper/stack.rb +86 -0
  15. data/lib/nugrant/mixin/parameters.rb +98 -0
  16. data/lib/nugrant/parameters.rb +14 -68
  17. data/lib/nugrant/vagrant/errors.rb +27 -0
  18. data/lib/nugrant/vagrant/v2/command/env.rb +101 -0
  19. data/lib/nugrant/vagrant/v2/command/helper.rb +30 -0
  20. data/lib/nugrant/vagrant/v2/command/parameters.rb +16 -4
  21. data/lib/nugrant/vagrant/v2/command/restricted_keys.rb +60 -0
  22. data/lib/nugrant/vagrant/v2/command/root.rb +12 -2
  23. data/lib/nugrant/vagrant/v2/config/user.rb +9 -21
  24. data/lib/nugrant/vagrant/v2/plugin.rb +0 -1
  25. data/lib/nugrant/version.rb +1 -1
  26. data/locales/en.yml +13 -0
  27. data/nugrant.gemspec +3 -7
  28. data/test/lib/nugrant/helper/env/test_exporter.rb +238 -0
  29. data/test/lib/nugrant/helper/test_bag.rb +16 -0
  30. data/test/lib/nugrant/helper/test_parameters.rb +17 -0
  31. data/test/lib/nugrant/helper/test_stack.rb +152 -0
  32. data/test/lib/nugrant/test_bag.rb +132 -22
  33. data/test/lib/nugrant/test_config.rb +95 -92
  34. data/test/lib/nugrant/test_parameters.rb +232 -177
  35. data/test/lib/test_helper.rb +3 -0
  36. data/test/resources/json/params_user_nil_values.json +9 -0
  37. data/test/resources/vagrantfiles/v2.defaults_mixed_string_symbols +18 -0
  38. data/test/resources/vagrantfiles/v2.defaults_null_values_in_vagrantuser +23 -0
  39. data/test/resources/vagrantfiles/v2.defaults_using_string +18 -0
  40. data/test/resources/vagrantfiles/v2.defaults_using_symbol +18 -0
  41. data/test/resources/{Vagrantfile.v2.empty → vagrantfiles/v2.empty} +0 -2
  42. data/test/resources/{Vagrantfile.v2.fake → vagrantfiles/v2.fake} +4 -3
  43. data/test/resources/vagrantfiles/v2.missing_parameter +3 -0
  44. data/test/resources/{Vagrantfile.v2.real → vagrantfiles/v2.real} +0 -2
  45. data/test/resources/yaml/params_user_nil_values.yml +5 -0
  46. metadata +55 -88
  47. data/lib/nugrant/vagrant/v1/command/parameters.rb +0 -134
  48. data/lib/nugrant/vagrant/v1/command/root.rb +0 -81
  49. data/lib/nugrant/vagrant/v1/config/user.rb +0 -37
  50. data/lib/nugrant/vagrant/v1/plugin.rb +0 -6
  51. data/lib/vagrant_init.rb +0 -2
  52. data/test/resources/Vagrantfile.v1.empty +0 -2
  53. data/test/resources/Vagrantfile.v1.fake +0 -10
  54. data/test/resources/Vagrantfile.v1.real +0 -19
data/Rakefile CHANGED
@@ -17,4 +17,5 @@ end
17
17
 
18
18
  desc "Run tests"
19
19
  task :default => :test
20
+ task :tests => :test
20
21
 
data/lib/nugrant.rb CHANGED
@@ -8,16 +8,24 @@ unless defined?(KeyError)
8
8
  end
9
9
  end
10
10
 
11
+ module Nugrant
12
+ def self.setup_i18n()
13
+ I18n.load_path << File.expand_path("locales/en.yml", Nugrant.source_root)
14
+ I18n.reload!
15
+ end
16
+
17
+ def self.source_root
18
+ @source_root ||= Pathname.new(File.expand_path("../../", __FILE__))
19
+ end
20
+ end
21
+
11
22
  if defined?(Vagrant)
23
+ Nugrant.setup_i18n()
24
+
12
25
  case
13
26
  when defined?(Vagrant::Plugin::V2)
14
27
  require 'nugrant/vagrant/v2/plugin'
15
- when Vagrant::VERSION =~ /1\.0\..*/
16
- # Nothing to do, v1 plugins are picked by the vagrant_init.rb file
17
28
  else
18
- abort("You are trying to use Nugrant with an unsupported Vagrant version [#{Vagrant::VERSION}]")
29
+ raise RuntimeError, "Vagrant [#{Vagrant::VERSION}] is not supported by Nugrant."
19
30
  end
20
31
  end
21
-
22
- module Nugrant
23
- end
data/lib/nugrant/bag.rb CHANGED
@@ -1,96 +1,150 @@
1
1
  module Nugrant
2
- class Bag
3
- attr_reader :__elements
2
+ class Bag < Hash
4
3
 
5
- def initialize(elements = nil)
6
- if elements.kind_of?(Bag)
7
- @__elements = elements
4
+ ##
5
+ # Create a new Bag object which holds key/value pairs.
6
+ # The Bag object inherits from the Hash object, the main
7
+ # differences with a normal Hash are indifferent access
8
+ # (symbol or string) and method access (via method call).
9
+ #
10
+ # =| Arguments
11
+ # * `elements`
12
+ # The initial elements the bag should be built with it.'
13
+ # Must be an object responding to `each` and accepting
14
+ # a block with two arguments: `key, value`.]. Defaults to
15
+ # the empty hash.
16
+ #
17
+ # * `options`
18
+ # An options hash where some customization option can be passed.
19
+ # Defaults to an empty hash, see options for specific option default
20
+ # values.
21
+ #
22
+ # =| Options
23
+ # * `:key_error`
24
+ # A callable object receiving a single parameter `key` that is
25
+ # called when a key cannot be found in the Bag. The received key
26
+ # is already converted to a symbol. If the callable does not
27
+ # raise an exception, the result of it's execution is returned.
28
+ # The default value is a callable that throws a KeyError exception.
29
+ #
30
+ def initialize(elements = {}, options = {})
31
+ super()
32
+
33
+ @__key_error = options[:key_error] || Proc.new do |key|
34
+ raise KeyError, "Undefined parameter '#{key}'" if not key?(key)
8
35
  end
9
36
 
10
- __recompute(elements)
37
+ (elements || {}).each do |key, value|
38
+ self[key] = value.kind_of?(Hash) ? Bag.new(value, options) : value
39
+ end
11
40
  end
12
41
 
13
- def [](key)
14
- return __fetch(key)
42
+ def method_missing(method, *args, &block)
43
+ return self[method]
15
44
  end
16
45
 
17
- def method_missing(method, *args, &block)
18
- return __fetch(method)
46
+ ##
47
+ ### Hash Overriden Methods (for string & symbol indifferent access)
48
+ ##
49
+
50
+ def [](input)
51
+ key = __convert_key(input)
52
+ return @__key_error.call(key) if not key?(key)
53
+
54
+ super(key)
19
55
  end
20
56
 
21
- def has?(key)
22
- return @__elements.has_key?(key)
57
+ def []=(input, value)
58
+ super(__convert_key(input), value)
23
59
  end
24
60
 
25
- def empty?()
26
- @__elements.size() <= 0
61
+ def key?(key)
62
+ super(__convert_key(key))
27
63
  end
28
64
 
29
65
  ##
30
- # This method always perform a deep merge and will deep merge
31
- # array scalar values only. This means that we do not merge
32
- # within array themselves.
66
+ # This method first start by converting the `input` parameter
67
+ # into a bag. It will then *deep* merge current values with
68
+ # the new ones coming from the `input`.
33
69
  #
34
- def __merge!(elements)
35
- bag = elements.kind_of?(Bag) ? elements : Bag.new(elements)
36
- return if bag.empty?()
37
-
38
- bag.each do |key, value|
39
- if has?(key)
40
- current = @__elements[key]
41
- if current.kind_of?(Bag) and value.kind_of?(Bag)
42
- current.__merge!(value)
43
- elsif current.kind_of?(Array) and value.kind_of?(Array)
44
- @__elements[key] = current | value
45
- else
46
- @__elements[key] = value
47
- end
48
-
49
- next
50
- end
70
+ # The array merge strategy is by default to replace current
71
+ # values with new ones. You can use option `:array_strategy`
72
+ # to change this default behavior.
73
+ #
74
+ # +Options+
75
+ # * :array_strategy
76
+ # * :replace (Default) => Replace current values by new ones
77
+ # * :extend => Merge current values with new ones
78
+ # * :concat => Append new values to current ones
79
+ #
80
+ def merge!(input, options = {})
81
+ options = {:array_strategy => :replace}.merge(options)
51
82
 
52
- @__elements[key] = value
53
- end
54
- end
83
+ array_strategy = options[:array_strategy]
84
+ input.each do |key, value|
85
+ current = __get(key)
86
+ case
87
+ when current == nil
88
+ self[key] = value
89
+
90
+ when current.kind_of?(Hash) && value.kind_of?(Hash)
91
+ current.merge!(value, options)
55
92
 
56
- def each()
57
- @__elements.each do |key, value|
58
- yield key, value
93
+ when current.kind_of?(Array) && value.kind_of?(Array)
94
+ self[key] = send("__#{array_strategy}_array_merge", current, value)
95
+
96
+ when value != nil
97
+ self[key] = value
98
+ end
59
99
  end
60
100
  end
61
101
 
62
- def __to_hash()
102
+ def to_hash(options = {})
63
103
  return {} if empty?()
64
104
 
65
- hash = {}
66
- each do |key, value|
67
- hash[key.to_sym()] = value.kind_of?(Bag) ? value.__to_hash() : value
68
- end
105
+ use_string_key = options[:use_string_key]
69
106
 
70
- return hash
107
+ Hash[map do |key, value|
108
+ key = use_string_key ? key.to_s() : key
109
+ value = value.kind_of?(Bag) ? value.to_hash(options) : value
110
+
111
+ [key, value]
112
+ end]
71
113
  end
72
114
 
73
- def __recompute(hash = nil)
74
- @__elements = {}
75
- return if hash == nil or not hash.kind_of?(Hash)
115
+ ##
116
+ ### Aliases
117
+ ##
76
118
 
77
- hash.each do |key, value|
78
- if not value.kind_of?(Hash)
79
- @__elements[key.to_sym()] = value
80
- next
81
- end
119
+ alias_method :to_ary, :to_a
82
120
 
83
- # It is a hash, transform it into a bag
84
- @__elements[key.to_sym()] = Bag.new(value)
85
- end
121
+ ##
122
+ ### Private Methods
123
+ ##
124
+
125
+ private
126
+
127
+ def __convert_key(key)
128
+ return key.to_sym() if key.respond_to?(:to_sym)
129
+
130
+ raise ArgumentError, "Key cannot be converted to symbol, current value [#{key}] (#{key.class.name})"
86
131
  end
87
132
 
88
- def __fetch(key)
89
- if not has?(key)
90
- raise KeyError, "Undefined parameter '#{key}'"
91
- end
133
+ def __get(key)
134
+ # Calls Hash method [__convert_key(key)], used internally to retrieve value without raising Undefined parameter
135
+ self.class.superclass.instance_method(:[]).bind(self).call(__convert_key(key))
136
+ end
137
+
138
+ def __concat_array_merge(current_array, new_array)
139
+ current_array + new_array
140
+ end
141
+
142
+ def __extend_array_merge(current_array, new_array)
143
+ current_array | new_array
144
+ end
92
145
 
93
- return @__elements[key]
146
+ def __replace_array_merge(current_array, new_array)
147
+ new_array
94
148
  end
95
149
  end
96
150
  end
@@ -6,36 +6,36 @@ require 'nugrant/bag'
6
6
  module Nugrant
7
7
  module Helper
8
8
  module Bag
9
- def self.read(filepath, format, error_handler = nil)
10
- data = parse_data(filepath, format, error_handler)
9
+ def self.read(filepath, filetype, options = {})
10
+ data = parse_data(filepath, filetype, options)
11
11
 
12
- return Nugrant::Bag.new(data)
12
+ return Nugrant::Bag.new(data, options)
13
13
  end
14
14
 
15
- def self.parse_data(filepath, format, error_handler = nil)
15
+ def self.restricted_keys()
16
+ Nugrant::Bag.instance_methods()
17
+ end
18
+
19
+ def self.parse_data(filepath, filetype, options = {})
16
20
  return if not File.exists?(filepath)
17
21
 
18
- begin
19
- File.open(filepath, "rb") do |file|
20
- parsing_method = "parse_#{format.to_s}"
21
- return send(parsing_method, file.read())
22
- end
23
- rescue => error
24
- if error_handler
25
- # TODO: Implements error handler logic
26
- error_handler.handle("Could not parse the user #{format.to_s} parameters file '#{filepath}': #{error}")
27
- end
22
+ File.open(filepath, "rb") do |file|
23
+ return send("parse_#{filetype}", file)
24
+ end
25
+ rescue => error
26
+ if options[:error_handler]
27
+ options[:error_handler].handle("Could not parse the user #{filetype} parameters file '#{filepath}': #{error}")
28
28
  end
29
29
  end
30
30
 
31
- def self.parse_json(input)
32
- JSON.parse(input)
31
+ def self.parse_json(io)
32
+ MultiJson.load(io.read())
33
33
  end
34
34
 
35
- def self.parse_yaml(input)
36
- YAML::ENGINE.yamler= 'syck' if defined?(YAML::ENGINE)
35
+ def self.parse_yaml(io)
36
+ YAML::ENGINE.yamler = 'syck' if (defined?(Syck) || defined?(YAML::Syck)) && defined?(YAML::ENGINE)
37
37
 
38
- YAML.load(input)
38
+ YAML.load(io.read())
39
39
  end
40
40
  end
41
41
  end
@@ -0,0 +1,208 @@
1
+ require 'shellwords'
2
+
3
+ require 'nugrant/bag'
4
+ require 'nugrant/helper/env/namer'
5
+
6
+ module Nugrant
7
+ module Helper
8
+ module Env
9
+ module Exporter
10
+ @@DEFAULT_AUTOENV_PATH = "./.env"
11
+ @@DEFAULT_SCRIPT_PATH = "./nugrant2env.sh"
12
+
13
+ @@VALID_EXPORTERS = [:autoenv, :script, :terminal]
14
+
15
+ ##
16
+ # Returns true if the exporter name received is a valid
17
+ # valid export, false otherwise.
18
+ #
19
+ # @param exporter The exporter name to check validity
20
+ #
21
+ # @return true if exporter is valid, false otherwise.
22
+ def self.valid?(exporter)
23
+ @@VALID_EXPORTERS.include?(exporter)
24
+ end
25
+
26
+ ##
27
+ # Creates an autoenv script containing the commands that are required
28
+ # to export or unset a bunch of environment variables taken from the
29
+ # bag.
30
+ #
31
+ # @param bag The bag to create the script for.
32
+ #
33
+ # @return (side-effect) Creates a script file containing commands
34
+ # to export or unset environment variables for
35
+ # bag.
36
+ #
37
+ # Options:
38
+ # * :autoenv_path => The path where to write the script, defaults to `./.env`.
39
+ # * :escape_value => If true, escape the value to export (or unset), default to true.
40
+ # * :io => The io where the command should be written, default to nil which create the autoenv on disk.
41
+ # * :namer => The namer used to transform bag segments into variable name, default to Namer::default().
42
+ # * :override => If true, variable a exported even when the override an existing env key, default to true.
43
+ # * :type => The type of command, default to :export.
44
+ #
45
+ def self.autoenv_exporter(bag, options = {})
46
+ io = options[:io] || (File.open(File.expand_path(options[:autoenv_path] || @@DEFAULT_AUTOENV_PATH), "w"))
47
+
48
+ terminal_exporter(bag, options.merge({:io => io}))
49
+ ensure
50
+ io.close() if io
51
+ end
52
+
53
+ ##
54
+ # Creates a bash script containing the commands that are required
55
+ # to export or unset a bunch of environment variables taken from the
56
+ # bag.
57
+ #
58
+ # @param bag The bag to create the script for.
59
+ #
60
+ # @return (side-effect) Creates a script file containing commands
61
+ # to export or unset environment variables for
62
+ # bag.
63
+ #
64
+ # Options:
65
+ # * :escape_value => If true, escape the value to export (or unset), default to true.
66
+ # * :io => The io where the command should be written, default to nil which create the script on disk.
67
+ # * :namer => The namer used to transform bag segments into variable name, default to Namer::default().
68
+ # * :override => If true, variable a exported even when the override an existing env key, default to true.
69
+ # * :script_path => The path where to write the script, defaults to `./nugrant2env.sh`.
70
+ # * :type => The type of command, default to :export.
71
+ #
72
+ def self.script_exporter(bag, options = {})
73
+ io = options[:io] || (File.open(File.expand_path(options[:script_path] || @@DEFAULT_SCRIPT_PATH), "w"))
74
+
75
+ io.puts("#!/bin/env sh")
76
+ io.puts()
77
+
78
+ terminal_exporter(bag, options.merge({:io => io}))
79
+ ensure
80
+ io.close() if io
81
+ end
82
+
83
+ ##
84
+ # Export to terminal the commands that are required
85
+ # to export or unset a bunch of environment variables taken from the
86
+ # bag.
87
+ #
88
+ # @param bag The bag to create the script for.
89
+ #
90
+ # @return (side-effect) Outputs to io the commands generated.
91
+ #
92
+ # Options:
93
+ # * :escape_value => If true, escape the value to export (or unset), default to true.
94
+ # * :io => The io where the command should be displayed, default to $stdout.
95
+ # * :namer => The namer used to transform bag segments into variable name, default to Namer::default().
96
+ # * :override => If true, variable a exported even when the override an existing env key, default to true.
97
+ # * :type => The type of command, default to :export.
98
+ #
99
+ def self.terminal_exporter(bag, options = {})
100
+ io = options[:io] || $stdout
101
+ type = options[:type] || :export
102
+
103
+ export(bag, options) do |key, value|
104
+ io.puts(command(type, key, value, options))
105
+ end
106
+ end
107
+
108
+ ##
109
+ # Generic function to export a bag. This walk the bag,
110
+ # for each element, it creates the key using the namer
111
+ # and then forward the key and value to the block if
112
+ # the variable does not override an existing environment
113
+ # variable or if options :override is set to true.
114
+ #
115
+ # @param bag The bag to export.
116
+ #
117
+ # @return (side-effect) Yields each key and value to a block
118
+ #
119
+ # Options:
120
+ # * :namer => The namer used to transform bag segments into variable name, default to Namer::default().
121
+ # * :override => If true, variable a exported even when the override an existing env key, default to true.
122
+ #
123
+ def self.export(bag, options = {})
124
+ namer = options[:namer] || Env::Namer.default()
125
+ override = options.fetch(:override, true)
126
+
127
+ variables = {}
128
+ walk_bag(bag) do |segments, key, value|
129
+ key = namer.call(segments)
130
+
131
+ variables[key] = value if override or not ENV[key]
132
+ end
133
+
134
+ variables.sort().each do |key, value|
135
+ yield key, value
136
+ end
137
+ end
138
+
139
+ ##
140
+ # Given a key and a value, return a string representation
141
+ # of the command type requested. Available types:
142
+ #
143
+ # * :export => A bash compatible export command
144
+ # * :unset => A bash compatible export command
145
+ #
146
+ def self.command(type, key, value, options = {})
147
+ # TODO: Replace by a map type => function name
148
+ case
149
+ when type == :export
150
+ export_command(key, value, options)
151
+ when type == :unset
152
+ unset_command(key, value, options)
153
+ end
154
+ end
155
+
156
+ ##
157
+ # Returns a string representation of the command
158
+ # that needs to be used on the current platform
159
+ # to export an environment variable.
160
+ #
161
+ # @param key The key of the environment variable to export.
162
+ # It cannot be nil.
163
+ # @param value The value of the environment variable to export
164
+ #
165
+ # @return The export command, as a string
166
+ #
167
+ # Options:
168
+ # * :escape_value (true) => If true, escape the value to export.
169
+ #
170
+ def self.export_command(key, value, options = {})
171
+ value = value.to_s()
172
+ value = Shellwords.escape(value) if options[:escape_value] == nil || options[:escape_value]
173
+
174
+ # TODO: Handle platform differently
175
+ "export #{key}=#{value}"
176
+ end
177
+
178
+ ##
179
+ # Returns a string representation of the command
180
+ # that needs to be used on the current platform
181
+ # to unset an environment variable.
182
+ #
183
+ # @param key The key of the environment variable to export.
184
+ # It cannot be nil.
185
+ #
186
+ # @return The unset command, as a string
187
+ #
188
+ def self.unset_command(key, value, options = {})
189
+ # TODO: Handle platform differently
190
+ "unset #{key}"
191
+ end
192
+
193
+ # FIXME: Move this directly into bag class
194
+ def self.walk_bag(bag, parents = [], &block)
195
+ commands = []
196
+
197
+ bag.each do |key, value|
198
+ segments = parents + [key]
199
+ nested_bag = value.kind_of?(Nugrant::Bag)
200
+
201
+ walk_bag(value, segments, &block) if nested_bag
202
+ yield segments, key, value if not nested_bag
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end