lib/rouge/cli.rb in rouge-0.5.3 vs lib/rouge/cli.rb in rouge-0.5.4
- old
+ new
@@ -1,101 +1,334 @@
# not required by the main lib.
# to use this module, require 'rouge/cli'.
-# stdlib
-require 'optparse'
+module Rouge
+ class FileReader
+ attr_reader :input
+ def initialize(input)
+ @input = input
+ end
-# gems
-require 'thor'
+ def file
+ case input
+ when '-'
+ $stdin
+ when String
+ File.new(input)
+ when ->(i){ i.respond_to? :read }
+ input
+ end
+ end
-module Rouge
- class CLI < Thor
- default_task :highlight
+ def read
+ @read ||= begin
+ file.read
+ rescue => e
+ $stderr.puts "unable to open #{input}: #{e.message}"
+ exit 1
+ ensure
+ file.close
+ end
+ end
+ end
- def self.start(argv=ARGV, *a)
- if argv.include? '-v' or argv.include? '--version'
- puts Rouge.version
- exit 0
+ class CLI
+ def self.doc
+ return enum_for(:doc) unless block_given?
+
+ yield %|usage: rougify [command] [args...]|
+ yield %||
+ yield %|where <command> is one of:|
+ yield %| highlight #{Highlight.desc}|
+ yield %| help #{Help.desc}|
+ yield %| style #{Style.desc}|
+ yield %| list #{List.desc}|
+ yield %||
+ yield %|See `rougify help <command>` for more info.|
+ end
+
+ class Error < StandardError
+ attr_reader :message, :status
+ def initialize(message, status=1)
+ @message = message
+ @status = status
end
+ end
- unless %w(highlight style list --help -h help).include?(argv.first)
- argv.unshift 'highlight'
+ def self.parse(argv=ARGV)
+ argv = normalize_syntax(argv)
+
+ mode = argv.shift
+
+ klass = class_from_arg(mode)
+ return klass.parse(argv) if klass
+
+ case mode
+ when '-h', '--help', 'help', '-help'
+ Help.parse(argv)
+ else
+ argv.unshift(mode) if mode
+ Highlight.parse(argv)
end
+ end
- super(argv, *a)
+ def initialize(options={})
end
- desc 'highlight [FILE]', 'highlight some code'
- option :input_file, :aliases => '-i', :desc => 'the file to operate on'
- option :lexer, :aliases => '-l',
- :desc => ('Which lexer to use. If not provided, rougify will try to ' +
- 'guess based on --mimetype, the filename, and the file ' +
- 'contents.')
- option :formatter, :aliases => '-f', :default => 'terminal256',
- :desc => ('Which formatter to use.')
- option :mimetype, :aliases => '-m',
- :desc => ('a mimetype that Rouge will use to guess the correct lexer. ' +
- 'This is ignored if --lexer is specified.')
- option :lexer_opts, :aliases => '-L', :type => :hash, :default => {},
- :desc => ('a hash of options to pass to the lexer.')
- option :formatter_opts, :aliases => '-F', :type => :hash, :default => {},
- :desc => ('a hash of options to pass to the formatter.')
- def highlight(file=nil)
- filename = options[:file] || file
- source = filename ? File.read(filename) : $stdin.read
+ def error!(msg, status=1)
+ raise Error.new(msg, status)
+ end
- if options[:lexer].nil?
- lexer_class = Lexer.guess(
- :filename => filename,
- :mimetype => options[:mimetype],
- :source => source,
+ def self.class_from_arg(arg)
+ case arg
+ when 'help'
+ Help
+ when 'highlight', 'hi'
+ Highlight
+ when 'style'
+ Style
+ when 'list'
+ List
+ end
+ end
+
+ class Help < CLI
+ def self.desc
+ "print help info"
+ end
+
+ def self.doc
+ return enum_for(:doc) unless block_given?
+
+ yield %|usage: rougify help <command>|
+ yield %||
+ yield %|print help info for <command>.|
+ end
+
+ def self.parse(argv)
+ opts = { :mode => CLI }
+ until argv.empty?
+ arg = argv.shift
+ klass = class_from_arg(arg)
+ if klass
+ opts[:mode] = klass
+ next
+ end
+ end
+ new(opts)
+ end
+
+ def initialize(opts={})
+ @mode = opts[:mode]
+ end
+
+ def run
+ @mode.doc.each(&method(:puts))
+ end
+ end
+
+ class Highlight < CLI
+ def self.desc
+ "highlight code"
+ end
+
+ def self.doc
+ return enum_for(:doc) unless block_given?
+
+ yield %[usage: rougify highlight <filename> [options...]]
+ yield %[ rougify highlight [options...]]
+ yield %[]
+ yield %[--input-file|-i <filename> specify a file to read, or - to use stdin]
+ yield %[]
+ yield %[--lexer|-l <lexer> specify the lexer to use.]
+ yield %[ If not provided, rougify will try to guess]
+ yield %[ based on --mimetype, the filename, and the]
+ yield %[ file contents.]
+ yield %[]
+ yield %[--mimetype|-m <mimetype> specify a mimetype for lexer guessing]
+ yield %[]
+ yield %[--lexer-opts|-L <opts> specify lexer options in CGI format]
+ yield %[ (opt1=val1&opt2=val2)]
+ yield %[]
+ yield %[--formatter-opts|-F <opts> specify formatter options in CGI format]
+ yield %[ (opt1=val1&opt2=val2)]
+ end
+
+ def self.parse(argv)
+ opts = {
+ :formatter => 'terminal256',
+ :input_file => '-',
+ :lexer_opts => {},
+ :formatter_opts => {},
+ }
+
+ until argv.empty?
+ arg = argv.shift
+ case arg
+ when '--input-file', '-i'
+ opts[:input_file] = argv.shift
+ when '--mimetype', '-m'
+ opts[:mimetype] = argv.shift
+ when '--lexer', '-l'
+ opts[:lexer] = argv.shift
+ when '--formatter', '-f'
+ opts[:formatter] = argv.shift
+ when '--lexer-opts', '-L'
+ opts[:lexer_opts] = parse_cgi(argv.shift)
+ when '--formatter-opts', '-F'
+ opts[:formatter_opts] = parse_cgi(argv.shift)
+ when /^--/
+ error! "unknown option #{arg.inspect}"
+ else
+ opts[:input_file] = arg
+ end
+ end
+
+ new(opts)
+ end
+
+ def input_stream
+ @input_stream ||= FileReader.new(@input_file)
+ end
+
+ def input
+ @input ||= input_stream.read
+ end
+
+ def lexer_class
+ @lexer_class ||= Lexer.guess(
+ :filename => @input_file,
+ :mimetype => @mimetype,
+ :source => input_stream,
)
- else
- lexer_class = Lexer.find(options[:lexer])
- raise "unknown lexer: #{options[:lexer]}" unless lexer_class
end
- formatter_class = Formatter.find(options[:formatter])
+ def lexer
+ @lexer ||= lexer_class.new(@lexer_opts)
+ end
- # only HTML is supported for now
- formatter = formatter_class.new(normalize_hash_keys(options[:formatter_opts]))
- lexer = lexer_class.new(normalize_hash_keys(options[:lexer_opts]))
+ attr_reader :input_file, :lexer_name, :mimetype, :formatter
- formatter.format(lexer.lex(source), &method(:print))
+ def initialize(opts={})
+ @input_file = opts[:input_file]
+
+ if opts[:lexer]
+ @lexer_class = Lexer.find(opts[:lexer]) \
+ or error! "unkown lexer #{opts[:lexer].inspect}"
+ else
+ @lexer_name = opts[:lexer]
+ @mimetype = opts[:mimetype]
+ end
+
+ @lexer_opts = opts[:lexer_opts]
+
+ formatter_class = Formatter.find(opts[:formatter]) \
+ or error! "unknown formatter #{opts[:formatter]}"
+
+ @formatter = formatter_class.new(opts[:formatter_opts])
+ end
+
+ def run
+ formatter.format(lexer.lex(input)) do |chunk|
+ print chunk
+ end
+ end
+
+ private
+ def self.parse_cgi(str)
+ pairs = CGI.parse(str).map { |k, v| v.first }
+ Hash[pairs]
+ end
end
- desc 'style THEME', 'render THEME as css'
- option :scope, :desc => "a css selector to scope the styles to"
- def style(theme_name='thankful_eyes')
- theme = Theme.find(theme_name)
- raise "unknown theme: #{theme_name}" unless theme
+ class Style < CLI
+ def self.desc
+ "print CSS styles"
+ end
- theme.new(options).render(&method(:puts))
+ def self.doc
+ return enum_for(:doc) unless block_given?
+
+ yield %|usage: rougify style [<theme-name>] [<options>]|
+ yield %||
+ yield %|Print CSS styles for the given theme. Extra options are|
+ yield %|passed to the theme. Theme defaults to thankful_eyes.|
+ yield %||
+ yield %|options:|
+ yield %| --scope (default: .highlight) a css selector to scope by|
+ end
+
+ def self.parse(argv)
+ opts = { :theme_name => 'thankful_eyes' }
+
+ until argv.empty?
+ arg = argv.shift
+ case arg
+ when /--(\w+)/
+ opts[$1.tr('-', '_').to_sym] = argv.shift
+ else
+ opts[:theme_name] = arg
+ end
+ end
+
+ new(opts)
+ end
+
+ def initialize(opts)
+ theme_class = Theme.find(opts.delete(:theme_name)) \
+ or error! "unknown theme: #{theme_name}"
+
+ @theme = theme_class.new(opts)
+ end
+
+ def run
+ @theme.render(&method(:puts))
+ end
end
- desc 'list', 'list the available lexers, formatters, and styles'
- def list
- puts "== Available Lexers =="
- all_lexers = Lexer.all
- max_len = all_lexers.map { |l| l.tag.size }.max
+ class List < CLI
+ def self.desc
+ "list available lexers"
+ end
- Lexer.all.each do |lexer|
- desc = "#{lexer.desc}"
- if lexer.aliases.any?
- desc << " [aliases: #{lexer.aliases.join(',')}]"
+ def self.doc
+ return enum_for(:doc) unless block_given?
+
+ yield %|usage: rouge list|
+ yield %||
+ yield %|print a list of all available lexers with their descriptions.|
+ end
+
+ def self.parse(argv)
+ new
+ end
+
+ def run
+ puts "== Available Lexers =="
+
+ Lexer.all.each do |lexer|
+ desc = "#{lexer.desc}"
+ if lexer.aliases.any?
+ desc << " [aliases: #{lexer.aliases.join(',')}]"
+ end
+ puts "%s: %s" % [lexer.tag, desc]
+ puts
end
- puts "%s: %s" % [lexer.tag, desc]
- puts
end
end
private
- # TODO: does Thor do this for me?
- def normalize_hash_keys(hash)
- out = {}
- hash.each do |k, v|
- new_key = k.tr('-', '_').to_sym
- out[new_key] = v
+ def self.normalize_syntax(argv)
+ out = []
+ argv.each do |arg|
+ case arg
+ when /^(--\w+)=(.*)$/
+ out << $1 << $2
+ when /^(-\w)(.+)$/
+ out << $1 << $2
+ else
+ out << arg
+ end
end
out
end
end