| Class | Erubis::Main |
| In: |
lib/erubis/main.rb
|
| Parent: | Object |
# File lib/erubis/main.rb, line 40
40: def self.main(argv=ARGV)
41: status = 0
42: begin
43: Main.new.execute(ARGV)
44: rescue CommandOptionError => ex
45: $stderr.puts ex.message
46: status = 1
47: end
48: exit(status)
49: end
# File lib/erubis/main.rb, line 51
51: def initialize
52: @single_options = "hvxztTSbeBXNUC"
53: @arg_options = "pcrfKIlaE" #C
54: @option_names = {
55: 'h' => :help,
56: 'v' => :version,
57: 'x' => :source,
58: 'z' => :syntax,
59: 'T' => :unexpand,
60: 't' => :untabify, # obsolete
61: 'S' => :intern,
62: 'b' => :bodyonly,
63: 'B' => :binding,
64: 'p' => :pattern,
65: 'c' => :context,
66: #'C' => :class,
67: 'e' => :escape,
68: 'r' => :requires,
69: 'f' => :datafiles,
70: 'K' => :kanji,
71: 'I' => :includes,
72: 'l' => :lang,
73: 'a' => :action,
74: 'E' => :enhancers,
75: 'X' => :notext,
76: 'N' => :linenum,
77: 'U' => :unique,
78: 'C' => :compact,
79: }
80: assert unless @single_options.length + @arg_options.length == @option_names.length
81: (@single_options + @arg_options).each_byte do |ch|
82: assert unless @option_names.key?(ch.chr)
83: end
84: end
# File lib/erubis/main.rb, line 87
87: def execute(argv=ARGV)
88: ## parse command-line options
89: options, properties = parse_argv(argv, @single_options, @arg_options)
90: filenames = argv
91: options['h'] = true if properties[:help]
92: opts = Object.new
93: arr = @option_names.collect { |ch, name| "def #{name}; @#{name}; end\n" }
94: opts.instance_eval arr.join
95: options.each do |ch, val|
96: name = @option_names[ch]
97: opts.instance_variable_set("@#{name}", val)
98: end
99:
100: ## help, version, enhancer list
101: if opts.help || opts.version
102: puts version() if opts.version
103: puts usage() if opts.help
104: puts show_properties() if opts.help
105: puts show_enhancers() if opts.help
106: return
107: end
108:
109: ## include path
110: opts.includes.split(/,/).each do |path|
111: $: << path
112: end if opts.includes
113:
114: ## require library
115: opts.requires.split(/,/).each do |library|
116: require library
117: end if opts.requires
118:
119: ## action
120: action = opts.action
121: action ||= 'syntax' if opts.syntax
122: action ||= 'convert' if opts.source || opts.notext
123:
124: ## lang
125: lang = opts.lang || 'ruby'
126: action ||= 'convert' if opts.lang
127:
128: ## class name of Eruby
129: #classname = opts.class
130: classname = nil
131: klass = get_classobj(classname, lang, properties[:pi])
132:
133: ## kanji code
134: $KCODE = opts.kanji if opts.kanji
135:
136: ## read context values from yaml file
137: datafiles = opts.datafiles
138: context = load_datafiles(datafiles, opts)
139:
140: ## parse context data
141: if opts.context
142: context = parse_context_data(opts.context, opts)
143: end
144:
145: ## properties for engine
146: properties[:escape] = true if opts.escape && !properties.key?(:escape)
147: properties[:pattern] = opts.pattern if opts.pattern
148: #properties[:trim] = false if opts.notrim
149: properties[:preamble] = properties[:postamble] = false if opts.bodyonly
150: properties[:pi] = nil if properties[:pi] == true
151:
152: ## create engine and extend enhancers
153: engine = klass.new(nil, properties)
154: enhancers = get_enhancers(opts.enhancers)
155: #enhancers.push(Erubis::EscapeEnhancer) if opts.escape
156: enhancers.each do |enhancer|
157: engine.extend(enhancer)
158: engine.bipattern = properties[:bipattern] if enhancer == Erubis::BiPatternEnhancer
159: end
160:
161: ## no-text
162: engine.extend(Erubis::NoTextEnhancer) if opts.notext
163:
164: ## convert and execute
165: val = nil
166: msg = "Syntax OK\n"
167: if filenames && !filenames.empty?
168: filenames.each do |filename|
169: test(?f, filename) or raise CommandOptionError.new("#{filename}: file not found.")
170: engine.filename = filename
171: engine.convert!(File.read(filename))
172: val = do_action(action, engine, context, filename, opts)
173: msg = nil if val
174: end
175: else
176: engine.filename = filename = '(stdin)'
177: engine.convert!($stdin.read())
178: val = do_action(action, engine, context, filename, opts)
179: msg = nil if val
180: end
181: print msg if action == 'syntax' && msg
182:
183: end
# File lib/erubis/main.rb, line 434
434: def _instance_eval(_context, _str)
435: _context.instance_eval(_str)
436: end
# File lib/erubis/main.rb, line 473
473: def check_syntax(filename, src)
474: require 'open3'
475: command = (ENV['_'] || 'ruby') + ' -wc' # ENV['_'] stores command name
476: stdin, stdout, stderr = Open3.popen3(command)
477: stdin.write(src)
478: stdin.close
479: result = stdout.read()
480: stdout.close()
481: errmsg = stderr.read()
482: stderr.close()
483: return nil unless errmsg && !errmsg.empty?
484: errmsg =~ /\A-:(\d+): /
485: linenum, message = $1, $'
486: return "#{filename}:#{linenum}: #{message}"
487: end
# File lib/erubis/main.rb, line 253
253: def collect_supported_properties(erubis_klass)
254: list = []
255: erubis_klass.ancestors.each do |klass|
256: if klass.respond_to?(:supported_properties)
257: list.concat(klass.supported_properties)
258: end
259: end
260: return list
261: end
# File lib/erubis/main.rb, line 187
187: def do_action(action, engine, context, filename, opts)
188: case action
189: when 'convert'
190: s = manipulate_src(engine.src, opts)
191: when nil, 'exec', 'execute'
192: s = opts.binding ? engine.result(context.to_hash) : engine.evaluate(context)
193: when 'syntax'
194: s = check_syntax(filename, engine.src)
195: else
196: raise "*** internal error"
197: end
198: print s if s
199: return s
200: end
# File lib/erubis/main.rb, line 374
374: def get_classobj(classname, lang, pi)
375: classname ||= "E#{lang}"
376: base_module = pi ? Erubis::PI : Erubis
377: begin
378: klass = base_module.const_get(classname)
379: rescue NameError
380: klass = nil
381: end
382: unless klass
383: if lang
384: msg = "-l #{lang}: invalid language name (class #{base_module.name}::#{classname} not found)."
385: else
386: msg = "-c #{classname}: invalid class name."
387: end
388: raise CommandOptionError.new(msg)
389: end
390: return klass
391: end
# File lib/erubis/main.rb, line 393
393: def get_enhancers(enhancer_names)
394: return [] unless enhancer_names
395: enhancers = []
396: shortname = nil
397: begin
398: enhancer_names.split(/,/).each do |shortname|
399: enhancers << Erubis.const_get("#{shortname}Enhancer")
400: end
401: rescue NameError
402: raise CommandOptionError.new("#{shortname}: no such Enhancer (try '-E' to show all enhancers).")
403: end
404: return enhancers
405: end
# File lib/erubis/main.rb, line 454
454: def intern_hash_keys(obj, done={})
455: return if done.key?(obj.__id__)
456: case obj
457: when Hash
458: done[obj.__id__] = obj
459: obj.keys.each do |key|
460: obj[key.intern] = obj.delete(key) if key.is_a?(String)
461: end
462: obj.values.each do |val|
463: intern_hash_keys(val, done) if val.is_a?(Hash) || val.is_a?(Array)
464: end
465: when Array
466: done[obj.__id__] = obj
467: obj.each do |val|
468: intern_hash_keys(val, done) if val.is_a?(Hash) || val.is_a?(Array)
469: end
470: end
471: end
# File lib/erubis/main.rb, line 407
407: def load_datafiles(filenames, opts)
408: context = Erubis::Context.new
409: return context unless filenames
410: filenames.split(/,/).each do |filename|
411: filename.strip!
412: test(?f, filename) or raise CommandOptionError.new("#{filename}: file not found.")
413: if filename =~ /\.ya?ml$/
414: if opts.unexpand
415: ydoc = YAML.load_file(filename)
416: else
417: ydoc = YAML.load(untabify(File.read(filename)))
418: end
419: ydoc.is_a?(Hash) or raise CommandOptionError.new("#{filename}: root object is not a mapping.")
420: intern_hash_keys(ydoc) if opts.intern
421: context.update(ydoc)
422: elsif filename =~ /\.rb$/
423: str = File.read(filename)
424: context2 = Erubis::Context.new
425: _instance_eval(context2, str)
426: context.update(context2)
427: else
428: CommandOptionError.new("#{filename}: '*.yaml', '*.yml', or '*.rb' required.")
429: end
430: end
431: return context
432: end
# File lib/erubis/main.rb, line 202
202: def manipulate_src(source, opts)
203: flag_linenum = opts.linenum
204: flag_unique = opts.unique
205: flag_compact = opts.compact
206: if flag_linenum
207: n = 0
208: source.gsub!(/^/) { n += 1; "%5d: " % n }
209: source.gsub!(/^ *\d+:\s+?\n/, '') if flag_compact
210: source.gsub!(/(^ *\d+:\s+?\n)+/, "\n") if flag_unique
211: else
212: source.gsub!(/^\s*?\n/, '') if flag_compact
213: source.gsub!(/(^\s*?\n)+/, "\n") if flag_unique
214: end
215: return source
216: end
# File lib/erubis/main.rb, line 303
303: def parse_argv(argv, arg_none='', arg_required='', arg_optional='')
304: options = {}
305: context = {}
306: while argv[0] && argv[0][0] == ?-
307: optstr = argv.shift
308: optstr = optstr[1, optstr.length-1]
309: #
310: if optstr[0] == ?- # context
311: unless optstr =~ /\A\-([-\w]+)(?:=(.*))?/
312: raise CommandOptionError.new("-#{optstr}: invalid context value.")
313: end
314: name = $1; value = $2
315: name = name.gsub(/-/, '_').intern
316: #value = value.nil? ? true : YAML.load(value) # error, why?
317: value = value.nil? ? true : YAML.load("---\n#{value}\n")
318: context[name] = value
319: #
320: else # options
321: while optstr && !optstr.empty?
322: optchar = optstr[0].chr
323: optstr[0,1] = ""
324: if arg_none.include?(optchar)
325: options[optchar] = true
326: elsif arg_required.include?(optchar)
327: arg = optstr.empty? ? argv.shift : optstr
328: unless arg
329: mesg = "-#{optchar.chr}: #{@option_args[optchar]} required."
330: raise CommandOptionError.new(mesg)
331: end
332: options[optchar] = arg
333: optstr = nil
334: elsif arg_optional.include?(optchar)
335: arg = optstr.empty? ? true : optstr
336: options[optchar] = arg
337: optstr = nil
338: else
339: raise CommandOptionError.new("-#{optchar.chr}: unknown option.")
340: end
341: end
342: end
343: #
344: end # end of while
345:
346: return options, context
347: end
# File lib/erubis/main.rb, line 438
438: def parse_context_data(context_str, opts)
439: if context_str[0] == ?{
440: require 'yaml'
441: ydoc = YAML.load(context_str)
442: unless ydoc.is_a?(Hash)
443: raise CommandOptionError.new("-c: root object is not a mapping.")
444: end
445: intern_hash_keys(ydoc) if opts.intern
446: return ydoc
447: else
448: context = Erubis::Context.new
449: context.instance_eval(context_str, '-c')
450: return context
451: end
452: end
# File lib/erubis/main.rb, line 286
286: def show_enhancers
287: s = "enhancers:\n"
288: list = []
289: ObjectSpace.each_object(Module) do |m| list << m end
290: list.sort_by { |m| m.name.to_s }.each do |m|
291: next unless m.name =~ /\AErubis::(.*)Enhancer\z/
292: name = $1
293: desc = m.desc
294: s << (" %-13s : %s\n" % [name, desc])
295: end
296: return s
297: end
# File lib/erubis/main.rb, line 263
263: def show_properties
264: s = "supported properties:\n"
265: basic_props = collect_supported_properties(Erubis::Basic::Engine)
266: pi_props = collect_supported_properties(Erubis::PI::Engine)
267: list = []
268: common_props = basic_props & pi_props
269: list << ['(common)', common_props]
270: list << ['(basic)', basic_props - common_props]
271: list << ['(pi)', pi_props - common_props]
272: %w[ruby php c java scheme perl javascript].each do |lang|
273: klass = Erubis.const_get("E#{lang}")
274: list << [lang, collect_supported_properties(klass) - basic_props]
275: end
276: list.each do |lang, props|
277: s << " * #{lang}\n"
278: props.each do |name, default_val, desc|
279: s << (" --%-23s : %s\n" % ["#{name}=#{default_val.inspect}", desc])
280: end
281: end
282: s << "\n"
283: return s
284: end
# File lib/erubis/main.rb, line 350
350: def untabify(str, width=8)
351: list = str.split(/\t/)
352: last = list.pop
353: sb = ''
354: list.each do |s|
355: column = (n = s.rindex(?\n)) ? s.length - n - 1 : s.length
356: n = width - (column % width)
357: sb << s << (' ' * n)
358: end
359: sb << last
360: return sb
361: end
# File lib/erubis/main.rb, line 218
218: def usage(command=nil)
219: command ||= File.basename($0)
220: buf = []
221: buf << "erubis - embedded program converter for multi-language"
222: buf << "Usage: #{command} [..options..] [file ...]"
223: buf << " -h, --help : help"
224: buf << " -v : version"
225: buf << " -x : show converted code"
226: buf << " -X : show converted code, only ruby code and no text part"
227: buf << " -N : numbering: add line numbers (for '-x/-X')"
228: buf << " -U : unique: compress empty lines to a line (for '-x/-X')"
229: buf << " -C : compact: remove empty lines (for '-x/-X')"
230: buf << " -b : body only: no preamble nor postamble (for '-x/-X')"
231: buf << " -z : syntax checking"
232: buf << " -e : escape (equal to '--E Escape')"
233: buf << " -p pattern : embedded pattern (default '<% %>')"
234: buf << " -l lang : convert but no execute (ruby/php/c/java/scheme/perl/js)"
235: buf << " -E e1,e2,... : enhancer names (Escape, PercentLine, BiPattern, ...)"
236: buf << " -I path : library include path"
237: buf << " -K kanji : kanji code (euc/sjis/utf8) (default none)"
238: buf << " -c context : context data string (yaml inline style or ruby code)"
239: buf << " -f datafile : context data file ('*.yaml', '*.yml', or '*.rb')"
240: #buf << " -t : expand tab characters in YAML file"
241: buf << " -T : don't expand tab characters in YAML file"
242: buf << " -S : convert mapping key from string to symbol in YAML file"
243: buf << " -B : invoke 'result(binding)' instead of 'evaluate(context)'"
244: buf << " --pi=name : parse '<?name ... ?>' instead of '<% ... %>'"
245: #'
246: # -T : don't trim spaces around '<% %>'
247: # -c class : class name (XmlEruby/PercentLineEruby/...) (default Eruby)
248: # -r library : require library
249: # -a : action (convert/execute)
250: return buf.join("\n")
251: end