mdless 2.0.18 → 2.0.20
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.
- checksums.yaml +4 -4
- data/lib/mdless/colors.rb +8 -4
- data/lib/mdless/console.rb +76 -69
- data/lib/mdless/converter.rb +114 -93
- data/lib/mdless/string.rb +33 -27
- data/lib/mdless/taskpaper.rb +79 -9
- data/lib/mdless/version.rb +1 -1
- data/lib/mdless.rb +44 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4af619d93405da0eb2861158c261c7cd2c494530f32c3e5de2a8be9b4c7d713
|
4
|
+
data.tar.gz: 17c466f16b258b5959be6ac0f2540594a36b3319007692897200999749c34cc1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36f0963f1629e9cf7f65576c6340dc1bd1fd2ee8e2f47ccf9cde5852581db3a502804e571b28b3501f1a3dc9b29ea0c214eb504b9771c89bed27fd6ce9147377
|
7
|
+
data.tar.gz: d39dcf70ca3b26573f9459489b4ca3aafaa9f3d014d83d63aa76e0d4dbe37138a8c25b421b9b00457d9c6ab105b8fdbd3cf6fb85f6549d4963ae72694bc6e4ab
|
data/lib/mdless/colors.rb
CHANGED
@@ -133,7 +133,6 @@ module CLIMarkdown
|
|
133
133
|
end
|
134
134
|
|
135
135
|
def wrap(width=78,foreground=:x)
|
136
|
-
|
137
136
|
if self.uncolor =~ /(^([%~] |\s*>)| +[=\-]{5,})/
|
138
137
|
return self
|
139
138
|
end
|
@@ -145,6 +144,9 @@ module CLIMarkdown
|
|
145
144
|
|
146
145
|
line += self.match(/^\s*/)[0].gsub(/\t/,' ')
|
147
146
|
input = self.dup # .gsub(/(\w-)(\w)/,'\1 \2')
|
147
|
+
input.gsub!(/\[.*?\]\(.*?\)/) do |link|
|
148
|
+
link.gsub(/ /, "\u00A0")
|
149
|
+
end
|
148
150
|
input.split(/\s+/).each do |word|
|
149
151
|
last_ansi = line.scan(/\e\[[\d;]+m/)[-1] || ''
|
150
152
|
if visible_width + word.size_clean >= width
|
@@ -158,9 +160,11 @@ module CLIMarkdown
|
|
158
160
|
visible_width += word.size_clean + 1
|
159
161
|
line << " " << last_ansi + word
|
160
162
|
end
|
161
|
-
|
162
|
-
|
163
|
-
|
163
|
+
end
|
164
|
+
lines << line + match(/\s*$/)[0] + xc(foreground) if line
|
165
|
+
lines.join("\n").gsub(/\[.*?\]\(.*?\)/) do |link|
|
166
|
+
link.gsub(/\u00A0/, ' ')
|
167
|
+
end
|
164
168
|
end
|
165
169
|
|
166
170
|
def c(args)
|
data/lib/mdless/console.rb
CHANGED
@@ -4,13 +4,13 @@ module Redcarpet
|
|
4
4
|
include CLIMarkdown::Colors
|
5
5
|
include CLIMarkdown::Theme
|
6
6
|
|
7
|
-
|
7
|
+
attr_accessor :headers
|
8
|
+
attr_writer :file
|
8
9
|
|
9
10
|
@@listitemid = 0
|
10
11
|
@@listid = 0
|
11
12
|
@@elementid = 0
|
12
13
|
@@footnotes = []
|
13
|
-
@@headers = []
|
14
14
|
@@links = []
|
15
15
|
@@footer_links = []
|
16
16
|
|
@@ -66,22 +66,22 @@ module Redcarpet
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def hilite_code(code_block, language)
|
69
|
-
if
|
70
|
-
|
71
|
-
|
69
|
+
if MDLess.options[:syntax_higlight] && !exec_available('pygmentize')
|
70
|
+
MDLess.log.error('Syntax highlighting requested by pygmentize is not available')
|
71
|
+
MDLess.options[:syntax_higlight] = false
|
72
72
|
end
|
73
73
|
|
74
74
|
longest_line = code_block.uncolor.split(/\n/).longest_element.length + 4
|
75
|
-
longest_line = longest_line >
|
75
|
+
longest_line = longest_line > MDLess.cols ? MDLess.cols : longest_line
|
76
76
|
|
77
|
-
if
|
77
|
+
if MDLess.options[:syntax_higlight]
|
78
78
|
pyg = TTY::Which.which('pygmentize')
|
79
79
|
lexer = language&.valid_lexer? ? "-l #{language}" : '-g'
|
80
80
|
begin
|
81
|
-
pygments_theme =
|
81
|
+
pygments_theme = MDLess.options[:pygments_theme] || MDLess.theme['code_block']['pygments_theme']
|
82
82
|
|
83
83
|
unless pygments_theme.valid_pygments_theme?
|
84
|
-
|
84
|
+
MDLess.log.error("Invalid Pygments theme #{pygments_theme}, defaulting to 'default' for highlighting")
|
85
85
|
pygments_theme = 'default'
|
86
86
|
end
|
87
87
|
|
@@ -100,10 +100,10 @@ module Redcarpet
|
|
100
100
|
'> ',
|
101
101
|
"#{color('code_block bg')}#{l.strip}#{xc}"
|
102
102
|
].join
|
103
|
-
end.join("\n").blackout(
|
103
|
+
end.join("\n").blackout(MDLess.theme['code_block']['bg']) + "#{xc}\n"
|
104
104
|
end
|
105
105
|
rescue StandardError => e
|
106
|
-
|
106
|
+
MDLess.log.error(e)
|
107
107
|
hilite = code_block
|
108
108
|
end
|
109
109
|
else
|
@@ -115,7 +115,7 @@ module Redcarpet
|
|
115
115
|
line,
|
116
116
|
xc
|
117
117
|
].join
|
118
|
-
end.join("\n").blackout(
|
118
|
+
end.join("\n").blackout(MDLess.theme['code_block']['bg']) + "#{xc}\n"
|
119
119
|
end
|
120
120
|
|
121
121
|
top_border = if language.nil? || language.empty?
|
@@ -142,17 +142,17 @@ module Redcarpet
|
|
142
142
|
def color(key)
|
143
143
|
val = nil
|
144
144
|
keys = key.split(/[ ,>]/)
|
145
|
-
if
|
146
|
-
val =
|
145
|
+
if MDLess.theme.key?(keys[0])
|
146
|
+
val = MDLess.theme[keys.shift]
|
147
147
|
else
|
148
|
-
|
148
|
+
MDLess.log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
|
149
149
|
return c([:reset])
|
150
150
|
end
|
151
151
|
keys.each do |k|
|
152
152
|
if val.key?(k)
|
153
153
|
val = val[k]
|
154
154
|
else
|
155
|
-
|
155
|
+
MDLess.log.error("Invalid theme key: #{k}")
|
156
156
|
return c([:reset])
|
157
157
|
end
|
158
158
|
end
|
@@ -171,11 +171,12 @@ module Redcarpet
|
|
171
171
|
|
172
172
|
def block_quote(quote)
|
173
173
|
ret = "\n\n"
|
174
|
-
quote.split(
|
174
|
+
quote.wrap(MDLess.cols, color('blockquote color')).split(/\n/).each do |line|
|
175
175
|
ret += [
|
176
176
|
color('blockquote marker color'),
|
177
|
-
|
177
|
+
MDLess.theme['blockquote']['marker']['character'],
|
178
178
|
color('blockquote color'),
|
179
|
+
' ',
|
179
180
|
line,
|
180
181
|
"\n"
|
181
182
|
].join('')
|
@@ -190,17 +191,18 @@ module Redcarpet
|
|
190
191
|
def header(text, header_level)
|
191
192
|
pad = ''
|
192
193
|
ansi = ''
|
194
|
+
text.clean_header_ids!
|
193
195
|
case header_level
|
194
196
|
when 1
|
195
197
|
ansi = color('h1 color')
|
196
198
|
pad = color('h1 pad')
|
197
|
-
char =
|
198
|
-
pad += text.length + 2 >
|
199
|
+
char = MDLess.theme['h1']['pad_char'] || '='
|
200
|
+
pad += text.length + 2 > MDLess.cols ? char * text.length : char * (MDLess.cols - (text.length + 1))
|
199
201
|
when 2
|
200
202
|
ansi = color('h2 color')
|
201
203
|
pad = color('h2 pad')
|
202
|
-
char =
|
203
|
-
pad += text.length + 2 >
|
204
|
+
char = MDLess.theme['h2']['pad_char'] || '-'
|
205
|
+
pad += text.length + 2 > MDLess.cols ? char * text.length : char * (MDLess.cols - (text.length + 1))
|
204
206
|
when 3
|
205
207
|
ansi = color('h3 color')
|
206
208
|
when 4
|
@@ -215,15 +217,15 @@ module Redcarpet
|
|
215
217
|
# iTerm Marks for navigation on h1-3
|
216
218
|
if header_level < 4 &&
|
217
219
|
ENV['TERM_PROGRAM'] =~ /^iterm/i &&
|
218
|
-
|
220
|
+
MDLess.options[:pager] == false
|
219
221
|
ansi = "\e]1337;SetMark\a#{ansi}"
|
220
222
|
end
|
221
223
|
|
222
|
-
"\n#{xc}#{ansi}#{text} #{pad}#{xc}\n\n"
|
224
|
+
"\n\n#{xc}#{ansi}#{text} #{pad}#{xc}\n\n"
|
223
225
|
end
|
224
226
|
|
225
227
|
def hrule()
|
226
|
-
"\n\n#{color('hr color')}#{'_' *
|
228
|
+
"\n\n#{color('hr color')}#{'_' * MDLess.cols}#{xc}\n\n"
|
227
229
|
end
|
228
230
|
|
229
231
|
def paragraph(text)
|
@@ -248,6 +250,7 @@ module Redcarpet
|
|
248
250
|
end
|
249
251
|
|
250
252
|
def table(header, body)
|
253
|
+
@header_row = []
|
251
254
|
formatted = CLIMarkdown::MDTableCleanup.new([
|
252
255
|
"#{header}",
|
253
256
|
table_header_row,
|
@@ -255,7 +258,7 @@ module Redcarpet
|
|
255
258
|
"#{body}\n\n"
|
256
259
|
].join(''))
|
257
260
|
res = formatted.to_md
|
258
|
-
"#{color_table(res)}\n"
|
261
|
+
"#{color_table(res)}\n\n"
|
259
262
|
# res
|
260
263
|
end
|
261
264
|
|
@@ -290,11 +293,11 @@ module Redcarpet
|
|
290
293
|
[
|
291
294
|
pre_element,
|
292
295
|
color('code_span marker'),
|
293
|
-
|
296
|
+
MDLess.theme['code_span']['character'],
|
294
297
|
color('code_span color'),
|
295
298
|
code,
|
296
299
|
color('code_span marker'),
|
297
|
-
|
300
|
+
MDLess.theme['code_span']['character'],
|
298
301
|
xc,
|
299
302
|
post_element
|
300
303
|
].join('')
|
@@ -304,9 +307,9 @@ module Redcarpet
|
|
304
307
|
[
|
305
308
|
pre_element,
|
306
309
|
color('emphasis bold'),
|
307
|
-
|
310
|
+
MDLess.theme['emphasis']['bold_character'],
|
308
311
|
text,
|
309
|
-
|
312
|
+
MDLess.theme['emphasis']['bold_character'],
|
310
313
|
xc,
|
311
314
|
post_element
|
312
315
|
].join
|
@@ -316,9 +319,9 @@ module Redcarpet
|
|
316
319
|
[
|
317
320
|
pre_element,
|
318
321
|
color('emphasis italic'),
|
319
|
-
|
322
|
+
MDLess.theme['emphasis']['italic_character'],
|
320
323
|
text,
|
321
|
-
|
324
|
+
MDLess.theme['emphasis']['italic_character'],
|
322
325
|
xc,
|
323
326
|
post_element
|
324
327
|
].join
|
@@ -328,11 +331,11 @@ module Redcarpet
|
|
328
331
|
[
|
329
332
|
pre_element,
|
330
333
|
color('emphasis bold-italic'),
|
331
|
-
|
332
|
-
|
334
|
+
MDLess.theme['emphasis']['italic_character'],
|
335
|
+
MDLess.theme['emphasis']['bold_character'],
|
333
336
|
text,
|
334
|
-
|
335
|
-
|
337
|
+
MDLess.theme['emphasis']['bold_character'],
|
338
|
+
MDLess.theme['emphasis']['italic_character'],
|
336
339
|
xc,
|
337
340
|
post_element
|
338
341
|
].join
|
@@ -528,7 +531,7 @@ module Redcarpet
|
|
528
531
|
lines = input.split(/\n/)
|
529
532
|
line1 = lines.shift
|
530
533
|
pre = ' ' * (indent + 1)
|
531
|
-
cols =
|
534
|
+
cols = MDLess.cols - pre.length
|
532
535
|
body = lines.map { |l| "#{pre}#{l}" }.join("\n")
|
533
536
|
"#{line1}\n#{body}"
|
534
537
|
end
|
@@ -622,8 +625,8 @@ module Redcarpet
|
|
622
625
|
end
|
623
626
|
|
624
627
|
def get_headers(input)
|
625
|
-
unless
|
626
|
-
|
628
|
+
unless @headers && !@headers.empty?
|
629
|
+
@headers = []
|
627
630
|
headers = input.scan(/^((?!#!)(\#{1,6})\s*([^#]+?)(?: #+)?\s*|(\S.+)\n([=-]+))$/i)
|
628
631
|
|
629
632
|
headers.each do |h|
|
@@ -639,7 +642,7 @@ module Redcarpet
|
|
639
642
|
hlevel = h[1].length
|
640
643
|
title = h[2]
|
641
644
|
end
|
642
|
-
|
645
|
+
@headers << [
|
643
646
|
'#' * hlevel,
|
644
647
|
title,
|
645
648
|
h[0]
|
@@ -647,7 +650,7 @@ module Redcarpet
|
|
647
650
|
end
|
648
651
|
end
|
649
652
|
|
650
|
-
|
653
|
+
@headers
|
651
654
|
end
|
652
655
|
|
653
656
|
def color_meta(text)
|
@@ -656,15 +659,15 @@ module Redcarpet
|
|
656
659
|
|
657
660
|
first_line = input.split("\n").first
|
658
661
|
if first_line =~ /(?i-m)^---[ \t]*?$/
|
659
|
-
|
662
|
+
MDLess.log.info('Found YAML')
|
660
663
|
# YAML
|
661
664
|
in_yaml = true
|
662
665
|
input.sub!(/(?i-m)^---[ \t]*\n([\s\S]*?)\n[-.]{3}[ \t]*\n/m) do
|
663
666
|
m = Regexp.last_match
|
664
|
-
|
667
|
+
MDLess.log.info('Processing YAML Header')
|
665
668
|
lines = m[0].split(/\n/)
|
666
669
|
longest = lines.longest_element.length
|
667
|
-
longest = longest <
|
670
|
+
longest = longest < MDLess.cols ? longest + 1 : MDLess.cols
|
668
671
|
lines.map do |line|
|
669
672
|
if line =~ /^[-.]{3}\s*$/
|
670
673
|
line = "#{color('metadata marker')}#{'%' * longest}"
|
@@ -680,13 +683,13 @@ module Redcarpet
|
|
680
683
|
end
|
681
684
|
|
682
685
|
if !in_yaml && first_line =~ /(?i-m)^[\w ]+:\s+\S+/
|
683
|
-
|
686
|
+
MDLess.log.info('Found MMD Headers')
|
684
687
|
input.sub!(/(?i-m)^([\S ]+:[\s\S]*?)+(?=\n\n)/) do |mmd|
|
685
688
|
lines = mmd.split(/\n/)
|
686
689
|
return mmd if lines.count > 20
|
687
690
|
|
688
691
|
longest = lines.inject { |memo, word| memo.length > word.length ? memo : word }.length
|
689
|
-
longest = longest <
|
692
|
+
longest = longest < MDLess.cols ? longest + 1 : MDLess.cols
|
690
693
|
lines.map do |line|
|
691
694
|
line.sub!(/^(.*?:)[ \t]+(\S)/, '\1 \2')
|
692
695
|
line = "#{color('metadata color')}#{line}"
|
@@ -706,14 +709,17 @@ module Redcarpet
|
|
706
709
|
input.gsub!(/^([^\n]+)\n={2,}\s*$/m, "# \\1\n")
|
707
710
|
input.gsub!(/^([^\n]+?)\n-{2,}\s*$/m, "## \\1\n")
|
708
711
|
|
709
|
-
|
712
|
+
@headers = get_headers(input)
|
710
713
|
|
711
|
-
if
|
714
|
+
if MDLess.options[:section]
|
712
715
|
new_content = []
|
713
|
-
|
716
|
+
MDLess.log.info("Matching section(s) #{MDLess.options[:section].join(', ')}")
|
717
|
+
MDLess.options[:section].each do |sect|
|
718
|
+
comparison = MDLess.options[:section][0].is_a?(String) ? :regex : :numeric
|
719
|
+
|
714
720
|
in_section = false
|
715
721
|
top_level = 1
|
716
|
-
input.split(/\n/).
|
722
|
+
input.split(/\n/).each_with_index do |graf, idx|
|
717
723
|
if graf =~ /^(#+) *(.*?)( *#+)?$/
|
718
724
|
m = Regexp.last_match
|
719
725
|
level = m[1].length
|
@@ -725,7 +731,8 @@ module Redcarpet
|
|
725
731
|
in_section = false
|
726
732
|
break
|
727
733
|
end
|
728
|
-
elsif title.downcase
|
734
|
+
elsif (comparison == :regex && title.downcase =~ sect.downcase.to_rx) ||
|
735
|
+
(comparison == :numeric && title.downcase == @headers[sect - 1][1].downcase)
|
729
736
|
in_section = true
|
730
737
|
top_level = level + 1
|
731
738
|
new_content.push(graf)
|
@@ -774,7 +781,7 @@ module Redcarpet
|
|
774
781
|
counter = 1
|
775
782
|
|
776
783
|
grafs.map! do |graf|
|
777
|
-
|
784
|
+
return "\n" if graf =~ /^ *\n$/
|
778
785
|
|
779
786
|
links_added = false
|
780
787
|
|
@@ -784,7 +791,7 @@ module Redcarpet
|
|
784
791
|
content = link[:content]
|
785
792
|
title = link[:title]&.uncolor
|
786
793
|
graf.gsub!(/#{Regexp.escape(link[:link])}/, color_link_reference(url, counter, content))
|
787
|
-
if
|
794
|
+
if MDLess.options[:links] == :paragraph
|
788
795
|
if links_added
|
789
796
|
graf += "\n#{color_reference_link(url, title, counter)}"
|
790
797
|
else
|
@@ -800,7 +807,7 @@ module Redcarpet
|
|
800
807
|
"\n#{graf}\n"
|
801
808
|
end
|
802
809
|
|
803
|
-
if
|
810
|
+
if MDLess.options[:links] == :paragraph
|
804
811
|
grafs.join("\n")
|
805
812
|
else
|
806
813
|
grafs.join("\n") + "\n#{@@footer_links.join("\n")}\n"
|
@@ -820,16 +827,16 @@ module Redcarpet
|
|
820
827
|
input.gsub(%r{<<img>>(.*?)<</img>>}) do
|
821
828
|
link, title, alt_text = Regexp.last_match(1).split(/\|\|/)
|
822
829
|
|
823
|
-
if (exec_available('imgcat') || exec_available('chafa')) &&
|
830
|
+
if (exec_available('imgcat') || exec_available('chafa')) && MDLess.options[:local_images]
|
824
831
|
if exec_available('imgcat')
|
825
|
-
|
832
|
+
MDLess.log.info('Using imgcat for image rendering')
|
826
833
|
elsif exec_available('chafa')
|
827
|
-
|
834
|
+
MDLess.log.info('Using chafa for image rendering')
|
828
835
|
end
|
829
836
|
img_path = link
|
830
|
-
if img_path =~ /^http/ &&
|
837
|
+
if img_path =~ /^http/ && MDLess.options[:remote_images]
|
831
838
|
if exec_available('imgcat')
|
832
|
-
|
839
|
+
MDLess.log.info('Using imgcat for image rendering')
|
833
840
|
begin
|
834
841
|
res, s = Open3.capture2(%(curl -sS "#{img_path}" 2> /dev/null | imgcat))
|
835
842
|
|
@@ -839,10 +846,10 @@ module Redcarpet
|
|
839
846
|
result = pre + res + post
|
840
847
|
end
|
841
848
|
rescue StandardError => e
|
842
|
-
|
849
|
+
MDLess.log.error(e)
|
843
850
|
end
|
844
851
|
elsif exec_available('chafa')
|
845
|
-
|
852
|
+
MDLess.log.info('Using chafa for image rendering')
|
846
853
|
term = '-f sixels'
|
847
854
|
term = ENV['TERMINAL_PROGRAM'] =~ /iterm/i ? '-f iterm' : term
|
848
855
|
term = ENV['TERMINAL_PROGRAM'] =~ /kitty/i ? '-f kitty' : term
|
@@ -858,13 +865,13 @@ module Redcarpet
|
|
858
865
|
Dir.chdir('..')
|
859
866
|
FileUtils.rm_r '.mdless_tmp', force: true
|
860
867
|
else
|
861
|
-
|
868
|
+
MDLess.log.warn('No viewer for remote images')
|
862
869
|
end
|
863
870
|
else
|
864
871
|
if img_path =~ %r{^[~/]}
|
865
872
|
img_path = File.expand_path(img_path)
|
866
|
-
elsif
|
867
|
-
base = File.expand_path(File.dirname(
|
873
|
+
elsif MDLess.file
|
874
|
+
base = File.expand_path(File.dirname(MDLess.file))
|
868
875
|
img_path = File.join(base, img_path)
|
869
876
|
end
|
870
877
|
if File.exist?(img_path)
|
@@ -956,9 +963,9 @@ module Redcarpet
|
|
956
963
|
def postprocess(input)
|
957
964
|
input.scrub!
|
958
965
|
|
959
|
-
input = highlight_wiki_links(input) if
|
966
|
+
input = highlight_wiki_links(input) if MDLess.options[:wiki_links]
|
960
967
|
|
961
|
-
if
|
968
|
+
if MDLess.options[:inline_footnotes]
|
962
969
|
input = insert_footnotes(input)
|
963
970
|
else
|
964
971
|
footnotes = @@footnotes.map.with_index do |fn, i|
|
@@ -975,11 +982,11 @@ module Redcarpet
|
|
975
982
|
# misc html
|
976
983
|
input.gsub!(%r{<br */?>}, "#{pre_element}\n#{post_element}")
|
977
984
|
# format links
|
978
|
-
input = reference_links(input) if
|
985
|
+
input = reference_links(input) if MDLess.options[:links] == :reference || MDLess.options[:links] == :paragraph
|
979
986
|
# lists
|
980
987
|
input = fix_lists(input)
|
981
|
-
input = render_images(input) if
|
982
|
-
input = highlight_tags(input) if
|
988
|
+
input = render_images(input) if MDLess.options[:local_images]
|
989
|
+
input = highlight_tags(input) if MDLess.options[:at_tags] || MDLess.options[:taskpaper]
|
983
990
|
fix_colors(input)
|
984
991
|
end
|
985
992
|
end
|
data/lib/mdless/converter.rb
CHANGED
@@ -4,39 +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
|
|
15
12
|
def default(option, default)
|
16
|
-
if
|
17
|
-
@options[option] = default
|
18
|
-
end
|
13
|
+
MDLess.options[option] = default if MDLess.options[option].nil?
|
19
14
|
end
|
20
15
|
|
21
16
|
def initialize(args)
|
22
|
-
|
23
|
-
@log.level = Logger::WARN
|
17
|
+
MDLess.log.level = Logger::WARN
|
24
18
|
|
25
|
-
|
19
|
+
MDLess.options = {}
|
26
20
|
config = File.expand_path('~/.config/mdless/config.yml')
|
27
|
-
|
21
|
+
MDLess.options = YAML.load(IO.read(config)) if File.exist?(config)
|
28
22
|
|
29
23
|
optparse = OptionParser.new do |opts|
|
30
24
|
opts.banner = "#{version} by Brett Terpstra\n\n> Usage: #{CLIMarkdown::EXECUTABLE_NAME} [options] [path]\n\n"
|
31
25
|
|
32
26
|
default(:color, true)
|
33
27
|
opts.on('-c', '--[no-]color', 'Colorize output (default on)') do |c|
|
34
|
-
|
28
|
+
MDLess.options[:color] = c
|
35
29
|
end
|
36
30
|
|
37
31
|
opts.on('-d', '--debug LEVEL', 'Level of debug messages to output (1-4, 4 to see all messages)') do |level|
|
38
32
|
if level.to_i.positive? && level.to_i < 5
|
39
|
-
|
33
|
+
MDLess.log.level = 5 - level.to_i
|
40
34
|
else
|
41
35
|
puts 'Error: Debug level out of range (1-4)'
|
42
36
|
Process.exit 1
|
@@ -55,58 +49,64 @@ module CLIMarkdown
|
|
55
49
|
if exec_available('imgcat') || exec_available('chafa')
|
56
50
|
case type
|
57
51
|
when /^(r|b|a)/i
|
58
|
-
|
59
|
-
|
52
|
+
MDLess.options[:local_images] = true
|
53
|
+
MDLess.options[:remote_images] = true
|
60
54
|
when /^l/i
|
61
|
-
|
55
|
+
MDLess.options[:local_images] = true
|
62
56
|
when /^n/
|
63
|
-
|
64
|
-
|
57
|
+
MDLess.options[:local_images] = false
|
58
|
+
MDLess.options[:remote_images] = false
|
65
59
|
end
|
66
60
|
else
|
67
|
-
|
61
|
+
MDLess.log.warn('images turned on but imgcat/chafa not found')
|
68
62
|
end
|
69
63
|
end
|
70
64
|
|
71
65
|
opts.on('-I', '--all-images', 'Include local and remote images in output (requires imgcat or chafa)') do
|
72
66
|
if exec_available('imgcat') || exec_available('chafa') # && ENV['TERM_PROGRAM'] == 'iTerm.app'
|
73
|
-
|
74
|
-
|
67
|
+
MDLess.options[:local_images] = true
|
68
|
+
MDLess.options[:remote_images] = true
|
75
69
|
else
|
76
|
-
|
70
|
+
MDLess.log.warn('images turned on but imgcat/chafa not found')
|
77
71
|
end
|
78
72
|
end
|
79
73
|
|
80
|
-
|
81
74
|
default(:list, false)
|
82
75
|
opts.on('-l', '--list', 'List headers in document and exit') do
|
83
|
-
|
76
|
+
MDLess.options[:list] = true
|
84
77
|
end
|
85
78
|
|
86
79
|
default(:pager, true)
|
87
80
|
opts.on('-p', '--[no-]pager', 'Formatted output to pager (default on)') do |p|
|
88
|
-
|
81
|
+
MDLess.options[:pager] = p
|
89
82
|
end
|
90
83
|
|
91
84
|
default(:pager, true)
|
92
85
|
opts.on('-P', 'Disable pager (same as --no-pager)') do
|
93
|
-
|
86
|
+
MDLess.options[:pager] = false
|
94
87
|
end
|
95
88
|
|
96
89
|
default(:section, nil)
|
97
90
|
opts.on('-s', '--section=NUMBER[,NUMBER]',
|
98
91
|
'Output only a headline-based section of the input (numeric from --list)') do |section|
|
99
|
-
|
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
|
100
100
|
end
|
101
101
|
|
102
102
|
default(:theme, 'default')
|
103
103
|
opts.on('-t', '--theme=THEME_NAME', 'Specify an alternate color theme to load') do |theme|
|
104
|
-
|
104
|
+
MDLess.options[:theme] = theme
|
105
105
|
end
|
106
106
|
|
107
107
|
default(:at_tags, false)
|
108
108
|
opts.on('-@', '--at_tags', 'Highlight @tags and values in the document') do
|
109
|
-
|
109
|
+
MDLess.options[:at_tags] = true
|
110
110
|
end
|
111
111
|
|
112
112
|
opts.on('-v', '--version', 'Display version number') do
|
@@ -116,37 +116,37 @@ module CLIMarkdown
|
|
116
116
|
|
117
117
|
default(:width, TTY::Screen.cols)
|
118
118
|
opts.on('-w', '--width=COLUMNS', 'Column width to format for (default: terminal width)') do |columns|
|
119
|
-
|
119
|
+
MDLess.options[:width] = columns.to_i
|
120
120
|
end
|
121
121
|
cols = TTY::Screen.cols
|
122
|
-
|
122
|
+
MDLess.options[:width] = cols if MDLess.options[:width] > cols
|
123
123
|
|
124
124
|
default(:autolink, true)
|
125
125
|
opts.on('--[no-]autolink', 'Convert bare URLs and emails to <links>') do |p|
|
126
|
-
|
126
|
+
MDLess.options[:autolink] = p
|
127
127
|
end
|
128
128
|
|
129
129
|
default(:inline_footnotes, false)
|
130
130
|
opts.on('--[no-]inline_footnotes',
|
131
131
|
'Display footnotes immediately after the paragraph that references them') do |p|
|
132
|
-
|
132
|
+
MDLess.options[:inline_footnotes] = p
|
133
133
|
end
|
134
134
|
|
135
135
|
default(:intra_emphasis, true)
|
136
136
|
opts.on('--[no-]intra-emphasis', 'Parse emphasis inside of words (e.g. Mark_down_)') do |opt|
|
137
|
-
|
137
|
+
MDLess.options[:intra_emphasis] = opt
|
138
138
|
end
|
139
139
|
|
140
140
|
default(:lax_spacing, true)
|
141
141
|
opts.on('--[no-]lax-spacing', 'Allow lax spacing') do |opt|
|
142
|
-
|
142
|
+
MDLess.options[:lax_spacing] = opt
|
143
143
|
end
|
144
144
|
|
145
145
|
default(:links, :inline)
|
146
146
|
opts.on('--links=FORMAT',
|
147
147
|
'Link style ([inline, reference, paragraph], default inline,
|
148
148
|
"paragraph" will position reference links after each paragraph)') do |fmt|
|
149
|
-
|
149
|
+
MDLess.options[:links] = case fmt
|
150
150
|
when /^:?r/i
|
151
151
|
:reference
|
152
152
|
when /^:?p/i
|
@@ -158,11 +158,11 @@ module CLIMarkdown
|
|
158
158
|
|
159
159
|
default(:syntax_higlight, false)
|
160
160
|
opts.on('--[no-]syntax', 'Syntax highlight code blocks') do |p|
|
161
|
-
|
161
|
+
MDLess.options[:syntax_higlight] = p
|
162
162
|
end
|
163
163
|
|
164
|
-
|
165
|
-
case
|
164
|
+
MDLess.options[:taskpaper] = if MDLess.options[:taskpaper]
|
165
|
+
case MDLess.options[:taskpaper].to_s
|
166
166
|
when /^[ty1]/
|
167
167
|
true
|
168
168
|
when /^a/
|
@@ -174,7 +174,7 @@ module CLIMarkdown
|
|
174
174
|
false
|
175
175
|
end
|
176
176
|
opts.on('--taskpaper=OPTION', 'Highlight TaskPaper format (true|false|auto)') do |tp|
|
177
|
-
|
177
|
+
MDLess.options[:taskpaper] = case tp
|
178
178
|
when /^[ty1]/
|
179
179
|
true
|
180
180
|
when /^a/
|
@@ -186,12 +186,12 @@ module CLIMarkdown
|
|
186
186
|
|
187
187
|
default(:update_config, false)
|
188
188
|
opts.on('--update_config', 'Update the configuration file with new keys and current command line options') do
|
189
|
-
|
189
|
+
MDLess.options[:update_config] = true
|
190
190
|
end
|
191
191
|
|
192
192
|
default(:wiki_links, false)
|
193
193
|
opts.on('--[no-]wiki-links', 'Highlight [[wiki links]]') do |opt|
|
194
|
-
|
194
|
+
MDLess.options[:wiki_links] = opt
|
195
195
|
end
|
196
196
|
end
|
197
197
|
|
@@ -202,20 +202,20 @@ module CLIMarkdown
|
|
202
202
|
exit 1
|
203
203
|
end
|
204
204
|
|
205
|
-
if !File.exist?(config) ||
|
205
|
+
if !File.exist?(config) || MDLess.options[:update_config]
|
206
206
|
FileUtils.mkdir_p(File.dirname(config))
|
207
207
|
File.open(config, 'w') do |f|
|
208
|
-
opts =
|
208
|
+
opts = MDLess.options.dup
|
209
209
|
opts.delete(:list)
|
210
210
|
opts.delete(:section)
|
211
211
|
opts.delete(:update_config)
|
212
|
+
opts = opts.keys.map(&:to_s).sort.map { |k| [k.to_sym, opts[k.to_sym]] }.to_h
|
212
213
|
f.puts YAML.dump(opts)
|
213
214
|
warn "Config file saved to #{config}"
|
214
215
|
end
|
215
216
|
end
|
216
217
|
|
217
|
-
|
218
|
-
@cols = @options[:width] - 2
|
218
|
+
MDLess.cols = MDLess.options[:width] - 2
|
219
219
|
|
220
220
|
@output = ''
|
221
221
|
@headers = []
|
@@ -226,19 +226,15 @@ module CLIMarkdown
|
|
226
226
|
@footnotes = {}
|
227
227
|
|
228
228
|
renderer = Redcarpet::Render::Console.new
|
229
|
-
renderer.theme = @theme
|
230
|
-
renderer.cols = @cols
|
231
|
-
renderer.log = @log
|
232
|
-
renderer.options = @options
|
233
229
|
|
234
230
|
markdown = Redcarpet::Markdown.new(renderer,
|
235
|
-
no_intra_emphasis:
|
236
|
-
autolink:
|
231
|
+
no_intra_emphasis: !MDLess.options[:intra_emphasis],
|
232
|
+
autolink: MDLess.options[:autolink],
|
237
233
|
fenced_code_blocks: true,
|
238
234
|
footnotes: true,
|
239
235
|
hard_wrap: false,
|
240
236
|
highlight: true,
|
241
|
-
lax_spacing:
|
237
|
+
lax_spacing: MDLess.options[:lax_spacing],
|
242
238
|
quote: false,
|
243
239
|
space_after_headers: false,
|
244
240
|
strikethrough: true,
|
@@ -249,9 +245,9 @@ module CLIMarkdown
|
|
249
245
|
if !args.empty?
|
250
246
|
files = args.delete_if { |f| !File.exist?(f) }
|
251
247
|
files.each do |file|
|
252
|
-
|
253
|
-
|
254
|
-
|
248
|
+
MDLess.log.info(%(Processing "#{file}"))
|
249
|
+
MDLess.file = file
|
250
|
+
|
255
251
|
begin
|
256
252
|
input = IO.read(file).force_encoding('utf-8')
|
257
253
|
rescue StandardError
|
@@ -262,26 +258,27 @@ module CLIMarkdown
|
|
262
258
|
input.scrub!
|
263
259
|
input.gsub!(/\r?\n/, "\n")
|
264
260
|
|
265
|
-
if
|
266
|
-
|
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
|
267
276
|
Process.exit 0
|
268
277
|
else
|
269
|
-
if
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
elsif CLIMarkdown::TaskPaper.is_taskpaper?(input)
|
274
|
-
@log.info('TaskPaper document detected')
|
275
|
-
true
|
276
|
-
else
|
277
|
-
false
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
if @options[:taskpaper]
|
282
|
-
input = input.color_meta(@theme, @log, @cols)
|
283
|
-
input = CLIMarkdown::TaskPaper.highlight(input, @theme)
|
284
|
-
@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
|
285
282
|
else
|
286
283
|
@output = markdown.render(input)
|
287
284
|
end
|
@@ -289,14 +286,34 @@ module CLIMarkdown
|
|
289
286
|
end
|
290
287
|
printout
|
291
288
|
elsif !$stdin.isatty
|
292
|
-
|
289
|
+
MDLess.file = nil
|
293
290
|
input = $stdin.read.scrub
|
294
291
|
input.gsub!(/\r?\n/, "\n")
|
295
|
-
|
296
|
-
|
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
|
297
308
|
Process.exit 0
|
298
309
|
else
|
299
|
-
|
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
|
300
317
|
end
|
301
318
|
printout
|
302
319
|
else
|
@@ -308,17 +325,17 @@ module CLIMarkdown
|
|
308
325
|
def color(key)
|
309
326
|
val = nil
|
310
327
|
keys = key.split(/[ ,>]/)
|
311
|
-
if
|
312
|
-
val =
|
328
|
+
if MDLess.theme.key?(keys[0])
|
329
|
+
val = MDLess.theme[keys.shift]
|
313
330
|
else
|
314
|
-
|
331
|
+
MDLess.log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
|
315
332
|
return c([:reset])
|
316
333
|
end
|
317
334
|
keys.each do |k|
|
318
335
|
if val.key?(k)
|
319
336
|
val = val[k]
|
320
337
|
else
|
321
|
-
|
338
|
+
MDLess.log.error("Invalid theme key: #{k}")
|
322
339
|
return c([:reset])
|
323
340
|
end
|
324
341
|
end
|
@@ -410,8 +427,8 @@ module CLIMarkdown
|
|
410
427
|
|
411
428
|
def clean_markers(input)
|
412
429
|
input.gsub!(/^(\e\[[\d;]+m)?[%~] ?/, '\1')
|
413
|
-
input.gsub!(/^(\e\[[\d;]+m)*>(\e\[[\d;]+m)?( +)/, ' \3\1\2')
|
414
|
-
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')
|
415
432
|
input.gsub!(/(\e\[[\d;]+m)?@@@(\e\[[\d;]+m)?$/, '')
|
416
433
|
input
|
417
434
|
end
|
@@ -440,7 +457,7 @@ module CLIMarkdown
|
|
440
457
|
block.split(/\n/).map do |l|
|
441
458
|
new_code_line = l.gsub(/\t/, ' ')
|
442
459
|
orig_length = new_code_line.size + 8 + eol.size
|
443
|
-
pad_count = [
|
460
|
+
pad_count = [MDLess.cols - orig_length, 0].max
|
444
461
|
|
445
462
|
[
|
446
463
|
new_code_line,
|
@@ -464,11 +481,11 @@ module CLIMarkdown
|
|
464
481
|
IO.select [input]
|
465
482
|
|
466
483
|
pager = which_pager
|
467
|
-
|
484
|
+
MDLess.log.info("Using #{pager} as pager")
|
468
485
|
begin
|
469
486
|
exec(pager.join(' '))
|
470
487
|
rescue SystemCallError => e
|
471
|
-
|
488
|
+
MDLess.log.error(e)
|
472
489
|
exit 1
|
473
490
|
end
|
474
491
|
end
|
@@ -486,21 +503,25 @@ module CLIMarkdown
|
|
486
503
|
end
|
487
504
|
|
488
505
|
def printout
|
489
|
-
|
490
|
-
|
491
|
-
|
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
|
492
513
|
|
493
514
|
unless out.size&.positive?
|
494
|
-
|
515
|
+
MDLess.log.warn 'No results'
|
495
516
|
Process.exit
|
496
517
|
end
|
497
518
|
|
498
519
|
out = clean_markers(out)
|
499
520
|
out = "#{out.gsub(/\n{2,}/m, "\n\n")}#{xc}"
|
500
521
|
|
501
|
-
out.uncolor! unless
|
522
|
+
out.uncolor! unless MDLess.options[:color]
|
502
523
|
|
503
|
-
if
|
524
|
+
if MDLess.options[:pager]
|
504
525
|
page(out)
|
505
526
|
else
|
506
527
|
$stdout.print out.rstrip
|
@@ -523,7 +544,7 @@ module CLIMarkdown
|
|
523
544
|
if f.strip =~ /[ |]/
|
524
545
|
f
|
525
546
|
elsif f == 'most'
|
526
|
-
|
547
|
+
MDLess.log.warn('most not allowed as pager')
|
527
548
|
false
|
528
549
|
else
|
529
550
|
system "which #{f}", out: File::NULL, err: File::NULL
|
data/lib/mdless/string.rb
CHANGED
@@ -12,20 +12,20 @@ class ::String
|
|
12
12
|
replace clean_empty_lines
|
13
13
|
end
|
14
14
|
|
15
|
-
def color(key
|
15
|
+
def color(key)
|
16
16
|
val = nil
|
17
17
|
keys = key.split(/[ ,>]/)
|
18
|
-
if theme.key?(keys[0])
|
19
|
-
val = theme[keys.shift]
|
18
|
+
if MDLess.theme.key?(keys[0])
|
19
|
+
val = MDLess.theme[keys.shift]
|
20
20
|
else
|
21
|
-
log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
|
21
|
+
MDLess.log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
|
22
22
|
return c([:reset])
|
23
23
|
end
|
24
24
|
keys.each do |k|
|
25
25
|
if val.key?(k)
|
26
26
|
val = val[k]
|
27
27
|
else
|
28
|
-
log.error("Invalid theme key: #{k}")
|
28
|
+
MDLess.log.error("Invalid theme key: #{k}")
|
29
29
|
return c([:reset])
|
30
30
|
end
|
31
31
|
end
|
@@ -38,9 +38,21 @@ class ::String
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
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)
|
44
56
|
@cols = cols
|
45
57
|
input = dup
|
46
58
|
input.clean_empty_lines!
|
@@ -48,21 +60,21 @@ class ::String
|
|
48
60
|
in_yaml = false
|
49
61
|
first_line = input.split("\n").first
|
50
62
|
if first_line =~ /(?i-m)^---[ \t]*?$/
|
51
|
-
|
63
|
+
MDLess.log.info('Found YAML')
|
52
64
|
# YAML
|
53
65
|
in_yaml = true
|
54
66
|
input.sub!(/(?i-m)^---[ \t]*\n([\s\S]*?)\n[-.]{3}[ \t]*\n/m) do
|
55
67
|
m = Regexp.last_match
|
56
|
-
|
68
|
+
MDLess.log.info('Processing YAML Header')
|
57
69
|
lines = m[0].split(/\n/)
|
58
70
|
longest = lines.inject { |memo, word| memo.length > word.length ? memo : word }.length
|
59
71
|
longest = longest < @cols ? longest + 1 : @cols
|
60
72
|
lines.map do |line|
|
61
73
|
if line =~ /^[-.]{3}\s*$/
|
62
|
-
line = "#{color('metadata marker'
|
74
|
+
line = "#{color('metadata marker')}#{'%' * longest}"
|
63
75
|
else
|
64
76
|
line.sub!(/^(.*?:)[ \t]+(\S)/, '\1 \2')
|
65
|
-
line = "#{color('metadata color'
|
77
|
+
line = "#{color('metadata color')}#{line}"
|
66
78
|
end
|
67
79
|
|
68
80
|
line += "\u00A0" * (longest - line.uncolor.strip.length) + xc
|
@@ -72,7 +84,7 @@ class ::String
|
|
72
84
|
end
|
73
85
|
|
74
86
|
if !in_yaml && first_line =~ /(?i-m)^[\w ]+:\s+\S+/
|
75
|
-
|
87
|
+
MDLess.log.info('Found MMD Headers')
|
76
88
|
input.sub!(/(?i-m)^([\S ]+:[\s\S]*?)+(?=\n\n)/) do |mmd|
|
77
89
|
lines = mmd.split(/\n/)
|
78
90
|
return mmd if lines.count > 20
|
@@ -81,7 +93,7 @@ class ::String
|
|
81
93
|
longest = longest < @cols ? longest + 1 : @cols
|
82
94
|
lines.map do |line|
|
83
95
|
line.sub!(/^(.*?:)[ \t]+(\S)/, '\1 \2')
|
84
|
-
line = "#{color('metadata color'
|
96
|
+
line = "#{color('metadata color')}#{line}"
|
85
97
|
line += "\u00A0" * (longest - line.uncolor.strip.length)
|
86
98
|
line + xc
|
87
99
|
end.join("\n") + "#{"\u00A0" * longest}#{xc}\n"
|
@@ -91,9 +103,10 @@ class ::String
|
|
91
103
|
input
|
92
104
|
end
|
93
105
|
|
94
|
-
def highlight_tags
|
95
|
-
|
96
|
-
|
106
|
+
def highlight_tags
|
107
|
+
log = MDLess.log
|
108
|
+
tag_color = color('at_tags tag')
|
109
|
+
value_color = color('at_tags value')
|
97
110
|
gsub(/(?<pre>\s|m)(?<tag>@[^ \].?!,("']+)(?:(?<lparen>\()(?<value>.*?)(?<rparen>\)))?/) do
|
98
111
|
m = Regexp.last_match
|
99
112
|
last_color = m.pre_match.last_color_code
|
@@ -122,20 +135,13 @@ class ::String
|
|
122
135
|
|
123
136
|
def valid_pygments_theme?
|
124
137
|
return false unless TTY::Which.exist?('pygmentize')
|
125
|
-
|
126
|
-
|
127
|
-
styles = res.scan(/\* ([\w-]+):/).map { |l| l[0] }
|
128
|
-
styles.include?(self)
|
138
|
+
|
139
|
+
MDLess.pygments_styles.include?(self)
|
129
140
|
end
|
130
141
|
|
131
142
|
def valid_lexer?
|
132
143
|
return false unless TTY::Which.exist?('pygmentize')
|
133
|
-
pyg = TTY::Which.which('pygmentize')
|
134
|
-
res = `#{pyg} -L lexers`
|
135
|
-
lexers = res.scan(/\* ([\w-]+(?:, [\w-]+)*):/).map { |l| l[0] }
|
136
|
-
lexers_a = []
|
137
|
-
lexers.each { |l| lexers_a.concat(l.split(/, /)) }
|
138
144
|
|
139
|
-
|
145
|
+
MDLess.pygments_lexers.include?(self.downcase)
|
140
146
|
end
|
141
147
|
end
|
data/lib/mdless/taskpaper.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module CLIMarkdown
|
4
4
|
module TaskPaper
|
5
5
|
TASK_RX = /^(?<indent>(?: |\t)*?)(?<marker>-)(?<task>\s+\S.*?)$/
|
6
|
-
PROJECT_RX = /^(?<indent>(?: |\t)*?)(?<project>[^- \t].*?:)(?<tags>
|
6
|
+
PROJECT_RX = /^(?<indent>(?: |\t)*?)(?<project>[^- \t].*?:)(?<tags> +@\S+)*$/
|
7
7
|
NOTE_RX = /^(?<indent>(?: |\t)+)(?<note>(?<!- ).*?(?!:))$/
|
8
8
|
|
9
9
|
class << self
|
@@ -13,8 +13,8 @@ module CLIMarkdown
|
|
13
13
|
def color(key)
|
14
14
|
val = nil
|
15
15
|
keys = key.split(/[ ,>]/)
|
16
|
-
if
|
17
|
-
val =
|
16
|
+
if MDLess.theme.key?(keys[0])
|
17
|
+
val = MDLess.theme[keys.shift]
|
18
18
|
else
|
19
19
|
@log.error("Invalid theme key: #{key}") unless keys[0] =~ /^text/
|
20
20
|
return c([:reset])
|
@@ -36,25 +36,95 @@ module CLIMarkdown
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
def remove_meta(input)
|
40
|
+
first_line = input.split("\n").first
|
41
|
+
if first_line =~ /(?i-m)^---[ \t]*?$/
|
42
|
+
input.sub!(/(?im)^---[ \t]*\n([\s\S]*?)\n[-.]{3}[ \t]*\n/, '')
|
43
|
+
elsif first_line =~ /(?i-m)^[\w ]+:\s+\S+/
|
44
|
+
input.sub!(/(?im)^([\S ]+:[\s\S]*?)+(?=\n\n)/, '')
|
45
|
+
end
|
46
|
+
input
|
47
|
+
end
|
48
|
+
|
39
49
|
def is_taskpaper?(input)
|
40
|
-
|
50
|
+
return true if MDLess.file =~ /\.taskpaper$/
|
51
|
+
|
52
|
+
projects = sections(input)
|
53
|
+
|
41
54
|
tasks = 0
|
42
55
|
if projects.count > 1
|
43
|
-
projects.each do |proj|
|
44
|
-
tasks +=
|
56
|
+
projects.each do |proj, content|
|
57
|
+
tasks += content['content'].scan(TASK_RX).count
|
45
58
|
end
|
46
59
|
end
|
47
60
|
|
48
|
-
tasks >= 6
|
61
|
+
if tasks >= 6
|
62
|
+
return true
|
63
|
+
else
|
64
|
+
tst = remove_meta(input)
|
65
|
+
tst = tst.gsub(PROJECT_RX, '')
|
66
|
+
tst = tst.gsub(TASK_RX, '')
|
67
|
+
tst = tst.gsub(/^ *\n$/, '')
|
68
|
+
return tst.strip.length == 0
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def section(input, string)
|
73
|
+
sects = sections(input)
|
74
|
+
sects_to_s(sects.filter { |k, _| k.downcase =~ string.downcase.to_rx })
|
75
|
+
end
|
76
|
+
|
77
|
+
def sects_to_s(sects)
|
78
|
+
sects.map do |k, v|
|
79
|
+
"#{k}#{v['content']}"
|
80
|
+
end.join("\n")
|
81
|
+
end
|
82
|
+
|
83
|
+
def indent(input, idnt)
|
84
|
+
input.split(/\n/).map do |line|
|
85
|
+
line.sub(/^#{idnt}/, '')
|
86
|
+
end.join("\n")
|
87
|
+
end
|
88
|
+
|
89
|
+
def sections(input)
|
90
|
+
heirarchy = {}
|
91
|
+
sects = input.to_enum(:scan, /(?mix)
|
92
|
+
(?<=\n|\A)(?<indent>(?: |\t)*?)
|
93
|
+
(?<project>[^- \t\n].*?:)\s*(?=\n)
|
94
|
+
(?<content>.*?)
|
95
|
+
(?=\n\k<indent>\S.*?:|\Z)$/).map { Regexp.last_match }
|
96
|
+
sects.each do |sect|
|
97
|
+
heirarchy[sect['project']] = {}
|
98
|
+
heirarchy[sect['project']]['content'] = indent(sect['content'], sect['indent'])
|
99
|
+
heirarchy = heirarchy.merge(sections(sect['content']))
|
100
|
+
end
|
101
|
+
|
102
|
+
heirarchy
|
49
103
|
end
|
50
104
|
|
51
|
-
def
|
52
|
-
|
105
|
+
def list_projects(input)
|
106
|
+
projects = input.to_enum(:scan, PROJECT_RX).map { Regexp.last_match }
|
107
|
+
projects.delete_if { |proj| proj['project'] =~ /^[ \n]*$/ }
|
108
|
+
projects.map! { |proj| "#{color('taskpaper marker')}#{proj['indent']}- #{color('taskpaper project')}#{proj['project'].sub(/:$/, '')}" }
|
109
|
+
projects.join("\n")
|
110
|
+
end
|
111
|
+
|
112
|
+
def highlight(input)
|
53
113
|
mc = color('taskpaper marker')
|
54
114
|
tc = color('taskpaper task')
|
55
115
|
pc = color('taskpaper project')
|
56
116
|
nc = color('taskpaper note')
|
57
117
|
|
118
|
+
if MDLess.options[:section]
|
119
|
+
matches = []
|
120
|
+
MDLess.options[:section].each do |sect|
|
121
|
+
matches << section(input, sect)
|
122
|
+
end
|
123
|
+
input = matches.join("\n")
|
124
|
+
end
|
125
|
+
|
126
|
+
input.gsub!(/\t/, ' ')
|
127
|
+
|
58
128
|
input.gsub!(PROJECT_RX) do
|
59
129
|
m = Regexp.last_match
|
60
130
|
"#{m['indent']}#{pc}#{m['project']}#{m['tags']}"
|
data/lib/mdless/version.rb
CHANGED
data/lib/mdless.rb
CHANGED
@@ -20,3 +20,47 @@ require 'mdless/converter'
|
|
20
20
|
module CLIMarkdown
|
21
21
|
EXECUTABLE_NAME = 'mdless'
|
22
22
|
end
|
23
|
+
|
24
|
+
module MDLess
|
25
|
+
class << self
|
26
|
+
include CLIMarkdown::Theme
|
27
|
+
attr_accessor :options, :cols, :file
|
28
|
+
|
29
|
+
def log
|
30
|
+
@log ||= Logger.new($stderr)
|
31
|
+
end
|
32
|
+
|
33
|
+
def log_level(level)
|
34
|
+
@log.level = level
|
35
|
+
end
|
36
|
+
|
37
|
+
def theme
|
38
|
+
@theme ||= load_theme(@options[:theme])
|
39
|
+
end
|
40
|
+
|
41
|
+
def pygments_styles
|
42
|
+
@pygments_styles ||= read_pygments_styles
|
43
|
+
end
|
44
|
+
|
45
|
+
def pygments_lexers
|
46
|
+
@pygments_lexers ||= read_pygments_lexers
|
47
|
+
end
|
48
|
+
|
49
|
+
def read_pygments_styles
|
50
|
+
MDLess.log.info 'Reading Pygments styles'
|
51
|
+
pyg = TTY::Which.which('pygmentize')
|
52
|
+
res = `#{pyg} -L styles`
|
53
|
+
res.scan(/\* ([\w-]+):/).map { |l| l[0] }
|
54
|
+
end
|
55
|
+
|
56
|
+
def read_pygments_lexers
|
57
|
+
MDLess.log.info 'Reading Pygments lexers'
|
58
|
+
pyg = TTY::Which.which('pygmentize')
|
59
|
+
res = `#{pyg} -L lexers`
|
60
|
+
lexers = res.scan(/\* ([\w-]+(?:, [\w-]+)*):/).map { |l| l[0] }
|
61
|
+
lexers_a = []
|
62
|
+
lexers.each { |l| lexers_a.concat(l.split(/, /)) }
|
63
|
+
lexers_a
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mdless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redcarpet
|