transmuter 0.0.0.1 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +17 -0
- data/Guardfile +13 -0
- data/README.md +35 -1
- data/Rakefile +7 -0
- data/bin/transmute +7 -0
- data/lib/transmuter/cli/thor.rb +99 -0
- data/lib/transmuter/cli/transmute.rb +67 -0
- data/lib/transmuter/cli.rb +13 -0
- data/lib/transmuter/core_ext.rb +4 -0
- data/lib/transmuter/format/html.rb +81 -0
- data/lib/transmuter/format/markdown.rb +45 -0
- data/lib/transmuter/format/pdf.rb +25 -0
- data/lib/transmuter/format.rb +9 -0
- data/lib/transmuter/version.rb +3 -4
- data/lib/transmuter.rb +5 -4
- data/spec/spec_helper.rb +25 -0
- data/spec/transmuter/cli/thor_spec.rb +175 -0
- data/spec/transmuter/cli/transmute_spec.rb +138 -0
- data/spec/transmuter/cli_spec.rb +4 -0
- data/spec/transmuter/format/html_spec.rb +159 -0
- data/spec/transmuter/format/markdown_spec.rb +150 -0
- data/spec/transmuter/format/pdf_spec.rb +49 -0
- data/spec/transmuter/format_spec.rb +4 -0
- data/stylesheets/default.css +67 -0
- data/transmuter.gemspec +26 -3
- metadata +180 -8
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -2,3 +2,20 @@ source "http://rubygems.org"
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in transmuter.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
platforms :ruby do
|
7
|
+
# Require rbconfig to figure out the target OS
|
8
|
+
require 'rbconfig'
|
9
|
+
|
10
|
+
unless ENV['TRAVIS']
|
11
|
+
if RbConfig::CONFIG['target_os'] =~ /darwin/i
|
12
|
+
gem 'rb-fsevent', require: false
|
13
|
+
gem 'ruby-growl', require: false
|
14
|
+
gem 'growl', require: false
|
15
|
+
end
|
16
|
+
if RbConfig::CONFIG['target_os'] =~ /linux/i
|
17
|
+
gem 'rb-inotify', require: false
|
18
|
+
gem 'libnotify', require: false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'bundler' do
|
5
|
+
watch('Gemfile')
|
6
|
+
watch(/^.+\.gemspec/)
|
7
|
+
end
|
8
|
+
|
9
|
+
guard 'rspec', :version => 2 do
|
10
|
+
watch(%r{^spec/.+_spec\.rb$})
|
11
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
12
|
+
watch('spec/spec_helper.rb') { "spec" }
|
13
|
+
end
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Transmuter
|
1
|
+
# Transmuter [![Build Status](http://travis-ci.org/TechnoGate/transmuter.png)](http://travis-ci.org/TechnoGate/transmuter)
|
2
2
|
|
3
3
|
Transmuter is a command line tool to convert Markdown files into HTML or PDF
|
4
4
|
files, it can also be used to convert HTML files to PDF, it uses in the
|
@@ -6,6 +6,8 @@ backgound [Redcarper](https://github.com/tanoku/redcarpet),
|
|
6
6
|
[Albino](https://github.com/github/albino) and
|
7
7
|
[PDFkit](https://github.com/jdpace/PDFKit).
|
8
8
|
|
9
|
+
<a href='http://www.pledgie.com/campaigns/16086'><img alt='Click here to lend your support to: Transmuter and make a donation at www.pledgie.com !' src='http://www.pledgie.com/campaigns/16086.png?skin_name=chrome' border='0' /></a>
|
10
|
+
|
9
11
|
# Installation
|
10
12
|
|
11
13
|
To install Transmuter use the command
|
@@ -35,3 +37,35 @@ $ sudo easy_install pygments
|
|
35
37
|
2. Try using the wkhtmltopdf-binary gem (mac + linux i386)
|
36
38
|
|
37
39
|
gem install wkhtmltopdf-binary
|
40
|
+
|
41
|
+
# Usage
|
42
|
+
|
43
|
+
You should check the help
|
44
|
+
|
45
|
+
```bash
|
46
|
+
$ transmute --help
|
47
|
+
```
|
48
|
+
|
49
|
+
To Generate a PDF from a markdown file with the default CSS:
|
50
|
+
|
51
|
+
```bash
|
52
|
+
$ transmute file.md
|
53
|
+
```
|
54
|
+
|
55
|
+
To Generate an HTML from a markdown file with the default CSS:
|
56
|
+
|
57
|
+
```bash
|
58
|
+
$ transmute file.md -t html
|
59
|
+
```
|
60
|
+
|
61
|
+
To Generate an HTML from a markdown file with custom CSS:
|
62
|
+
|
63
|
+
```bash
|
64
|
+
$ transmute file.md -t html -s custom.css
|
65
|
+
```
|
66
|
+
|
67
|
+
Custom CSS files can be be multiple path separated by a space, for example:
|
68
|
+
|
69
|
+
```bash
|
70
|
+
$ transmute file.md -t html -s custom1.css custom2.css
|
71
|
+
```
|
data/Rakefile
CHANGED
data/bin/transmute
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thor/group'
|
3
|
+
|
4
|
+
module Transmuter
|
5
|
+
module CLI
|
6
|
+
module Thor
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.send :include, InstanceMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def self.included(base)
|
14
|
+
base.class_eval <<-END, __FILE__, __LINE__ + 1
|
15
|
+
desc "Transmute one file format into another."
|
16
|
+
|
17
|
+
class_option :input_format,
|
18
|
+
type: :string,
|
19
|
+
required: false,
|
20
|
+
aliases: "-f",
|
21
|
+
desc: "The input format."
|
22
|
+
|
23
|
+
class_option :output_format,
|
24
|
+
type: :string,
|
25
|
+
required: false,
|
26
|
+
aliases: "-t",
|
27
|
+
default: "pdf",
|
28
|
+
desc: "The output format."
|
29
|
+
|
30
|
+
class_option :stylesheets,
|
31
|
+
type: :array,
|
32
|
+
required: false,
|
33
|
+
aliases: "-s",
|
34
|
+
default: [DEFAULT_THEME],
|
35
|
+
desc: "The stylesheets."
|
36
|
+
|
37
|
+
argument :input,
|
38
|
+
type: :string,
|
39
|
+
required: true,
|
40
|
+
aliases: "-i",
|
41
|
+
desc: "The input file name."
|
42
|
+
|
43
|
+
argument :output,
|
44
|
+
type: :string,
|
45
|
+
required: false,
|
46
|
+
aliases: "-o",
|
47
|
+
desc: "The output file name."
|
48
|
+
|
49
|
+
def set_input_filename
|
50
|
+
@input_filename = input
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_input_fileformat
|
54
|
+
@input_fileformat = options[:input_format] || input_format
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_output_fileformat
|
58
|
+
@output_fileformat = options[:output_format]
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_output_filename
|
62
|
+
if output.blank? && options[:output_format].blank?
|
63
|
+
raise ArgumentError, "Either output or output_format should be given,"
|
64
|
+
end
|
65
|
+
|
66
|
+
@output_filename = output || output_file
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_stylesheets
|
70
|
+
@stylesheets = options[:stylesheets]
|
71
|
+
end
|
72
|
+
|
73
|
+
def transmute_input_to_output
|
74
|
+
transmute!
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
def input_format
|
80
|
+
case @input.split('.').last
|
81
|
+
when /^(md|markdown)$/
|
82
|
+
"markdown"
|
83
|
+
when /^(html|htm)/
|
84
|
+
"html"
|
85
|
+
else
|
86
|
+
raise ArgumentError, "No format was given and format could not be parsed from the file name"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def output_file
|
91
|
+
output = @input_filename.dup
|
92
|
+
output.gsub(/^(.+)\\.[^.]*$/, "\\\\1.\#{@output_fileformat}")
|
93
|
+
end
|
94
|
+
END
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Transmuter
|
2
|
+
module CLI
|
3
|
+
module Transmute
|
4
|
+
def self.included(base)
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module InstanceMethods
|
9
|
+
def transmute!
|
10
|
+
set_klasses!
|
11
|
+
set_methods
|
12
|
+
verify_klasses!
|
13
|
+
|
14
|
+
source_klass_instance = @source_klass.new(read_input_file, parse_transmute_options)
|
15
|
+
output = source_klass_instance.send(@source_transform_method)
|
16
|
+
|
17
|
+
write_output_file(output)
|
18
|
+
end
|
19
|
+
|
20
|
+
def transmute
|
21
|
+
transmute!
|
22
|
+
rescue Exception => e
|
23
|
+
handle_error(e)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
def handle_error(exception)
|
28
|
+
# TODO: Handle error properly
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_klasses!
|
32
|
+
@source_klass ||= "::Transmuter::Format::#{@input_fileformat.to_s.camelcase}".constantize
|
33
|
+
@destination_klass ||= "::Transmuter::Format::#{@output_fileformat.to_s.camelcase}".constantize
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_methods
|
37
|
+
@source_transform_method ||= "to_#{@output_fileformat.to_s.downcase}".to_sym
|
38
|
+
@destination_process_method ||= :process
|
39
|
+
end
|
40
|
+
|
41
|
+
def verify_klasses!
|
42
|
+
|
43
|
+
raise NotImplementedError,
|
44
|
+
"#{@source_klass} does not respond to #{@source_transform_method}" unless
|
45
|
+
@source_klass.public_instance_methods.include?(@source_transform_method)
|
46
|
+
|
47
|
+
raise NotImplementedError,
|
48
|
+
"#{@destination_klass} does not respond to #{@destination_process_method}" unless
|
49
|
+
@destination_klass.public_instance_methods.include?(@destination_process_method)
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_input_file
|
53
|
+
@input_file_contents ||= File.read(@input_filename)
|
54
|
+
end
|
55
|
+
|
56
|
+
def write_output_file(contents)
|
57
|
+
File.open(@output_filename, 'w') { |f| f.write(contents) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_transmute_options
|
61
|
+
{ stylesheets: @stylesheets }
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'transmuter/cli/thor'
|
2
|
+
require 'transmuter/cli/transmute'
|
3
|
+
|
4
|
+
module Transmuter
|
5
|
+
module CLI
|
6
|
+
class Runner < ::Thor::Group
|
7
|
+
DEFAULT_THEME = File.expand_path(File.join(ROOT_PATH, 'stylesheets', 'default.css'))
|
8
|
+
|
9
|
+
include Transmute
|
10
|
+
include Thor
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'albino'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Transmuter
|
5
|
+
module Format
|
6
|
+
class Html
|
7
|
+
|
8
|
+
def initialize(html, options = {})
|
9
|
+
parse_options(options)
|
10
|
+
@html = html
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_pdf
|
14
|
+
html = process
|
15
|
+
pdf = Pdf.new(html, get_options)
|
16
|
+
pdf.process
|
17
|
+
end
|
18
|
+
|
19
|
+
def process
|
20
|
+
include_inline_stylesheets(syntax_highlighter)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def get_options
|
25
|
+
options = @options.dup
|
26
|
+
options.delete(:redcarpet_options)
|
27
|
+
options
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_options(options)
|
31
|
+
@options = options.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
def read_stylesheet_files
|
35
|
+
case @options[:stylesheets]
|
36
|
+
when Array
|
37
|
+
@options[:stylesheets].collect do |f|
|
38
|
+
File.read(f)
|
39
|
+
end.join("\n")
|
40
|
+
when String
|
41
|
+
File.read @options[:stylesheets]
|
42
|
+
when NilClass
|
43
|
+
# Apparently no stylesheets has been requested
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def include_inline_stylesheets(html)
|
48
|
+
if @options[:stylesheets].present?
|
49
|
+
stylesheet_contents = read_stylesheet_files
|
50
|
+
|
51
|
+
doc = Nokogiri::HTML(html)
|
52
|
+
head = doc.xpath('/html/head').first
|
53
|
+
|
54
|
+
style = Nokogiri::XML::Node.new "style", doc
|
55
|
+
style['type'] = "text/css"
|
56
|
+
style.content = stylesheet_contents
|
57
|
+
|
58
|
+
unless head.present?
|
59
|
+
head = Nokogiri::XML::Node.new "head", doc
|
60
|
+
body = doc.xpath('/html/body').first
|
61
|
+
body.add_previous_sibling head
|
62
|
+
end
|
63
|
+
|
64
|
+
style.parent = head
|
65
|
+
|
66
|
+
html = doc.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
html
|
70
|
+
end
|
71
|
+
|
72
|
+
def syntax_highlighter
|
73
|
+
doc = Nokogiri::HTML(@html)
|
74
|
+
doc.search("//pre[@lang]").each do |pre|
|
75
|
+
pre.replace Albino.colorize(pre.text.rstrip, pre[:lang].downcase.to_sym)
|
76
|
+
end
|
77
|
+
doc.to_s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'redcarpet'
|
2
|
+
|
3
|
+
module Transmuter
|
4
|
+
module Format
|
5
|
+
class Markdown
|
6
|
+
REDCARPET_OPTIONS = [:autolink, :no_intraemphasis, :fenced_code, :gh_blockcode]
|
7
|
+
|
8
|
+
def initialize(markdown, options = {})
|
9
|
+
parse_options(options)
|
10
|
+
@markdown = markdown
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_pdf
|
14
|
+
html = to_html
|
15
|
+
pdf = Pdf.new(html, get_options)
|
16
|
+
pdf.process
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_html
|
20
|
+
html = Html.new(parse_markdown, get_options)
|
21
|
+
html.process
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def get_options
|
26
|
+
options = @options.dup
|
27
|
+
options.delete(:redcarpet_options)
|
28
|
+
options
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_options(options)
|
32
|
+
options = options.dup
|
33
|
+
@options = options.merge!(:redcarpet_options => REDCARPET_OPTIONS)
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_markdown
|
37
|
+
Redcarpet.new(@markdown, *@options[:redcarpet_options])
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_markdown
|
41
|
+
create_markdown.to_html
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'pdfkit'
|
2
|
+
|
3
|
+
module Transmuter
|
4
|
+
module Format
|
5
|
+
class Pdf
|
6
|
+
|
7
|
+
def initialize(html, options = {})
|
8
|
+
parse_options(options)
|
9
|
+
@html = html
|
10
|
+
end
|
11
|
+
|
12
|
+
def process
|
13
|
+
kit = PDFKit.new(@html, :page_size => @options[:page_size])
|
14
|
+
|
15
|
+
kit.to_pdf
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
def parse_options(options)
|
20
|
+
options = options.dup
|
21
|
+
@options = options.merge!(:page_size => 'Letter')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/transmuter/version.rb
CHANGED
data/lib/transmuter.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
ROOT_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require "transmuter/version"
|
4
|
+
require "transmuter/core_ext"
|
5
|
+
require "transmuter/format"
|
6
|
+
require "transmuter/cli"
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
ROOT_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
$:.push File.expand_path("#{ROOT_PATH}/lib")
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rspec'
|
7
|
+
|
8
|
+
# Require the library
|
9
|
+
require 'transmuter'
|
10
|
+
|
11
|
+
include Transmuter
|
12
|
+
|
13
|
+
# Require support files
|
14
|
+
Dir[ROOT_PATH + "/spec/support/**/*.rb"].each {|f| require f}
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
# == Mock Framework
|
18
|
+
#
|
19
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
20
|
+
#
|
21
|
+
config.mock_with :mocha
|
22
|
+
# config.mock_with :flexmock
|
23
|
+
# config.mock_with :rr
|
24
|
+
# config.mock_with :rspec
|
25
|
+
end
|