Skip to content

Commit ca285ef

Browse files
committed
Formalize the mmdoc gem.
1 parent c30dad5 commit ca285ef

23 files changed

+359
-348
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
source "http://rubygems.org"
22

33
gem "middleman", "3.0.0.rc.1"
4-
gem "scribe", path: './scribe'
4+
gem "mmdoc", path: './mmdoc'

Gemfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
2-
remote: ./scribe
2+
remote: ./mmdoc
33
specs:
4-
scribe (0.0.1)
4+
mmdoc (0.0.1)
55
middleman-core (>= 3.0.0.rc.1)
66

77
GEM
@@ -110,4 +110,4 @@ PLATFORMS
110110

111111
DEPENDENCIES
112112
middleman (= 3.0.0.rc.1)
113-
scribe!
113+
mmdoc!

config.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
require 'scribe_extension'
2-
31
activate :automatic_image_sizes
42
activate :relative_assets
5-
activate :scribe
3+
activate :mmdoc
64

75
set :layout, :'_templates/layout'
86
set :github, 'nadarei/mina'

mmdoc/lib/mmdoc.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
module Mmdoc
3+
require 'mmdoc/extension'
4+
5+
autoload :Page, 'mmdoc/page'
6+
autoload :Pages, 'mmdoc/pages'
7+
autoload :PageHelpers, 'mmdoc/page_helpers'
8+
autoload :MiscHelpers, 'mmdoc/misc_helpers'
9+
autoload :IndexBuilder, 'mmdoc/index_builder'
10+
end

mmdoc/lib/mmdoc/extension.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
require 'middleman-core'
2+
3+
module Mmdoc
4+
module Extension
5+
class << self
6+
# Root path
7+
def root(*args)
8+
File.join File.dirname(__FILE__), *args
9+
end
10+
11+
def registered(app, options={})
12+
app.helpers PageHelpers
13+
app.helpers MiscHelpers
14+
end
15+
end
16+
end
17+
end
18+
19+
::Middleman::Extensions.register(:mmdoc) { ::Mmdoc::Extension }

mmdoc/lib/mmdoc/index_builder.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
require 'json'
2+
3+
module Mmdoc
4+
class IndexBuilder
5+
STOPWORDS = %w(a an and are as at be but by for if in into is) +
6+
%w(it no not of on or s such t that the their then) +
7+
%w(there these they this to was will with) +
8+
%w(class method return end if else)
9+
10+
def initialize(pages)
11+
@pages = pages
12+
end
13+
14+
def pages_index
15+
@pages.inject([]) { |h, p|
16+
h << {
17+
:title => p.to_s,
18+
:url => p.path,
19+
:type => p.data['group'],
20+
:parent => (urls.index(p.parent.path) if p.parent?) }
21+
h
22+
}
23+
end
24+
25+
def urls
26+
@urls ||= @pages.map { |p| p.path }
27+
end
28+
29+
def search_index
30+
# Build a search index (hash of hashes of words and page indices)
31+
search_index = Hash.new { |hash, k| hash[k] = Hash.new { |hh, kk| hh[kk] = 0 } }
32+
33+
@pages.each do |p|
34+
i = urls.index(p.path)
35+
36+
if p.to_s.count(' ') > 0
37+
# Partial title match
38+
fuzzy_words(p).each { |word| search_index[word][i] += 30 }
39+
else
40+
# Exact title match
41+
fuzzy_words(p).each { |word| search_index[word][i] += 60 }
42+
end
43+
44+
# Fuzzy title match
45+
fuzzy_words(p).each { |word| search_index[word][i] += 3 }
46+
47+
# Content match
48+
fuzzy_words(p.content).each { |word| search_index[word][i] += 1 }
49+
end
50+
51+
search_index
52+
end
53+
54+
def indices
55+
{
56+
:pages => pages_index,
57+
:search => search_index
58+
}
59+
end
60+
61+
private
62+
63+
# "Hello world" => ["hello", "world" ]
64+
def words(str)
65+
str.to_s.downcase.scan(/[A-Za-z0-9\_]+/)
66+
end
67+
68+
# "hello" => ["he", "hel", "hell", "hello" ]
69+
def fuzzies(str)
70+
str = str.to_s.downcase; (1...str.size).map { |n| str[0..n] }
71+
end
72+
73+
# "hello world" => ["he", "hel", "hell", "hello", "wo", "wor", "worl", "world" ]
74+
def fuzzy_words(str)
75+
words(str).map { |word| fuzzies(word) if word.size > 2 and !STOPWORDS.include?(word) }.compact.flatten
76+
end
77+
end
78+
end

mmdoc/lib/mmdoc/misc_helpers.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module Mmdoc
2+
module MiscHelpers
3+
# Relativize a path
4+
def rel(url)
5+
# Assume abs path
6+
url = url[1..-1]
7+
url = url.squeeze('/')
8+
9+
# Append ../'s
10+
depth = request.path.count('/') - 1
11+
path = '../' * depth + url
12+
13+
path.squeeze('/')
14+
end
15+
end
16+
end

mmdoc/lib/mmdoc/page.rb

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
module Mmdoc
2+
# Page model.
3+
#
4+
# # Lookup by filename
5+
# Page['index.html.haml'] #=> Page instance or nil
6+
# Page.glob 'index.html.*' #=> Array of Pages
7+
#
8+
#
9+
# Metadata:
10+
#
11+
# page.title #=> "Getting started"
12+
# page.basename #=> "getting_started"
13+
# page.path #=> "/getting_started/index.html"
14+
# page.data #=> Hash
15+
# page.template_type #=> "haml"
16+
# page.raw #=> String (raw template data)
17+
# page.content #=> String
18+
#
19+
# Tree traversal:
20+
#
21+
# page.parent? #=> True/false
22+
# page.parent #=> Page, or nil if it's a root
23+
# page.root? #=> True if depth = 0
24+
# page.depth #=> Number, minimum of 0
25+
# page.breadcrumbs #=> Array of ancestors
26+
# page.children #=> Array
27+
# page.siblings #=> Array
28+
#
29+
class Page
30+
attr_reader :path
31+
attr_reader :basename
32+
attr_reader :raw
33+
attr_reader :title
34+
attr_reader :data
35+
attr_reader :template_type
36+
37+
def self.source_path
38+
'source'
39+
end
40+
41+
def self.glob(spec, except=nil)
42+
fullspec = File.join(source_path, spec)
43+
list = Dir[fullspec].map do |f|
44+
Page[ f[(source_path.length + 1)..-1] ]
45+
end.sort
46+
list = list.reject { |p| p.path == except.path } if except
47+
list
48+
end
49+
50+
def self.[](name)
51+
@pages ||= Hash.new
52+
@pages[name] ||= Page.new name
53+
end
54+
55+
def self.roots
56+
Pages.new(glob('*.html.*') + glob('*/index.html.*'))
57+
end
58+
59+
def self.all
60+
list = Array.new
61+
work = lambda { |page|
62+
list << page
63+
page.children.each { |subpage| work[subpage] }
64+
}
65+
roots.each { |page| work[page] }
66+
67+
Pages.new list
68+
end
69+
70+
def sort_index
71+
[ (data['order'] || 99999), @basename ]
72+
end
73+
74+
def <=>(other)
75+
sort_index <=> other.sort_index
76+
end
77+
78+
def self.mm_server
79+
::Middleman::Application.server.inst
80+
end
81+
82+
def self.fm_manager
83+
mm_server.frontmatter_manager
84+
end
85+
86+
def initialize(path)
87+
path = path.gsub(/^(\.?\/)+/, '')
88+
@basepath = (path =~ /^(.*)\.html\.([A-Za-z0-9]*)$/) && $1 || path
89+
@template_type = $2
90+
@basename = File.basename(@basepath)
91+
@dir = File.dirname(path)
92+
@parent_dir = File.dirname(@dir)
93+
@parent_dir = '' if @parent_dir == '.'
94+
@full_path = File.join(self.class.source_path, path)
95+
@path = "/#{@basepath}.html"
96+
@data, @raw = self.class.fm_manager.data(path)
97+
@title = data['title'] || @basename
98+
end
99+
100+
def content
101+
# TODO: Parse
102+
@content ||= @raw
103+
end
104+
105+
def to_s
106+
title
107+
end
108+
109+
def parent
110+
if @basename == 'index'
111+
# ./tasks/index.html is a child of ./index.html
112+
Page.glob(File.join(@parent_dir, 'index.html.*'), self).first
113+
else
114+
# ./tasks/git_clone.html is a child of ./tasks/index.html,
115+
# or ./tasks.html
116+
Page.glob(File.join(@dir, 'index.html.*'), self).first ||
117+
Page.glob(File.join(@parent_dir, "#{File.basename(@dir)}.html.*"), self).first
118+
end
119+
end
120+
121+
def parent?
122+
!! parent
123+
end
124+
125+
def root?
126+
! parent?
127+
end
128+
129+
def depth
130+
root ? 0 : parent.depth + 1
131+
end
132+
133+
def breadcrumbs
134+
if parent?
135+
[ *parent.breadcrumbs, self ]
136+
else
137+
[ self ]
138+
end
139+
end
140+
141+
def children
142+
list = if @basename == 'index' && !root?
143+
(Page.glob(File.join(@dir, '*.html.*'), self) +
144+
Page.glob(File.join(@dir, '*', 'index.html.*'), self)).sort
145+
else
146+
(Page.glob(File.join(@dir, @basepath, '*.html.*'), self) +
147+
Page.glob(File.join(@dir, @basepath, '*', 'index.html.*'), self)).sort
148+
end
149+
150+
Pages.new list
151+
end
152+
153+
def siblings
154+
if @basename == 'index' && !root?
155+
list = (Page.glob(File.join(@parent_dir, '*.html.*')) +
156+
Page.glob(File.join(@parent_dir, '*', 'index.html.*'))).sort
157+
else
158+
list = Page.glob(File.join(@dir, '*.html.*'))
159+
list = list.reject { |p| p.basename == 'index' }
160+
list += Page.glob(File.join(@dir, '*', 'index.html.*'))
161+
list = list.sort
162+
end
163+
Pages.new list
164+
end
165+
end
166+
end

mmdoc/lib/mmdoc/page_helpers.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
module Mmdoc
2+
module PageHelpers
3+
# Returns the current page (a Page instance).
4+
def here
5+
Page.glob(request.path + '.*').first
6+
end
7+
8+
def all_pages
9+
Page.all
10+
end
11+
12+
def roots
13+
Page.roots
14+
end
15+
16+
# Returns the main page.
17+
def index
18+
Page.glob('index.html.*').first
19+
end
20+
21+
# Returns the name of the site.
22+
def site_name
23+
index ? index.title : "Site"
24+
end
25+
26+
# Returns groups for the side nav.
27+
# Returns a hash with keys as group names, values as Page arrays (Pages).
28+
def nav_groups
29+
children = here.children.any? ? here.children : here.siblings
30+
groups = children.groups
31+
groups[here.title] = groups.delete("") if groups[""]
32+
groups
33+
end
34+
35+
# Breadcrumbs for side nav.
36+
def nav_breadcrumbs
37+
parent = (here.children.any? ? here : here.parent) || here
38+
parent.breadcrumbs
39+
end
40+
41+
# Returns a CSS class name for a Page.
42+
def item_class(page)
43+
[
44+
('active' if page.path == here.path),
45+
('more' if page.children.any?)
46+
].compact.join(' ')
47+
end
48+
end
49+
end

mmdoc/lib/mmdoc/pages.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module Mmdoc
2+
class Pages < ::Array
3+
def groups(attribute='group')
4+
group_by { |p| p.data[attribute.to_s] }
5+
end
6+
7+
def indices
8+
i = IndexBuilder.new(self)
9+
i.indices
10+
end
11+
end
12+
end

scribe/scribe.gemspec renamed to mmdoc/mmdoc.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Gem::Specification.new do |s|
2-
s.name = "scribe"
2+
s.name = "mmdoc"
33
s.version = "0.0.1"
44
s.summary = ""
55
s.description = ""

0 commit comments

Comments
 (0)