mdless 0.0.5
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 +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: []
|