#!/usr/bin/env ruby -w # process the flags.yaml file # and generate a flags.h and flags.c file. # # # todo -- support for long-options (--longoption, --longoption=value, etc) # # require 'erb' require 'yaml' header_preamble = <' => 'gt', '<' => 'lt', ',' => 'comma', '.' => 'period', '/' => 'forward_slash', '\\' => 'back_slash', '?' => 'question', '|' => 'pipe', '~' => 'tilde', '`' => 'grave', '!' => 'bang', '@' => 'at', '#' => 'hash', '$' => 'dollar', '%' => 'percent', '^' => 'caret', '&' => 'ampersand', '*' => 'star', '(' => 'left_paren', ')' => 'right_paren', '-' => 'minus', '+' => 'plus', '=' => 'equal', '[' => 'left_bracket', ']' => 'right_bracket', '{' => 'left_brace', '}' => 'right_brace', ':' => 'colon', ';' => 'semi_colon', '\'' => 'apostrophe', '"' => 'quote' } def initialize(hash) @char = hash['char'].to_s @argument = hash['argument'] || false @ignore = hash['ignore'] || false @flag_name = hash['flag_name'] @flag_name = @flag_name.to_s if @flag_name @xor = hash['xor'] || [] @xor = case @xor when Array @xor when Integer, String [ @xor ] else raise "Invalid xor type: #{@xor}" end @long = @char.length > 1 @xor.map! { |x| x.to_s } end attr_reader :char, :xor, :argument, :long def flag_name return @flag_name if @flag_name return self.class.flag_name(@char) end def field_type return :optarg if @argument return :null if @ignore return :bit end def self.flag_name(char) if char.length > 1 name = char name = '_' + name.gsub(/[^A-Za-z0-9_]/, '_') name = name.gsub('/_+/', '_') return name end return '_' + @@map[char] if @@map[char] return '_' + char end end # better ARGF. def argf_each if ARGV.count > 0 ARGV.each {|file| File.open(file, "r") {|io| yield file, io } } else yield nil, $stdin end end def escape_cstr(x) # escape a c string x.gsub(/([\\"])/, "\\\\1") end code = ERB.new(DATA.read(), 0, "%<>") argf_each {|filename, file| data = YAML.load(file) help = data['help'] options = data['options'] case_insensitive = data['case_insensitive'] || false name = data['name'] || '' # prevent warnings for unused vars. options = options help = help case_insensitive = case_insensitive name = name # options is an array of items which may be hashes, strings, or numbers. # normalize them. options = options.map {|opt| opt = case opt when String, Integer { 'char' => opt } when Hash # {'o' => { ... }} # or # {'char' => , ... } if opt['char'] opt else opt = opt.first opt[1].merge({ 'char' => opt[0] }) end else raise "Unexpected data type: #{opt}" end opt['char'] = opt['char'].downcase if case_insensitive Option.new(opt) } long_options = options.select {|opt| opt.long } long_options = long_options #data[options] = options # check for help? basename = filename basename = $1 if filename && filename =~ /^(.*)\./ b = binding # bind help, options for ERB. #io = $stdout; io = basename ? File.open(basename + ".c", "w") : $stdout #io = File.open(basename + ".c", "w") unless basename.nil? io.write(code.result(b)) io.close unless io == $stdout io = basename ? File.open(basename + ".h", "w") : $stdout io.write(header_preamble) # two passes - one with arguments, one without. options.each {|opt| if opt.field_type == :optarg io.printf(" char *%s;\n", opt.flag_name) end } io.puts() options.each {|opt| if opt.field_type == :bit io.printf(" unsigned %s:1;\n", opt.flag_name) end } io.puts io.write(header_postamble) io.close unless io == $stdout # #puts options.to_yaml # puts code.result(binding()) } __END__ #ifdef __ORCAC__ #pragma optimize 79 #pragma noroot #endif #include #include #include #include "<%= basename ? basename + '.h' : '' %>" void FlagsHelp(void) { % help.each do |h| fputs("<%= escape_cstr(h) %>\n", stdout); % end fputs("\n", stdout); exit(0); } struct Flags flags; int FlagsParse(int argc, char **argv) { char *cp; char *optarg; char c; int i; int j; int eof; // end-of-flags int mindex; // mutation index. memset(&flags, 0, sizeof(flags)); for (i = 1, mindex = 1, eof = 0; i < argc; ++i) { cp = argv[i]; c = cp[0]; if (c != '-' || eof) { if (i != mindex) argv[mindex] = argv[i]; mindex++; continue; } // -- = end of options. if (cp[1] == '-' && cp[2] == 0) { eof = 1; continue; } % long_options.each do |opt| % flag_name = 'flags.' + opt.flag_name % strcmp = case_insensitive ? 'strcasecmp' : 'strcmp' if (!<%= strcmp %>("<%= opt.char %>", cp+1)) { % case opt.field_type % when :optarg if (++i >= argc) { fputs("### <%= name %> - \"-<%= opt.char %>\" requires an argument.\n", stderr); exit(1); } <%= flag_name %> = argv[i]; % when :bit <%= flag_name %> = 1; % end continue; } % end // now scan all the flags in the string... optarg = NULL; for (j = 1; ; ++j) { c = cp[j]; if (c == 0) break; switch (c) { % if help && !options.find_index {|x| x.char == 'h' } case 'h': FlagsHelp(); exit(0); % end % # % options.each do |opt| % next if opt.long case '<%= escape_cstr(opt.char) %>': % if case_insensitive && opt.char =~ /^[a-z]$/ case '<%= escape_cstr(opt.char.upcase) %>': % end % # check for an argument. % flag_name = 'flags.' + opt.flag_name % # % if opt.argument // -xarg or -x arg ++j; if (cp[j]) { optarg = cp + j; } else { if (++i >= argc) { fputs("### <%= name %> - \"-<%= opt.char %>\" requires an argument.\n", stderr); exit(1); } optarg = argv[i]; } <%= flag_name %> = optarg; % else # no argument. <%= flag_name %> = 1; % end # if no argument. % # % # unset any exclusive or values % opt.xor.each do |xor_opt| flags.<%= Option.flag_name(xor_opt) %> = 0; %end break; % end # options.each default: fprintf(stderr, "### <%= name %> - \"-%c\" is not an option.\n", c); exit(1); } if (optarg) break; } } return mindex; }