chef 11.12.0.alpha.1-x86-mingw32 → 11.12.0.rc.1-x86-mingw32

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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chef/api_client/registration.rb +46 -9
  3. data/lib/chef/application.rb +1 -0
  4. data/lib/chef/application/client.rb +25 -24
  5. data/lib/chef/client.rb +34 -0
  6. data/lib/chef/config.rb +11 -0
  7. data/lib/chef/cookbook/chefignore.rb +10 -2
  8. data/lib/chef/cookbook/metadata.rb +31 -3
  9. data/lib/chef/cookbook/synchronizer.rb +2 -2
  10. data/lib/chef/cookbook/syntax_check.rb +4 -4
  11. data/lib/chef/encrypted_data_bag_item.rb +37 -1
  12. data/lib/chef/exceptions.rb +1 -0
  13. data/lib/chef/guard_interpreter/default_guard_interpreter.rb +42 -0
  14. data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +122 -0
  15. data/lib/chef/http.rb +0 -1
  16. data/lib/chef/http/decompressor.rb +7 -4
  17. data/lib/chef/http/simple.rb +5 -0
  18. data/lib/chef/http/validate_content_length.rb +28 -12
  19. data/lib/chef/knife.rb +1 -0
  20. data/lib/chef/knife/client_bulk_delete.rb +48 -9
  21. data/lib/chef/knife/client_delete.rb +4 -4
  22. data/lib/chef/knife/cookbook_bulk_delete.rb +1 -1
  23. data/lib/chef/knife/cookbook_upload.rb +17 -7
  24. data/lib/chef/knife/core/bootstrap_context.rb +1 -1
  25. data/lib/chef/knife/core/ui.rb +42 -5
  26. data/lib/chef/knife/node_run_list_add.rb +31 -2
  27. data/lib/chef/knife/ssh.rb +44 -31
  28. data/lib/chef/knife/ssl_check.rb +213 -0
  29. data/lib/chef/knife/ssl_fetch.rb +145 -0
  30. data/lib/chef/mixin/deep_merge.rb +13 -5
  31. data/lib/chef/mixin/shell_out.rb +9 -3
  32. data/lib/chef/node.rb +23 -4
  33. data/lib/chef/node/immutable_collections.rb +32 -0
  34. data/lib/chef/platform/provider_mapping.rb +21 -18
  35. data/lib/chef/platform/query_helpers.rb +10 -2
  36. data/lib/chef/policy_builder/expand_node_object.rb +3 -6
  37. data/lib/chef/provider/cron.rb +25 -3
  38. data/lib/chef/provider/mount/mount.rb +1 -1
  39. data/lib/chef/provider/package/dpkg.rb +2 -1
  40. data/lib/chef/provider/package/windows.rb +80 -0
  41. data/lib/chef/provider/package/windows/msi.rb +69 -0
  42. data/lib/chef/provider/powershell_script.rb +19 -6
  43. data/lib/chef/provider/service/solaris.rb +11 -7
  44. data/lib/chef/resource.rb +18 -5
  45. data/lib/chef/resource/conditional.rb +20 -7
  46. data/lib/chef/resource/cron.rb +18 -2
  47. data/lib/chef/resource/execute.rb +0 -2
  48. data/lib/chef/resource/powershell_script.rb +23 -1
  49. data/lib/chef/resource/script.rb +25 -0
  50. data/lib/chef/resource/subversion.rb +4 -0
  51. data/lib/chef/resource/windows_package.rb +79 -0
  52. data/lib/chef/resource/windows_script.rb +0 -5
  53. data/lib/chef/resources.rb +1 -0
  54. data/lib/chef/rest.rb +6 -1
  55. data/lib/chef/run_context.rb +22 -2
  56. data/lib/chef/run_context/cookbook_compiler.rb +12 -0
  57. data/lib/chef/util/editor.rb +92 -0
  58. data/lib/chef/util/file_edit.rb +22 -54
  59. data/lib/chef/version.rb +2 -2
  60. data/lib/chef/win32/api/installer.rb +166 -0
  61. data/lib/chef/win32/version.rb +8 -0
  62. data/spec/data/standalone_cookbook/Gemfile +1 -0
  63. data/spec/data/standalone_cookbook/chefignore +9 -0
  64. data/spec/data/standalone_cookbook/recipes/default.rb +3 -0
  65. data/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb +1 -0
  66. data/spec/functional/resource/powershell_spec.rb +262 -1
  67. data/spec/functional/win32/versions_spec.rb +3 -3
  68. data/spec/integration/knife/chefignore_spec.rb +1 -2
  69. data/spec/integration/knife/raw_spec.rb +8 -13
  70. data/spec/integration/knife/redirection_spec.rb +6 -14
  71. data/spec/integration/solo/solo_spec.rb +19 -0
  72. data/spec/support/shared/functional/windows_script.rb +1 -1
  73. data/spec/support/shared/integration/app_server_support.rb +42 -0
  74. data/spec/support/shared/integration/integration_helper.rb +1 -0
  75. data/spec/support/shared/unit/script_resource.rb +38 -0
  76. data/spec/unit/api_client/registration_spec.rb +109 -38
  77. data/spec/unit/application/client_spec.rb +48 -1
  78. data/spec/unit/cookbook/chefignore_spec.rb +10 -0
  79. data/spec/unit/cookbook/metadata_spec.rb +45 -1
  80. data/spec/unit/cookbook/syntax_check_spec.rb +28 -0
  81. data/spec/unit/cookbook_spec.rb +0 -10
  82. data/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb +56 -0
  83. data/spec/unit/http/simple_spec.rb +32 -0
  84. data/spec/unit/http/validate_content_length_spec.rb +187 -0
  85. data/spec/unit/knife/bootstrap_spec.rb +13 -4
  86. data/spec/unit/knife/client_bulk_delete_spec.rb +123 -38
  87. data/spec/unit/knife/client_delete_spec.rb +4 -4
  88. data/spec/unit/knife/cookbook_upload_spec.rb +181 -88
  89. data/spec/unit/knife/core/bootstrap_context_spec.rb +11 -1
  90. data/spec/unit/knife/core/ui_spec.rb +109 -38
  91. data/spec/unit/knife/node_run_list_add_spec.rb +24 -1
  92. data/spec/unit/knife/ssh_spec.rb +17 -6
  93. data/spec/unit/knife/ssl_check_spec.rb +187 -0
  94. data/spec/unit/knife/ssl_fetch_spec.rb +151 -0
  95. data/spec/unit/mixin/deep_merge_spec.rb +17 -0
  96. data/spec/unit/node/immutable_collections_spec.rb +55 -0
  97. data/spec/unit/node_spec.rb +9 -0
  98. data/spec/unit/platform/query_helpers_spec.rb +32 -0
  99. data/spec/unit/platform_spec.rb +193 -175
  100. data/spec/unit/policy_builder/expand_node_object_spec.rb +1 -1
  101. data/spec/unit/provider/cron_spec.rb +175 -1
  102. data/spec/unit/provider/mount/mount_spec.rb +33 -3
  103. data/spec/unit/provider/package/dpkg_spec.rb +4 -0
  104. data/spec/unit/provider/package/windows/msi_spec.rb +60 -0
  105. data/spec/unit/provider/package/windows_spec.rb +80 -0
  106. data/spec/unit/provider/service/macosx_spec.rb +3 -3
  107. data/spec/unit/provider/service/solaris_smf_service_spec.rb +35 -10
  108. data/spec/unit/pure_application_spec.rb +32 -0
  109. data/spec/unit/recipe_spec.rb +4 -0
  110. data/spec/unit/resource/conditional_spec.rb +13 -12
  111. data/spec/unit/resource/cron_spec.rb +7 -2
  112. data/spec/unit/resource/powershell_spec.rb +85 -2
  113. data/spec/unit/resource/subversion_spec.rb +5 -0
  114. data/spec/unit/resource/windows_package_spec.rb +74 -0
  115. data/spec/unit/resource_spec.rb +23 -1
  116. data/spec/unit/rest_spec.rb +15 -0
  117. data/spec/unit/run_context/cookbook_compiler_spec.rb +12 -0
  118. data/spec/unit/run_context_spec.rb +7 -0
  119. data/spec/unit/util/editor_spec.rb +152 -0
  120. data/spec/unit/util/file_edit_spec.rb +37 -1
  121. metadata +41 -30
@@ -43,6 +43,7 @@ class Chef
43
43
  @path = nil
44
44
  @shell = nil
45
45
  @home = nil
46
+ @time = nil
46
47
  @environment = {}
47
48
  end
48
49
 
@@ -121,13 +122,28 @@ class Chef
121
122
  converted_arg = arg
122
123
  end
123
124
  begin
124
- if integerize(arg) > 7 then raise RangeError end
125
+ error_message = "You provided '#{arg}' as a weekday, acceptable values are "
126
+ error_message << Provider::Cron::WEEKDAY_SYMBOLS.map {|sym| ":#{sym.to_s}"}.join(', ')
127
+ error_message << " and a string in crontab format"
128
+ if (arg.is_a?(Symbol) && !Provider::Cron::WEEKDAY_SYMBOLS.include?(arg)) ||
129
+ (!arg.is_a?(Symbol) && integerize(arg) > 7) ||
130
+ (!arg.is_a?(Symbol) && integerize(arg) < 0)
131
+ raise RangeError, error_message
132
+ end
125
133
  rescue ArgumentError
126
134
  end
127
135
  set_or_return(
128
136
  :weekday,
129
137
  converted_arg,
130
- :kind_of => String
138
+ :kind_of => [String, Symbol]
139
+ )
140
+ end
141
+
142
+ def time(arg=nil)
143
+ set_or_return(
144
+ :time,
145
+ arg,
146
+ :equal_to => Chef::Provider::Cron::SPECIAL_TIME_VALUES
131
147
  )
132
148
  end
133
149
 
@@ -125,8 +125,6 @@ class Chef
125
125
  )
126
126
  end
127
127
 
128
-
129
-
130
128
  end
131
129
  end
132
130
  end
@@ -15,17 +15,39 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
-
19
18
  require 'chef/resource/windows_script'
20
19
 
21
20
  class Chef
22
21
  class Resource
23
22
  class PowershellScript < Chef::Resource::WindowsScript
24
23
 
24
+ set_guard_inherited_attributes(:architecture)
25
+
25
26
  def initialize(name, run_context=nil)
26
27
  super(name, run_context, :powershell_script, "powershell.exe")
28
+ @convert_boolean_return = false
29
+ end
30
+
31
+ def convert_boolean_return(arg=nil)
32
+ set_or_return(
33
+ :convert_boolean_return,
34
+ arg,
35
+ :kind_of => [ FalseClass, TrueClass ]
36
+ )
27
37
  end
28
38
 
39
+ protected
40
+
41
+ # Allow callers evaluating guards to request default
42
+ # attribute values. This is needed to allow
43
+ # convert_boolean_return to be true in guard context by default,
44
+ # and false by default otherwise. When this mode becomes the
45
+ # default for this resource, this method can be removed since
46
+ # guard context and recipe resource context will have the
47
+ # same behavior.
48
+ def self.get_default_attributes(opts)
49
+ {:convert_boolean_return => true}
50
+ end
29
51
  end
30
52
  end
31
53
  end
@@ -58,6 +58,31 @@ class Chef
58
58
  )
59
59
  end
60
60
 
61
+ def self.set_guard_inherited_attributes(*inherited_attributes)
62
+ @class_inherited_attributes = inherited_attributes
63
+ end
64
+
65
+ def self.guard_inherited_attributes(*inherited_attributes)
66
+ # Similar to patterns elsewhere, return attributes from this
67
+ # class and superclasses as a form of inheritance
68
+ ancestor_attributes = []
69
+
70
+ if superclass.respond_to?(:guard_inherited_attributes)
71
+ ancestor_attributes = superclass.guard_inherited_attributes
72
+ end
73
+
74
+ ancestor_attributes.concat(@class_inherited_attributes ? @class_inherited_attributes : []).uniq
75
+ end
76
+
77
+ set_guard_inherited_attributes(
78
+ :cwd,
79
+ :environment,
80
+ :group,
81
+ :path,
82
+ :user,
83
+ :umask
84
+ )
85
+
61
86
  end
62
87
  end
63
88
  end
@@ -32,6 +32,10 @@ class Chef
32
32
  allowed_actions << :force_export
33
33
  end
34
34
 
35
+ # Override exception to strip password if any, so it won't appear in logs and different Chef notifications
36
+ def custom_exception_message(e)
37
+ "#{self} (#{defined_at}) had an error: #{e.class.name}: #{svn_password ? e.message.gsub(svn_password, "[hidden_password]") : e.message}"
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -0,0 +1,79 @@
1
+ #
2
+ # Author:: Bryan McLellan <btm@loftninjas.org>
3
+ # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/resource/package'
20
+ require 'chef/provider/package/windows'
21
+ require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
22
+
23
+ class Chef
24
+ class Resource
25
+ class WindowsPackage < Chef::Resource::Package
26
+
27
+ provides :package, :on_platforms => ["windows"]
28
+
29
+ def initialize(name, run_context=nil)
30
+ super
31
+ @allowed_actions = [ :install, :remove ]
32
+ @provider = Chef::Provider::Package::Windows
33
+ @resource_name = :windows_package
34
+ @source ||= source(@package_name)
35
+
36
+ # Unique to this resource
37
+ @installer_type = nil
38
+ @timeout = 600
39
+ # In the past we accepted return code 127 for an unknown reason and 42 because of a bug
40
+ @returns = [ 0 ]
41
+ end
42
+
43
+ def installer_type(arg=nil)
44
+ set_or_return(
45
+ :installer_type,
46
+ arg,
47
+ :kind_of => [ String ]
48
+ )
49
+ end
50
+
51
+ def timeout(arg=nil)
52
+ set_or_return(
53
+ :timeout,
54
+ arg,
55
+ :kind_of => [ String, Integer ]
56
+ )
57
+ end
58
+
59
+ def returns(arg=nil)
60
+ set_or_return(
61
+ :returns,
62
+ arg,
63
+ :kind_of => [ String, Integer, Array ]
64
+ )
65
+ end
66
+
67
+ def source(arg=nil)
68
+ if arg == nil && self.instance_variable_defined?(:@source) == true
69
+ @source
70
+ else
71
+ raise ArgumentError, "Bad type for WindowsPackage resource, use a String" unless arg.is_a?(String)
72
+ Chef::Log.debug("#{package_name}: sanitizing source path '#{arg}'")
73
+ @source = ::File.absolute_path(arg).gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
@@ -52,11 +52,6 @@ class Chef
52
52
  "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'"
53
53
  end
54
54
  end
55
-
56
- def node
57
- run_context && run_context.node
58
- end
59
-
60
55
  end
61
56
  end
62
57
  end
@@ -69,6 +69,7 @@ require 'chef/resource/template'
69
69
  require 'chef/resource/timestamped_deploy'
70
70
  require 'chef/resource/user'
71
71
  require 'chef/resource/whyrun_safe_ruby_block'
72
+ require 'chef/resource/windows_package'
72
73
  require 'chef/resource/yum_package'
73
74
  require 'chef/resource/lwrp_base'
74
75
  require 'chef/resource/bff_package'
@@ -57,6 +57,7 @@ class Chef
57
57
  # http://localhost:4000, a call to +get_rest+ with 'nodes' will make an
58
58
  # HTTP GET request to http://localhost:4000/nodes
59
59
  def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key], options={})
60
+ options = options.dup
60
61
  options[:client_name] = client_name
61
62
  options[:signing_key_filename] = signing_key_filename
62
63
  super(url, options)
@@ -65,7 +66,6 @@ class Chef
65
66
  @authenticator = Authenticator.new(options)
66
67
  @request_id = RemoteRequestID.new(options)
67
68
 
68
- @middlewares << ValidateContentLength.new(options)
69
69
  @middlewares << JSONInput.new(options)
70
70
  @middlewares << JSONToModelOutput.new(options)
71
71
  @middlewares << CookieManager.new(options)
@@ -73,6 +73,11 @@ class Chef
73
73
  @middlewares << @authenticator
74
74
  @middlewares << @request_id
75
75
 
76
+ # ValidateContentLength should come after Decompressor
77
+ # because the order of middlewares is reversed when handling
78
+ # responses.
79
+ @middlewares << ValidateContentLength.new(options)
80
+
76
81
  end
77
82
 
78
83
  def signing_key_filename
@@ -77,13 +77,15 @@ class Chef
77
77
  @events = events
78
78
 
79
79
  @node.run_context = self
80
+
81
+ @cookbook_compiler = nil
80
82
  end
81
83
 
82
84
  # Triggers the compile phase of the chef run. Implemented by
83
85
  # Chef::RunContext::CookbookCompiler
84
86
  def load(run_list_expansion)
85
- compiler = CookbookCompiler.new(self, run_list_expansion, events)
86
- compiler.compile
87
+ @cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
88
+ @cookbook_compiler.compile
87
89
  end
88
90
 
89
91
  # Adds an immediate notification to the
@@ -141,6 +143,18 @@ class Chef
141
143
  Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
142
144
 
143
145
  cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name)
146
+
147
+ if unreachable_cookbook?(cookbook_name) # CHEF-4367
148
+ Chef::Log.warn(<<-ERROR_MESSAGE)
149
+ MissingCookbookDependency:
150
+ Recipe `#{recipe_name}` is not in the run_list, and cookbook '#{cookbook_name}'
151
+ is not a dependency of any cookbook in the run_list. To load this recipe,
152
+ first add a dependency on cookbook '#{cookbook_name}' in the cookbook you're
153
+ including it from in that cookbook's metadata.
154
+ ERROR_MESSAGE
155
+ end
156
+
157
+
144
158
  if loaded_fully_qualified_recipe?(cookbook_name, recipe_short_name)
145
159
  Chef::Log.debug("I am not loading #{recipe_name}, because I have already seen it.")
146
160
  false
@@ -228,6 +242,12 @@ class Chef
228
242
  cookbook.has_cookbook_file_for_node?(node, cb_file_name)
229
243
  end
230
244
 
245
+ # Delegates to CookbookCompiler#unreachable_cookbook?
246
+ # Used to raise an error when attempting to load a recipe belonging to a
247
+ # cookbook that is not in the dependency graph. See also: CHEF-4367
248
+ def unreachable_cookbook?(cookbook_name)
249
+ @cookbook_compiler.unreachable_cookbook?(cookbook_name)
250
+ end
231
251
 
232
252
  private
233
253
 
@@ -16,6 +16,7 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
+ require 'set'
19
20
  require 'chef/log'
20
21
  require 'chef/recipe'
21
22
  require 'chef/resource/lwrp_base'
@@ -149,6 +150,17 @@ class Chef
149
150
  @events.recipe_load_complete
150
151
  end
151
152
 
153
+ # Whether or not a cookbook is reachable from the set of cookbook given
154
+ # by the run_list plus those cookbooks' dependencies.
155
+ def unreachable_cookbook?(cookbook_name)
156
+ !reachable_cookbooks.include?(cookbook_name)
157
+ end
158
+
159
+ # All cookbooks in the dependency graph, returned as a Set.
160
+ def reachable_cookbooks
161
+ @reachable_cookbooks ||= Set.new(cookbook_order)
162
+ end
163
+
152
164
  private
153
165
 
154
166
  def load_attributes_from_cookbook(cookbook_name)
@@ -0,0 +1,92 @@
1
+ #
2
+ # Author:: Chris Bandy (<bandy.chris@gmail.com>)
3
+ # Copyright:: Copyright (c) 2014 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ class Chef
20
+ class Util
21
+ class Editor
22
+ attr_reader :lines
23
+
24
+ def initialize(lines)
25
+ @lines = lines.to_a.clone
26
+ end
27
+
28
+ def append_line_after(search, line_to_append)
29
+ lines = []
30
+
31
+ @lines.each do |line|
32
+ lines << line
33
+ lines << line_to_append if line.match(search)
34
+ end
35
+
36
+ (lines.length - @lines.length).tap { @lines = lines }
37
+ end
38
+
39
+ def append_line_if_missing(search, line_to_append)
40
+ count = 0
41
+
42
+ unless @lines.find { |line| line.match(search) }
43
+ count = 1
44
+ @lines << line_to_append
45
+ end
46
+
47
+ count
48
+ end
49
+
50
+ def remove_lines(search)
51
+ count = 0
52
+
53
+ @lines.delete_if do |line|
54
+ count += 1 if line.match(search)
55
+ end
56
+
57
+ count
58
+ end
59
+
60
+ def replace(search, replace)
61
+ count = 0
62
+
63
+ @lines.map! do |line|
64
+ if line.match(search)
65
+ count += 1
66
+ line.gsub!(search, replace)
67
+ else
68
+ line
69
+ end
70
+ end
71
+
72
+ count
73
+ end
74
+
75
+ def replace_lines(search, replace)
76
+ count = 0
77
+
78
+ @lines.map! do |line|
79
+ if line.match(search)
80
+ count += 1
81
+ replace
82
+ else
83
+ line
84
+ end
85
+ end
86
+
87
+ count
88
+ end
89
+ end
90
+ end
91
+ end
92
+
@@ -15,8 +15,8 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require 'chef/util/editor'
18
19
  require 'fileutils'
19
- require 'tempfile'
20
20
 
21
21
  class Chef
22
22
  class Util
@@ -24,108 +24,76 @@ class Chef
24
24
 
25
25
  private
26
26
 
27
- attr_accessor :original_pathname, :contents, :file_edited
27
+ attr_reader :editor, :original_pathname
28
28
 
29
29
  public
30
30
 
31
31
  def initialize(filepath)
32
+ raise ArgumentError, "File '#{filepath}' does not exist" unless File.exist?(filepath)
33
+ @editor = Editor.new(File.open(filepath, &:readlines))
32
34
  @original_pathname = filepath
33
35
  @file_edited = false
36
+ end
34
37
 
35
- raise ArgumentError, "File doesn't exist" unless File.exist? @original_pathname
36
- @contents = File.open(@original_pathname) { |f| f.readlines }
38
+ # return if file has been edited
39
+ def file_edited?
40
+ @file_edited
37
41
  end
38
42
 
39
43
  #search the file line by line and match each line with the given regex
40
44
  #if matched, replace the whole line with newline.
41
45
  def search_file_replace_line(regex, newline)
42
- search_match(regex, newline, 'r', 1)
46
+ @changes = (editor.replace_lines(regex, newline) > 0) || @changes
43
47
  end
44
48
 
45
49
  #search the file line by line and match each line with the given regex
46
50
  #if matched, replace the match (all occurances) with the replace parameter
47
51
  def search_file_replace(regex, replace)
48
- search_match(regex, replace, 'r', 2)
52
+ @changes = (editor.replace(regex, replace) > 0) || @changes
49
53
  end
50
54
 
51
55
  #search the file line by line and match each line with the given regex
52
56
  #if matched, delete the line
53
57
  def search_file_delete_line(regex)
54
- search_match(regex, " ", 'd', 1)
58
+ @changes = (editor.remove_lines(regex) > 0) || @changes
55
59
  end
56
60
 
57
61
  #search the file line by line and match each line with the given regex
58
62
  #if matched, delete the match (all occurances) from the line
59
63
  def search_file_delete(regex)
60
- search_match(regex, " ", 'd', 2)
64
+ search_file_replace(regex, '')
61
65
  end
62
66
 
63
67
  #search the file line by line and match each line with the given regex
64
68
  #if matched, insert newline after each matching line
65
69
  def insert_line_after_match(regex, newline)
66
- search_match(regex, newline, 'i', 1)
70
+ @changes = (editor.append_line_after(regex, newline) > 0) || @changes
67
71
  end
68
72
 
69
73
  #search the file line by line and match each line with the given regex
70
74
  #if not matched, insert newline at the end of the file
71
75
  def insert_line_if_no_match(regex, newline)
72
- search_match(regex, newline, 'i', 2)
76
+ @changes = (editor.append_line_if_missing(regex, newline) > 0) || @changes
77
+ end
78
+
79
+ def unwritten_changes?
80
+ !!@changes
73
81
  end
74
82
 
75
83
  #Make a copy of old_file and write new file out (only if file changed)
76
84
  def write_file
77
-
78
- # file_edited is false when there was no match in the whole file and thus no contents have changed.
79
- if file_edited
85
+ if @changes
80
86
  backup_pathname = original_pathname + ".old"
81
87
  FileUtils.cp(original_pathname, backup_pathname, :preserve => true)
82
88
  File.open(original_pathname, "w") do |newfile|
83
- contents.each do |line|
89
+ editor.lines.each do |line|
84
90
  newfile.puts(line)
85
91
  end
86
92
  newfile.flush
87
93
  end
94
+ @file_edited = true
88
95
  end
89
- self.file_edited = false
90
- end
91
-
92
- private
93
-
94
- #helper method to do the match, replace, delete, and insert operations
95
- #command is the switch of delete, replace, and insert ('d', 'r', 'i')
96
- #method is to control operation on whole line or only the match (1 for line, 2 for match)
97
- def search_match(regex, replace, command, method)
98
-
99
- #convert regex to a Regexp object (if not already is one) and store it in exp.
100
- exp = Regexp.new(regex)
101
-
102
- #loop through contents and do the appropriate operation depending on 'command' and 'method'
103
- new_contents = []
104
-
105
- contents.each do |line|
106
- if line.match(exp)
107
- self.file_edited = true
108
- case
109
- when command == 'r'
110
- new_contents << ((method == 1) ? replace : line.gsub!(exp, replace))
111
- when command == 'd'
112
- if method == 2
113
- new_contents << line.gsub!(exp, "")
114
- end
115
- when command == 'i'
116
- new_contents << line
117
- new_contents << replace unless method == 2
118
- end
119
- else
120
- new_contents << line
121
- end
122
- end
123
- if command == 'i' && method == 2 && ! file_edited
124
- new_contents << replace
125
- self.file_edited = true
126
- end
127
-
128
- self.contents = new_contents
96
+ @changes = false
129
97
  end
130
98
  end
131
99
  end