mdless 2.0.17 → 2.0.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,33 +4,33 @@ require 'yaml'
4
4
  module CLIMarkdown
5
5
  class Converter
6
6
  include Colors
7
- include Theme
8
-
9
- attr_reader :helpers, :log
10
7
 
11
8
  def version
12
9
  "#{CLIMarkdown::EXECUTABLE_NAME} #{CLIMarkdown::VERSION}"
13
10
  end
14
11
 
12
+ def default(option, default)
13
+ MDLess.options[option] = default if MDLess.options[option].nil?
14
+ end
15
+
15
16
  def initialize(args)
16
- @log = Logger.new($stderr)
17
- @log.level = Logger::WARN
17
+ MDLess.log.level = Logger::WARN
18
18
 
19
- @options = {}
19
+ MDLess.options = {}
20
20
  config = File.expand_path('~/.config/mdless/config.yml')
21
- @options = YAML.load(IO.read(config)) if File.exist?(config)
21
+ MDLess.options = YAML.load(IO.read(config)) if File.exist?(config)
22
22
 
23
23
  optparse = OptionParser.new do |opts|
24
24
  opts.banner = "#{version} by Brett Terpstra\n\n> Usage: #{CLIMarkdown::EXECUTABLE_NAME} [options] [path]\n\n"
25
25
 
26
- @options[:color] ||= true
26
+ default(:color, true)
27
27
  opts.on('-c', '--[no-]color', 'Colorize output (default on)') do |c|
28
- @options[:color] = c
28
+ MDLess.options[:color] = c
29
29
  end
30
30
 
31
31
  opts.on('-d', '--debug LEVEL', 'Level of debug messages to output (1-4, 4 to see all messages)') do |level|
32
32
  if level.to_i.positive? && level.to_i < 5
33
- @log.level = 5 - level.to_i
33
+ MDLess.log.level = 5 - level.to_i
34
34
  else
35
35
  puts 'Error: Debug level out of range (1-4)'
36
36
  Process.exit 1
@@ -42,64 +42,71 @@ module CLIMarkdown
42
42
  exit
43
43
  end
44
44
 
45
- @options[:local_images] ||= false
46
- @options[:remote_images] ||= false
45
+ default(:local_images, false)
46
+ default(:remote_images, false)
47
47
  opts.on('-i', '--images=TYPE',
48
48
  'Include [local|remote (both)|none] images in output (requires chafa or imgcat, default none).') do |type|
49
49
  if exec_available('imgcat') || exec_available('chafa')
50
50
  case type
51
51
  when /^(r|b|a)/i
52
- @options[:local_images] = true
53
- @options[:remote_images] = true
52
+ MDLess.options[:local_images] = true
53
+ MDLess.options[:remote_images] = true
54
54
  when /^l/i
55
- @options[:local_images] = true
55
+ MDLess.options[:local_images] = true
56
56
  when /^n/
57
- @options[:local_images] = false
58
- @options[:remote_images] = false
57
+ MDLess.options[:local_images] = false
58
+ MDLess.options[:remote_images] = false
59
59
  end
60
60
  else
61
- @log.warn('images turned on but imgcat/chafa not found')
61
+ MDLess.log.warn('images turned on but imgcat/chafa not found')
62
62
  end
63
63
  end
64
64
 
65
65
  opts.on('-I', '--all-images', 'Include local and remote images in output (requires imgcat or chafa)') do
66
66
  if exec_available('imgcat') || exec_available('chafa') # && ENV['TERM_PROGRAM'] == 'iTerm.app'
67
- @options[:local_images] = true
68
- @options[:remote_images] = true
67
+ MDLess.options[:local_images] = true
68
+ MDLess.options[:remote_images] = true
69
69
  else
70
- @log.warn('images turned on but imgcat/chafa not found')
70
+ MDLess.log.warn('images turned on but imgcat/chafa not found')
71
71
  end
72
72
  end
73
73
 
74
- @options[:list] ||= false
74
+ default(:list, false)
75
75
  opts.on('-l', '--list', 'List headers in document and exit') do
76
- @options[:list] = true
76
+ MDLess.options[:list] = true
77
77
  end
78
78
 
79
- @options[:pager] ||= true
79
+ default(:pager, true)
80
80
  opts.on('-p', '--[no-]pager', 'Formatted output to pager (default on)') do |p|
81
- @options[:pager] = p
81
+ MDLess.options[:pager] = p
82
82
  end
83
83
 
84
- @options[:pager] ||= true
84
+ default(:pager, true)
85
85
  opts.on('-P', 'Disable pager (same as --no-pager)') do
86
- @options[:pager] = false
86
+ MDLess.options[:pager] = false
87
87
  end
88
88
 
89
- @options[:section] ||= nil
89
+ default(:section, nil)
90
90
  opts.on('-s', '--section=NUMBER[,NUMBER]',
91
91
  'Output only a headline-based section of the input (numeric from --list)') do |section|
92
- @options[:section] = section.split(/ *, */).map(&:strip).map(&:to_i)
92
+ sections = section.split(/ *, */).map(&:strip)
93
+ MDLess.options[:section] = sections.map do |sect|
94
+ if sect =~ /^\d+$/
95
+ sect.to_i
96
+ else
97
+ sect
98
+ end
99
+ end
93
100
  end
94
101
 
95
- @options[:theme] ||= 'default'
102
+ default(:theme, 'default')
96
103
  opts.on('-t', '--theme=THEME_NAME', 'Specify an alternate color theme to load') do |theme|
97
- @options[:theme] = theme
104
+ MDLess.options[:theme] = theme
98
105
  end
99
106
 
100
- @options[:at_tags] ||= false
107
+ default(:at_tags, false)
101
108
  opts.on('-@', '--at_tags', 'Highlight @tags and values in the document') do
102
- @options[:at_tags] = true
109
+ MDLess.options[:at_tags] = true
103
110
  end
104
111
 
105
112
  opts.on('-v', '--version', 'Display version number') do
@@ -107,32 +114,39 @@ module CLIMarkdown
107
114
  exit
108
115
  end
109
116
 
110
- @options[:width] = `tput cols`.strip.to_i
117
+ default(:width, TTY::Screen.cols)
111
118
  opts.on('-w', '--width=COLUMNS', 'Column width to format for (default: terminal width)') do |columns|
112
- @options[:width] = columns.to_i
119
+ MDLess.options[:width] = columns.to_i
120
+ end
121
+ cols = TTY::Screen.cols
122
+ MDLess.options[:width] = cols if MDLess.options[:width] > cols
123
+
124
+ default(:autolink, true)
125
+ opts.on('--[no-]autolink', 'Convert bare URLs and emails to <links>') do |p|
126
+ MDLess.options[:autolink] = p
113
127
  end
114
128
 
115
- @options[:inline_footnotes] ||= false
129
+ default(:inline_footnotes, false)
116
130
  opts.on('--[no-]inline_footnotes',
117
131
  'Display footnotes immediately after the paragraph that references them') do |p|
118
- @options[:inline_footnotes] = p
132
+ MDLess.options[:inline_footnotes] = p
119
133
  end
120
134
 
121
- @options[:intra_emphasis] ||= true
135
+ default(:intra_emphasis, true)
122
136
  opts.on('--[no-]intra-emphasis', 'Parse emphasis inside of words (e.g. Mark_down_)') do |opt|
123
- @options[:intra_emphasis] = opt
137
+ MDLess.options[:intra_emphasis] = opt
124
138
  end
125
139
 
126
- @options[:lax_spacing] ||= true
140
+ default(:lax_spacing, true)
127
141
  opts.on('--[no-]lax-spacing', 'Allow lax spacing') do |opt|
128
- @options[:lax_spacing] = opt
142
+ MDLess.options[:lax_spacing] = opt
129
143
  end
130
144
 
131
- @options[:links] ||= :inline
145
+ default(:links, :inline)
132
146
  opts.on('--links=FORMAT',
133
147
  'Link style ([inline, reference, paragraph], default inline,
134
148
  "paragraph" will position reference links after each paragraph)') do |fmt|
135
- @options[:links] = case fmt
149
+ MDLess.options[:links] = case fmt
136
150
  when /^:?r/i
137
151
  :reference
138
152
  when /^:?p/i
@@ -142,13 +156,13 @@ module CLIMarkdown
142
156
  end
143
157
  end
144
158
 
145
- @options[:syntax_higlight] ||= false
159
+ default(:syntax_higlight, false)
146
160
  opts.on('--[no-]syntax', 'Syntax highlight code blocks') do |p|
147
- @options[:syntax_higlight] = p
161
+ MDLess.options[:syntax_higlight] = p
148
162
  end
149
163
 
150
- @options[:taskpaper] = if @options[:taskpaper]
151
- case @options[:taskpaper].to_s
164
+ MDLess.options[:taskpaper] = if MDLess.options[:taskpaper]
165
+ case MDLess.options[:taskpaper].to_s
152
166
  when /^[ty1]/
153
167
  true
154
168
  when /^a/
@@ -160,7 +174,7 @@ module CLIMarkdown
160
174
  false
161
175
  end
162
176
  opts.on('--taskpaper=OPTION', 'Highlight TaskPaper format (true|false|auto)') do |tp|
163
- @options[:taskpaper] = case tp
177
+ MDLess.options[:taskpaper] = case tp
164
178
  when /^[ty1]/
165
179
  true
166
180
  when /^a/
@@ -170,14 +184,14 @@ module CLIMarkdown
170
184
  end
171
185
  end
172
186
 
173
- @options[:update_config] ||= false
187
+ default(:update_config, false)
174
188
  opts.on('--update_config', 'Update the configuration file with new keys and current command line options') do
175
- @options[:update_config] = true
189
+ MDLess.options[:update_config] = true
176
190
  end
177
191
 
178
- @options[:wiki_links] ||= false
192
+ default(:wiki_links, false)
179
193
  opts.on('--[no-]wiki-links', 'Highlight [[wiki links]]') do |opt|
180
- @options[:wiki_links] = opt
194
+ MDLess.options[:wiki_links] = opt
181
195
  end
182
196
  end
183
197
 
@@ -188,20 +202,21 @@ module CLIMarkdown
188
202
  exit 1
189
203
  end
190
204
 
191
- if !File.exist?(config) || @options[:update_config]
205
+ if !File.exist?(config) || MDLess.options[:update_config]
192
206
  FileUtils.mkdir_p(File.dirname(config))
193
207
  File.open(config, 'w') do |f|
194
- opts = @options.dup
208
+ opts = MDLess.options.dup
195
209
  opts.delete(:list)
196
210
  opts.delete(:section)
197
211
  opts.delete(:update_config)
212
+ opts = opts.keys.map(&:to_s).sort.map { |k| [k.to_sym, opts[k.to_sym]] }.to_h
198
213
  f.puts YAML.dump(opts)
199
214
  warn "Config file saved to #{config}"
200
215
  end
201
216
  end
202
217
 
203
- @theme = load_theme(@options[:theme])
204
- @cols = @options[:width] - 2
218
+ MDLess.cols = MDLess.options[:width] - 2
219
+
205
220
  @output = ''
206
221
  @headers = []
207
222
  @setheaders = []
@@ -211,19 +226,15 @@ module CLIMarkdown
211
226
  @footnotes = {}
212
227
 
213
228
  renderer = Redcarpet::Render::Console.new
214
- renderer.theme = @theme
215
- renderer.cols = @cols
216
- renderer.log = @log
217
- renderer.options = @options
218
229
 
219
230
  markdown = Redcarpet::Markdown.new(renderer,
220
- no_intra_emphasis: !@options[:intra_emphasis],
221
- autolink: true,
231
+ no_intra_emphasis: !MDLess.options[:intra_emphasis],
232
+ autolink: MDLess.options[:autolink],
222
233
  fenced_code_blocks: true,
223
234
  footnotes: true,
224
235
  hard_wrap: false,
225
236
  highlight: true,
226
- lax_spacing: @options[:lax_spacing],
237
+ lax_spacing: MDLess.options[:lax_spacing],
227
238
  quote: false,
228
239
  space_after_headers: false,
229
240
  strikethrough: true,
@@ -234,9 +245,9 @@ module CLIMarkdown
234
245
  if !args.empty?
235
246
  files = args.delete_if { |f| !File.exist?(f) }
236
247
  files.each do |file|
237
- @log.info(%(Processing "#{file}"))
238
- @file = file
239
- renderer.file = @file
248
+ MDLess.log.info(%(Processing "#{file}"))
249
+ MDLess.file = file
250
+
240
251
  begin
241
252
  input = IO.read(file).force_encoding('utf-8')
242
253
  rescue StandardError
@@ -247,25 +258,27 @@ module CLIMarkdown
247
258
  input.scrub!
248
259
  input.gsub!(/\r?\n/, "\n")
249
260
 
250
- if @options[:list]
251
- puts list_headers(input)
261
+ if MDLess.options[:taskpaper] == :auto
262
+ MDLess.options[:taskpaper] = if CLIMarkdown::TaskPaper.is_taskpaper?(input)
263
+ MDLess.log.info('TaskPaper detected')
264
+ true
265
+ else
266
+ false
267
+ end
268
+ end
269
+
270
+ if MDLess.options[:list]
271
+ if MDLess.options[:taskpaper]
272
+ puts CLIMarkdown::TaskPaper.list_projects(input)
273
+ else
274
+ puts list_headers(input)
275
+ end
252
276
  Process.exit 0
253
277
  else
254
- if @options[:taskpaper] == :auto
255
- @options[:taskpaper] = if file =~ /\.taskpaper/
256
- @log.info('TaskPaper extension detected')
257
- true
258
- elsif CLIMarkdown::TaskPaper.is_taskpaper?(input)
259
- @log.info('TaskPaper document detected')
260
- true
261
- else
262
- false
263
- end
264
- end
265
-
266
- if @options[:taskpaper]
267
- input = CLIMarkdown::TaskPaper.highlight(input, @theme)
268
- @output = input.highlight_tags(@theme, @log)
278
+ if MDLess.options[:taskpaper]
279
+ input = input.color_meta(MDLess.cols)
280
+ input = CLIMarkdown::TaskPaper.highlight(input)
281
+ @output = input.highlight_tags
269
282
  else
270
283
  @output = markdown.render(input)
271
284
  end
@@ -273,14 +286,34 @@ module CLIMarkdown
273
286
  end
274
287
  printout
275
288
  elsif !$stdin.isatty
276
- @file = nil
289
+ MDLess.file = nil
277
290
  input = $stdin.read.scrub
278
291
  input.gsub!(/\r?\n/, "\n")
279
- if @options[:list]
280
- puts list_headers(input)
292
+
293
+ if MDLess.options[:taskpaper] == :auto
294
+ MDLess.options[:taskpaper] = if CLIMarkdown::TaskPaper.is_taskpaper?(input)
295
+ MDLess.log.info('TaskPaper detected')
296
+ true
297
+ else
298
+ false
299
+ end
300
+ end
301
+
302
+ if MDLess.options[:list]
303
+ if MDLess.options[:taskpaper]
304
+ puts CLIMarkdown::TaskPaper.list_projects(input)
305
+ else
306
+ puts list_headers(input)
307
+ end
281
308
  Process.exit 0
282
309
  else
283
- @output = markdown.render(input)
310
+ if MDLess.options[:taskpaper]
311
+ input = input.color_meta(MDLess.cols)
312
+ input = CLIMarkdown::TaskPaper.highlight(input)
313
+ @output = input.highlight_tags
314
+ else
315
+ @output = markdown.render(input)
316
+ end
284
317
  end
285
318
  printout
286
319
  else
@@ -292,17 +325,17 @@ module CLIMarkdown
292
325
  def color(key)
293
326
  val = nil
294
327
  keys = key.split(/[ ,>]/)
295
- if @theme.key?(keys[0])
296
- val = @theme[keys.shift]
328
+ if MDLess.theme.key?(keys[0])
329
+ val = MDLess.theme[keys.shift]
297
330
  else
298
- @log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
331
+ MDLess.log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
299
332
  return c([:reset])
300
333
  end
301
334
  keys.each do |k|
302
335
  if val.key?(k)
303
336
  val = val[k]
304
337
  else
305
- @log.error("Invalid theme key: #{k}")
338
+ MDLess.log.error("Invalid theme key: #{k}")
306
339
  return c([:reset])
307
340
  end
308
341
  end
@@ -394,8 +427,8 @@ module CLIMarkdown
394
427
 
395
428
  def clean_markers(input)
396
429
  input.gsub!(/^(\e\[[\d;]+m)?[%~] ?/, '\1')
397
- input.gsub!(/^(\e\[[\d;]+m)*>(\e\[[\d;]+m)?( +)/, ' \3\1\2')
398
- input.gsub!(/^(\e\[[\d;]+m)*>(\e\[[\d;]+m)?/, '\1\2')
430
+ # input.gsub!(/^(\e\[[\d;]+m)*>(\e\[[\d;]+m)?( +)/, ' \3\1\2')
431
+ # input.gsub!(/^(\e\[[\d;]+m)*>(\e\[[\d;]+m)?/, '\1\2')
399
432
  input.gsub!(/(\e\[[\d;]+m)?@@@(\e\[[\d;]+m)?$/, '')
400
433
  input
401
434
  end
@@ -424,7 +457,7 @@ module CLIMarkdown
424
457
  block.split(/\n/).map do |l|
425
458
  new_code_line = l.gsub(/\t/, ' ')
426
459
  orig_length = new_code_line.size + 8 + eol.size
427
- pad_count = [@cols - orig_length, 0].max
460
+ pad_count = [MDLess.cols - orig_length, 0].max
428
461
 
429
462
  [
430
463
  new_code_line,
@@ -448,11 +481,11 @@ module CLIMarkdown
448
481
  IO.select [input]
449
482
 
450
483
  pager = which_pager
451
- @log.info("Using #{pager} as pager")
484
+ MDLess.log.info("Using #{pager} as pager")
452
485
  begin
453
486
  exec(pager.join(' '))
454
487
  rescue SystemCallError => e
455
- @log.error(e)
488
+ MDLess.log.error(e)
456
489
  exit 1
457
490
  end
458
491
  end
@@ -470,21 +503,25 @@ module CLIMarkdown
470
503
  end
471
504
 
472
505
  def printout
473
- out = @output.rstrip.split(/\n/).map do |p|
474
- p.wrap(@cols, color('text'))
475
- end.join("\n")
506
+ if MDLess.options[:taskpaper]
507
+ out = @output
508
+ else
509
+ out = @output.rstrip.split(/\n/).map do |p|
510
+ p.wrap(MDLess.cols, color('text'))
511
+ end.join("\n")
512
+ end
476
513
 
477
514
  unless out.size&.positive?
478
- @log.warn 'No results'
515
+ MDLess.log.warn 'No results'
479
516
  Process.exit
480
517
  end
481
518
 
482
519
  out = clean_markers(out)
483
520
  out = "#{out.gsub(/\n{2,}/m, "\n\n")}#{xc}"
484
521
 
485
- out.uncolor! unless @options[:color]
522
+ out.uncolor! unless MDLess.options[:color]
486
523
 
487
- if @options[:pager]
524
+ if MDLess.options[:pager]
488
525
  page(out)
489
526
  else
490
527
  $stdout.print out.rstrip
@@ -507,7 +544,7 @@ module CLIMarkdown
507
544
  if f.strip =~ /[ |]/
508
545
  f
509
546
  elsif f == 'most'
510
- @log.warn('most not allowed as pager')
547
+ MDLess.log.warn('most not allowed as pager')
511
548
  false
512
549
  else
513
550
  system "which #{f}", out: File::NULL, err: File::NULL
data/lib/mdless/string.rb CHANGED
@@ -4,20 +4,28 @@
4
4
  class ::String
5
5
  include CLIMarkdown::Colors
6
6
 
7
- def color(key, theme, log)
7
+ def clean_empty_lines
8
+ gsub(/^[ \t]+$/, '')
9
+ end
10
+
11
+ def clean_empty_lines!
12
+ replace clean_empty_lines
13
+ end
14
+
15
+ def color(key)
8
16
  val = nil
9
17
  keys = key.split(/[ ,>]/)
10
- if theme.key?(keys[0])
11
- val = theme[keys.shift]
18
+ if MDLess.theme.key?(keys[0])
19
+ val = MDLess.theme[keys.shift]
12
20
  else
13
- log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
21
+ MDLess.log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
14
22
  return c([:reset])
15
23
  end
16
24
  keys.each do |k|
17
25
  if val.key?(k)
18
26
  val = val[k]
19
27
  else
20
- log.error("Invalid theme key: #{k}")
28
+ MDLess.log.error("Invalid theme key: #{k}")
21
29
  return c([:reset])
22
30
  end
23
31
  end
@@ -30,9 +38,75 @@ class ::String
30
38
  end
31
39
  end
32
40
 
33
- def highlight_tags(theme, log)
34
- tag_color = color('at_tags tag', theme, log)
35
- value_color = color('at_tags value', theme, log)
41
+ def to_rx(distance: 2, string_start: false)
42
+ chars = downcase.split(//)
43
+ pre = string_start ? '^' : '^.*?'
44
+ /#{pre}#{chars.join(".{,#{distance}}")}.*?$/
45
+ end
46
+
47
+ def clean_header_ids!
48
+ replace clean_header_ids
49
+ end
50
+
51
+ def clean_header_ids
52
+ gsub(/ +\[.*?\] *$/, '').gsub(/ *\{#.*?\} *$/, '').strip
53
+ end
54
+
55
+ def color_meta(cols)
56
+ @cols = cols
57
+ input = dup
58
+ input.clean_empty_lines!
59
+
60
+ in_yaml = false
61
+ first_line = input.split("\n").first
62
+ if first_line =~ /(?i-m)^---[ \t]*?$/
63
+ MDLess.log.info('Found YAML')
64
+ # YAML
65
+ in_yaml = true
66
+ input.sub!(/(?i-m)^---[ \t]*\n([\s\S]*?)\n[-.]{3}[ \t]*\n/m) do
67
+ m = Regexp.last_match
68
+ MDLess.log.info('Processing YAML Header')
69
+ lines = m[0].split(/\n/)
70
+ longest = lines.inject { |memo, word| memo.length > word.length ? memo : word }.length
71
+ longest = longest < @cols ? longest + 1 : @cols
72
+ lines.map do |line|
73
+ if line =~ /^[-.]{3}\s*$/
74
+ line = "#{color('metadata marker')}#{'%' * longest}"
75
+ else
76
+ line.sub!(/^(.*?:)[ \t]+(\S)/, '\1 \2')
77
+ line = "#{color('metadata color')}#{line}"
78
+ end
79
+
80
+ line += "\u00A0" * (longest - line.uncolor.strip.length) + xc
81
+ line
82
+ end.join("\n") + "#{xc}\n"
83
+ end
84
+ end
85
+
86
+ if !in_yaml && first_line =~ /(?i-m)^[\w ]+:\s+\S+/
87
+ MDLess.log.info('Found MMD Headers')
88
+ input.sub!(/(?i-m)^([\S ]+:[\s\S]*?)+(?=\n\n)/) do |mmd|
89
+ lines = mmd.split(/\n/)
90
+ return mmd if lines.count > 20
91
+
92
+ longest = lines.inject { |memo, word| memo.length > word.length ? memo : word }.length
93
+ longest = longest < @cols ? longest + 1 : @cols
94
+ lines.map do |line|
95
+ line.sub!(/^(.*?:)[ \t]+(\S)/, '\1 \2')
96
+ line = "#{color('metadata color')}#{line}"
97
+ line += "\u00A0" * (longest - line.uncolor.strip.length)
98
+ line + xc
99
+ end.join("\n") + "#{"\u00A0" * longest}#{xc}\n"
100
+ end
101
+ end
102
+
103
+ input
104
+ end
105
+
106
+ def highlight_tags
107
+ log = MDLess.log
108
+ tag_color = color('at_tags tag')
109
+ value_color = color('at_tags value')
36
110
  gsub(/(?<pre>\s|m)(?<tag>@[^ \].?!,("']+)(?:(?<lparen>\()(?<value>.*?)(?<rparen>\)))?/) do
37
111
  m = Regexp.last_match
38
112
  last_color = m.pre_match.last_color_code
@@ -58,4 +132,16 @@ class ::String
58
132
  def scrub!
59
133
  replace scrub
60
134
  end
135
+
136
+ def valid_pygments_theme?
137
+ return false unless TTY::Which.exist?('pygmentize')
138
+
139
+ MDLess.pygments_styles.include?(self)
140
+ end
141
+
142
+ def valid_lexer?
143
+ return false unless TTY::Which.exist?('pygmentize')
144
+
145
+ MDLess.pygments_lexers.include?(self.downcase)
146
+ end
61
147
  end