tartancloth 0.0.1

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/.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: