wordsmith 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .rvmrc
4
+ .DS_Store
5
+ example
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
@@ -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!
@@ -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).
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/*_test.rb']
8
+ t.verbose = true
9
+ end
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Wordsmith
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ final/*.epub
2
+ final/*.mobi
3
+ final/*.pdf
4
+ .DS_STORE
@@ -0,0 +1,7 @@
1
+ ---
2
+ edition: 0.1
3
+ language: en
4
+ author: Your Name
5
+ title: Your Book Title
6
+ cover: assets/images/cover.jpg
7
+ stylesheet: assets/stylesheets/default.css
@@ -0,0 +1,3 @@
1
+ This book is written using the wordsmith gem, which can be found at:
2
+
3
+ http://github.com/tractical/wordsmith
@@ -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
@@ -0,0 +1,3 @@
1
+ <div id="cover">
2
+ <image src="assets/images/cover.jpg" />
3
+ </div>
@@ -0,0 +1,7 @@
1
+ require File.expand_path "../test_helper", __FILE__
2
+
3
+ context "wordsmith generate tests" do
4
+ setup do
5
+ @wordsmith = Wordsmith.new
6
+ end
7
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ require File.expand_path "../test_helper", __FILE__
2
+
3
+ context "wordsmith publish tests" do
4
+ setup do
5
+ @wordsmith = Wordsmith.new
6
+ end
7
+ end
@@ -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
@@ -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