mdprev 1.0.0
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.
- data/mdprev +15 -0
- data/mdprev.rb +185 -0
- data/mdprev_test.rb +58 -0
- metadata +98 -0
data/mdprev
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require File.expand_path('mdprev', File.dirname(__FILE__))
|
4
|
+
|
5
|
+
ARGV.options do |o|
|
6
|
+
flags = {}
|
7
|
+
o.set_summary_indent(' ')
|
8
|
+
o.banner = "Usage: #{File.basename($0)} [markdown-files,] [OPTIONS]"
|
9
|
+
o.define_head 'Styled preview of markdown files in browser'
|
10
|
+
o.on('-p', '--pdf', 'view as PDF') { flags[:pdf] = true }
|
11
|
+
o.on('-n', '--nav', 'include navigation') { flags[:nav] = true }
|
12
|
+
o.on('-h', '--help', 'show this help message') { puts o; exit }
|
13
|
+
o.parse!
|
14
|
+
ARGV.empty? ? puts(o) : MarkdownPreview.run(ARGV, flags)
|
15
|
+
end
|
data/mdprev.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'kramdown'
|
5
|
+
|
6
|
+
module MarkdownPreview
|
7
|
+
VERSION = '1.0.0'
|
8
|
+
OPEN_HTML = ENV['MDPREV_OPEN_HTML'] || 'open'
|
9
|
+
OPEN_PDF = ENV['MDPREV_OPEN_PDF'] || 'open'
|
10
|
+
PREVIEW_HTML = "#{ENV['HOME']}/.preview.html"
|
11
|
+
PREVIEW_PDF = "#{ENV['HOME']}/.preview.pdf"
|
12
|
+
|
13
|
+
def self.run(fnames, flags = {})
|
14
|
+
html = File.open(PREVIEW_HTML, 'w')
|
15
|
+
html.write(to_html(
|
16
|
+
fnames.map {|f| File.read(f)}.join("\n"),
|
17
|
+
title_from_files(fnames),
|
18
|
+
flags))
|
19
|
+
html.close
|
20
|
+
|
21
|
+
if flags[:pdf]
|
22
|
+
`wkhtmltopdf #{PREVIEW_HTML} #{PREVIEW_PDF} && #{OPEN_PDF} #{PREVIEW_PDF}`
|
23
|
+
else
|
24
|
+
`#{OPEN_HTML} #{PREVIEW_HTML}`
|
25
|
+
end
|
26
|
+
rescue StandardError => e
|
27
|
+
puts e.message
|
28
|
+
ensure
|
29
|
+
html.close if html && !html.closed?
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.to_html(str, title = 'Preview', flags = {})
|
33
|
+
body, anchors = build_body_and_anchors(str)
|
34
|
+
TEMPLATE.
|
35
|
+
sub('$nav$', flags[:nav] ? build_nav(anchors) : '').
|
36
|
+
sub('$body$', body).
|
37
|
+
sub('$class$', body_class(flags)).
|
38
|
+
sub('$title$', title).
|
39
|
+
gsub('<div class="section"></div>', '')
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.build_body_and_anchors(str)
|
43
|
+
html = Kramdown::Document.new(str, :auto_ids => false).to_html
|
44
|
+
anchors = []
|
45
|
+
html.gsub!('<h1>', '</div><div class="section"><h1>')
|
46
|
+
html.scan(/<h1>[^<]+<\/h1>/).each do |match|
|
47
|
+
text = match.gsub(/<\/?h1>/, '')
|
48
|
+
anchor = text.
|
49
|
+
gsub(/[^a-zA-Z0-9\- ]/, '').
|
50
|
+
gsub(/\s+/, '-').
|
51
|
+
downcase
|
52
|
+
anchors.push([anchor, text])
|
53
|
+
html.sub!(match,
|
54
|
+
"<a class=\"fragment-anchor\" name=\"#{anchor}\"></a>#{match}")
|
55
|
+
end
|
56
|
+
[html, anchors]
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.title_from_files(fnames)
|
60
|
+
File.basename(fnames.first).
|
61
|
+
gsub(/#{File.extname(fnames.first)}$/, '').
|
62
|
+
gsub(/[^a-zA-Z0-9\-_ ]/, '').
|
63
|
+
gsub(/[\-_]/, ' ').
|
64
|
+
split(/\s+/).
|
65
|
+
map {|s| s.capitalize}.
|
66
|
+
join(' ')
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.body_class(flags)
|
70
|
+
if flags[:pdf]
|
71
|
+
'pdf-layout'
|
72
|
+
elsif flags[:nav]
|
73
|
+
'nav-layout'
|
74
|
+
else
|
75
|
+
''
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.build_nav(anchors)
|
80
|
+
html = ['<ul id="main-nav">']
|
81
|
+
anchors.each do |anchor, text|
|
82
|
+
html << "<li><a href=\"##{anchor}\">#{text}</a></li>"
|
83
|
+
end
|
84
|
+
html << '</ul>'
|
85
|
+
html.join('')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
MarkdownPreview::TEMPLATE = <<END
|
90
|
+
<!doctype html>
|
91
|
+
<head>
|
92
|
+
<title>$title$</title>
|
93
|
+
<style>
|
94
|
+
html, body, div, span, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote,
|
95
|
+
pre,a, abbr, acronym, address, big, cite, code,del, dfn, em, img, ins, kbd, q,
|
96
|
+
s, samp,small, strike, strong, sub, sup, tt, var,dl, dt, dd, ol, ul, li,
|
97
|
+
fieldset, form, label, legend,table, caption, tbody, tfoot, thead, tr, th, td {
|
98
|
+
margin: 0; padding: 0; border: 0; outline: 0; font-weight: inherit;
|
99
|
+
font-style: inherit;
|
100
|
+
vertical-align: baseline;
|
101
|
+
}
|
102
|
+
body { text-align: center; font-size: 11.5px; background: #e0e0e0;
|
103
|
+
line-height: 1.7em; color: #333; width: 100%; height: 100%;
|
104
|
+
font-family: lucida grande, helvetica, sans-serif; }
|
105
|
+
h1,h2,h3,h4,h5,h6 { font-weight: bold; font-family: Copperplate / Copperplate Gothic Light, sans-serif; }
|
106
|
+
h1 { margin: 36px 0 24px; color: #111; border-bottom: 1px dashed #aaa;
|
107
|
+
padding-bottom: 6px; font-size: 2.2em; }
|
108
|
+
h1 + p, h1 + ol, h1 + ul { margin-top: -12px; }
|
109
|
+
h2, h3, h4, h5, h6 { margin: 24px 0; color: #111; }
|
110
|
+
h2 + p, h2 + ol, h2 + ul,
|
111
|
+
h3 + p, h3 + ol, h3 + ul,
|
112
|
+
h4 + p, h4 + ol, h4 + ul,
|
113
|
+
h5 + p, h5 + ol, h5 + ul,
|
114
|
+
h6 + p, h6 + ol, h6 + ul { margin-top: -12px; }
|
115
|
+
h2 { font-size: 1.8em; }
|
116
|
+
h3 { font-size: 1.6em; }
|
117
|
+
h3 { font-size: 1.4em; }
|
118
|
+
h4 { font-size: 1.2em; }
|
119
|
+
h5 { font-size: 1.1em; }
|
120
|
+
h6 { font-size: 1em; }
|
121
|
+
a { color: #a37142; text-decoration: none; }
|
122
|
+
a:hover { color: #234f32; }
|
123
|
+
.fragment-anchor + h1, h1:first-child { margin-top: 0; }
|
124
|
+
select, input, textarea { font: 99% lucida grande, helvetica, sans-serif; }
|
125
|
+
pre, code { font-family: Lucida Console, Monaco, monospace; }
|
126
|
+
ol { list-style: decimal; }
|
127
|
+
ul { list-style: disc; }
|
128
|
+
ol, ul { margin: 24px 0 24px 1.7em; }
|
129
|
+
p + ol, p + ul { margin-top: -16px; }
|
130
|
+
ol:last-child, ul:last-child { margin-bottom: 0; }
|
131
|
+
table { border-collapse: collapse; border-spacing: 0; }
|
132
|
+
caption, th, td { text-align: left; font-weight: normal; }
|
133
|
+
blockquote:before, blockquote:after,q:before, q:after { content: ""; }
|
134
|
+
blockquote, q { quotes: "" ""; }
|
135
|
+
em { font-style: italic; }
|
136
|
+
strong { font-weight: bold; }
|
137
|
+
p { margin: 24px 0; }
|
138
|
+
p:first-child { margin-top: 0; }
|
139
|
+
p:last-child { margin-bottom: 0; }
|
140
|
+
#main { width: 500px; margin: 60px auto; text-align: left; position: relative;
|
141
|
+
left: 0; }
|
142
|
+
.section { padding: 36px; background: #fff; border: 1px solid #bcbcbc;
|
143
|
+
-webkit-box-shadow: 2px 2px 4px #ccc;
|
144
|
+
-moz-box-shadow: 2px 2px 4px #ccc; margin-bottom: 36px; }
|
145
|
+
.section pre { border-top: 1px solid #000; border-bottom: 1px solid #000;
|
146
|
+
color: #fff; background: #555; width: 100%; padding: 12px 36px;
|
147
|
+
position: relative; right: 36px; font-family: Monaco, monospace; }
|
148
|
+
.section pre code { font-weight: normal; }
|
149
|
+
.section code { font-family: Monaco, monospace; font-weight: bold; }
|
150
|
+
.section strong { border-bottom: 1px dashed #aaa; }
|
151
|
+
|
152
|
+
#main-nav { position: fixed; top: 40px; text-align: left; list-style: none;
|
153
|
+
text-align: right; margin-left: -170px; width: 240px; }
|
154
|
+
#main-nav li { margin-bottom: 4px; }
|
155
|
+
.nav-layout #main { left: 100px; }
|
156
|
+
|
157
|
+
body.pdf-layout { background: #fff; line-height: 1.4em; font-size: 10px; }
|
158
|
+
.pdf-layout #main { width: 420px; }
|
159
|
+
.pdf-layout h1, .pdf-layout h2, .pdf-layout h3, .pdf-layout h4,
|
160
|
+
.pdf-layout h5, .pdf-layout h6 { margin: 12px 0 -6px; color: #000; }
|
161
|
+
.pdf-layout h1 { font-size: 1.4em; border-bottom: none; }
|
162
|
+
.pdf-layout h2 { font-size: 1.3em; }
|
163
|
+
.pdf-layout h3 { font-size: 1.2em; }
|
164
|
+
.pdf-layout h4 { font-size: 1.1em; }
|
165
|
+
.pdf-layout h5 { font-size: 1em; }
|
166
|
+
.pdf-layout .section { border: none; margin-bottom: 0; padding: 12px 0 0 0;
|
167
|
+
-webkit-box-shadow: none; -moz-box-shadow: none; }
|
168
|
+
.pdf-layout .section:first-child { padding-top: 0; }
|
169
|
+
.pdf-layout .section pre { position: static; padding: 6px 12px; width: 396px;
|
170
|
+
background: #e0e0e0; color: #000; font-size: 0.95em; border: none; }
|
171
|
+
.pdf-layout .section pre:last-child { margin-bottom: 24px; }
|
172
|
+
.pdf-layout .section code { font-weight: normal; }
|
173
|
+
.pdf-layout p { text-align: justify; }
|
174
|
+
.pdf-layout p, .pdf-layout ol, .pdf-layout ul { margin-top: 16px;
|
175
|
+
margin-bottom: 16px; }
|
176
|
+
</style>
|
177
|
+
</head>
|
178
|
+
<body class="$class$">
|
179
|
+
<div id="main">
|
180
|
+
<div class="section">$body$</div>
|
181
|
+
$nav$
|
182
|
+
</div>
|
183
|
+
</body>
|
184
|
+
</html>
|
185
|
+
END
|
data/mdprev_test.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mdprev'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
class MarkdownPreviewTest < MiniTest::Unit::TestCase
|
6
|
+
def test_build_anchors
|
7
|
+
body, anchors = MarkdownPreview.build_body_and_anchors("
|
8
|
+
# Header !@%^_ 1\n
|
9
|
+
Paragraph\n
|
10
|
+
# Header 2\n
|
11
|
+
Paragraph 2\n
|
12
|
+
".gsub(/^ +/, ''))
|
13
|
+
assert_equal(
|
14
|
+
[['header-1', 'Header !@%^_ 1'], ['header-2', 'Header 2']],
|
15
|
+
anchors)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_build_body
|
19
|
+
body, anchors = MarkdownPreview.build_body_and_anchors("
|
20
|
+
# Header
|
21
|
+
Sample Text
|
22
|
+
".gsub(/^\s+/, ''))
|
23
|
+
assert_match(/<h1>Header<\/h1>/, body)
|
24
|
+
assert_match(/<p>Sample Text<\/p>/, body)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_title
|
28
|
+
assert_equal('Sample', MarkdownPreview.title_from_files(['sample.md']))
|
29
|
+
assert_equal(
|
30
|
+
'League Of Extraordinary Gentlemen',
|
31
|
+
MarkdownPreview.title_from_files([
|
32
|
+
'league-of-extraordinary-_^Gentlemen',
|
33
|
+
'ignoreothernames'
|
34
|
+
]))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_build_nav
|
38
|
+
nav = MarkdownPreview.build_nav([
|
39
|
+
['first-id', 'First ID'],
|
40
|
+
['second-item', 'Second Item'],
|
41
|
+
['third', 'Third'],
|
42
|
+
['fourth', 'Fourth'],
|
43
|
+
['fifth', 'Fifth']
|
44
|
+
])
|
45
|
+
assert_match(/^<ul id="main-nav">/, nav)
|
46
|
+
assert_match(/<li><a href="#first-id">First ID<\/a><\/li>/, nav)
|
47
|
+
assert_match(/<li><a href="#second-item">Second Item<\/a><\/li>/, nav)
|
48
|
+
assert_match(/<li><a href="#third">Third<\/a><\/li>/, nav)
|
49
|
+
assert_match(/<li><a href="#fourth">Fourth<\/a><\/li>/, nav)
|
50
|
+
assert_match(/<\/ul>$/, nav)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_body_class
|
54
|
+
assert_equal('', MarkdownPreview.body_class(:pdf => false, :nav => false))
|
55
|
+
assert_equal('pdf-layout', MarkdownPreview.body_class(:pdf => true))
|
56
|
+
assert_equal('nav-layout', MarkdownPreview.body_class(:nav => true))
|
57
|
+
end
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mdprev
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Hugh Bien
|
14
|
+
autorequire:
|
15
|
+
bindir: .
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-10-06 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: kramdown
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: minitest
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: Run files through markdown processor and open in a browser. Adds a default theme. Works with wkhtmltopdf.
|
49
|
+
email:
|
50
|
+
- hugh@hughbien.com
|
51
|
+
executables:
|
52
|
+
- mdprev
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- mdprev.rb
|
59
|
+
- mdprev_test.rb
|
60
|
+
- mdprev
|
61
|
+
- ./mdprev
|
62
|
+
homepage: https://github.com/hughbien/mdprev
|
63
|
+
licenses: []
|
64
|
+
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 23
|
85
|
+
segments:
|
86
|
+
- 1
|
87
|
+
- 3
|
88
|
+
- 6
|
89
|
+
version: 1.3.6
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.8.11
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: Styled preview of markdown files in browser
|
97
|
+
test_files: []
|
98
|
+
|