docverter-server 1.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.buildpacks +2 -0
  2. data/.gitignore +6 -0
  3. data/.vendor_urls +3 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +55 -0
  6. data/LICENSE +67 -0
  7. data/Procfile +1 -0
  8. data/README.md +32 -0
  9. data/Rakefile +23 -0
  10. data/config.ru +3 -0
  11. data/doc/api.md +1664 -0
  12. data/doc/examples/html_to_encrypted_pdf/convert.sh +16 -0
  13. data/doc/examples/html_to_encrypted_pdf/imfeldoublepica.ttf +0 -0
  14. data/doc/examples/html_to_encrypted_pdf/input.html +27 -0
  15. data/doc/examples/html_to_encrypted_pdf/marcellus.ttf +0 -0
  16. data/doc/examples/html_to_encrypted_pdf/stylesheet.css +22 -0
  17. data/doc/examples/html_to_pdf/convert.sh +16 -0
  18. data/doc/examples/html_to_pdf/imfeldoublepica.ttf +0 -0
  19. data/doc/examples/html_to_pdf/input.html +27 -0
  20. data/doc/examples/html_to_pdf/marcellus.ttf +0 -0
  21. data/doc/examples/html_to_pdf/stylesheet.css +22 -0
  22. data/doc/examples/markdown_to_epub/chapter1.md +11 -0
  23. data/doc/examples/markdown_to_epub/chapter2.md +10 -0
  24. data/doc/examples/markdown_to_epub/convert.sh +20 -0
  25. data/doc/examples/markdown_to_epub/document-open.png +0 -0
  26. data/doc/examples/markdown_to_epub/markdown_to_epub.epub +241 -0
  27. data/doc/examples/markdown_to_epub/markdown_to_epub.epub.html +251 -0
  28. data/doc/examples/markdown_to_epub/metadata.xml +2 -0
  29. data/doc/examples/markdown_to_epub/stylesheet.css +216 -0
  30. data/doc/examples/markdown_to_epub/title.txt +2 -0
  31. data/doc/examples/markdown_to_mobi/chapter1.md +11 -0
  32. data/doc/examples/markdown_to_mobi/chapter2.md +10 -0
  33. data/doc/examples/markdown_to_mobi/convert.sh +20 -0
  34. data/doc/examples/markdown_to_mobi/document-open.png +0 -0
  35. data/doc/examples/markdown_to_mobi/metadata.xml +2 -0
  36. data/doc/examples/markdown_to_mobi/stylesheet.css +216 -0
  37. data/doc/examples/markdown_to_mobi/title.txt +2 -0
  38. data/doc/examples/markdown_to_pdf/chapter1.md +11 -0
  39. data/doc/examples/markdown_to_pdf/chapter2.md +10 -0
  40. data/doc/examples/markdown_to_pdf/convert.sh +18 -0
  41. data/doc/examples/markdown_to_pdf/imfeldoublepica.ttf +0 -0
  42. data/doc/examples/markdown_to_pdf/manifest.yml +8 -0
  43. data/doc/examples/markdown_to_pdf/marcellus.ttf +0 -0
  44. data/doc/examples/markdown_to_pdf/stylesheet.css +22 -0
  45. data/docverter.gemspec +28 -0
  46. data/lib/docverter-server/app.rb +50 -0
  47. data/lib/docverter-server/conversion.rb +38 -0
  48. data/lib/docverter-server/conversion_types.rb +59 -0
  49. data/lib/docverter-server/jars/bcprov-ext-jdk15-1.43.jar +0 -0
  50. data/lib/docverter-server/jars/htmlcleaner-2.2.jar +0 -0
  51. data/lib/docverter-server/manifest.rb +107 -0
  52. data/lib/docverter-server/runner/base.rb +36 -0
  53. data/lib/docverter-server/runner/calibre.rb +10 -0
  54. data/lib/docverter-server/runner/pandoc.rb +18 -0
  55. data/lib/docverter-server/runner/pdf.rb +91 -0
  56. data/lib/docverter-server/version.rb +3 -0
  57. data/lib/docverter-server.rb +11 -0
  58. metadata +230 -0
@@ -0,0 +1,216 @@
1
+ /* This assumes geometric header shrinkage */
2
+ /* Also, it tries to make h2 be 1em */
3
+ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
4
+ /* Note kindle hates margin:0 ! (or margin-left or margin-top set) it inserts newlines galore */
5
+ /* margin: 0; */
6
+ margin-right: 0;
7
+ padding: 0;
8
+ border: 0;
9
+ font-size: 100%;
10
+ /* font: inherit; */
11
+ vertical-align: baseline; }
12
+
13
+ table {
14
+ border-collapse: collapse;
15
+ border-spacing: 0; }
16
+
17
+ /* end reset */
18
+ @page {
19
+ margin-top: 30px;
20
+ margin-bottom: 20px; }
21
+
22
+ div.cover {
23
+ text-align: center;
24
+ page-break-after: always;
25
+ padding: 0px;
26
+ margin: 0px; }
27
+ div.cover img {
28
+ height: 100%;
29
+ max-width: 100%;
30
+ padding: 0px;
31
+ margin: 0px; }
32
+
33
+ .cover-img {
34
+ height: 100%;
35
+ max-width: 100%;
36
+ padding: 0px;
37
+ margin: 0px; }
38
+
39
+ h1, h2, h3, h4, h5, h6 {
40
+ hyphens: none !important;
41
+ -moz-hyphens: none !important;
42
+ -webkit-hyphens: none !important;
43
+ adobe-hyphenate: none !important;
44
+ page-break-after: avoid;
45
+ page-break-inside: avoid;
46
+ text-indent: 0px;
47
+ text-align: left; }
48
+
49
+ h1 {
50
+ font-size: 1.6em;
51
+ margin-bottom: 3.2em; }
52
+
53
+ h2 {
54
+ font-size: 1em;
55
+ margin-top: 0.5em;
56
+ margin-bottom: 0.5em; }
57
+
58
+ h3 {
59
+ font-size: 0.625em; }
60
+
61
+ h4 {
62
+ font-size: 0.391em; }
63
+
64
+ h5 {
65
+ font-size: 0.244em; }
66
+
67
+ h6 {
68
+ font-size: 0.153em; }
69
+
70
+ /* Do not indent first paragraph. Mobi will need class='first-para' */
71
+ h1 + p, h2 + p, h3 + p, h4 + p, h5 + p, h6 + p {
72
+ text-indent: 0; }
73
+
74
+ p {
75
+ hyphenate-after: 3;
76
+ hyphenate-before: 3;
77
+ hyphenate-lines: 2;
78
+ -webkit-hyphenate-after: 3;
79
+ -webkit-hyphenate-before: 3;
80
+ -webkit-hyphenate-lines: 2;
81
+ line-height: 1.25em;
82
+ margin: 0;
83
+ orphans: 2;
84
+ text-align: justify;
85
+ text-indent: 1em;
86
+ widows: 2; }
87
+ p.first-para {
88
+ text-indent: 0; }
89
+
90
+ .drop {
91
+ overflow: hidden;
92
+ line-height: 89%;
93
+ height: 0.8em;
94
+ font-size: 281%;
95
+ margin-right: 0.075em;
96
+ float: left; }
97
+
98
+ /* lists */
99
+ ul, ol, dl {
100
+ margin: 1em 0 1em 0; }
101
+
102
+ li {
103
+ line-height: 1.25em;
104
+ orphans: 2;
105
+ widows: 2;
106
+ text-align: justify;
107
+ text-indent: 0;
108
+ margin: 0; }
109
+
110
+ /* code for me */
111
+ pre {
112
+ margin-left: 0;
113
+ /* margin-top: 1em; */
114
+ margin-bottom: 1em;
115
+ /* mobi fun */
116
+ font-size: 0.6em;
117
+ font-family: "Courier New", Courier, monospace;
118
+ white-space: pre-wrap;
119
+ display: block; }
120
+
121
+ div.div-literal-block-admonition {
122
+ margin-left: 1em; }
123
+ div.note, div.tip {
124
+ margin: 1em 0 1em 0 !important;
125
+ padding: 0 !important;
126
+ /* kindle is finnicky with borders, bottoms dissappear, width is ignored */
127
+ border-top: 0px solid #178e7d;
128
+ border-bottom: 0px dashed #178e7d;
129
+ page-break-inside: avoid; }
130
+
131
+ /* sidebar */
132
+ p.note-title, .admonition-title {
133
+ margin-top: 0;
134
+ /*mobi doesn't like div margins */
135
+ font-variant: small-caps;
136
+ font-size: 1em;
137
+ text-align: center;
138
+ font-weight: bold;
139
+ font-style: normal;
140
+ -webkit-hyphens: none;
141
+ -moz-hyphens: none;
142
+ hyphens: none;
143
+ /* margin:0 1em 0 1em; */ }
144
+
145
+ div.note p, .note-p {
146
+ text-indent: 1em;
147
+ margin-left: 0;
148
+ margin-right: 0;
149
+ font-style: italic; }
150
+
151
+ /* Since Kindle doesn't like multiple classes have to have combinations */
152
+ .note-p-first {
153
+ text-indent: 0;
154
+ margin-left: 0;
155
+ margin-right: 0; }
156
+
157
+ /* Tables */
158
+ table {
159
+ width: 100%;
160
+ page-break-inside: avoid;
161
+ border: 1px; }
162
+
163
+ td {
164
+ border-bottom: 1px solid black;
165
+ font-size: small;
166
+ hyphens: none;
167
+ -moz-hyphens: none;
168
+ -webkit-hyphens: none;
169
+ padding: 5px !important;
170
+ page-break-inside: avoid;
171
+ text-align: left;
172
+ text-indent: 0;
173
+ vertical-align: top; }
174
+
175
+ sup {
176
+ vertical-align: super;
177
+ font-size: 0.5em; }
178
+
179
+ sub {
180
+ vertical-align: sub;
181
+ font-size: 0.5em; }
182
+
183
+ .footnote {
184
+ font-size: 0.8em; }
185
+
186
+ .footnote-link {
187
+ font-size: 0.8em;
188
+ vertical-align: super; }
189
+
190
+ /* Samples */
191
+ .center {
192
+ text-align: center; }
193
+
194
+ .right {
195
+ text-align: right; }
196
+
197
+ .left {
198
+ text-align: left; }
199
+
200
+ .f-right {
201
+ float: right; }
202
+
203
+ .f-left {
204
+ float: left; }
205
+
206
+ .box-example {
207
+ background-color: green;
208
+ margin: 2em;
209
+ padding: 1em;
210
+ border: 2px dashed red; }
211
+
212
+ .padding-only {
213
+ padding: 1em; }
214
+
215
+ .margin-only {
216
+ margin: 2em; }
@@ -0,0 +1,2 @@
1
+ % Example Book Title
2
+ % John Q. Author
@@ -0,0 +1,11 @@
1
+ # Chapter 1
2
+
3
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed rutrum, nisl in malesuada mattis, lorem ante rutrum turpis, vitae aliquam enim nunc ut leo. Vivamus ultrices, turpis sit amet vehicula rhoncus, nulla risus placerat libero, molestie vehicula purus dolor at erat. Etiam ac ligula nec justo ornare interdum at in magna. Curabitur tempus nibh in erat lacinia sit amet facilisis elit mollis. Quisque elementum accumsan convallis. Maecenas ut orci ac odio suscipit convallis at ac erat. Sed at suscipit orci. Aliquam sed interdum felis. Sed pretium facilisis aliquet. In tellus dolor, scelerisque a aliquam non, faucibus et metus. Cras pellentesque aliquam metus rutrum convallis. Vestibulum et leo vitae purus accumsan varius. Donec quis luctus enim. Mauris lacus odio, molestie vitae suscipit at, porta sed turpis. Sed eros dui, aliquet at varius vel, ultricies ac augue.
4
+
5
+ Nullam quis justo ac dui fringilla feugiat sed gravida nisl. Nam id egestas nisl. Pellentesque sodales nibh et eros pretium id posuere lectus pellentesque. Nulla pretium interdum porta. Nulla a quam eget risus pulvinar porta sit amet vitae leo. Aliquam scelerisque ornare libero tristique varius. Suspendisse consectetur vulputate ipsum a eleifend. Nullam a neque felis, consectetur vehicula ante. Etiam varius libero in sem tristique et sagittis quam gravida. Aenean eleifend, elit a porttitor dignissim, dui elit rutrum quam, in malesuada massa felis vitae sapien. Nulla convallis viverra mauris a condimentum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
6
+
7
+ Etiam sed laoreet eros. Duis porttitor magna dolor, sed vehicula odio. Pellentesque turpis purus, pulvinar commodo luctus non, congue at turpis. Etiam vehicula elit pulvinar sem vehicula vel varius ipsum hendrerit. Donec odio dolor, ornare non volutpat id, bibendum sed odio. Aliquam ante risus, tristique vitae condimentum quis, posuere viverra diam. Etiam interdum accumsan tellus, ut mollis lectus fringilla consectetur. Nullam porttitor, dui quis consequat suscipit, metus tellus consequat ante, a sodales lorem nisl eu odio. Nullam elementum consequat lacinia. Nunc in nulla purus. Duis ut lacus quis justo tempor iaculis. Nunc egestas urna eu lacus euismod eu tempor eros venenatis. Curabitur imperdiet dui vel turpis pretium eleifend. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi facilisis dignissim turpis et rutrum.
8
+
9
+ Suspendisse posuere congue ante vitae tristique. Sed pretium interdum nulla, sed rutrum justo porta nec. Donec justo dolor, faucibus quis interdum vel, imperdiet varius est. Vivamus ullamcorper, mi ut aliquet fermentum, turpis leo blandit dolor, sed venenatis dui libero vehicula enim. Fusce malesuada vehicula velit, in ullamcorper mauris fermentum mattis. Duis vitae libero mauris, et consequat arcu. Ut a orci sem. Donec dui neque, placerat nec pharetra sit amet, aliquam nec erat. Curabitur rhoncus, sapien ac molestie bibendum, nunc purus cursus risus, tincidunt semper erat justo vel lectus. Fusce vulputate tempor malesuada. Maecenas in porta eros.
10
+
11
+ Mauris eget lectus neque. Nullam elementum felis nec turpis gravida sed varius ipsum lobortis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque aliquam luctus nisi, eget sollicitudin neque malesuada vel. Aliquam quis nulla nulla. Quisque tincidunt, augue et vulputate mollis, libero nibh imperdiet erat, et interdum erat orci sit amet libero. Nullam convallis gravida commodo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
@@ -0,0 +1,10 @@
1
+ # Chapter 2
2
+
3
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ut suscipit nibh. Nunc in libero arcu. Integer eu libero quam, nec pretium odio. Sed egestas arcu eget erat convallis vitae pellentesque mauris aliquam. Pellentesque posuere, elit a ullamcorper tincidunt, dolor enim posuere urna, non consequat ligula erat placerat augue. Vivamus eu nisl libero. Nullam consectetur pellentesque turpis, sed lobortis magna cursus facilisis. Aliquam fermentum auctor varius. Ut ligula velit, luctus sed venenatis at, accumsan a dui. Donec sodales mi eget massa molestie at cursus velit interdum.
4
+
5
+ Sed risus orci, suscipit pellentesque feugiat nec, pulvinar eget tellus. Cras vitae condimentum nulla. Proin in enim sem. Aliquam erat volutpat. Cras consequat malesuada semper. Nulla ornare nisl quis dolor faucibus ut iaculis justo elementum. Suspendisse interdum ultricies velit, id convallis lorem lacinia quis. Suspendisse potenti. Sed elit nulla, feugiat vel dapibus non, laoreet at justo. Vestibulum eget dui odio, ut venenatis sapien. Donec ac purus quam, id vestibulum diam. Proin augue turpis, laoreet eu placerat at, fringilla in orci. Etiam diam magna, luctus a pellentesque eu, convallis id enim. Pellentesque hendrerit urna eu urna laoreet vel iaculis arcu vestibulum.
6
+
7
+ Suspendisse placerat rutrum blandit. Proin a lacus lectus. Aenean vestibulum mi sit amet metus porttitor sed ullamcorper orci adipiscing. Proin ipsum ligula, rhoncus a aliquet nec, rutrum vel tortor. Phasellus fermentum lacinia nunc, sed porta orci aliquet eu. In sapien velit, lacinia eget vulputate ut, egestas et est. Vestibulum odio eros, blandit ac accumsan at, fringilla eu velit. Sed dignissim sodales condimentum. Vivamus at massa metus. Duis imperdiet ultrices quam sed pretium.
8
+
9
+ Fusce ut tortor eget lectus rutrum mattis quis in leo. Duis id risus nec justo hendrerit consequat quis quis sem. Aenean sollicitudin bibendum elit vel sollicitudin. Cras ipsum odio, bibendum porttitor luctus a, vulputate et dolor. Donec faucibus urna mattis tortor convallis vel ornare magna vestibulum. Sed pellentesque elit vitae ipsum dapibus in hendrerit mi lobortis. Suspendisse nec sapien vitae tellus sollicitudin vulputate. Sed lacinia, arcu et accumsan placerat, leo justo ultricies diam, vel semper dolor nulla ut nisi. Sed volutpat ultrices erat, a consequat est consectetur sed. Donec pharetra odio sit amet ligula lacinia in consequat neque pellentesque. Pellentesque tortor diam, tempor sit amet semper in, semper a libero. Integer sodales, nisl non varius porttitor, arcu magna congue velit, sed ultricies elit quam non eros.
10
+
@@ -0,0 +1,18 @@
1
+ #!/bin/bash
2
+
3
+ if [[ "$DOCVERTER_API_URL" == "" ]]; then
4
+ export DOCVERTER_API_URL=http://localhost:9595/convert
5
+ fi
6
+
7
+ curl --form from=markdown \
8
+ --form to=pdf \
9
+ --form test_mode=true \
10
+ --form input_files[]=@chapter1.md \
11
+ --form input_files[]=@chapter2.md \
12
+ --form other_files[]=@stylesheet.css \
13
+ --form other_files[]=@imfeldoublepica.ttf \
14
+ --form other_files[]=@marcellus.ttf \
15
+ --form css=stylesheet.css \
16
+ $DOCVERTER_API_URL > markdown_to_pdf.pdf
17
+
18
+ echo markdown_to_pdf.pdf
@@ -0,0 +1,8 @@
1
+ ---
2
+ from: markdown
3
+ to: pdf
4
+ test_mode: true
5
+ input_files:
6
+ - chapter1.md
7
+ - chapter2.md
8
+ css: stylesheet.css
@@ -0,0 +1,22 @@
1
+ @font-face {
2
+ font-family: 'Marcellus';
3
+ font-style: normal;
4
+ font-weight: 400;
5
+ src: url('marcellus.ttf');
6
+ -fs-pdf-font-embed: embed;
7
+ -fs-pdf-font-encoding: Identity-H;
8
+ }
9
+ @font-face {
10
+ font-family: 'IM FELL Double Pica';
11
+ font-style: normal;
12
+ font-weight: 400;
13
+ src: url('imfeldoublepica.ttf');
14
+ -fs-pdf-font-embed: embed;
15
+ -fs-pdf-font-encoding: Identity-H;
16
+ }
17
+ h1,h2 {
18
+ font-family: 'Marcellus';
19
+ }
20
+ p {
21
+ font-family: 'IM FELL Double Pica';
22
+ }
data/docverter.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'docverter-server/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "docverter-server"
7
+ gem.version = DocverterServer::VERSION
8
+ gem.authors = ["Pete Keen"]
9
+ gem.email = ["pete@docverter.com"]
10
+ gem.description = %{Document conversion service with a REST API}
11
+ gem.summary = %{Document conversion service with a REST API}
12
+ gem.homepage = 'http://www.docverter.com'
13
+ gem.platform = "java"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency('mime-types')
21
+ gem.add_dependency('sinatra')
22
+ gem.add_dependency('rake')
23
+ gem.add_dependency('mizuno')
24
+ gem.add_dependency('flying_saucer')
25
+
26
+ gem.add_development_dependency("mocha")
27
+ gem.add_development_dependency("shoulda")
28
+ end
@@ -0,0 +1,50 @@
1
+ require 'sinatra/base'
2
+ require 'fileutils'
3
+
4
+ class DocverterServer::App < Sinatra::Base
5
+ post '/convert' do
6
+
7
+ dir = Dir.mktmpdir
8
+
9
+ Dir.chdir(dir) do
10
+ manifest = DocverterServer::Manifest.new
11
+
12
+ input_files = params.delete('input_files') || []
13
+ other_files = params.delete('other_files') || []
14
+
15
+ input_files.each do |upload|
16
+ FileUtils.cp upload[:tempfile].path, upload[:filename]
17
+ manifest['input_files'] << upload[:filename]
18
+ end
19
+
20
+ other_files.each do |upload|
21
+ FileUtils.cp upload[:tempfile].path, upload[:filename]
22
+ end
23
+
24
+ params.each do |key,val|
25
+ next if key == 'controller' || key == 'action'
26
+ key = key.gsub("'", '') if key.is_a?(String)
27
+ val = val.gsub("'", '') if val.is_a?(String)
28
+
29
+ manifest[key] = val
30
+ end
31
+
32
+ manifest.write('manifest.yml')
33
+
34
+ output_file = DocverterServer::Conversion.new(dir).run
35
+
36
+ content_type(DocverterServer::ConversionTypes.mime_type(manifest['to']))
37
+
38
+ @output = nil
39
+ File.open(output_file) do |f|
40
+ @output = f.read
41
+ end
42
+ @output
43
+ end
44
+
45
+ end
46
+
47
+ get '/' do
48
+ 'hi'
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ class DocverterServer::Conversion < DocverterServer::Runner::Base
2
+
3
+ def run
4
+ with_manifest do |manifest|
5
+ manifest.validate!(directory)
6
+
7
+ if manifest['to'] == 'pdf'
8
+ manifest['to'] = 'html'
9
+
10
+ manifest.write('manifest.yml')
11
+
12
+ if manifest['from'] != 'html'
13
+ @html_filename = DocverterServer::Runner::Pandoc.new(directory).run
14
+ else
15
+ @html_filename = manifest['input_files'][0]
16
+ end
17
+ @output_filename = DocverterServer::Runner::PDF.new(directory, @html_filename).run
18
+ elsif manifest['to'] == 'mobi'
19
+ manifest['to'] = 'epub'
20
+ manifest.write('manifest.yml')
21
+ epub = DocverterServer::Runner::Pandoc.new('.').run
22
+ @output_filename = DocverterServer::Runner::Calibre.new(directory, epub).run
23
+ else
24
+ @output_filename = DocverterServer::Runner::Pandoc.new(directory).run
25
+ end
26
+ @output_filename
27
+ end
28
+ end
29
+
30
+ def output_mime_type
31
+ DocverterServer::ConversionTypes.mime_type(@manifest.pdf ? 'pdf' : @manifest['to'])
32
+ end
33
+
34
+ def output_extension
35
+ DocverterServer::ConversionTypes.extension(@manifest.pdf ? 'pdf' : @manifest['to'])
36
+ end
37
+
38
+ end
@@ -0,0 +1,59 @@
1
+ class DocverterServer::ConversionTypes
2
+
3
+ TYPES = [
4
+ # file extension, pandoc name, visible name, mime type, input, output
5
+ ['asciidoc', 'asciidoc', 'AsciiDoc', 'application/octet-stream', false, true],
6
+ ['docx', 'docx', 'Docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', false, true],
7
+ ['doc', 'doc', 'Doc', 'application/msword', true, false],
8
+ ['epub', 'epub', 'ePub', 'application/epub+zip', false, true],
9
+ ['groff', 'groff', 'Groff', 'application/x-troff', false, true],
10
+ ['html', 'html', 'HTML', 'text/html', true, true],
11
+ ['md', 'markdown', 'Markdown', 'application/octet-stream', true, true],
12
+ ['org', 'orgmode', 'Emacs Org-Mode', 'application/octet-stream', false, true],
13
+ ['mobi', 'mobi', 'Mobi', 'application/octet-stream', false, true],
14
+ ['pdf', 'pdf', 'PDF', 'application/pdf', false, true],
15
+ ['rtf', 'rtf', 'RTF', 'text/rtf', false, true],
16
+ ['rst', 'rst', 'reStructured Text', 'application/octet-stream', true, true],
17
+ ['tex', 'context', 'ConTeXt', 'application/octet-stream', false, true],
18
+ ['tex', 'latex', 'LaTeX', 'application/octet-stream', true, true],
19
+ ['texi', 'texinfo', 'TexInfo', 'application/octet-stream', false, true],
20
+ ['textile', 'textile', 'Textile', 'application/octet-stream', true, true],
21
+ ['wiki', 'mediawiki', 'MediaWiki', 'application/octet-stream', false, true],
22
+ ['xml', 'docbook', 'DocBook', 'application/docbook+xml', false, true],
23
+ ]
24
+
25
+ def self.extension(pandoc_name)
26
+ for_pandoc(pandoc_name)[0]
27
+ end
28
+
29
+ def self.mime_type(pandoc_name)
30
+ for_pandoc(pandoc_name)[3]
31
+ end
32
+
33
+ def self.for_pandoc(pandoc_name)
34
+ TYPES.find { |t| t[1].downcase == pandoc_name.downcase }
35
+ end
36
+
37
+ def self.for_extension(extension)
38
+ TYPES.find { |t| t[0].downcase == extension.downcase }
39
+ end
40
+
41
+ def self.inputs
42
+ TYPES.find_all { |t| t[4] == true }
43
+ end
44
+
45
+ def self.outputs
46
+ TYPES.find_all { |t| t[5] == true }
47
+ end
48
+
49
+ def self.valid_input?(input)
50
+ type = for_pandoc(input)
51
+ return type && type[4]
52
+ end
53
+
54
+ def self.valid_output?(output)
55
+ type = for_pandoc(output)
56
+ return type && type[5]
57
+ end
58
+
59
+ end
@@ -0,0 +1,107 @@
1
+ require 'yaml'
2
+
3
+ class DocverterServer::Manifest
4
+
5
+ class InvalidManifestError < RuntimeError; end
6
+
7
+ def pdf
8
+ @pdf
9
+ end
10
+
11
+ def pdf_page_size
12
+ @pdf_page_size
13
+ end
14
+
15
+ def self.load_file(filename)
16
+ File.open(filename) do |f|
17
+ self.load_stream(f)
18
+ end
19
+ end
20
+
21
+ def self.load_stream(stream)
22
+ self.new(YAML.load(stream))
23
+ end
24
+
25
+ def initialize(options={})
26
+ @options = options
27
+ @options['input_files'] ||= []
28
+ end
29
+
30
+ def [](key)
31
+ @options[key]
32
+ end
33
+
34
+ def []=(key, val)
35
+ @options[key] = val
36
+ end
37
+
38
+ def write(filename)
39
+ File.open(filename, "w+") do |f|
40
+ write_to_stream(f)
41
+ end
42
+ end
43
+
44
+ def write_to_stream(stream)
45
+ stream.write YAML.dump(@options)
46
+ end
47
+
48
+ def test_mode?
49
+ @test_mode
50
+ end
51
+
52
+ def command_options
53
+ options = @options.dup
54
+ input_files = options.delete('input_files')
55
+ raise "No input files provided!" unless input_files.length > 0
56
+
57
+ @pdf_page_size = options.delete('pdf_page_size')
58
+
59
+ if options['to'] == 'pdf'
60
+ _ = options.delete 'to'
61
+ @pdf = true
62
+ end
63
+
64
+ command_options = []
65
+
66
+ @test_mode = options.delete('test_mode')
67
+
68
+ options.each do |k,v|
69
+
70
+ raise NameError.new("Invalid option: #{k}") unless k.match(/^[a-z0-9-]+/)
71
+
72
+ option_key = k.to_s.gsub('_', '-')
73
+ [v].flatten.each do |option_val|
74
+ raise RuntimeError.new("Invalid option value: #{option_val}") unless option_val.to_s.match(/^[a-zA-Z0-9._-]+/)
75
+ if option_val.is_a?(TrueClass)
76
+ command_options << "--#{option_key}"
77
+ else
78
+ command_options << "--#{option_key}=#{option_val}"
79
+ end
80
+ end
81
+ end
82
+
83
+ command_options += [input_files].flatten.compact
84
+
85
+ command_options
86
+ end
87
+
88
+ def validate!(dir)
89
+ raise InvalidManifestError.new("No input files found") unless @options['input_files'] && @options['input_files'].length > 0
90
+
91
+ Dir.chdir(dir) do
92
+ @options['input_files'].each do |filename|
93
+ raise InvalidManifestError.new("Invalid input file: #{filename} not found") unless File.exists?(filename)
94
+ raise InvalidManifestError.new("Invalid input file: #{filename} cannot start with /") if filename.strip[0] == '/'
95
+ end
96
+
97
+ raise InvalidManifestError.new("'from' key required") unless @options['from']
98
+ raise InvalidManifestError.new("'to' key required") unless @options['to']
99
+
100
+ raise InvalidManifestError.new("Not a valid 'from' type") unless
101
+ DocverterServer::ConversionTypes.valid_input?(@options['from'])
102
+
103
+ raise InvalidManifestError.new("Not a valid 'to' type") unless
104
+ DocverterServer::ConversionTypes.valid_output?(@options['to'])
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,36 @@
1
+ module DocverterServer
2
+ module Runner
3
+ class Base
4
+ attr_reader :directory, :input_filename, :options
5
+
6
+ def initialize(directory, input_filename=nil, options={})
7
+ @directory = directory
8
+ @input_filename = input_filename
9
+ @options = options
10
+ end
11
+
12
+ def run
13
+ raise "implement in subclass"
14
+ end
15
+
16
+ def generate_output_filename(extension)
17
+ "output.#{SecureRandom.hex(10)}.#{extension}"
18
+ end
19
+
20
+ def run_command(options)
21
+ output = IO.popen(options) do |f|
22
+ f.read
23
+ end
24
+ if $?.exitstatus != 0
25
+ raise output
26
+ end
27
+ end
28
+
29
+ def with_manifest
30
+ Dir.chdir(directory) do
31
+ yield DocverterServer::Manifest.load_file("manifest.yml")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ class DocverterServer::Runner::Calibre < DocverterServer::Runner::Base
2
+ def run
3
+ with_manifest do
4
+ output = generate_output_filename('mobi')
5
+ options = ["ebook-convert", input_filename, output]
6
+ run_command(options)
7
+ output
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ require 'securerandom'
2
+
3
+ class DocverterServer::Runner::Pandoc < DocverterServer::Runner::Base
4
+
5
+ def run
6
+ with_manifest do |manifest|
7
+ options = manifest.command_options
8
+
9
+ extension = DocverterServer::ConversionTypes.extension(manifest['to'])
10
+ output = generate_output_filename(extension)
11
+
12
+ options = ['pandoc', '--standalone', "--output=#{output}"] + options
13
+ run_command(options)
14
+ output
15
+ end
16
+ end
17
+
18
+ end