tartancloth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tartancloth.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jeff McAffee
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # TartanCloth
2
+
3
+ A wrapper around the BlueCloth gem which incorporates HTML5 headers, footers,
4
+ and a table of contents all with a nice stylesheet.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'tartancloth'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install tartancloth
19
+
20
+ ## Usage
21
+
22
+ TartanCloth's main feature is that it generates a _linked_ Table of Contents
23
+ from the headers (`h1, h2, h3, h4, h5, h6`) in your markdown document.
24
+
25
+ Simply add a header at any header level (level 2: `h2`, shown here):
26
+
27
+ ## TOC
28
+
29
+ TartanCloth will parse the document and collect all headers **after** the TOC
30
+ and create a Table of Contents. The Table of Contents will be inserted at the
31
+ location of the `## TOC` header, replacing it.
32
+
33
+ In most of my documents, I include the Title (h1) and a summary before
34
+ displaying the TOC. I didn't want to include the sections prior to the TOC in
35
+ the Table of Contents, that's why header collection starts after.
36
+
37
+ ### Quick Example
38
+
39
+ A quick example of using TartanCloth.
40
+
41
+ Given the following markdown:
42
+
43
+ ###### markdown.md
44
+
45
+ # My Wrench User Manual
46
+
47
+ ## Summary
48
+
49
+ My Wrench is an awesome tool blah, blah blah.
50
+
51
+ - - -
52
+ ## TOC
53
+
54
+ - - -
55
+ ## Running Wrench from the command line
56
+
57
+ How to run wrench from the command line
58
+
59
+ - - -
60
+ ## Documentation Conventions
61
+
62
+ Text surrounded with square brackets [] is optional.
63
+ Text formatted like `this` indicates a _keyword_.
64
+
65
+ ### Some Other Header
66
+
67
+ #### Another, Deeper Header
68
+
69
+ ##### Yet Another Header
70
+
71
+ - - -
72
+ ## Look at this header
73
+
74
+ ###### Small Note Header
75
+
76
+
77
+ Markit.rb will convert the markdown to HTML, with an embedded stylesheet and
78
+ include a Table of Contents.
79
+
80
+ ###### markit.rb
81
+
82
+ require 'tartancloth'
83
+
84
+ title = 'My Markdown'
85
+ mdsrc = 'path/to/my/markdown.md'
86
+ mdout = 'path/to/my/markdown.html'
87
+
88
+ puts "Title: #{title}"
89
+ puts "Source: #{mdsrc}"
90
+ puts "Output: #{mdout}"
91
+
92
+ TartanCloth.new( mdsrc, title ).to_html_file( mdout )
93
+
94
+ - - -
95
+ ### Using TartanCloth from a Rake Task
96
+
97
+ I like to use TartanCloth from a rake task to generate pretty docs.
98
+
99
+ ###### markdown.rake
100
+
101
+ require "pathname"
102
+ require 'tartancloth'
103
+
104
+ # Call this as: rake md2html[path/to/file/to/convert.md]
105
+ #
106
+ desc "Convert a .MD file to HTML"
107
+ task :md2html, [:mdfile] do |t, args|
108
+ Rake::Task['markdown:md2html'].invoke( args[:mdfile] )
109
+ end
110
+
111
+
112
+ namespace :markdown do
113
+
114
+ desc "md2html usage instructions"
115
+ task :help do
116
+ puts <<HELP
117
+
118
+ ----------------------------------------------------------------------
119
+
120
+ Usage: md2html
121
+
122
+ Generate HTML from a markdown document
123
+
124
+ The generated HTML document will be located in the same location as
125
+ the source markdown document.
126
+
127
+ To generate the document, call it as follows:
128
+
129
+ rake md2html[path/to/doc.md]
130
+
131
+ Note that no quotes are needed.
132
+
133
+ To set the title of the document, provide it as an ENV variable:
134
+
135
+ TITLE="My Title" rake md2html[path/to/doc.md]
136
+
137
+ If no title is given, the title will default to the filename.
138
+
139
+ ----------------------------------------------------------------------
140
+
141
+ HELP
142
+ end
143
+
144
+
145
+ task :md2html, [:mdfile] do |t, args|
146
+ args.with_defaults(:mdfile => nil)
147
+ if args[:mdfile].nil?
148
+ puts "ERROR: Full path to file to convert required."
149
+ puts
150
+ puts "usage: rake md2html['path/to/md/file.md']"
151
+ exit
152
+ end
153
+
154
+ mdsrc = args[:mdfile]
155
+ mdout = mdsrc.pathmap( "%X.html" )
156
+ title = ENV['TITLE']
157
+
158
+ puts "Title: #{title}"
159
+ puts "Source: #{mdsrc}"
160
+ puts "Output: #{mdout}"
161
+
162
+ TartanCloth.new( mdsrc, title ).to_html_file( mdout )
163
+ end
164
+
165
+ end # namespace :markdown
166
+
167
+ - - -
168
+ ### A Task to Generate a User Manual
169
+
170
+ I use the tasks above to generate a user manual as well:
171
+
172
+ ###### Rakefile
173
+
174
+ require "bundler/gem_tasks"
175
+
176
+ desc 'Generate user manual HTML'
177
+ task :man do
178
+
179
+ ENV['TITLE'] = 'User Manual'
180
+
181
+ Rake::Task['markdown:md2html'].invoke( 'docs/user_manual.md' )
182
+ Rake::Task['markdown:md2html'].reenable
183
+
184
+ end
185
+
186
+ - - -
187
+ ## Credits
188
+
189
+ + [BlueCloth](https://github.com/ged/bluecloth) is used to generate the markdown
190
+ + [Nokogiri](http://nokogiri.org/) is used to generate the table of contents
191
+ + Chris Coyier has some [great code for pretty HRs](http://css-tricks.com/examples/hrs/)
192
+
193
+ - - -
194
+ ## Contributing
195
+
196
+ 1. Fork it
197
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
198
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
199
+ 4. Push to the branch (`git push origin my-new-feature`)
200
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,335 @@
1
+ require 'bluecloth'
2
+ require 'nokogiri'
3
+
4
+
5
+
6
+ class TartanCloth
7
+
8
+ VERSION = "0.0.1"
9
+
10
+ attr_accessor :title
11
+
12
+ def initialize( markdown_file, title = nil )
13
+ @markdown_file = markdown_file
14
+ @title = title
15
+ end
16
+
17
+ def to_html_file( html_file )
18
+
19
+ File.open( html_file, 'w') do |f|
20
+ f << to_html()
21
+ end
22
+ end
23
+
24
+ ###
25
+ # Convert a markdown source file to HTML. If a header element with text TOC
26
+ # exists within the markdown document, a Table of Contents will be generated
27
+ # and inserted at that location.
28
+ #
29
+ # The TOC will only contain header (h1-h6) elements from the location of the
30
+ # TOC header to the end of the document
31
+ def to_html
32
+ bc = BlueCloth::new( File::read( @markdown_file ), header_labels: true )
33
+ content = bc.to_html
34
+
35
+ content = build_toc( content )
36
+
37
+ html = ""
38
+
39
+ # Add a well formed HTML5 header.
40
+ html << html_header(title)
41
+
42
+ # Add the body content.
43
+ html << content
44
+
45
+ # Add the document closing tags.
46
+ html << html_footer()
47
+ end
48
+
49
+ ###
50
+ # Build a TOC based on headers located within HTML content.
51
+ # If a header element with text TOC exists within the markdown document, a
52
+ # Table of Contents will be generated and inserted at that location.
53
+ #
54
+ # The TOC will only contain header (h1-h6) elements from the location of the
55
+ # TOC header to the end of the document
56
+ def build_toc( html_content )
57
+ # Generate Nokogiri elements from HTML
58
+ doc = Nokogiri::HTML::DocumentFragment.parse( html_content )
59
+
60
+ # Find the TOC header
61
+ toc = find_toc_header(doc)
62
+
63
+ # Just return what was passed to us if there's no TOC.
64
+ return html_content if toc.nil?
65
+
66
+ # Get all headers in the document, starting from the TOC header.
67
+ headers = get_headers(doc, toc)
68
+
69
+ # Build the link info for the TOC.
70
+ toc_links = []
71
+ headers.each do |element|
72
+ toc_links << link_hash(element)
73
+ end
74
+
75
+ # Convert link info to markdown.
76
+ toc_md = toc_to_markdown(toc_links)
77
+
78
+ # Convert the TOC markdown to HTML
79
+ bc = BlueCloth::new( toc_md, pseudoprotocols: true )
80
+ toc_content = bc.to_html
81
+
82
+ # Convert the TOC HTML to Nokogiri elements.
83
+ toc_html = Nokogiri::HTML::DocumentFragment.parse(toc_content)
84
+
85
+ # Add toc class to the <ul> element
86
+ toc_html.css('ul').add_class('toc')
87
+
88
+ # Insert the TOC content before the toc element
89
+ toc.before(toc_html.children)
90
+
91
+ # Remove the TOC header placeholder element.
92
+ toc.remove
93
+
94
+ # Return the HTML
95
+ doc.to_html
96
+ end
97
+
98
+ ###
99
+ # Convert an array of link hashes to markdown
100
+ #
101
+ # toc_links - hash of links
102
+ # returns - markdown content
103
+ def toc_to_markdown(toc_links)
104
+ markdown = "## Table of Contents\n\n"
105
+ toc_links.each do |link_data|
106
+ text = link_data[:text]
107
+ link = link_data[:link]
108
+ klass = link_data[:klass]
109
+ markdown << "+ [[#{text}](##{link})](class:#{klass})\n"
110
+ end
111
+ markdown << "\n"
112
+ end
113
+
114
+ ###
115
+ # return the TOC header element or nil
116
+ #
117
+ # doc - Nokogiri DocumentFragment
118
+ def find_toc_header(doc)
119
+ return nil unless doc
120
+
121
+ doc.children.each do |element|
122
+ return element if is_toc_header(element)
123
+ end
124
+
125
+ return nil
126
+ end
127
+
128
+ ###
129
+ # returns true if the element is a header (h1-h6) element
130
+ def is_header_element(element)
131
+ %w(h1 h2 h3 h4 h5 h6).include? element.name
132
+ end
133
+
134
+ ###
135
+ # returns true when a header (h1-h6) element contains the text: TOC
136
+ def is_toc_header(element)
137
+ return (is_header_element(element) && element.text == 'TOC')
138
+ end
139
+
140
+ ###
141
+ # Create an array of all header (h1-h6) elements in an HTML document
142
+ # starting from a specific element
143
+ #
144
+ # starting_element - element to start parsing from
145
+ # returns - array of Nokogiri elements
146
+ def get_headers(doc, starting_element)
147
+ headers = []
148
+ capture = false
149
+
150
+ doc.children.each do |element|
151
+ unless capture
152
+ capture = true if element == starting_element
153
+ next
154
+ end # unless
155
+
156
+ headers << element if is_header_element(element)
157
+ end
158
+
159
+ headers
160
+ end
161
+
162
+ ###
163
+ # Build a link hash for an element containing the text, link,
164
+ # and a children array.
165
+ #
166
+ # element - Nokogiri element
167
+ def link_hash(element)
168
+ # The previous element should be a simple anchor.
169
+ # Get the actual link value from the anchor.
170
+ a = element.previous_element
171
+ anchor_link = a.attributes['name'].value if a.name == 'a'
172
+
173
+ # Store the header text (link text) and the anchor link and a class for styling.
174
+ { text: element.text, link: anchor_link, klass: "#{element.name}toc" }
175
+ end
176
+
177
+ # Create an HTML5 header
178
+ #
179
+ # returns - HTML header and body open tags
180
+ def html_header(title)
181
+ styles = css()
182
+ header = <<HTML_HEADER
183
+ <!DOCTYPE html>
184
+ <html>
185
+ <head><title>#{title}</title></head>
186
+
187
+ #{styles}
188
+
189
+ <body>
190
+ <div class="content">
191
+ <div class="rendered-content">
192
+
193
+ HTML_HEADER
194
+ end
195
+
196
+ ###
197
+ # Create some stylish CSS
198
+ #
199
+ # returns - html style element
200
+ def css()
201
+ styles = <<CSS
202
+ <style media="screen" type="text/css">
203
+ <!--
204
+ body {
205
+ font-family: Arial, sans-serif;
206
+ }
207
+
208
+ .content {
209
+ margin: 0 auto;
210
+ min-height: 100%;
211
+ padding: 0 0 100px;
212
+ width: 980px;
213
+ border: 1px solid #ccc;
214
+ border-radius: 5px;
215
+ }
216
+
217
+ .rendered-content {
218
+ padding: 10px;
219
+ }
220
+
221
+ /* Fancy HR styles based on http://css-tricks.com/examples/hrs/ */
222
+ hr {
223
+ border: 0;
224
+ height: 1px;
225
+ background-image: -webkit-linear-gradient(left, rgba(200,200,200,1), rgba(200,200,200,0.5), rgba(200,200,200,0));
226
+ background-image: -moz-linear-gradient(left, rgba(200,200,200,1), rgba(200,200,200,0.5), rgba(200,200,200,0));
227
+ background-image: -ms-linear-gradient(left, rgba(200,200,200,1), rgba(200,200,200,0.5), rgba(200,200,200,0));
228
+ background-image: -o-linear-gradient(left, rgba(200,200,200,1), rgba(200,200,200,0.5), rgba(200,200,200,0));
229
+ }
230
+
231
+ h1 {
232
+ font-size: 24px;
233
+ font-weight: normal;
234
+ line-height: 1.25;
235
+ }
236
+
237
+ h2 {
238
+ font-size: 20px;
239
+ font-weight: normal;
240
+ line-height: 1.5;
241
+ }
242
+
243
+ h3 {
244
+ font-size: 16px;
245
+ font-weight: bold;
246
+ line-height: 1.5625;
247
+ }
248
+
249
+ h4 {
250
+ font-size: 14px;
251
+ font-weight: bold;
252
+ line-height: 1.5;
253
+ }
254
+
255
+ h5 {
256
+ font-size: 12px;
257
+ font-weight: bold;
258
+ line-height: 1.66;
259
+ text-transform: uppercase;
260
+ }
261
+
262
+ h6 {
263
+ font-size: 12px;
264
+ font-style: italic;
265
+ font-weight: bold;
266
+ line-height: 1.66;
267
+ text-transform: uppercase;
268
+ }
269
+
270
+ pre {
271
+ margin-left: 2em;
272
+ display: block;
273
+ background: #f5f5f5;
274
+ font-family: monospace;
275
+ border: 1px solid #ccc;
276
+ border-radius: 2px;
277
+ padding: 1px 3px;
278
+ }
279
+
280
+ code {
281
+ background: #f5f5f5;
282
+ font-family: monospace;
283
+ border: 1px solid #ccc;
284
+ border-radius: 2px;
285
+ padding: 1px 3px;
286
+ }
287
+
288
+ pre, code {
289
+ font-size: 12px;
290
+ line-height: 1.4;
291
+ }
292
+
293
+ pre code {
294
+ border: 0;
295
+ padding: 0;
296
+ }
297
+
298
+ ul.toc li {
299
+ list-style: none;
300
+ font-size: 14px;
301
+ }
302
+
303
+ ul.toc li span.h3toc {
304
+ margin-left: 20px;
305
+ }
306
+
307
+ ul.toc li span.h4toc {
308
+ margin-left: 40px;
309
+ }
310
+
311
+ ul.toc li span.h5toc {
312
+ margin-left: 60px;
313
+ }
314
+
315
+ ul.toc li span.h6toc {
316
+ margin-left: 80px;
317
+ }
318
+ -->
319
+ </style>
320
+ CSS
321
+ end
322
+
323
+ ###
324
+ # returns - HTML closing tags
325
+ def html_footer()
326
+ footer = <<HTML_FOOTER
327
+
328
+ </div> <!-- .content -->
329
+ </div> <!-- .rendered-content -->
330
+ </body>
331
+ </html>
332
+ HTML_FOOTER
333
+ end
334
+
335
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tartancloth'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tartancloth"
8
+ spec.version = TartanCloth::VERSION
9
+ spec.authors = ["Jeff McAffee"]
10
+ spec.email = ["jeff@ktechsystems.com"]
11
+ spec.description = %q{A wrapper around the BlueCloth gem which incorporates HTML5 headers, footers, and a table of contents all with a nice stylesheet.}
12
+ spec.summary = %q{Generate nice HTML with a table of contents}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_runtime_dependency 'bluecloth', '~> 2.2', '>= 2.2.0'
25
+ spec.add_runtime_dependency 'nokogiri', '~> 1.5', '>= 1.5.6'
26
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tartancloth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeff McAffee
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bluecloth
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.2'
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: 2.2.0
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ version: '2.2'
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: 2.2.0
68
+ - !ruby/object:Gem::Dependency
69
+ name: nokogiri
70
+ requirement: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '1.5'
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: 1.5.6
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ~>
85
+ - !ruby/object:Gem::Version
86
+ version: '1.5'
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 1.5.6
90
+ description: A wrapper around the BlueCloth gem which incorporates HTML5 headers,
91
+ footers, and a table of contents all with a nice stylesheet.
92
+ email:
93
+ - jeff@ktechsystems.com
94
+ executables: []
95
+ extensions: []
96
+ extra_rdoc_files: []
97
+ files:
98
+ - .gitignore
99
+ - Gemfile
100
+ - LICENSE.txt
101
+ - README.md
102
+ - Rakefile
103
+ - lib/tartancloth.rb
104
+ - tartancloth.gemspec
105
+ homepage: ''
106
+ licenses:
107
+ - MIT
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 1.8.24
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: Generate nice HTML with a table of contents
130
+ test_files: []
131
+ has_rdoc: