repobrowse 0.0.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.
- checksums.yaml +7 -0
- data/.gitattributes +5 -0
- data/.gitignore +3 -0
- data/COPYING +661 -0
- data/GNUmakefile +38 -0
- data/MANIFEST +42 -0
- data/README +35 -0
- data/lib/repobrowse.rb +7 -0
- data/lib/repobrowse/app.rb +60 -0
- data/lib/repobrowse/config.rb +66 -0
- data/lib/repobrowse/error.rb +21 -0
- data/lib/repobrowse/escape.rb +28 -0
- data/lib/repobrowse/git.rb +71 -0
- data/lib/repobrowse/git_atom.rb +109 -0
- data/lib/repobrowse/git_commit_html.rb +320 -0
- data/lib/repobrowse/git_disambiguate.rb +21 -0
- data/lib/repobrowse/git_http_backend.rb +109 -0
- data/lib/repobrowse/git_log.rb +4 -0
- data/lib/repobrowse/git_patch.rb +55 -0
- data/lib/repobrowse/git_raw.rb +50 -0
- data/lib/repobrowse/git_raw_tree_html.rb +50 -0
- data/lib/repobrowse/git_show.rb +32 -0
- data/lib/repobrowse/git_src.rb +37 -0
- data/lib/repobrowse/git_src_blob_html.rb +89 -0
- data/lib/repobrowse/git_src_tree_html.rb +118 -0
- data/lib/repobrowse/html.rb +66 -0
- data/lib/repobrowse/limit_rd.rb +86 -0
- data/lib/repobrowse/pipe_body.rb +25 -0
- data/lib/repobrowse/repo.rb +21 -0
- data/lib/repobrowse/static.rb +96 -0
- data/repobrowse.gemspec +29 -0
- data/test/covshow.rb +30 -0
- data/test/git.fast-import-data +101 -0
- data/test/helper.rb +182 -0
- data/test/test_config.rb +29 -0
- data/test/test_git.rb +15 -0
- data/test/test_git_atom.rb +27 -0
- data/test/test_git_clone.rb +73 -0
- data/test/test_git_patch.rb +44 -0
- data/test/test_git_raw_tree_html.rb +34 -0
- data/test/test_git_show.rb +17 -0
- data/test/test_html.rb +23 -0
- metadata +139 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
require 'time'
|
|
6
|
+
require_relative 'pipe_body'
|
|
7
|
+
|
|
8
|
+
# included by Repobrowse::Git
|
|
9
|
+
module Repobrowse::GitPatch
|
|
10
|
+
|
|
11
|
+
# mboxrd support requires git 2.10+ (2016/09/02)
|
|
12
|
+
@@mboxrd = '--pretty=mboxrd'
|
|
13
|
+
|
|
14
|
+
# /$REPO_NAME/$HEX.patch
|
|
15
|
+
def patch(r, repo, objid)
|
|
16
|
+
rgd = repo.driver.rugged
|
|
17
|
+
cmt = rgd.rev_parse("#{objid}^{commit}") rescue nil
|
|
18
|
+
return r404(r) unless Rugged::Commit === cmt
|
|
19
|
+
time = cmt.author[:time]
|
|
20
|
+
cmt = cmt.oid
|
|
21
|
+
|
|
22
|
+
# disambiguate, expand abbreviated commit IDs to be cache-friendly
|
|
23
|
+
if objid != cmt
|
|
24
|
+
loc = r.base_url
|
|
25
|
+
loc << "#{r.script_name}/#{repo.name}/#{cmt}.patch"
|
|
26
|
+
return r.redirect(loc)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# command should match signature, so not using rugged, here;
|
|
30
|
+
# and mboxrd support is fairly recent in git.git. rugged/libgit2
|
|
31
|
+
# developers also do not use an email+patch-driven workflow, so I
|
|
32
|
+
# don't expect it to support patch formatting as well as git.git does
|
|
33
|
+
begin
|
|
34
|
+
cmd = %W(format-patch -M --stdout --encoding=utf8 -1 #{cmt})
|
|
35
|
+
mboxrd = @@mboxrd and cmd << mboxrd
|
|
36
|
+
cmd << "--signature=git #{cmd.join(' ')}"
|
|
37
|
+
io = repo.driver.popen(cmd)
|
|
38
|
+
if buf = io.read(0x2000)
|
|
39
|
+
h = {
|
|
40
|
+
'Content-Type' => 'text/plain; charset=UTF-8',
|
|
41
|
+
'Last-Modified' => (time || Time.now).httpdate,
|
|
42
|
+
'Expires' => (Time.now + 604800).httpdate,
|
|
43
|
+
'Content-Disposition' => %Q{inline; filename="#{cmt}.patch"},
|
|
44
|
+
}
|
|
45
|
+
return r.halt [ 200, h, Repobrowse::PipeBody.new(io, buf) ]
|
|
46
|
+
end
|
|
47
|
+
io.close
|
|
48
|
+
if mboxrd
|
|
49
|
+
@@mboxrd = nil
|
|
50
|
+
else
|
|
51
|
+
return r_err(r, 'Internal Server Error')
|
|
52
|
+
end
|
|
53
|
+
end while true
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require_relative 'escape'
|
|
7
|
+
require_relative 'git_raw_tree_html'
|
|
8
|
+
require_relative 'git_disambiguate'
|
|
9
|
+
require_relative 'pipe_body'
|
|
10
|
+
|
|
11
|
+
module Repobrowse::GitRaw
|
|
12
|
+
include Repobrowse::GitDisambiguate
|
|
13
|
+
|
|
14
|
+
# /$REPO_NAME/raw/$REF:$PATH
|
|
15
|
+
def raw(r, repo, ref, path)
|
|
16
|
+
git_disambiguate(r, repo, 'raw', ref, path)
|
|
17
|
+
h = { 'Content-Type' => 'text/plain; charset=UTF-8' }
|
|
18
|
+
spec = "#{ref}:#{path}"
|
|
19
|
+
rgd = rugged
|
|
20
|
+
begin
|
|
21
|
+
oid = rgd.rev_parse_oid(spec)
|
|
22
|
+
hdr = rgd.read_header(oid)
|
|
23
|
+
rescue
|
|
24
|
+
return r404(r)
|
|
25
|
+
end
|
|
26
|
+
type = hdr[:type]
|
|
27
|
+
if type == :tree
|
|
28
|
+
body = Repobrowse::GitRawTreeHTML.new(rgd.lookup(oid), ref, path)
|
|
29
|
+
h['Content-Type'] = 'text/html; charset=UTF-8'
|
|
30
|
+
else
|
|
31
|
+
fn = path.split('/')[-1]
|
|
32
|
+
h['ETag'] = %Q{"#{oid}"}
|
|
33
|
+
len = hdr[:len]
|
|
34
|
+
h['Content-Length'] = len.to_s
|
|
35
|
+
h['Content-Disposition'] = %Q{inline; filename="#{fn}"}
|
|
36
|
+
if len < 512 * 1024
|
|
37
|
+
body = rgd.lookup(oid).content
|
|
38
|
+
peek = body[0, 8000]
|
|
39
|
+
body = [ body ]
|
|
40
|
+
else # large objects
|
|
41
|
+
io = repo.driver.popen(%Q(cat-file -t #{type} #{oid}))
|
|
42
|
+
peek = io.read(8000) or return r404(r)
|
|
43
|
+
body = Repobrowse::PipeBody.new(io, peek)
|
|
44
|
+
end
|
|
45
|
+
peek.include?("\0") and
|
|
46
|
+
h['Content-Type'] = 'application/octet-stream'
|
|
47
|
+
end
|
|
48
|
+
r.halt [ 200, h, body ]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
require_relative 'escape'
|
|
6
|
+
|
|
7
|
+
# ugly "raw" tree view, "git_src_tree_html" has the prettier one
|
|
8
|
+
class Repobrowse::GitRawTreeHTML
|
|
9
|
+
include Repobrowse::Escape
|
|
10
|
+
|
|
11
|
+
def initialize(tree, ref, path)
|
|
12
|
+
@tree = tree
|
|
13
|
+
@ref = ref
|
|
14
|
+
@path = path # nil ok
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def each
|
|
18
|
+
if @path
|
|
19
|
+
t = ht(+"/#@path/")
|
|
20
|
+
path = @path.split('/')
|
|
21
|
+
case path.size
|
|
22
|
+
when 1
|
|
23
|
+
ref = CGI.escape(@ref.split('/')[-1])
|
|
24
|
+
pfx = "./#{ref}:#{CGI.escape(path[0])}/"
|
|
25
|
+
up = ha('./' + ref)
|
|
26
|
+
else
|
|
27
|
+
pfx = "#{CGI.escape(path[-1])}/"
|
|
28
|
+
up = "#@ref:#@path".split('/')[-2]
|
|
29
|
+
up = up.split(':').map! { |u| CGI.escape(u) }.join(':')
|
|
30
|
+
up = ha('../' + up)
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
t = '/'
|
|
34
|
+
pfx = "./#{CGI.escape(@ref.split('/')[-1])}:"
|
|
35
|
+
end
|
|
36
|
+
yield "<html><head><title>#{t}</title></head><body><h2>#{t}</h2><ul>"
|
|
37
|
+
yield %Q(<li><a\nhref=#{up}>../</a></li>) if up
|
|
38
|
+
@tree.each do |ent|
|
|
39
|
+
name = ent[:name]
|
|
40
|
+
href = ha(pfx + CGI.escape(name))
|
|
41
|
+
name = ht(name)
|
|
42
|
+
yield %Q(<li><a\nhref=#{href}>#{name}</a></li>)
|
|
43
|
+
end
|
|
44
|
+
yield '</ul></body></html>'
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def close
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require_relative 'git_commit_html'
|
|
7
|
+
|
|
8
|
+
module Repobrowse::GitShow
|
|
9
|
+
|
|
10
|
+
def show(r, repo, objid)
|
|
11
|
+
rgd = repo.driver.rugged
|
|
12
|
+
begin
|
|
13
|
+
oid = rgd.rev_parse_oid(objid)
|
|
14
|
+
rescue
|
|
15
|
+
return r404(r)
|
|
16
|
+
end
|
|
17
|
+
# disambiguate, expand abbreviated commit IDs to be cache-friendly
|
|
18
|
+
if oid != objid
|
|
19
|
+
return r.redirect("#{r.base_url}#{r.script_name}/#{repo.name}/#{oid}")
|
|
20
|
+
end
|
|
21
|
+
hdr = rgd.read_header(oid)
|
|
22
|
+
case hdr[:type]
|
|
23
|
+
when :commit
|
|
24
|
+
html = Repobrowse::GitCommitHTML.new
|
|
25
|
+
html.commit_header(r.env, repo, rgd.lookup(oid))
|
|
26
|
+
r.halt html.response(200)
|
|
27
|
+
# TODO: other types
|
|
28
|
+
else
|
|
29
|
+
r404(r)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require_relative 'git_src_blob_html'
|
|
7
|
+
require_relative 'git_src_tree_html'
|
|
8
|
+
require_relative 'git_disambiguate'
|
|
9
|
+
|
|
10
|
+
module Repobrowse::GitSrc
|
|
11
|
+
include Repobrowse::GitDisambiguate
|
|
12
|
+
|
|
13
|
+
MAX = 1024 * 1024
|
|
14
|
+
|
|
15
|
+
# /$REPO_NAME/src/$REF:$PATH
|
|
16
|
+
def src(r, repo, ref, path)
|
|
17
|
+
git_disambiguate(r, repo, 'src', ref, path)
|
|
18
|
+
spec = "#{ref}:#{path}"
|
|
19
|
+
rgd = rugged
|
|
20
|
+
begin
|
|
21
|
+
oid = rgd.rev_parse_oid(spec)
|
|
22
|
+
hdr = rgd.read_header(oid)
|
|
23
|
+
rescue => e
|
|
24
|
+
warn e.message
|
|
25
|
+
return r404(r)
|
|
26
|
+
end
|
|
27
|
+
h = { 'ETag' => %Q{"#{oid}"} }
|
|
28
|
+
case hdr[:type]
|
|
29
|
+
when :tree
|
|
30
|
+
tree = rgd.lookup(oid)
|
|
31
|
+
html = Repobrowse::GitSrcTreeHTML.new(tree, ref, path, repo)
|
|
32
|
+
when :blob
|
|
33
|
+
html = Repobrowse::GitSrcBlobHTML.new(hdr, oid, ref, path, repo)
|
|
34
|
+
end
|
|
35
|
+
r.halt(html.response(200, h))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
require_relative 'html'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
class Repobrowse::GitSrcBlobHTML < Repobrowse::HTML
|
|
9
|
+
def initialize(hdr, oid, ref, path, repo)
|
|
10
|
+
super()
|
|
11
|
+
@repo = repo
|
|
12
|
+
ref_parts = ref.split('/')
|
|
13
|
+
ref_uri = ref_parts.map { |s| CGI.escape(s) }
|
|
14
|
+
ref_html = ht(ref.dup)
|
|
15
|
+
path_parts = path.split('/')
|
|
16
|
+
path_uri = path_parts.map { |s| CGI.escape(s) }
|
|
17
|
+
path_html = ht(path.dup)
|
|
18
|
+
relup = (2..(ref_parts.size + path_parts.size)).map { '../' }.join
|
|
19
|
+
raw = +"#{relup}/raw/#{ref_uri.join('/')}:#{path_uri.join('/')}"
|
|
20
|
+
path_uri[0] = "#{ref_uri[-1]}:#{path_uri[0]}" if path_uri[0]
|
|
21
|
+
relup = (2..path_parts.size).map { '../' }.join
|
|
22
|
+
base = path_parts.pop
|
|
23
|
+
_base_uri = path_uri.pop
|
|
24
|
+
tmp = []
|
|
25
|
+
path_parts.map! do |x|
|
|
26
|
+
tmp << path_uri.shift
|
|
27
|
+
href = +"#{relup}#{tmp.join('/')}"
|
|
28
|
+
%Q(<a\nhref=#{ha(href)}>#{ht(x)}</a>)
|
|
29
|
+
end
|
|
30
|
+
path_parts << "<b>#{ht(base)}</b>"
|
|
31
|
+
@buf = start("#{ref_html}:#{path_html}", repo)
|
|
32
|
+
len = hdr[:len]
|
|
33
|
+
@buf << <<EOS
|
|
34
|
+
<a
|
|
35
|
+
href=#{ha(relup + ref_uri[-1])}>#{ref_html}</a> : #{path_parts.join(' / ')}
|
|
36
|
+
|
|
37
|
+
blob #{oid}\t#{len} bytes (<a
|
|
38
|
+
href=#{ha(raw)}>raw</a>)</pre><hr/>
|
|
39
|
+
EOS
|
|
40
|
+
@buf.chomp!
|
|
41
|
+
if len < (512 * 1024)
|
|
42
|
+
@oid = oid
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Called by the rack server
|
|
47
|
+
def each
|
|
48
|
+
if @buf
|
|
49
|
+
yield @buf
|
|
50
|
+
@buf.clear
|
|
51
|
+
@buf = nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# TODO: optional syntax highlighting
|
|
55
|
+
# highlight(1) supports streaming; but needs be spawned
|
|
56
|
+
# highlight should also be familiar to gitweb and cgit users
|
|
57
|
+
# rouge allows streaming output, but not input :<
|
|
58
|
+
# coderay doesn't seem to support streaming at all
|
|
59
|
+
if @oid
|
|
60
|
+
body = @repo.driver.rugged.lookup(@oid).content
|
|
61
|
+
if body[0, 8000].include?("\0")
|
|
62
|
+
yield "<pre>Binary file, save using the 'raw' link above</pre>"
|
|
63
|
+
else
|
|
64
|
+
yield '<table><tr><td><pre>'
|
|
65
|
+
n = body.count("\n")
|
|
66
|
+
yield ht(body)
|
|
67
|
+
unless body.end_with?("\n")
|
|
68
|
+
n += 1
|
|
69
|
+
yield "\n\"
|
|
70
|
+
end
|
|
71
|
+
yield '</pre></td><td><pre>'
|
|
72
|
+
pfx = @repo.lineno_prefix
|
|
73
|
+
(1..n).each do |i|
|
|
74
|
+
anchor = "#{pfx}#{i}"
|
|
75
|
+
yield %Q(<a\nid=#{anchor}\nhref="##{anchor}">#{i}</a>\n)
|
|
76
|
+
end
|
|
77
|
+
yield '</pre></td></tr></table>'
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
yield "<pre>File is too big to display, save using the 'raw' link</pre>"
|
|
81
|
+
end
|
|
82
|
+
yield '</body></html>'
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# called by the Rack server
|
|
87
|
+
def close
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
require_relative 'html'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
class Repobrowse::GitSrcTreeHTML < Repobrowse::HTML
|
|
9
|
+
def initialize(tree, ref, path, repo)
|
|
10
|
+
super()
|
|
11
|
+
@tree = tree
|
|
12
|
+
@repo = repo
|
|
13
|
+
ref_parts = ref.split('/')
|
|
14
|
+
ref_uri = ref_parts.map { |s| CGI.escape(s) }
|
|
15
|
+
ref_html = ht(ref.dup)
|
|
16
|
+
if path
|
|
17
|
+
path_parts = path.split('/')
|
|
18
|
+
path_uri = path_parts.map { |s| CGI.escape(s) }
|
|
19
|
+
path_html = ht(path.dup)
|
|
20
|
+
path_uri[0] = "#{ref_uri[-1]}:#{path_uri[0]}" if path_uri[0]
|
|
21
|
+
relup = (2..path_parts.size).map { '../' }.join
|
|
22
|
+
base = path_parts.pop
|
|
23
|
+
base_uri = path_uri.pop
|
|
24
|
+
tmp = []
|
|
25
|
+
path_parts.map! do |x|
|
|
26
|
+
tmp << path_uri.shift
|
|
27
|
+
href = ha(+"#{relup}#{tmp.join('/')}")
|
|
28
|
+
%Q(<a\nhref=#{href}>#{ht(x)}</a>)
|
|
29
|
+
end
|
|
30
|
+
path_parts << "<b>#{ht(base)}</b>"
|
|
31
|
+
@rel_prefix = "./#{base_uri}/"
|
|
32
|
+
else
|
|
33
|
+
path_parts = [ '[root]' ]
|
|
34
|
+
relup = ''
|
|
35
|
+
@rel_prefix = "./#{ref_uri[-1]}:"
|
|
36
|
+
end
|
|
37
|
+
@buf = start("#{ref_html}:#{path_html}", repo)
|
|
38
|
+
@buf << <<EOS
|
|
39
|
+
<a
|
|
40
|
+
href=#{ha(relup + ref_uri[-1])}>#{ref_html}</a> : #{path_parts.join(' / ')}
|
|
41
|
+
|
|
42
|
+
tree #{@tree.oid}</pre><hr/><pre>
|
|
43
|
+
EOS
|
|
44
|
+
@buf.chomp!
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def fmt_ent(ent, rgd, parts)
|
|
48
|
+
name = ent[:name]
|
|
49
|
+
name_text = ht(name.dup)
|
|
50
|
+
case ent[:filemode]
|
|
51
|
+
when 0100644 # plain blob, nothing special
|
|
52
|
+
md = ' '
|
|
53
|
+
when 0040000
|
|
54
|
+
return do_descend(ent, rgd) unless parts
|
|
55
|
+
md = 'd'
|
|
56
|
+
len = '-'
|
|
57
|
+
when 0100755
|
|
58
|
+
md = 'x'
|
|
59
|
+
name_text = "<b>#{name_text}</b>"
|
|
60
|
+
when 0120000
|
|
61
|
+
md = 'l'
|
|
62
|
+
name_text = "<i>#{name_text}</i>"
|
|
63
|
+
when 0160000
|
|
64
|
+
md = 'g'
|
|
65
|
+
name_text = "#{name_text} <u>@ #{ent[:oid]}</u>"
|
|
66
|
+
len = '-'
|
|
67
|
+
else
|
|
68
|
+
warn "unknown mode: #{'%06o' % ent[:filemode]}"
|
|
69
|
+
end
|
|
70
|
+
len ||= rgd.read_header(ent[:oid])[:len].to_s
|
|
71
|
+
if parts && dir = parts.shift
|
|
72
|
+
href = @rel_prefix + URI.escape(dir)
|
|
73
|
+
dir = "<a\nhref=#{ha(href.dup)}>#{ht(dir)}</a>"
|
|
74
|
+
parts.map! do |part|
|
|
75
|
+
href << "/#{URI.escape(part)}"
|
|
76
|
+
"<a\nhref=#{ha(href.dup)}>#{ht(part)}</a>"
|
|
77
|
+
end
|
|
78
|
+
parts.unshift(dir)
|
|
79
|
+
href << "/#{URI.escape(name)}"
|
|
80
|
+
parts << "<a\nhref=#{ha(href)}>#{name_text}</a>"
|
|
81
|
+
rest = parts.join(' / ')
|
|
82
|
+
else
|
|
83
|
+
rest = "<a\nhref=#{ha(@rel_prefix + URI.escape(name))}>#{name_text}</a>"
|
|
84
|
+
end
|
|
85
|
+
"#{md} #{sprintf('% 8s', len)}\t#{rest}\n"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def do_descend(ent, rgd)
|
|
89
|
+
parts = []
|
|
90
|
+
begin
|
|
91
|
+
tree = rgd.lookup(ent[:oid])
|
|
92
|
+
if tree.count != 1
|
|
93
|
+
break
|
|
94
|
+
else
|
|
95
|
+
parts << ent[:name]
|
|
96
|
+
ent = tree.get_entry(0)
|
|
97
|
+
ent[:filemode] == 0040000 or break
|
|
98
|
+
end
|
|
99
|
+
end while true
|
|
100
|
+
fmt_ent(ent, rgd, parts)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# called by the Rack server
|
|
104
|
+
def each
|
|
105
|
+
yield @buf
|
|
106
|
+
@buf.clear
|
|
107
|
+
rgd = @repo.driver.rugged
|
|
108
|
+
@tree.each do |ent|
|
|
109
|
+
yield fmt_ent(ent, rgd, nil)
|
|
110
|
+
end
|
|
111
|
+
yield '</pre></body></html>'
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# called by the Rack server
|
|
116
|
+
def close
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require_relative 'escape'
|
|
7
|
+
|
|
8
|
+
# used to give each HTML page a consistent look, this is a Rack response body
|
|
9
|
+
class Repobrowse::HTML
|
|
10
|
+
attr_reader :buf # meant for appending to via String#<<
|
|
11
|
+
|
|
12
|
+
include Repobrowse::Escape
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@buf = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def start(title_html, desc, robots: nil)
|
|
19
|
+
meta = %Q(<meta\nname=robots\ncontent="#{robots}" />) if robots
|
|
20
|
+
@buf = +<<~EOF
|
|
21
|
+
<!DOCTYPE html>
|
|
22
|
+
<html><head><title>#{
|
|
23
|
+
title_html
|
|
24
|
+
}</title><style>pre{white-space:pre-wrap}</style>#{
|
|
25
|
+
meta
|
|
26
|
+
}</head><body><pre><b>#{
|
|
27
|
+
case desc
|
|
28
|
+
when String, nil
|
|
29
|
+
desc
|
|
30
|
+
else
|
|
31
|
+
desc.driver.description.encode(xml: :text)
|
|
32
|
+
end
|
|
33
|
+
}</b>
|
|
34
|
+
EOF
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
ESCAPES = { '/' => ':' }
|
|
38
|
+
(0..255).each { |x|
|
|
39
|
+
key = -x.chr
|
|
40
|
+
next if key =~ %r{\A[\w\.\-/]}n
|
|
41
|
+
ESCAPES[key] = -sprintf('::%02x', x)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def to_anchor(str)
|
|
45
|
+
str = str.dup
|
|
46
|
+
first = ''
|
|
47
|
+
# must start with alphanum
|
|
48
|
+
str.sub!(/\A([^A-Ya-z])/n, '') and first = sprintf('Z%02x', $1.ord)
|
|
49
|
+
str.gsub!(/([^\w\.\-])/n, ESCAPES)
|
|
50
|
+
"#{first}#{str}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def from_anchor(str)
|
|
54
|
+
str = str.dup
|
|
55
|
+
first = ''
|
|
56
|
+
str.sub!(/\AZ([a-f0-9]{2})/n, '') and first = -$1.hex.chr
|
|
57
|
+
str.gsub!(/::([a-f0-9]{2})/n) { $1.hex.chr }
|
|
58
|
+
str.tr!(':', '/')
|
|
59
|
+
"#{first}#{str}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def response(code, headers = {})
|
|
63
|
+
headers['Content-Type'] ||= 'text/html; charset=UTF-8'
|
|
64
|
+
[ 200, headers, self ]
|
|
65
|
+
end
|
|
66
|
+
end
|