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
@@ -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