Module: Rconftool

Defined in:
lib/rconftool.rb

Defined Under Namespace

Classes: ConfigFile, NoVersionLine, Processor, Setting

Constant Summary collapse

VERSION =
"0.1"
HEADER_ID =
'__header__'

Class Method Summary collapse

Class Method Details

.install(distfile, targetfile = nil, oldfile = nil, opt = {}) ⇒ Object

This module function installs a single source (.dist) file to a target location, having first merged in any compatible settings from the target file if it existed previously [if it does not exist, any settings from 'oldfile' are used instead]

If the distfile is not in sysconftool format (i.e. doesn't have a ##VERSION: header within the first 20 lines), then for safety it is only installed if the target file does not already exist. No attempt at data merging is made in that case.

Raises:

  • (Errno::EEXIST)

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rconftool.rb', line 51

def self.install(distfile, targetfile=nil, oldfile=nil, opt={})
  debug = opt[:debug] || $stdout

  targetfile ||= distfile
  if opt[:strip_regexp]
    targetfile = targetfile.sub(opt[:strip_regexp], '')
    oldfile    = oldfile.sub(opt[:strip_regexp], '') if oldfile
  end
  if opt[:add_suffix]
    targetfile = targetfile + opt[:add_suffix]
    oldfile    = oldfile + opt[:add_suffix] if oldfile
  end
  raise Errno::EEXIST, "#{distfile}: dist and target filenames are the same" if distfile == targetfile

  # Read in the source (.dist) file
  begin
    src = ConfigFile.new(distfile)
  rescue NoVersionLine
    # Fallback behaviour when installing a file which is not in sysconftool
    # format: we install the file only if it doesn't already exist
    if File.exist?(targetfile)
      debug << "#{targetfile}: already exists, skipping\n"
      return
    end
    return if opt[:noclobber]
    copyfrom = (oldfile and File.exist?(oldfile)) ? oldfile : distfile
    if File.symlink?(copyfrom)
      File.symlink(File.readlink(copyfrom), targetfile)
      debug << "#{targetfile}: symlink copied from #{copyfrom}\n"
    else
      FileUtils.cp copyfrom, targetfile, :preserve=>true
      debug << "#{targetfile}: copied from #{copyfrom}\n"
    end
    return
  end

  # OK, so we have a sysconftool file to install. Read in the existing
  # target file, or if that does not exist, the oldfile
  begin
    old = ConfigFile.new
    old.read(targetfile)
  rescue NoVersionLine
    # That's OK; the old target will be renamed to .bak
  rescue Errno::ENOENT
    begin
      target_missing = true
      old.read(oldfile) if oldfile
    rescue Errno::ENOENT, NoVersionLine
    end
  end

  # Same VERSION? No merge is required
  if src.version == old.version and not opt[:force]
    if target_missing
      FileUtils.cp oldfile, targetfile, :preserve=>true
      debug << "#{targetfile}: same VERSION, copied from #{oldfile}\n"
      return
    end
    debug << "#{targetfile}: same VERSION, no change\n"
    return
  end

  # Merge in old settings (note: any settings which are in targetfile but
  # not in distfile will be silently dropped)
  debug << "#{targetfile}:\n"
  src.settings[1..-1].each do |src_setting|
    name = src_setting.name
    old_setting = old[name]
    unless old_setting
      debug << "  #{name}: new\n"
      next
    end
    if old_setting.version == src_setting.version
      debug << "  #{name}: unchanged\n"
      src_setting.add_comment("\n DEFAULT SETTING from #{distfile}:\n")
      src_setting.add_comment(src_setting.content)
      src_setting.content = old_setting.content
      next
    end
    # Otherwise, must install updated setting and comment out
    # the current setting for reference
    debug << "  #{name}: UPDATED\n"
    src_setting.add_comment("\n Previous setting (inserted by rconftool):\n\n")
    src_setting.add_comment(old_setting.content)
  end

  return if opt[:noclobber]

  # Write out the new file and carry forward permissions
  begin
    tempfile = targetfile+".new#{$$}"
    src.write(tempfile)
    st = File.stat(distfile)
    begin
      File.chown(st.uid, st.gid, tempfile)
    rescue Errno::EPERM
    end
    File.chmod(st.mode, tempfile)
    File.rename(targetfile, targetfile+".bak") unless target_missing
    File.rename(tempfile, targetfile)
  rescue
    File.delete(tempfile) rescue nil
    raise
  end
end

.recurse_dir(base) ⇒ Object

Yield directory contents recursively, without doing chdir(). Note that yielded pathnames are relative to the base directory given; so that, for example, you can simulate 'cp -r /foo/bar/ /baz/' by

recurse_dir("/foo/bar") { |n| copy("/foo/bar/"+n,"/baz/"+n) unless 
                                   File.directory?("/foo/bar/"+n) }

Current behaviour is that if a directory is a symlink, we follow it. (Perhaps the block we yield should return true/false?)


255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/rconftool.rb', line 255

def self.recurse_dir(base)
  base = base+File::SEPARATOR unless base[-1,1] == File::SEPARATOR
  dirs = ['']
  while dir = dirs.pop
    yield dir unless dir == ''
    Dir.foreach(base+dir) do |n|
      next if n == '.' || n == '..'
      target = dir + n
      if File.directory?(base+target)
        dirs << target+File::SEPARATOR
        next
      end
      yield target
    end
  end
end