rdaux 0.1.0

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 ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ public/img/ditaa/
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem 'rake'
8
+ gem 'minitest'
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,47 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rdaux (0.1.0)
5
+ avalanche-cli (= 0.1.0)
6
+ posix-spawn (= 0.3.6)
7
+ pygments.rb (= 0.5.1)
8
+ redcarpet (= 3.0.0)
9
+ sinatra (= 1.3.3)
10
+ tilt (= 1.4.1)
11
+ unicorn (= 4.6.2)
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ avalanche-cli (0.1.0)
17
+ kgio (2.8.0)
18
+ minitest (5.0.6)
19
+ posix-spawn (0.3.6)
20
+ pygments.rb (0.5.1)
21
+ posix-spawn (~> 0.3.6)
22
+ yajl-ruby (~> 1.1.0)
23
+ rack (1.5.2)
24
+ rack-protection (1.5.0)
25
+ rack
26
+ raindrops (0.11.0)
27
+ rake (10.1.0)
28
+ redcarpet (3.0.0)
29
+ sinatra (1.3.3)
30
+ rack (~> 1.3, >= 1.3.6)
31
+ rack-protection (~> 1.2)
32
+ tilt (~> 1.3, >= 1.3.3)
33
+ tilt (1.4.1)
34
+ unicorn (4.6.2)
35
+ kgio (~> 2.6)
36
+ rack
37
+ raindrops (~> 0.7)
38
+ yajl-ruby (1.1.0)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ bundler (~> 1.3)
45
+ minitest
46
+ rake
47
+ rdaux!
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Bulat Shakirzyanov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # RDaux
2
+
3
+ RDaux creates beautiful documentation websites from markdown files.
4
+ It is inspired by daux.io and uses redcarpet with pygments.rb to process
5
+ github-flavored markdown files into beautiful documentation websites and
6
+ supports ASCII art with help of [Ditaa](http://sourceforge.net/projects/ditaa/).
7
+
8
+ ## Installation
9
+
10
+ RDaux is a rubygem, install it by running the following in your shell:
11
+
12
+ ```bash
13
+ > gem install rdaux
14
+ ```
15
+
16
+ ### Requirements
17
+
18
+ RDaux requires Python 2.5+ for Pygments and Java 1.5+ for ditaa.
19
+ Make sure those tools are installed and available in the `$PATH`
20
+
21
+ ## Usage
22
+
23
+ After RDaux has been installed, you can begin using it from your cli:
24
+
25
+ ```bash
26
+ > rdaux -h
27
+ Usage: rdaux [--version] [--help] command [options] [args]
28
+
29
+ Available rdaux commands are:
30
+ serve Dynamically serve html documentation from a given directory
31
+ generate Generates static html site using docs from given directory
32
+
33
+ Common options:
34
+ -h, --help Show this message
35
+ -v, --version Show version
36
+ ```
37
+
38
+ As you can see, RDaux can dynamically serve documentation from your markdown
39
+ sources or it can generate a static documentation website. You can determine
40
+ options available to each subcommand by running:
41
+
42
+ ```bash
43
+ > rdaux <subcommand> -h|--help
44
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/rdaux ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ root_path = File.expand_path(__FILE__ + '/../..')
4
+
5
+ ['lib', 'bundle'].each do |path|
6
+ dir = root_path + '/' + path
7
+
8
+ $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir)
9
+ end
10
+
11
+ require 'rubygems'
12
+
13
+ Dir.chdir(root_path) { require 'bundler/setup' }
14
+
15
+ require 'avalanche/cli'
16
+ require 'rdaux'
17
+ require 'rdaux/version'
18
+ require 'rdaux/cli'
19
+
20
+ extend RDaux::CLI
21
+
22
+ app = Avalanche::CLI::Application.new('rdaux', RDaux::VERSION)
23
+
24
+ app.command('serve [options] PATH', 'Dynamically serve html documentation from a given directory', {
25
+ :title => ['--title TITLE', 'Website title (e.g. RDaux)'],
26
+ :description => ['--description DESCRIPTION', 'Website description, displayed under title'],
27
+ :author => ['--author AUTHOR', 'Website author, defaults to $USER'],
28
+ :log_level => ['--log-level LEVEL', ['debug', 'info', 'warn', 'error', 'fatal'], 'Log level'],
29
+ :bind => ['--bind ADDRESS', 'IP and port or a unix domain socket to bind on (e.g. localhost:8080 or /var/run/socket.sock)'],
30
+ :workers => ['--workers NUM', Integer, 'Number of unicorn worker processes to use, defaults to 4']
31
+ }, command(:start_serving))
32
+
33
+ app.command('generate [options] PATH', 'Generates static html site using docs from given directory', {
34
+ :title => ['--title TITLE', 'Website title (e.g. RDaux)'],
35
+ :description => ['--description DESCRIPTION', 'Website description, displayed under title'],
36
+ :author => ['--author AUTHOR', 'Website author, defaults to $USER'],
37
+ :log_level => ['--log-level LEVEL', ['debug', 'info', 'warn', 'error', 'fatal'], 'Log level'],
38
+ :output_path => ['--output-path PATH', 'Path to store generated files in, defaults to site/ directory, next to the PATH given']
39
+ }, command(:generate_site))
40
+
41
+ app.run(ARGV)
data/lib/rdaux/cli.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'pathname'
2
+ require 'rdaux/container'
3
+
4
+ module RDaux
5
+ module CLI
6
+ include RDaux::Container
7
+
8
+ attr_reader :options, :directory
9
+
10
+ def command(method)
11
+ Proc.new do |opts, args|
12
+ process_options(opts)
13
+ send(method, *args)
14
+ end
15
+ end
16
+
17
+ def process_options(options)
18
+ @options = options
19
+ end
20
+
21
+ def use_directory(directory)
22
+ raise 'PATH is a required argument to serve command' if directory.nil?
23
+
24
+ @directory = Pathname(directory)
25
+ end
26
+
27
+ def start_serving(directory = nil)
28
+ use_directory(directory)
29
+ webserver.serve(website)
30
+ end
31
+
32
+ def generate_site(directory = nil)
33
+ use_directory(directory)
34
+ generator.generate_static(website)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,103 @@
1
+ require 'redcarpet'
2
+ require 'logger'
3
+
4
+ require 'rdaux/notifier'
5
+ require 'rdaux/web/application'
6
+ require 'rdaux/web/server'
7
+ require 'rdaux/web/site'
8
+ require 'rdaux/web/site/generator'
9
+ require 'rdaux/renderer'
10
+ require 'rdaux/logging_listener'
11
+
12
+ module RDaux
13
+ module Container
14
+ def webapp
15
+ @webapp ||= with_config(Web::Application) do |app|
16
+ app.set(:markdown, markdown)
17
+ app.set(:ditaa_jar, ditaa_jar)
18
+ end
19
+ end
20
+
21
+ def webserver
22
+ @webserver ||= with_logging(Web::Server.new(webapp, logger, public_folder, options))
23
+ end
24
+
25
+ def website
26
+ @website ||= Web::Site.new(title, description, author, directory)
27
+ end
28
+
29
+ def generator
30
+ @generator ||= with_logging(Web::Site::Generator.new(markdown, views_dir, public_folder, ditaa_jar, target_dir))
31
+ end
32
+
33
+ def logger
34
+ with_config(Logger.new($stderr)) do |l|
35
+ l.level = log_level
36
+ l.formatter = proc { |s, d, p, m| "%s | %-10s %s\n" % [d.strftime("%T,%L"), "[#{s}]", m] }
37
+ end
38
+ end
39
+
40
+ def log_level
41
+ Logger.const_get(@options.fetch(:log_level, 'info').upcase)
42
+ end
43
+
44
+ def logging_listener
45
+ @logging_listener ||= LoggingListener.new(logger)
46
+ end
47
+
48
+ def public_folder
49
+ File.expand_path(__FILE__ + '/../../../public')
50
+ end
51
+
52
+ def ditaa_jar
53
+ File.expand_path(__FILE__ + '/../../../vendor/ditaa/ditaa0_9.jar')
54
+ end
55
+
56
+ def markdown
57
+ Redcarpet::Markdown.new(Renderer.new({
58
+ :filter_html => true,
59
+ :images_dir => public_folder,
60
+ :ditaa_root => '/img/ditaa'
61
+ }), {
62
+ :no_intra_emphasis => true,
63
+ :tables => true,
64
+ :fenced_code_blocks => true,
65
+ :autolink => true,
66
+ :space_after_headers => true,
67
+ :superscript => true,
68
+ :underline => true,
69
+ :highlight => true
70
+ })
71
+ end
72
+
73
+ private
74
+
75
+ def views_dir
76
+ File.expand_path(__FILE__ + '/../web/views')
77
+ end
78
+
79
+ def title
80
+ options.fetch(:title, "RDaux")
81
+ end
82
+
83
+ def description
84
+ options.fetch(:description) { "Documentation for <em>#{directory.relative_path_from(Pathname(ENV['PWD']))}</em>" }
85
+ end
86
+
87
+ def author
88
+ options.fetch(:author) { ENV['USER'] }
89
+ end
90
+
91
+ def target_dir
92
+ Pathname(options.fetch(:output_path) { directory + '../site/' })
93
+ end
94
+
95
+ def with_logging(obj)
96
+ with_config(obj) {|o| o.add_listener(logging_listener)}
97
+ end
98
+
99
+ def with_config(obj)
100
+ obj.tap {|o| yield(o)}
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,19 @@
1
+ module RDaux
2
+ class LoggingListener
3
+ def initialize(logger)
4
+ @logger = logger
5
+ end
6
+
7
+ def creating_application(path)
8
+ @logger.debug("Creating application for: #{path}")
9
+ end
10
+
11
+ def adding_file(path)
12
+ @logger.debug("Adding file: #{path}")
13
+ end
14
+
15
+ def adding_directory(path)
16
+ @logger.debug("Adding directory: #{path}")
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module RDaux
2
+ module Notifier
3
+ def notifier_initialize
4
+ @__listeners__ = []
5
+ end
6
+
7
+ def add_listener(listener)
8
+ @__listeners__ << listener
9
+ end
10
+
11
+ def broadcast(event, *args)
12
+ @__listeners__.each do |listener|
13
+ listener.__send__(event, *args) if listener.respond_to?(event)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ require 'digest'
2
+ require 'pygments'
3
+
4
+ module RDaux
5
+ class Renderer < Redcarpet::Render::SmartyHTML
6
+ def initialize(options)
7
+ @images_dir = options.delete(:images_dir) { raise ":images_dir is a required option" }
8
+ @ditaa_root = options.delete(:ditaa_root) { raise ":ditaa_root is a required option" }
9
+
10
+ super(options)
11
+ end
12
+
13
+ def block_code(code, language)
14
+ case language
15
+ when nil
16
+ "<pre><code>#{code}</code></pre>"
17
+ when 'ditaa'
18
+ ascii2png(code)
19
+ else
20
+ highlight(code, language)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def highlight(code, language)
27
+ markup = Pygments.highlight(code, :lexer => language)
28
+ markup = markup.sub(/<div class="highlight"><pre>/,'<pre><code class="' + language + '">')
29
+ markup = markup.sub(/<\/pre><\/div>/,"</code></pre>")
30
+ markup
31
+ rescue MentosError
32
+ "<pre><code class=\"#{language}\">#{code}</code></pre>"
33
+ end
34
+
35
+ def ascii2png(code)
36
+ image_id = Digest::MD5.hexdigest(code)
37
+ png_path = @images_dir + @ditaa_root + "/#{image_id}.txt"
38
+ txt_path = @images_dir + @ditaa_root + "/#{image_id}.txt"
39
+
40
+ File.open(txt_path, 'w+') { |f| f.write(code) } unless File.exists?(png_path)
41
+
42
+ "<img src=\"#{@ditaa_root}/#{image_id}.png\" alt=\"Text Diagram\" class=\"img-rounded img-polaroid ditaa\">"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module RDaux
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,40 @@
1
+ require 'posix/spawn'
2
+ require 'sinatra/base'
3
+
4
+ module RDaux
5
+ module Web
6
+ class Application < Sinatra::Base
7
+ attr_reader :current_section
8
+
9
+ enable :logging
10
+
11
+ get '/img/ditaa/:id.png/?' do |id|
12
+ txt_path = settings.public_folder + "/img/ditaa/#{id}.txt"
13
+ png_path = settings.public_folder + "/img/ditaa/#{id}.png"
14
+
15
+ halt(404) unless File.exists?(txt_path)
16
+
17
+ Process::waitpid(POSIX::Spawn.spawn("java", '-jar', settings.ditaa_jar, txt_path, png_path))
18
+ File.unlink(txt_path)
19
+
20
+ send_file(png_path, :status => 201)
21
+ end
22
+
23
+ get '/' do
24
+ erb(:site)
25
+ end
26
+
27
+ def site
28
+ settings.site || halt(500)
29
+ end
30
+
31
+ def markdown
32
+ settings.markdown || halt(500)
33
+ end
34
+
35
+ def render_markdown(markup)
36
+ markdown.render(markup)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,44 @@
1
+ require 'unicorn'
2
+ require 'tmpdir'
3
+ require 'fileutils'
4
+
5
+ module RDaux
6
+ module Web
7
+ class Server
8
+ include Notifier
9
+
10
+ def initialize(app, logger, static_dir, options)
11
+ @app = app
12
+ @logger = logger
13
+ @static_dir = static_dir
14
+ @options = options
15
+
16
+ notifier_initialize
17
+ end
18
+
19
+ def serve(website)
20
+ with_duplicate_static_dir do |dir|
21
+ @app.configure do
22
+ @app.set(:site, website)
23
+ @app.set(:public_folder, dir)
24
+ end
25
+
26
+ Unicorn::HttpServer.new(@app, {
27
+ :listeners => @options.fetch(:bind, 'localhost:8080'),
28
+ :worker_processes => @options.fetch(:workers, 4),
29
+ :logger => @logger
30
+ }).start.join
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def with_duplicate_static_dir
37
+ Dir.mktmpdir('rdaux') do |dir|
38
+ FileUtils.cp_r(@static_dir + '/.', dir)
39
+ yield(dir)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,66 @@
1
+ require 'tilt'
2
+ require 'posix/spawn'
3
+
4
+ module RDaux
5
+ module Web
6
+ class Site
7
+ class Generator
8
+ include Notifier
9
+
10
+ def initialize(markdown, views_dir, static_dir, ditaa_jar, target_dir)
11
+ @markdown = markdown
12
+ @views_dir = views_dir
13
+ @static_dir = static_dir
14
+ @ditaa_jar = ditaa_jar
15
+ @target_dir = target_dir
16
+
17
+ @cached_templates = {}
18
+ notifier_initialize
19
+ end
20
+
21
+ def generate_static(website)
22
+ with_duplicate_static_dir do |dir|
23
+ File.open("#{dir}/index.html", 'w+') do |f|
24
+ f.write(erb(:site, :locals => {:site => website}))
25
+ end
26
+
27
+ Dir.glob("#{dir}/img/ditaa/*.txt").each do |txt_path|
28
+ Process::waitpid(POSIX::Spawn.spawn("java", '-jar', @ditaa_jar, txt_path))
29
+ File.unlink(txt_path)
30
+ end
31
+
32
+ if File.directory?(@target_dir)
33
+ FileUtils.cp_r("#{dir}/.", @target_dir)
34
+ else
35
+ FileUtils.mv(dir, @target_dir)
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def with_duplicate_static_dir
43
+ dir = Dir.mktmpdir('rdaux')
44
+
45
+ FileUtils.cp_r("#{@static_dir}/.", dir)
46
+ yield(dir)
47
+ ensure
48
+ FileUtils.remove_entry_secure(dir) if File.directory?(dir)
49
+ end
50
+
51
+ def erb(view, options)
52
+ unless @cached_templates.has_key?(view)
53
+ @cached_templates[view] = Tilt[:erb].new("#{@views_dir}/#{view}.erb", 1, {
54
+ :default_encoding => 'UTF-8'
55
+ })
56
+ end
57
+ @cached_templates[view].render(self, options.fetch(:locals, {}))
58
+ end
59
+
60
+ def render_markdown(markup)
61
+ @markdown.render(markup)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,86 @@
1
+ module RDaux
2
+ module Web
3
+ class Site
4
+ attr_reader :title, :description, :author, :root
5
+
6
+ class Section
7
+ attr_reader :key
8
+ attr_writer :contents
9
+ attr_accessor :sections
10
+
11
+ def initialize(key)
12
+ @key = key
13
+ end
14
+
15
+ def title
16
+ @title ||= @key.split('-').map(&:capitalize).join(' ')
17
+ end
18
+
19
+ def contents
20
+ @contents && @contents.read
21
+ end
22
+
23
+ def has_children?
24
+ @sections && !@sections.empty?
25
+ end
26
+
27
+ def has_contents?
28
+ !@contents.nil?
29
+ end
30
+ end
31
+
32
+ def initialize(title, description, author, root)
33
+ @title = title
34
+ @description = description
35
+ @author = author
36
+ @root = root
37
+ end
38
+
39
+ def sections
40
+ find_sections(@root)
41
+ end
42
+
43
+ private
44
+
45
+ def find_sections(root)
46
+ root.children.inject({}) do |sections, path|
47
+ unless path.symlink?
48
+ if path.file? && path.extname == '.md'
49
+ key = basename_to_key(base_filename(path))
50
+ section = get_or_create_section(key, sections)
51
+
52
+ section.contents = path
53
+ elsif path.directory? && !path.basename.to_s.start_with?('.')
54
+ key = basename_to_key(base_dirname(path))
55
+ section = get_or_create_section(key, sections)
56
+
57
+ section.sections = find_sections(path)
58
+ end
59
+ end
60
+
61
+ sections
62
+ end
63
+ end
64
+
65
+ def get_or_create_section(key, sections)
66
+ unless sections.has_key?(key)
67
+ sections[key] = Section.new(key)
68
+ end
69
+
70
+ sections[key]
71
+ end
72
+
73
+ def basename_to_key(basename)
74
+ basename.split(/[\_\- ]/).map(&:downcase).join('-')
75
+ end
76
+
77
+ def base_filename(path)
78
+ path.sub_ext('').basename.to_s.sub(/^[0-9]*[\_\-]?/, '')
79
+ end
80
+
81
+ def base_dirname(path)
82
+ path.basename.to_s.sub(/^[0-9]*[\_\-]?/, '')
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,11 @@
1
+ <% for key, section in sections %>
2
+ <a name="<%= base %><%= section.key %>"></a>
3
+ <% if section.has_contents? %>
4
+ <section class="docs-section">
5
+ <%= render_markdown(section.contents) %>
6
+ </section>
7
+ <% end %>
8
+ <% if section.has_children? %>
9
+ <%= erb(:docs, :locals => { :sections => section.sections, :base => section.key + '.' }) %>
10
+ <% end %>
11
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <% for key, section in sections %>
2
+ <li>
3
+ <a href="#<%= base %><%= section.key %>" title="<%= section.title %>"><i class="icon-chevron-right"></i><%= section.title %></a>
4
+ <% if section.has_children? %>
5
+ <ul class="nav nav-list">
6
+ <%= erb(:nav, :locals => { :sections => section.sections, :base => section.key + '.' }) %>
7
+ </ul>
8
+ <% end %>
9
+ </li>
10
+ <% end %>