wordsmith 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +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
|