trac-export-wiki 1.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.
- data/LICENSE +22 -0
- data/README.md +36 -0
- data/Rakefile +33 -0
- data/bin/trac-export-wiki +3 -0
- data/examples/config.yaml +42 -0
- data/examples/config.yaml.sample +58 -0
- data/lib/trac-export-wiki.rb +234 -0
- metadata +82 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010 Loren Segal
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Trac Wiki Exporter
|
2
|
+
|
3
|
+
## Synopsis
|
4
|
+
|
5
|
+
Exports wiki pages in Trac as HTML pages organized in categories for easy
|
6
|
+
local viewing.
|
7
|
+
|
8
|
+
## Installing
|
9
|
+
|
10
|
+
$ [sudo] gem install trac-export-wiki
|
11
|
+
|
12
|
+
## Using
|
13
|
+
|
14
|
+
To use this script, you need to create a `config.yaml` file that tells the
|
15
|
+
exporter which pages you want to export under which categories. You can
|
16
|
+
see a sample config.yaml file in the "examples" directory. Once you've
|
17
|
+
created your configuration file, you can call the script with:
|
18
|
+
|
19
|
+
$ trac-export-wiki config.yaml
|
20
|
+
|
21
|
+
This will download the wiki pages from your site into your current directory.
|
22
|
+
If you want to put your files in a `docs` directory, make sure to cd there
|
23
|
+
first:
|
24
|
+
|
25
|
+
$ mkdir docs
|
26
|
+
$ cd docs
|
27
|
+
$ trac-export-wiki ../config.yaml
|
28
|
+
|
29
|
+
You can specify some parameters on the command-line, see `trac-export-wiki --help`
|
30
|
+
for a list of options. Specifically, you may want to only regenerate the index
|
31
|
+
page, or not generate the index page. This is done with `-i` or `-n` respectively.
|
32
|
+
|
33
|
+
## License & Author
|
34
|
+
|
35
|
+
This library is written by Loren Segal and released under the MIT license.
|
36
|
+
See the `LICENSE` file attached with this archive.
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
WINDOWS = (Config::CONFIG['host_os'] =~ /mingw|win32|cygwin/ ? true : false) rescue false
|
4
|
+
SUDO = WINDOWS ? '' : 'sudo'
|
5
|
+
|
6
|
+
task :default => :specs
|
7
|
+
|
8
|
+
desc "Builds the gem"
|
9
|
+
task :gem do
|
10
|
+
load 'trac-export-wiki.gemspec'
|
11
|
+
Gem::Builder.new(SPEC).build
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Installs the gem"
|
15
|
+
task :install => :gem do
|
16
|
+
sh "#{SUDO} gem install trac-export-wiki-1.0.0.gem --no-rdoc --no-ri"
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
require 'spec'
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
|
23
|
+
desc "Run all specs"
|
24
|
+
Spec::Rake::SpecTask.new("specs") do |t|
|
25
|
+
$DEBUG = true if ENV['DEBUG']
|
26
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
27
|
+
t.spec_opts += ["--require", File.join(File.dirname(__FILE__), 'spec', 'spec_helper')]
|
28
|
+
t.spec_files = Dir["spec/**/*_spec.rb"].sort
|
29
|
+
end
|
30
|
+
task :spec => :specs
|
31
|
+
rescue LoadError
|
32
|
+
warn "warn: RSpec tests not available. `gem install rspec` to enable them."
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#username: username_here
|
2
|
+
#password: password_here
|
3
|
+
base_url: http://example.com/trac/soen490
|
4
|
+
pages:
|
5
|
+
mgmt:
|
6
|
+
- BiWeeklyStatusReport
|
7
|
+
- Proposal
|
8
|
+
- ActivityPlan
|
9
|
+
- RiskManagement
|
10
|
+
- ProcessDocumentation
|
11
|
+
- ImpactAnalysis
|
12
|
+
research:
|
13
|
+
- CachingStrategies
|
14
|
+
- CachingEvaluation
|
15
|
+
- LoadBalancing
|
16
|
+
- PlanForDistributedReq
|
17
|
+
- ProversBenchmark
|
18
|
+
- ProfilingResults
|
19
|
+
reqs:
|
20
|
+
- VisionDocument
|
21
|
+
- SupplementarySpecification
|
22
|
+
- InstallationAndCommissioning
|
23
|
+
- Glossary
|
24
|
+
design:
|
25
|
+
- Jml4-LogicalView
|
26
|
+
- FirstPrototype
|
27
|
+
- Jml4Disco-LogicalView
|
28
|
+
- Jml4Disco-PhysicalView
|
29
|
+
- Jml4Disco-UseCaseView
|
30
|
+
- DevelopmentView
|
31
|
+
test:
|
32
|
+
- TestPlan
|
33
|
+
- BoogieTraceability
|
34
|
+
- PerformanceReport
|
35
|
+
categories:
|
36
|
+
mgmt: 'Management, Planning & Risk Analysis Docs'
|
37
|
+
research: 'Research & Potential Strategies Docs'
|
38
|
+
reqs: 'Requirements Docs'
|
39
|
+
design: 'Design & Architecture'
|
40
|
+
test: 'Testing & Implementation'
|
41
|
+
wiki_title: JML4 Disco Documentation
|
42
|
+
wiki_title_prefix: Disco
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# The base URL to the trac page (excluding /wiki)
|
2
|
+
base_url: http://example.com/trac/myproject
|
3
|
+
|
4
|
+
# Optional authentication information, uncomment below.
|
5
|
+
# You can also specify this information via the command-line (-u/-p).
|
6
|
+
#username: username_here
|
7
|
+
#password: password_here
|
8
|
+
|
9
|
+
# The list of pages within separated categories
|
10
|
+
# The category should be listed first, followed by a list of pages
|
11
|
+
pages:
|
12
|
+
# Make sure to include the category short-name first
|
13
|
+
mgmt:
|
14
|
+
- BiWeeklyStatusReport
|
15
|
+
- Proposal
|
16
|
+
- ActivityPlan
|
17
|
+
- RiskManagement
|
18
|
+
- ProcessDocumentation
|
19
|
+
- ImpactAnalysis
|
20
|
+
research:
|
21
|
+
- CachingStrategies
|
22
|
+
- CachingEvaluation
|
23
|
+
- LoadBalancing
|
24
|
+
- PlanForDistributedReq
|
25
|
+
- ProversBenchmark
|
26
|
+
- ProfilingResults
|
27
|
+
reqs:
|
28
|
+
- VisionDocument
|
29
|
+
- SupplementarySpecification
|
30
|
+
- InstallationAndCommissioning
|
31
|
+
- Glossary
|
32
|
+
design:
|
33
|
+
- Jml4-LogicalView
|
34
|
+
- FirstPrototype
|
35
|
+
- Jml4Disco-LogicalView
|
36
|
+
- Jml4Disco-PhysicalView
|
37
|
+
- Jml4Disco-UseCaseView
|
38
|
+
- DevelopmentView
|
39
|
+
test:
|
40
|
+
- TestPlan
|
41
|
+
- BoogieTraceability
|
42
|
+
- PerformanceReport
|
43
|
+
|
44
|
+
# The full titles and order of the category short-names. These titles will be
|
45
|
+
# listed in the order they are given on the index.html page.
|
46
|
+
categories:
|
47
|
+
mgmt: 'Management, Planning & Risk Analysis Docs'
|
48
|
+
research: 'Research & Potential Strategies Docs'
|
49
|
+
reqs: 'Requirements Docs'
|
50
|
+
design: 'Design & Architecture'
|
51
|
+
test: 'Testing & Implementation'
|
52
|
+
|
53
|
+
# The wiki title listed on the index.html page
|
54
|
+
wiki_title: JML4 Disco Documentation
|
55
|
+
|
56
|
+
# The wiki short title used in the <title> of individual wiki pages
|
57
|
+
# in the form: "SHORT_TITLE - WIKI_PAGE_TITLE"
|
58
|
+
wiki_title_prefix: Disco
|
@@ -0,0 +1,234 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'net/https'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'hpricot'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
module TracWiki
|
9
|
+
class HashStruct < Hash
|
10
|
+
def self.[](*hash)
|
11
|
+
case hash.first
|
12
|
+
when Hash
|
13
|
+
super(*hash.first.map {|k, v| [k.to_sym, v] }.flatten(1))
|
14
|
+
else
|
15
|
+
i = 0
|
16
|
+
super(*hash.map {|e| e = i % 2 == 0 ? e.to_sym : e; i += 1; e })
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(key, value)
|
21
|
+
super(key.to_sym, value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(sym, *args, &block)
|
25
|
+
if has_key?(sym.to_sym)
|
26
|
+
self[sym.to_sym]
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
DefaultConfiguration = HashStruct[
|
34
|
+
:username => nil,
|
35
|
+
:password => nil,
|
36
|
+
:base_url => nil,
|
37
|
+
:destination_path => '.',
|
38
|
+
:pages => {},
|
39
|
+
:categories => [],
|
40
|
+
:wiki_title => "Trac Wiki Pages",
|
41
|
+
:wiki_title_prefix => "Wiki",
|
42
|
+
:no_index => false,
|
43
|
+
:only_index => false,
|
44
|
+
].freeze
|
45
|
+
|
46
|
+
class CLI
|
47
|
+
attr_accessor :config
|
48
|
+
|
49
|
+
def initialize
|
50
|
+
self.config = DefaultConfiguration.dup
|
51
|
+
end
|
52
|
+
|
53
|
+
def run(*args)
|
54
|
+
args = args.dup
|
55
|
+
opts = OptionParser.new
|
56
|
+
opts.banner = "Usage: trac-export-wiki [options] config.yaml"
|
57
|
+
opts.on('-u', '--username USERNAME') {|username| config[:username] = username }
|
58
|
+
opts.on('-p', '--password PASSWORD') {|password| config[:password] = password }
|
59
|
+
opts.on('-b', '--base-url URL') {|url| config[:base_url] = url }
|
60
|
+
opts.on('-t', '--wiki-title TITLE') {|title| config[:wiki_title] = title }
|
61
|
+
opts.on('-T', '--wiki-title-prefix TITLE') {|title| config[:wiki_title_prefix] = title }
|
62
|
+
opts.on('-i', '--only-index') { config[:only_index] = true }
|
63
|
+
opts.on('-n', '--no-index') { config[:no_index] = true }
|
64
|
+
opts.parse!(args)
|
65
|
+
parse_from_yaml(args.first) if args.size > 0
|
66
|
+
Exporter.new(config).export
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def parse_from_yaml(file)
|
72
|
+
require 'yaml'
|
73
|
+
config.update HashStruct[YAML.load_file(file)]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Exporter
|
78
|
+
# common HTML elements to remove (expressed with css selectors)
|
79
|
+
ELEMENTS_TO_REMOVE = ["html > head > link",
|
80
|
+
"html > head > style",
|
81
|
+
"html > head > script",
|
82
|
+
"html > body > script",
|
83
|
+
"div#banner",
|
84
|
+
"div#header",
|
85
|
+
"div#search",
|
86
|
+
"div#ctxtnav",
|
87
|
+
"div#metanav",
|
88
|
+
"div#mainnav",
|
89
|
+
"div.buttons",
|
90
|
+
"div#altlinks",
|
91
|
+
"div#footer",
|
92
|
+
"h3#tkt-changes-hdr",
|
93
|
+
"ul.tkt-chg-list"]
|
94
|
+
|
95
|
+
attr_accessor :config
|
96
|
+
|
97
|
+
def initialize(config = DefaultConfiguration)
|
98
|
+
self.config = config
|
99
|
+
end
|
100
|
+
|
101
|
+
def export
|
102
|
+
write_pages unless config.only_index
|
103
|
+
generate_index unless config.no_index
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def write_pages
|
109
|
+
config.pages.each do |category, page_names|
|
110
|
+
page_names.each do |page|
|
111
|
+
print "Exporting \"" + page + "\"... "
|
112
|
+
Page.new(page, category).export(config)
|
113
|
+
puts "done."
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def generate_index
|
119
|
+
print "Exporting index..."
|
120
|
+
index = <<-eof
|
121
|
+
<html>
|
122
|
+
<head>
|
123
|
+
<title>#{config.wiki_title}</title>
|
124
|
+
</head>
|
125
|
+
<body>
|
126
|
+
<h1>#{config.wiki_title}</h1>
|
127
|
+
eof
|
128
|
+
config.categories.each do |line|
|
129
|
+
cat, name = *line
|
130
|
+
index += "<h2>#{name}</h2>\n"
|
131
|
+
index += "<ul>\n"
|
132
|
+
config.pages.select {|k,v| k == cat }.each do |cat, docs|
|
133
|
+
docs.each do |doc|
|
134
|
+
fname = Page.new(doc, cat).filename
|
135
|
+
index += "<li><a href='#{fname}'>#{doc}</a>\n"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
index += "</ul>\n"
|
139
|
+
end
|
140
|
+
index += <<-eof
|
141
|
+
</body>
|
142
|
+
</html>
|
143
|
+
eof
|
144
|
+
File.open('index.html', "w") { |f| f.write(index) }
|
145
|
+
|
146
|
+
puts "done."
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class Page
|
151
|
+
attr_accessor :page_title, :category, :filename, :config
|
152
|
+
|
153
|
+
def initialize(page_title, category = nil)
|
154
|
+
self.page_title = page_title
|
155
|
+
self.category = category
|
156
|
+
self.filename = File.join(*[category, page_title.gsub(/([a-z])([A-Z])/,'\1-\2').split(/\?/).first.downcase + '.html'].compact)
|
157
|
+
end
|
158
|
+
|
159
|
+
def export(config)
|
160
|
+
self.config = config
|
161
|
+
|
162
|
+
# load the wiki page
|
163
|
+
doc = Hpricot(read_asset(page_title))
|
164
|
+
|
165
|
+
# search for each element and remove it from the doc
|
166
|
+
Exporter::ELEMENTS_TO_REMOVE.each { |e| doc.search(e).remove }
|
167
|
+
|
168
|
+
# set title
|
169
|
+
doc.search("html > head").at("title").inner_html = "#{config.wiki_title_prefix} - " + page_title.gsub(/([a-z])([A-Z])/,'\1 \2')
|
170
|
+
|
171
|
+
# add link to css
|
172
|
+
updir = "../" * category.split(/\//).size
|
173
|
+
css = %Q(<link rel="stylesheet" type="text/css" href="#{updir}style.css" />)
|
174
|
+
charset = %Q(<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />)
|
175
|
+
doc.search("html > head").append(css + charset)
|
176
|
+
|
177
|
+
# give toc's parent ol a class
|
178
|
+
ol = doc.search("html > body > div.wiki-toc > ol").first
|
179
|
+
ol.raw_attributes = ol.attributes.to_hash.merge('class' => 'top-most') unless ol.nil?
|
180
|
+
|
181
|
+
# change the toc's li's class names
|
182
|
+
doc.search("html > body > div.wiki-toc > ol").search("li.active").set(:class => 'toc') rescue nil
|
183
|
+
|
184
|
+
# create category directory if it does not exist
|
185
|
+
FileUtils.mkdir_p(File.dirname(filename)) rescue nil
|
186
|
+
|
187
|
+
# find all images
|
188
|
+
doc.search("//img").each do |img|
|
189
|
+
imgfile = img.attributes['src']
|
190
|
+
short_imgfile = File.basename(imgfile).split(/\?/).first
|
191
|
+
|
192
|
+
# change image attribute in source
|
193
|
+
img.raw_attributes = img.attributes.to_hash.merge("src" => File.join('images', short_imgfile))
|
194
|
+
|
195
|
+
# make image directory
|
196
|
+
outdir = File.join(File.dirname(filename), 'images')
|
197
|
+
FileUtils.mkdir_p(outdir)
|
198
|
+
|
199
|
+
# write image to file
|
200
|
+
begin
|
201
|
+
uri = URI.parse(config.base_url)
|
202
|
+
contents = read_asset(imgfile, "#{uri.scheme}://#{uri.host}")
|
203
|
+
File.open(File.join(outdir, short_imgfile), "wb") do |f|
|
204
|
+
f.write(contents)
|
205
|
+
end
|
206
|
+
rescue OpenURI::HTTPError
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# write HTML to file
|
211
|
+
File.open(filename, "w") { |f| f.write(doc.to_html) }
|
212
|
+
print "wrote #{filename}... "
|
213
|
+
rescue StandardError => bang
|
214
|
+
print "(Oops! " + bang.message + ") "
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
def read_asset(asset, base = nil)
|
220
|
+
base ||= File.join(config.base_url, "wiki")
|
221
|
+
open(File.join(base, asset), open_options).read
|
222
|
+
end
|
223
|
+
|
224
|
+
def open_options
|
225
|
+
@open_options ||= config.username ?
|
226
|
+
{:http_basic_authentication => [config.username, config.password]} : {}
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
class Net::HTTP
|
232
|
+
alias :old_verify_mode :verify_mode=
|
233
|
+
def verify_mode=(x) old_verify_mode(OpenSSL::SSL::VERIFY_NONE) end
|
234
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trac-export-wiki
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Loren Segal
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-09-11 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: hpricot
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
description: Exports Trac wiki pages as local HTML files
|
34
|
+
email: lsegal@soen.ca
|
35
|
+
executables:
|
36
|
+
- trac-export-wiki
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
files:
|
42
|
+
- bin/trac-export-wiki
|
43
|
+
- lib/trac-export-wiki.rb
|
44
|
+
- examples/config.yaml
|
45
|
+
- examples/config.yaml.sample
|
46
|
+
- LICENSE
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
has_rdoc: yard
|
50
|
+
homepage: http://github.com/lsegal/trac-export-wiki
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project: trac-export-wiki
|
77
|
+
rubygems_version: 1.3.7
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Exports Trac wiki pages as local HTML files.
|
81
|
+
test_files: []
|
82
|
+
|