wordsmith 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +24 -0
- data/README.md +69 -0
- data/Rakefile +9 -0
- data/bin/wordsmith +20 -0
- data/lib/subcommand.rb +274 -0
- data/lib/wordsmith.rb +45 -0
- data/lib/wordsmith/cli.rb +81 -0
- data/lib/wordsmith/generate.rb +132 -0
- data/lib/wordsmith/init.rb +27 -0
- data/lib/wordsmith/publish.rb +62 -0
- data/lib/wordsmith/version.rb +3 -0
- data/template/.gitignore +4 -0
- data/template/.wordsmith +7 -0
- data/template/README.md +3 -0
- data/template/assets/images/cover.jpg +0 -0
- data/template/assets/stylesheets/default.css +44 -0
- data/template/content/01_manifesto.md +12 -0
- data/template/content/02_read_read_read.md +10 -0
- data/template/content/03_pay_special_attention/01_intro.md +20 -0
- data/template/content/03_pay_special_attention/02_conclusion.md +14 -0
- data/template/layout/footer.html +0 -0
- data/template/layout/header.html +3 -0
- data/test/generate_test.rb +7 -0
- data/test/init_test.rb +34 -0
- data/test/publish_test.rb +7 -0
- data/test/test_helper.rb +42 -0
- data/wordsmith.gemspec +26 -0
- metadata +138 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
wordsmith (0.0.1)
|
5
|
+
kindlegen
|
6
|
+
nokogiri
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
kindlegen (2.3.1)
|
12
|
+
systemu
|
13
|
+
nokogiri (1.5.2)
|
14
|
+
rake (0.9.2.2)
|
15
|
+
systemu (2.5.0)
|
16
|
+
test-unit (2.4.8)
|
17
|
+
|
18
|
+
PLATFORMS
|
19
|
+
ruby
|
20
|
+
|
21
|
+
DEPENDENCIES
|
22
|
+
rake
|
23
|
+
test-unit
|
24
|
+
wordsmith!
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
## What is it about?
|
2
|
+
|
3
|
+
Wordsmith is a ruby Framework with a built in command-line toolset to
|
4
|
+
create, share, publish and collaborate on e-books, guides, manuals, etc.
|
5
|
+
|
6
|
+
Through the command line interface, you can create a project repository
|
7
|
+
with a standard directory structure that allows you and your co-authors
|
8
|
+
to easily manage your content.
|
9
|
+
|
10
|
+
The command line interface provides a set of commands to export
|
11
|
+
in the following formats:
|
12
|
+
|
13
|
+
* epub
|
14
|
+
* mobi
|
15
|
+
* pdf
|
16
|
+
* html
|
17
|
+
|
18
|
+
You can also publish your book to your github project page with a single command.
|
19
|
+
|
20
|
+
## Installing
|
21
|
+
|
22
|
+
First of all, you should have [Pandoc][pandoc] installed in your system.
|
23
|
+
|
24
|
+
[pandoc]: http://johnmacfarlane.net/pandoc/installing.html
|
25
|
+
|
26
|
+
Next, install the wordsmith ruby gem.
|
27
|
+
|
28
|
+
gem install wordsmith
|
29
|
+
|
30
|
+
Now you're good to go.
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
Wordsmith uses the following directory structure
|
35
|
+
|
36
|
+
book/
|
37
|
+
layout/
|
38
|
+
header.html
|
39
|
+
footer.html
|
40
|
+
assets/
|
41
|
+
images/
|
42
|
+
cover.jpg
|
43
|
+
stylesheets/
|
44
|
+
default.css
|
45
|
+
book.css
|
46
|
+
content/
|
47
|
+
01_chapter_one.md
|
48
|
+
02_chapter_two.md
|
49
|
+
03_chapter_three/
|
50
|
+
01_lorem.md
|
51
|
+
02_ipsum.md
|
52
|
+
final/
|
53
|
+
book.epub
|
54
|
+
book.mobi
|
55
|
+
book.pdf
|
56
|
+
book_html/
|
57
|
+
Makefile
|
58
|
+
|
59
|
+
The **layout** directory contains the header and footer for an online version of your book.
|
60
|
+
|
61
|
+
The **assets** directory contains images and stylesheets.
|
62
|
+
|
63
|
+
In the **content** directory you can:
|
64
|
+
|
65
|
+
1. write content as single files
|
66
|
+
* use directories to easily manage long chapters
|
67
|
+
|
68
|
+
**Please note** that the file names in the 'content' folder must be snake cased and begin with the number of
|
69
|
+
the chapter or section (e.g. 01_chapter_one.md).
|
data/Rakefile
ADDED
data/bin/wordsmith
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
begin
|
6
|
+
# resolve bin path, ignoring symlinks
|
7
|
+
require "pathname"
|
8
|
+
bin_file = Pathname.new(__FILE__).realpath
|
9
|
+
|
10
|
+
# add self to libpath
|
11
|
+
$:.unshift File.expand_path("../../lib", bin_file)
|
12
|
+
|
13
|
+
require 'wordsmith'
|
14
|
+
|
15
|
+
# start up the CLI
|
16
|
+
require 'wordsmith/cli'
|
17
|
+
Wordsmith.new.run
|
18
|
+
rescue Interrupt
|
19
|
+
puts("\n ! Command cancelled.")
|
20
|
+
end
|
data/lib/subcommand.rb
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
######################################
|
3
|
+
# A tiny wrapper over optparse that gives easy subcommand facility.
|
4
|
+
# It also neatly prints help for global and subcommands
|
5
|
+
# as well as summarizes subcommands in global help.
|
6
|
+
#
|
7
|
+
# Thanks to Robert Klemme for his idea on lazy loading the subcommand option parsers.
|
8
|
+
#
|
9
|
+
# @author Rahul Kumar, Jun 2010
|
10
|
+
# @date 2010-06-20 22:33
|
11
|
+
#
|
12
|
+
# @examples
|
13
|
+
# if a program has subcommands foo and baz
|
14
|
+
#
|
15
|
+
# ruby subcommand.rb help
|
16
|
+
# ruby subcommand.rb --help
|
17
|
+
# ruby subcommand.rb help foo
|
18
|
+
# ruby subcommand.rb foo --help
|
19
|
+
# ruby subcommand.rb baz --quiet "some text"
|
20
|
+
# ruby subcommand.rb --verbose foo --force file.zzz
|
21
|
+
#
|
22
|
+
# == STEPS
|
23
|
+
# 1. define global_options (optional)
|
24
|
+
#
|
25
|
+
# global_options do |opts|
|
26
|
+
# opts.banner = "Usage: #{$0} [options] [subcommand [options]]"
|
27
|
+
# opts.description = "Stupid program that does something"
|
28
|
+
# opts.separator ""
|
29
|
+
# opts.separator "Global options are:"
|
30
|
+
# opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
31
|
+
# options[:verbose] = v
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# 2. define commands using command().
|
36
|
+
# command :foo do |opts|
|
37
|
+
# opts.banner = "Usage: foo [options]"
|
38
|
+
# opts.description = "desc for foo"
|
39
|
+
# opts.on("-f", "--[no-]force", "force verbosely") do |v|
|
40
|
+
# options[:force] = v
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# 3. call opt_parse()
|
45
|
+
#
|
46
|
+
# 4. As before, handle ARGS and options hash.
|
47
|
+
#
|
48
|
+
# TODO: add aliases for commands
|
49
|
+
######################################
|
50
|
+
require 'optparse'
|
51
|
+
|
52
|
+
# Allow command to have a description to generate help
|
53
|
+
class OptionParser
|
54
|
+
attr_accessor :description
|
55
|
+
#attr_accessor :action
|
56
|
+
end
|
57
|
+
|
58
|
+
module Subcommands
|
59
|
+
##
|
60
|
+
# specify a single command and all its options
|
61
|
+
# If multiple names are given, they are treated as aliases.
|
62
|
+
# Do repeatedly for each command
|
63
|
+
# Yields the optionparser
|
64
|
+
def command *names
|
65
|
+
name = names.shift
|
66
|
+
@commands ||= {}
|
67
|
+
@aliases ||= {}
|
68
|
+
if names.length > 0
|
69
|
+
names.each do |n|
|
70
|
+
#puts "aliases #{n} => #{name} "
|
71
|
+
@aliases[n.to_s] = name.to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# Thanks to Robert Klemme for the lazy loading idea.
|
75
|
+
opt = lambda { OptionParser.new do |opts|
|
76
|
+
yield opts
|
77
|
+
# append desc to banner in next line
|
78
|
+
opts.banner << "\n#{opts.description}\n" if opts.description
|
79
|
+
end }
|
80
|
+
@commands[name.to_s] = opt
|
81
|
+
end
|
82
|
+
# specify global options and banner and description
|
83
|
+
# Yields the optionparser
|
84
|
+
def global_options
|
85
|
+
if !defined? @global
|
86
|
+
@global = OptionParser.new do |opts|
|
87
|
+
yield opts
|
88
|
+
end
|
89
|
+
else
|
90
|
+
yield @global
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Added so applications can print out a bare listing of top level commands
|
95
|
+
# for dynamic custom completion.
|
96
|
+
def list_actions
|
97
|
+
@commands.each_pair do |c, opt| puts c
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def print_actions
|
102
|
+
cmdtext = "Commands are:"
|
103
|
+
@commands.each_pair do |c, opt|
|
104
|
+
#puts "inside opt.call loop"
|
105
|
+
desc = opt.call.description
|
106
|
+
cmdtext << "\n #{c} : #{desc}"
|
107
|
+
end
|
108
|
+
|
109
|
+
# print aliases
|
110
|
+
unless @aliases.empty?
|
111
|
+
cmdtext << "\n\nAliases: \n"
|
112
|
+
@aliases.each_pair { |name, val| cmdtext << " #{name} - #{val}\n" }
|
113
|
+
end
|
114
|
+
|
115
|
+
cmdtext << "\n\nSee '#{$0} help COMMAND' for more information on a specific command."
|
116
|
+
end
|
117
|
+
## add text of subcommands in help and --help option
|
118
|
+
def add_subcommand_help
|
119
|
+
# user has defined some, but lets add subcommand information
|
120
|
+
|
121
|
+
cmdtext = print_actions
|
122
|
+
|
123
|
+
global_options do |opts|
|
124
|
+
# lets add the description user gave into banner
|
125
|
+
opts.banner << "\n#{opts.description}\n" if opts.description
|
126
|
+
opts.separator ""
|
127
|
+
opts.separator cmdtext
|
128
|
+
end
|
129
|
+
end
|
130
|
+
# this is so that on pressing --help he gets same subcommand help as when doing help.
|
131
|
+
# This is to be added in your main program, after defining global options
|
132
|
+
# if you want detailed help on --help. This is since optionparser's default
|
133
|
+
# --help will not print your actions/commands
|
134
|
+
def add_help_option
|
135
|
+
global_options do |opts|
|
136
|
+
opts.on("-h", "--help", "Print this help") do |v|
|
137
|
+
add_subcommand_help
|
138
|
+
puts @global
|
139
|
+
exit
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
# first parse global optinos
|
144
|
+
# then parse subcommand options if valid subcommand
|
145
|
+
# special case of "help command" so we print help of command - git style (3)
|
146
|
+
# in all invalid cases print global help
|
147
|
+
# @return command name if relevant
|
148
|
+
def opt_parse
|
149
|
+
# if user has not defined global, we need to create it
|
150
|
+
@command_name = nil
|
151
|
+
if !defined? @global
|
152
|
+
global_options do |opts|
|
153
|
+
opts.banner = "Usage: #{$0} [options] [subcommand [options]]"
|
154
|
+
opts.separator ""
|
155
|
+
opts.separator "Global options are:"
|
156
|
+
opts.on("-h", "--help", "Print this help") do |v|
|
157
|
+
add_subcommand_help
|
158
|
+
puts @global
|
159
|
+
exit
|
160
|
+
end
|
161
|
+
opts.separator ""
|
162
|
+
#opts.separator subtext # FIXME: no such variable supposed to have subcommand help
|
163
|
+
end
|
164
|
+
else
|
165
|
+
end
|
166
|
+
@global.order!
|
167
|
+
cmd = ARGV.shift
|
168
|
+
if cmd
|
169
|
+
#$stderr.puts "Command: #{cmd}, args:#{ARGV}, #{@commands.keys} "
|
170
|
+
sc = @commands[cmd]
|
171
|
+
#puts "sc: #{sc}: #{@commands}"
|
172
|
+
unless sc
|
173
|
+
# see if an alias exists
|
174
|
+
sc, cmd = _check_alias cmd
|
175
|
+
end
|
176
|
+
# if valid command parse the args
|
177
|
+
if sc
|
178
|
+
@command_name = cmd
|
179
|
+
sc.call.order!
|
180
|
+
else
|
181
|
+
# else if help <command> then print its help GIT style (3)
|
182
|
+
if !ARGV.empty? && cmd == "help"
|
183
|
+
cmd = ARGV.shift
|
184
|
+
#$stderr.puts " 110 help #{cmd}"
|
185
|
+
sc = @commands[cmd]
|
186
|
+
# if valid command print help, else print global help
|
187
|
+
unless sc
|
188
|
+
sc, cmd = _check_alias cmd
|
189
|
+
end
|
190
|
+
if sc
|
191
|
+
#puts " 111 help #{cmd}"
|
192
|
+
puts sc.call
|
193
|
+
else
|
194
|
+
# no help for this command XXX check for alias
|
195
|
+
puts "Invalid command: #{cmd}."
|
196
|
+
add_subcommand_help
|
197
|
+
puts @global
|
198
|
+
end
|
199
|
+
else
|
200
|
+
# invalid command
|
201
|
+
puts "Invalid command: #{cmd}" unless cmd == "help"
|
202
|
+
add_subcommand_help
|
203
|
+
puts @global
|
204
|
+
end
|
205
|
+
exit 0
|
206
|
+
end
|
207
|
+
end
|
208
|
+
return @command_name
|
209
|
+
end
|
210
|
+
def alias_command name, *args
|
211
|
+
@aliases[name.to_s] = args
|
212
|
+
end
|
213
|
+
def _check_alias cmd
|
214
|
+
alas = @aliases[cmd]
|
215
|
+
#$stderr.puts "195 alas: #{alas} "
|
216
|
+
if alas
|
217
|
+
case alas
|
218
|
+
when Array
|
219
|
+
cmd = alas.shift
|
220
|
+
#$stderr.puts "Array cmd: #{cmd} "
|
221
|
+
ARGV.unshift alas.shift unless alas.empty?
|
222
|
+
#$stderr.puts "ARGV #{ARGV} "
|
223
|
+
else
|
224
|
+
cmd = alas
|
225
|
+
end
|
226
|
+
end
|
227
|
+
sc = @commands[cmd] if cmd
|
228
|
+
return sc, cmd
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
if __FILE__ == $PROGRAM_NAME
|
233
|
+
include Subcommands
|
234
|
+
options = {}
|
235
|
+
appname = File.basename($0)
|
236
|
+
# global is optional
|
237
|
+
global_options do |opts|
|
238
|
+
opts.banner = "Usage: #{appname} [options] [subcommand [options]]"
|
239
|
+
opts.description = "Stupid program that does something"
|
240
|
+
opts.separator ""
|
241
|
+
opts.separator "Global options are:"
|
242
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
243
|
+
options[:verbose] = v
|
244
|
+
end
|
245
|
+
end
|
246
|
+
add_help_option
|
247
|
+
# define a command
|
248
|
+
command :foo, :goo do |opts|
|
249
|
+
opts.banner = "Usage: foo [options]"
|
250
|
+
opts.description = "desc for foo"
|
251
|
+
opts.on("-f", "--[no-]force", "force verbosely") do |v|
|
252
|
+
options[:force] = v
|
253
|
+
end
|
254
|
+
end
|
255
|
+
command :baz do |opts|
|
256
|
+
opts.banner = "Usage: baz [options]"
|
257
|
+
opts.description = "desc for baz"
|
258
|
+
opts.on("-q", "--[no-]quiet", "quietly run ") do |v|
|
259
|
+
options[:quiet] = v
|
260
|
+
end
|
261
|
+
end
|
262
|
+
alias_command :bar, 'baz'
|
263
|
+
alias_command :boo, 'foo', '--force'
|
264
|
+
alias_command :zoo, 'foo', 'ruby'
|
265
|
+
|
266
|
+
# do the parsing.
|
267
|
+
cmd = opt_parse()
|
268
|
+
|
269
|
+
puts "cmd: #{cmd}"
|
270
|
+
puts "options ......"
|
271
|
+
p options
|
272
|
+
puts "ARGV:"
|
273
|
+
p ARGV
|
274
|
+
end
|
data/lib/wordsmith.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'kindlegen'
|
4
|
+
require 'yaml'
|
5
|
+
require 'git'
|
6
|
+
|
7
|
+
require 'wordsmith/init'
|
8
|
+
require 'wordsmith/generate'
|
9
|
+
require 'wordsmith/publish'
|
10
|
+
|
11
|
+
require 'fileutils'
|
12
|
+
require 'pp'
|
13
|
+
|
14
|
+
class Wordsmith
|
15
|
+
include Init
|
16
|
+
include Generate
|
17
|
+
include Publish
|
18
|
+
|
19
|
+
attr_accessor :subcommand, :args, :options, :name, :files, :stylesheet
|
20
|
+
attr_reader :info
|
21
|
+
|
22
|
+
OUTPUT_TYPES = ['html', 'epub', 'mobi', 'pdf']
|
23
|
+
WORDSMITH_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@subcommand = nil
|
27
|
+
@args = []
|
28
|
+
@options = {}
|
29
|
+
@config = YAML::parse(File.open(local('.wordsmith'))).transform rescue {}
|
30
|
+
@name = File.basename(local('.'))
|
31
|
+
end
|
32
|
+
|
33
|
+
def info(message)
|
34
|
+
@info ||= []
|
35
|
+
@info << message
|
36
|
+
end
|
37
|
+
|
38
|
+
def local(file)
|
39
|
+
File.expand_path(File.join(Dir.pwd, file))
|
40
|
+
end
|
41
|
+
|
42
|
+
def base(file)
|
43
|
+
File.join(WORDSMITH_ROOT, file)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'subcommand'
|
2
|
+
|
3
|
+
class Wordsmith
|
4
|
+
include Subcommands
|
5
|
+
|
6
|
+
def info(message)
|
7
|
+
puts message
|
8
|
+
end
|
9
|
+
|
10
|
+
module CLI
|
11
|
+
|
12
|
+
def run
|
13
|
+
parse_options
|
14
|
+
if @subcommand && self.respond_to?(@subcommand)
|
15
|
+
begin
|
16
|
+
self.send @subcommand, @args
|
17
|
+
rescue Object => e
|
18
|
+
error e
|
19
|
+
end
|
20
|
+
else
|
21
|
+
help
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def error(e)
|
26
|
+
puts 'Error: ' + e.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def help
|
30
|
+
puts print_actions
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_options
|
34
|
+
@options = {}
|
35
|
+
global_options do |opts|
|
36
|
+
opts.banner = "Usage: #{$0} [options] [subcommand [options]]"
|
37
|
+
opts.description = "wordsmith helps you write books collectively with Git"
|
38
|
+
opts.separator ""
|
39
|
+
opts.separator "Global options are:"
|
40
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
41
|
+
@options[:verbose] = v
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
command :init, :new, :n do |opts|
|
46
|
+
opts.banner = "Usage: wordsmith new (directory)"
|
47
|
+
opts.description = "initialize a new book layout"
|
48
|
+
end
|
49
|
+
|
50
|
+
command :generate, :g do |opts|
|
51
|
+
opts.banner = "Usage: wordsmith generate [options]"
|
52
|
+
opts.description = "generate digital formats"
|
53
|
+
end
|
54
|
+
|
55
|
+
command :publish do |opts|
|
56
|
+
opts.banner = "Usage: wordsmith publish"
|
57
|
+
opts.description = "publish your book to github project page"
|
58
|
+
end
|
59
|
+
|
60
|
+
@subcommand = opt_parse
|
61
|
+
@args = ARGV
|
62
|
+
end
|
63
|
+
|
64
|
+
# DISPLAY HELPER FUNCTIONS #
|
65
|
+
|
66
|
+
def l(info, size)
|
67
|
+
clean(info)[0, size].ljust(size)
|
68
|
+
end
|
69
|
+
|
70
|
+
def r(info, size)
|
71
|
+
clean(info)[0, size].rjust(size)
|
72
|
+
end
|
73
|
+
|
74
|
+
def clean(info)
|
75
|
+
info.to_s.gsub("\n", ' ')
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
include CLI
|
81
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
class Wordsmith
|
2
|
+
module Generate
|
3
|
+
|
4
|
+
attr_reader :files, :output
|
5
|
+
|
6
|
+
# generate the new media
|
7
|
+
def generate(args = [])
|
8
|
+
@output = local(File.join('final', @name))
|
9
|
+
|
10
|
+
content_dir = local(File.join('content'))
|
11
|
+
@files = Dir.glob(content_dir + '/**/*.*').join(" \\\n")
|
12
|
+
|
13
|
+
if @files.empty?
|
14
|
+
raise "Exiting.. Nothing to generate in #{content_dir}.\nHave you run 'wordsmith new'?"
|
15
|
+
end
|
16
|
+
|
17
|
+
build_metadata_xml
|
18
|
+
|
19
|
+
@stylesheet = if @config["stylesheet"] && File.exists?(local(@config["stylesheet"]))
|
20
|
+
local(@config["stylesheet"])
|
21
|
+
end
|
22
|
+
|
23
|
+
Dir.mkdir(local('final')) unless File.exists?(local('final'))
|
24
|
+
|
25
|
+
formats = args.empty? ? OUTPUT_TYPES : args
|
26
|
+
formats.each do |format|
|
27
|
+
if respond_to?("to_#{format}")
|
28
|
+
if format == 'mobi'
|
29
|
+
out = to_mobi
|
30
|
+
else
|
31
|
+
out = run_command(send("to_#{format}"))
|
32
|
+
end
|
33
|
+
if $?.exitstatus == 0 && out == '' || $?.exitstatus == 1 && format == 'mobi'
|
34
|
+
if format == 'html'
|
35
|
+
info "Created #{@output}/index.html"
|
36
|
+
else
|
37
|
+
info "Created #{@output}.#{format}"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
raise "#{format} generator failed"
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise "No generator found for #{format}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_metadata_xml
|
49
|
+
metadata = local(File.join('metadata.xml'))
|
50
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
51
|
+
xml.metadata('xmlns:dc' => 'http://purl.org/dc/elements/1.1/') {
|
52
|
+
xml['dc'].title { xml.text @config["title"] }
|
53
|
+
xml['dc'].creator { xml.text @config["author"] }
|
54
|
+
xml['dc'].language { xml.text @config["language"] }
|
55
|
+
}
|
56
|
+
end
|
57
|
+
frg = Nokogiri::XML.fragment(builder.to_xml)
|
58
|
+
nodes = frg.search('.//metadata/*')
|
59
|
+
File.open(metadata, 'w') { |f| f.write(nodes.to_xml) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_html
|
63
|
+
info "Generating html..."
|
64
|
+
html_dir = local(File.join('final', @name))
|
65
|
+
Dir.mkdir(html_dir) unless File.exists?(html_dir)
|
66
|
+
header = if File.exists?(local(File.join('layout', 'header.html')))
|
67
|
+
local(File.join('layout', 'header.html'))
|
68
|
+
end
|
69
|
+
footer = if File.exists?(local(File.join('layout', 'footer.html')))
|
70
|
+
local(File.join('layout', 'footer.html'))
|
71
|
+
end
|
72
|
+
`cp -r #{base(File.join('template', 'assets'))} #{html_dir}`
|
73
|
+
cmd = "pandoc -s -S --toc -o #{File.join(html_dir, 'index.html')} -t html"
|
74
|
+
puts @config['stylesheet']
|
75
|
+
cmd += " -c #{@config['stylesheet']}" if @stylesheet
|
76
|
+
cmd += " -B #{header}" if header
|
77
|
+
cmd += " -A #{footer}" if footer
|
78
|
+
cmd += " \\\n#{@files}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_epub
|
82
|
+
info "Generating epub..."
|
83
|
+
metadata = if File.exists?(local(File.join('metadata.xml')))
|
84
|
+
local(File.join('metadata.xml'))
|
85
|
+
end
|
86
|
+
cover = if @config["cover"] && File.exists?(local(File.join(@config["cover"])))
|
87
|
+
local(File.join(@config["cover"]))
|
88
|
+
end
|
89
|
+
cmd = "pandoc -S -o #{@output}.epub -t epub"
|
90
|
+
cmd += " \\\n--epub-metadata=#{metadata}"
|
91
|
+
cmd += " \\\n--epub-cover-image=#{cover}" if cover
|
92
|
+
cmd += " \\\n--epub-stylesheet=#{@stylesheet}" if @stylesheet
|
93
|
+
cmd += " \\\n#{@files}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_mobi
|
97
|
+
if File.exists?(@output + '.epub')
|
98
|
+
info "Generating mobi..."
|
99
|
+
Kindlegen.run("#{@output}.epub", "-o", "#{@name}.mobi")
|
100
|
+
else
|
101
|
+
info "Skipping .mobi (#{@name}.epub doesn't exist)"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_pdf
|
106
|
+
info "Generating pdf..."
|
107
|
+
engine = ''
|
108
|
+
[['pdftex', 'pdflatex'], ['xetex', 'xelatex'], 'lualatex'].each do |e|
|
109
|
+
if e.is_a? Array
|
110
|
+
cmd, name = e
|
111
|
+
else
|
112
|
+
cmd = name = e
|
113
|
+
end
|
114
|
+
if can_run?(cmd + ' -v')
|
115
|
+
engine = name
|
116
|
+
break
|
117
|
+
end
|
118
|
+
end
|
119
|
+
cmd = "pandoc -N --toc -o #{@output}.pdf #{@files}"
|
120
|
+
cmd += " --latex-engine=#{engine}" unless engine == ''
|
121
|
+
end
|
122
|
+
|
123
|
+
def run_command(cmd)
|
124
|
+
`#{cmd}`.strip
|
125
|
+
end
|
126
|
+
|
127
|
+
def can_run?(cmd)
|
128
|
+
`#{cmd} 2>&1`
|
129
|
+
$?.success?
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Wordsmith
|
2
|
+
module Init
|
3
|
+
|
4
|
+
# start a new wordsmith directory with skeleton structure
|
5
|
+
def init(args = [])
|
6
|
+
name = Array(args).shift
|
7
|
+
raise "needs a directory name" unless name
|
8
|
+
raise "directory already exists" if File.exists?(name)
|
9
|
+
|
10
|
+
info "Creating wordsmith directory structure in #{name}"
|
11
|
+
template_dir = File.join(WORDSMITH_ROOT, 'template')
|
12
|
+
ign = Dir.glob(template_dir + '/.[a-z]*')
|
13
|
+
FileUtils.cp_r template_dir, name
|
14
|
+
|
15
|
+
# also copy files that start with .
|
16
|
+
FileUtils.cp_r ign, name
|
17
|
+
if Git.init(local(name))
|
18
|
+
info "Initialized empty Git repository in #{File.join(local(name), '.git')}"
|
19
|
+
@git = Git.open(local(name))
|
20
|
+
@git.add '.'
|
21
|
+
info "git add ."
|
22
|
+
@git.commit 'initial commit'
|
23
|
+
info "git commit -m 'initial commit'"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Wordsmith
|
2
|
+
module Publish
|
3
|
+
|
4
|
+
# publish html book to github project page
|
5
|
+
def publish(args = [])
|
6
|
+
options = args.first
|
7
|
+
|
8
|
+
@git = Git.open(local('.'))
|
9
|
+
|
10
|
+
if options =~ /\.git/ && !@git.remotes.map(&:to_s).include?('origin')
|
11
|
+
@git.add_remote 'origin', options
|
12
|
+
info "Added remote origin #{options}"
|
13
|
+
elsif !@git.remotes.map(&:to_s).include?('origin')
|
14
|
+
raise "You must add a remote origin.\ne.g: wordsmith publish git@github.com:jassa/wordsmith-example.git"
|
15
|
+
end
|
16
|
+
|
17
|
+
if @git.current_branch != 'master'
|
18
|
+
begin
|
19
|
+
@git.checkout 'master'
|
20
|
+
info "Switched to branch 'master'"
|
21
|
+
rescue
|
22
|
+
raise "You must be in the 'master' branch to publish"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
html_dir = File.join('final', @name)
|
27
|
+
|
28
|
+
unless File.exists?(File.join(html_dir, 'index.html'))
|
29
|
+
raise "Exiting.. Nothing to publish.\nHave you run 'wordsmith generate'?"
|
30
|
+
end
|
31
|
+
|
32
|
+
# http://pages.github.com/#project_pages
|
33
|
+
`git symbolic-ref HEAD refs/heads/gh-pages`
|
34
|
+
info "Switched to branch 'gh-pages'"
|
35
|
+
`rm .git/index`
|
36
|
+
`git clean -fdx`
|
37
|
+
info "Removed files"
|
38
|
+
`git checkout master #{html_dir}`
|
39
|
+
info "Copied files from 'master' #{html_dir}"
|
40
|
+
`mv #{html_dir}/* ./`
|
41
|
+
`rm -r final/`
|
42
|
+
`git add .`
|
43
|
+
`git rm -r final/*`
|
44
|
+
begin
|
45
|
+
@git.commit 'updating project page'
|
46
|
+
info "git commit -m 'updating project page'"
|
47
|
+
rescue Exception => e
|
48
|
+
if e.to_s =~ /nothing to commit/
|
49
|
+
raise 'Already up to date. Nothing to commit.'
|
50
|
+
else
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@git.push 'origin', 'gh-pages'
|
55
|
+
info "git push origin gh-pages"
|
56
|
+
@git.checkout 'master'
|
57
|
+
rescue Exception => e
|
58
|
+
@git.checkout 'master' rescue nil
|
59
|
+
raise e
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/template/.gitignore
ADDED
data/template/.wordsmith
ADDED
data/template/README.md
ADDED
Binary file
|
@@ -0,0 +1,44 @@
|
|
1
|
+
html {
|
2
|
+
margin: 0;
|
3
|
+
padding: 40px;
|
4
|
+
background: #DFE1E5;
|
5
|
+
}
|
6
|
+
body {
|
7
|
+
margin: 0;
|
8
|
+
padding: 30px;
|
9
|
+
font-family: "Lucida Grande",verdana,arial,helvetica,sans-serif;
|
10
|
+
font-size: 14px;
|
11
|
+
color: #373737;
|
12
|
+
width: 800px;
|
13
|
+
background: rgba(255, 255, 255, 0.8);
|
14
|
+
-webkit-box-shadow: 2px 2px 6px rgba(0,0,0,.30);
|
15
|
+
-moz-box-shadow: 2px 2px 6px rgba(0,0,0,.30);
|
16
|
+
box-shadow: 2px 2px 6px rgba(0,0,0,.30);
|
17
|
+
border: 1px solid #C7C7C7;
|
18
|
+
-webkit-border-radius: 3px;
|
19
|
+
-moz-border-radius: 3px;
|
20
|
+
border-radius: 3px;
|
21
|
+
|
22
|
+
}
|
23
|
+
a {
|
24
|
+
color: #1A4882;
|
25
|
+
}
|
26
|
+
h1, h2, h3 {
|
27
|
+
margin: 30px 0 10px 0;
|
28
|
+
}
|
29
|
+
body {
|
30
|
+
display: inline-block;
|
31
|
+
}
|
32
|
+
div#TOC {
|
33
|
+
border: 1px solid #C7C7C7;
|
34
|
+
-webkit-border-radius: 3px;
|
35
|
+
-moz-border-radius: 3px;
|
36
|
+
border-radius: 3px;
|
37
|
+
}
|
38
|
+
div#cover {
|
39
|
+
text-align: center;
|
40
|
+
padding: 0 0 30px 0;
|
41
|
+
}
|
42
|
+
div#cover img {
|
43
|
+
max-height: 400px;
|
44
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
## A Wordsmith's Manifesto
|
2
|
+
|
3
|
+
I eat, pray and love the written word. My every spare moment is spent reading,
|
4
|
+
writing, or listening to words that bring inspiration, comfort, wisdom and joy.
|
5
|
+
|
6
|
+
I am always poised to pen something down, especially when triggered by
|
7
|
+
a flash of insight, moved by a scene, or teased by an idea that refuses
|
8
|
+
to leave my head.
|
9
|
+
|
10
|
+
I carry a notepad and pen with me whenever I go for meetings, regardless of
|
11
|
+
the agenda. Capturing prodigious quantities of notes is always a good practice
|
12
|
+
for the inner journalist in me.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
## Read, Read, Read
|
2
|
+
|
3
|
+
I read, and read, and read. During bus rides to and from one destination
|
4
|
+
to another. Before I sleep at night. Whenever I'm waiting for
|
5
|
+
an activity to happen. You will always find a book in my bag - and yes,
|
6
|
+
I am a "bag man". Nothing triggers good writing better than good reads.
|
7
|
+
|
8
|
+
I'm a keen observer of my environment, the people surrounding me,
|
9
|
+
and the unique situations that I find myself in constantly. They provide fodder
|
10
|
+
to my literary outputs, planting the seeds of the narratives that chronicle my thoughts.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
## Pay Special Attention
|
2
|
+
|
3
|
+
I pay special attention to how every word is crafted and honed
|
4
|
+
to meet the page. I fuss over the flow and cadence of my sentences,
|
5
|
+
the expressions used, the points highlighted, and how they are
|
6
|
+
all assembled together. Especially the beginning and the end.
|
7
|
+
|
8
|
+
### Being human
|
9
|
+
|
10
|
+
Being human, however, there will be "off" days when I can barely muster
|
11
|
+
a single virtuous word. I understand that good writing isn't as easy
|
12
|
+
as turning on a tap and letting it flow. Writer's block is
|
13
|
+
an inevitable occupational hazard that hits all of us.
|
14
|
+
|
15
|
+
### Different strokes for different folks
|
16
|
+
|
17
|
+
I believe in different strokes for different folks. Write for your audiences
|
18
|
+
and write for the medium. There are distinct differences between an advertisement,
|
19
|
+
a press release, a blog post, an invitation email, a strategy paper, and
|
20
|
+
a diary documenting one's innermost thoughts.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
### There is a time and place for everything
|
2
|
+
|
3
|
+
I am always mindful of the contexts of my writing. There is a time and place
|
4
|
+
for everything under the Sun. Finding the right moment to tell the right story
|
5
|
+
to the right audience under the right conditions are crucial elements of writing
|
6
|
+
success. Anything else is a compromise.
|
7
|
+
|
8
|
+
### I am a Wordsmith
|
9
|
+
|
10
|
+
I am always learning and always picking up new things.
|
11
|
+
While I trust in my own instincts, I am also painfully aware that I'm not omniscient.
|
12
|
+
I will make mistakes, but I will pick myself up again, brush off the dust, and carry on.
|
13
|
+
|
14
|
+
I am a wordsmith. Writing is my craft.
|
File without changes
|
data/test/init_test.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path "../test_helper", __FILE__
|
2
|
+
|
3
|
+
context "wordsmith init tests" do
|
4
|
+
setup do
|
5
|
+
@wordsmith = Wordsmith.new
|
6
|
+
end
|
7
|
+
|
8
|
+
test "can't init a wordsmith repo without a directory" do
|
9
|
+
in_temp_dir do
|
10
|
+
assert_raise RuntimeError do
|
11
|
+
@wordsmith.init
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
test "can't init a wordsmith repo for existing dir" do
|
17
|
+
in_temp_dir do
|
18
|
+
Dir.mkdir('w')
|
19
|
+
assert_raise RuntimeError do
|
20
|
+
@wordsmith.init('w')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
test "can init a wordsmith repo" do
|
26
|
+
in_temp_dir do
|
27
|
+
@wordsmith.init('w')
|
28
|
+
files = Dir.glob('w/**/*', File::FNM_DOTMATCH)
|
29
|
+
assert files.include? "w/README.md"
|
30
|
+
assert files.include? "w/.wordsmith"
|
31
|
+
assert files.include? "w/.gitignore"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
2
|
+
$LOAD_PATH.unshift dir + '/../lib'
|
3
|
+
$TESTING = true
|
4
|
+
|
5
|
+
# Necessary to override stdlib: http://www.ruby-forum.com/topic/212974
|
6
|
+
require 'rubygems'
|
7
|
+
gem 'test-unit'
|
8
|
+
require 'test/unit'
|
9
|
+
|
10
|
+
require 'wordsmith'
|
11
|
+
require 'pp'
|
12
|
+
require 'tempfile'
|
13
|
+
|
14
|
+
##
|
15
|
+
# test/spec/mini 3
|
16
|
+
# http://gist.github.com/25455
|
17
|
+
# chris@ozmm.org
|
18
|
+
#
|
19
|
+
def context(*args, &block)
|
20
|
+
return super unless (name = args.first) && block
|
21
|
+
require 'test/unit'
|
22
|
+
klass = Class.new(defined?(ActiveSupport::TestCase) ? ActiveSupport::TestCase : Test::Unit::TestCase) do
|
23
|
+
def self.test(name, &block)
|
24
|
+
define_method("test_#{name.gsub(/\W/,'_')}", &block) if block
|
25
|
+
end
|
26
|
+
def self.xtest(*args) end
|
27
|
+
def self.setup(&block) define_method(:setup, &block) end
|
28
|
+
def self.teardown(&block) define_method(:teardown, &block) end
|
29
|
+
end
|
30
|
+
(class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
|
31
|
+
klass.class_eval &block
|
32
|
+
end
|
33
|
+
|
34
|
+
def in_temp_dir
|
35
|
+
f = Tempfile.new('test')
|
36
|
+
p = f.path
|
37
|
+
f.unlink
|
38
|
+
Dir.mkdir(p)
|
39
|
+
Dir.chdir(p) do
|
40
|
+
yield
|
41
|
+
end
|
42
|
+
end
|
data/wordsmith.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$LOAD_PATH.unshift 'lib'
|
3
|
+
require 'wordsmith/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "wordsmith"
|
7
|
+
s.version = Wordsmith::VERSION
|
8
|
+
s.authors = ["Amed Rodriguez", "Javier Saldana", "Rene Cienfuegos"]
|
9
|
+
s.email = ["amed@tractical.com", "javier@tractical.com", "renecienfuegos@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/tractical/wordsmith"
|
11
|
+
s.summary = "E-books publisher."
|
12
|
+
s.description = "Create, collaborate and publish e-books -- easily."
|
13
|
+
s.has_rdoc = false
|
14
|
+
|
15
|
+
s.rubyforge_project = "wordsmith"
|
16
|
+
|
17
|
+
s.executables = %w( wordsmith )
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
|
21
|
+
s.add_dependency('nokogiri')
|
22
|
+
s.add_dependency('kindlegen')
|
23
|
+
s.add_dependency("git")
|
24
|
+
s.add_development_dependency("rake")
|
25
|
+
s.add_development_dependency("test-unit")
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wordsmith
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Amed Rodriguez
|
9
|
+
- Javier Saldana
|
10
|
+
- Rene Cienfuegos
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2012-03-25 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: nokogiri
|
18
|
+
requirement: &70114637222780 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *70114637222780
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: kindlegen
|
29
|
+
requirement: &70114637222320 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ! '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *70114637222320
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: git
|
40
|
+
requirement: &70114637221800 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
type: :runtime
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *70114637221800
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rake
|
51
|
+
requirement: &70114637221360 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *70114637221360
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: test-unit
|
62
|
+
requirement: &70114637220940 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: *70114637220940
|
71
|
+
description: Create, collaborate and publish e-books -- easily.
|
72
|
+
email:
|
73
|
+
- amed@tractical.com
|
74
|
+
- javier@tractical.com
|
75
|
+
- renecienfuegos@gmail.com
|
76
|
+
executables:
|
77
|
+
- wordsmith
|
78
|
+
extensions: []
|
79
|
+
extra_rdoc_files: []
|
80
|
+
files:
|
81
|
+
- .gitignore
|
82
|
+
- Gemfile
|
83
|
+
- Gemfile.lock
|
84
|
+
- README.md
|
85
|
+
- Rakefile
|
86
|
+
- bin/wordsmith
|
87
|
+
- lib/subcommand.rb
|
88
|
+
- lib/wordsmith.rb
|
89
|
+
- lib/wordsmith/cli.rb
|
90
|
+
- lib/wordsmith/generate.rb
|
91
|
+
- lib/wordsmith/init.rb
|
92
|
+
- lib/wordsmith/publish.rb
|
93
|
+
- lib/wordsmith/version.rb
|
94
|
+
- template/.gitignore
|
95
|
+
- template/.wordsmith
|
96
|
+
- template/README.md
|
97
|
+
- template/assets/images/cover.jpg
|
98
|
+
- template/assets/stylesheets/default.css
|
99
|
+
- template/content/01_manifesto.md
|
100
|
+
- template/content/02_read_read_read.md
|
101
|
+
- template/content/03_pay_special_attention/01_intro.md
|
102
|
+
- template/content/03_pay_special_attention/02_conclusion.md
|
103
|
+
- template/layout/footer.html
|
104
|
+
- template/layout/header.html
|
105
|
+
- test/generate_test.rb
|
106
|
+
- test/init_test.rb
|
107
|
+
- test/publish_test.rb
|
108
|
+
- test/test_helper.rb
|
109
|
+
- wordsmith.gemspec
|
110
|
+
homepage: https://github.com/tractical/wordsmith
|
111
|
+
licenses: []
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project: wordsmith
|
130
|
+
rubygems_version: 1.8.17
|
131
|
+
signing_key:
|
132
|
+
specification_version: 3
|
133
|
+
summary: E-books publisher.
|
134
|
+
test_files:
|
135
|
+
- test/generate_test.rb
|
136
|
+
- test/init_test.rb
|
137
|
+
- test/publish_test.rb
|
138
|
+
- test/test_helper.rb
|