rouge 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,13 +2,16 @@
2
2
 
3
3
  require 'pathname'
4
4
  ROOT_DIR = Pathname.new(__FILE__).dirname.parent
5
- $:.push(ROOT_DIR.join('lib'))
5
+ $:.push(ROOT_DIR.join('lib').to_s)
6
6
  load ROOT_DIR.join('lib/rouge.rb')
7
7
  load ROOT_DIR.join('lib/rouge/cli.rb')
8
8
 
9
9
  begin
10
- Rouge::CLI.start
11
- rescue => e
12
- $stderr.puts "rouge: #{e.message}"
13
- exit 1
10
+ Rouge::CLI.parse(ARGV).run
11
+ rescue Rouge::CLI::Error => e
12
+ puts e.message
13
+ exit e.status
14
+ rescue Interrupt
15
+ $stderr.puts "\nrouge: interrupted"
16
+ exit 2
14
17
  end
@@ -1,100 +1,333 @@
1
1
  # not required by the main lib.
2
2
  # to use this module, require 'rouge/cli'.
3
3
 
4
- # stdlib
5
- require 'optparse'
4
+ module Rouge
5
+ class FileReader
6
+ attr_reader :input
7
+ def initialize(input)
8
+ @input = input
9
+ end
6
10
 
7
- # gems
8
- require 'thor'
11
+ def file
12
+ case input
13
+ when '-'
14
+ $stdin
15
+ when String
16
+ File.new(input)
17
+ when ->(i){ i.respond_to? :read }
18
+ input
19
+ end
20
+ end
9
21
 
10
- module Rouge
11
- class CLI < Thor
12
- default_task :highlight
22
+ def read
23
+ @read ||= begin
24
+ file.read
25
+ rescue => e
26
+ $stderr.puts "unable to open #{input}: #{e.message}"
27
+ exit 1
28
+ ensure
29
+ file.close
30
+ end
31
+ end
32
+ end
33
+
34
+ class CLI
35
+ def self.doc
36
+ return enum_for(:doc) unless block_given?
37
+
38
+ yield %|usage: rougify [command] [args...]|
39
+ yield %||
40
+ yield %|where <command> is one of:|
41
+ yield %| highlight #{Highlight.desc}|
42
+ yield %| help #{Help.desc}|
43
+ yield %| style #{Style.desc}|
44
+ yield %| list #{List.desc}|
45
+ yield %||
46
+ yield %|See `rougify help <command>` for more info.|
47
+ end
48
+
49
+ class Error < StandardError
50
+ attr_reader :message, :status
51
+ def initialize(message, status=1)
52
+ @message = message
53
+ @status = status
54
+ end
55
+ end
56
+
57
+ def self.parse(argv=ARGV)
58
+ argv = normalize_syntax(argv)
59
+
60
+ mode = argv.shift
61
+
62
+ klass = class_from_arg(mode)
63
+ return klass.parse(argv) if klass
64
+
65
+ case mode
66
+ when '-h', '--help', 'help', '-help'
67
+ Help.parse(argv)
68
+ else
69
+ argv.unshift(mode) if mode
70
+ Highlight.parse(argv)
71
+ end
72
+ end
13
73
 
14
- def self.start(argv=ARGV, *a)
15
- if argv.include? '-v' or argv.include? '--version'
16
- puts Rouge.version
17
- exit 0
74
+ def initialize(options={})
75
+ end
76
+
77
+ def error!(msg, status=1)
78
+ raise Error.new(msg, status)
79
+ end
80
+
81
+ def self.class_from_arg(arg)
82
+ case arg
83
+ when 'help'
84
+ Help
85
+ when 'highlight', 'hi'
86
+ Highlight
87
+ when 'style'
88
+ Style
89
+ when 'list'
90
+ List
18
91
  end
92
+ end
19
93
 
20
- unless %w(highlight style list --help -h help).include?(argv.first)
21
- argv.unshift 'highlight'
94
+ class Help < CLI
95
+ def self.desc
96
+ "print help info"
97
+ end
98
+
99
+ def self.doc
100
+ return enum_for(:doc) unless block_given?
101
+
102
+ yield %|usage: rougify help <command>|
103
+ yield %||
104
+ yield %|print help info for <command>.|
105
+ end
106
+
107
+ def self.parse(argv)
108
+ opts = { :mode => CLI }
109
+ until argv.empty?
110
+ arg = argv.shift
111
+ klass = class_from_arg(arg)
112
+ if klass
113
+ opts[:mode] = klass
114
+ next
115
+ end
116
+ end
117
+ new(opts)
22
118
  end
23
119
 
24
- super(argv, *a)
120
+ def initialize(opts={})
121
+ @mode = opts[:mode]
122
+ end
123
+
124
+ def run
125
+ @mode.doc.each(&method(:puts))
126
+ end
25
127
  end
26
128
 
27
- desc 'highlight [FILE]', 'highlight some code'
28
- option :input_file, :aliases => '-i', :desc => 'the file to operate on'
29
- option :lexer, :aliases => '-l',
30
- :desc => ('Which lexer to use. If not provided, rougify will try to ' +
31
- 'guess based on --mimetype, the filename, and the file ' +
32
- 'contents.')
33
- option :formatter, :aliases => '-f', :default => 'terminal256',
34
- :desc => ('Which formatter to use.')
35
- option :mimetype, :aliases => '-m',
36
- :desc => ('a mimetype that Rouge will use to guess the correct lexer. ' +
37
- 'This is ignored if --lexer is specified.')
38
- option :lexer_opts, :aliases => '-L', :type => :hash, :default => {},
39
- :desc => ('a hash of options to pass to the lexer.')
40
- option :formatter_opts, :aliases => '-F', :type => :hash, :default => {},
41
- :desc => ('a hash of options to pass to the formatter.')
42
- def highlight(file=nil)
43
- filename = options[:file] || file
44
- source = filename ? File.read(filename) : $stdin.read
45
-
46
- if options[:lexer].nil?
47
- lexer_class = Lexer.guess(
48
- :filename => filename,
49
- :mimetype => options[:mimetype],
50
- :source => source,
129
+ class Highlight < CLI
130
+ def self.desc
131
+ "highlight code"
132
+ end
133
+
134
+ def self.doc
135
+ return enum_for(:doc) unless block_given?
136
+
137
+ yield %[usage: rougify highlight <filename> [options...]]
138
+ yield %[ rougify highlight [options...]]
139
+ yield %[]
140
+ yield %[--input-file|-i <filename> specify a file to read, or - to use stdin]
141
+ yield %[]
142
+ yield %[--lexer|-l <lexer> specify the lexer to use.]
143
+ yield %[ If not provided, rougify will try to guess]
144
+ yield %[ based on --mimetype, the filename, and the]
145
+ yield %[ file contents.]
146
+ yield %[]
147
+ yield %[--mimetype|-m <mimetype> specify a mimetype for lexer guessing]
148
+ yield %[]
149
+ yield %[--lexer-opts|-L <opts> specify lexer options in CGI format]
150
+ yield %[ (opt1=val1&opt2=val2)]
151
+ yield %[]
152
+ yield %[--formatter-opts|-F <opts> specify formatter options in CGI format]
153
+ yield %[ (opt1=val1&opt2=val2)]
154
+ end
155
+
156
+ def self.parse(argv)
157
+ opts = {
158
+ :formatter => 'terminal256',
159
+ :input_file => '-',
160
+ :lexer_opts => {},
161
+ :formatter_opts => {},
162
+ }
163
+
164
+ until argv.empty?
165
+ arg = argv.shift
166
+ case arg
167
+ when '--input-file', '-i'
168
+ opts[:input_file] = argv.shift
169
+ when '--mimetype', '-m'
170
+ opts[:mimetype] = argv.shift
171
+ when '--lexer', '-l'
172
+ opts[:lexer] = argv.shift
173
+ when '--formatter', '-f'
174
+ opts[:formatter] = argv.shift
175
+ when '--lexer-opts', '-L'
176
+ opts[:lexer_opts] = parse_cgi(argv.shift)
177
+ when '--formatter-opts', '-F'
178
+ opts[:formatter_opts] = parse_cgi(argv.shift)
179
+ when /^--/
180
+ error! "unknown option #{arg.inspect}"
181
+ else
182
+ opts[:input_file] = arg
183
+ end
184
+ end
185
+
186
+ new(opts)
187
+ end
188
+
189
+ def input_stream
190
+ @input_stream ||= FileReader.new(@input_file)
191
+ end
192
+
193
+ def input
194
+ @input ||= input_stream.read
195
+ end
196
+
197
+ def lexer_class
198
+ @lexer_class ||= Lexer.guess(
199
+ :filename => @input_file,
200
+ :mimetype => @mimetype,
201
+ :source => input_stream,
51
202
  )
52
- else
53
- lexer_class = Lexer.find(options[:lexer])
54
- raise "unknown lexer: #{options[:lexer]}" unless lexer_class
55
203
  end
56
204
 
57
- formatter_class = Formatter.find(options[:formatter])
205
+ def lexer
206
+ @lexer ||= lexer_class.new(@lexer_opts)
207
+ end
58
208
 
59
- # only HTML is supported for now
60
- formatter = formatter_class.new(normalize_hash_keys(options[:formatter_opts]))
61
- lexer = lexer_class.new(normalize_hash_keys(options[:lexer_opts]))
209
+ attr_reader :input_file, :lexer_name, :mimetype, :formatter
62
210
 
63
- formatter.format(lexer.lex(source), &method(:print))
211
+ def initialize(opts={})
212
+ @input_file = opts[:input_file]
213
+
214
+ if opts[:lexer]
215
+ @lexer_class = Lexer.find(opts[:lexer]) \
216
+ or error! "unkown lexer #{opts[:lexer].inspect}"
217
+ else
218
+ @lexer_name = opts[:lexer]
219
+ @mimetype = opts[:mimetype]
220
+ end
221
+
222
+ @lexer_opts = opts[:lexer_opts]
223
+
224
+ formatter_class = Formatter.find(opts[:formatter]) \
225
+ or error! "unknown formatter #{opts[:formatter]}"
226
+
227
+ @formatter = formatter_class.new(opts[:formatter_opts])
228
+ end
229
+
230
+ def run
231
+ formatter.format(lexer.lex(input)) do |chunk|
232
+ print chunk
233
+ end
234
+ end
235
+
236
+ private
237
+ def self.parse_cgi(str)
238
+ pairs = CGI.parse(str).map { |k, v| v.first }
239
+ Hash[pairs]
240
+ end
64
241
  end
65
242
 
66
- desc 'style THEME', 'render THEME as css'
67
- option :scope, :desc => "a css selector to scope the styles to"
68
- def style(theme_name='thankful_eyes')
69
- theme = Theme.find(theme_name)
70
- raise "unknown theme: #{theme_name}" unless theme
243
+ class Style < CLI
244
+ def self.desc
245
+ "print CSS styles"
246
+ end
71
247
 
72
- theme.new(options).render(&method(:puts))
248
+ def self.doc
249
+ return enum_for(:doc) unless block_given?
250
+
251
+ yield %|usage: rougify style [<theme-name>] [<options>]|
252
+ yield %||
253
+ yield %|Print CSS styles for the given theme. Extra options are|
254
+ yield %|passed to the theme. Theme defaults to thankful_eyes.|
255
+ yield %||
256
+ yield %|options:|
257
+ yield %| --scope (default: .highlight) a css selector to scope by|
258
+ end
259
+
260
+ def self.parse(argv)
261
+ opts = { :theme_name => 'thankful_eyes' }
262
+
263
+ until argv.empty?
264
+ arg = argv.shift
265
+ case arg
266
+ when /--(\w+)/
267
+ opts[$1.tr('-', '_').to_sym] = argv.shift
268
+ else
269
+ opts[:theme_name] = arg
270
+ end
271
+ end
272
+
273
+ new(opts)
274
+ end
275
+
276
+ def initialize(opts)
277
+ theme_class = Theme.find(opts.delete(:theme_name)) \
278
+ or error! "unknown theme: #{theme_name}"
279
+
280
+ @theme = theme_class.new(opts)
281
+ end
282
+
283
+ def run
284
+ @theme.render(&method(:puts))
285
+ end
73
286
  end
74
287
 
75
- desc 'list', 'list the available lexers, formatters, and styles'
76
- def list
77
- puts "== Available Lexers =="
78
- all_lexers = Lexer.all
79
- max_len = all_lexers.map { |l| l.tag.size }.max
288
+ class List < CLI
289
+ def self.desc
290
+ "list available lexers"
291
+ end
292
+
293
+ def self.doc
294
+ return enum_for(:doc) unless block_given?
295
+
296
+ yield %|usage: rouge list|
297
+ yield %||
298
+ yield %|print a list of all available lexers with their descriptions.|
299
+ end
300
+
301
+ def self.parse(argv)
302
+ new
303
+ end
80
304
 
81
- Lexer.all.each do |lexer|
82
- desc = "#{lexer.desc}"
83
- if lexer.aliases.any?
84
- desc << " [aliases: #{lexer.aliases.join(',')}]"
305
+ def run
306
+ puts "== Available Lexers =="
307
+
308
+ Lexer.all.each do |lexer|
309
+ desc = "#{lexer.desc}"
310
+ if lexer.aliases.any?
311
+ desc << " [aliases: #{lexer.aliases.join(',')}]"
312
+ end
313
+ puts "%s: %s" % [lexer.tag, desc]
314
+ puts
85
315
  end
86
- puts "%s: %s" % [lexer.tag, desc]
87
- puts
88
316
  end
89
317
  end
90
318
 
91
319
  private
92
- # TODO: does Thor do this for me?
93
- def normalize_hash_keys(hash)
94
- out = {}
95
- hash.each do |k, v|
96
- new_key = k.tr('-', '_').to_sym
97
- out[new_key] = v
320
+ def self.normalize_syntax(argv)
321
+ out = []
322
+ argv.each do |arg|
323
+ case arg
324
+ when /^(--\w+)=(.*)$/
325
+ out << $1 << $2
326
+ when /^(-\w)(.+)$/
327
+ out << $1 << $2
328
+ else
329
+ out << arg
330
+ end
98
331
  end
99
332
 
100
333
  out
@@ -163,7 +163,8 @@ module Rouge
163
163
 
164
164
  private
165
165
  def filter_by_mimetype(lexers, mt)
166
- lexers.select { |lexer| lexer.mimetypes.include? mt }
166
+ filtered = lexers.select { |lexer| lexer.mimetypes.include? mt }
167
+ filtered.any? ? filtered : lexers
167
168
  end
168
169
 
169
170
  # returns a list of lexers that match the given filename with
@@ -195,10 +196,19 @@ module Rouge
195
196
  end
196
197
  end
197
198
 
198
- out
199
+ out.any? ? out : lexers
199
200
  end
200
201
 
201
202
  def best_by_source(lexers, source, threshold=0)
203
+ source = case source
204
+ when String
205
+ source
206
+ when ->(s){ s.respond_to? :read }
207
+ source.read
208
+ else
209
+ raise 'invalid source'
210
+ end
211
+
202
212
  assert_utf8!(source)
203
213
 
204
214
  source = TextAnalyzer.new(source)
@@ -67,6 +67,8 @@ module Rouge
67
67
  rule(//) { pop! }
68
68
  end
69
69
 
70
+ # :expr_bol is the same as :bol but without labels, since
71
+ # labels can only appear at the beginning of a statement.
70
72
  state :bol do
71
73
  rule /#{id}:(?!:)/, Name::Label
72
74
  mixin :expr_bol
@@ -1,4 +1,4 @@
1
- require Pathname.new(__FILE__).dirname.join('c.rb')
1
+ require 'rouge/lexers/c'
2
2
 
3
3
  module Rouge
4
4
  module Lexers
@@ -90,12 +90,17 @@ module Rouge
90
90
  end
91
91
 
92
92
  state :table do
93
- rule(/^(?=\s*[^\s|])/) { reset_stack }
94
93
  mixin :basic
94
+ rule /\n/, Text, :table_bol
95
95
  rule /[|]/, Punctuation
96
96
  rule /[^|\s]+/, Name
97
97
  end
98
98
 
99
+ state :table_bol do
100
+ rule(/(?=\s*[^\s|])/) { reset_stack }
101
+ rule(//) { pop! }
102
+ end
103
+
99
104
  state :description do
100
105
  mixin :basic
101
106
  mixin :has_examples
@@ -8,6 +8,14 @@ module Rouge
8
8
  @methods ||= %w(GET POST PUT DELETE HEAD OPTIONS TRACE)
9
9
  end
10
10
 
11
+ def content_lexer
12
+ return Lexers::PlainText unless @content_type
13
+
14
+ @content_lexer ||= Lexer.guess_by_mimetype(@content_type)
15
+ rescue Lexer::AmbiguousGuess
16
+ @content_lexer = Lexers::PlainText
17
+ end
18
+
11
19
  start { @content_type = 'text/plain' }
12
20
 
13
21
  state :root do
@@ -59,8 +67,8 @@ module Rouge
59
67
  end
60
68
 
61
69
  state :content do
62
- rule /.+/ do |m|
63
- delegate Lexer.guess_by_mimetype(@content_type)
70
+ rule /.+/m do |m|
71
+ delegate(content_lexer)
64
72
  end
65
73
  end
66
74
  end
@@ -1,4 +1,4 @@
1
- require Pathname.new(__FILE__).dirname.join('c.rb')
1
+ require 'rouge/lexers/c'
2
2
 
3
3
  module Rouge
4
4
  module Lexers
@@ -133,6 +133,8 @@ module Rouge
133
133
  end
134
134
 
135
135
  state :forward_classname do
136
+ mixin :whitespace
137
+
136
138
  rule /(#{id})(\s*)(,)(\s*)/ do
137
139
  groups(Name::Class, Text, Punctuation, Text)
138
140
  push
@@ -163,7 +163,7 @@ module Rouge
163
163
  end
164
164
 
165
165
  def single_css_selector(token)
166
- return @scope if token == Token['Text']
166
+ return @scope if token == Text
167
167
 
168
168
  "#{@scope} .#{token.shortname}"
169
169
  end
@@ -1,5 +1,5 @@
1
1
  module Rouge
2
2
  def self.version
3
- "0.5.3"
3
+ "0.5.4"
4
4
  end
5
5
  end
@@ -15,6 +15,4 @@ Gem::Specification.new do |s|
15
15
  s.files = Dir['Gemfile', 'LICENSE', 'rouge.gemspec', 'lib/**/*.rb', 'bin/rougify', 'lib/rouge/demos/*']
16
16
  s.executables = %w(rougify)
17
17
  s.license = 'MIT (see LICENSE file)'
18
-
19
- s.add_dependency 'thor'
20
18
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rouge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-09-17 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- type: :runtime
16
- name: thor
17
- prerelease: false
18
- requirement: !ruby/object:Gem::Requirement
19
- requirements:
20
- - - ! '>='
21
- - !ruby/object:Gem::Version
22
- version: '0'
23
- none: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ! '>='
27
- - !ruby/object:Gem::Version
28
- version: '0'
29
- none: false
12
+ date: 2013-09-22 00:00:00.000000000 Z
13
+ dependencies: []
30
14
  description: Rouge aims to a be a simple, easy-to-extend drop-in replacement for pygments.
31
15
  email:
32
16
  - jjmadkisson@gmail.com