smeagol 0.5.9 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +80 -0
- data/.yardopts +9 -0
- data/HISTORY.md +147 -0
- data/LICENSE.txt +30 -0
- data/README.md +132 -26
- data/bin/smeagol +9 -39
- data/bin/smeagol-init +3 -0
- data/bin/smeagol-preview +4 -0
- data/bin/smeagol-serve +4 -0
- data/bin/smeagol-update +4 -0
- data/bin/smeagold +1 -4
- data/lib/smeagol.rb +77 -6
- data/lib/smeagol/app.rb +121 -75
- data/lib/smeagol/cache.rb +43 -24
- data/lib/smeagol/cli.rb +166 -0
- data/lib/smeagol/config.rb +154 -0
- data/lib/smeagol/console.rb +369 -0
- data/lib/smeagol/controller.rb +275 -0
- data/lib/smeagol/core_ext.rb +12 -0
- data/lib/smeagol/gollum/blob_entry.rb +17 -0
- data/lib/smeagol/gollum/file.rb +44 -0
- data/lib/smeagol/gollum/page.rb +47 -0
- data/lib/smeagol/gollum/wiki.rb +31 -0
- data/lib/smeagol/helpers/rss.rb +96 -0
- data/lib/smeagol/helpers/toc.rb +69 -0
- data/lib/smeagol/public/assets/smeagol/gollum.css +716 -0
- data/lib/smeagol/public/{smeagol → assets/smeagol}/html5.js +0 -0
- data/lib/smeagol/public/{smeagol → assets/smeagol}/pygment.css +0 -0
- data/lib/smeagol/public/assets/smeagol/template.css +631 -0
- data/lib/smeagol/repository.rb +85 -0
- data/lib/smeagol/settings.rb +302 -0
- data/lib/smeagol/templates/layouts/page.mustache +19 -0
- data/lib/smeagol/templates/layouts/versions.mustache +16 -0
- data/lib/smeagol/templates/partials/footer.mustache +17 -0
- data/lib/smeagol/templates/partials/header.mustache +47 -0
- data/lib/smeagol/templates/settings.yml +64 -0
- data/lib/smeagol/version.rb +1 -1
- data/lib/smeagol/views/base.rb +188 -27
- data/lib/smeagol/views/form.rb +56 -0
- data/lib/smeagol/views/page.rb +126 -25
- data/lib/smeagol/views/post.rb +51 -0
- data/lib/smeagol/views/versions.rb +20 -6
- data/lib/smeagol/wiki.rb +25 -45
- data/test/helper.rb +72 -8
- data/test/test_app.rb +57 -0
- data/test/test_cache.rb +10 -11
- data/test/test_init.rb +27 -0
- data/test/test_update.rb +20 -0
- data/test/test_wiki.rb +13 -10
- metadata +142 -216
- data/bin/smeagol-static +0 -115
- data/lib/file.rb +0 -10
- data/lib/smeagol/hash.rb +0 -13
- data/lib/smeagol/option_parser.rb +0 -138
- data/lib/smeagol/public/smeagol/main.css +0 -234
- data/lib/smeagol/templates/page.mustache +0 -58
- data/lib/smeagol/templates/versions.mustache +0 -50
- data/test/test_file.rb +0 -12
- data/test/test_hash.rb +0 -18
@@ -0,0 +1,275 @@
|
|
1
|
+
module Smeagol
|
2
|
+
|
3
|
+
# Shared controller.
|
4
|
+
class Controller
|
5
|
+
|
6
|
+
# Initialize new Controller instance.
|
7
|
+
#
|
8
|
+
# wiki - Gollum::Wiki
|
9
|
+
#
|
10
|
+
def initialize(wiki)
|
11
|
+
@wiki = wiki
|
12
|
+
#@views = Hash.new{ |h,k| h[k]={} }
|
13
|
+
#@media = Hash.new{ |h,k| h[k]={} }
|
14
|
+
#@preloaded = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Access to Gollum::Wiki.
|
18
|
+
attr :wiki
|
19
|
+
|
20
|
+
# Public: The Smeagol wiki settings. These can be found in the _settings.yml
|
21
|
+
# file at the root of the repository.
|
22
|
+
# This method caches the settings for all subsequent calls.
|
23
|
+
#
|
24
|
+
# TODO: Should settings be coming from current file or from repo version?
|
25
|
+
# This can be tricky. Right now they come from current file, but
|
26
|
+
# In future we probably may need to split this into two config files.
|
27
|
+
# One that comes from version and one that is current.
|
28
|
+
#
|
29
|
+
# Returns a Settings object.
|
30
|
+
def settings
|
31
|
+
@settings ||= Settings.load(wiki.path)
|
32
|
+
end
|
33
|
+
|
34
|
+
#def view(wiki_file, version='master')
|
35
|
+
# @views[version][wiki_file] ||= create_view(wiki_file, version)
|
36
|
+
#end
|
37
|
+
|
38
|
+
# Lookup view by wiki file and version.
|
39
|
+
def view(wiki_file, version='master')
|
40
|
+
case wiki_file
|
41
|
+
when Gollum::Page
|
42
|
+
if wiki_file.post?
|
43
|
+
Views::Post.new(self, wiki_file, version)
|
44
|
+
else
|
45
|
+
Views::Page.new(self, wiki_file, version)
|
46
|
+
end
|
47
|
+
when Gollum::File
|
48
|
+
if wiki_file.extname == '.mustache'
|
49
|
+
Views::Form.new(self, wiki_file, version)
|
50
|
+
else
|
51
|
+
nil #Views::Raw.new(self, wiki_file, version)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a list of filtered wiki files.
|
57
|
+
def wiki_files
|
58
|
+
filter(wiki.files + wiki.pages)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Collect a list of all views.
|
62
|
+
def views(version='master')
|
63
|
+
list = []
|
64
|
+
wiki_files.each do |file|
|
65
|
+
view = view(file, version)
|
66
|
+
list << view if view
|
67
|
+
end
|
68
|
+
list
|
69
|
+
end
|
70
|
+
|
71
|
+
# Collect a list of all posts.
|
72
|
+
def posts(version='master')
|
73
|
+
list = []
|
74
|
+
wiki_files.each do |file|
|
75
|
+
next unless Gollum::Page === file && file.post?
|
76
|
+
list << view(file, version)
|
77
|
+
end
|
78
|
+
list
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns Array of views.
|
82
|
+
#def views(version='master')
|
83
|
+
# wiki_files.map{ |wf| view(wf) }.compact
|
84
|
+
#end
|
85
|
+
|
86
|
+
# Media are raw files that simply need to be passed long.
|
87
|
+
# Unlike assets they are versioned, but they do not need
|
88
|
+
# to be rendered via a view.
|
89
|
+
#def media(version='master')
|
90
|
+
# preload(version)
|
91
|
+
# @media[version].values
|
92
|
+
#end
|
93
|
+
|
94
|
+
# Collect a list of all files in assets directory.
|
95
|
+
# These files are never versioned.
|
96
|
+
def assets
|
97
|
+
files = collect_files(wiki.path, 'assets')
|
98
|
+
filter(files)
|
99
|
+
end
|
100
|
+
|
101
|
+
=begin
|
102
|
+
#
|
103
|
+
def preload(version='master')
|
104
|
+
@preloaded[version] ||= (
|
105
|
+
filtered_files.each do |file|
|
106
|
+
if file.extname == '.mustache'
|
107
|
+
view(file, version)
|
108
|
+
else
|
109
|
+
@media[version][file] = file
|
110
|
+
end
|
111
|
+
end
|
112
|
+
filtered_pages.each do |page|
|
113
|
+
view(page, version)
|
114
|
+
end
|
115
|
+
true
|
116
|
+
)
|
117
|
+
end
|
118
|
+
=end
|
119
|
+
|
120
|
+
|
121
|
+
#
|
122
|
+
#def filtered_files
|
123
|
+
# @filtered_files ||= filter(wiki.files)
|
124
|
+
#end
|
125
|
+
|
126
|
+
#
|
127
|
+
#def filtered_pages
|
128
|
+
# @filtered_pages ||= filter(wiki.pages)
|
129
|
+
#end
|
130
|
+
|
131
|
+
#
|
132
|
+
def collect_files(base, offset)
|
133
|
+
list = []
|
134
|
+
dir = ::File.join(base, offset)
|
135
|
+
return list unless File.directory?(dir)
|
136
|
+
|
137
|
+
::Dir.entries(dir).each do |path|
|
138
|
+
next if path == '.' or path == '..'
|
139
|
+
subdir = ::File.join(dir, path)
|
140
|
+
if ::File.directory?(subdir)
|
141
|
+
sublist = collect_files(base, File.join(offset, path))
|
142
|
+
list.concat(sublist)
|
143
|
+
else
|
144
|
+
list << ::File.join(offset, path)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
list
|
148
|
+
end
|
149
|
+
|
150
|
+
# Filter files according to settings `include` and `exclude` fields.
|
151
|
+
# Selection block can be given to further filter the list.
|
152
|
+
#
|
153
|
+
# files - Array of wiki files to be filtered.
|
154
|
+
#
|
155
|
+
# Returns [Array<String>].
|
156
|
+
def filter(files, &selection)
|
157
|
+
result = []
|
158
|
+
files.map do |file|
|
159
|
+
path = (String === file ? file : file.path)
|
160
|
+
unless settings.include.any?{ |x| File.fnmatch?(x, path) }
|
161
|
+
# TODO: If we enforce the use of underscore the we would
|
162
|
+
# not need to filter out settings, partials and static locations.
|
163
|
+
# exclude settings file
|
164
|
+
next if path == Settings::FILE
|
165
|
+
# # exlcude assets
|
166
|
+
# next if path.index('assets') == 0
|
167
|
+
# exclude template directory (TODO: future version may allow this)
|
168
|
+
next if path.index(settings.partials) == 0
|
169
|
+
# exclude any files starting with `.` or `_`
|
170
|
+
next if path.split('/').any? do |x|
|
171
|
+
x.start_with?('_') or x.start_with?('.')
|
172
|
+
end
|
173
|
+
# exclude any files specifically exluded by settings
|
174
|
+
next if settings.exclude.any? do |x|
|
175
|
+
::File.fnmatch?(x, path) ||
|
176
|
+
x.end_with?('/') && path.index(x) == 0
|
177
|
+
end
|
178
|
+
end
|
179
|
+
result << file
|
180
|
+
end
|
181
|
+
result = result.select(&selection) if selection
|
182
|
+
result
|
183
|
+
end
|
184
|
+
|
185
|
+
# Render wiki file.
|
186
|
+
#
|
187
|
+
# wiki_file - Gollum::Page or Gollum::File.
|
188
|
+
# version - Commit id, branch or tag.
|
189
|
+
#
|
190
|
+
# Returns [String].
|
191
|
+
def render(wiki_file, version='master')
|
192
|
+
view = view(wiki_file, version)
|
193
|
+
render_view(view)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Render view.
|
197
|
+
#
|
198
|
+
# view - Views::Base subclass.
|
199
|
+
#
|
200
|
+
# Returns [String].
|
201
|
+
def render_view(view)
|
202
|
+
if view.layout
|
203
|
+
content = Mustache.render(view.layout, view)
|
204
|
+
else
|
205
|
+
content = view.content
|
206
|
+
end
|
207
|
+
|
208
|
+
return content
|
209
|
+
end
|
210
|
+
|
211
|
+
## For static sites we cannot depend on the web server to default a link
|
212
|
+
## to a directory to the index.html file within it. So we need to append
|
213
|
+
## index.html to any href links for which we have wiki pages.
|
214
|
+
## This is not a prefect solution, but there may not be a better one.
|
215
|
+
##
|
216
|
+
#def index_directory_hrefs(html)
|
217
|
+
# html.gsub(/href=\"(.*)\"/) do |match|
|
218
|
+
# link = "#{$1}/index.html"
|
219
|
+
# if @pages[link] #if File.directory?(File.join(current_directory, $1))
|
220
|
+
# "href=\"#{link}\""
|
221
|
+
# else
|
222
|
+
# match # no change
|
223
|
+
# end
|
224
|
+
# end
|
225
|
+
# end
|
226
|
+
|
227
|
+
# Public: Get a list of plugin files.
|
228
|
+
#
|
229
|
+
# Returns Array of plugin files.
|
230
|
+
#def plugins
|
231
|
+
# files.map do |f|
|
232
|
+
# File.fnmatch?('_plugins/*.rb', f.path)
|
233
|
+
# end
|
234
|
+
#end
|
235
|
+
|
236
|
+
=begin
|
237
|
+
# Collect list of pages.
|
238
|
+
def pages
|
239
|
+
@pages ||= filtered_pages.select{ |p| !p.post? }
|
240
|
+
end
|
241
|
+
|
242
|
+
# Collect list of posts.
|
243
|
+
def posts
|
244
|
+
@posts ||= filtered_pages.select{ |p| p.post? }
|
245
|
+
end
|
246
|
+
|
247
|
+
# Collect list of non-page files to be rendered.
|
248
|
+
def files
|
249
|
+
@files ||= filtered_files.select{ |f| f.extname == '.mustache' }
|
250
|
+
end
|
251
|
+
|
252
|
+
# Collect list of raw asset files.
|
253
|
+
def assets
|
254
|
+
@assets ||= filtered_files.select{ |f| f.extname != '.mustache' }
|
255
|
+
end
|
256
|
+
|
257
|
+
#
|
258
|
+
def page_views(version='master')
|
259
|
+
@page_views ||= pages.map{ |page| Views::Page.new(page, version) }
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
def post_views(version='master')
|
264
|
+
@post_views ||= posts.map{ |post| Views::Post.new(post, version) }
|
265
|
+
end
|
266
|
+
|
267
|
+
#
|
268
|
+
def file_views(version='master')
|
269
|
+
@file_views ||= files.map{ |post| Views::Template.new(file, versionb) }
|
270
|
+
end
|
271
|
+
=end
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Gollum
|
2
|
+
class BlobEntry
|
3
|
+
|
4
|
+
# Gets a File instance for this blob.
|
5
|
+
#
|
6
|
+
# wiki - Gollum::Wiki instance for the Gollum::Page
|
7
|
+
#
|
8
|
+
# Returns a Gollum::File instance.
|
9
|
+
def file(wiki, commit)
|
10
|
+
blob = self.blob(wiki.repo)
|
11
|
+
file = wiki.file_class.new(wiki).populate(blob, self.dir)
|
12
|
+
file.version = commit
|
13
|
+
file
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Gollum
|
2
|
+
class File
|
3
|
+
|
4
|
+
#alias :filename :name
|
5
|
+
|
6
|
+
# Populate the Page with information from the Blob.
|
7
|
+
#
|
8
|
+
# blob - The Grit::Blob that contains the info.
|
9
|
+
# path - The String directory path of the page file.
|
10
|
+
#
|
11
|
+
# Returns the populated Gollum::Page.
|
12
|
+
def populate(blob, path=nil)
|
13
|
+
@blob = blob
|
14
|
+
@path = "#{path}/#{blob.name}"[1..-1]
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
attr_reader :wiki
|
20
|
+
|
21
|
+
# Public: The current version of the page.
|
22
|
+
#
|
23
|
+
# Returns the Grit::Commit.
|
24
|
+
attr_accessor :version
|
25
|
+
|
26
|
+
# Recent addition to Gollum.
|
27
|
+
alias filename name unless method_defined?(:filename)
|
28
|
+
|
29
|
+
# Public: The title will be constructed from the
|
30
|
+
# filename by stripping the extension and replacing any dashes with
|
31
|
+
# spaces.
|
32
|
+
#
|
33
|
+
# Returns the fully sanitized String title.
|
34
|
+
def title
|
35
|
+
Sanitize.clean(name).strip
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
def extname
|
40
|
+
::File.extname(path)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Gollum
|
2
|
+
|
3
|
+
class Page
|
4
|
+
def post?
|
5
|
+
/^(\d\d\d\d-\d\d-\d\d)/.match(filename)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Markup
|
10
|
+
|
11
|
+
# Attempt to process the tag as a page link tag.
|
12
|
+
#
|
13
|
+
# tag - The String tag contents (the stuff inside the double
|
14
|
+
# brackets).
|
15
|
+
#
|
16
|
+
# Returns the String HTML if the tag is a valid page link tag or nil
|
17
|
+
# if it is not.
|
18
|
+
def process_page_link_tag(tag)
|
19
|
+
parts = tag.split('|')
|
20
|
+
parts.reverse! if @format == :mediawiki
|
21
|
+
|
22
|
+
name, page_name = *parts.compact.map(&:strip)
|
23
|
+
cname = @wiki.page_class.cname(page_name || name)
|
24
|
+
|
25
|
+
if name =~ %r{^https?://} && page_name.nil?
|
26
|
+
%{<a href="#{name}">#{name}</a>}
|
27
|
+
else
|
28
|
+
presence = "absent"
|
29
|
+
link_name = cname
|
30
|
+
page, extra = find_page_from_name(cname)
|
31
|
+
if page
|
32
|
+
link_name = @wiki.page_class.cname(page.name)
|
33
|
+
presence = "present"
|
34
|
+
end
|
35
|
+
link = ::File.join(@wiki.base_path, CGI.escape(link_name))
|
36
|
+
|
37
|
+
# TODO: This is a temporary hack for posts until actual subdirs are supported.
|
38
|
+
# Also, this needs to be improved so /\d-/ does match if part of actual title.
|
39
|
+
link = link.gsub(/(\d)-/, '\1/')
|
40
|
+
|
41
|
+
%{<a class="internal #{presence}" href="#{link}#{extra}">#{name}</a>}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Gollum
|
2
|
+
class Wiki
|
3
|
+
|
4
|
+
# Public: Lists all non-page files for this wiki.
|
5
|
+
#
|
6
|
+
# treeish - The String commit ID or ref to find (default: @ref)
|
7
|
+
#
|
8
|
+
# Returns an Array of Gollum::File instances.
|
9
|
+
def files(treeish = nil)
|
10
|
+
file_list(treeish || @ref)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Fill an array with a list of files.
|
14
|
+
#
|
15
|
+
# ref - A String ref that is either a commit SHA or references one.
|
16
|
+
#
|
17
|
+
# Returns a flat Array of Gollum::File instances.
|
18
|
+
def file_list(ref)
|
19
|
+
if sha = @access.ref_to_sha(ref)
|
20
|
+
commit = @access.commit(sha)
|
21
|
+
tree_map_for(sha).inject([]) do |list, entry|
|
22
|
+
next list if @page_class.valid_page_name?(entry.name)
|
23
|
+
list << entry.file(self, commit)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# TODO: Techincally @pages should probably be called @files since
|
2
|
+
# it includes all files that end up in the site.
|
3
|
+
|
4
|
+
require 'rss/maker'
|
5
|
+
|
6
|
+
module Smeagol
|
7
|
+
class RSS
|
8
|
+
#
|
9
|
+
# Initialize a new Smeagol::RSS instance.
|
10
|
+
#
|
11
|
+
def initialize(ctrl, options={})
|
12
|
+
@ctrl = ctrl
|
13
|
+
@wiki = ctrl.wiki
|
14
|
+
@version = options[:version] || 'master'
|
15
|
+
@posts = options[:posts]
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
## Collect the items to include in the RSS from the pages.
|
20
|
+
## Only pages with post_dates are included.
|
21
|
+
##
|
22
|
+
#def items
|
23
|
+
# list = []
|
24
|
+
# @pages.each do |page|
|
25
|
+
# next unless Smeagol::Views::Page === page
|
26
|
+
# list << page if page.post_date
|
27
|
+
# end
|
28
|
+
# list
|
29
|
+
#end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Build the RSS instance and cache the result.
|
33
|
+
#
|
34
|
+
# Returns an RSS::Rss object.
|
35
|
+
#
|
36
|
+
def rss
|
37
|
+
@rss ||= build_rss
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Build an RSS instance using Ruby's RSS::Maker library.
|
42
|
+
#
|
43
|
+
# Returns an RSS::Rss object.
|
44
|
+
#
|
45
|
+
def build_rss
|
46
|
+
::RSS::Maker.make('2.0') do |maker|
|
47
|
+
maker.channel.link = @wiki.settings.url
|
48
|
+
maker.channel.title = @wiki.settings.title
|
49
|
+
maker.channel.description = @wiki.settings.description.to_s
|
50
|
+
maker.channel.author = @wiki.settings.author.to_s
|
51
|
+
maker.channel.updated = Time.now.to_s
|
52
|
+
maker.items.do_sort = true
|
53
|
+
|
54
|
+
posts.each do |post|
|
55
|
+
html = post.content
|
56
|
+
date = Time.parse(post.post_date)
|
57
|
+
|
58
|
+
next if date > Time.now unless @wiki.settings.future
|
59
|
+
|
60
|
+
if i = html.index('</p>')
|
61
|
+
text = html[0..i+4]
|
62
|
+
else
|
63
|
+
text = html
|
64
|
+
end
|
65
|
+
|
66
|
+
maker.items.new_item do |item|
|
67
|
+
item.title = post.title
|
68
|
+
item.link = File.join(@wiki.settings.url, post.href)
|
69
|
+
item.date = date
|
70
|
+
item.description = text
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Convert the RSS object to XML string.
|
78
|
+
#
|
79
|
+
def to_s
|
80
|
+
rss.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
def posts
|
85
|
+
@ctrl.views(@version).select{ |p| p.post? }
|
86
|
+
#@posts ||= (
|
87
|
+
# @wiki.pages.map do |page|
|
88
|
+
# next unless page.post?
|
89
|
+
# Smeagol::Views::Post.new(@ctrl, page)
|
90
|
+
# end.compact
|
91
|
+
#)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|