root/lang/ruby/misc/configpp/configpp.rb @ 9981

Revision 9981, 8.2 kB (checked in by nyaxt, 5 years ago)

lang/ruby/misc/configpp: first import

  • Property svn:executable set to *
  • Property svn:keywords set to Id
Line 
1#!/usr/bin/ruby
2#
3#= configpp : Preprocessor for config files
4#
5#Authors::  Kouhei Ueno (nyaxt), ueno@nyaxtstep.com
6#Version::  $Id$
7#
8#License::
9# Copyright (c) 2007, Kouhei Ueno
10#
11# All rights reserved.
12#
13# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
14#
15#     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
16#     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
17#     * Neither the name of the nyaxtstep.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30#
31
32#
33# TODO:
34#   - Refactoring! Refactoring! Refactoring!
35#     - ConfigPP class should not handle command line options.
36#
37
38require 'optparse'
39require 'tmpdir'
40require 'erb'
41
42module ConfigPP
43
44  VERSION = "rev. #{'$Revision: 636 $'.split[1]}"
45
46  module RuntimeEnv
47    RUNTIMEENV = binding
48  end
49
50  class ConfigPP
51    attr_accessor :options, :erb_trimming
52    attr_accessor :overwrite, :out_filename, :out_dir, :diff # TODO: move these to appcontroller class when refactoring!
53
54    def initialize
55      @options = Hash.new
56      @erb_trimming = '<>'
57
58      @out_dir = Dir.pwd
59
60      auto_detect_options
61    end
62
63    POSSIBLE_OS = ['win', 'cygwin', 'linux', 'osx', 'other']
64    POSSIBLE_DIST = ['debian', 'zaurus', 'other']
65
66    # parse command line options
67    #
68    # _argv_ :: pass ARGV here
69    #
70    # return :: argv without parsed options
71    #
72    # raise :: when parse failed
73    #
74    def parse_options argv=ARGV
75      opt = OptionParser.new('configpp : preprocessor for config files')
76      opt.version = VERSION
77
78      opt.on('-o FILE', '--out-file FILE', 'set output file name') do |v|
79        @out_filename = v
80      end
81
82      opt.on('--out-dir DIR', 'set output directory') do |v|
83        raise "no such dir: #{v}" unless File.exists?(v)       
84        raise "not directory: #{v}" unless File.ftype(v) == 'directory'
85
86        @out_dir = v
87      end
88
89      opt.on('--overwrite', 'overwrite output file') do |v|
90        @overwrite = v
91      end
92
93      opt.on('-d', '--diff', 'view diff to current') do |v|
94        @diff = v
95      end
96
97      opt.on('-t TRIMMINGOPT', '--trimming TRIMMINGOPT', "set trimming option for erb interpreter (defaults to '#{@erb_trimming}')") do |v|
98        @erb_trimming = v
99      end
100     
101      opt.on('--os=OS', "set operating system (#{POSSIBLE_OS.join('/')})", POSSIBLE_OS) do |v|
102        @options[:os] = v
103      end
104
105      opt.on('--dist=DISTRIBUTION', "set distribution (#{POSSIBLE_DIST.join('/')})", POSSIBLE_DIST) do |v|
106        @options[:dist] = v
107      end
108
109      opt.on('--ext KEY=VAL', 'set extended options', /^(\w+?)=(\S+)$/) do |v|
110        @options[v[1]] = v[2]
111      end
112
113      ret = opt.parse(argv)
114
115      raise 'both output directory and output filename is specified' if @out_filename && @out_dir
116
117      ret
118    end
119
120    # auto detect standard options from running env.
121    def auto_detect_options
122      @options[:dist] = 'other'
123
124      # handle win env separately
125      if RUBY_PLATFORM.match(/mswin/)
126        @options[:os] = 'win'
127        return
128      end
129
130      # detect os
131      uname = `uname -a`
132
133      @options[:os] = case uname
134      when /cygwin/
135        'cygwin'
136      when /Darwin/
137        'osx'
138      when /Linux/
139        'linux'
140      else
141        'other'
142      end
143
144      # detect linux distribution
145      if @options[:os] == 'linux'
146        @options[:dist] = case
147        when File.exist?('/opt/QtPalmtop') # TODO: improve
148          'zaurus'
149        when File.exist?('/etc/debian_version')
150          @options['debian_version'] = File.readlines("/etc/debian_version", nil)[0].chomp
151
152          'debian'
153        else
154          'other'
155        end
156      end
157
158    end
159
160    # parse signature of a configpp sourcefile
161    #
162    # __filepath__ :: file to process
163    def parse_signature(filepath)
164      # first pass : parse configpp signature line
165      filename = versioninfo = nil
166
167      File.open(filepath, 'r') do |srcfile|
168        srcfile.each_line do |line|
169          if match = line.match(/.*? configpp\s*:\s*(\S+)\s*,\s*(.*)$/)   
170            filename = match[1]
171            versioninfo = match[2]
172
173            break
174          end
175        end
176      end
177
178      raise "configpp signature not found" unless filename and versioninfo
179
180      [filename, versioninfo]
181    end
182
183    # process a configpp source file and write result to output file
184    #
185    # __filepath__ :: file to process
186    def process(filepath, filename, versioninfo)
187      # second pass : actually parse the file using erb
188     
189      evalenvklass = Class.new
190
191      erb = ERB.new(File.readlines(filepath), nil, @erb_trimming)
192      erb.def_method(evalenvklass, '_process', File.basename(filepath))
193
194      options = @options
195      evalenv = evalenvklass.new
196      evalenv.instance_eval do
197        @cpp = ConfigPPUtil.new(filename, versioninfo, options)
198      end
199
200      evalenv._process
201    end
202
203  end
204
205  class ConfigPPUtil
206
207    # initialize ConfigPP runtime utils
208    #
209    # __filename__ : recommended output filename
210    # __version__ : version str
211    # __options__ : options hash
212    #
213    def initialize(filename, version, options)
214      @filename = filename
215      @version = version
216      @options = options
217      @commentprefix = '# '
218    end
219   
220    def method_missing(name, *args)
221      res_sym = @options[name]
222      res_sym ? res_sym : @options[name.to_s]
223    end
224
225    # print header str
226    def header
227      headerstr =<<END
228        Generated by configpp #{VERSION} on: #{Time.now.to_s}
229        Using options: #{@options.inspect}
230END
231
232      headerstr.split($/).map {|line| @commentprefix + line.match(/^\s+(.*)/)[1]}.join("\n")+"\n"
233    end
234
235  end
236
237end
238
239if __FILE__ == $0
240  configpp = ConfigPP::ConfigPP.new
241
242  files = nil
243  begin
244    files = configpp.parse_options(ARGV)
245    STDERR.puts "options : #{configpp.options.inspect}"
246    raise "no file was given" if files.empty?
247  rescue
248    STDERR.puts "exception while parsing options: #{$!.to_s}"
249    STDERR.puts
250    configpp.parse_options(["--help"])
251    exit 1
252  end
253
254  files.each do |path|
255    next if File.ftype(path) != 'file'
256
257    STDERR.puts "processing file: #{path}"
258    begin
259      if configpp.diff
260        p tmpfile = Dir::tmpdir + "/configpptmp"
261
262        filename, versioninfo = *configpp.parse_signature(path)
263
264        output = configpp.process(path, filename, versioninfo)
265
266        File.open(tmpfile, 'w') do |outfile|
267          outfile.write output
268        end
269
270        orig_filename = configpp.out_filename
271        orig_filename = File.expand_path(filename, configpp.out_dir) unless orig_filename
272
273        exec "diff -c #{orig_filename} #{tmpfile}"
274      else
275        filename, versioninfo = *configpp.parse_signature(path)
276
277        out_filename = configpp.out_filename unless out_filename
278        out_filename = File.expand_path(filename, configpp.out_dir) unless out_filename
279       
280        raise "output file already exists : #{out_filename}" if File.exists?(out_filename) and not (configpp.overwrite or configpp.diff)
281
282        output = configpp.process(path, filename, versioninfo)
283
284        File.open(out_filename, 'w') do |outfile|
285          outfile.write output
286        end
287      end
288    rescue
289      STDERR.puts "error: #{$!.to_s}"
290      STDERR.puts $!.backtrace.map {|e| "\t"+e}.join("\n")
291    end
292  end
293
294  STDERR.puts "done!"
295
296end
Note: See TracBrowser for help on using the browser.