gitdocs 0.0.2 → 0.1.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.
- data/README.md +56 -2
- data/Rakefile +7 -0
- data/bin/gitdocs +2 -21
- data/gitdocs.gemspec +10 -0
- data/lib/gitdocs.rb +16 -103
- data/lib/gitdocs/cli.rb +110 -0
- data/lib/gitdocs/configuration.rb +52 -0
- data/lib/gitdocs/runner.rb +133 -0
- data/lib/gitdocs/server.rb +46 -0
- data/lib/gitdocs/version.rb +2 -2
- data/test/configuration_test.rb +32 -0
- data/test/runner_test.rb +13 -0
- data/test/test_helper.rb +65 -0
- metadata +215 -43
data/README.md
CHANGED
|
@@ -1,13 +1,67 @@
|
|
|
1
1
|
# Gitdocs
|
|
2
2
|
|
|
3
|
+
Collaborate on files and docs through a shared git repository. gitdocs will automatically push changes to the repo as well as pull in changes.
|
|
4
|
+
This allows any git repo to be used as a collaborative task list or wiki for a team.
|
|
5
|
+
|
|
6
|
+
You can also start a web front-end allowing the repo to be viewed through the browser.
|
|
7
|
+
|
|
3
8
|
## Installation
|
|
4
9
|
|
|
10
|
+
Install the gem:
|
|
11
|
+
|
|
5
12
|
```
|
|
6
13
|
gem install gitdocs
|
|
7
14
|
```
|
|
8
15
|
|
|
16
|
+
If you have Growl installed, you'll probably want to run `brew install growlnotify` to enable Growl support.
|
|
17
|
+
|
|
9
18
|
## Usage
|
|
10
19
|
|
|
11
|
-
|
|
20
|
+
First add the doc folders to watch:
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
```
|
|
23
|
+
gitdocs add my/path/to/watch
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You can remove and clear paths as well:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
gitdocs rm my/path/to/watch
|
|
30
|
+
# or gitdocs clear
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
You need to startup gitdocs:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
gitdocs start
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
You can also `stop` and `restart` gitdocs as needed. Run
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
gitdocs status
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
for a helpful listing of the current state. Once gitdocs is started, simply start editing or adding files to your
|
|
46
|
+
designated git repository. Changes will be automatically pushed and pulled to your local repo.
|
|
47
|
+
|
|
48
|
+
You can also have gitdocs fetch a remote repository with:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
gitdocs create my/path/for/doc git@github.com:user/some_docs.git
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This will clone the repo and add the path to your watched docs. Be sure to restart gitdocs
|
|
55
|
+
to have path changes update:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
gitdocs restart
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
To view the docs in your browser with file formatting:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
gitdocs serve
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
and then visit `http://localhost:8888` for access to all your docs in the browser.
|
data/Rakefile
CHANGED
data/bin/gitdocs
CHANGED
|
@@ -1,24 +1,5 @@
|
|
|
1
|
-
#!/usr/
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
3
|
require 'gitdocs'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
dirs << File.expand_path(Dir.pwd, File.dirname(__FILE__)) if dirs.empty?
|
|
7
|
-
|
|
8
|
-
pids = dirs.map do |dir|
|
|
9
|
-
fork do
|
|
10
|
-
Dir.chdir(dir) do
|
|
11
|
-
Gitdocs.new(Dir.pwd).run
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
pids.each do |pid|
|
|
17
|
-
Process.waitpid(pid)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
at_exit do
|
|
21
|
-
pids.each do |pid|
|
|
22
|
-
Process.kill("INT", pid) rescue nil
|
|
23
|
-
end
|
|
24
|
-
end
|
|
5
|
+
Gitdocs::Cli.start(ARGV)
|
data/gitdocs.gemspec
CHANGED
|
@@ -19,6 +19,16 @@ Gem::Specification.new do |s|
|
|
|
19
19
|
s.require_paths = ["lib"]
|
|
20
20
|
|
|
21
21
|
s.add_dependency 'rb-fsevent', '~> 0.4.3.1'
|
|
22
|
+
s.add_dependency 'thin'
|
|
23
|
+
s.add_dependency 'renee', '~> 0.3.6'
|
|
24
|
+
s.add_dependency 'redcarpet'
|
|
25
|
+
s.add_dependency 'thor'
|
|
26
|
+
s.add_dependency 'dante', '~> 0.0.4'
|
|
22
27
|
s.add_dependency 'growl', '~> 1.0.3'
|
|
23
28
|
s.add_dependency 'yajl-ruby'
|
|
29
|
+
|
|
30
|
+
s.add_development_dependency 'minitest', "~> 2.6.1"
|
|
31
|
+
s.add_development_dependency 'rake'
|
|
32
|
+
s.add_development_dependency 'mocha'
|
|
33
|
+
s.add_development_dependency 'fakeweb'
|
|
24
34
|
end
|
data/lib/gitdocs.rb
CHANGED
|
@@ -1,112 +1,25 @@
|
|
|
1
1
|
require 'gitdocs/version'
|
|
2
|
+
require 'gitdocs/configuration'
|
|
3
|
+
require 'gitdocs/runner'
|
|
4
|
+
require 'gitdocs/server'
|
|
5
|
+
require 'gitdocs/cli'
|
|
2
6
|
require 'thread'
|
|
3
7
|
require 'rb-fsevent'
|
|
4
8
|
require 'growl'
|
|
5
9
|
require 'yajl'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
info("Running gitdocs!", "Running gitdocs in `#{@root}'")
|
|
17
|
-
@current_revision = `git rev-parse HEAD`.strip
|
|
18
|
-
mutex = Mutex.new
|
|
19
|
-
Thread.new do
|
|
20
|
-
loop do
|
|
21
|
-
mutex.synchronize do
|
|
22
|
-
out, status = sh_with_code("git fetch --all && git merge origin/master")
|
|
23
|
-
if status.success?
|
|
24
|
-
changes = get_latest_changes
|
|
25
|
-
unless changes.empty?
|
|
26
|
-
info("Updated with #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been updated")
|
|
27
|
-
end
|
|
28
|
-
else
|
|
29
|
-
warn("Error attempting to pull", out)
|
|
30
|
-
end
|
|
31
|
-
push_changes
|
|
32
|
-
end
|
|
33
|
-
sleep 15
|
|
34
|
-
end
|
|
35
|
-
end.abort_on_exception = true
|
|
36
|
-
listener = FSEvent.new
|
|
37
|
-
listener.watch(@root) do |directories|
|
|
38
|
-
directories.uniq!
|
|
39
|
-
directories.delete_if {|d| d =~ /\/\.git/}
|
|
40
|
-
unless directories.empty?
|
|
41
|
-
mutex.synchronize do
|
|
42
|
-
push_changes
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
listener.run
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def push_changes
|
|
50
|
-
out, status = sh_with_code("git ls-files -o --exclude-per-directory=.gitignore | git update-index --add --stdin")
|
|
51
|
-
unless sh("git status -s").strip.empty?
|
|
52
|
-
sh "git commit -a -m'Auto-commit from gitdocs'" # TODO make this message nicer
|
|
53
|
-
out, code = sh_with_code("git push")
|
|
54
|
-
changes = get_latest_changes
|
|
55
|
-
info("Pushed #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been pushed")
|
|
56
|
-
error("Could not push changes", out) unless code.success?
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def get_latest_changes
|
|
61
|
-
out = sh "git log #{@current_revision}.. --pretty='format:{\"commit\": \"%H\",%n \"author\": \"%an <%ae>\",%n \"date\": \"%ad\",%n \"message\": \"%s\"%n}'"
|
|
62
|
-
if out.empty?
|
|
63
|
-
[]
|
|
64
|
-
else
|
|
65
|
-
lines = []
|
|
66
|
-
Yajl::Parser.new.parse(out) do |obj|
|
|
67
|
-
lines << obj
|
|
10
|
+
require 'dante'
|
|
11
|
+
|
|
12
|
+
module Gitdocs
|
|
13
|
+
def self.run(config_root = nil)
|
|
14
|
+
loop do
|
|
15
|
+
@config = Configuration.new(config_root)
|
|
16
|
+
@threads = @config.paths.map do |path|
|
|
17
|
+
t = Thread.new { Runner.new(path).run }
|
|
18
|
+
t.abort_on_exception = true
|
|
19
|
+
t
|
|
68
20
|
end
|
|
69
|
-
@
|
|
70
|
-
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def warn(title, msg)
|
|
75
|
-
if @use_growl
|
|
76
|
-
Growl.notify_warning(msg, :title => title)
|
|
77
|
-
else
|
|
78
|
-
Kernel.warn("#{title}: #{msg}")
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def info(title, msg)
|
|
83
|
-
if @use_growl
|
|
84
|
-
Growl.notify_ok(msg, :title => title, :icon => @icon)
|
|
85
|
-
else
|
|
86
|
-
puts("#{title}: #{msg}")
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def error(title, msg)
|
|
91
|
-
if @use_growl
|
|
92
|
-
Growl.notify_error(msg, :title => title)
|
|
93
|
-
else
|
|
94
|
-
Kernel.warn("#{title}: #{msg}")
|
|
21
|
+
@threads.each(&:join)
|
|
22
|
+
sleep(60)
|
|
95
23
|
end
|
|
96
|
-
exit
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def sh(cmd)
|
|
100
|
-
out, code = sh_with_code(cmd)
|
|
101
|
-
code == 0 ? out : raise(out.empty? ? "Running `#{cmd}' failed. Run this command directly for more detailed output." : out)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Run in shell, return both status and output
|
|
105
|
-
# @see #sh
|
|
106
|
-
def sh_with_code(cmd)
|
|
107
|
-
cmd << " 2>&1"
|
|
108
|
-
outbuf = ''
|
|
109
|
-
outbuf = `#{cmd}`
|
|
110
|
-
[outbuf, $?]
|
|
111
24
|
end
|
|
112
25
|
end
|
data/lib/gitdocs/cli.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Gitdocs
|
|
2
|
+
require 'thor'
|
|
3
|
+
|
|
4
|
+
class Cli < Thor
|
|
5
|
+
include Thor::Actions
|
|
6
|
+
|
|
7
|
+
def self.source_root; File.expand_path('../../', __FILE__); end
|
|
8
|
+
|
|
9
|
+
desc "start", "Starts a daemonized gitdocs process"
|
|
10
|
+
def start
|
|
11
|
+
if !self.running?
|
|
12
|
+
self.runner(:daemonize => true, :pid_path => self.pid_path).execute { Gitdocs.run }
|
|
13
|
+
say "Started gitdocs", :green
|
|
14
|
+
else
|
|
15
|
+
say "Gitdocs is already running, please use restart", :red
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc "stop", "Stops the gitdocs process"
|
|
20
|
+
def stop
|
|
21
|
+
self.runner(:kill => true, :pid_path => self.pid_path).execute
|
|
22
|
+
say "Stopped gitdocs", :red
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "restart", "Restarts the gitdocs process"
|
|
26
|
+
def restart
|
|
27
|
+
self.stop
|
|
28
|
+
sleep(1)
|
|
29
|
+
self.start
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc "add PATH", "Adds a path to gitdocs"
|
|
33
|
+
def add(path)
|
|
34
|
+
self.config.add_path(path)
|
|
35
|
+
say "Added path #{path} to doc list"
|
|
36
|
+
self.restart if self.running?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
desc "rm PATH", "Removes a path from gitdocs"
|
|
40
|
+
def rm(path)
|
|
41
|
+
self.config.remove_path(path)
|
|
42
|
+
say "Removed path #{path} from doc list"
|
|
43
|
+
self.restart if self.running?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "clear", "Clears all paths from gitdocs"
|
|
47
|
+
def clear
|
|
48
|
+
self.config.paths = []
|
|
49
|
+
say "Cleared paths from gitdocs"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "create PATH REMOTE", "Creates a new gitdoc root based on an existing remote"
|
|
53
|
+
def create(path, remote)
|
|
54
|
+
path = self.config.normalize_path(path)
|
|
55
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
56
|
+
system("git clone -q #{remote} #{path}") or raise "Unable to clone into #{path}"
|
|
57
|
+
self.add(path)
|
|
58
|
+
say "Created & added path #{path} to doc list"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
desc "status", "Retrieve gitdocs status"
|
|
62
|
+
def status
|
|
63
|
+
say "GitDoc v#{VERSION}"
|
|
64
|
+
say "Running: #{self.running?}"
|
|
65
|
+
say "Watching paths:"
|
|
66
|
+
say self.config.paths.map { |p| " - #{p}" }.join("\n")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
desc "serve", "Serves web frontend for files"
|
|
70
|
+
def serve
|
|
71
|
+
puts "Serving docs..."
|
|
72
|
+
Gitdocs::Server.new(*self.config.paths.map{|p| Gitdocs::Runner.new(p)}).start
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
desc "config", "Configuration options for gitdocs"
|
|
76
|
+
def config
|
|
77
|
+
# TODO make this work
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
desc "help", "Prints out the help"
|
|
81
|
+
def help(task = nil, subcommand = false)
|
|
82
|
+
say "\nGitdocs: Collaborate with ease.\n\n"
|
|
83
|
+
task ? self.class.task_help(shell, task) : self.class.help(shell, subcommand)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Helpers for thor
|
|
87
|
+
no_tasks do
|
|
88
|
+
def runner(options={})
|
|
89
|
+
Dante::Runner.new('gitdocs', options)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def config
|
|
93
|
+
@config ||= Configuration.new
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def running?
|
|
97
|
+
return false unless File.exist?(pid_path)
|
|
98
|
+
Process.kill 0, File.read(pid_path).to_i
|
|
99
|
+
true
|
|
100
|
+
rescue Errno::ESRCH
|
|
101
|
+
false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def pid_path
|
|
105
|
+
"/tmp/gitdocs.pid"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Gitdocs
|
|
2
|
+
class Configuration
|
|
3
|
+
attr_reader :config_root
|
|
4
|
+
|
|
5
|
+
def initialize(config_root = nil)
|
|
6
|
+
@config_root = config_root || File.expand_path(".gitdocs", ENV["HOME"])
|
|
7
|
+
FileUtils.mkdir_p(@config_root)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# @config.paths => ['my/path/1', 'my/path/2']
|
|
11
|
+
def paths
|
|
12
|
+
self.read_file('paths').split("\n")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def paths=(paths)
|
|
16
|
+
self.write_file('paths', paths.uniq.join("\n"))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @config.add_path('my/path/1')
|
|
20
|
+
def add_path(path)
|
|
21
|
+
path = normalize_path(path)
|
|
22
|
+
self.paths += [path]
|
|
23
|
+
path
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @config.remove_path('my/path/1')
|
|
27
|
+
def remove_path(path)
|
|
28
|
+
path = normalize_path(path)
|
|
29
|
+
self.paths -= [path]
|
|
30
|
+
path
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def normalize_path(path)
|
|
34
|
+
File.expand_path(path, Dir.pwd)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
# Read file from gitdocs repo
|
|
40
|
+
# @config.read_file('paths')
|
|
41
|
+
def read_file(name)
|
|
42
|
+
full_path = File.expand_path(name, @config_root)
|
|
43
|
+
File.exist?(full_path) ? File.read(full_path) : ''
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Writes configuration file
|
|
47
|
+
# @config.write_file('paths', '...')
|
|
48
|
+
def write_file(name, content)
|
|
49
|
+
File.open(File.expand_path(name, @config_root), 'w') { |f| f.puts content }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
module Gitdocs
|
|
2
|
+
class Runner
|
|
3
|
+
attr_accessor :root
|
|
4
|
+
|
|
5
|
+
def initialize(root, opts = nil)
|
|
6
|
+
@root = root
|
|
7
|
+
out, status = sh_with_code "which growlnotify"
|
|
8
|
+
@use_growl = opts && opts.key?(:growl) ? opts[:growl] : status.success?
|
|
9
|
+
@polling_interval = opts && opts[:polling_interval] || 15
|
|
10
|
+
@icon = File.expand_path("../img/icon.png", __FILE__)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run
|
|
14
|
+
info("Running gitdocs!", "Running gitdocs in `#{@root}'")
|
|
15
|
+
@current_revision = sh("git rev-parse HEAD").strip rescue nil
|
|
16
|
+
mutex = Mutex.new
|
|
17
|
+
Thread.new do
|
|
18
|
+
loop do
|
|
19
|
+
mutex.synchronize do
|
|
20
|
+
begin
|
|
21
|
+
out, status = sh_with_code("git fetch --all && git merge origin/master")
|
|
22
|
+
if status.success?
|
|
23
|
+
changes = get_latest_changes
|
|
24
|
+
unless changes.empty?
|
|
25
|
+
info("Updated with #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been updated")
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
warn("Error attempting to pull", out)
|
|
29
|
+
end
|
|
30
|
+
push_changes
|
|
31
|
+
rescue Exception
|
|
32
|
+
error("There was an error", $!.message) rescue nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
sleep @polling_interval
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
listener = FSEvent.new
|
|
39
|
+
listener.watch(@root) do |directories|
|
|
40
|
+
directories.uniq!
|
|
41
|
+
directories.delete_if {|d| d =~ /\/\.git/}
|
|
42
|
+
unless directories.empty?
|
|
43
|
+
mutex.synchronize do
|
|
44
|
+
push_changes
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
at_exit { listener.stop }
|
|
49
|
+
listener.run
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def push_changes
|
|
53
|
+
sh 'find . -type d -regex ``./[^.].*'' -empty -exec touch \'{}/.gitignore\' \;'
|
|
54
|
+
sh 'git add .'
|
|
55
|
+
# TODO make this message nicer
|
|
56
|
+
sh "git commit -a -m'Auto-commit from gitdocs'" unless sh("git status -s").strip.empty?
|
|
57
|
+
if @current_revision.nil?
|
|
58
|
+
out, code = sh_with_code("git push origin master")
|
|
59
|
+
if code.success?
|
|
60
|
+
changes = get_latest_changes
|
|
61
|
+
info("Pushed #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been pushed")
|
|
62
|
+
else
|
|
63
|
+
error("Could not push changes", out)
|
|
64
|
+
end
|
|
65
|
+
elsif sh('git status')[/branch is ahead/]
|
|
66
|
+
out, code = sh_with_code("git push")
|
|
67
|
+
if code.success?
|
|
68
|
+
changes = get_latest_changes
|
|
69
|
+
info("Pushed #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been pushed")
|
|
70
|
+
else
|
|
71
|
+
error("Could not push changes", out)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def get_latest_changes
|
|
77
|
+
if @current_revision
|
|
78
|
+
out = sh "git log #{@current_revision}.. --pretty='format:{\"commit\": \"%H\",%n \"author\": \"%an <%ae>\",%n \"date\": \"%ad\",%n \"message\": \"%s\"%n}'"
|
|
79
|
+
if out.empty?
|
|
80
|
+
[]
|
|
81
|
+
else
|
|
82
|
+
lines = []
|
|
83
|
+
Yajl::Parser.new.parse(out) do |obj|
|
|
84
|
+
lines << obj
|
|
85
|
+
end
|
|
86
|
+
@current_revision = sh("git rev-parse HEAD").strip
|
|
87
|
+
lines
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
[]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def warn(title, msg)
|
|
95
|
+
if @use_growl
|
|
96
|
+
Growl.notify_warning(msg, :title => title)
|
|
97
|
+
else
|
|
98
|
+
Kernel.warn("#{title}: #{msg}")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def info(title, msg)
|
|
103
|
+
if @use_growl
|
|
104
|
+
Growl.notify_ok(msg, :title => title, :icon => @icon)
|
|
105
|
+
else
|
|
106
|
+
puts("#{title}: #{msg}")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def error(title, msg)
|
|
111
|
+
if @use_growl
|
|
112
|
+
Growl.notify_error(msg, :title => title)
|
|
113
|
+
else
|
|
114
|
+
Kernel.warn("#{title}: #{msg}")
|
|
115
|
+
end
|
|
116
|
+
raise
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def sh(cmd)
|
|
120
|
+
out, code = sh_with_code(cmd)
|
|
121
|
+
code == 0 ? out : raise(out.empty? ? "Running `#{cmd}' failed. Run this command directly for more detailed output." : out)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Run in shell, return both status and output
|
|
125
|
+
# @see #sh
|
|
126
|
+
def sh_with_code(cmd)
|
|
127
|
+
cmd << " 2>&1"
|
|
128
|
+
outbuf = ''
|
|
129
|
+
outbuf = `cd #{@root} && #{cmd}`
|
|
130
|
+
[outbuf, $?]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'thin'
|
|
2
|
+
require 'renee'
|
|
3
|
+
|
|
4
|
+
module Gitdocs
|
|
5
|
+
class Server
|
|
6
|
+
def initialize(*gitdocs)
|
|
7
|
+
@gitdocs = gitdocs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def start(port = 8888)
|
|
11
|
+
gds = @gitdocs
|
|
12
|
+
Thin::Server.start('127.0.0.1', port) do
|
|
13
|
+
run Renee {
|
|
14
|
+
if request.path_info == '/'
|
|
15
|
+
inline!(<<-EOT, :erb, :locals => {:gds => gds})
|
|
16
|
+
<html><body>
|
|
17
|
+
<table>
|
|
18
|
+
<% gds.each_with_index do |gd, idx| %>
|
|
19
|
+
<tr><a href="/<%=idx%>"><%=gd.root%></a></tr>
|
|
20
|
+
<% end %>
|
|
21
|
+
</table>
|
|
22
|
+
</body></html>
|
|
23
|
+
EOT
|
|
24
|
+
else
|
|
25
|
+
var :int do |idx|
|
|
26
|
+
gd = gds[idx]
|
|
27
|
+
halt 404 if gd.nil?
|
|
28
|
+
expanded_path = File.expand_path(".#{request.path_info}", gd.root)
|
|
29
|
+
halt 400 unless expanded_path[/^#{Regexp.quote(gd.root)}/]
|
|
30
|
+
halt 404 unless File.exist?(expanded_path)
|
|
31
|
+
if File.directory?(expanded_path)
|
|
32
|
+
run! Rack::Directory.new(gd.root)
|
|
33
|
+
else
|
|
34
|
+
begin
|
|
35
|
+
render! expanded_path
|
|
36
|
+
rescue
|
|
37
|
+
run! Rack::File.new(gd.root)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/gitdocs/version.rb
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
VERSION = "0.0
|
|
1
|
+
module Gitdocs
|
|
2
|
+
VERSION = "0.1.0"
|
|
3
3
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
|
2
|
+
|
|
3
|
+
describe "gitdocs configuration" do
|
|
4
|
+
before do
|
|
5
|
+
@config = Gitdocs::Configuration.new("/tmp/gitdocs")
|
|
6
|
+
@config.paths = [] # Reset paths
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "has sensible default config root" do
|
|
10
|
+
assert_equal "/tmp/gitdocs", @config.config_root
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "can retrieve empty paths" do
|
|
14
|
+
assert_equal [], @config.paths
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "can have a path added" do
|
|
18
|
+
@config.add_path('/my/path')
|
|
19
|
+
assert_equal "/my/path", @config.paths.first
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "can have a path removed" do
|
|
23
|
+
@config.add_path('/my/path')
|
|
24
|
+
@config.add_path('/my/path/2')
|
|
25
|
+
@config.remove_path('/my/path/2')
|
|
26
|
+
assert_equal ["/my/path"], @config.paths
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "can normalize paths" do
|
|
30
|
+
assert_equal File.expand_path("../test_helper.rb", Dir.pwd), @config.normalize_path("../test_helper.rb")
|
|
31
|
+
end
|
|
32
|
+
end
|
data/test/runner_test.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
|
2
|
+
|
|
3
|
+
describe "gitdocs runner" do
|
|
4
|
+
it "should clone files" do
|
|
5
|
+
with_clones(3) do |clone1, clone2, clone3|
|
|
6
|
+
File.open(File.join(clone1, "test"), 'w') { |f| f << "testing" }
|
|
7
|
+
sleep 2
|
|
8
|
+
assert_equal "testing", File.read(File.join(clone1, "test"))
|
|
9
|
+
assert_equal "testing", File.read(File.join(clone2, "test"))
|
|
10
|
+
assert_equal "testing", File.read(File.join(clone3, "test"))
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
ENV["TEST"] = 'true'
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
$:.unshift File.expand_path("../../lib")
|
|
5
|
+
require 'gitdocs'
|
|
6
|
+
require 'fakeweb'
|
|
7
|
+
require 'mocha'
|
|
8
|
+
|
|
9
|
+
FakeWeb.allow_net_connect = false
|
|
10
|
+
|
|
11
|
+
## Kernel Extensions
|
|
12
|
+
require 'stringio'
|
|
13
|
+
|
|
14
|
+
module Kernel
|
|
15
|
+
# Redirect standard out, standard error and the buffered logger for sprinkle to StringIO
|
|
16
|
+
# capture_stdout { any_commands; you_want } => "all output from the commands"
|
|
17
|
+
def capture_out
|
|
18
|
+
old_out, old_err = STDOUT.dup, STDERR.dup
|
|
19
|
+
stdout_read, stdout_write = IO.pipe
|
|
20
|
+
stderr_read, stderr_write = IO.pipe
|
|
21
|
+
$stdout.reopen(stdout_write)
|
|
22
|
+
$stderr.reopen(stderr_write)
|
|
23
|
+
yield
|
|
24
|
+
stdout_write.close
|
|
25
|
+
stderr_write.close
|
|
26
|
+
out = stdout_read.rewind && stdout_read.read rescue nil
|
|
27
|
+
err = stderr_read.rewind && stderr_read.read rescue nil
|
|
28
|
+
[out, err]
|
|
29
|
+
ensure
|
|
30
|
+
$stdout.reopen(old_out)
|
|
31
|
+
$stderr.reopen(old_err)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class MiniTest::Spec
|
|
36
|
+
def with_clones(count = 3)
|
|
37
|
+
FileUtils.rm_rf("/tmp/gitdocs")
|
|
38
|
+
master_path = "/tmp/gitdocs/master"
|
|
39
|
+
FileUtils.mkdir_p("/tmp/gitdocs/master")
|
|
40
|
+
capture_out { `git init /tmp/gitdocs/master --bare` }
|
|
41
|
+
sub_paths = count.times.map do |c|
|
|
42
|
+
capture_out { `cd /tmp/gitdocs && git clone file://#{master_path} #{c}` }
|
|
43
|
+
"/tmp/gitdocs/#{c}"
|
|
44
|
+
end
|
|
45
|
+
pids = sub_paths.map { |path| fork do
|
|
46
|
+
STDOUT.reopen(File.open("/dev/null", 'w'))
|
|
47
|
+
STDERR.reopen(File.open("/dev/null", 'w'))
|
|
48
|
+
begin
|
|
49
|
+
Gitdocs::Runner.new(path, :growl => false, :polling_interval => 0.1).run
|
|
50
|
+
rescue
|
|
51
|
+
puts "RATHER BAD ~~~~~"
|
|
52
|
+
puts $!.message
|
|
53
|
+
puts $!.backtrace.join("\n ")
|
|
54
|
+
end
|
|
55
|
+
end }
|
|
56
|
+
begin
|
|
57
|
+
sleep 0.1
|
|
58
|
+
yield sub_paths
|
|
59
|
+
ensure
|
|
60
|
+
pids.each { |pid| Process.kill("INT", pid) rescue nil }
|
|
61
|
+
end
|
|
62
|
+
ensure
|
|
63
|
+
FileUtils.rm_rf("/tmp/gitdocs")
|
|
64
|
+
end
|
|
65
|
+
end
|
metadata
CHANGED
|
@@ -1,57 +1,211 @@
|
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitdocs
|
|
3
|
-
version: !ruby/object:Gem::Version
|
|
4
|
-
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 27
|
|
5
5
|
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 1
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.1.0
|
|
6
11
|
platform: ruby
|
|
7
|
-
authors:
|
|
12
|
+
authors:
|
|
8
13
|
- Josh Hull
|
|
9
14
|
autorequire:
|
|
10
15
|
bindir: bin
|
|
11
16
|
cert_chain: []
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
date: 2011-12-01 00:00:00 Z
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
|
17
22
|
none: false
|
|
18
|
-
requirements:
|
|
23
|
+
requirements:
|
|
19
24
|
- - ~>
|
|
20
|
-
- !ruby/object:Gem::Version
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
hash: 97
|
|
27
|
+
segments:
|
|
28
|
+
- 0
|
|
29
|
+
- 4
|
|
30
|
+
- 3
|
|
31
|
+
- 1
|
|
21
32
|
version: 0.4.3.1
|
|
33
|
+
requirement: *id001
|
|
22
34
|
type: :runtime
|
|
23
35
|
prerelease: false
|
|
24
|
-
|
|
25
|
-
- !ruby/object:Gem::Dependency
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
name: rb-fsevent
|
|
37
|
+
- !ruby/object:Gem::Dependency
|
|
38
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
|
39
|
+
none: false
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
hash: 3
|
|
44
|
+
segments:
|
|
45
|
+
- 0
|
|
46
|
+
version: "0"
|
|
47
|
+
requirement: *id002
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
name: thin
|
|
51
|
+
- !ruby/object:Gem::Dependency
|
|
52
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
|
53
|
+
none: false
|
|
54
|
+
requirements:
|
|
55
|
+
- - ~>
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
hash: 31
|
|
58
|
+
segments:
|
|
59
|
+
- 0
|
|
60
|
+
- 3
|
|
61
|
+
- 6
|
|
62
|
+
version: 0.3.6
|
|
63
|
+
requirement: *id003
|
|
64
|
+
type: :runtime
|
|
65
|
+
prerelease: false
|
|
66
|
+
name: renee
|
|
67
|
+
- !ruby/object:Gem::Dependency
|
|
68
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
|
69
|
+
none: false
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
hash: 3
|
|
74
|
+
segments:
|
|
75
|
+
- 0
|
|
76
|
+
version: "0"
|
|
77
|
+
requirement: *id004
|
|
78
|
+
type: :runtime
|
|
79
|
+
prerelease: false
|
|
80
|
+
name: redcarpet
|
|
81
|
+
- !ruby/object:Gem::Dependency
|
|
82
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
|
83
|
+
none: false
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
hash: 3
|
|
88
|
+
segments:
|
|
89
|
+
- 0
|
|
90
|
+
version: "0"
|
|
91
|
+
requirement: *id005
|
|
92
|
+
type: :runtime
|
|
93
|
+
prerelease: false
|
|
94
|
+
name: thor
|
|
95
|
+
- !ruby/object:Gem::Dependency
|
|
96
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
|
28
97
|
none: false
|
|
29
|
-
requirements:
|
|
98
|
+
requirements:
|
|
30
99
|
- - ~>
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
hash: 23
|
|
102
|
+
segments:
|
|
103
|
+
- 0
|
|
104
|
+
- 0
|
|
105
|
+
- 4
|
|
106
|
+
version: 0.0.4
|
|
107
|
+
requirement: *id006
|
|
108
|
+
type: :runtime
|
|
109
|
+
prerelease: false
|
|
110
|
+
name: dante
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
|
113
|
+
none: false
|
|
114
|
+
requirements:
|
|
115
|
+
- - ~>
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
hash: 17
|
|
118
|
+
segments:
|
|
119
|
+
- 1
|
|
120
|
+
- 0
|
|
121
|
+
- 3
|
|
32
122
|
version: 1.0.3
|
|
123
|
+
requirement: *id007
|
|
33
124
|
type: :runtime
|
|
34
125
|
prerelease: false
|
|
35
|
-
|
|
36
|
-
- !ruby/object:Gem::Dependency
|
|
37
|
-
|
|
38
|
-
requirement: &70199644890140 !ruby/object:Gem::Requirement
|
|
126
|
+
name: growl
|
|
127
|
+
- !ruby/object:Gem::Dependency
|
|
128
|
+
version_requirements: &id008 !ruby/object:Gem::Requirement
|
|
39
129
|
none: false
|
|
40
|
-
requirements:
|
|
41
|
-
- -
|
|
42
|
-
- !ruby/object:Gem::Version
|
|
43
|
-
|
|
130
|
+
requirements:
|
|
131
|
+
- - ">="
|
|
132
|
+
- !ruby/object:Gem::Version
|
|
133
|
+
hash: 3
|
|
134
|
+
segments:
|
|
135
|
+
- 0
|
|
136
|
+
version: "0"
|
|
137
|
+
requirement: *id008
|
|
44
138
|
type: :runtime
|
|
45
139
|
prerelease: false
|
|
46
|
-
|
|
140
|
+
name: yajl-ruby
|
|
141
|
+
- !ruby/object:Gem::Dependency
|
|
142
|
+
version_requirements: &id009 !ruby/object:Gem::Requirement
|
|
143
|
+
none: false
|
|
144
|
+
requirements:
|
|
145
|
+
- - ~>
|
|
146
|
+
- !ruby/object:Gem::Version
|
|
147
|
+
hash: 21
|
|
148
|
+
segments:
|
|
149
|
+
- 2
|
|
150
|
+
- 6
|
|
151
|
+
- 1
|
|
152
|
+
version: 2.6.1
|
|
153
|
+
requirement: *id009
|
|
154
|
+
type: :development
|
|
155
|
+
prerelease: false
|
|
156
|
+
name: minitest
|
|
157
|
+
- !ruby/object:Gem::Dependency
|
|
158
|
+
version_requirements: &id010 !ruby/object:Gem::Requirement
|
|
159
|
+
none: false
|
|
160
|
+
requirements:
|
|
161
|
+
- - ">="
|
|
162
|
+
- !ruby/object:Gem::Version
|
|
163
|
+
hash: 3
|
|
164
|
+
segments:
|
|
165
|
+
- 0
|
|
166
|
+
version: "0"
|
|
167
|
+
requirement: *id010
|
|
168
|
+
type: :development
|
|
169
|
+
prerelease: false
|
|
170
|
+
name: rake
|
|
171
|
+
- !ruby/object:Gem::Dependency
|
|
172
|
+
version_requirements: &id011 !ruby/object:Gem::Requirement
|
|
173
|
+
none: false
|
|
174
|
+
requirements:
|
|
175
|
+
- - ">="
|
|
176
|
+
- !ruby/object:Gem::Version
|
|
177
|
+
hash: 3
|
|
178
|
+
segments:
|
|
179
|
+
- 0
|
|
180
|
+
version: "0"
|
|
181
|
+
requirement: *id011
|
|
182
|
+
type: :development
|
|
183
|
+
prerelease: false
|
|
184
|
+
name: mocha
|
|
185
|
+
- !ruby/object:Gem::Dependency
|
|
186
|
+
version_requirements: &id012 !ruby/object:Gem::Requirement
|
|
187
|
+
none: false
|
|
188
|
+
requirements:
|
|
189
|
+
- - ">="
|
|
190
|
+
- !ruby/object:Gem::Version
|
|
191
|
+
hash: 3
|
|
192
|
+
segments:
|
|
193
|
+
- 0
|
|
194
|
+
version: "0"
|
|
195
|
+
requirement: *id012
|
|
196
|
+
type: :development
|
|
197
|
+
prerelease: false
|
|
198
|
+
name: fakeweb
|
|
47
199
|
description: Docs + git = gitdocs.
|
|
48
|
-
email:
|
|
200
|
+
email:
|
|
49
201
|
- joshbuddy@gmail.com
|
|
50
|
-
executables:
|
|
202
|
+
executables:
|
|
51
203
|
- gitdocs
|
|
52
204
|
extensions: []
|
|
205
|
+
|
|
53
206
|
extra_rdoc_files: []
|
|
54
|
-
|
|
207
|
+
|
|
208
|
+
files:
|
|
55
209
|
- .gitignore
|
|
56
210
|
- Gemfile
|
|
57
211
|
- README.md
|
|
@@ -59,31 +213,49 @@ files:
|
|
|
59
213
|
- bin/gitdocs
|
|
60
214
|
- gitdocs.gemspec
|
|
61
215
|
- lib/gitdocs.rb
|
|
216
|
+
- lib/gitdocs/cli.rb
|
|
217
|
+
- lib/gitdocs/configuration.rb
|
|
218
|
+
- lib/gitdocs/runner.rb
|
|
219
|
+
- lib/gitdocs/server.rb
|
|
62
220
|
- lib/gitdocs/version.rb
|
|
63
221
|
- lib/img/icon.png
|
|
64
|
-
|
|
222
|
+
- test/configuration_test.rb
|
|
223
|
+
- test/runner_test.rb
|
|
224
|
+
- test/test_helper.rb
|
|
225
|
+
homepage: ""
|
|
65
226
|
licenses: []
|
|
227
|
+
|
|
66
228
|
post_install_message:
|
|
67
229
|
rdoc_options: []
|
|
68
|
-
|
|
230
|
+
|
|
231
|
+
require_paths:
|
|
69
232
|
- lib
|
|
70
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
|
233
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
234
|
none: false
|
|
72
|
-
requirements:
|
|
73
|
-
- -
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
|
|
76
|
-
|
|
235
|
+
requirements:
|
|
236
|
+
- - ">="
|
|
237
|
+
- !ruby/object:Gem::Version
|
|
238
|
+
hash: 3
|
|
239
|
+
segments:
|
|
240
|
+
- 0
|
|
241
|
+
version: "0"
|
|
242
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
243
|
none: false
|
|
78
|
-
requirements:
|
|
79
|
-
- -
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
|
|
244
|
+
requirements:
|
|
245
|
+
- - ">="
|
|
246
|
+
- !ruby/object:Gem::Version
|
|
247
|
+
hash: 3
|
|
248
|
+
segments:
|
|
249
|
+
- 0
|
|
250
|
+
version: "0"
|
|
82
251
|
requirements: []
|
|
252
|
+
|
|
83
253
|
rubyforge_project: gitdocs
|
|
84
254
|
rubygems_version: 1.8.10
|
|
85
255
|
signing_key:
|
|
86
256
|
specification_version: 3
|
|
87
257
|
summary: Docs + git = gitdocs
|
|
88
|
-
test_files:
|
|
89
|
-
|
|
258
|
+
test_files:
|
|
259
|
+
- test/configuration_test.rb
|
|
260
|
+
- test/runner_test.rb
|
|
261
|
+
- test/test_helper.rb
|