ruby-augeas 0.4.1 → 0.6.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.
@@ -0,0 +1,326 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # augeas.rb: Ruby wrapper for augeas
4
+ #
5
+ # Copyright (C) 2008 Red Hat Inc.
6
+ # Copyright (C) 2011 SUSE LINUX Products GmbH, Nuernberg, Germany.
7
+ #
8
+ # This library is free software; you can redistribute it and/or
9
+ # modify it under the terms of the GNU Lesser General Public
10
+ # License as published by the Free Software Foundation; either
11
+ # version 2.1 of the License, or (at your option) any later version.
12
+ #
13
+ # This library is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ # Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public
19
+ # License along with this library; if not, write to the Free Software
20
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
+ #
22
+ # Authors: Ionuț Arțăriși <iartarisi@suse.cz>
23
+ # Bryan Kearney <bkearney@redhat.com>
24
+ # Artem Sheremet <dot.doom@gmail.com>
25
+ ##
26
+
27
+ # Do not require this file explicitly; instead require "augeas"
28
+
29
+ # Wrapper class for the augeas[http://augeas.net] library.
30
+ class Augeas::Facade
31
+ private_class_method :new
32
+
33
+ def self.create(opts={}, &block)
34
+ # aug_flags is a bitmask in the underlying library, we add all the
35
+ # values of the flags which were set to true to the default value
36
+ # Augeas::NONE (which is 0)
37
+ aug_flags = defined?(Augeas::NO_ERR_CLOSE) ? Augeas::NO_ERR_CLOSE : Augeas::NONE
38
+
39
+ flags = {
40
+ :type_check => Augeas::TYPE_CHECK,
41
+ :no_stdinc => Augeas::NO_STDINC,
42
+ :no_load => Augeas::NO_LOAD,
43
+ :no_modl_autoload => Augeas::NO_MODL_AUTOLOAD,
44
+ :enable_span => Augeas::ENABLE_SPAN
45
+ }
46
+ save_modes = {
47
+ :backup => Augeas::SAVE_BACKUP,
48
+ :newfile => Augeas::SAVE_NEWFILE,
49
+ :noop => Augeas::SAVE_NOOP
50
+ }
51
+ opts.each_key do |key|
52
+ if flags.key? key
53
+ aug_flags |= flags[key]
54
+ elsif key == :save_mode
55
+ if save_modes[opts[:save_mode]]
56
+ aug_flags |= save_modes[opts[:save_mode]]
57
+ else
58
+ raise ArgumentError, "Invalid save mode #{opts[:save_mode]}."
59
+ end
60
+ elsif key != :root && key != :loadpath
61
+ raise ArgumentError, "Unknown argument #{key}."
62
+ end
63
+ end
64
+
65
+ aug = Augeas::Facade::open3(opts[:root], opts[:loadpath], aug_flags)
66
+
67
+ begin
68
+ aug.send(:raise_last_error)
69
+ rescue
70
+ aug.close
71
+ raise
72
+ end
73
+
74
+ if block_given?
75
+ begin
76
+ yield aug
77
+ ensure
78
+ aug.close
79
+ end
80
+ else
81
+ return aug
82
+ end
83
+ end
84
+
85
+ # Get the value associated with +path+.
86
+ def get(path)
87
+ run_command :augeas_get, path
88
+ end
89
+
90
+ # Return true if there is an entry for this path, false otherwise
91
+ def exists(path)
92
+ run_command :augeas_exists, path
93
+ end
94
+
95
+ # Set one or multiple elements to path.
96
+ # Multiple elements are mainly sensible with a path like
97
+ # .../array[last()+1], since this will append all elements.
98
+ def set(path, *values)
99
+ values.flatten.each { |v| run_command :augeas_set, path, v }
100
+ end
101
+
102
+ # Set multiple nodes in one operation. Find or create a node matching SUB
103
+ # by interpreting SUB as a path expression relative to each node matching
104
+ # BASE. If SUB is '.', the nodes matching BASE will be modified.
105
+
106
+ # +base+ the base node
107
+ # +sub+ the subtree relative to the base
108
+ # +value+ the value for the nodes
109
+ def setm(base, sub, value)
110
+ run_command :augeas_setm, base, sub, value
111
+ end
112
+
113
+ # Remove all nodes matching path expression +path+ and all their
114
+ # children.
115
+ # Raises an <tt>Augeas::InvalidPathError</tt> when the +path+ is invalid.
116
+ def rm(path)
117
+ run_command :augeas_rm, path
118
+ end
119
+
120
+ # Return an Array of all the paths that match the path expression +path+
121
+ #
122
+ # Returns an empty Array if no paths were found.
123
+ # Raises an <tt>Augeas::InvalidPathError</tt> when the +path+ is invalid.
124
+ def match(path)
125
+ run_command :augeas_match, path
126
+ end
127
+
128
+ # Create the +path+ with empty value if it doesn't exist
129
+ def touch(path)
130
+ set(path, nil) if match(path).empty?
131
+ end
132
+
133
+ # Evaluate +expr+ and set the variable +name+ to the resulting
134
+ # nodeset. The variable can be used in path expressions as $name.
135
+ # Note that +expr+ is evaluated when the variable is defined, not when
136
+ # it is used.
137
+ def defvar(name, expr)
138
+ run_command :augeas_defvar, name, expr
139
+ end
140
+
141
+ # Define the variable +name+ to the result of evaluating +expr+, which
142
+ # must be a nodeset. If no node matching +expr+ exists yet, one is
143
+ # created and +name+ will refer to it. When a node is created and
144
+ # +value+ is given, the new node's value is set to +value+.
145
+ def defnode(name, expr, value=nil)
146
+ run_command :augeas_defnode, name, expr, value
147
+ end
148
+
149
+ # Clear the +path+, i.e. make its value +nil+
150
+ def clear(path)
151
+ augeas_set(path, nil)
152
+ end
153
+
154
+ # Add a transform under <tt>/augeas/load</tt>
155
+ #
156
+ # The HASH can contain the following entries
157
+ # * <tt>:lens</tt> - the name of the lens to use
158
+ # * <tt>:name</tt> - a unique name; use the module name of the LENS
159
+ # when omitted
160
+ # * <tt>:incl</tt> - a list of glob patterns for the files to transform
161
+ # * <tt>:excl</tt> - a list of the glob patterns to remove from the
162
+ # list that matches <tt>:incl</tt>
163
+ def transform(hash)
164
+ lens = hash[:lens]
165
+ name = hash[:name]
166
+ incl = hash[:incl]
167
+ excl = hash[:excl]
168
+ raise ArgumentError, "No lens specified" unless lens
169
+ raise ArgumentError, "No files to include" unless incl
170
+ name = lens.split(".")[0].sub("@", "") unless name
171
+
172
+ xfm = "/augeas/load/#{name}/"
173
+ set(xfm + "lens", lens)
174
+ set(xfm + "incl[last()+1]", incl)
175
+ set(xfm + "excl[last()+1]", excl) if excl
176
+ end
177
+
178
+ # Clear all transforms under <tt>/augeas/load</tt>. If +load+
179
+ # is called right after this, there will be no files
180
+ # under +/files+
181
+ def clear_transforms
182
+ rm("/augeas/load/*")
183
+ end
184
+
185
+ # Write all pending changes to disk.
186
+ # Raises <tt>Augeas::CommandExecutionError</tt> if saving fails.
187
+ def save
188
+ begin
189
+ run_command :augeas_save
190
+ rescue Augeas::CommandExecutionError => e
191
+ raise e, 'Saving failed. Search the augeas tree in /augeas//error ' <<
192
+ 'for the actual errors.'
193
+ end
194
+
195
+ nil
196
+ end
197
+
198
+ def clearm(path, sub)
199
+ setm(path, sub, nil)
200
+ end
201
+
202
+ # Load files according to the transforms in /augeas/load or those
203
+ # defined via <tt>transform</tt>. A transform Foo is represented
204
+ # with a subtree /augeas/load/Foo. Underneath /augeas/load/Foo, one
205
+ # node labeled 'lens' must exist, whose value is the fully
206
+ # qualified name of a lens, for example 'Foo.lns', and multiple
207
+ # nodes 'incl' and 'excl' whose values are globs that determine
208
+ # which files are transformed by that lens. It is an error if one
209
+ # file can be processed by multiple transforms.
210
+ def load
211
+ begin
212
+ run_command :augeas_load
213
+ rescue Augeas::CommandExecutionError => e
214
+ raise e, "Loading failed. Search the augeas tree in /augeas//error"+
215
+ "for the actual errors."
216
+ end
217
+
218
+ nil
219
+ end
220
+
221
+ # Move node +src+ to +dst+. +src+ must match exactly one node in
222
+ # the tree. +dst+ must either match exactly one node in the tree,
223
+ # or may not exist yet. If +dst+ exists already, it and all its
224
+ # descendants are deleted. If +dst+ does not exist yet, it and all
225
+ # its missing ancestors are created.
226
+ #
227
+ # Raises <tt>Augeas::NoMatchError</tt> if the +src+ node does not exist
228
+ # Raises <tt>Augeas::MultipleMatchesError</tt> if there were
229
+ # multiple matches in +src+
230
+ # Raises <tt>Augeas::DescendantError</tt> if the +dst+ node is a
231
+ # descendant of the +src+ node.
232
+ def mv(src, dst)
233
+ run_command :augeas_mv, src, dst
234
+ end
235
+
236
+ # Get the filename, label and value position in the text of this node
237
+ #
238
+ # Raises <tt>Augeas::NoMatchError</tt> if the node could not be found
239
+ # Raises <tt>Augeas::NoSpanInfo</tt> if the node associated with
240
+ # +path+ doesn't belong to a file or doesn't exist
241
+ def span(path)
242
+ run_command :augeas_span, path
243
+ end
244
+
245
+ # Run one or more newline-separated commands specified by +text+,
246
+ # returns an array of [successful_commands_number, output] or
247
+ # [-2, output] in case 'quit' command has been encountered.
248
+ # Raises <tt>Augeas::CommandExecutionError</tt> if gets an invalid command
249
+ def srun(text)
250
+ run_command(:augeas_srun, text)
251
+ end
252
+
253
+ # Lookup the label associated with +path+
254
+ # Raises <tt>Augeas::NoMatchError</tt> if the +path+ node does not exist
255
+ def label(path)
256
+ run_command :augeas_label, path
257
+ end
258
+
259
+ # Rename the label of all nodes matching +path+ to +label+
260
+ # Raises <tt>Augeas::NoMatchError</tt> if the +path+ node does not exist
261
+ # Raises <tt>Augeas::InvalidLabelError</tt> if +label+ is invalid
262
+ def rename(path, label)
263
+ run_command :augeas_rename, path, label
264
+ end
265
+
266
+ # Use the value of node +node+ as a string and transform it into a tree
267
+ # using the lens +lens+ and store it in the tree at +path+,
268
+ # which will be overwritten. +path+ and +node+ are path expressions.
269
+ def text_store(lens, node, path)
270
+ run_command :augeas_text_store, lens, node, path
271
+ end
272
+
273
+ # Transform the tree at +path+ into a string lens +lens+ and store it
274
+ # in the node +node_out+, assuming the tree was initially generated using
275
+ # the value of node +node_in+. +path+, +node_in+ and +node_out+ are path expressions.
276
+ def text_retrieve(lens, node_in, path, node_out)
277
+ run_command :augeas_text_retrieve, lens, node_in, path, node_out
278
+ end
279
+
280
+ # Make +label+ a sibling of +path+ by inserting it directly before
281
+ # or after +path+.
282
+ # The boolean +before+ determines if +label+ is inserted before or
283
+ # after +path+.
284
+ def insert(path, label, before)
285
+ run_command :augeas_insert, path, label, before
286
+ end
287
+
288
+ # Set path expression context to +path+ (in /augeas/context)
289
+ def context=(path)
290
+ set('/augeas/context', path)
291
+ end
292
+
293
+ # Get path expression context (from /augeas/context)
294
+ def context
295
+ get('/augeas/context')
296
+ end
297
+
298
+ private
299
+
300
+ # Run a command and raise any errors that happen due to execution.
301
+ #
302
+ # +cmd+ name of the Augeas command to run
303
+ # +params+ parameters with which +cmd+ will be called
304
+ #
305
+ # Returns whatever the original +cmd+ returns
306
+ def run_command(cmd, *params)
307
+ result = self.send cmd, *params
308
+
309
+ raise_last_error
310
+
311
+ if result.kind_of? Integer and result < 0
312
+ # we raise CommandExecutionError here, because this is the error that
313
+ # augtool raises in this case as well
314
+ raise Augeas::CommandExecutionError, "Command failed. Return code was #{result}."
315
+ end
316
+
317
+ return result
318
+ end
319
+
320
+ def raise_last_error
321
+ error_cache = error
322
+ unless error_cache[:code].zero?
323
+ raise Augeas::ERRORS_HASH[error_cache[:code]], "#{error_cache[:message]} #{error_cache[:details]}"
324
+ end
325
+ end
326
+ end
data/lib/augeas.rb CHANGED
@@ -21,12 +21,83 @@
21
21
  ##
22
22
 
23
23
  require "_augeas"
24
+ require "augeas/facade"
24
25
 
25
26
  # Wrapper class for the augeas[http://augeas.net] library.
26
27
  class Augeas
27
28
  private_class_method :new
28
29
 
29
30
  class Error < RuntimeError; end
31
+ class NoMemoryError < Error; end
32
+ class InternalError < Error; end
33
+ class InvalidPathError < Error; end
34
+ class NoMatchError < Error; end
35
+ class MultipleMatchesError < Error; end
36
+ class LensSyntaxError < Error; end
37
+ class LensNotFoundError < Error; end
38
+ class MultipleTransformsError < Error; end
39
+ class NoSpanInfoError < Error; end
40
+ class DescendantError < Error; end
41
+ class CommandExecutionError < Error; end
42
+ class InvalidArgumentError < Error; end
43
+ class InvalidLabelError < Error; end
44
+ ERRORS_HASH = Hash[{
45
+ # the cryptic error names come from the C library, we just make
46
+ # them more ruby and more human
47
+ :ENOMEM => NoMemoryError,
48
+ :EINTERNAL => InternalError,
49
+ :EPATHX => InvalidPathError,
50
+ :ENOMATCH => NoMatchError,
51
+ :EMMATCH => MultipleMatchesError,
52
+ :ESYNTAX => LensSyntaxError,
53
+ :ENOLENS => LensNotFoundError,
54
+ :EMXFM => MultipleTransformsError,
55
+ :ENOSPAN => NoSpanInfoError,
56
+ :EMVDESC => DescendantError,
57
+ :ECMDRUN => CommandExecutionError,
58
+ :EBADARG => InvalidArgumentError,
59
+ :ELABEL => InvalidLabelError,
60
+ }.map { |k, v| [(const_get(k) rescue nil), v] }].freeze
61
+
62
+ # Create a new Augeas instance and return it.
63
+ #
64
+ # Use +:root+ as the filesystem root. If +:root+ is +nil+, use the value
65
+ # of the environment variable +AUGEAS_ROOT+. If that doesn't exist
66
+ # either, use "/".
67
+ #
68
+ # +:loadpath+ is a colon-spearated list of directories that modules
69
+ # should be searched in. This is in addition to the standard load path
70
+ # and the directories in +AUGEAS_LENS_LIB+
71
+ #
72
+ # The following flags can be specified in a hash. They all default to
73
+ # false and can be enabled by setting them to true
74
+ #
75
+ # :type_check - typecheck lenses (since it can be very expensive it is
76
+ # not done by default)
77
+ #
78
+ # :no_stdinc - do not use the builtin load path for modules
79
+ #
80
+ # :no_load - do not load the tree during the initialization phase
81
+ #
82
+ # :no_modl_autoload - do not load the tree during the initialization phase
83
+ #
84
+ # :enable_span - track the span in the input nodes
85
+ #
86
+ # :save_mode can be one of :backup, :newfile, :noop as explained below.
87
+ #
88
+ # :noop - make save a no-op process, just record what would have changed
89
+ #
90
+ # :backup - keep the original file with an .augsave extension
91
+ #
92
+ # :newfile - save changes into a file with an .augnew extension and
93
+ # do not overwrite the original file.
94
+ #
95
+ # When a block is given, the Augeas instance is passed as the only
96
+ # argument into the block and closed when the block exits.
97
+ # With no block, the Augeas instance is returned.
98
+ def self.create(opts={}, &block)
99
+ Augeas::Facade::create(opts, &block)
100
+ end
30
101
 
31
102
  # Create a new Augeas instance and return it.
32
103
  #
@@ -77,6 +148,18 @@ class Augeas
77
148
  set_internal(path, nil)
78
149
  end
79
150
 
151
+ # Clear multiple nodes values in one operation. Find or create a node matching +sub+
152
+ # by interpreting +sub+ as a path expression relative to each node matching
153
+ # +base+. If +sub+ is '.', the nodes matching +base+ will be modified.
154
+ def clearm(base, sub)
155
+ setm(base, sub, nil)
156
+ end
157
+
158
+ # Create the +path+ with empty value if it doesn't exist
159
+ def touch(path)
160
+ set_internal(path, nil) if match(path).empty?
161
+ end
162
+
80
163
  # Clear all transforms under <tt>/augeas/load</tt>. If +load+
81
164
  # is called right after this, there will be no files
82
165
  # under +/files+
@@ -98,6 +181,7 @@ class Augeas
98
181
  excl = hash[:excl]
99
182
  raise ArgumentError, "No lens specified" unless lens
100
183
  raise ArgumentError, "No files to include" unless incl
184
+ lens = "#{lens}.lns" unless lens.include? '.'
101
185
  name = lens.split(".")[0].sub("@", "") unless name
102
186
 
103
187
  xfm = "/augeas/load/#{name}/"
@@ -116,4 +200,14 @@ class Augeas
116
200
  raise Augeas::Error unless load
117
201
  end
118
202
 
203
+ # Set path expression context to +path+ (in /augeas/context)
204
+ def context=(path)
205
+ set_internal('/augeas/context', path)
206
+ end
207
+
208
+ # Get path expression context (from /augeas/context)
209
+ def context
210
+ get('/augeas/context')
211
+ end
212
+
119
213
  end
data/tests/tc_augeas.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'test/unit'
2
2
 
3
- TOPDIR = File::expand_path(File::join(File::dirname(__FILE__), ".."))
3
+ unless defined?(TOPDIR)
4
+ TOPDIR = File::expand_path(File::join(File::dirname(__FILE__), ".."))
5
+ end
4
6
 
5
7
  $:.unshift(File::join(TOPDIR, "lib"))
6
8
  $:.unshift(File::join(TOPDIR, "ext", "augeas"))
@@ -84,7 +86,7 @@ class TestAugeas < Test::Unit::TestCase
84
86
  :incl => [ "/etc/fstab" ],
85
87
  :excl => [ "*~", "*.rpmnew" ])
86
88
  }
87
- aug.transform(:lens => "Inittab.lns",
89
+ aug.transform(:lens => "Inittab",
88
90
  :incl => "/etc/inittab")
89
91
  aug.transform(:lens => "Fstab.lns",
90
92
  :incl => "/etc/fstab*",
@@ -195,6 +197,87 @@ class TestAugeas < Test::Unit::TestCase
195
197
  assert_equal(29..40, span[:span])
196
198
  end
197
199
 
200
+ def test_srun
201
+ aug = aug_open
202
+
203
+ path = "/files/etc/hosts/*[canonical='localhost.localdomain']/ipaddr"
204
+ r, out = aug.srun("get #{path}\n")
205
+ assert_equal(1, r)
206
+ assert_equal("#{path} = 127.0.0.1\n", out)
207
+
208
+ assert_equal(0, aug.srun(" ")[0])
209
+ assert_equal(-1, aug.srun("foo")[0])
210
+ assert_equal(-1, aug.srun("set")[0])
211
+ assert_equal(-2, aug.srun("quit")[0])
212
+ end
213
+
214
+ def test_label
215
+ Augeas::open("/dev/null") do |aug|
216
+ assert_equal 'augeas', aug.label('/augeas')
217
+ assert_equal 'files', aug.label('/files')
218
+ end
219
+ end
220
+
221
+ def test_rename
222
+ Augeas::open("/dev/null") do |aug|
223
+ assert_equal false, aug.rename('/files', 'invalid/label')
224
+ assert_equal 0, aug.rename('/nonexistent', 'label')
225
+ assert_equal ['/files'], aug.match('/files')
226
+ assert_equal 1, aug.rename('/files', 'label')
227
+ end
228
+ end
229
+
230
+ def test_text_store_retrieve
231
+ Augeas::open("/dev/null") do |aug|
232
+ # text_store errors
233
+ assert_equal false, aug.text_store('Simplelines.lns', '/input', '/store')
234
+
235
+ # text_store
236
+ aug.set('/input', "line1\nline2\n")
237
+ assert aug.text_store('Simplelines.lns', '/input', '/store')
238
+ assert_equal 'line2', aug.get('/store/2')
239
+
240
+ # text_retrieve errors
241
+ assert_equal false, aug.text_retrieve('Simplelines.lns', '/unknown', '/store', '/output')
242
+
243
+ # text_retrieve
244
+ aug.set('/store/3', 'line3')
245
+ assert aug.text_retrieve('Simplelines.lns', '/input', '/store', '/output')
246
+ assert_equal "line1\nline2\nline3\n", aug.get('/output')
247
+ end
248
+ end
249
+
250
+ def test_context
251
+ Augeas::open("/dev/null") do |aug|
252
+ aug.context = '/augeas'
253
+ assert_equal '/augeas', aug.get('/augeas/context')
254
+ assert_equal '/augeas', aug.get('context')
255
+ assert_equal '/augeas', aug.context
256
+ end
257
+ end
258
+
259
+ def test_touch
260
+ Augeas::open("/dev/null") do |aug|
261
+ assert_equal [], aug.match('/foo')
262
+ aug.touch '/foo'
263
+ assert_equal ['/foo'], aug.match('/foo')
264
+
265
+ aug.set '/foo', 'bar'
266
+ aug.touch '/foo'
267
+ assert_equal 'bar', aug.get('/foo')
268
+ end
269
+ end
270
+
271
+ def test_clearm
272
+ Augeas::open("/dev/null") do |aug|
273
+ aug.set('/foo/a', '1')
274
+ aug.set('/foo/b', '2')
275
+ aug.clearm('/foo', '*')
276
+ assert_nil aug.get('/foo/a')
277
+ assert_nil aug.get('/foo/b')
278
+ end
279
+ end
280
+
198
281
  private
199
282
  def aug_open(flags = Augeas::NONE)
200
283
  if File::directory?(TST_ROOT)