mdless 2.0.1 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c645677f1a192348600bf54180afe501b76888d14bad121c4577090ca81718b2
4
- data.tar.gz: 7e82688bd079cede12e529a813c628297070b91b47d68276e54b368b7155bc25
3
+ metadata.gz: 5d7335d03b6ff9a4cd2453de9ecaf5eec3a60fe6dc10d2950cd7b33b298c2852
4
+ data.tar.gz: b83ff9e51541d1baebb07a747a369e5a90bc073fc590ff5620fa33a320ebb888
5
5
  SHA512:
6
- metadata.gz: 830a9aea162d1481ceca6a5dd9cfed3e1a46821a4f1ceaae0d77a843d19a9d0515338025120bc8cfd6bd55fec449009782be872f6558d01d55155bfea9699804
7
- data.tar.gz: ae994c34ece07c3fa05cad41c0e28c866e642dd268a6d175e8746bbd1c8e4d492ca1eef4b943ba506b4769079958296f0ac885e3a10eadc37d595164618db349
6
+ metadata.gz: 20e2aa0e4c82a944fa07cdeed22ea77205387dac18b2393dbd2550620ead1bd1e8b3a9bc2ef130596c273325fa2913f8a0226154988df5cf4452cc004a73b067
7
+ data.tar.gz: '053559f22fd9d96a802f0a4bb98f40512b37591715e977d1994e00a86698901dc39053f0ffb2ebe0ea455ec930feab1004548f139ed87e0be4511bc64696d5b1'
data/README.md CHANGED
@@ -16,10 +16,8 @@ I often use iTerm2 in visor mode, so `qlmanage -p` is annoying. I still wanted a
16
16
  - Colorize Markdown syntax for most elements
17
17
  - Normalize spacing and link formatting
18
18
  - Display footnotes after each paragraph
19
- - Inline image display (local, optionally remote) if using iTerm2 2.9+
19
+ - Inline image display (local, optionally remote) (with compatible tools like imgcat or chafa)
20
20
  - Syntax highlighting when [Pygments](http://pygments.org/) is installed
21
- - Only fenced code with a language defined (e.g. `python`) will be highlighted
22
- - Languages can also be determined by hashbang in the code block
23
21
  - List headlines in document
24
22
  - Display single section of the document based on headlines
25
23
  - Customizable colors
@@ -29,6 +27,8 @@ I often use iTerm2 in visor mode, so `qlmanage -p` is annoying. I still wanted a
29
27
 
30
28
  gem install mdless
31
29
 
30
+ If you run into errors, try `gem install --user-install mdless`, or `sudo gem install mdless` (in that order).
31
+
32
32
  ### Dependencies
33
33
 
34
34
  Some OSs are missing `tput`, which is necessary for mdless.
@@ -37,16 +37,17 @@ Some OSs are missing `tput`, which is necessary for mdless.
37
37
  apt install ruby ncurses-utils
38
38
  gem install mdless
39
39
 
40
+ To render images, you need `imgcat` or `chafa` installed (`brew install chafa`).
41
+
42
+ For syntax highlighting, the `pygmentize` command must be available, part of the [Pygments](http://pygments.org/) package (`brew install pygments`).
43
+
40
44
  ## Usage
41
45
 
42
46
  `mdless [options] path` or `cat [path] | mdless`
43
47
 
44
48
  The pager used is determined by system configuration in this order of preference:
45
49
 
46
- * `$GIT_PAGER`
47
50
  * `$PAGER`
48
- * `git config --get-all core.pager`
49
- * `bat`
50
51
  * `less`
51
52
  * `more`
52
53
  * `cat`
@@ -55,18 +56,24 @@ The pager used is determined by system configuration in this order of preference
55
56
  ### Options
56
57
 
57
58
  -c, --[no-]color Colorize output (default on)
58
- -d, --debug LEVEL Level of debug messages to output
59
+ -d, --debug LEVEL Level of debug messages to output (1-4, 4 to see all messages)
59
60
  -h, --help Display this screen
60
- -i, --images=TYPE Include [local|remote (both)] images in output (requires imgcat and iTerm2, default NONE)
61
- -I, --all-images Include local and remote images in output (requires imgcat and iTerm2)
61
+ -i, --images=TYPE Include [local|remote (both)] images in output (requires chafa or imgcat, default NONE).
62
+ -I, --all-images Include local and remote images in output (requires imgcat or chafa)
63
+ --syntax Syntax highlight code blocks
62
64
  --links=FORMAT Link style ([inline, reference], default inline) [NOT CURRENTLY IMPLEMENTED]
63
65
  -l, --list List headers in document and exit
64
66
  -p, --[no-]pager Formatted output to pager (default on)
65
67
  -P Disable pager (same as --no-pager)
66
- -s, --section=NUMBER Output only a headline-based section of the input (numeric from --list)
68
+ -s, --section=NUMBER[,NUMBER] Output only a headline-based section of the input (numeric from --list)
67
69
  -t, --theme=THEME_NAME Specify an alternate color theme to load
68
70
  -v, --version Display version number
69
- -w, --width=COLUMNS Column width to format for (default terminal width)
71
+ -w, --width=COLUMNS Column width to format for (default: terminal width)
72
+ --[no-]inline_footnotes Display footnotes immediately after the paragraph that references them
73
+
74
+ ## Configuration
75
+
76
+ The first time mdless is run, a config file will be written to `~/.config/mdless/config.yml`, based on the command line options used on the first run. Update that file to make any options permanent (config options will always be overridden by command line flags).
70
77
 
71
78
  ## Customization
72
79
 
@@ -0,0 +1,654 @@
1
+ module Redcarpet
2
+ module Render
3
+ class Console < Base
4
+ include CLIMarkdown::Colors
5
+ include CLIMarkdown::Theme
6
+ attr_writer :theme, :cols, :log, :options, :file
7
+
8
+ @@listitemid = 0
9
+ @@listid = 0
10
+ @@footnotes = []
11
+ @@headers = []
12
+
13
+ def xc
14
+ x + color('text')
15
+ end
16
+
17
+ def x
18
+ c([:reset])
19
+ end
20
+
21
+ def color_table(input)
22
+ first = true
23
+ input.split(/\n/).map do |line|
24
+ if first
25
+ if line =~ /^\+-+/
26
+ line.gsub!(/^/, color('table border'))
27
+ else
28
+ first = false
29
+ line.gsub!(/\|/, "#{color('table border')}|#{color('table header')}")
30
+ end
31
+ elsif line.strip =~ /^[|:\- +]+$/
32
+ line.gsub!(/^(.*)$/, "#{color('table border')}\\1#{color('table color')}")
33
+ line.gsub!(/([:\-+]+)/, "#{color('table divider')}\\1#{color('table border')}")
34
+ else
35
+ line.gsub!(/\|/, "#{color('table border')}|#{color('table color')}")
36
+ end
37
+ end.join("\n")
38
+ end
39
+
40
+ def exec_available(cli)
41
+ if File.exist?(File.expand_path(cli))
42
+ File.executable?(File.expand_path(cli))
43
+ else
44
+ TTY::Which.exist?(cli)
45
+ end
46
+ end
47
+
48
+ def valid_lexer?(language)
49
+ lexers = %w(Clipper XBase Cucumber cucumber Gherkin gherkin RobotFramework robotframework abap ada ada95ada2005 ahk antlr-as antlr-actionscript antlr-cpp antlr-csharp antlr-c# antlr-java antlr-objc antlr-perl antlr-python antlr-ruby antlr-rb antlr apacheconf aconf apache applescript as actionscript as3 actionscript3 aspectj aspx-cs aspx-vb asy asymptote autoit Autoit awk gawk mawk nawk basemake bash sh ksh bat bbcode befunge blitzmax bmax boo brainfuck bf bro bugs winbugs openbugs c-objdump c ca65 cbmbas ceylon cfengine3 cf3 cfm cfs cheetah spitfire clojure clj cmake cobol cobolfree coffee-script coffeescript common-lisp cl console control coq cpp c++ cpp-objdump c++-objdumb cxx-objdump croc csharp c# css+django css+jinja css+erb css+ruby css+genshitext css+genshi css+lasso css+mako css+myghty css+php css+smarty css cuda cu cython pyx d-objdump d dart delphi pas pascal objectpascal dg diff udiff django jinja dpatch dtd duel Duel Engine Duel View JBST jbst JsonML+BST dylan-console dylan-repl dylan-lid lid dylan ec ecl elixir ex exs erb erl erlang evoque factor fan fancy fy felix flx fortran fsharp gas genshi kid xml+genshi xml+kid genshitext glsl gnuplot go gooddata-cl gosu groff nroff man groovy gst haml HAML haskell hs haxeml hxml html+cheetah html+spitfire html+django html+jinja html+evoque html+genshi html+kid html+lasso html+mako html+myghty html+php html+smarty html+velocity html http hx haXe hybris hy idl iex ini cfg io ioke ik irc jade JADE jags java jlcon js+cheetah javascript+cheetah js+spitfire javascript+spitfire js+django javascript+django js+jinja javascript+jinja js+erb javascript+erb js+ruby javascript+ruby js+genshitext js+genshi javascript+genshitext javascript+genshi js+lasso javascript+lasso js+mako javascript+mako js+myghty javascript+myghty js+php javascript+php js+smarty javascript+smarty js javascript json jsp julia jl kconfig menuconfig linux-config kernel-config koka kotlin lasso lassoscript lhs literate-haskell lighty lighttpd live-script livescript llvm logos logtalk lua make makefile mf bsdmake mako maql mason matlab matlabsession minid modelica modula2 m2 monkey moocode moon moonscript mscgen msc mupad mxml myghty mysql nasm nemerle newlisp newspeak nginx nimrod nim nsis nsi nsh numpy objdump objective-c++ objectivec++ obj-c++ objc++ objective-c objectivec obj-c objc objective-j objectivej obj-j objj ocaml octave ooc opa openedge abl progress perl pl php php3 php4 php5 plpgsql postgresql postgres postscript pot po pov powershell posh ps1 prolog properties protobuf psql postgresql-console postgres-console puppet py3tb pycon pypylog pypy pytb python py sage python3 py3 qml Qt Meta Language Qt modeling Language racket rkt ragel-c ragel-cpp ragel-d ragel-em ragel-java ragel-objc ragel-ruby ragel-rb ragel raw rb ruby duby rbcon irb rconsole rout rd rebol redcode registry rhtml html+erb html+ruby rst rest restructuredtext rust sass SASS scala scaml SCAML scheme scm scilab scss shell-session smali smalltalk squeak smarty sml snobol sourceslist sources.list sp spec splus s r sql sqlite3 squidconf squid.conf squid ssp stan systemverilog sv tcl tcsh csh tea tex latex text trac-wiki moin treetop ts urbiscript vala vapi vb.net vbnet velocity verilog v vgl vhdl vim xml+cheetah xml+spitfire xml+django xml+jinja xml+erb xml+ruby xml+evoque xml+lasso xml+mako xml+myghty xml+php xml+smarty xml+velocity xml xquery xqy xq xql xqm xslt xtend yaml)
50
+ lexers.include? language.strip
51
+ end
52
+
53
+ def hilite_code(code_block, language)
54
+ @log.error('Syntax highlighting requested by pygmentize is not available') if @options[:syntax_higlight] && !exec_available('pygmentize')
55
+
56
+ if @options[:syntax_higlight] && exec_available('pygmentize')
57
+ lexer = language && valid_lexer?(language) ? "-l #{language}" : '-g'
58
+ begin
59
+ cmd = [
60
+ 'pygmentize -f terminal256',
61
+ "-O style=#{@theme['code_block']['pygments_theme']}",
62
+ lexer,
63
+ '2> /dev/null'
64
+ ].join(' ')
65
+ hilite, s = Open3.capture2(cmd,
66
+ stdin_data: code_block)
67
+ if s.success?
68
+ hilite = xc + hilite.split(/\n/).map do |l|
69
+ [
70
+ color('code_block marker'),
71
+ '> ',
72
+ "#{color('code_block bg')}#{l.strip}#{xc}"
73
+ ].join
74
+ end.join("\n").blackout(@theme['code_block']['bg']) + "#{xc}\n"
75
+ end
76
+ rescue StandardError => e
77
+ @log.error(e)
78
+ hilite = code_block
79
+ end
80
+ else
81
+ hilite = code_block.split(/\n/).map do |line|
82
+ [
83
+ color('code_block marker'),
84
+ '> ',
85
+ color('code_block color'),
86
+ line,
87
+ xc
88
+ ].join
89
+ end.join("\n").blackout(@theme['code_block']['bg']) + "#{xc}\n"
90
+ end
91
+
92
+ [
93
+ xc,
94
+ color('code_block border'),
95
+ '-' * @cols,
96
+ xc,
97
+ "\n",
98
+ color('code_block color'),
99
+ hilite.chomp,
100
+ "\n",
101
+ color('code_block border'),
102
+ '-' * @cols,
103
+ xc
104
+ ].join
105
+ end
106
+
107
+ def color(key)
108
+ val = nil
109
+ keys = key.split(/[ ,>]/)
110
+ if @theme.key?(keys[0])
111
+ val = @theme[keys.shift]
112
+ else
113
+ @log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
114
+ return c([:reset])
115
+ end
116
+ keys.each do |k|
117
+ if val.key?(k)
118
+ val = val[k]
119
+ else
120
+ @log.error("Invalid theme key: #{k}")
121
+ return c([:reset])
122
+ end
123
+ end
124
+ if val.is_a? String
125
+ val = "x #{val}"
126
+ res = val.split(/ /).map(&:to_sym)
127
+ c(res)
128
+ else
129
+ c([:reset])
130
+ end
131
+ end
132
+
133
+ def block_code(code, language)
134
+ "\n\n#{hilite_code(code, language)}#{xc}\n\n"
135
+ end
136
+
137
+ def block_quote(quote)
138
+ ret = "\n\n"
139
+ quote.split("\n").each do |line|
140
+ ret += [
141
+ color('blockquote marker color'),
142
+ @theme['blockquote']['marker']['character'],
143
+ color('blockquote color'),
144
+ line,
145
+ "\n"
146
+ ].join('')
147
+ end
148
+ "#{ret}\n\n"
149
+ end
150
+
151
+ def block_html(raw_html)
152
+ "#{color('html color')}#{color_tags(raw_html)}#{xc}"
153
+ end
154
+
155
+ def header(text, header_level)
156
+ pad = ''
157
+ ansi = ''
158
+ case header_level
159
+ when 1
160
+ ansi = color('h1 color')
161
+ pad = color('h1 pad')
162
+ char = @theme['h1']['pad_char'] || '='
163
+ pad += text.length + 2 > @cols ? char * text.length : char * (@cols - (text.length + 1))
164
+ when 2
165
+ ansi = color('h2 color')
166
+ pad = color('h2 pad')
167
+ char = @theme['h2']['pad_char'] || '-'
168
+ pad += text.length + 2 > @cols ? char * text.length : char * (@cols - (text.length + 1))
169
+ when 3
170
+ ansi = color('h3 color')
171
+ when 4
172
+ ansi = color('h4 color')
173
+ when 5
174
+ ansi = color('h5 color')
175
+ else
176
+ ansi = color('h6 color')
177
+ end
178
+
179
+ # If we're in iTerm and not paginating, add
180
+ # iTerm Marks for navigation on h1-3
181
+ if header_level < 4 &&
182
+ ENV['TERM_PROGRAM'] =~ /^iterm/i &&
183
+ @options[:pager] == false
184
+ ansi = "\e]1337;SetMark\a#{ansi}"
185
+ end
186
+
187
+ "\n#{xc}#{ansi}#{text} #{pad}#{xc}\n\n"
188
+ end
189
+
190
+ def hrule()
191
+ "\n\n#{color('hr color')}#{'_' * @cols}#{xc}\n\n"
192
+ end
193
+
194
+ def list(contents, list_type)
195
+ @@listitemid = 0
196
+ @@listid += 1
197
+ "<<list#{@@listid}>>#{contents}<</list#{@@listid}>>"
198
+ end
199
+
200
+ def list_item(text, list_type)
201
+ case list_type
202
+ when :unordered
203
+ [
204
+ "#{color('list bullet')}• ",
205
+ color('list color'),
206
+ text,
207
+ xc
208
+ ].join('')
209
+ when :ordered
210
+ @@listitemid += 1
211
+ [
212
+ color('list number'),
213
+ "#{@@listitemid}. ",
214
+ color('list color'),
215
+ text,
216
+ xc
217
+ ].join('')
218
+ end
219
+ end
220
+
221
+ def paragraph(text)
222
+ "#{xc}#{text}#{xc}#{x}\n\n"
223
+ end
224
+
225
+ @table_cols = nil
226
+
227
+ def table_header_row
228
+ @header_row.map do |alignment|
229
+ case alignment
230
+ when :left
231
+ '|:---'
232
+ when :right
233
+ '|---:'
234
+ when :center
235
+ '|:--:'
236
+ else
237
+ '|----'
238
+ end
239
+ end.join('') + '|'
240
+ end
241
+
242
+ def table(header, body)
243
+ formatted = CLIMarkdown::MDTableCleanup.new([
244
+ "#{header}",
245
+ table_header_row,
246
+ "|\n",
247
+ "#{body}\n\n"
248
+ ].join(''))
249
+ res = formatted.to_md
250
+ "#{color_table(res)}\n"
251
+ # res
252
+ end
253
+
254
+ def table_row(content)
255
+ @table_cols = content.scan(/\|/).count
256
+ %(#{content}\n)
257
+ end
258
+
259
+ def table_cell(content, alignment)
260
+ @header_row ||= []
261
+ if @table_cols && @header_row.count < @table_cols
262
+ @header_row << alignment
263
+ end
264
+ %(#{content} |)
265
+ end
266
+
267
+ def autolink(link, _)
268
+ [
269
+ color('link brackets'),
270
+ '<',
271
+ color('link url'),
272
+ link,
273
+ color('link brackets'),
274
+ '>',
275
+ xc
276
+ ].join('')
277
+ end
278
+
279
+ def codespan(code)
280
+ [
281
+ color('code_span marker'),
282
+ '`',
283
+ color('code_span color'),
284
+ code,
285
+ color('code_span marker'),
286
+ '`',
287
+ xc
288
+ ].join('')
289
+ end
290
+
291
+ def double_emphasis(text)
292
+ "#{color('emphasis bold')}#{text}#{xc}"
293
+ end
294
+
295
+ def emphasis(text)
296
+ "#{color('emphasis italic')}#{text}#{xc}"
297
+ end
298
+
299
+ def triple_emphasis(text)
300
+ "#{color('emphasis bold-italic')}#{text}#{xc}"
301
+ end
302
+
303
+ def highlight(text)
304
+ "#{color('highlight')}#{text}#{xc}"
305
+ end
306
+
307
+ def color_image_tag(link, title, alt_text)
308
+ [
309
+ color('image bang'),
310
+ '!',
311
+ color('image brackets'),
312
+ '[',
313
+ color('image title'),
314
+ alt_text,
315
+ color('image brackets'),
316
+ '](',
317
+ color('image url'),
318
+ link,
319
+ title.nil? ? '' : %( "#{title}"),
320
+ color('image brackets'),
321
+ ')',
322
+ xc
323
+ ].join
324
+ end
325
+
326
+ def image(link, title, alt_text)
327
+ if (exec_available('imgcat') || exec_available('chafa')) && @options[:local_images]
328
+ if exec_available('imgcat')
329
+ @log.info('Using imgcat for image rendering')
330
+ elsif exec_available('chafa')
331
+ @log.info('Using chafa for image rendering')
332
+ end
333
+ img_path = link
334
+ if img_path =~ /^http/ && @options[:remote_images]
335
+ if exec_available('imgcat')
336
+ @log.info('Using imgcat for image rendering')
337
+ begin
338
+ res, s = Open3.capture2(%(curl -sS "#{img_path}" 2> /dev/null | imgcat))
339
+
340
+ if s.success?
341
+ pre = !alt_text.nil? ? " #{c(%i[d blue])}[#{alt_text.strip}]\n" : ''
342
+ post = !title.nil? ? "\n #{c(%i[b blue])}-- #{title} --" : ''
343
+ result = pre + res + post
344
+ end
345
+ rescue StandardError => e
346
+ @log.error(e)
347
+ end
348
+ elsif exec_available('chafa')
349
+ @log.info('Using chafa for image rendering')
350
+ term = '-f sixels'
351
+ term = ENV['TERMINAL_PROGRAM'] =~ /iterm/i ? '-f iterm' : term
352
+ term = ENV['TERMINAL_PROGRAM'] =~ /kitty/i ? '-f kitty' : term
353
+ FileUtils.rm_r '.mdless_tmp', force: true if File.directory?('.mdless_tmp')
354
+ Dir.mkdir('.mdless_tmp')
355
+ Dir.chdir('.mdless_tmp')
356
+ `curl -SsO #{img_path} 2> /dev/null`
357
+ tmp_img = File.basename(img_path)
358
+ img = `chafa #{term} "#{tmp_img}"`
359
+ pre = alt_text ? " #{c(%i[d blue])}[#{alt_text.strip}]\n" : ''
360
+ post = title ? "\n #{c(%i[b blue])}-- #{tail} --" : ''
361
+ result = pre + img + post
362
+ Dir.chdir('..')
363
+ FileUtils.rm_r '.mdless_tmp', force: true
364
+ else
365
+ @log.warn('No viewer for remote images')
366
+ end
367
+ else
368
+ if img_path =~ %r{^[~/]}
369
+ img_path = File.expand_path(img_path)
370
+ elsif @file
371
+ base = File.expand_path(File.dirname(@file))
372
+ img_path = File.join(base, img_path)
373
+ end
374
+ if File.exist?(img_path)
375
+ pre = !alt_text.nil? ? " #{c(%i[d blue])}[#{alt_text.strip}]\n" : ''
376
+ post = !title.nil? ? "\n #{c(%i[b blue])}-- #{title} --" : ''
377
+ if exec_available('imgcat')
378
+ img = `imgcat "#{img_path}"`
379
+ elsif exec_available('chafa')
380
+ term = '-f sixels'
381
+ term = ENV['TERMINAL_PROGRAM'] =~ /iterm/i ? '-f iterm' : term
382
+ term = ENV['TERMINAL_PROGRAM'] =~ /kitty/i ? '-f kitty' : term
383
+ img = `chafa #{term} "#{img_path}"`
384
+ end
385
+ result = pre + img + post
386
+ end
387
+ end
388
+ end
389
+ if result.nil?
390
+ color_image_tag(link, title, alt_text)
391
+ else
392
+ result + xc
393
+ end
394
+ end
395
+
396
+ def linebreak()
397
+ "\n"
398
+ end
399
+
400
+ def link(link, title, content)
401
+ [
402
+ color('link brackets'),
403
+ '[',
404
+ color('link text'),
405
+ content,
406
+ color('link brackets'),
407
+ '](',
408
+ color('link url'),
409
+ link,
410
+ title.nil? ? '' : %( "#{title}"),
411
+ color('link brackets'),
412
+ ')',
413
+ xc
414
+ ].join
415
+ end
416
+
417
+ def color_tags(html)
418
+ html.gsub(%r{(<\S+( .*?)?/?>)}, "#{color('html brackets')}\\1#{xc}")
419
+ end
420
+
421
+ def raw_html(raw_html)
422
+ "#{color('html color')}#{color_tags(raw_html)}#{xc}"
423
+ end
424
+
425
+ def strikethrough(text)
426
+ "#{color('strikethrough')}#{text}#{xc}"
427
+ end
428
+
429
+ def superscript(text)
430
+ "#{color('super')}^#{text}#{xc}"
431
+ end
432
+
433
+ def footnotes(text)
434
+ # [
435
+ # color('footnote note'),
436
+ # text,
437
+ # "\n",
438
+ # xc,
439
+ # ].join('')
440
+ nil
441
+ end
442
+
443
+ def color_footnote_def(idx)
444
+ text = @@footnotes[idx]
445
+ [
446
+ color('footnote brackets'),
447
+ "[",
448
+ color('footnote caret'),
449
+ "^",
450
+ color('footnote title'),
451
+ idx,
452
+ color('footnote brackets'),
453
+ "]:",
454
+ color('footnote note'),
455
+ ' ',
456
+ text.uncolor.strip,
457
+ xc,
458
+ "\n"
459
+ ].join('')
460
+ end
461
+
462
+ def footnote_def(text, idx)
463
+ @@footnotes[idx] = text
464
+ end
465
+
466
+ def footnote_ref(text)
467
+ [
468
+ color('footnote title'),
469
+ "[^#{text}]",
470
+ xc
471
+ ].join('')
472
+ end
473
+
474
+ def insert_footnotes(input)
475
+ input.split(/\n/).map do |line|
476
+ notes = line.to_enum(:scan, /\[\^(?<ref>\d+)\]/).map { Regexp.last_match }
477
+ if notes.count.positive?
478
+ footnotes = notes.map { |n| color_footnote_def(n['ref'].to_i) }.join("\n")
479
+ "#{line}\n\n#{footnotes}\n\n"
480
+ else
481
+ line
482
+ end
483
+ end.join("\n")
484
+ end
485
+
486
+ def fix_lists(input, indent = 0)
487
+ input.gsub(%r{(?<line><<list(?<id>\d+)>>(?<content>.*?)<</list\k<id>>>)}m) do
488
+ m = Regexp.last_match
489
+ fix_lists(m['content'].split(/\n/).map do |l|
490
+ outdent = l.scan(%r{<</list\d+>>}).count
491
+ indent += l.scan(/<<list\d+>>/).count
492
+ indent -= outdent
493
+ "#{' ' * indent}#{l}"
494
+ end.join("\n"), indent)
495
+ end + "\n"
496
+ end
497
+
498
+ def get_headers(input)
499
+ unless @@headers && !@@headers.empty?
500
+ @@headers = []
501
+ headers = input.scan(/^((?!#!)(\#{1,6})\s*([^#]+?)(?: #+)?\s*|(\S.+)\n([=-]+))$/i)
502
+
503
+ headers.each do |h|
504
+ hlevel = 6
505
+ title = nil
506
+ if h[4] =~ /=+/
507
+ hlevel = 1
508
+ title = h[3]
509
+ elsif h[4] =~ /-+/
510
+ hlevel = 2
511
+ title = h[3]
512
+ else
513
+ hlevel = h[1].length
514
+ title = h[2]
515
+ end
516
+ @@headers << [
517
+ '#' * hlevel,
518
+ title,
519
+ h[0]
520
+ ]
521
+ end
522
+ end
523
+
524
+ @@headers
525
+ end
526
+
527
+ def preprocess(input)
528
+ in_yaml = false
529
+ if input.split("\n")[0] =~ /(?i-m)^---[ \t]*?(\n|$)/
530
+ @log.info('Found YAML')
531
+ # YAML
532
+ in_yaml = true
533
+ input.sub!(/(?i-m)^---[ \t]*\n([\s\S]*?)\n[-.]{3}[ \t]*\n/) do
534
+ m = Regexp.last_match
535
+ @log.info('Processing YAML Header')
536
+ m[0].split(/\n/).map do |line|
537
+ if line =~ /^[-.]{3}\s*$/
538
+ line = "#{color('metadata marker')}%% #{color('metadata border')}#{line}"
539
+ else
540
+ line.sub!(/^(.*?:)[ \t]+(\S)/, '\1 \2')
541
+ line = "#{color('metadata marker')}%% #{color('metadata color')}#{line}"
542
+ end
543
+ line += ' ' * (@cols - line.uncolor.size) if (@cols - line.uncolor.size).positive?
544
+ line
545
+ end.join("\n") + "#{xc}\n"
546
+ end
547
+ end
548
+
549
+ if !in_yaml && input.gsub(/\n/, ' ') =~ /(?i-m)^[\w ]+:\s+\S+/
550
+ @log.info('Found MMD Headers')
551
+ input.sub!(/(?i-m)^([\S ]+:[\s\S]*?)+(?=\n\n)/) do |mmd|
552
+ mmd.split(/\n/).map do |line|
553
+ line.sub!(/^(.*?:)[ \t]+(\S)/, '\1 \2')
554
+ line = "#{color('metadata marker')}%% #{color('metadata color')}#{line}"
555
+ line += ' ' * (@cols - line.uncolor.size) if (@cols - line.uncolor.size).positive?
556
+ line
557
+ end.join("\n") + "#{' ' * @cols}#{xc}\n"
558
+ end
559
+
560
+ end
561
+
562
+ ## Replace setex headers with ATX
563
+ input.gsub!(/^([^\n]+)\n={3,}\s*$/m, "# \\1\n")
564
+ input.gsub!(/^([^\n]+?)\n-{3,}\s*$/m, "## \\1\n")
565
+
566
+ @@headers = get_headers(input)
567
+
568
+ if @options[:section]
569
+ new_content = []
570
+ @options[:section].each do |sect|
571
+ in_section = false
572
+ top_level = 1
573
+ input.split(/\n/).each do |graf|
574
+ if graf =~ /^(#+) *(.*?)( *#+)?$/
575
+ m = Regexp.last_match
576
+ level = m[1].length
577
+ title = m[2]
578
+ if in_section
579
+ if level >= top_level
580
+ new_content.push(graf)
581
+ else
582
+ in_section = false
583
+ break
584
+ end
585
+ elsif title.downcase == @@headers[sect - 1][1].downcase
586
+ in_section = true
587
+ top_level = level + 1
588
+ new_content.push(graf)
589
+ else
590
+ next
591
+ end
592
+ elsif in_section
593
+ new_content.push(graf)
594
+ end
595
+ end
596
+ end
597
+ input = new_content.join("\n")
598
+ end
599
+
600
+ # definition lists
601
+ input.gsub!(/(?mi)(?<=\n|\A)(?<term>(?!<:)[^\n]+)(?<def>(\n+: [^\n]+)+)/) do
602
+ m = Regexp.last_match
603
+ "#{color('dd term')}#{m['term']}#{xc}#{color('dd color')}#{color_dd_def(m['def'])}"
604
+ end
605
+
606
+ input
607
+ end
608
+
609
+ def color_dd_def(input)
610
+ input.gsub(/(?<=\n|\A)(?::)\s+(.*)/) do
611
+ m = Regexp.last_match
612
+ [
613
+ color('dd marker'),
614
+ ": ",
615
+ color('dd color'),
616
+ m[1],
617
+ xc
618
+ ].join
619
+ end
620
+ end
621
+
622
+ def postprocess(input)
623
+ if @options[:inline_footnotes]
624
+ input = insert_footnotes(input)
625
+ else
626
+ footnotes = @@footnotes.map.with_index do |fn, i|
627
+ next if fn.nil?
628
+
629
+ color_footnote_def(i)
630
+ end.join("\n")
631
+ input = "#{input}\n\n#{footnotes}"
632
+ end
633
+ # escaped characters
634
+ input.gsub!(/\\(\S)/, '\1')
635
+ # equations
636
+ input.gsub!(/((\\\\\[|\$\$)(.*?)(\\\\\]|\$\$)|(\\\\\(|\$)(.*?)(\\\\\)|\$))/) do
637
+ m = Regexp.last_match
638
+ if m[2]
639
+ brackets = [m[2], m[4]]
640
+ equat = m[3]
641
+ else
642
+ brackets = [m[5], m[7]]
643
+ equat = m[6]
644
+ end
645
+ "#{c(%i[b black])}#{brackets[0]}#{xc}#{c(%i[b blue])}#{equat}#{c(%i[b black])}#{brackets[1]}" + xc
646
+ end
647
+ # misc html
648
+ input.gsub!(%r{<br */?>}, "\n")
649
+ # lists
650
+ fix_lists(input, 0)
651
+ end
652
+ end
653
+ end
654
+ end
@@ -1,3 +1,3 @@
1
1
  module CLIMarkdown
2
- VERSION = '2.0.1'
2
+ VERSION = '2.0.3'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mdless
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
@@ -79,6 +79,7 @@ files:
79
79
  - bin/mdless
80
80
  - lib/mdless.rb
81
81
  - lib/mdless/colors.rb
82
+ - lib/mdless/console.rb
82
83
  - lib/mdless/converter.rb
83
84
  - lib/mdless/hash.rb
84
85
  - lib/mdless/tables.rb