smeagol 0.5.9 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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