smeagol 0.5.9 → 0.6.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.
Files changed (59) hide show
  1. data/.ruby +80 -0
  2. data/.yardopts +9 -0
  3. data/HISTORY.md +147 -0
  4. data/LICENSE.txt +30 -0
  5. data/README.md +132 -26
  6. data/bin/smeagol +9 -39
  7. data/bin/smeagol-init +3 -0
  8. data/bin/smeagol-preview +4 -0
  9. data/bin/smeagol-serve +4 -0
  10. data/bin/smeagol-update +4 -0
  11. data/bin/smeagold +1 -4
  12. data/lib/smeagol.rb +77 -6
  13. data/lib/smeagol/app.rb +121 -75
  14. data/lib/smeagol/cache.rb +43 -24
  15. data/lib/smeagol/cli.rb +166 -0
  16. data/lib/smeagol/config.rb +154 -0
  17. data/lib/smeagol/console.rb +369 -0
  18. data/lib/smeagol/controller.rb +275 -0
  19. data/lib/smeagol/core_ext.rb +12 -0
  20. data/lib/smeagol/gollum/blob_entry.rb +17 -0
  21. data/lib/smeagol/gollum/file.rb +44 -0
  22. data/lib/smeagol/gollum/page.rb +47 -0
  23. data/lib/smeagol/gollum/wiki.rb +31 -0
  24. data/lib/smeagol/helpers/rss.rb +96 -0
  25. data/lib/smeagol/helpers/toc.rb +69 -0
  26. data/lib/smeagol/public/assets/smeagol/gollum.css +716 -0
  27. data/lib/smeagol/public/{smeagol → assets/smeagol}/html5.js +0 -0
  28. data/lib/smeagol/public/{smeagol → assets/smeagol}/pygment.css +0 -0
  29. data/lib/smeagol/public/assets/smeagol/template.css +631 -0
  30. data/lib/smeagol/repository.rb +85 -0
  31. data/lib/smeagol/settings.rb +302 -0
  32. data/lib/smeagol/templates/layouts/page.mustache +19 -0
  33. data/lib/smeagol/templates/layouts/versions.mustache +16 -0
  34. data/lib/smeagol/templates/partials/footer.mustache +17 -0
  35. data/lib/smeagol/templates/partials/header.mustache +47 -0
  36. data/lib/smeagol/templates/settings.yml +64 -0
  37. data/lib/smeagol/version.rb +1 -1
  38. data/lib/smeagol/views/base.rb +188 -27
  39. data/lib/smeagol/views/form.rb +56 -0
  40. data/lib/smeagol/views/page.rb +126 -25
  41. data/lib/smeagol/views/post.rb +51 -0
  42. data/lib/smeagol/views/versions.rb +20 -6
  43. data/lib/smeagol/wiki.rb +25 -45
  44. data/test/helper.rb +72 -8
  45. data/test/test_app.rb +57 -0
  46. data/test/test_cache.rb +10 -11
  47. data/test/test_init.rb +27 -0
  48. data/test/test_update.rb +20 -0
  49. data/test/test_wiki.rb +13 -10
  50. metadata +142 -216
  51. data/bin/smeagol-static +0 -115
  52. data/lib/file.rb +0 -10
  53. data/lib/smeagol/hash.rb +0 -13
  54. data/lib/smeagol/option_parser.rb +0 -138
  55. data/lib/smeagol/public/smeagol/main.css +0 -234
  56. data/lib/smeagol/templates/page.mustache +0 -58
  57. data/lib/smeagol/templates/versions.mustache +0 -50
  58. data/test/test_file.rb +0 -12
  59. data/test/test_hash.rb +0 -18
@@ -1,46 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- require 'daemons'
4
- require 'optparse'
5
- require 'ostruct'
6
- require File.expand_path(File.dirname(__FILE__) + '/../lib/smeagol')
7
2
 
8
- # Catch signals
9
- Signal.trap('TERM') do
10
- Process.kill('KILL', 0)
11
- end
12
-
13
- # Parse options
14
- options = Smeagol::OptionParser.parse(ARGV)
3
+ cmd = []
15
4
 
16
- # Show repositories being served
17
- $stderr.puts "\n Now serving:"
18
- options.repositories.each do |repository|
19
- $stderr.puts " #{repository.path} (#{repository.cname})"
5
+ while (arg = ARGV.first)
6
+ break if arg.start_with?('-')
7
+ cmd << ARGV.shift
20
8
  end
21
- $stderr.puts "\n"
22
9
 
23
- # Run the auto update process
24
- if options.git && options.auto_update
25
- Thread.new do
26
- while true do
27
- sleep 86400
28
- options.repositories.each do |repository|
29
- wiki = Smeagol::Wiki.new(repository.path)
30
- wiki.update(options.git)
31
- end
32
- end
33
- end
10
+ if cmd.empty?
11
+ puts "smeagol: no command given"
12
+ else
13
+ cmd = 'smeagol-' + cmd.join('-')
14
+ exec(cmd, *ARGV)
34
15
  end
35
16
 
36
- # Clear the caches
37
- options.repositories.each do |repository|
38
- Smeagol::Cache.new(Gollum::Wiki.new(repository.path)).clear()
39
- end
40
-
41
- # Run the web server
42
- Smeagol::App.set(:repositories, options.repositories)
43
- Smeagol::App.set(:git, options.git)
44
- Smeagol::App.set(:cache_enabled, options.cache_enabled)
45
- Smeagol::App.run!(:port => options.port)
46
-
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'smeagol'
3
+ Smeagol::CLI.init(ARGV)
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'smeagol'
3
+ Smeagol::CLI.preview(ARGV)
4
+
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'smeagol'
3
+ Smeagol::CLI.serve(ARGV)
4
+
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'smeagol'
3
+ Smeagol::CLI.update(ARGV)
4
+
@@ -1,6 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
2
  require 'daemons'
4
-
5
- filename = File.expand_path('smeagol', File.dirname(__FILE__))
6
- Daemons.run(filename)
3
+ Daemons.run('smeagol-serve')
@@ -1,9 +1,80 @@
1
- $:.unshift(File.dirname(__FILE__))
2
- require 'file'
1
+ module Smeagol
2
+ LIBDIR = File.dirname(__FILE__) + '/smeagol'
3
3
 
4
+ # Locates the git binary in common places in the file system.
5
+ #
6
+ # TODO: Can we use shell.rb for this?
7
+ #
8
+ # TODO: This hsould not be necessary. 99% of the time it's just `git`.
9
+ # For the rest if $GIT environment variable.
10
+ #
11
+ # Returns String path to git executable.
12
+ def self.git
13
+ ENV['git'] || ENV['GIT'] || 'git'
14
+ end
15
+
16
+ =begin
17
+ def self.git
18
+ ENV['GIT'] ||= (
19
+ git = nil
20
+
21
+ ['/usr/bin', '/usr/sbin', '/usr/local/bin', '/opt/local/bin'].each do |path|
22
+ file = "#{path}/git"
23
+ git = file if File.executable?(file)
24
+ break if git
25
+ end
26
+
27
+ # Alert user that updates are unavailable if git is not found
28
+ if git.nil? || !File.executable?(git)
29
+ warn "warning: git executable could not be found."
30
+ else
31
+ $stderr.puts "git found: #{git}" if $DEBUG
32
+ end
33
+
34
+ git
35
+ )
36
+ end
37
+ =end
38
+
39
+ end
40
+
41
+ require 'gollum'
42
+ require 'rack/file'
43
+ require 'mustache'
44
+ require 'tmpdir'
45
+ require 'ostruct'
46
+ require 'yaml'
47
+ require 'optparse'
48
+ require 'fileutils'
49
+ require 'sinatra/base'
50
+
51
+ require 'smeagol/version'
52
+ require 'smeagol/core_ext'
53
+
54
+ # some gollum plugins, can be removed when new version of Gollum is out.
55
+ require 'smeagol/gollum/wiki'
56
+ require 'smeagol/gollum/file'
57
+ require 'smeagol/gollum/page'
58
+ require 'smeagol/gollum/blob_entry'
59
+
60
+ require 'smeagol/wiki'
4
61
  require 'smeagol/app'
5
62
  require 'smeagol/cache'
6
- require 'smeagol/hash'
7
- require 'smeagol/option_parser'
8
- require 'smeagol/wiki'
9
- require 'smeagol/version'
63
+ require 'smeagol/config'
64
+ require 'smeagol/repository'
65
+ require 'smeagol/settings'
66
+ require 'smeagol/controller'
67
+
68
+ require 'smeagol/views/base'
69
+ require 'smeagol/views/page'
70
+ require 'smeagol/views/post'
71
+ require 'smeagol/views/form'
72
+ #require 'smeagol/views/file'
73
+ require 'smeagol/views/versions'
74
+
75
+ require 'smeagol/helpers/rss'
76
+ require 'smeagol/helpers/toc'
77
+
78
+ require 'smeagol/cli'
79
+ require 'smeagol/console'
80
+
@@ -1,28 +1,15 @@
1
- require 'gollum'
2
- require 'rack/file'
3
- require 'sinatra'
4
- require 'mustache'
5
- require 'tmpdir'
6
- require 'smeagol/views/base'
7
- require 'smeagol/views/page'
8
- require 'smeagol/views/versions'
9
-
10
1
  module Smeagol
2
+
3
+ # Sinatra based app for serving the the site directly
4
+ # from the Gollum wiki repo.
5
+ #
11
6
  class App < Sinatra::Base
12
- ##############################################################################
13
- #
14
- # Settings
15
- #
16
- ##############################################################################
17
7
 
18
- set :public, File.dirname(__FILE__) + '/public'
19
-
8
+ # S E T T I N G S
20
9
 
21
- ##############################################################################
22
- #
23
- # Routes
24
- #
25
- ##############################################################################
10
+ set :public_folder, File.dirname(__FILE__) + '/public'
11
+
12
+ # R O U T E S
26
13
 
27
14
  # Update the gollum repository
28
15
  get '/update/?*' do
@@ -32,11 +19,8 @@ module Smeagol
32
19
  # secret is appended to the URL.
33
20
  if repository.secret.nil? || key == repository.secret
34
21
  wiki = Smeagol::Wiki.new(repository.path)
35
- if wiki.update(settings.git)
36
- 'ok'
37
- else
38
- 'error'
39
- end
22
+ repository.update
23
+ 'ok'
40
24
  else
41
25
  # Show a forbidden response if the secret was not correct
42
26
  'forbidden'
@@ -45,53 +29,75 @@ module Smeagol
45
29
 
46
30
  # Lists the tagged versions of the repo.
47
31
  get '/versions' do
48
- wiki = Smeagol::Wiki.new(repository.path)
49
- Mustache.render(get_template('versions'), Smeagol::Views::Versions.new(wiki))
32
+ wiki = Smeagol::Wiki.new(repository.path, {:base_path => mount_path})
33
+ ctrl = Smeagol::Controller.new(wiki)
34
+ view = Smeagol::Views::Versions.new(ctrl)
35
+ Mustache.render(view.layout, view)
36
+ end
37
+
38
+ # Assets are alwasy served unversioned directly from the file system
39
+ # and not via the git repo.
40
+ get '/assets/*' do
41
+ name = params[:splat].first
42
+ file_path = "#{repository.path}/assets/#{name}"
43
+ content = File.read(file_path)
44
+ content_type get_mime_type(name)
45
+ content
50
46
  end
51
47
 
48
+ # TODO: Instead of using `/^v\d/` as a match of versioned pages,
49
+ # use `/v/{tag_name}` path instead.
52
50
 
53
51
  # All other resources go through Gollum.
54
52
  get '/*' do
55
- splat = params[:splat].first
53
+ wiki = Smeagol::Wiki.new(repository.path, {:base_path => mount_path})
54
+ cache = Smeagol::Cache.new(wiki)
55
+ ctrl = Smeagol::Controller.new(wiki) # settings)
56
56
 
57
- # If the path starts with a version identifier, use it.
58
- version = 'master'
59
- tag_name = nil
60
- if splat.index(/^v\d/)
61
- repo = Grit::Repo.new(repository.path)
62
- tag_name = splat.split('/').first
63
- repo.tags.each do |tag|
64
- if tag.name == tag_name
65
- version = tag.commit.id
66
- splat = splat.split('/')[1..-1].join('/')
67
- end
68
- end
69
- end
70
-
71
- name = splat
72
- name = "Home" if name == ""
57
+ name, version, tag_name = parse_params(params)
58
+
59
+ name = (ctrl.settings.index || "Home") if name == ""
73
60
  name = name.gsub(/\/+$/, '')
74
- name = File.sanitize_path(name)
61
+ name = sanitize_path(name)
75
62
  file_path = "#{repository.path}/#{name}"
76
-
77
- # Load the wiki settings
78
- wiki = Smeagol::Wiki.new(repository.path)
79
- cache = Smeagol::Cache.new(wiki)
80
-
63
+
81
64
  # First check the cache
82
65
  if settings.cache_enabled && cache.cache_hit?(name, version)
83
66
  cache.get_page(name, version)
84
67
  # Then try to create the wiki page
85
68
  elsif page = wiki.page(name, version)
86
- content = Mustache.render(get_template('page'), Smeagol::Views::Page.new(page, tag_name))
69
+ if page.post?
70
+ content = ctrl.render(page, version)
71
+ else
72
+ content = ctrl.render(page, version)
73
+ end
87
74
  cache.set_page(name, page.version.id, content) if settings.cache_enabled
88
75
  content
89
- # If it is a directory, redirect to the index page
76
+ # If it is not a wiki page then try to find the file
77
+ elsif file = wiki.file(name+'.mustache', version)
78
+ content = ctrl.render(file, version)
79
+ cache.set_page(name, file.version.id, content) if settings.cache_enabled
80
+ content
81
+ # Smeagol can create an RSS feed automatically.
82
+ elsif name == 'rss.xml'
83
+ rss = RSS.new(ctrl, :version=>version)
84
+ content = rss.to_s
85
+ content_type 'application/rss+xml'
86
+ content
87
+ # Smeagol can create a JSON-formatted table of contents.
88
+ elsif name == 'toc.json'
89
+ toc = TOC.new(ctrl, :version=>version)
90
+ content = toc.to_s
91
+ content_type 'application/json'
92
+ content
93
+ # If it is a directory, redirect to the index page.
94
+ # TODO: The server usually handles this automatically
95
+ # so do we really need this? Just in case, I guess?
90
96
  elsif File.directory?(file_path)
91
97
  url = "/#{name}/index.html"
92
98
  url = "/#{tag_name}#{url}" unless tag_name.nil?
93
99
  redirect url
94
- # If it is not a wiki page then try to find the file
100
+ # If not anything else then it must be a raw asset file.
95
101
  elsif file = wiki.file(name, version)
96
102
  content_type get_mime_type(name)
97
103
  file.raw_data
@@ -101,44 +107,60 @@ module Smeagol
101
107
  end
102
108
  end
103
109
 
110
+ # P R I V A T E M E T H O D S
111
+
112
+ private
104
113
 
105
- ##############################################################################
106
114
  #
107
- # Private methods
115
+ # If the path starts with a version identifier, use it.
108
116
  #
109
- ##############################################################################
110
-
111
- private
112
- # The Mustache template to use for page rendering.
117
+ # params - The request parameters. [Hash]
113
118
  #
114
- # name - The name of the template to use.
119
+ # Returns the version number. [String]
115
120
  #
116
- # Returns the content of the page.mustache file in the root of the Gollum
117
- # repository if it exists. Otherwise, it uses the default page.mustache file
118
- # packaged with the Smeagol library.
119
- def get_template(name)
120
- if File.exists?("#{repository.path}/#{name}.mustache")
121
- IO.read("#{repository.path}/#{name}.mustache")
122
- else
123
- IO.read(File.join(File.dirname(__FILE__), "templates/#{name}.mustache"))
121
+ def parse_params(params)
122
+ name = params[:splat].first
123
+ version = 'master'
124
+ tag_name = nil
125
+
126
+ if name.index(/^v\d/)
127
+ repo = Grit::Repo.new(repository.path)
128
+ tag_name = name.split('/').first
129
+ repo_tag = repo.tags.find do |tag|
130
+ tag_name == tag.name or tag_name == "v#{tag.name}"
131
+ end
132
+ if repo_tag
133
+ version = repo_tag.name #repo_tag.commit.id
134
+ name = name.split('/')[1..-1].join('/')
135
+ else
136
+ # TODO: page not found
137
+ end
124
138
  end
139
+
140
+ return name, version, tag_name
125
141
  end
126
142
 
143
+ #
127
144
  # Retrieves the mime type for a filename based on its extension.
128
145
  #
129
- # file - The filename.
146
+ # file - The filename. [String]
147
+ #
148
+ # Returns the mime type for a file. [String]
130
149
  #
131
- # Returns the mime type for a file.
132
150
  def get_mime_type(file)
133
- if !file.nil?
134
- extension = file.slice(file.rindex('.')..-1) if file.rindex('.')
151
+ unless file.nil?
152
+ extension = ::File.extname(file)
135
153
  return Rack::Mime::MIME_TYPES[extension] || 'text/plain'
136
154
  end
137
155
 
138
156
  return 'text/plain'
139
157
  end
140
158
 
159
+ #
141
160
  # Determines the repository to use based on the hostname.
161
+ #
162
+ # Returns the matching repository. [Repository]
163
+ #
142
164
  def repository
143
165
  # Match on hostname
144
166
  settings.repositories.each do |repository|
@@ -147,9 +169,33 @@ module Smeagol
147
169
  return repository
148
170
  end
149
171
  end
150
-
151
- # If no match, use the first repository as the default
172
+
173
+ # If no match, use the first repository as the default.
152
174
  settings.repositories.first
153
175
  end
176
+
177
+ #
178
+ # Determines the mounted path to prefix to internal links.
179
+ #
180
+ # Returns the mount path. [String]
181
+ #
182
+ def mount_path
183
+ path = settings.mount_path
184
+ path += '/' unless path.end_with?('/')
185
+ path
186
+ end
187
+
188
+ #
189
+ # Removes all references to parent directories (../) in a path.
190
+ #
191
+ # path - The path to sanitize. [String]
192
+ #
193
+ # Returns a clean, pristine path. [String]
194
+ #
195
+ def sanitize_path(path)
196
+ path.gsub(/\.\.(?=$|\/)/, '') unless path.nil?
197
+ end
198
+
154
199
  end
200
+
155
201
  end
@@ -1,59 +1,72 @@
1
- require 'fileutils'
2
-
3
1
  module Smeagol
2
+
4
3
  class Cache
4
+
5
+ #
5
6
  # Creates a cache object for a Gollum wiki.
6
7
  #
7
- # wiki - The wiki to cache.
8
+ # wiki - The wiki to cache. [Wiki]
9
+ #
10
+ # Returns cache. [Cache]
8
11
  #
9
- # Returns a Smeagol::Cache object.
10
12
  def initialize(wiki)
11
13
  @wiki = wiki
12
14
  @path = "#{Dir.tmpdir}/smeagol/#{File.expand_path(@wiki.path)}"
13
15
  end
14
-
16
+
17
+ #
15
18
  # The cached wiki.
19
+ #
16
20
  attr_reader :wiki
17
21
 
22
+ #
18
23
  # The path to the smeagol cache for this wiki.
24
+ #
19
25
  attr_accessor :path
20
26
 
21
-
27
+ #
22
28
  # Clears the entire cache.
29
+ #
23
30
  def clear
24
31
  FileUtils.rm_rf(path)
25
32
  end
26
-
33
+
34
+ #
27
35
  # Checks if a cache hit is found for a given gollum page.
28
36
  #
29
- # name - The name of the page to check.
30
- # version - The version of the page to check.
37
+ # name - The name of the page to check. [String]
38
+ # version - The version of the page to check. [String]
39
+ #
40
+ # Returns true if the page has been cached, otherwise false. [Boolean]
31
41
  #
32
- # Returns true if the page has been cached, otherwise returns false.
33
42
  def cache_hit?(name, version='master')
34
43
  page = wiki.page(name, version)
35
44
  File.exists?(page_path(name, version)) unless page.nil?
36
45
  end
37
-
46
+
47
+ #
38
48
  # Retrieves the content of the cached page.
39
49
  #
40
- # name - The name of the wiki page.
41
- # version - The version of the wiki page.
50
+ # name - The name of the wiki page. [String]
51
+ # version - The version of the page. [String]
52
+ #
53
+ # Returns the contents of the HTML page if cached, otherwise nil. [String,nil]
42
54
  #
43
- # Returns the contents of the HTML page if cached. Otherwise returns nil.
44
55
  def get_page(name, version='master')
45
56
  IO.read(page_path(name, version)) if cache_hit?(name, version)
46
57
  end
47
58
 
59
+ #
48
60
  # Sets the cached content for a page.
49
61
  #
50
- # name - The name of the wiki page.
51
- # version - The version of the page.
52
- # content - The content to cache.
62
+ # name - The name of the wiki page. [String]
63
+ # version - The version of the page. [String]
64
+ # content - The content to cache. [String]
53
65
  #
54
66
  # Returns nothing.
67
+ #
55
68
  def set_page(name, version, content)
56
- p "set page: #{name} : #{version.class}"
69
+ $stderr.puts "set page: #{name} : #{version.class}" unless $QUIET
57
70
  page = wiki.page(name, version)
58
71
  if !page.nil?
59
72
  path = page_path(name, version)
@@ -64,28 +77,34 @@ module Smeagol
64
77
  end
65
78
  end
66
79
 
80
+ #
67
81
  # Removes the cached content for a page.
68
82
  #
69
- # name - The name of the wiki page.
70
- # version - The version of the page.
83
+ # name - The name of the wiki page. [String]
84
+ # version - The version of the page. [String]
71
85
  #
72
86
  # Returns nothing.
87
+ #
73
88
  def remove_page(name, version='master')
74
89
  page = wiki.page(name, version)
75
90
  File.delete(page_path(name, version)) if !page.nil? && File.exists?(page_path(name, version))
76
91
  end
77
-
92
+
93
+ #
78
94
  # Retrieves the path to the cache for a given page.
79
95
  #
80
- # name - The name of the wiki page.
81
- # version - The version of the page.
96
+ # name - The name of the wiki page. [String]
97
+ # version - The version of the page. [String]
98
+ #
99
+ # Returns the file path to the cached wiki page. [String]
82
100
  #
83
- # Returns a file path to the cached wiki page.
84
101
  def page_path(name, version='master')
85
102
  page = wiki.page(name, version)
86
103
  if !page.nil?
87
104
  "#{path}/#{page.path}/#{page.version.id}"
88
105
  end
89
106
  end
107
+
90
108
  end
109
+
91
110
  end