docter 1.0.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/CHANGELOG +2 -0
- data/LICENSE +202 -0
- data/README +1 -0
- data/lib/docter/collection.rb +307 -0
- data/lib/docter/common.rb +303 -0
- data/lib/docter/page.rb +288 -0
- data/lib/docter/rake.rb +25 -0
- data/lib/docter/server.rb +109 -0
- data/lib/docter/template.rb +183 -0
- data/lib/docter/ultraviolet.rb +26 -0
- data/lib/docter.rb +33 -0
- metadata +99 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
module Docter
|
2
|
+
|
3
|
+
class MongrelHandler < Mongrel::HttpHandler
|
4
|
+
|
5
|
+
attr_reader :collection, :template, :options
|
6
|
+
|
7
|
+
def initialize(collection, template, options = {})
|
8
|
+
@collection, @template, @options = collection, template, options || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def resources()
|
12
|
+
@resources ||= Resources.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def process(request, response)
|
16
|
+
# Absolute path to relative path, default index.
|
17
|
+
path = request.params[Mongrel::Const::PATH_INFO].sub(/^\//, "")
|
18
|
+
path = "index.html" if path.empty?
|
19
|
+
|
20
|
+
begin
|
21
|
+
if file = template.find(path)
|
22
|
+
# Files served directly from disk (CSS, images, RDoc, etc).
|
23
|
+
since = request.params[Mongrel::Const::HTTP_IF_MODIFIED_SINCE]
|
24
|
+
if since && Time.parse(since) >= File.stat(file).mtime
|
25
|
+
response.status = 304
|
26
|
+
response.finished
|
27
|
+
else
|
28
|
+
puts "Serving #{path}" if verbose
|
29
|
+
response.start(200) do |head,out|
|
30
|
+
head[Mongrel::Const::LAST_MODIFIED] = CGI.rfc1123_date(File.stat(file).mtime)
|
31
|
+
out.write File.read(file)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
elsif options[:one_page] && path == "index.html"
|
35
|
+
puts "Serving #{path}" if verbose
|
36
|
+
response.start(200) do |head,out|
|
37
|
+
head[Mongrel::Const::CONTENT_TYPE] = "text/html"
|
38
|
+
out.write collection.render(template, options)
|
39
|
+
end
|
40
|
+
elsif page = collection.page(path)
|
41
|
+
puts "Serving #{path}" if verbose
|
42
|
+
response.start(200) do |head,out|
|
43
|
+
head[Mongrel::Const::CONTENT_TYPE] = "text/html"
|
44
|
+
out.write collection.render(template, page, options)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
response.start 404 do |head, out|
|
48
|
+
head[Mongrel::Const::CONTENT_TYPE] = "text/html"
|
49
|
+
out.write "<h1>Did you accidentally rm #{path}, or did you forget to :w it?</h1>"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
rescue Exception=>error
|
53
|
+
response.start(500) do |head, out|
|
54
|
+
head["Content-Type"] = "text/plain"
|
55
|
+
error = ["#{error.class}: #{error}", error.backtrace.join("\n")]
|
56
|
+
out.puts *error
|
57
|
+
puts *error
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
class Server
|
66
|
+
|
67
|
+
PORT = 3000
|
68
|
+
|
69
|
+
attr_reader :collection, :template
|
70
|
+
attr_accessor :port, :options
|
71
|
+
|
72
|
+
def initialize(collection, template, *args)
|
73
|
+
@collection, @template = collection, template
|
74
|
+
@options = Hash === args.last ? args.pop.clone : {}
|
75
|
+
args.each { |arg| @options[arg.to_sym] = true }
|
76
|
+
@port = @options[:port] || PORT
|
77
|
+
end
|
78
|
+
|
79
|
+
def start(wait = true)
|
80
|
+
puts "Starting Mongrel on port #{port}"
|
81
|
+
@mongrel = Mongrel::HttpServer.new("0.0.0.0", port, 4)
|
82
|
+
@mongrel.register("/", MongrelHandler.new(collection, template, options))
|
83
|
+
if wait
|
84
|
+
@mongrel.run.join rescue nil
|
85
|
+
else
|
86
|
+
@mongrel.run
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def stop()
|
91
|
+
puts "Stopping Mongrel"
|
92
|
+
@mongrel.stop if @mongrel
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
class Collection
|
99
|
+
|
100
|
+
def serve(template, *args)
|
101
|
+
options = Hash === args.last ? args.pop.clone : {}
|
102
|
+
options[:port] = args.shift if Integer === args.first
|
103
|
+
args.each { |arg| options[arg.to_sym] = true }
|
104
|
+
Server.new(self, template, options).start
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module Docter
|
2
|
+
|
3
|
+
# A template for formatting pages. The template is parsed once and processed using each Page to
|
4
|
+
# produce an HTML document. Processing can rely on various attributes of the Scope.
|
5
|
+
#
|
6
|
+
# A template will require additional files like CSS stylesheets, images, JavaScript, etc.
|
7
|
+
# You can associate additional resources with the template using #include and #exclude.
|
8
|
+
# These resources are copied to the destination directory when generating output, and
|
9
|
+
# served from the integrated Web server.
|
10
|
+
class Template < Resource::Base
|
11
|
+
|
12
|
+
module ContextMethods
|
13
|
+
|
14
|
+
include HTML
|
15
|
+
|
16
|
+
def collect_links(content, mark = false)
|
17
|
+
@links ||= []
|
18
|
+
content.gsub(regexp_element("a")) do |link|
|
19
|
+
url = $3 if link =~ regexp_attribute("href")
|
20
|
+
if url =~ /^\w+:/
|
21
|
+
unless index = @links.index(url)
|
22
|
+
index = @links.size
|
23
|
+
@links << [inner_text_from(link), url]
|
24
|
+
end
|
25
|
+
mark ? "#{link}<sup>[#{index + 1}]</sup>" : link
|
26
|
+
else
|
27
|
+
link
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def footnote_links(cls = nil)
|
33
|
+
list = @links.map { |link| %{<dt>#{link.first}</dt><dd><a href="#{link.last}">#{link.last}</a></dd>} }
|
34
|
+
%{<dl class="#{cls}">#{list.join}</dl>}
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
# Options passed when creating the template.
|
40
|
+
attr_reader :options
|
41
|
+
|
42
|
+
def initialize(*args)
|
43
|
+
super
|
44
|
+
@sources = FileList[]
|
45
|
+
end
|
46
|
+
|
47
|
+
# :call-seq:
|
48
|
+
# render(context) => html
|
49
|
+
#
|
50
|
+
# Renders this template. The template is processed using a context that provides the template
|
51
|
+
# with access to various methods and variables, e.g. the page being rendered, or the ToC.
|
52
|
+
#
|
53
|
+
# There are two ways to supply a context:
|
54
|
+
# * Hash -- Each key becomes a method you can call on the hash to obtain it's value.
|
55
|
+
# The hash will include a method called template that returns the template itself.
|
56
|
+
# * Object -- Creates a context object that delegates all method calls to this object,
|
57
|
+
# and adds the method template that returns the template itself.
|
58
|
+
def render(context)
|
59
|
+
load
|
60
|
+
if Hash === context
|
61
|
+
hash = context.merge(:template=>self)
|
62
|
+
struct = Struct.new(*hash.keys).new(*hash.values)
|
63
|
+
struct.class.send :include, ContextMethods
|
64
|
+
@process.call struct
|
65
|
+
else
|
66
|
+
delegate = Class.new
|
67
|
+
context.class.instance_methods.each { |method| delegate.send :define_method, method, &context.method(method) }
|
68
|
+
context.class.send :include, ContextMethods
|
69
|
+
delegate.send(:define_method, :template) { self }
|
70
|
+
@process.call delegate.new
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# :call-seq:
|
75
|
+
# include(*paths) => self
|
76
|
+
#
|
77
|
+
# Adds files and directories included in the generated output.
|
78
|
+
def include(*paths)
|
79
|
+
@sources.include *paths.flatten
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
alias :add :include
|
84
|
+
|
85
|
+
# :call-seq:
|
86
|
+
# exclude(*paths) => self
|
87
|
+
#
|
88
|
+
# Excludes files or directories from the generated output.
|
89
|
+
def exclude(*paths)
|
90
|
+
@sources.exclude *paths.flatten
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
# :call-seq:
|
95
|
+
# find(path) => file
|
96
|
+
#
|
97
|
+
# Returns the location of a file on disk based on the request path.
|
98
|
+
#
|
99
|
+
# For example:
|
100
|
+
# template.include("html/index.html", "images", "css/*")
|
101
|
+
# map.find("index.html") => "html/index.html"
|
102
|
+
# map.find("images/logo.png") => "images/logo.png"
|
103
|
+
# map.find("fancy.css") => "css/fancy.css"
|
104
|
+
def find(path)
|
105
|
+
@sources.inject(nil) do |found, file|
|
106
|
+
break found if found
|
107
|
+
if File.directory?(file)
|
108
|
+
base = File.dirname(file) + "/"
|
109
|
+
FileList["#{file}/**/*"].find { |file| file.sub(base, "") == path }
|
110
|
+
else
|
111
|
+
file if File.basename(file) == path
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# :call-seq:
|
117
|
+
# copy_resources(to_dir)
|
118
|
+
#
|
119
|
+
# Copy resource files to the destination directory.
|
120
|
+
def copy_resources(to_dir)
|
121
|
+
mkpath to_dir
|
122
|
+
@sources.each do |file|
|
123
|
+
if File.directory?(file)
|
124
|
+
base = File.dirname(file) + "/"
|
125
|
+
FileList[File.join(file, "**/*")].each do |file|
|
126
|
+
target = File.join(to_dir, file.sub(base, ""))
|
127
|
+
mkpath File.dirname(target)
|
128
|
+
cp file, target
|
129
|
+
end
|
130
|
+
else
|
131
|
+
cp file, to_dir
|
132
|
+
end
|
133
|
+
end
|
134
|
+
touch to_dir # For Rake dependency management.
|
135
|
+
end
|
136
|
+
|
137
|
+
# :call-seq:
|
138
|
+
#
|
139
|
+
# Returns a list of dependencies (resource files, the template file, etc). Useful when creating
|
140
|
+
# a Rake task based on this template.
|
141
|
+
def dependencies()
|
142
|
+
@sources.map { |path| File.directory?(path) ? FileList[path, File.join(path, "**/*")] : path }.flatten +
|
143
|
+
[@filename].compact
|
144
|
+
end
|
145
|
+
|
146
|
+
protected
|
147
|
+
|
148
|
+
if defined?(::Haml)
|
149
|
+
def create_from_haml(content, options)
|
150
|
+
@options = options
|
151
|
+
template = Haml::Engine.new(content, :filename=>@filename)
|
152
|
+
@process = lambda { |context| template.render(context) }
|
153
|
+
end
|
154
|
+
else
|
155
|
+
def create_from_haml(content, options)
|
156
|
+
fail "You need to install HAML first:\n gem install haml"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def create_from_erb(content, options)
|
161
|
+
@options = options
|
162
|
+
template = ERB.new(content)
|
163
|
+
@process = lambda { |context| template.result(context.instance_eval { binding }) }
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
class << self
|
170
|
+
|
171
|
+
# :call-seq:
|
172
|
+
# template(filename, options?)
|
173
|
+
# template(format, content, options?)
|
174
|
+
#
|
175
|
+
# The first form loads the template from the specified filename. The second creates the template from
|
176
|
+
# the content string based on the specified format.
|
177
|
+
def template(*args)
|
178
|
+
Template.new(*args)
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Docter
|
2
|
+
|
3
|
+
SYNTAX_THEME = "eiffel"
|
4
|
+
SYNTAX_STYLESHEET = "css/#{SYNTAX_THEME}.css"
|
5
|
+
SYNTAX_MAP = { "sh"=>"shell-unix-generic" }
|
6
|
+
|
7
|
+
filter_for :syntax do |html|
|
8
|
+
html.gsub(HTML.regexp_element("pre")) do |pre|
|
9
|
+
attributes, code = $2, $3
|
10
|
+
if attributes[HTML.regexp_attribute("class")]
|
11
|
+
classes = $3.split(/\s+/)
|
12
|
+
lang = classes.first
|
13
|
+
end
|
14
|
+
if lang == "text"
|
15
|
+
Uv.parse(CGI.unescapeHTML(code), "xhtml", "plain_text", false, SYNTAX_THEME).
|
16
|
+
gsub(URI.regexp) { |uri| uri =~ /^http(s?):\/\// ? %{<a href="#{uri}">#{uri}</a>} : uri }
|
17
|
+
elsif lang
|
18
|
+
syntax = SYNTAX_MAP[lang] || (Uv.syntaxes.include?(lang) ? lang : "plain_text")
|
19
|
+
Uv.parse(CGI.unescapeHTML(code), "xhtml", syntax || "plain_text", classes.include?("lines"), SYNTAX_THEME)
|
20
|
+
else
|
21
|
+
Uv.parse(CGI.unescapeHTML(code), "xhtml", "plain_text", false, SYNTAX_THEME)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/lib/docter.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# &:symbol goodness.
|
2
|
+
require "facet/symbol/to_proc"
|
3
|
+
# blank? on string and nil
|
4
|
+
require "facet/string/blank"
|
5
|
+
require "facet/nilclass/blank"
|
6
|
+
# x.in?(y) is better than y.include?(x)
|
7
|
+
require "facet/string/starts_with"
|
8
|
+
require "facets/core/kernel/tap"
|
9
|
+
require "facet/kernel/__DIR__"
|
10
|
+
|
11
|
+
module Docter
|
12
|
+
VERSION = "1.0.0".freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift __DIR__
|
16
|
+
|
17
|
+
require "cgi"
|
18
|
+
require "erb"
|
19
|
+
# All these Gems are optional.
|
20
|
+
["redcloth", "haml", "mongrel", "uv"].each do |gem|
|
21
|
+
begin
|
22
|
+
require gem
|
23
|
+
rescue LoadError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require "docter/common.rb"
|
28
|
+
require "docter/page.rb"
|
29
|
+
require "docter/template.rb"
|
30
|
+
require "docter/collection.rb"
|
31
|
+
require "docter/server.rb" if defined?(Mongrel)
|
32
|
+
require "docter/rake.rb" if defined?(Rake)
|
33
|
+
require "docter/ultraviolet.rb" if defined?(Uv)
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.2
|
3
|
+
specification_version: 1
|
4
|
+
name: docter
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 1.0.0
|
7
|
+
date: 2007-07-03 00:00:00 -07:00
|
8
|
+
summary: We has docs
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: arkin@intalio.com
|
12
|
+
homepage: http://docter.rubyforge.org
|
13
|
+
rubyforge_project: buildr
|
14
|
+
description:
|
15
|
+
autorequire: docter.rb
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Assaf Arkin
|
31
|
+
files:
|
32
|
+
- lib/docter
|
33
|
+
- lib/docter/server.rb
|
34
|
+
- lib/docter/template.rb
|
35
|
+
- lib/docter/collection.rb
|
36
|
+
- lib/docter/rake.rb
|
37
|
+
- lib/docter/ultraviolet.rb
|
38
|
+
- lib/docter/page.rb
|
39
|
+
- lib/docter/common.rb
|
40
|
+
- lib/docter.rb
|
41
|
+
- CHANGELOG
|
42
|
+
- README
|
43
|
+
- LICENSE
|
44
|
+
test_files: []
|
45
|
+
|
46
|
+
rdoc_options:
|
47
|
+
- --title
|
48
|
+
- Docter -- We has docs
|
49
|
+
- --main
|
50
|
+
- README
|
51
|
+
- --line-numbers
|
52
|
+
- -inline-source
|
53
|
+
extra_rdoc_files:
|
54
|
+
- README
|
55
|
+
- CHANGELOG
|
56
|
+
- LICENSE
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
dependencies:
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: facets
|
66
|
+
version_requirement:
|
67
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ~>
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "1.8"
|
72
|
+
version:
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: RedCloth
|
75
|
+
version_requirement:
|
76
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ~>
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "3.0"
|
81
|
+
version:
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: haml
|
84
|
+
version_requirement:
|
85
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "1.5"
|
90
|
+
version:
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: mongrel
|
93
|
+
version_requirement:
|
94
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ~>
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "1.0"
|
99
|
+
version:
|