rouge 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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