propaganda 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jeff Rafter
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,78 @@
1
+ = propaganda
2
+
3
+ Generate PDFs from HTML. Generate them from Markdown and Textile. Take over
4
+ the world.
5
+
6
+ == Installation
7
+
8
+ Propaganda is available on Rubygems.org.
9
+
10
+ gem install propaganda
11
+
12
+ This will install the library as well as the propoganda binary.
13
+
14
+ == Usage
15
+
16
+ Once installed you can run
17
+
18
+ propaganda --help
19
+
20
+ Some examples:
21
+
22
+ propaganda sample.html sample.pdf
23
+
24
+ Will convert sample.html to sample.pdf. You can also submit textile and
25
+ markdown documents and these will be automagically converted.
26
+
27
+ propaganda sample.textile sample.pdf
28
+
29
+ The document will be formatted into a basic layout.
30
+
31
+ == Apache and the Formatting Objects processor
32
+
33
+ Propaganda uses the Apache FOP library (http://xmlgraphics.apache.org/fop/). I
34
+ have included those binaries in the gem so that the versions are set. The
35
+ licenses for items in the jar folder are separate from propaganda.
36
+
37
+ == Doing it with style
38
+
39
+ The stylesheets I am using are based entirely on the stylesheets from
40
+ Antenna House, Inc. (http://www.antennahouse.com) that were made for their
41
+ formatting engine. It was widely known that these were incompatible with
42
+ Apache FOP for various reasons. Well, I fixed those, mostly and then tweaked
43
+ things to do what I want a bit more.
44
+
45
+ I also reworked the way keep-with-next and friends works. Basically there
46
+ wasn't a good way to implement this directly, so I extended support for blocks
47
+ with css classes. Breaking logic (for table cells and block level objects)
48
+ 'break-before' and 'break-after' converted to break-before="<context>" or
49
+ break-after="<context>" where <context> is column if it is within a table
50
+ otherwise page. Keeping logic (for table cells and block level objects)
51
+ 'keep-together' and 'keep-with-next' and 'keep-with-previous' and converted to
52
+ keep-together.within-<context>="always" etc. where <context> is line if the
53
+ declaration is found on an inline level element, column if within a table cell
54
+ otherwise page.
55
+
56
+ == Note on Ruby Java Bridge
57
+
58
+ For non-jruby usage this gem relies on the Ruby Java Bridge (rjb) which is
59
+ available on rubygems (http://rubygems.org/gems/rjb). Source is available
60
+ on Github (http://github.com/arton/rjb). When installing the rjb gem you
61
+ must have a JAVA_HOME environment variable set. If you install your gems
62
+ using sudo this can be flummoxing. Just use:
63
+
64
+ sudo env JAVA_HOME=/Library/Java/Home gem install rjb
65
+
66
+ == Note on Patches/Pull Requests
67
+
68
+ * Fork the project.
69
+ * Make your feature addition or bug fix.
70
+ * Add tests for it. This is important so I don't break it in a
71
+ future version unintentionally.
72
+ * Commit, do not mess with rakefile, version, or history.
73
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
74
+ * Send me a pull request. Bonus points for topic branches.
75
+
76
+ == Copyright
77
+
78
+ Copyright (c) 2010 Jeff Rafter. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "propaganda"
8
+ gem.summary = %Q{Generate PDFs from HTML. Generate them from Markdown and Textile. Take over the world.}
9
+ gem.description = %Q{Propaganda uses Apache FOP to convert html to PDF using a series of stylesheets. Propaganda can also format textile and markdown documents. }
10
+ gem.email = "jeff@socialrange.org"
11
+ gem.homepage = "http://github.com/jeffrafter/propaganda"
12
+ gem.authors = ["Jeff Rafter"]
13
+ gem.files = FileList["[A-Z]*", "{bin,java,lib,templates,test}/**/*"]
14
+ gem.add_dependency "BlueCloth", ">= 1.0.0"
15
+ gem.add_dependency "RedCloth", ">= 4.1.1"
16
+ gem.add_dependency "rjb", ">= 1.2.0"
17
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/*_test.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |test|
35
+ test.libs << 'test'
36
+ test.pattern = 'test/**/*_test.rb'
37
+ test.verbose = true
38
+ end
39
+ rescue LoadError
40
+ task :rcov do
41
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
42
+ end
43
+ end
44
+
45
+ task :test => :check_dependencies
46
+
47
+ task :default => :test
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "propaganda #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/propaganda ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ def usage
4
+ puts "Usage: propaganda [options] input output"
5
+ puts ""
6
+ puts " --title=='A Friendly Title' (will detect from filename if omitted)"
7
+ puts " --template=={default|clean} (will use default if omitted)"
8
+ puts " --engine=={markdown|textile|none} (will detect from filename if omitted)"
9
+ puts " --verbose Show errors and warnings"
10
+ puts " --help"
11
+ exit
12
+ end
13
+
14
+ usage if ARGV.size == 0 || ARGV.include?('--help')
15
+
16
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) if ENV['PROPAGANDA_ENV'] == 'test'
17
+
18
+ require 'rubygems'
19
+ require 'propaganda'
20
+
21
+ def opt(name)
22
+ p = ARGV.select{|arg| arg =~ /^--#{name}/}.first
23
+ p.gsub(/^--#{name}=/, '') if p
24
+ end
25
+
26
+ template = opt "template"
27
+ title = opt "title"
28
+ engine = opt "engine"
29
+ verbose = !opt("verbose").nil?
30
+
31
+ # Grab the params
32
+ params = ARGV.reject{|arg| arg =~ /^--/}
33
+ input = params[0] rescue nil
34
+ output = params[1] rescue nil
35
+ usage if input.nil? || output.nil?
36
+
37
+ # Run it
38
+ Propaganda.convert(input, output, title, template, engine, verbose)
Binary file
@@ -0,0 +1,19 @@
1
+ // Hack to disable calls to System.exit in your application. Based on code from
2
+ // http://sprauer.wordpress.com/2009/03/18/disable-java-systemexit/ which is
3
+ // in turn based on http://www.jroller.com/ethdsy/entry/disabling_system_exit
4
+ public class SystemExitManager extends SecurityManager {
5
+ public void checkPermission(java.security.Permission permission) {
6
+ if ("exitVM".equals(permission.getName())) {
7
+ throw new SecurityException("System exit disabled");
8
+ }
9
+ }
10
+
11
+ public static void disableSystemExitCall() {
12
+ SystemExitManager securityManager = new SystemExitManager();
13
+ System.setSecurityManager(securityManager);
14
+ }
15
+
16
+ public static void enableSystemExitCall() {
17
+ System.setSecurityManager(null);
18
+ }
19
+ }
Binary file
Binary file
Binary file
Binary file
data/java/fop.jar ADDED
Binary file
@@ -0,0 +1,207 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one or more
3
+ * contributor license agreements. See the NOTICE file distributed with
4
+ * this work for additional information regarding copyright ownership.
5
+ * The ASF licenses this file to You under the Apache License, Version 2.0
6
+ * (the "License"); you may not use this file except in compliance with
7
+ * the License. You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /* $Id$ */
19
+
20
+ package org.apache.fop.cli;
21
+
22
+ import java.io.File;
23
+ import java.io.FileFilter;
24
+ import java.io.OutputStream;
25
+ import java.lang.reflect.Method;
26
+ import java.net.MalformedURLException;
27
+ import java.net.URL;
28
+ import java.util.List;
29
+
30
+ import org.apache.commons.io.IOUtils;
31
+
32
+ import org.apache.fop.apps.FOUserAgent;
33
+ import org.apache.fop.apps.MimeConstants;
34
+
35
+ /**
36
+ * Main command-line class for Apache FOP.
37
+ */
38
+ public class Manager {
39
+
40
+ /**
41
+ * @return the list of URLs to all libraries.
42
+ * @throws MalformedURLException In case there is a problem converting java.io.File
43
+ * instances to URLs.
44
+ */
45
+ public static URL[] getJARList() throws MalformedURLException {
46
+ String fopHome = System.getProperty("fop.home");
47
+ File baseDir;
48
+ if (fopHome != null) {
49
+ baseDir = new File(fopHome).getAbsoluteFile();
50
+ } else {
51
+ baseDir = new File(".").getAbsoluteFile().getParentFile();
52
+ }
53
+ File buildDir;
54
+ if ("build".equals(baseDir.getName())) {
55
+ buildDir = baseDir;
56
+ baseDir = baseDir.getParentFile();
57
+ } else {
58
+ buildDir = new File(baseDir, "build");
59
+ }
60
+ File fopJar = new File(buildDir, "fop.jar");
61
+ if (!fopJar.exists()) {
62
+ fopJar = new File(baseDir, "fop.jar");
63
+ }
64
+ if (!fopJar.exists()) {
65
+ throw new RuntimeException("fop.jar not found in directory: "
66
+ + baseDir.getAbsolutePath() + " (or below)");
67
+ }
68
+ List jars = new java.util.ArrayList();
69
+ jars.add(fopJar.toURI().toURL());
70
+ File[] files;
71
+ FileFilter filter = new FileFilter() {
72
+ public boolean accept(File pathname) {
73
+ return pathname.getName().endsWith(".jar");
74
+ }
75
+ };
76
+ File libDir = new File(baseDir, "lib");
77
+ if (!libDir.exists()) {
78
+ libDir = baseDir;
79
+ }
80
+ files = libDir.listFiles(filter);
81
+ if (files != null) {
82
+ for (int i = 0, size = files.length; i < size; i++) {
83
+ jars.add(files[i].toURI().toURL());
84
+ }
85
+ }
86
+ String optionalLib = System.getProperty("fop.optional.lib");
87
+ if (optionalLib != null) {
88
+ files = new File(optionalLib).listFiles(filter);
89
+ if (files != null) {
90
+ for (int i = 0, size = files.length; i < size; i++) {
91
+ jars.add(files[i].toURI().toURL());
92
+ }
93
+ }
94
+ }
95
+ URL[] urls = (URL[])jars.toArray(new URL[jars.size()]);
96
+ /*
97
+ for (int i = 0, c = urls.length; i < c; i++) {
98
+ System.out.println(urls[i]);
99
+ }*/
100
+ return urls;
101
+ }
102
+
103
+ /**
104
+ * @return true if FOP's dependecies are available in the current ClassLoader setup.
105
+ */
106
+ public static boolean checkDependencies() {
107
+ try {
108
+ //System.out.println(Thread.currentThread().getContextClassLoader());
109
+ Class clazz = Class.forName("org.apache.commons.io.IOUtils");
110
+ if (clazz != null) {
111
+ clazz = Class.forName("org.apache.avalon.framework.configuration.Configuration");
112
+ }
113
+ return (clazz != null);
114
+ } catch (Exception e) {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Dynamically builds a ClassLoader and executes FOP.
121
+ * @param args command-line arguments
122
+ */
123
+ public static void startFOPWithDynamicClasspath(String[] args) {
124
+ try {
125
+ URL[] urls = getJARList();
126
+ //System.out.println("CCL: "
127
+ // + Thread.currentThread().getContextClassLoader().toString());
128
+ ClassLoader loader = new java.net.URLClassLoader(urls, null);
129
+ Thread.currentThread().setContextClassLoader(loader);
130
+ Class clazz = Class.forName("org.apache.fop.cli.Main", true, loader);
131
+ //System.out.println("CL: " + clazz.getClassLoader().toString());
132
+ Method mainMethod = clazz.getMethod("startFOP", new Class[] {String[].class});
133
+ mainMethod.invoke(null, new Object[] {args});
134
+ } catch (Exception e) {
135
+ System.err.println("Unable to start FOP:");
136
+ e.printStackTrace();
137
+ System.exit(-1);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Executes FOP with the given arguments. If no argument is provided, returns its
143
+ * version number as well as a short usage statement; if '-v' is provided, returns its
144
+ * version number alone; if '-h' is provided, returns its short help message.
145
+ *
146
+ * @param args command-line arguments
147
+ */
148
+ public static void startFOP(String[] args) {
149
+ //System.out.println("static CCL: "
150
+ // + Thread.currentThread().getContextClassLoader().toString());
151
+ //System.out.println("static CL: " + Fop.class.getClassLoader().toString());
152
+ CommandLineOptions options = null;
153
+ FOUserAgent foUserAgent = null;
154
+ OutputStream out = null;
155
+
156
+ try {
157
+ options = new CommandLineOptions();
158
+ options.parse(args);
159
+
160
+ foUserAgent = options.getFOUserAgent();
161
+ String outputFormat = options.getOutputFormat();
162
+
163
+ try {
164
+ if (options.getOutputFile() != null) {
165
+ out = new java.io.BufferedOutputStream(
166
+ new java.io.FileOutputStream(options.getOutputFile()));
167
+ foUserAgent.setOutputFile(options.getOutputFile());
168
+ }
169
+ if (!MimeConstants.MIME_XSL_FO.equals(outputFormat)) {
170
+ options.getInputHandler().renderTo(foUserAgent, outputFormat, out);
171
+ } else {
172
+ options.getInputHandler().transformTo(out);
173
+ }
174
+ } finally {
175
+ IOUtils.closeQuietly(out);
176
+ }
177
+
178
+ // System.exit(0) called to close AWT/SVG-created threads, if any.
179
+ // AWTRenderer closes with window shutdown, so exit() should not
180
+ // be called here
181
+ if (!MimeConstants.MIME_FOP_AWT_PREVIEW.equals(outputFormat)) {
182
+ return;
183
+ }
184
+ } catch (Exception e) {
185
+ if (options != null) {
186
+ options.getLogger().error("Exception", e);
187
+ if (options.getOutputFile() != null) {
188
+ options.getOutputFile().delete();
189
+ }
190
+ }
191
+ return;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * The main routine for the command line interface
197
+ * @param args the command line parameters
198
+ */
199
+ public static void main(String[] args) {
200
+ if (checkDependencies()) {
201
+ startFOP(args);
202
+ } else {
203
+ startFOPWithDynamicClasspath(args);
204
+ }
205
+ }
206
+
207
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,63 @@
1
+ require 'rjb'
2
+ require 'tempfile'
3
+
4
+ module Propaganda
5
+ class Fop
6
+ def initialize(verbose=false)
7
+ unless verbose
8
+ end
9
+ end
10
+
11
+ def version
12
+ invoke('-v')
13
+ Output.toString
14
+ end
15
+
16
+ def render(html, output, template=nil)
17
+ template ||= 'default'
18
+ stylesheet = File.join(File.dirname(__FILE__), '..', '..', 'templates', "#{template}.xsl")
19
+ stylesheet = File.expand_path(stylesheet)
20
+ tmp = Tempfile.new('fop')
21
+ tmp << html
22
+ tmp.flush
23
+ tmp.close
24
+ output = File.expand_path(output)
25
+ invoke('-xml', tmp.path, '-xsl', stylesheet, '-pdf', output)
26
+ ensure
27
+ tmp = nil
28
+ end
29
+
30
+ private
31
+
32
+ def invoke(*args)
33
+ # When invoking we need to use our own Manager class because the default
34
+ # cli Main class deletes the file on exit and always calls System.exit
35
+ # which closes our application. We avoid that and also setup additional
36
+ # protection against rogue System.exit calls in the library
37
+ SystemExitManager.disableSystemExitCall
38
+ Manager._invoke('main', '[Ljava.lang.String;', args)
39
+ rescue Exception => e
40
+ raise "Could not render document [#{e}] (" + Errors.toString + ")"
41
+ ensure
42
+ SystemExitManager.enableSystemExitCall
43
+ end
44
+
45
+ def self.classpath
46
+ path = File.join(File.dirname(__FILE__), '..', '..', 'java')
47
+ File.expand_path(path+':'+File.join(path, 'fop.jar'))
48
+ end
49
+
50
+ Rjb::load(Fop.classpath, ['-Djava.awt.headless=true'])
51
+ SystemExitManager = Rjb::import 'SystemExitManager'
52
+ Manager = Rjb::import 'org.apache.fop.cli.Manager'
53
+ ByteArray = Rjb::import 'java.io.ByteArrayOutputStream'
54
+ PrintStream = Rjb::import 'java.io.PrintStream'
55
+
56
+ # Internally fop is very noisy, we have to block all of that if we don't
57
+ # want to go crazy. To do that we overwrite the default streams
58
+ Errors = ByteArray.new
59
+ Rjb::import('java.lang.System').err = PrintStream.new(Errors)
60
+ Output = ByteArray.new
61
+ Rjb::import('java.lang.System').out = PrintStream.new(Output)
62
+ end
63
+ end
@@ -0,0 +1,35 @@
1
+ require 'redcloth'
2
+ require 'bluecloth'
3
+
4
+ module Propaganda
5
+ class Formatter
6
+ def format(text, title=nil, engine=nil)
7
+ case engine
8
+ when 'markdown'
9
+ text = BlueCloth.new(text).to_html
10
+ text = layout(text, title)
11
+ when 'textile'
12
+ r = RedCloth.new(text)
13
+ r.hard_breaks = false
14
+ text = r.to_html
15
+ text = layout(text, title)
16
+ else
17
+ text
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def layout(text, title=nil)
24
+ "<html xmlns='http://www.w3.org/1999/xhtml'>
25
+ <head>
26
+ <meta http-equiv='Content-type' content='text/html; charset=utf-8' />
27
+ <title>#{title}</title>
28
+ </head>
29
+ <body>
30
+ #{text}
31
+ </body>
32
+ </html>"
33
+ end
34
+ end
35
+ end
data/lib/propaganda.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'propaganda/fop'
2
+ require 'propaganda/formatter'
3
+
4
+ module Propaganda
5
+ def self.convert(input, output, title=nil, template=nil, engine=nil, verbose=false)
6
+ title ||= File.basename(input, File.extname(input))
7
+ engine ||= detect(input)
8
+ text = IO.read(input)
9
+ formatter = Formatter.new
10
+ text = formatter.format(text, title, engine)
11
+ fop = Fop.new(verbose)
12
+ fop.render(text, output, template)
13
+ end
14
+
15
+ def self.detect(input)
16
+ case File.extname(input)
17
+ when '.textile'
18
+ 'textile'
19
+ when '.markdown', '.md'
20
+ 'markdown'
21
+ when '.html', '.xhtml'
22
+ 'none'
23
+ else
24
+ raise "Unknown format for #{input}, use .html, .textile or .markdown extension"
25
+ end
26
+ end
27
+
28
+ end