Krambook: Build a Gem

It’s time to package up all these custom Kramdown extensions into a Gem.

T> If you so desire you can skip this section and simply visit the repo and
T> install the completed gem.

First create a directory for the gem:

{:lang=”sh”}
$ mkdir krambook
$ cd krambook

Now we need to add a gemspec file:

{:lang=”sh”}
$ touch krambook.gemspec

Add the contents:

{:lang=”ruby”}

  1. #!/usr/bin/env gem build
  2. # encoding: utf-8
  3. Gem::Specification.new do |s|
  4. s.name = 'krambook'
  5. s.rubyforge_project = 'krambook'
  6. s.authors = ["K-2052"]
  7. s.email = 'k@2052.me'
  8. s.summary = 'Kraft you ebooks with Kramdown.'
  9. s.homepage = 'http://github.com/bookworm/krambook'
  10. s.description = 'Kraft you ebooks with Kramdown. Intended as a collection
  11. of helpers for working with Markdown based ebooks.
  12. Currently operates like a local version of LeanPub.'
  13. s.required_rubygems_version = '>= 1.3.6'
  14. s.version = '0.0.1'
  15. s.date = Time.now.strftime("%Y-%m-%d")
  16. s.files = `git ls-files`.split("\n") | Dir.glob("{lib}/**/*")
  17. s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
  18. s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
  19. s.require_paths = ['lib']
  20. s.rdoc_options = ['--charset=UTF-8']
  21. s.add_dependency('kramdown')
  22. s.add_dependency('bundler', '~> 1.0')
  23. s.add_dependency('thor')
  24. s.add_dependency('active_support')
  25. end

Most of these lines are self explanatory. We use git to pull our files and we add Thor as a dependency. Thor is a
wrapper around command line and file generation stuff. Think rake, but on steroids. Thor will allow us to construct
minimal code for handling arguments.

Add a Gemfile for bundler’s sake:

{:lang=”ruby”}
source ‘https://rubygems.org

  1. gemspec

This tells bundler to use the gemspec as a source for the gems. Run $ bundle install to make sure things are
installed.

Now we need to a base file to load up our gem file files when someone does require 'krambook':

{:lang=”sh”}
$ mkdir lib
$ cd lib
$ touch krambook.rb

Open up krambook.rb and define a class for the CLI interface:

{:lang=”ruby”}
require ‘kramdown’
require ‘thor’

  1. module Krambook
  2. class CLI < Thor
  3. include Thor::Actions
  4. class_option :type, :desc => 'The type to convert to', :aliases => '-t', :default => 'kramdown', :type => :string
  5. class_option :help, :type => :boolean, :desc => 'Show help usage'
  6. default_task :join
  7. def join
  8. end
  9. end
  10. end

Now on to our convert method. We need to add the code we created earlier and modify it to to use arguments passed to it.

{:lang=”ruby”}
def join
files = []
joined_file_path = File.expand_path ‘./manuscript/‘ + ‘Joined.md’
File.delete(joined_file_path) if File.exist?(joined_file_path)

  1. File.open(File.expand_path('./manuscript/Book.txt'), 'r') do |f|
  2. f.each_line do |line|
  3. files << line.gsub(/\s+/, "")
  4. end
  5. end
  6. File.open(joined_file_path, 'a') do |f|
  7. files.each do |file_path|
  8. full_file_path = File.expand_path './manuscript/' + file_path
  9. f.puts Kramdown::Document.new(IO.read(full_file_path)).send("to_#{options[:format]}".to_sym) if File.exists?(full_file_path)
  10. f.puts '' unless file_path = files.last
  11. end
  12. end
  13. end

Let’s add that external file inclusion converter we created earlier to lib/kramdown/converter:

{:lang=”ruby”}
module Kramdown
module Converter
class Includey < ::Kramdown::Converter::Kramdown
def convert_codeblock(el, indent)
if el.attr.include?(‘include’)
file_path = File.expand_path(Dir.pwd + ‘/‘ + el.attr[‘include’])
el.value = IO.read(file_path) if File.exists?(file_path)
end

  1. attr = el.attr.dup
  2. lang = extract_code_language!(attr)
  3. "~~~ #{lang}\n" + el.value.split(/\n/).map {|l| l.empty? ? "" : "#{l}"}.join("\n") + "\n~~~\n"
  4. end
  5. end
  6. end
  7. end

Remember to require it at the top of lib/krambook.rb

{:lang=”ruby”}
require ‘kramdown’
require ‘kramdown/converter/includey’

We want this to work from CLI so do the following.

{:lang=”sh”}
$ mkdir bin
$ touch bin/krambook
$ chmod a+x bin/krambook

Then add the following to bin/krambook:

{:lang=”ruby”}

  1. #!/usr/bin/env ruby
  2. $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
  3. require 'krambook'
  4. Krambook::CLI.start

We can test this by running the bin file in a directory with a book. For example:

{:lang=”sh”}
$ ruby /home/k2052/creations/krambook/bin/krambook

We get the following back:

{:lang=”sh”}
[WARNING] Attempted to create command “convert” without usage or description. Call desc if you want this method to be available as command or declare it inside a no_commands{} block. Invoked from “/home/k2052/creations/krambook/lib/krambook.rb:12:in `‘“.
Could not find command “”.

We can fix both issues by adding a desc before the join method definition.

Add the following to krambook.rb:

{:lang=”ruby”}
desc ‘join’, ‘Joins the markdown files’
def join

Run it again and you should have a Joined.md’ in the manuscript folder. Now it’s just a matter of submitting our gem
to rubygems.

Note: Obviously you don’t actually perform this step as I’m the maintainer of the Krambook gem.

First add a README.md file:

{:lang=”sh”}
$ touch README.md

Then do all the git stuff:

{:lang=”sh”}
$ git init .
$ git add .
$ git commit -m “first commit” -a

If your bin file is ignored you may have to do:

{:lang=”sh”}
$ gi]t add bin/ —force

Then we need to build the gem and submit:

{:lang=”sh”}
$ gem build krambook.gemspec
$ gem push krambook-0.0.1.gem

Done!