class Frag::App

Attributes

status[R]

Public Class Methods

new(args, input=STDIN, output=STDOUT, error=STDERR) click to toggle source
# File lib/frag/app.rb, line 8
def initialize(args, input=STDIN, output=STDOUT, error=STDERR)
  @input, @output, @error = input, output, error
  @status = 0
  @version_printed = false

  @state = State.new('frag:', 'frag end', '#', '', nil, nil)

  begin
    parser.parse!(args)
  rescue OptionParser::InvalidOption => e
    return error e.message
  end
  args.size > 0 || @version_printed or
    return error "no files given"

  @input_paths = args
end

Public Instance Methods

run() click to toggle source
# File lib/frag/app.rb, line 26
def run
  return @status if @status != 0
  global_state = @state.dup
  @input_paths.each do |input_path|
    @state = global_state.dup
    manage_files(input_path) do |input, output|
      process(input, output)
    end
  end
  @status
end

Private Instance Methods

error(message, status=1) click to toggle source
# File lib/frag/app.rb, line 42
def error(message, status=1)
  @error.puts "error: #{message}"
  @status = status
  false
end
manage_files(input_path) { |input, output or return| ... } click to toggle source
# File lib/frag/app.rb, line 121
def manage_files(input_path)
  File.exist?(input_path) or
    return error "file not found: #{input_path}"
  File.file?(input_path) or
    return error "not a file: #{input_path}"
  File.readable?(input_path) or
    return error "cannot read file: #{input_path}"
  File.writable?(input_path) or
    return error "cannot write file: #{input_path}"
  tempfile = nil
  success = nil
  open(input_path) do |input|
    tempfile = Tempfile.open('frag') do |output|
      yield input, output or
        return
      output
    end
  end
  if @state.backup_prefix || @state.backup_suffix
    backup_path = "#{@state.backup_prefix}#{File.expand_path(input_path)}#{@state.backup_suffix}"
    FileUtils.mkdir_p File.dirname(backup_path)
    FileUtils.cp input_path, backup_path
  end
  FileUtils.cp tempfile.path, input_path
end
parser() click to toggle source
# File lib/frag/app.rb, line 52
    def parser
      @parser ||= OptionParser.new do |parser|
        parser.banner = "USAGE: #$0 [options] file ..."
        parser.separator "\nOptions:"

        parser.on '-b', '--begin DELIMITER', "Delimiter that begins each generated fragment. Default: 'frag:'" do |value|
          @state.beginning = value
        end
        parser.on '-e', '--end DELIMITER', "Delimiter that ends each generated fragment. Default: 'frag end'" do |value|
          @state.ending = value
        end
        parser.on '-l', '--leader STRING', "String that precedes each begin or end delimiter. Default: '#'" do |value|
          if parser.parsing_subconfig
            warn "-l / --leader is unnecessary in $frag-config line"
          end
          @state.leader = value
        end
        parser.on '-t', '--trailer STRING', "String that succeeds each begin or end delimiter. Default: ''" do |value|
          if parser.parsing_subconfig
            warn "-t / --trailer is unnecessary in $frag-config line"
          end
          @state.trailer = value
        end
        parser.on '-p', '--backup-prefix PREFIX', "Back up original files with the given prefix. May be a directory." do |value|
          @state.backup_prefix = value
        end
        parser.on '-s', '--backup-suffix SUFFIX', "Back up original files with the given suffix." do |value|
          @state.backup_suffix = value
        end
        parser.on '-v', '--version', "" do
          @output.puts "Frag version #{Frag::VERSION}"
          @version_printed = true
        end

        parser.separator <<-EOS.gsub(/^ *\|/, '')
          |
          |## Embedded options
          |
          |Options may also be embedded in the file itself via a line that
          |contains "$frag-config:". Example:
          |
          |    <!-- $frag-config: -p ~/.frag-backups/ -->
          |
          |Note that the leader and trailer are always taken from the strings
          |that preceed the "$frag-config" and succeed the last option
          |respectively. They need not be set with --leader and --trailer.
          |
        EOS

        class << parser
          attr_accessor :parsing_subconfig

          def parse_subconfig!(args)
            self.parsing_subconfig = true
            # OptionParser will error on an argument like like "-->".
            if args.last =~ /\A--?(?:[^0-9a-zA-Z]|\z)/
              last_arg = args.pop
              parse!(args)
              args << last_arg
            else
              parse!(args)
            end
          ensure
            self.parsing_subconfig = false
          end
        end
      end
    end
process(input, output) click to toggle source
# File lib/frag/app.rb, line 147
def process(input, output)
  region_start = nil
  command = nil
  while (line = input.gets)
    unless region_start
      output.puts line
    end
    case line
    when @state.begin_line
      region_start.nil? or
        return error "#{input.lineno}: nested region"
      command = $1
      region_start = input.lineno
    when @state.end_line
      region_start or
        return error "#{input.lineno}: unmatched end delimiter"
      output.puts `#{command}`
      if !$?.success?
        return error "#{region_start}: failed: (#{$?.exitstatus}) #{command}"
      end
      region_start = nil
      output.puts line
    when /\A\s*(?:(\S+)\s*)?\$frag-config:\s*(.*)$/
      args = Shellwords.shellsplit($2)
      begin
        parser.parse_subconfig!(args)
      rescue OptionParser::InvalidOption => e
        return error "#{input.lineno}: #{e.message}"
      end
      args.size <= 1 or
        return error "#{input.lineno}: unexpected argument(s): #{args[0..-2].join(' ')}"
      @state.leader = $1 || ''
      @state.trailer = args.first || ''
    end
  end
  if region_start
    return error "#{region_start}: unmatched begin delimiter"
  end
  true
end
warn(message) click to toggle source
# File lib/frag/app.rb, line 48
def warn(message)
  @error.puts "warning: #{message}"
end