mdprev 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|