siuying-gitdocs 0.4.14.md

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 (87) hide show
  1. data/.gitignore +5 -0
  2. data/CHANGELOG +79 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +20 -0
  5. data/README.md +192 -0
  6. data/Rakefile +8 -0
  7. data/bin/gitdocs +5 -0
  8. data/gitdocs.gemspec +41 -0
  9. data/lib/gitdocs/cli.rb +111 -0
  10. data/lib/gitdocs/configuration.rb +79 -0
  11. data/lib/gitdocs/docfile.rb +23 -0
  12. data/lib/gitdocs/manager.rb +96 -0
  13. data/lib/gitdocs/markdown_converter.rb +44 -0
  14. data/lib/gitdocs/markdown_template.rb +20 -0
  15. data/lib/gitdocs/migration/001_create_shares.rb +13 -0
  16. data/lib/gitdocs/migration/002_add_remote_branch.rb +10 -0
  17. data/lib/gitdocs/migration/003_create_configs.rb +11 -0
  18. data/lib/gitdocs/migration/004_add_index_for_path.rb +10 -0
  19. data/lib/gitdocs/migration/005_add_start_web_frontend.rb +9 -0
  20. data/lib/gitdocs/migration/006_add_web_port_to_config.rb +9 -0
  21. data/lib/gitdocs/public/css/app.css +51 -0
  22. data/lib/gitdocs/public/css/bootstrap.css +356 -0
  23. data/lib/gitdocs/public/css/coderay.css +41 -0
  24. data/lib/gitdocs/public/css/tilt.css +82 -0
  25. data/lib/gitdocs/public/img/error.png +0 -0
  26. data/lib/gitdocs/public/img/file.png +0 -0
  27. data/lib/gitdocs/public/img/folder.png +0 -0
  28. data/lib/gitdocs/public/img/git_logo.png +0 -0
  29. data/lib/gitdocs/public/img/info.png +0 -0
  30. data/lib/gitdocs/public/img/ok.png +0 -0
  31. data/lib/gitdocs/public/img/warning.png +0 -0
  32. data/lib/gitdocs/public/js/ace/ace-compat.js +1 -0
  33. data/lib/gitdocs/public/js/ace/ace-uncompressed.js +14202 -0
  34. data/lib/gitdocs/public/js/ace/ace.js +1 -0
  35. data/lib/gitdocs/public/js/ace/keybinding-emacs.js +1 -0
  36. data/lib/gitdocs/public/js/ace/keybinding-vim.js +1 -0
  37. data/lib/gitdocs/public/js/ace/mode-coffee.js +1 -0
  38. data/lib/gitdocs/public/js/ace/mode-css.js +1 -0
  39. data/lib/gitdocs/public/js/ace/mode-html.js +1 -0
  40. data/lib/gitdocs/public/js/ace/mode-javascript.js +1 -0
  41. data/lib/gitdocs/public/js/ace/mode-json.js +1 -0
  42. data/lib/gitdocs/public/js/ace/mode-lua.js +1 -0
  43. data/lib/gitdocs/public/js/ace/mode-markdown.js +1 -0
  44. data/lib/gitdocs/public/js/ace/mode-php.js +1 -0
  45. data/lib/gitdocs/public/js/ace/mode-python.js +1 -0
  46. data/lib/gitdocs/public/js/ace/mode-ruby.js +1 -0
  47. data/lib/gitdocs/public/js/ace/mode-scala.js +1 -0
  48. data/lib/gitdocs/public/js/ace/mode-scss.js +1 -0
  49. data/lib/gitdocs/public/js/ace/mode-sql.js +1 -0
  50. data/lib/gitdocs/public/js/ace/mode-svg.js +1 -0
  51. data/lib/gitdocs/public/js/ace/mode-textile.js +1 -0
  52. data/lib/gitdocs/public/js/ace/mode-xml.js +1 -0
  53. data/lib/gitdocs/public/js/ace/theme-tomorrow.js +1 -0
  54. data/lib/gitdocs/public/js/ace/theme-tomorrow_night.js +1 -0
  55. data/lib/gitdocs/public/js/ace/theme-tomorrow_night_blue.js +1 -0
  56. data/lib/gitdocs/public/js/ace/theme-twilight.js +1 -0
  57. data/lib/gitdocs/public/js/ace/theme-vibrant_ink.js +1 -0
  58. data/lib/gitdocs/public/js/ace/worker-coffee.js +7041 -0
  59. data/lib/gitdocs/public/js/ace/worker-css.js +9525 -0
  60. data/lib/gitdocs/public/js/ace/worker-javascript.js +9739 -0
  61. data/lib/gitdocs/public/js/app.js +107 -0
  62. data/lib/gitdocs/public/js/bootstrap-alerts.js +113 -0
  63. data/lib/gitdocs/public/js/edit.js +30 -0
  64. data/lib/gitdocs/public/js/jquery.js +4 -0
  65. data/lib/gitdocs/public/js/jquery.tablesorter.js +4 -0
  66. data/lib/gitdocs/public/js/search.js +13 -0
  67. data/lib/gitdocs/public/js/settings.js +25 -0
  68. data/lib/gitdocs/public/js/util.js +145 -0
  69. data/lib/gitdocs/runner.rb +259 -0
  70. data/lib/gitdocs/server.rb +154 -0
  71. data/lib/gitdocs/version.rb +3 -0
  72. data/lib/gitdocs/views/_ace_scripts.erb +8 -0
  73. data/lib/gitdocs/views/_header.haml +14 -0
  74. data/lib/gitdocs/views/app.haml +40 -0
  75. data/lib/gitdocs/views/dir.haml +43 -0
  76. data/lib/gitdocs/views/edit.haml +15 -0
  77. data/lib/gitdocs/views/file.haml +8 -0
  78. data/lib/gitdocs/views/home.haml +10 -0
  79. data/lib/gitdocs/views/revisions.haml +28 -0
  80. data/lib/gitdocs/views/search.haml +16 -0
  81. data/lib/gitdocs/views/settings.haml +55 -0
  82. data/lib/gitdocs.rb +37 -0
  83. data/lib/img/icon.png +0 -0
  84. data/test/configuration_test.rb +39 -0
  85. data/test/runner_test.rb +25 -0
  86. data/test/test_helper.rb +54 -0
  87. metadata +370 -0
@@ -0,0 +1,145 @@
1
+ Utils = {
2
+ getKeys : function(hash) {
3
+ var keys = [];
4
+ for(var i in hash) {
5
+ keys.push(i);
6
+ }
7
+ return keys;
8
+ },
9
+
10
+ getValues : function(hash) {
11
+ var values = [];
12
+ for(var i in hash) {
13
+ values.push(hash[i]);
14
+ }
15
+ return values;
16
+ },
17
+ // humanizeBytes(1234)
18
+ humanizeBytes : function(filesize) {
19
+ if (filesize == null || filesize <= 0 || filesize == "") { return "&mdash;" }
20
+ if (filesize >= 1073741824) {
21
+ filesize = Utils.number_format(filesize / 1073741824, 2, '.', '') + ' Gb';
22
+ } else {
23
+ if (filesize >= 1048576) {
24
+ filesize = Utils.number_format(filesize / 1048576, 2, '.', '') + ' Mb';
25
+ } else {
26
+ if (filesize >= 1024) {
27
+ filesize = Utils.number_format(filesize / 1024, 0) + ' Kb';
28
+ } else {
29
+ filesize = Utils.number_format(filesize, 0) + ' bytes';
30
+ };
31
+ };
32
+ };
33
+ return filesize;
34
+ },
35
+ number_format : function( number, decimals, dec_point, thousands_sep ) {
36
+ var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
37
+ var d = dec_point == undefined ? "," : dec_point;
38
+ var t = thousands_sep == undefined ? "." : thousands_sep, s = n < 0 ? "-" : "";
39
+ var i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0;
40
+ return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
41
+ }
42
+ };
43
+
44
+ // Strings
45
+ StringFormatter = {
46
+ // autolink text within a plain text file
47
+ // apply to the wrapper around any text (.autolink)
48
+ autoLink : function() {
49
+ $('.autolink:not(.linked)').each(function(index, item) {
50
+ var result = $(item).html().toString() + ' ';
51
+ $(result.match(/(https?.*?)[^<\s]*/gm)).each(function(index, linkString) {
52
+ var link = "<a href='" + linkString + "' target='_blank'>" + linkString + "</a>";
53
+ result = result.replace(linkString, link);
54
+ $(item).addClass('linked');
55
+ });
56
+ $(item).html(result.slice(0, -1));
57
+ });
58
+ }
59
+ };
60
+
61
+ // DATES
62
+ // RelativeDate.time_ago_in_words(date)
63
+ var RelativeDate = {
64
+ time_ago_in_words: function(from) {
65
+ return RelativeDate.distance_of_time_in_words(new Date, RelativeDate.parseISO8601(from));
66
+
67
+ },
68
+ distance_of_time_in_words: function(to, from) {
69
+ var distance_in_seconds = ((to - from) / 1000);
70
+ var distance_in_minutes = Math.floor(distance_in_seconds / 60);
71
+
72
+ if (distance_in_minutes <= 0) { return 'less than a minute ago'; }
73
+ if (distance_in_minutes == 1) { return 'a minute ago'; }
74
+ if (distance_in_minutes < 45) { return distance_in_minutes + ' minutes ago'; }
75
+ if (distance_in_minutes < 120) { return '1 hour ago'; }
76
+ if (distance_in_minutes < 1440) { return Math.floor(distance_in_minutes / 60) + ' hours ago'; }
77
+ if (distance_in_minutes < 2880) { return '1 day ago'; }
78
+ if (distance_in_minutes < 43200) { return Math.floor(distance_in_minutes / 1440) + ' days ago'; }
79
+ if (distance_in_minutes < 86400) { return '1 month ago'; }
80
+ if (distance_in_minutes < 525960) { return Math.floor(distance_in_minutes / 43200) + ' months ago'; }
81
+ if (distance_in_minutes < 1051199) { return 'about 1 year ago'; }
82
+
83
+ return 'over ' + Math.floor(distance_in_minutes / 525960) + ' years ago';
84
+ },
85
+ parseISO8601 : function(str) {
86
+ // we assume str is a UTC date ending in 'Z'
87
+
88
+ var parts = str.split('T'),
89
+ dateParts = parts[0].split('-'),
90
+ timeParts = parts[1].split('Z'),
91
+ timeSubParts = timeParts[0].split(':'),
92
+ timeSecParts = timeSubParts[2].split('.'),
93
+ timeHours = Number(timeSubParts[0]),
94
+ _date = new Date;
95
+
96
+ _date.setUTCFullYear(Number(dateParts[0]));
97
+ _date.setUTCMonth(Number(dateParts[1])-1);
98
+ _date.setUTCDate(Number(dateParts[2]));
99
+ _date.setUTCHours(Number(timeHours));
100
+ _date.setUTCMinutes(Number(timeSubParts[1]));
101
+ _date.setUTCSeconds(Number(timeSecParts[0]));
102
+ if (timeSecParts[1]) _date.setUTCMilliseconds(Number(timeSecParts[1]));
103
+
104
+ // by using setUTC methods the date has already been converted to local time(?)
105
+ return _date;
106
+ },
107
+ humanize : function(str, shortened) {
108
+ var parts = str.split('T')[0].split('-')
109
+ var humDate = new Date;
110
+
111
+ humDate.setFullYear(Number(parts[0]));
112
+ humDate.setMonth(Number(parts[1])-1);
113
+ humDate.setDate(Number(parts[2]));
114
+
115
+ switch(humDate.getDay())
116
+ {
117
+ case 0:
118
+ var day = "Sunday";
119
+ break;
120
+ case 1:
121
+ var day = "Monday";
122
+ break;
123
+ case 2:
124
+ var day = "Tuesday";
125
+ break;
126
+ case 3:
127
+ var day = "Wednesday";
128
+ break;
129
+ case 4:
130
+ var day = "Thursday";
131
+ break;
132
+ case 5:
133
+ var day = "Friday";
134
+ break;
135
+ case 6:
136
+ var day = "Saturday";
137
+ break;
138
+ }
139
+ if(shortened) {
140
+ return humDate.toLocaleDateString();
141
+ } else {
142
+ return day + ', ' + humDate.toLocaleDateString();
143
+ }
144
+ }
145
+ };
@@ -0,0 +1,259 @@
1
+ module Gitdocs
2
+ class Runner
3
+ include ShellTools
4
+
5
+ attr_reader :root, :listener
6
+
7
+ def initialize(share)
8
+ @share = share
9
+ @root = share.path.sub(%r{/+$},'') if share.path
10
+ @polling_interval = share.polling_interval
11
+ @icon = File.expand_path("../../img/icon.png", __FILE__)
12
+ end
13
+
14
+ SearchResult = Struct.new(:file, :context)
15
+ def search(term)
16
+ return [] if term.empty?
17
+
18
+ results = []
19
+ if result_test = sh_string("git grep -i #{ShellTools.escape(term)}")
20
+ result_test.scan(/(.*?):([^\n]*)/) do |(file, context)|
21
+ if result = results.find { |s| s.file == file }
22
+ result.context += ' ... ' + context
23
+ else
24
+ results << SearchResult.new(file, context)
25
+ end
26
+ end
27
+ end
28
+ results
29
+ end
30
+
31
+ def run
32
+ return false unless self.valid?
33
+
34
+ @show_notifications = @share.notification
35
+ @current_remote = @share.remote_name
36
+ @current_branch = @share.branch_name
37
+ @current_revision = sh_string("git rev-parse HEAD")
38
+ Guard::Notifier.turn_on if @show_notifications
39
+
40
+ mutex = Mutex.new
41
+
42
+ info("Running gitdocs!", "Running gitdocs in `#{@root}'")
43
+
44
+ # Pull changes from remote repository
45
+ syncer = proc do
46
+ EM.defer(proc do
47
+ mutex.synchronize { sync_changes }
48
+ end, proc do
49
+ EM.add_timer(@polling_interval) {
50
+ syncer.call
51
+ }
52
+ end)
53
+ end
54
+ syncer.call
55
+ # Listen for changes in local repository
56
+
57
+ EM.defer(proc{
58
+ listener = Guard::Listener.select_and_init(@root, :watch_all_modifications => true)
59
+ listener.on_change { |directories|
60
+ directories.uniq!
61
+ directories.delete_if {|d| d =~ /\/\.git/}
62
+ unless directories.empty?
63
+ EM.next_tick do
64
+ EM.defer(proc {
65
+ mutex.synchronize { push_changes }
66
+ }, proc {} )
67
+ end
68
+ end
69
+ }
70
+ listener.start
71
+ }, proc{EM.stop_reactor})
72
+ end
73
+
74
+ def clear_state
75
+ @state = nil
76
+ end
77
+
78
+ def sync_changes
79
+ out, status = sh_with_code("git fetch --all && git merge #{@current_remote}/#{@current_branch}")
80
+ if status.success?
81
+ changes = get_latest_changes
82
+ unless changes.empty?
83
+ author_list = changes.inject(Hash.new{|h, k| h[k] = 0}) {|h, c| h[c['author']] += 1; h}.to_a.sort{|a,b| b[1] <=> a[1]}.map{|(name, count)| "* #{name} (#{count} change#{count == 1 ? '' : 's'})"}.join("\n")
84
+ info("Updated with #{changes.size} change#{changes.size == 1 ? '' : 's'}", "In `#{@root}':\n#{author_list}")
85
+ end
86
+ push_changes
87
+ elsif out[/CONFLICT/]
88
+ conflicted_files = sh("git ls-files -u --full-name -z").split("\0").
89
+ inject(Hash.new{|h, k| h[k] = []}) {|h, line|
90
+ parts = line.split(/\t/)
91
+ h[parts.last] << parts.first.split(/ /)
92
+ h
93
+ }
94
+ warn("There were some conflicts", "#{conflicted_files.keys.map{|f| "* #{f}"}.join("\n")}")
95
+ conflicted_files.each do |conflict, ids|
96
+ conflict_start, conflict_end = conflict.scan(/(.*?)(|\.[^\.]+)$/).first
97
+ ids.each do |(mode, sha, id)|
98
+ author = " original" if id == "1"
99
+ system("cd #{@root} && git show :#{id}:#{conflict} > '#{conflict_start} (#{sha[0..6]}#{author})#{conflict_end}'")
100
+ end
101
+ system("cd #{@root} && git rm #{conflict}") or raise
102
+ end
103
+ push_changes
104
+ elsif sh_string("git remote").nil? # no remote to pull from
105
+ # Do nothing, no remote repo yet
106
+ else
107
+ error("There was a problem synchronizing this gitdoc", "A problem occurred in #{@root}:\n#{out}")
108
+ end
109
+ end
110
+
111
+ def push_changes
112
+ message_file = File.expand_path(".gitmessage~", @root)
113
+ if File.exist? message_file
114
+ message = File.read message_file
115
+ File.delete message_file
116
+ else
117
+ message = 'Auto-commit from gitdocs'
118
+ end
119
+ sh 'find . -type d -regex ``./[^.].*'' -empty -exec touch \'{}/.gitignore\' \;'
120
+ sh 'git add .'
121
+ sh "git commit -a -m #{ShellTools.escape(message)}" unless sh("git status -s").strip.empty?
122
+ if @current_revision.nil? || sh('git status')[/branch is ahead/]
123
+ out, code = sh_with_code("git push #{@current_remote} #{@current_branch}")
124
+ if code.success?
125
+ changes = get_latest_changes
126
+ info("Pushed #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been pushed")
127
+ elsif @current_revision.nil?
128
+ # ignorable
129
+ elsif out[/\[rejected\]/]
130
+ warn("There was a conflict in #{@root}, retrying", "")
131
+ else
132
+ error("BAD Could not push changes in #{@root}", out)
133
+ exit
134
+ end
135
+ end
136
+ end
137
+
138
+ def get_latest_changes
139
+ if @current_revision
140
+ out = sh "git log #{@current_revision}.. --pretty='format:{\"commit\": \"%H\",%n \"author\": \"%an <%ae>\",%n \"date\": \"%ad\",%n \"message\": \"%s\"%n}'"
141
+ if out.empty?
142
+ []
143
+ else
144
+ lines = []
145
+ Yajl::Parser.new.parse(out) do |obj|
146
+ lines << obj
147
+ end
148
+ @current_revision = sh("git rev-parse HEAD").strip
149
+ lines
150
+ end
151
+ else
152
+ []
153
+ end
154
+ end
155
+
156
+ IGNORED_FILES = ['.gitignore']
157
+ # Returns the list of files in a given directory
158
+ # dir_files("some/dir") => [<Docfile>, <Docfile>]
159
+ def dir_files(dir_path)
160
+ Dir[File.join(dir_path, "*")].to_a.map { |path| Docfile.new(path) }
161
+ end
162
+
163
+ # Returns file meta data based on relative file path
164
+ # file_meta("path/to/file")
165
+ # => { :author => "Nick", :size => 1000, :modified => ... }
166
+ def file_meta(file)
167
+ result = {}
168
+ file = file.gsub(%r{^/}, '')
169
+ full_path = File.expand_path(file, @root)
170
+ log_result = sh_string("git log --format='%aN|%ai' -n1 #{ShellTools.escape(file)}")
171
+ result = {} unless File.exist?(full_path) && log_result
172
+ author, modified = log_result.split("|")
173
+ modified = Time.parse(modified.sub(' ', 'T')).utc.iso8601
174
+ size = if File.directory?(full_path)
175
+ Dir[File.join(full_path, '**', '*')].inject(0) do |size, file|
176
+ File.symlink?(file) ? size : size += File.size(file)
177
+ end
178
+ else
179
+ File.symlink?(full_path) ? 0 : File.size(full_path)
180
+ end
181
+ size = -1 if size == 0 # A value of 0 breaks the table sort for some reason
182
+ result = { :author => author, :size => size, :modified => modified }
183
+ result
184
+ end
185
+
186
+ # Returns the revisions available for a particular file
187
+ # file_revisions("README")
188
+ def file_revisions(file)
189
+ file = file.gsub(%r{^/}, '')
190
+ output = sh_string("git log --format='%h|%s|%aN|%ai' -n100 #{ShellTools.escape(file)}")
191
+ output.to_s.split("\n").map do |log_result|
192
+ commit, subject, author, date = log_result.split("|")
193
+ date = Time.parse(date.sub(' ', 'T')).utc.iso8601
194
+ { :commit => commit, :subject => subject, :author => author, :date => date }
195
+ end
196
+ end
197
+
198
+ # Returns the temporary path of a particular revision of a file
199
+ # file_revision_at("README", "a4c56h") => "/tmp/some/path/README"
200
+ def file_revision_at(file, ref)
201
+ file = file.gsub(%r{^/}, '')
202
+ content = sh_string("git show #{ref}:#{ShellTools.escape(file)}")
203
+ tmp_path = File.expand_path(File.basename(file), Dir.tmpdir)
204
+ File.open(tmp_path, 'w') { |f| f.puts content }
205
+ tmp_path
206
+ end
207
+
208
+ # Revert a file to a particular revision
209
+ def file_revert(file, ref)
210
+ if file_revisions(file).map {|r| r[:commit]}.include? ref
211
+ file = file.gsub(%r{^/}, '')
212
+ full_path = File.expand_path(file, @root)
213
+ content = File.read(file_revision_at(file, ref))
214
+ File.open(full_path, 'w') { |f| f.puts content }
215
+ end
216
+ end
217
+
218
+ def valid?
219
+ out, status = sh_with_code "git status"
220
+ @root.present? && status.success?
221
+ end
222
+
223
+ def warn(title, msg)
224
+ if @show_notifications
225
+ Guard::Notifier.notify(msg, :title => title)
226
+ else
227
+ Kernel.warn("#{title}: #{msg}")
228
+ end
229
+ end
230
+
231
+ def info(title, msg)
232
+ if @show_notifications
233
+ Guard::Notifier.notify(msg, :title => title, :image => @icon)
234
+ else
235
+ puts("#{title}: #{msg}")
236
+ end
237
+ end
238
+
239
+ def error(title, msg)
240
+ if @show_notifications
241
+ Guard::Notifier.notify(msg, :title => title, :image => :failure)
242
+ else
243
+ Kernel.warn("#{title}: #{msg}")
244
+ end
245
+ end
246
+
247
+ # sh_string("git config branch.`git branch | grep '^\*' | sed -e 's/\* //'`.remote", "origin")
248
+ def sh_string(cmd, default=nil)
249
+ val = sh(cmd).strip rescue nil
250
+ (val.nil? || val.empty?) ? default : val
251
+ end
252
+
253
+ # Run in shell, return both status and output
254
+ # @see #sh
255
+ def sh_with_code(cmd)
256
+ ShellTools.sh_with_code(cmd, @root)
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,154 @@
1
+ require 'thin'
2
+ require 'renee'
3
+ require 'coderay'
4
+ require 'uri'
5
+ require 'haml'
6
+ require 'mimetype_fu'
7
+ require 'pygments'
8
+
9
+ module Gitdocs
10
+ class Server
11
+ def initialize(manager, *gitdocs)
12
+ @manager = manager
13
+ @gitdocs = gitdocs
14
+ end
15
+
16
+ def start(port = 8888)
17
+ gds = @gitdocs
18
+ manager = @manager
19
+
20
+ Tilt.prefer ::MarkdownTemplate, 'md'
21
+ Tilt.prefer ::MarkdownTemplate, 'mkd'
22
+ Tilt.prefer ::MarkdownTemplate, 'markdown'
23
+
24
+ Thin::Logging.debug = @manager.debug
25
+ Thin::Server.start('127.0.0.1', port) do
26
+ use Rack::Static, :urls => ['/css', '/js', '/img', '/doc'], :root => File.expand_path("../public", __FILE__)
27
+ use Rack::MethodOverride
28
+ run Renee {
29
+ if request.path_info == '/'
30
+ if manager.config.shares.size == 1
31
+ redirect! "/0"
32
+ else
33
+ render! "home", :layout => 'app', :locals => {:conf => manager.config, :nav_state => "home" }
34
+ end
35
+ else
36
+ path 'settings' do
37
+ get.render! 'settings', :layout => 'app', :locals => {:conf => manager.config, :nav_state => "settings" }
38
+ post do
39
+ shares = manager.config.shares
40
+ manager.config.global.update_attributes(request.POST['config'])
41
+ request.POST['share'].each do |idx, share|
42
+ if remote_branch = share.delete('remote_branch')
43
+ share['remote_name'], share['branch_name'] = remote_branch.split('/', 2)
44
+ end
45
+ # Update paths
46
+ if share['path'] && !share['path'].empty?
47
+ shares[Integer(idx)].update_attributes(share)
48
+ end
49
+ end
50
+ EM.add_timer(0.1) { manager.restart }
51
+ redirect! '/settings'
52
+ end
53
+ end
54
+
55
+ path('search').get do
56
+ render! "search", :layout => 'app', :locals => {:conf => manager.config, :results => manager.search(request.GET['q']), :nav_state => nil}
57
+ end
58
+
59
+ path('shares') do
60
+ post do
61
+ Configuration::Share.create
62
+ redirect! '/settings'
63
+ end
64
+
65
+ var(:int) do |id|
66
+ delete do
67
+ share = manager.config.shares.find { |s| s.id == id }
68
+ halt 404 if share.nil?
69
+ share.destroy
70
+ redirect! '/settings'
71
+ end
72
+ end
73
+ end
74
+
75
+ var :int do |idx|
76
+ gd = gds[idx]
77
+ halt 404 if gd.nil?
78
+ file_path = URI.unescape(request.path_info)
79
+ file_ext = File.extname(file_path)
80
+ expanded_path = File.expand_path(".#{file_path}", gd.root)
81
+ message_file = File.expand_path(".gitmessage~", gd.root)
82
+ halt 400 unless expanded_path[/^#{Regexp.quote(gd.root)}/]
83
+ parent = File.dirname(file_path)
84
+ parent = '' if parent == '/'
85
+ parent = nil if parent == '.'
86
+ locals = {:idx => idx, :parent => parent, :root => gd.root, :file_path => expanded_path, :nav_state => nil }
87
+ mime = File.mime_type?(File.open(expanded_path)) if File.file?(expanded_path)
88
+ mode = request.params['mode']
89
+ if mode == 'meta' # Meta
90
+ halt 200, { 'Content-Type' => 'application/json' }, [gd.file_meta(file_path).to_json]
91
+ elsif mode == 'save' # Saving
92
+ File.open(expanded_path, 'w') { |f| f.print request.params['data'] }
93
+ File.open(message_file, 'w') { |f| f.print request.params['message'] } unless request.params['message'] == ''
94
+ redirect! "/" + idx.to_s + file_path
95
+ elsif mode == 'upload' # Uploading
96
+ halt 404 unless file = request.params['file']
97
+ tempfile, filename = file[:tempfile], file[:filename]
98
+ FileUtils.mv(tempfile.path, File.expand_path(filename, expanded_path))
99
+ redirect! "/" + idx.to_s + file_path + "/" + filename
100
+ elsif !File.exist?(expanded_path) && !request.params['dir'] # edit for non-existent file
101
+ FileUtils.mkdir_p(File.dirname(expanded_path))
102
+ FileUtils.touch(expanded_path)
103
+ redirect! "/" + idx.to_s + file_path + "?mode=edit"
104
+ elsif !File.exist?(expanded_path) && request.params['dir'] # create directory
105
+ FileUtils.mkdir_p(expanded_path)
106
+ redirect! "/" + idx.to_s + file_path
107
+ elsif File.directory?(expanded_path) # list directory
108
+ contents = gd.dir_files(expanded_path)
109
+ rendered_readme = nil
110
+ if readme = Dir[File.expand_path("README.{md,txt}", expanded_path)].first
111
+ rendered_readme = '<h3>' + File.basename(readme) + '</h3><div class="tilt">' + render(readme) + '</div>'
112
+ end
113
+ render! "dir", :layout => 'app', :locals => locals.merge(:contents => contents, :rendered_readme => rendered_readme)
114
+ elsif mode == "revisions" # list revisions
115
+ revisions = gd.file_revisions(file_path)
116
+ render! "revisions", :layout => 'app', :locals => locals.merge(:revisions => revisions)
117
+ elsif mode == "revert" # revert file
118
+ if revision = request.params['revision']
119
+ File.open(message_file, 'w') { |f| f.print "Reverting '#{file_path}' to #{revision}" }
120
+ gd.file_revert(file_path, revision)
121
+ end
122
+ redirect! "/" + idx.to_s + file_path
123
+ elsif mode == 'delete' # delete file
124
+ FileUtils.rm(expanded_path)
125
+ redirect! "/" + idx.to_s + parent
126
+ elsif mode == 'edit' && (mime.match(%r{text/}) || mime.match(%r{x-empty})) # edit file
127
+ contents = File.read(expanded_path)
128
+ render! "edit", :layout => 'app', :locals => locals.merge(:contents => contents)
129
+ elsif mode != 'raw' # render file
130
+ revision = request.params['revision']
131
+ expanded_path = gd.file_revision_at(file_path, revision) if revision
132
+ begin # attempting to render file
133
+ contents = '<div class="tilt">' + render(expanded_path) + '</div>'
134
+ rescue RuntimeError => e # not tilt supported
135
+ contents = if mime.match(%r{text/})
136
+ '<pre class="CodeRay">' + CodeRay.scan_file(expanded_path).encode(:html) + '</pre>'
137
+ else
138
+ %|<embed class="inline-file" src="/#{idx}#{request.path_info}?mode=raw"></embed>|
139
+ end
140
+ end
141
+ render! "file", :layout => 'app', :locals => locals.merge(:contents => contents,
142
+ :pygments_css => Pygments.css('.highlight'))
143
+ else # other file
144
+ run! Rack::File.new(gd.root)
145
+ end
146
+ end
147
+ end
148
+ }.setup {
149
+ views_path File.expand_path("../views", __FILE__)
150
+ }
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,3 @@
1
+ module Gitdocs
2
+ VERSION = "0.4.14.md"
3
+ end
@@ -0,0 +1,8 @@
1
+ <script src="/js/ace/ace.js" type="text/javascript" charset="utf-8"></script>
2
+ <script src="/js/ace/theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
3
+ <script src="/js/ace/mode-css.js" type="text/javascript" charset="utf-8"></script>
4
+ <script src="/js/ace/mode-html.js" type="text/javascript" charset="utf-8"></script>
5
+ <script src="/js/ace/mode-markdown.js" type="text/javascript" charset="utf-8"></script>
6
+ <script src="/js/ace/mode-javascript.js" type="text/javascript" charset="utf-8"></script>
7
+ <script src="/js/ace/mode-ruby.js" type="text/javascript" charset="utf-8"></script>
8
+ <script src="/js/edit.js" type="text/javascript" charset="utf-8"></script>
@@ -0,0 +1,14 @@
1
+ %h2.path
2
+ %span.path= request.path_info.empty? ? '/' : request.path_info
3
+ - if file
4
+ %ul{ :class => "tabs" }
5
+ %li
6
+ %a{ :href => "#{request.path}" } View
7
+ %li
8
+ %a{ :href => "?mode=raw" } Raw
9
+ %li
10
+ %a{ :href => "?mode=edit" } Edit
11
+ %li
12
+ %a{ :href => "?mode=revisions" } Revisions
13
+ %li
14
+ %a{ :href => "?mode=delete", :onclick => "javascript:return confirm('Are you sure?')" } Delete
@@ -0,0 +1,40 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %meta{ "http-equiv" => "content-type", :content => "text/html; charset=UTF-8" }
5
+ %title Gitdocs #{Gitdocs::VERSION}
6
+ %link{ :href => "/css/bootstrap.css", :rel => "stylesheet"}
7
+ %link{ :href => "/css/app.css", :rel => "stylesheet" }
8
+ %link{ :href => "/css/tilt.css", :rel => "stylesheet" }
9
+ %link{ :href => "/css/coderay.css", :rel => "stylesheet" }
10
+ %script{ :src => "/js/util.js", :type => "text/javascript", :charset => "utf-8" }
11
+ %script{ :src => "/js/jquery.js", :type => "text/javascript", :charset => "utf-8" }
12
+ %script{ :src => "/js/jquery.tablesorter.js", :type => "text/javascript", :charset => "utf-8" }
13
+ %script{ :src => "/js/bootstrap-alerts.js", :type => "text/javascript", :charset => "utf-8" }
14
+ %script{ :src => "/js/app.js", :type => "text/javascript", :charset => "utf-8" }
15
+ - if @css
16
+ %style{:type => "text/css"}
17
+ = @css
18
+ %body
19
+ #nav.topbar
20
+ .fill
21
+ .container
22
+ %a{:class => "brand", :href => "/"} Gitdocs
23
+ %ul(class="nav")
24
+ %li{ :class => ("active" if nav_state == "home") }
25
+ %a(href = "/") Home
26
+ %li{ :class => ("active" if nav_state == "settings") }
27
+ %a(href = "/settings") Settings
28
+ %form{:class => "pull-left", :action => "/search", :method => 'GET'}
29
+ %input{:type => "text", :placeholder => "Search", :name => 'q'}
30
+
31
+ #main.container
32
+ .content
33
+ - if @title
34
+ .page-header
35
+ %h1= @title
36
+ .row
37
+ .span16= preserve(yield)
38
+
39
+ %footer
40
+ %p &copy; Gitdocs v#{Gitdocs::VERSION}
@@ -0,0 +1,43 @@
1
+ - @title = root
2
+
3
+ = partial("header", :locals => { :parent => parent, :file => false, :idx => idx })
4
+
5
+ - if contents && contents.any?
6
+ %table#fileListing.condensed-table.zebra-striped
7
+ %thead
8
+ %tr
9
+ %th File
10
+ %th Author
11
+ %th Last Modified
12
+ %th Size
13
+
14
+ %tbody
15
+ - contents.each_with_index do |f, i|
16
+ %tr
17
+ %td
18
+ %img{ :src => "/img/#{f.dir? ? 'folder' : 'file'}.png", :width => 16, :height => 16 }
19
+ %a{ :href => "/#{idx}#{request.path_info}/#{f.name}" }
20
+ = f.name
21
+ %td.author
22
+ %td.modified
23
+ %td.size
24
+
25
+ - if contents.empty?
26
+ %p No files were found in this directory.
27
+
28
+ .row
29
+ .span6
30
+ %form.upload{ :method => "post", :enctype => "multipart/form-data", :action => "/#{idx}#{request.path_info}?mode=upload" }
31
+ %p Upload file to this directory
32
+ %input{:type => 'file', :value => "Select a file", :name => "file", :class => "uploader", :size => 12}
33
+ %input{:type => 'submit', :value => "Upload file", :class => "btn secondary" }
34
+ .span8
35
+ %form.add
36
+ %p Add new file or directory
37
+ %input{:type => 'text', :name => "path", :class => "edit", :placeholder => "somefile.md or somedir" }
38
+ %input{:type => 'submit', :value => "New file", :class => "btn secondary file" }
39
+ %input{:type => 'submit', :value => "New directory", :class => "btn secondary directory" }
40
+
41
+ - if rendered_readme
42
+ .contents
43
+ = preserve rendered_readme