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
@@ -0,0 +1,166 @@
1
+ module Smeagol
2
+
3
+ # Smeagol::CLI module is a function module that provide
4
+ # all command line interfaces.
5
+ #
6
+ module CLI
7
+
8
+ extend self
9
+
10
+ #
11
+ # Initialize Gollum wiki site for use with Smeagol.
12
+ #
13
+ def init(argv)
14
+ parser.banner = "usage: smeagol-init [OPTIONS] [WIKI-URI]\n"
15
+
16
+ parser.on('-t', '--title [TITLE]') do |title|
17
+ options[:title] = title
18
+ end
19
+
20
+ parser.on('-i', '--index [PAGE]') do |page_name|
21
+ options[:index] = page_name
22
+ end
23
+
24
+ # TODO: support more settings options for creating setup
25
+
26
+ Console.init(*parse(argv))
27
+ end
28
+
29
+ #
30
+ # Preview current Gollum wiki.
31
+ #
32
+ def preview(argv)
33
+ parser.banner = "Usage: smeagol-preview [OPTIONS]\n"
34
+
35
+ parser.on('--port [PORT]', 'Bind port (default 4567).') do |port|
36
+ options[:port] = port.to_i
37
+ end
38
+
39
+ parser.on('--[no-]cache', 'Enables page caching.') do |flag|
40
+ options[:cache] = flag
41
+ end
42
+
43
+ parser.on('--mount-path', 'Serve website from this base path.') do |path|
44
+ options[:mount_path] = path
45
+ end
46
+
47
+ #parser.on('--auto-update', 'Updates the repository on a daily basis.') do |flag|
48
+ # options[:auto_update] = flag
49
+ #end
50
+
51
+ #parser.on('--secret [KEY]', 'Specifies the secret key used to update.') do |str|
52
+ # options[:secret] = str
53
+ #end
54
+
55
+ $stderr.puts "Starting preview..."
56
+
57
+ Console.preview(*parse(argv))
58
+ end
59
+
60
+ #
61
+ # Serve all Gollum repositories as setup in Deagol config file.
62
+ # This can be used to serve sites in production. It makes use
63
+ # of cnames to serve multiple sites via a single service.
64
+ #
65
+ def serve(argv)
66
+ config_file = nil
67
+
68
+ parser.banner = "usage: smeagol-serve [OPTIONS]\n"
69
+
70
+ parser.on('-c', '--config [PATH]', 'Load config file instead of default.') do |path|
71
+ options[:config_file] = path
72
+ end
73
+
74
+ parser.on('--port [PORT]', 'Bind port (default 4567).') do |port|
75
+ options[:port] = port.to_i
76
+ end
77
+
78
+ parser.on('--[no-]cache', 'Enables page caching.') do |flag|
79
+ options[:cache] = flag
80
+ end
81
+
82
+ parser.on('--mount-path', 'Serve website from this base path.') do |path|
83
+ options[:mount_path] = path
84
+ end
85
+
86
+ parser.on('--auto-update', 'Updates the repository on a daily basis.') do |flag|
87
+ options[:auto_update] = flag
88
+ end
89
+
90
+ parser.on('--secret [KEY]', 'Specifies the secret key, if needed to update.') do |str|
91
+ options[:secret] = str
92
+ end
93
+
94
+ Console.serve(*parse(argv))
95
+ end
96
+
97
+ #
98
+ # Update wiki repo and update/clone site repo, if designated
99
+ # in settings.
100
+ #
101
+ def update(argv)
102
+ parser.banner = "Usage: smeagol-update [OPTIONS] [WIKI-DIR]\n"
103
+
104
+ #parser.on('-s', '--site', 'Also update site directories, if applicable.') do
105
+ # options[:site] = true
106
+ #end
107
+
108
+ Console.update(*parse(argv))
109
+ end
110
+
111
+ private
112
+
113
+ #
114
+ # Command line options.
115
+ #
116
+ # Returns the command line options. [Hash]
117
+ #
118
+ def options
119
+ @options ||= {}
120
+ end
121
+
122
+ #
123
+ # Read command line options into `options` hash.
124
+ #
125
+ # Returns arguments and options. [Array]
126
+ #
127
+ def parse(argv)
128
+ begin
129
+ parser.parse!(argv)
130
+ rescue ::OptionParser::InvalidOption
131
+ puts "smeagol: #{$!.message}"
132
+ puts "smeagol: try 'smeagol --help' for more information"
133
+ exit 1
134
+ end
135
+ return *(argv + [options])
136
+ end
137
+
138
+ #
139
+ # Create and cache option parser.
140
+ #
141
+ # Returns option parser instance. [OptionParser]
142
+ #
143
+ def parser
144
+ @parser ||= (
145
+ parser = ::OptionParser.new
146
+ parser.on_tail('--quiet', 'Turn on $QUIET mode.') do
147
+ $QUIET = true
148
+ end
149
+ parser.on_tail('--debug', 'Turn on $DEBUG mode.') do
150
+ $DEBUG = true
151
+ end
152
+ parser.on_tail('-v', '--version', 'Display current version.') do
153
+ puts "Smeagol #{Smeagol::VERSION}"
154
+ exit 0
155
+ end
156
+ parser.on_tail('-h', '-?', '--help', 'Display this help screen.') do
157
+ puts parser
158
+ exit 0
159
+ end
160
+ parser
161
+ )
162
+ end
163
+
164
+ end
165
+
166
+ end
@@ -0,0 +1,154 @@
1
+ module Smeagol
2
+
3
+ # Server configuration is used to store the options for the Smeagol
4
+ # server for serving sites.
5
+ #
6
+ # Configuration can be loaded from configuration files located
7
+ # at `/etc/smaegol/config.yml` and `~/.config/smaegol/config.yml`
8
+ # or `~/.smaegol/config.yml`. Here is an example configuration:
9
+ #
10
+ # Examples
11
+ #
12
+ # ---
13
+ # port: 3000
14
+ # auto_update: true
15
+ # cache_enabled: true
16
+ # repositories:
17
+ # - path: /path/to/wiki/repo
18
+ # cname: 'domain.name'
19
+ # origin: 'git@github.com:foo/foo.github.com.wiki.git'
20
+ # ref: master
21
+ # bare: false
22
+ # secret: 'pass123'
23
+ #
24
+ class Config
25
+
26
+ # Directory which contains user configuration.
27
+ CONFIG_HOME = ENV['XDG_CONFIG_HOME'] || '~/.config'
28
+
29
+ # Public: Load Smeagol server configuration.
30
+ #
31
+ # Returns [Config]
32
+ def self.load(file=nil)
33
+ config = {}
34
+
35
+ if file
36
+ config.update(load_config(file))
37
+ else
38
+ config.update(load_config('/etc/smeagol'))
39
+ config.update(load_config("#{CONFIG_HOME}/smeagol", '~/.smeagol'))
40
+ end
41
+
42
+ new(config)
43
+ end
44
+
45
+ # Internal: Searches through the given directories looking for
46
+ # `settings.yml` file. Loads and returns the result of
47
+ # the first file found.
48
+ #
49
+ # dirs - List of directories to search for config file. [Array<String>]
50
+ #
51
+ # Returns configuration settings or empty Hash if none found. [Hash]
52
+ def self.load_config(*dirs)
53
+ dirs.each do |dir|
54
+ file = File.join(dir, 'config.yml')
55
+ file = File.expand_path(file)
56
+ if File.exist?(file)
57
+ return YAML.load_file(file)
58
+ end
59
+ end
60
+ return {}
61
+ end
62
+
63
+ #
64
+ # Initialize new Config instance.
65
+ #
66
+ def initialize(settings={})
67
+ @port = 4567
68
+ @auto_update = false
69
+ @cache_enabled = true
70
+ @base_path = ''
71
+ @repositories = []
72
+
73
+ assign(settings)
74
+ end
75
+
76
+ #
77
+ # Given a Hash of settings, assign via writer methods.
78
+ #
79
+ def assign(settings={})
80
+ settings.each do |k,v|
81
+ __send__("#{k}=", v)
82
+ end
83
+ end
84
+
85
+ # A T T R I B U T E S
86
+
87
+ # Port to use for server. Default is 4567.
88
+ attr_accessor :port
89
+
90
+ # While running server, auto-update wiki every day.
91
+ attr_accessor :auto_update
92
+ alias :update :auto_update
93
+ alias :update= :auto_update=
94
+
95
+ # Use page cache to speed up page requests.
96
+ attr_accessor :cache
97
+ alias :cache_enabled :cache
98
+ alias :cache_enabled= :cache=
99
+
100
+ # Serve website via a given base path.
101
+ attr_accessor :base_path
102
+ alias :mount_path :base_path
103
+ alias :mount_path= :base_path=
104
+
105
+ # Wiki repository list.
106
+ #
107
+ # Examples
108
+ #
109
+ # repositories:
110
+ # - path: ~/wikis/banana-blog
111
+ # - cname: blog.bananas.org
112
+ # - secret: abc123
113
+ #
114
+ attr_reader :repositories
115
+
116
+ #
117
+ # Set list of repositories.
118
+ #
119
+ def repositories=(repos)
120
+ @repositories = (
121
+ repos.map do |repo|
122
+ case repo
123
+ when Repository then repo
124
+ else Repository.new(repo)
125
+ end
126
+ end
127
+ )
128
+ end
129
+
130
+ #
131
+ # Deprecated: Ability to access config like hash.
132
+ #
133
+ def [](name)
134
+ instance_variable_get("@#{name}")
135
+ end
136
+
137
+ # Lookup git executable.
138
+ #def git
139
+ # ENV['git'] || ENV['GIT'] || 'git'
140
+ #end
141
+
142
+ #
143
+ # Set secret for all repositories.
144
+ #
145
+ def secret=(secret)
146
+ return if secret.nil?
147
+ repositories.each do |repo|
148
+ repo.secret = secret
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+ end
@@ -0,0 +1,369 @@
1
+ module Smeagol
2
+
3
+ # Console encapsulates all the public methods
4
+ # smeagol is likely to need, generally it maps
5
+ # to all the CLI commands.
6
+ #
7
+ module Console
8
+ extend self
9
+
10
+ #
11
+ def report(msg)
12
+ $stderr.puts msg unless $QUIET
13
+ end
14
+
15
+ #
16
+ # Initialize Gollum wiki for use with Smeagol.
17
+ # This will clone the wiki repo, if given and it
18
+ # doesn't already exist and create `_settings.yml`,
19
+ # `_layouts/` and `assets/smeagol/`.
20
+ #
21
+ # TODO: Perhaps use a supporting "managed copy" gem in future?
22
+ #
23
+ # TODO: Add --force option to override skips?
24
+ #
25
+ # Returns nothing.
26
+ #
27
+ def init(*args)
28
+ options = (Hash === args.last ? args.pop : {})
29
+
30
+ abort "Too many arguments." if args.size > 2
31
+
32
+ @wiki_url = args.shift
33
+ @wiki_dir = args.shift
34
+
35
+ if @wiki_url
36
+ unless @wiki_dir
37
+ @wiki_dir = File.basename(@wiki_url).chomp('.git')
38
+ end
39
+ @clone = true
40
+ else
41
+ @wiki_dir = Dir.pwd
42
+ if File.exist?(File.join(@wiki_dir, '.git'))
43
+ @wiki_url = wiki.repo.config['remote.origin.url'].to_s
44
+ else
45
+ abort "smeagol: not a git repo."
46
+ end
47
+ @clone = false
48
+ end
49
+
50
+ clone_wiki if @clone
51
+
52
+ save_settings(options)
53
+ save_gitignore
54
+
55
+ copy_layouts unless options[:no_layouts]
56
+ copy_assets unless options[:no_assets]
57
+ end
58
+
59
+ # The wiki git url.
60
+ attr_accessor :wiki_url
61
+
62
+ # Local directory to house wiki repo.
63
+ attr_accessor :wiki_dir
64
+
65
+ #
66
+ # If a wiki git URI is given, the clone the wiki.
67
+ #
68
+ # @todo Use grit instead of shelling out.
69
+ #
70
+ def clone_wiki
71
+ system "git clone #{wiki_url} #{wiki_dir}"
72
+ end
73
+
74
+ #
75
+ # Copy layout templates to `_layouts` directory and
76
+ # partial templates to `_partials`.
77
+ #
78
+ def copy_layouts
79
+ dst_dir = File.join(wiki_dir, '_layouts')
80
+ src_dir = LIBDIR + '/templates/layouts'
81
+ copy_dir(src_dir, dst_dir)
82
+
83
+ dst_dir = File.join(wiki_dir, '_partials')
84
+ src_dir = LIBDIR + '/templates/partials'
85
+ copy_dir(src_dir, dst_dir)
86
+ end
87
+
88
+ #
89
+ # Copy assets to `assets` directory.
90
+ #
91
+ def copy_assets
92
+ dst_dir = File.join(wiki_dir, 'assets')
93
+ src_dir = LIBDIR + '/public/assets'
94
+ copy_dir(src_dir, dst_dir)
95
+ end
96
+
97
+ #
98
+ def copy_dir(src_dir, dst_dir)
99
+ FileUtils.mkdir_p(dst_dir)
100
+
101
+ Dir[File.join(src_dir, '**/*')].each do |src|
102
+ next if File.directory?(src)
103
+ dst = File.join(dst_dir, src.sub(src_dir, ''))
104
+ copy_file(src, dst)
105
+ end
106
+ end
107
+
108
+ #
109
+ def copy_file(src, dst)
110
+ if File.exist?(dst)
111
+ report " skip: #{dst.sub(Dir.pwd,'')}"
112
+ else
113
+ FileUtils.mkdir_p(File.dirname(dst))
114
+ FileUtils.cp(src, dst)
115
+ report " copy: #{dst.sub(Dir.pwd,'')}"
116
+ end
117
+ end
118
+
119
+ #
120
+ # Create or append `_site` to .gitignore file.
121
+ #
122
+ def save_gitignore
123
+ file = File.join(wiki_dir, '.gitignore')
124
+ if File.exist?(file)
125
+ File.open(file, 'a') do |f|
126
+ f.write("_site")
127
+ end
128
+ else
129
+ File.open(file, 'w') do |f|
130
+ f.write("_site")
131
+ end
132
+ end
133
+ end
134
+
135
+ #
136
+ # Save settings.
137
+ #
138
+ def save_settings(options)
139
+ file = File.join(wiki_dir, "_settings.yml")
140
+ if File.exist?(file)
141
+ $stderr.puts " skip: #{file}"
142
+ else
143
+ text = Mustache.render(settings_template, initial_settings(options))
144
+ File.open(file, 'w') do |f|
145
+ f.write(text)
146
+ end
147
+ end
148
+ end
149
+
150
+ #
151
+ # When using #init, this provided the initial settings.
152
+ #
153
+ # @returns [Settings]
154
+ #
155
+ def initial_settings(options={})
156
+ options[:wiki_origin] = wiki_url
157
+ options[:site_origin] = wiki_url.sub('.wiki', '')
158
+
159
+ @settings = Settings.new(options)
160
+ end
161
+
162
+ #
163
+ # Read in the settings mustache template.
164
+ #
165
+ def settings_template
166
+ file = LIBDIR + '/templates/settings.yml'
167
+ IO.read(file)
168
+ end
169
+
170
+ #
171
+ # Preview current wiki (from working directory).
172
+ #
173
+ def preview(options)
174
+ repository = {}
175
+ repository[:path] = Dir.pwd
176
+ #repository[:cname] = options[:cname] if options[:cname]
177
+ repository[:secret] = options.delete(:secret) if options.key?(:secret)
178
+
179
+ options[:repositories] = [repository]
180
+
181
+ config = Smeagol::Config.new(options)
182
+
183
+ catch_signals
184
+ show_repository(config)
185
+
186
+ run_server(config)
187
+ end
188
+
189
+ #
190
+ # Serve up sites defined in smeagol config file.
191
+ #
192
+ def serve(options)
193
+ config_file = options[:config_file]
194
+ config = Config.load(config_file)
195
+ config.assign(options)
196
+ abort "No repositories configured." if config.repositories.empty?
197
+
198
+ # Set secret on all repositories if passed in by command line option
199
+ # We can only assume they are all the same, in this case.
200
+ #
201
+ # TODO: Maybe only apply if no secret is given in config file?
202
+ if options[:secret]
203
+ config.repositories.each{ |r| r['secret'] = options['secret'] }
204
+ end
205
+
206
+ catch_signals
207
+
208
+ show_repository(config)
209
+ auto_update(config)
210
+ clear_caches(config)
211
+
212
+ run_server(config)
213
+ end
214
+
215
+ #
216
+ # Setup trap signals.
217
+ #
218
+ def catch_signals
219
+ Signal.trap('TERM') do
220
+ Process.kill('KILL', 0)
221
+ end
222
+ end
223
+
224
+ #
225
+ # Show repositories being served
226
+ #
227
+ def show_repository(server_config)
228
+ $stderr.puts "\n Now serving on port #{server_config.port} at /#{server_config.base_path}:"
229
+ server_config.repositories.each do |repository|
230
+ $stderr.puts " * #{repository.path} (#{repository.cname})"
231
+ end
232
+ $stderr.puts "\n"
233
+ end
234
+
235
+ #
236
+ # Run the auto update process.
237
+ #
238
+ def auto_update(server_config)
239
+ return unless server_config.auto_update
240
+ Thread.new do
241
+ while true do
242
+ sleep 86400
243
+ server_config.repositories.each do |repository|
244
+ next unless repository.auto_update?
245
+ out = repository.update
246
+ out = out[1] if Array === out
247
+ if out.index('Already up-to-date').nil?
248
+ $stderr.puts "== Repository updated at #{Time.new()} : #{repository.path} =="
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ #
256
+ # Clear the caches.
257
+ #
258
+ def clear_caches(server_config)
259
+ server_config.repositories.each do |repository|
260
+ Smeagol::Cache.new(Gollum::Wiki.new(repository.path)).clear()
261
+ end
262
+ end
263
+
264
+ #
265
+ # Run the web server.
266
+ #
267
+ def run_server(server_config)
268
+ #Smeagol::App.set(:git, server_config.git)
269
+ Smeagol::App.set(:repositories, server_config.repositories)
270
+ Smeagol::App.set(:cache_enabled, server_config.cache_enabled)
271
+ Smeagol::App.set(:mount_path, server_config.mount_path)
272
+ Smeagol::App.run!(:port => server_config.port)
273
+ end
274
+
275
+ #
276
+ # Update wiki repo(s).
277
+ #
278
+ def update(*args)
279
+ options = (Hash === args.last ? args.pop : {})
280
+ wiki_dir = args.first
281
+
282
+ if wiki_dir
283
+ dir = File.expand_path(wiki_dir)
284
+ repo = Repository.new(:path=>dir)
285
+ out = repo.update
286
+ out = out[1] if Array === out
287
+ report out
288
+ else
289
+ file = options[:config_file]
290
+ config = Config.load(file)
291
+ abort "No repositories configured." if config.repositories.empty?
292
+
293
+ config.secret = options[:secret]
294
+ config.repositories.each do |repository|
295
+ report "Updating: #{repository.path}"
296
+ out = repository.update
297
+ out = out[1] if Array === out
298
+ report out
299
+ end
300
+ end
301
+ end
302
+
303
+ #if settings.site
304
+ # if Dir.exist?(site_path)
305
+ # $stderr.puts "Pulling `#{repo.branch}' from `origin' in `#{repo.path}'..."
306
+ # repo.pull
307
+ # else
308
+ # $stderr.puts "Cloning `#{repo.origin}' in `#{repo.path}'..."
309
+ # repo.clone
310
+ # end
311
+ #end
312
+
313
+ #
314
+ # Site directory path.
315
+ #
316
+ # Returns expanded site path. [String]
317
+ #
318
+ def site_path
319
+ settings.site_path
320
+ end
321
+
322
+ #
323
+ # Site repository.
324
+ #
325
+ # Returns repository. [Repository]
326
+ #
327
+ def site_repo
328
+ settings.site_repo
329
+ end
330
+
331
+ #
332
+ # Current wiki directory.
333
+ #
334
+ # Returns wiki directory. [String]
335
+ #
336
+ def wiki_dir
337
+ @wiki_dir || Dir.pwd
338
+ end
339
+
340
+ #
341
+ # Get and cache Wiki object.
342
+ #
343
+ # Returns wiki. [Wiki]
344
+ #
345
+ def wiki
346
+ @wiki ||= Smeagol::Wiki.new(wiki_dir)
347
+ end
348
+
349
+ #
350
+ # Local wiki settings.
351
+ #
352
+ # Returns wiki settings. [Settings]
353
+ #
354
+ def settings
355
+ @settings ||= Settings.load(wiki_dir)
356
+ end
357
+
358
+ #
359
+ # Git executable.
360
+ #
361
+ # Returns git command path. [String]
362
+ #
363
+ def git
364
+ Smeagol.git
365
+ end
366
+
367
+ end
368
+
369
+ end