mdless 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +56 -0
- data/bin/mdless +17 -0
- data/lib/helpers/formattables.py +105 -0
- data/lib/mdless/colors.rb +124 -0
- data/lib/mdless/converter.rb +781 -0
- data/lib/mdless/version.rb +3 -0
- data/lib/mdless.rb +12 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f01cf923665ca168bdbe40383e7182173c9b3427
|
4
|
+
data.tar.gz: 9d9e98c876836336fa9f789997db7c939b931948
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 26539f402a4c058de79aa8539e8872fc12a8c564380f58231a5ec4f8df637740c3f48e336c71bdc88c1cb6f289424c7fc05cad5d33cea284896525d83082885c
|
7
|
+
data.tar.gz: 885ca66c4586a75d3aabef7a7cd50c6f692864e298f8041be6abf3270746e92916ddfb29012dc0cf2d2f180f206651a5e6252cea91758d2beb5cae876c19066b
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
# mdless
|
3
|
+
|
4
|
+
|
5
|
+
`mdless` is a utility that provides a formatted and highlighted view of Markdown files in Terminal.
|
6
|
+
|
7
|
+
I often use iTerm2 in visor mode, so `qlmanage -p` is annoying. I still wanted a way to view Markdown files quickly and without cruft.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- Built in pager functionality with pipe capability, `less` replacement for Markdown files
|
12
|
+
- Format tables
|
13
|
+
- Colorize Markdown syntax for most elements
|
14
|
+
- Normalize spacing and link formatting
|
15
|
+
- Display footnotes after each paragraph
|
16
|
+
- Inline image display (local, optionally remote) if using iTerm2 2.9+
|
17
|
+
- Syntax highlighting when `pygmentize` is available
|
18
|
+
- List headlines in document
|
19
|
+
- Display section of the document via headline search
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
gem install mdless
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
`mdless [options] path` or `cat [path] | mdless`
|
28
|
+
|
29
|
+
The pager used is determined by system configuration in this order of preference:
|
30
|
+
|
31
|
+
* `$GIT_PAGER`
|
32
|
+
* `$PAGER`
|
33
|
+
* `git config --get-all core.pager`
|
34
|
+
* `less`
|
35
|
+
* `more`
|
36
|
+
* `cat`
|
37
|
+
* `pager`
|
38
|
+
|
39
|
+
### Options
|
40
|
+
|
41
|
+
-s, --section=TITLE Output only a headline-based section of
|
42
|
+
the input
|
43
|
+
-w, --width=COLUMNS Column width to format for (default 80)
|
44
|
+
-p, --[no-]pager Formatted output to pager (default on)
|
45
|
+
-P Disable pager (same as --no-pager)
|
46
|
+
-c, --[no-]color Colorize output (default on)
|
47
|
+
-l, --list List headers in document and exit
|
48
|
+
-i, --images=TYPE Include [local|remote (both)] images in
|
49
|
+
output (requires imgcat and iTerm2,
|
50
|
+
default NONE)
|
51
|
+
-I, --all-images Include local and remote images in output
|
52
|
+
-h, --help Display this screen
|
53
|
+
-v, --version Display version number
|
54
|
+
|
55
|
+
|
56
|
+
|
data/bin/mdless
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
require 'mdless'
|
4
|
+
|
5
|
+
def class_exists?(class_name)
|
6
|
+
klass = Module.const_get(class_name)
|
7
|
+
return klass.is_a?(Class)
|
8
|
+
rescue NameError
|
9
|
+
return false
|
10
|
+
end
|
11
|
+
|
12
|
+
if class_exists? 'Encoding'
|
13
|
+
Encoding.default_external = Encoding::UTF_8 if Encoding.respond_to?('default_external')
|
14
|
+
Encoding.default_internal = Encoding::UTF_8 if Encoding.respond_to?('default_internal')
|
15
|
+
end
|
16
|
+
|
17
|
+
CLIMarkdown::Converter.new(ARGV)
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/python
|
2
|
+
|
3
|
+
import sys
|
4
|
+
import re
|
5
|
+
|
6
|
+
def just(string, type, n):
|
7
|
+
"Justify a string to length n according to type."
|
8
|
+
|
9
|
+
if type == '::':
|
10
|
+
return string.center(n)
|
11
|
+
elif type == '-:':
|
12
|
+
return string.rjust(n)
|
13
|
+
elif type == ':-':
|
14
|
+
return string.ljust(n)
|
15
|
+
else:
|
16
|
+
return string
|
17
|
+
|
18
|
+
|
19
|
+
def normtable(text):
|
20
|
+
"Aligns the vertical bars in a text table."
|
21
|
+
|
22
|
+
# Start by turning the text into a list of lines.
|
23
|
+
lines = text.splitlines()
|
24
|
+
rows = len(lines)
|
25
|
+
|
26
|
+
# Figure out the cell formatting.
|
27
|
+
# First, find the separator line.
|
28
|
+
for i in range(rows):
|
29
|
+
if set(lines[i]).issubset('|:.-'):
|
30
|
+
formatline = lines[i]
|
31
|
+
formatrow = i
|
32
|
+
break
|
33
|
+
|
34
|
+
# Delete the separator line from the content.
|
35
|
+
del lines[formatrow]
|
36
|
+
|
37
|
+
# Determine how each column is to be justified.
|
38
|
+
formatline = formatline.strip(' ')
|
39
|
+
if formatline[0] == '|': formatline = formatline[1:]
|
40
|
+
if formatline[-1] == '|': formatline = formatline[:-1]
|
41
|
+
fstrings = formatline.split('|')
|
42
|
+
justify = []
|
43
|
+
for cell in fstrings:
|
44
|
+
ends = cell[0] + cell[-1]
|
45
|
+
if ends == '::':
|
46
|
+
justify.append('::')
|
47
|
+
elif ends == '-:':
|
48
|
+
justify.append('-:')
|
49
|
+
else:
|
50
|
+
justify.append(':-')
|
51
|
+
|
52
|
+
# Assume the number of columns in the separator line is the number
|
53
|
+
# for the entire table.
|
54
|
+
columns = len(justify)
|
55
|
+
|
56
|
+
# Extract the content into a matrix.
|
57
|
+
content = []
|
58
|
+
for line in lines:
|
59
|
+
line = line.strip(' ')
|
60
|
+
if line[0] == '|': line = line[1:]
|
61
|
+
if line[-1] == '|': line = line[:-1]
|
62
|
+
cells = line.split('|')
|
63
|
+
# Put exactly one space at each end as "bumpers."
|
64
|
+
linecontent = [ ' ' + x.strip() + ' ' for x in cells ]
|
65
|
+
content.append(linecontent)
|
66
|
+
|
67
|
+
# Append cells to rows that don't have enough.
|
68
|
+
rows = len(content)
|
69
|
+
for i in range(rows):
|
70
|
+
while len(content[i]) < columns:
|
71
|
+
content[i].append('')
|
72
|
+
|
73
|
+
# Get the width of the content in each column. The minimum width will
|
74
|
+
# be 2, because that's the shortest length of a formatting string and
|
75
|
+
# because that matches an empty column with "bumper" spaces.
|
76
|
+
widths = [2] * columns
|
77
|
+
for row in content:
|
78
|
+
for i in range(columns):
|
79
|
+
widths[i] = max(len(row[i]), widths[i])
|
80
|
+
|
81
|
+
# Add whitespace to make all the columns the same width and
|
82
|
+
formatted = []
|
83
|
+
for row in content:
|
84
|
+
formatted.append('|' + '|'.join([ just(s, t, n) for (s, t, n) in zip(row, justify, widths) ]) + '|')
|
85
|
+
|
86
|
+
# Recreate the format line with the appropriate column widths.
|
87
|
+
formatline = '|' + '|'.join([ s[0] + '-'*(n-2) + s[-1] for (s, n) in zip(justify, widths) ]) + '|'
|
88
|
+
|
89
|
+
# Insert the formatline back into the table.
|
90
|
+
formatted.insert(formatrow, formatline)
|
91
|
+
|
92
|
+
# Return the formatted table.
|
93
|
+
return '\n'.join(formatted)
|
94
|
+
|
95
|
+
space_cleaner = r'''(?s)[ \t]*(?P<value>[\|:])[ \t]*'''
|
96
|
+
|
97
|
+
def whitespaceProcess(group_object):
|
98
|
+
return group_object.group('value')
|
99
|
+
|
100
|
+
|
101
|
+
# Read the input, process, and print.
|
102
|
+
unformatted = sys.stdin.read()
|
103
|
+
unformatted = re.sub(space_cleaner, whitespaceProcess, unformatted, flags=re.DOTALL)
|
104
|
+
|
105
|
+
print normtable(unformatted)
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module CLIMarkdown
|
2
|
+
module Colors
|
3
|
+
|
4
|
+
def uncolor
|
5
|
+
self.gsub(/\e\[[\d;]+m/,'')
|
6
|
+
end
|
7
|
+
|
8
|
+
def uncolor!
|
9
|
+
self.replace self.uncolor
|
10
|
+
end
|
11
|
+
|
12
|
+
def size_clean
|
13
|
+
self.uncolor.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def wrap(width=78)
|
17
|
+
|
18
|
+
if self.uncolor =~ /(^([%~] |\s*>)| +[=\-]{5,})/
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
|
22
|
+
visible_width = 0
|
23
|
+
lines = []
|
24
|
+
line = ''
|
25
|
+
last_ansi = ''
|
26
|
+
|
27
|
+
line += self.match(/^\s*/)[0].gsub(/\t/,' ')
|
28
|
+
input = self.dup # .gsub(/(\w-)(\w)/,'\1 \2')
|
29
|
+
input.split(/\s+/).each do |word|
|
30
|
+
last_ansi = line.scan(/\e\[[\d;]+m/)[-1] || ''
|
31
|
+
if visible_width + word.size_clean >= width
|
32
|
+
lines << line + xc
|
33
|
+
visible_width = word.size_clean
|
34
|
+
line = last_ansi + word
|
35
|
+
elsif line.empty?
|
36
|
+
visible_width = word.size_clean
|
37
|
+
line = last_ansi + word
|
38
|
+
else
|
39
|
+
visible_width += word.size_clean + 1
|
40
|
+
line << " " << last_ansi + word
|
41
|
+
end
|
42
|
+
end
|
43
|
+
lines << line + self.match(/\s*$/)[0] + xc if line
|
44
|
+
return lines.join("\n") # .gsub(/\- (\S)/,'-\1')
|
45
|
+
end
|
46
|
+
|
47
|
+
def xc(count=0)
|
48
|
+
c([:x,:white])
|
49
|
+
end
|
50
|
+
|
51
|
+
def c(args)
|
52
|
+
|
53
|
+
colors = {
|
54
|
+
:reset => 0, # synonym for :clear
|
55
|
+
:x => 0,
|
56
|
+
:bold => 1,
|
57
|
+
:b => 1,
|
58
|
+
:dark => 2,
|
59
|
+
:d => 2,
|
60
|
+
:italic => 3, # not widely implemented
|
61
|
+
:i => 3,
|
62
|
+
:underline => 4,
|
63
|
+
:underscore => 4, # synonym for :underline
|
64
|
+
:u => 4,
|
65
|
+
:blink => 5,
|
66
|
+
:rapid_blink => 6, # not widely implemented
|
67
|
+
:negative => 7, # no reverse because of String#reverse
|
68
|
+
:r => 7,
|
69
|
+
:concealed => 8,
|
70
|
+
:strikethrough => 9, # not widely implemented
|
71
|
+
:black => 30,
|
72
|
+
:red => 31,
|
73
|
+
:green => 32,
|
74
|
+
:yellow => 33,
|
75
|
+
:blue => 34,
|
76
|
+
:magenta => 35,
|
77
|
+
:cyan => 36,
|
78
|
+
:white => 37,
|
79
|
+
:on_black => 40,
|
80
|
+
:on_red => 41,
|
81
|
+
:on_green => 42,
|
82
|
+
:on_yellow => 43,
|
83
|
+
:on_blue => 44,
|
84
|
+
:on_magenta => 45,
|
85
|
+
:on_cyan => 46,
|
86
|
+
:on_white => 47,
|
87
|
+
:intense_black => 90, # High intensity, aixterm (works in OS X)
|
88
|
+
:intense_red => 91,
|
89
|
+
:intense_green => 92,
|
90
|
+
:intense_yellow => 93,
|
91
|
+
:intense_blue => 94,
|
92
|
+
:intense_magenta => 95,
|
93
|
+
:intense_cyan => 96,
|
94
|
+
:intense_white => 97,
|
95
|
+
:on_intense_black => 100, # High intensity background, aixterm (works in OS X)
|
96
|
+
:on_intense_red => 101,
|
97
|
+
:on_intense_green => 102,
|
98
|
+
:on_intense_yellow => 103,
|
99
|
+
:on_intense_blue => 104,
|
100
|
+
:on_intense_magenta => 105,
|
101
|
+
:on_intense_cyan => 106,
|
102
|
+
:on_intense_white => 107
|
103
|
+
}
|
104
|
+
|
105
|
+
out = []
|
106
|
+
|
107
|
+
args.each {|arg|
|
108
|
+
if colors.key? arg
|
109
|
+
out << colors[arg]
|
110
|
+
end
|
111
|
+
}
|
112
|
+
|
113
|
+
if out.size > 0
|
114
|
+
"\e[#{out.sort.join(';')}m"
|
115
|
+
else
|
116
|
+
''
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class String
|
123
|
+
include CLIMarkdown::Colors
|
124
|
+
end
|
@@ -0,0 +1,781 @@
|
|
1
|
+
module CLIMarkdown
|
2
|
+
class Converter
|
3
|
+
include Colors
|
4
|
+
|
5
|
+
attr_reader :helpers, :log
|
6
|
+
|
7
|
+
def version
|
8
|
+
"#{CLIMarkdown::EXECUTABLE_NAME} #{CLIMarkdown::VERSION}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(args)
|
12
|
+
@log = Logger.new(STDERR)
|
13
|
+
@log.level = Logger::FATAL
|
14
|
+
|
15
|
+
@options = {}
|
16
|
+
optparse = OptionParser.new do |opts|
|
17
|
+
opts.banner = "#{version} by Brett Terpstra\n\n> Usage: #{CLIMarkdown::EXECUTABLE_NAME} [options] path\n\n"
|
18
|
+
|
19
|
+
@options[:section] = nil
|
20
|
+
opts.on( '-s', '--section=TITLE', 'Output only a headline-based section of the input' ) do |section|
|
21
|
+
@options[:section] = section
|
22
|
+
end
|
23
|
+
|
24
|
+
@options[:width] = 80
|
25
|
+
opts.on( '-w', '--width=COLUMNS', 'Column width to format for (default 80)' ) do |columns|
|
26
|
+
@options[:width] = columns.to_i
|
27
|
+
if @options[:width] = 0
|
28
|
+
@options[:width] = %x{tput cols}.strip.to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@options[:pager] = true
|
33
|
+
opts.on( '-p', '--[no-]pager', 'Formatted output to pager (default on)' ) do |p|
|
34
|
+
@options[:pager] = p
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on( '-P', 'Disable pager (same as --no-pager)' ) do
|
38
|
+
@options[:pager] = false
|
39
|
+
end
|
40
|
+
|
41
|
+
@options[:color] = true
|
42
|
+
opts.on( '-c', '--[no-]color', 'Colorize output (default on)' ) do |c|
|
43
|
+
@options[:color] = c
|
44
|
+
end
|
45
|
+
|
46
|
+
@options[:links] = :inline
|
47
|
+
opts.on( '--links=FORMAT', 'Link style ([inline, reference], default inline)' ) do |format|
|
48
|
+
if format =~ /^r/i
|
49
|
+
@options[:links] = :reference
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@options[:list] = false
|
54
|
+
opts.on( '-l', '--list', 'List headers in document and exit' ) do
|
55
|
+
@options[:list] = true
|
56
|
+
end
|
57
|
+
|
58
|
+
@options[:local_images] = false
|
59
|
+
@options[:remote_images] = false
|
60
|
+
|
61
|
+
if exec_available('imgcat') && ENV['TERM_PROGRAM'] == 'iTerm.app'
|
62
|
+
opts.on('-i', '--images=TYPE', 'Include [local|remote (both)] images in output (requires imgcat and iTerm2, default NONE)' ) do |type|
|
63
|
+
if type =~ /^(r|b|a)/i
|
64
|
+
@options[:local_images] = true
|
65
|
+
@options[:remote_images] = true
|
66
|
+
elsif type =~ /^l/i
|
67
|
+
@options[:local_images] = true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
opts.on('-I', '--all-images', 'Include local and remote images in output (requires imgcat and iTerm2)' ) do
|
71
|
+
@options[:local_images] = true
|
72
|
+
@options[:remote_images] = true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
opts.on( '-d', '--debug LEVEL', 'Level of debug messages to output' ) do |level|
|
78
|
+
if level.to_i > 0 && level.to_i < 5
|
79
|
+
@log.level = 5 - level.to_i
|
80
|
+
else
|
81
|
+
$stderr.puts "Log level out of range (1-4)"
|
82
|
+
Process.exit 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
87
|
+
puts opts
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
|
91
|
+
opts.on( '-v', '--version', 'Display version number' ) do
|
92
|
+
puts version
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
optparse.parse!
|
98
|
+
|
99
|
+
@cols = @options[:width] || %x{tput cols}.strip.to_i * 0.9
|
100
|
+
@output = ''
|
101
|
+
|
102
|
+
input = ''
|
103
|
+
@ref_links = {}
|
104
|
+
@footnotes = {}
|
105
|
+
|
106
|
+
if args.length > 0
|
107
|
+
files = args.delete_if { |f| !File.exists?(f) }
|
108
|
+
files.each {|file|
|
109
|
+
@log.info(%Q{Processing "#{file}"})
|
110
|
+
@file = file
|
111
|
+
begin
|
112
|
+
input = IO.read(file).force_encoding('utf-8')
|
113
|
+
rescue
|
114
|
+
input = IO.read(file)
|
115
|
+
end
|
116
|
+
if @options[:list]
|
117
|
+
list_headers(input)
|
118
|
+
else
|
119
|
+
convert_markdown(input)
|
120
|
+
end
|
121
|
+
}
|
122
|
+
printout
|
123
|
+
elsif STDIN.stat.size > 0
|
124
|
+
@file = nil
|
125
|
+
begin
|
126
|
+
input = STDIN.read.force_encoding('utf-8')
|
127
|
+
rescue
|
128
|
+
input = STDIN.read
|
129
|
+
end
|
130
|
+
if @options[:list]
|
131
|
+
list_headers(input)
|
132
|
+
else
|
133
|
+
convert_markdown(input)
|
134
|
+
end
|
135
|
+
printout
|
136
|
+
else
|
137
|
+
$stderr.puts "No input"
|
138
|
+
Process.exit 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def list_headers(input)
|
143
|
+
h_adjust = highest_header(input) - 1
|
144
|
+
input.gsub!(/^(#+)/) do |m|
|
145
|
+
match = Regexp.last_match
|
146
|
+
new_level = match[1].length - h_adjust
|
147
|
+
if new_level > 0
|
148
|
+
"#" * new_level
|
149
|
+
else
|
150
|
+
''
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
headers = []
|
155
|
+
last_level = 0
|
156
|
+
input.split(/\n/).each do |line|
|
157
|
+
if line =~ /^(#+)\s*(.*?)( #+)?\s*$/
|
158
|
+
level = $1.size - 1
|
159
|
+
title = $2
|
160
|
+
|
161
|
+
if level - 1 > last_level
|
162
|
+
level = last_level + 1
|
163
|
+
end
|
164
|
+
last_level = level
|
165
|
+
|
166
|
+
subdoc = case level
|
167
|
+
when 0
|
168
|
+
' '
|
169
|
+
when 1
|
170
|
+
'- '
|
171
|
+
when 2
|
172
|
+
'+ '
|
173
|
+
when 3
|
174
|
+
'* '
|
175
|
+
else
|
176
|
+
' '
|
177
|
+
end
|
178
|
+
headers.push((" "*level) + (c([:x, :yellow]) + subdoc + title.strip + xc))
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
@output += headers.join("\n")
|
183
|
+
end
|
184
|
+
|
185
|
+
def highest_header(input)
|
186
|
+
headers = input.scan(/^(#+)/)
|
187
|
+
top = 6
|
188
|
+
headers.each {|h|
|
189
|
+
top = h[0].length if h[0].length < top
|
190
|
+
}
|
191
|
+
top
|
192
|
+
end
|
193
|
+
|
194
|
+
def color_table(input)
|
195
|
+
first = true
|
196
|
+
input.split(/\n/).map{|line|
|
197
|
+
if first
|
198
|
+
first = false
|
199
|
+
line.gsub!(/\|/, "#{c([:d,:black])}|#{c([:x,:yellow])}")
|
200
|
+
elsif line.strip =~ /^[|:\- ]+$/
|
201
|
+
line.gsub!(/^(.*)$/, "#{c([:d,:black])}\\1#{c([:x,:white])}")
|
202
|
+
line.gsub!(/([:\-]+)/,"#{c([:b,:black])}\\1#{c([:d,:black])}")
|
203
|
+
else
|
204
|
+
line.gsub!(/\|/, "#{c([:d,:black])}|#{c([:x,:white])}")
|
205
|
+
end
|
206
|
+
}.join("\n")
|
207
|
+
end
|
208
|
+
|
209
|
+
def table_cleanup(input)
|
210
|
+
in_table = false
|
211
|
+
header_row = false
|
212
|
+
all_content = []
|
213
|
+
this_table = []
|
214
|
+
orig_table = []
|
215
|
+
input.split(/\n/).each {|line|
|
216
|
+
if line =~ /(\|.*?)+/ && line !~ /^\s*~/
|
217
|
+
in_table = true
|
218
|
+
table_line = line.to_s.uncolor.strip.sub(/^\|?\s*/,'|').gsub(/\s*([\|:])\s*/,'\1')
|
219
|
+
|
220
|
+
if table_line.strip.gsub(/[\|:\- ]/,'') == ''
|
221
|
+
header_row = true
|
222
|
+
end
|
223
|
+
this_table.push(table_line)
|
224
|
+
orig_table.push(line)
|
225
|
+
else
|
226
|
+
if in_table
|
227
|
+
if this_table.length > 3
|
228
|
+
# if there's no header row, add one, cleanup requires it
|
229
|
+
unless header_row
|
230
|
+
cells = this_table[0].sub(/^\|/,'').scan(/.*?\|/).length
|
231
|
+
cell_row = '|' + ':-----|'*cells
|
232
|
+
this_table.insert(1, cell_row)
|
233
|
+
end
|
234
|
+
|
235
|
+
table = this_table.join("\n")
|
236
|
+
begin
|
237
|
+
res = clean_table(table)
|
238
|
+
res = color_table(res)
|
239
|
+
rescue
|
240
|
+
res = orig_table.join("\n")
|
241
|
+
end
|
242
|
+
all_content.push("\n" + res)
|
243
|
+
else
|
244
|
+
all_content.push(orig_table.join("\n"))
|
245
|
+
end
|
246
|
+
this_table = []
|
247
|
+
orig_table = []
|
248
|
+
end
|
249
|
+
in_table = false
|
250
|
+
header_row = false
|
251
|
+
all_content.push(line)
|
252
|
+
end
|
253
|
+
}
|
254
|
+
all_content.join("\n")
|
255
|
+
end
|
256
|
+
|
257
|
+
def clean_table(input)
|
258
|
+
dir = File.dirname(__FILE__)
|
259
|
+
lib = File.expand_path(dir + '/../../lib')
|
260
|
+
script = File.join(lib, 'helpers/formattables.py')
|
261
|
+
|
262
|
+
if File.exists?(script) and File.executable?(script)
|
263
|
+
begin
|
264
|
+
|
265
|
+
res, s = Open3.capture2(script, :stdin_data=>input.strip)
|
266
|
+
|
267
|
+
if s.success?
|
268
|
+
res
|
269
|
+
else
|
270
|
+
input
|
271
|
+
end
|
272
|
+
rescue => e
|
273
|
+
@log.error(e)
|
274
|
+
input
|
275
|
+
end
|
276
|
+
else
|
277
|
+
input
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def clean_markers(input)
|
282
|
+
input.gsub!(/^(\e\[[\d;]+m)?[%~] ?/,'\1')
|
283
|
+
input
|
284
|
+
end
|
285
|
+
|
286
|
+
def update_inline_links(input)
|
287
|
+
links = {}
|
288
|
+
counter = 1
|
289
|
+
input.gsub!(/(?<=\])\((.*?)\)/) do |m|
|
290
|
+
links[counter] = $1.uncolor
|
291
|
+
"[#{counter}]"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def find_color(line,nullable=false)
|
296
|
+
return line if line.nil?
|
297
|
+
colors = line.scan(/\e\[[\d;]+m/)
|
298
|
+
if colors && colors.size > 0
|
299
|
+
colors[-1]
|
300
|
+
else
|
301
|
+
nullable ? nil : xc
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def color_link(line, text, url)
|
306
|
+
out = c([:b,:black])
|
307
|
+
out += "[#{c([:u,:blue])}#{text}"
|
308
|
+
out += c([:b,:black])
|
309
|
+
out += "]("
|
310
|
+
out += c([:x,:cyan])
|
311
|
+
out += url
|
312
|
+
out += c([:b,:black])
|
313
|
+
out += ")"
|
314
|
+
out += find_color(line)
|
315
|
+
out
|
316
|
+
end
|
317
|
+
|
318
|
+
def color_image(line, text, url)
|
319
|
+
text.gsub!(/\e\[0m/,c([:x,:cyan]))
|
320
|
+
|
321
|
+
"#{c([:x,:red])}!#{c([:b,:black])}[#{c([:x,:cyan])}#{text}#{c([:b,:black])}](#{c([:u,:yellow])}#{url}#{c([:b,:black])})" + find_color(line)
|
322
|
+
end
|
323
|
+
|
324
|
+
def convert_markdown(input)
|
325
|
+
|
326
|
+
# yaml/MMD headers
|
327
|
+
in_yaml = false
|
328
|
+
if input.split("\n")[0] =~ /(?i-m)^---[ \t]*?(\n|$)/
|
329
|
+
@log.info("Found YAML")
|
330
|
+
# YAML
|
331
|
+
in_yaml = true
|
332
|
+
input.sub!(/(?i-m)^---[ \t]*\n([\s\S]*?)\n[\-.]{3}[ \t]*\n/) do |yaml|
|
333
|
+
m = Regexp.last_match
|
334
|
+
|
335
|
+
@log.warn("Processing YAML Header")
|
336
|
+
m[0].split(/\n/).map {|line|
|
337
|
+
if line =~ /^[\-.]{3}\s*$/
|
338
|
+
line = c([:d,:black,:on_black]) + "% " + c([:d,:black,:on_black]) + line
|
339
|
+
else
|
340
|
+
line.sub!(/^(.*?:)[ \t]+(\S)/, '\1 \2')
|
341
|
+
line = c([:d,:black,:on_black]) + "% " + c([:d,:white]) + line
|
342
|
+
end
|
343
|
+
if @cols - line.uncolor.size > 0
|
344
|
+
line += " "*(@cols-line.uncolor.size)
|
345
|
+
end
|
346
|
+
}.join("\n") + "#{xc}\n"
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
if !in_yaml && input.gsub(/\n/,' ') =~ /(?i-m)^\w.+:\s+\S+ /
|
351
|
+
@log.info("Found MMD Headers")
|
352
|
+
input.sub!(/(?i-m)^([\S ]+:[\s\S]*?)+(?=\n\n)/) do |mmd|
|
353
|
+
puts mmd
|
354
|
+
mmd.split(/\n/).map {|line|
|
355
|
+
line.sub!(/^(.*?:)[ \t]+(\S)/, '\1 \2')
|
356
|
+
line = c([:d,:black,:on_black]) + "% " + c([:d,:white,:on_black]) + line
|
357
|
+
if @cols - line.uncolor.size > 0
|
358
|
+
line += " "*(@cols - line.uncolor.size)
|
359
|
+
end
|
360
|
+
}.join("\n") + " "*@cols + "#{xc}\n"
|
361
|
+
end
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
|
366
|
+
# Gather reference links
|
367
|
+
input.gsub!(/^\s{,3}(?<![\e*])\[\b(.+)\b\]: +(.+)/) do |m|
|
368
|
+
match = Regexp.last_match
|
369
|
+
@ref_links[match[1]] = match[2]
|
370
|
+
''
|
371
|
+
end
|
372
|
+
|
373
|
+
# Gather footnotes (non-inline)
|
374
|
+
input.gsub!(/^ {,3}(?<!\*)(?:\e\[[\d;]+m)*\[(?:\e\[[\d;]+m)*\^(?:\e\[[\d;]+m)*\b(.+)\b(?:\e\[[\d;]+m)*\]: *(.*?)\n/) do |m|
|
375
|
+
match = Regexp.last_match
|
376
|
+
@footnotes[match[1].uncolor] = match[2].uncolor
|
377
|
+
''
|
378
|
+
end
|
379
|
+
|
380
|
+
if @options[:section]
|
381
|
+
in_section = false
|
382
|
+
top_level = 1
|
383
|
+
new_content = []
|
384
|
+
|
385
|
+
input.split(/\n/).each {|graf|
|
386
|
+
if graf =~ /^(#+) *(.*?)( *#+)?$/
|
387
|
+
level = $1.length
|
388
|
+
title = $2
|
389
|
+
|
390
|
+
if in_section
|
391
|
+
if level > top_level
|
392
|
+
new_content.push(graf)
|
393
|
+
else
|
394
|
+
break
|
395
|
+
end
|
396
|
+
elsif title.downcase =~ /#{@options[:section]}/i
|
397
|
+
in_section = true
|
398
|
+
top_level = level
|
399
|
+
new_content.push(graf)
|
400
|
+
else
|
401
|
+
next
|
402
|
+
end
|
403
|
+
elsif in_section
|
404
|
+
new_content.push(graf)
|
405
|
+
end
|
406
|
+
}
|
407
|
+
|
408
|
+
input = new_content.join("\n")
|
409
|
+
end
|
410
|
+
|
411
|
+
h_adjust = highest_header(input) - 1
|
412
|
+
input.gsub!(/^(#+)/) do |m|
|
413
|
+
match = Regexp.last_match
|
414
|
+
"#" * (match[1].length - h_adjust)
|
415
|
+
end
|
416
|
+
|
417
|
+
# TODO: Probably easiest to just collect these with line indexes, remove until other highlighting is finished
|
418
|
+
input.gsub!(/(?i-m)([`~]{3,})([\s\S]*?)\n([\s\S]*?)\1/ ) do |cb|
|
419
|
+
m = Regexp.last_match
|
420
|
+
leader = m[2] ? m[2].upcase + ":" : 'CODE:'
|
421
|
+
leader += xc
|
422
|
+
|
423
|
+
if exec_available('pygmentize')
|
424
|
+
lexer = m[2].nil? ? '-g' : "-l #{m[2]}"
|
425
|
+
begin
|
426
|
+
hilite, s = Open3.capture2(%Q{pygmentize #{lexer} 2> /dev/null}, :stdin_data=>m[3])
|
427
|
+
|
428
|
+
if s.success?
|
429
|
+
hilite = hilite.split(/\n/).map{|l| "#{c([:x,:black])}~ #{xc}" + l}.join("\n")
|
430
|
+
end
|
431
|
+
rescue => e
|
432
|
+
@log.error(e)
|
433
|
+
hilite = m[0]
|
434
|
+
end
|
435
|
+
|
436
|
+
else
|
437
|
+
|
438
|
+
hilite = m[3].split(/\n/).map{|l|
|
439
|
+
new_code_line = l.gsub(/\t/,' ')
|
440
|
+
orig_length = new_code_line.size + 3
|
441
|
+
new_code_line.gsub!(/ /,"#{c([:x,:white,:on_black])} ")
|
442
|
+
"#{c([:x,:black])}~ #{c([:x,:white,:on_black])} " + new_code_line + c([:x,:white,:on_black]) + " "*(@cols - orig_length) + xc
|
443
|
+
}.join("\n")
|
444
|
+
end
|
445
|
+
"#{c([:x,:magenta])}#{leader}\n#{hilite}#{xc}"
|
446
|
+
end
|
447
|
+
|
448
|
+
# remove empty links
|
449
|
+
input.gsub!(/\[(.*?)\]\(\s*?\)/, '\1')
|
450
|
+
input.gsub!(/\[(.*?)\]\[\]/, '[\1][\1]')
|
451
|
+
|
452
|
+
lines = input.split(/\n/)
|
453
|
+
|
454
|
+
# previous_indent = 0
|
455
|
+
|
456
|
+
lines.map!.with_index do |aLine, i|
|
457
|
+
line = aLine.dup
|
458
|
+
clean_line = line.dup.uncolor
|
459
|
+
|
460
|
+
|
461
|
+
if clean_line.uncolor =~ /(^[%~])/ # || clean_line.uncolor =~ /^( {4,}|\t+)/
|
462
|
+
## TODO: find indented code blocks and prevent highlighting
|
463
|
+
## Needs to miss block indented 1 level in lists
|
464
|
+
## Needs to catch lists in code
|
465
|
+
## Needs to avoid within fenced code blocks
|
466
|
+
# if line =~ /^([ \t]+)([^*-+]+)/
|
467
|
+
# indent = $1.gsub(/\t/, " ").size
|
468
|
+
# if indent >= previous_indent
|
469
|
+
# line = "~" + line
|
470
|
+
# end
|
471
|
+
# p [indent, previous_indent]
|
472
|
+
# previous_indent = indent
|
473
|
+
# end
|
474
|
+
else
|
475
|
+
# Headlines
|
476
|
+
line.gsub!(/^(#+) *(.*?)(\s*#+)?\s*$/) do |match|
|
477
|
+
m = Regexp.last_match
|
478
|
+
pad = ""
|
479
|
+
ansi = ''
|
480
|
+
case m[1].length
|
481
|
+
when 1
|
482
|
+
ansi = c([:b, :black, :on_intense_white])
|
483
|
+
pad = c([:b,:white])
|
484
|
+
pad += m[2].length + 2 > @cols ? "*"*m[2].length : "*"*(@cols - (m[2].length + 2))
|
485
|
+
when 2
|
486
|
+
ansi = c([:b, :green, :on_black])
|
487
|
+
pad = c([:b,:black])
|
488
|
+
pad += m[2].length + 2 > @cols ? "-"*m[2].length : "-"*(@cols - (m[2].length + 2))
|
489
|
+
when 3
|
490
|
+
ansi = c([:u, :b, :yellow])
|
491
|
+
when 4
|
492
|
+
ansi = c([:x, :u, :yellow])
|
493
|
+
else
|
494
|
+
ansi = c([:b, :white])
|
495
|
+
end
|
496
|
+
|
497
|
+
"\n#{xc}#{ansi}#{m[2]} #{pad}#{xc}\n"
|
498
|
+
end
|
499
|
+
|
500
|
+
# place footnotes under paragraphs that reference them
|
501
|
+
if line =~ /\[(?:\e\[[\d;]+m)*\^(?:\e\[[\d;]+m)*(\S+)(?:\e\[[\d;]+m)*\]/
|
502
|
+
key = $1.uncolor
|
503
|
+
if @footnotes.key? key
|
504
|
+
line += "\n\n#{c([:b,:black,:on_black])}[#{c([:b,:cyan,:on_black])}^#{c([:x,:yellow,:on_black])}#{key}#{c([:b,:black,:on_black])}]: #{c([:u,:white,:on_black])}#{@footnotes[key]}#{xc}"
|
505
|
+
@footnotes.delete(key)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
# color footnote references
|
510
|
+
line.gsub!(/\[\^(\S+)\]/) do |m|
|
511
|
+
match = Regexp.last_match
|
512
|
+
last = find_color(match.pre_match, true)
|
513
|
+
counter = i
|
514
|
+
while last.nil? && counter > 0
|
515
|
+
counter -= 1
|
516
|
+
find_color(lines[counter])
|
517
|
+
end
|
518
|
+
"#{c([:b,:black])}[#{c([:b,:yellow])}^#{c([:x,:yellow])}#{match[1]}#{c([:b,:black])}]" + (last ? last : xc)
|
519
|
+
end
|
520
|
+
|
521
|
+
# blockquotes
|
522
|
+
line.gsub!(/^(\s*>)+( .*?)?$/) do |m|
|
523
|
+
match = Regexp.last_match
|
524
|
+
last = find_color(match.pre_match, true)
|
525
|
+
counter = i
|
526
|
+
while last.nil? && counter > 0
|
527
|
+
counter -= 1
|
528
|
+
find_color(lines[counter])
|
529
|
+
end
|
530
|
+
"#{c([:b,:black])}#{match[1]}#{c([:x,:magenta])} #{match[2]}" + (last ? last : xc)
|
531
|
+
end
|
532
|
+
|
533
|
+
# make reference links inline
|
534
|
+
line.gsub!(/(?<![\e*])\[(\b.*?\b)?\]\[(\b.+?\b)?\]/) do |m|
|
535
|
+
match = Regexp.last_match
|
536
|
+
title = match[2] || ''
|
537
|
+
text = match[1] || ''
|
538
|
+
if match[2] && @ref_links.key?(title.downcase)
|
539
|
+
"[#{text}](#{@ref_links[title]})"
|
540
|
+
elsif match[1] && @ref_links.key?(text.downcase)
|
541
|
+
"[#{text}](#{@ref_links[text]})"
|
542
|
+
else
|
543
|
+
if input.match(/^#+\s*#{Regexp.escape(text)}/i)
|
544
|
+
"[#{text}](##{text})"
|
545
|
+
else
|
546
|
+
match[1]
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
# color inline links
|
552
|
+
line.gsub!(/(?<![\e*!])\[(\S.*?\S)\]\((\S.+?\S)\)/) do |m|
|
553
|
+
match = Regexp.last_match
|
554
|
+
color_link(match.pre_match, match[1], match[2])
|
555
|
+
end
|
556
|
+
|
557
|
+
|
558
|
+
|
559
|
+
# inline code
|
560
|
+
line.gsub!(/`(.*?)`/) do |m|
|
561
|
+
match = Regexp.last_match
|
562
|
+
last = find_color(match.pre_match, true)
|
563
|
+
"#{c([:b,:black])}`#{c([:b,:white])}#{match[1]}#{c([:b,:black])}`" + (last ? last : xc)
|
564
|
+
end
|
565
|
+
|
566
|
+
# horizontal rules
|
567
|
+
line.gsub!(/^ {,3}([\-*] ?){3,}$/) do |m|
|
568
|
+
c([:x,:black]) + '_'*@cols + xc
|
569
|
+
end
|
570
|
+
|
571
|
+
# bold, bold/italic
|
572
|
+
line.gsub!(/(^|\s)[\*_]{2,3}([^\*_\s][^\*_]+?[^\*_\s])[\*_]{2,3}/) do |m|
|
573
|
+
match = Regexp.last_match
|
574
|
+
last = find_color(match.pre_match, true)
|
575
|
+
counter = i
|
576
|
+
while last.nil? && counter > 0
|
577
|
+
counter -= 1
|
578
|
+
find_color(lines[counter])
|
579
|
+
end
|
580
|
+
"#{match[1]}#{c([:b])}#{match[2]}" + (last ? last : xc)
|
581
|
+
end
|
582
|
+
|
583
|
+
# italic
|
584
|
+
line.gsub!(/(^|\s)[\*_]([^\*_\s][^\*_]+?[^\*_\s])[\*_]/) do |m|
|
585
|
+
match = Regexp.last_match
|
586
|
+
last = find_color(match.pre_match, true)
|
587
|
+
counter = i
|
588
|
+
while last.nil? && counter > 0
|
589
|
+
counter -= 1
|
590
|
+
find_color(lines[counter])
|
591
|
+
end
|
592
|
+
"#{match[1]}#{c([:u])}#{match[2]}" + (last ? last : xc)
|
593
|
+
end
|
594
|
+
|
595
|
+
# equations
|
596
|
+
line.gsub!(/((\\\\\[)(.*?)(\\\\\])|(\\\\\()(.*?)(\\\\\)))/) do |m|
|
597
|
+
match = Regexp.last_match
|
598
|
+
last = find_color(match.pre_match)
|
599
|
+
if match[2]
|
600
|
+
brackets = [match[2], match[4]]
|
601
|
+
equat = match[3]
|
602
|
+
else
|
603
|
+
brackets = [match[5], match[7]]
|
604
|
+
equat = match[6]
|
605
|
+
end
|
606
|
+
"#{c([:b, :black])}#{brackets[0]}#{xc}#{c([:b,:blue])}#{equat}#{c([:b, :black])}#{brackets[1]}" + (last ? last : xc)
|
607
|
+
end
|
608
|
+
|
609
|
+
# list items
|
610
|
+
line.gsub!(/^(\s*)([*\-+]|\d\.) /) do |m|
|
611
|
+
match = Regexp.last_match
|
612
|
+
last = find_color(match.pre_match)
|
613
|
+
indent = match[1] || ''
|
614
|
+
"#{indent}#{c([:d, :red])}#{match[2]} " + (last ? last : xc)
|
615
|
+
end
|
616
|
+
|
617
|
+
# definition lists
|
618
|
+
line.gsub!(/^(:\s*)(.*?)/) do |m|
|
619
|
+
match = Regexp.last_match
|
620
|
+
"#{c([:d, :red])}#{match[1]} #{c([:b, :white])}#{match[2]}#{xc}"
|
621
|
+
end
|
622
|
+
|
623
|
+
# misc html
|
624
|
+
line.gsub!(/<br\/?>/, "\n")
|
625
|
+
line.gsub!(/(?i-m)((<\/?)(\w+[\s\S]*?)(>))/) do |tag|
|
626
|
+
match = Regexp.last_match
|
627
|
+
last = find_color(match.pre_match)
|
628
|
+
"#{c([:d,:black])}#{match[2]}#{c([:b,:black])}#{match[3]}#{c([:d,:black])}#{match[4]}" + (last ? last : xc)
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
line
|
633
|
+
end
|
634
|
+
|
635
|
+
input = lines.join("\n")
|
636
|
+
|
637
|
+
# images
|
638
|
+
input.gsub!(/^(.*?)!\[(.*)?\]\((.*?\.(?:png|gif|jpg))( +.*)?\)/) do |m|
|
639
|
+
match = Regexp.last_match
|
640
|
+
if match[1].uncolor =~ /^( {4,}|\t)+/
|
641
|
+
match[0]
|
642
|
+
else
|
643
|
+
tail = match[4].nil? ? '' : " "+match[4].strip
|
644
|
+
result = nil
|
645
|
+
if exec_available('imgcat') && @options[:local_images]
|
646
|
+
if match[3]
|
647
|
+
img_path = match[3]
|
648
|
+
if img_path =~ /^http/ && @options[:remote_images]
|
649
|
+
begin
|
650
|
+
res, s = Open3.capture2(%Q{curl -sS "#{img_path}" 2> /dev/null | imgcat})
|
651
|
+
|
652
|
+
if s.success?
|
653
|
+
pre = match[2].size > 0 ? " #{c([:d,:blue])}[#{match[2].strip}]\n" : ''
|
654
|
+
post = tail.size > 0 ? "\n #{c([:b,:blue])}-- #{tail} --" : ''
|
655
|
+
result = pre + res + post
|
656
|
+
end
|
657
|
+
rescue => e
|
658
|
+
@log.error(e)
|
659
|
+
end
|
660
|
+
else
|
661
|
+
if img_path =~ /^[~\/]/
|
662
|
+
img_path = File.expand_path(img_path)
|
663
|
+
elsif @file
|
664
|
+
base = File.expand_path(File.dirname(@file))
|
665
|
+
img_path = File.join(base,img_path)
|
666
|
+
end
|
667
|
+
if File.exists?(img_path)
|
668
|
+
pre = match[2].size > 0 ? " #{c([:d,:blue])}[#{match[2].strip}]\n" : ''
|
669
|
+
post = tail.size > 0 ? "\n #{c([:b,:blue])}-- #{tail} --" : ''
|
670
|
+
img = %x{imgcat "#{img_path}"}
|
671
|
+
result = pre + img + post
|
672
|
+
end
|
673
|
+
end
|
674
|
+
end
|
675
|
+
end
|
676
|
+
if result.nil?
|
677
|
+
match[1] + color_image(match.pre_match, match[2], match[3] + tail) + xc
|
678
|
+
else
|
679
|
+
match[1] + result + xc
|
680
|
+
end
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
@footnotes.each {|t, v|
|
685
|
+
input += "\n\n#{c([:b,:black,:on_black])}[#{c([:b,:yellow,:on_black])}^#{c([:x,:yellow,:on_black])}#{t}#{c([:b,:black,:on_black])}]: #{c([:u,:white,:on_black])}#{v}#{xc}"
|
686
|
+
}
|
687
|
+
|
688
|
+
@output += input
|
689
|
+
|
690
|
+
end
|
691
|
+
|
692
|
+
def exec_available(cli)
|
693
|
+
if File.exists?(File.expand_path(cli))
|
694
|
+
File.executable?(File.expand_path(cli))
|
695
|
+
else
|
696
|
+
system "which #{cli} &> /dev/null"
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
def page(text, &callback)
|
701
|
+
read_io, write_io = IO.pipe
|
702
|
+
|
703
|
+
input = $stdin
|
704
|
+
|
705
|
+
pid = Kernel.fork do
|
706
|
+
write_io.close
|
707
|
+
input.reopen(read_io)
|
708
|
+
read_io.close
|
709
|
+
|
710
|
+
# Wait until we have input before we start the pager
|
711
|
+
IO.select [input]
|
712
|
+
|
713
|
+
pager = which_pager
|
714
|
+
begin
|
715
|
+
exec(pager.join(' '))
|
716
|
+
rescue SystemCallError => e
|
717
|
+
@log.error(e)
|
718
|
+
exit 1
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
read_io.close
|
723
|
+
write_io.write(text)
|
724
|
+
write_io.close
|
725
|
+
|
726
|
+
_, status = Process.waitpid2(pid)
|
727
|
+
status.success?
|
728
|
+
end
|
729
|
+
|
730
|
+
def printout
|
731
|
+
out = @output.strip.split(/\n/).map {|p|
|
732
|
+
p.wrap(@cols)
|
733
|
+
}.join("\n")
|
734
|
+
|
735
|
+
|
736
|
+
unless out && out.size > 0
|
737
|
+
$stderr.puts "No results"
|
738
|
+
Process.exit
|
739
|
+
end
|
740
|
+
|
741
|
+
out = table_cleanup(out)
|
742
|
+
out = clean_markers(out)
|
743
|
+
out = out.gsub(/\n+{2,}/m,"\n\n") + "\n#{xc}\n\n"
|
744
|
+
|
745
|
+
unless @options[:color]
|
746
|
+
out.uncolor!
|
747
|
+
end
|
748
|
+
|
749
|
+
if @options[:pager]
|
750
|
+
page("\n\n" + out)
|
751
|
+
else
|
752
|
+
$stdout.puts ("\n\n" + out)
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|
756
|
+
def which_pager
|
757
|
+
pagers = [ENV['GIT_PAGER'], ENV['PAGER'],
|
758
|
+
`git config --get-all core.pager`.split.first,
|
759
|
+
'less', 'more', 'cat', 'pager']
|
760
|
+
pagers.select! do |f|
|
761
|
+
if f
|
762
|
+
system "which #{f} &> /dev/null"
|
763
|
+
else
|
764
|
+
false
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
pg = pagers.first
|
769
|
+
args = case pg
|
770
|
+
when 'more'
|
771
|
+
' -r'
|
772
|
+
when 'less'
|
773
|
+
' -r'
|
774
|
+
else
|
775
|
+
''
|
776
|
+
end
|
777
|
+
|
778
|
+
[pg, args]
|
779
|
+
end
|
780
|
+
end
|
781
|
+
end
|
data/lib/mdless.rb
ADDED
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mdless
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brett Terpstra
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rdoc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.1'
|
34
|
+
- - '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 4.1.1
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '4.1'
|
44
|
+
- - '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 4.1.1
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: aruba
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
description: A CLI that provides a formatted and highlighted view of Markdown files
|
62
|
+
in a terminal
|
63
|
+
email: me@brettterpstra.com
|
64
|
+
executables:
|
65
|
+
- mdless
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files:
|
68
|
+
- README.md
|
69
|
+
files:
|
70
|
+
- README.md
|
71
|
+
- bin/mdless
|
72
|
+
- lib/helpers/formattables.py
|
73
|
+
- lib/mdless.rb
|
74
|
+
- lib/mdless/colors.rb
|
75
|
+
- lib/mdless/converter.rb
|
76
|
+
- lib/mdless/version.rb
|
77
|
+
homepage: http://brettterpstra.com/project/mdless/
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options:
|
83
|
+
- --title
|
84
|
+
- mdless
|
85
|
+
- --main
|
86
|
+
- README.md
|
87
|
+
- --markup
|
88
|
+
- markdown
|
89
|
+
- -ri
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.4.6
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: A pager like less, but for Markdown files
|
109
|
+
test_files: []
|