tendersync 1.0.2
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/History.txt +13 -0
- data/Manifest +27 -0
- data/README.md +157 -0
- data/Rakefile +40 -0
- data/bin/tendersync +14 -0
- data/config/website.yml.sample +2 -0
- data/lib/tendersync/document.rb +218 -0
- data/lib/tendersync/runner.rb +211 -0
- data/lib/tendersync/session.rb +102 -0
- data/lib/tendersync/tendersync.rb +7 -0
- data/lib/tendersync.rb +6 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +71 -0
- data/spec/fixtures/passenger_restart_issues +31 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/tendersync_document_spec.rb +58 -0
- data/spec/tendersync_session_spec.rb +26 -0
- data/spec/tendersync_spec.rb +12 -0
- data/tasks/rspec.rake +21 -0
- data/tendersync.gemspec +39 -0
- data/website/index.html +11 -0
- data/website/index.txt +81 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +159 -0
- data/website/template.html.erb +50 -0
- metadata +115 -0
data/History.txt
ADDED
data/Manifest
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
bin/tendersync
|
2
|
+
config/website.yml.sample
|
3
|
+
History.txt
|
4
|
+
lib/tendersync/document.rb
|
5
|
+
lib/tendersync/runner.rb
|
6
|
+
lib/tendersync/session.rb
|
7
|
+
lib/tendersync/tendersync.rb
|
8
|
+
lib/tendersync.rb
|
9
|
+
Manifest
|
10
|
+
Rakefile
|
11
|
+
README.md
|
12
|
+
script/console
|
13
|
+
script/destroy
|
14
|
+
script/generate
|
15
|
+
script/txt2html
|
16
|
+
spec/fixtures/passenger_restart_issues
|
17
|
+
spec/spec.opts
|
18
|
+
spec/spec_helper.rb
|
19
|
+
spec/tendersync_document_spec.rb
|
20
|
+
spec/tendersync_session_spec.rb
|
21
|
+
spec/tendersync_spec.rb
|
22
|
+
tasks/rspec.rake
|
23
|
+
website/index.html
|
24
|
+
website/index.txt
|
25
|
+
website/javascripts/rounded_corners_lite.inc.js
|
26
|
+
website/stylesheets/screen.css
|
27
|
+
website/template.html.erb
|
data/README.md
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# Tendersync
|
2
|
+
|
3
|
+
Authors: Markus Roberts and Bill Kayser
|
4
|
+
|
5
|
+
Tendersync allows you to sync documents stored in the
|
6
|
+
[ENTP Tender](http://www.tenderapp.com)
|
7
|
+
`faqs` section with a local filesystem, allowing you to manage your
|
8
|
+
documents with git or subversion.
|
9
|
+
|
10
|
+
It includes a command for creating an index document for any given
|
11
|
+
section.
|
12
|
+
|
13
|
+
Find out more about Tender by visiting [the Tender site](http://www.tenderapp.com).
|
14
|
+
|
15
|
+
## Features
|
16
|
+
|
17
|
+
* List remote sections and documents under the `/faqs` area
|
18
|
+
* Pull single documents or entire tree from Tender site to local
|
19
|
+
filesystem
|
20
|
+
* Push local changes back to Tender one at a time or en masse
|
21
|
+
* Manage document meta-data, like keywords, in headers
|
22
|
+
* Push changed versions to the server
|
23
|
+
|
24
|
+
## Synopsis
|
25
|
+
|
26
|
+
Create a working directory where you want to store the tender docs in a hierarchy
|
27
|
+
and run tendersync from there.
|
28
|
+
|
29
|
+
sudo gem install tendersync
|
30
|
+
cd $workdir
|
31
|
+
tendersync -h
|
32
|
+
|
33
|
+
## Using Tendersync
|
34
|
+
|
35
|
+
To get started, you need to pass in your account information. You
|
36
|
+
only need to do this once. A local file `.tendersync` is created with
|
37
|
+
the configuration information.
|
38
|
+
|
39
|
+
This will get you set up:
|
40
|
+
|
41
|
+
tendersync -u user@me.com -p password --docurl=http://company.tenderapp.com
|
42
|
+
|
43
|
+
To verify it worked run the `ls` command:
|
44
|
+
|
45
|
+
tendersync ls
|
46
|
+
|
47
|
+
Tender documents are organized into sections defined by you. At New
|
48
|
+
Relic, we have faqs, docs, and troubleshooting. You can specify
|
49
|
+
commands to apply to one or more sections by passing in section names
|
50
|
+
with -s:
|
51
|
+
|
52
|
+
tendersync -s docs -s troubleshooting pull
|
53
|
+
|
54
|
+
### Examples
|
55
|
+
|
56
|
+
Start with:
|
57
|
+
|
58
|
+
tendersync -h
|
59
|
+
|
60
|
+
Download all your docs:
|
61
|
+
|
62
|
+
tendersync pull
|
63
|
+
|
64
|
+
Download just the faq docs:
|
65
|
+
|
66
|
+
tendersync pull -s faqs
|
67
|
+
|
68
|
+
Create a git repository and save all the documents:
|
69
|
+
|
70
|
+
git init
|
71
|
+
git add .
|
72
|
+
git commit -m "First version of docs on Tender"
|
73
|
+
|
74
|
+
Upload docs to the server:
|
75
|
+
|
76
|
+
tendersync post faqs/sinatra_support
|
77
|
+
tendersync post docs/install-*
|
78
|
+
tendersync post -s docs
|
79
|
+
|
80
|
+
Upload everything to the server (regardless of whether the content has
|
81
|
+
changed or not):
|
82
|
+
|
83
|
+
tendersync post
|
84
|
+
|
85
|
+
### Using the `index` Command
|
86
|
+
|
87
|
+
You can generate a table of contents for any section with the index
|
88
|
+
command. By default index will generate a single file named
|
89
|
+
`SECTION_table_of_contents`.
|
90
|
+
|
91
|
+
In this file will be a list of all the files in the given section with
|
92
|
+
links to those files. Under each file link will be a bullet list of
|
93
|
+
the topmost sections in the document. If these sections are preceeded
|
94
|
+
by anchor links (A elements with the name attribute) then the bullets
|
95
|
+
will have links to those sections.
|
96
|
+
|
97
|
+
It will look something like this:
|
98
|
+
|
99
|
+
<pre>
|
100
|
+
## Installation and configuration
|
101
|
+
### [Agent Installation (Ruby)](agent-installation)
|
102
|
+
* [Installing the Plug-in](agent-installation#Installing_the_Plug-in)
|
103
|
+
* [Installing the Gem](agent-installation#Installing_the_Gem)
|
104
|
+
</pre>
|
105
|
+
|
106
|
+
#### Customizing the amount of detail in the Index
|
107
|
+
|
108
|
+
You can show sections deeper than one level in a particular document
|
109
|
+
using the `-d` option. The default is 1.
|
110
|
+
|
111
|
+
tendersync index -d 2
|
112
|
+
|
113
|
+
#### Definiting TOC groups
|
114
|
+
|
115
|
+
If you want to divide the table of contents into groups of related
|
116
|
+
documents, you can pass in a title for a group and a regular expression
|
117
|
+
to match against document titles that belong in that group. These group
|
118
|
+
definitions will be saved so you only need to enter them once.
|
119
|
+
|
120
|
+
Enter a group using the `-g` option passing in a title and regular
|
121
|
+
expression separated by a semi-colon.
|
122
|
+
|
123
|
+
tendersync index -g "Page Details;/page/i"
|
124
|
+
|
125
|
+
You can add multiple groups with additional -g options:
|
126
|
+
|
127
|
+
tendersync index -g "Page Details;/page/i" -g "Installation Info;/installation/i"
|
128
|
+
|
129
|
+
If you want to remove a group definition, you need to remove it
|
130
|
+
manually from the `.tendersync` file.
|
131
|
+
|
132
|
+
## THANKS
|
133
|
+
|
134
|
+
All due regards, credit, thanks, etc., to the ENTP team for a great tool.
|
135
|
+
|
136
|
+
## LICENSE
|
137
|
+
|
138
|
+
Copyright (c) 2009 New Relic, Inc.
|
139
|
+
|
140
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
141
|
+
a copy of this software and associated documentation files (the
|
142
|
+
'Software'), to deal in the Software without restriction, including
|
143
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
144
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
145
|
+
permit persons to whom the Software is furnished to do so, subject to
|
146
|
+
the following conditions:
|
147
|
+
|
148
|
+
The above copyright notice and this permission notice shall be
|
149
|
+
included in all copies or substantial portions of the Software.
|
150
|
+
|
151
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
152
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
153
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
154
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
155
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
156
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
157
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'echoe'
|
3
|
+
%w[rake rake/clean fileutils newgem rubigen].each { |f| require f }
|
4
|
+
require File.dirname(__FILE__) + '/lib/tendersync'
|
5
|
+
|
6
|
+
GEM_NAME = "tendersync"
|
7
|
+
GEM_VERSION = Tendersync::VERSION
|
8
|
+
AUTHOR = "Bill Kayser"
|
9
|
+
EMAIL = "bkayser@newrelic.com"
|
10
|
+
HOMEPAGE = "http://www.github.com/newrelic/tendersync"
|
11
|
+
SUMMARY = "Utility for syncing and indexing files from ENTP's Tender site."
|
12
|
+
DESCRIPTION = <<-EOF
|
13
|
+
Tendersync is a utility for syncing files from ENTP's Tender site for managing customer facing documentation. It can be used to pull and push documents to a local repository as well as create indexes for each documentation section.
|
14
|
+
EOF
|
15
|
+
|
16
|
+
# Generate all the Rake tasks
|
17
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
18
|
+
Echoe.new(GEM_NAME, Tendersync::VERSION) do |p|
|
19
|
+
p.author = AUTHOR
|
20
|
+
p.email = EMAIL
|
21
|
+
p.summary = SUMMARY
|
22
|
+
p.url = HOMEPAGE
|
23
|
+
p.project = 'newrelic'
|
24
|
+
p.description = DESCRIPTION
|
25
|
+
p.version = Tendersync::VERSION
|
26
|
+
p.need_tar_gz = false
|
27
|
+
p.need_gem = true
|
28
|
+
p.bin_files = 'bin/tendersync'
|
29
|
+
p.runtime_dependencies = [
|
30
|
+
['mechanize','>= 0.9.3'],
|
31
|
+
]
|
32
|
+
p.development_dependencies = [
|
33
|
+
['newgem', ">= #{::Newgem::VERSION}"]
|
34
|
+
]
|
35
|
+
p.ignore_pattern = %w[docs/** general/** troubleshooting/**]
|
36
|
+
p.clean_pattern |= %w[**/.DS_Store tmp *.log]
|
37
|
+
end
|
38
|
+
|
39
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
40
|
+
|
data/bin/tendersync
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created on 2009-6-11.
|
4
|
+
# Copyright (c) 2009. All rights reserved.
|
5
|
+
|
6
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/tendersync")
|
7
|
+
|
8
|
+
require "tendersync/runner"
|
9
|
+
|
10
|
+
begin
|
11
|
+
Tendersync::Runner.new(ARGV.dup).run
|
12
|
+
rescue Tendersync::Runner::Error => e
|
13
|
+
puts e.message
|
14
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'set'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
class Tendersync::Document
|
6
|
+
Properties = [:section, :document_id, :title, :permalink, :keywords, :body]
|
7
|
+
attr_accessor *Properties
|
8
|
+
|
9
|
+
NUM_DASHES = 28 # the number of dashes in keyword fields
|
10
|
+
|
11
|
+
class TOCEntry
|
12
|
+
attr_reader :name, :link, :level
|
13
|
+
attr_accessor :parent
|
14
|
+
def initialize name, link=nil, level=nil
|
15
|
+
@name = name
|
16
|
+
@link = link if link
|
17
|
+
@level = level if level
|
18
|
+
end
|
19
|
+
def children
|
20
|
+
@children ||= []
|
21
|
+
end
|
22
|
+
# Write this element and all children, recursively as bullet lists with links
|
23
|
+
def write_entries(io, depth=1, indent = 0, doc_link = parent.link)
|
24
|
+
io.write " " * 4 * indent # indentation
|
25
|
+
io.write "* " # bullet
|
26
|
+
if link
|
27
|
+
io.puts "[#{name}](#{doc_link}##{self.link})"
|
28
|
+
else
|
29
|
+
io.puts name
|
30
|
+
end
|
31
|
+
children.each { | child | child.write_entries(io, depth-1, indent+1, doc_link)} unless depth == 1
|
32
|
+
end
|
33
|
+
def add child
|
34
|
+
if !parent || child.level > self.level
|
35
|
+
children << child
|
36
|
+
child.parent = self
|
37
|
+
else
|
38
|
+
parent.add(child)
|
39
|
+
end
|
40
|
+
child
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Group < TOCEntry
|
45
|
+
attr_reader :title_regex
|
46
|
+
def initialize(name, title_regex=//)
|
47
|
+
super(name, nil, nil)
|
48
|
+
@title_regex = title_regex
|
49
|
+
end
|
50
|
+
Default = Group.new('Other')
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(values={})
|
55
|
+
values.each do | prop, value |
|
56
|
+
self.send "#{prop}=", value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
#
|
60
|
+
# Documents can be read from / written to a file
|
61
|
+
#
|
62
|
+
def to_s
|
63
|
+
io = StringIO.new
|
64
|
+
Properties.each do |field|
|
65
|
+
next unless value = self.send(field)
|
66
|
+
io.write "-" * NUM_DASHES
|
67
|
+
io.write " #{field} "
|
68
|
+
io.write "-" * NUM_DASHES
|
69
|
+
io.puts
|
70
|
+
io.puts value
|
71
|
+
end
|
72
|
+
io.string
|
73
|
+
end
|
74
|
+
|
75
|
+
def save
|
76
|
+
FileUtils.mkdir_p section
|
77
|
+
File.open("#{section}/#{permalink}",'w') { |f| f.print self }
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.load(section, io)
|
82
|
+
values = { :section => section }
|
83
|
+
key = data = nil
|
84
|
+
while line = io.gets
|
85
|
+
line.chomp!
|
86
|
+
if line =~ /^----+ (.+) -----+$/
|
87
|
+
values[key] = data.join("\n") if data
|
88
|
+
key = $1.intern
|
89
|
+
data = []
|
90
|
+
else
|
91
|
+
raise "keyword line not recognized: #{line}" unless data
|
92
|
+
data << line
|
93
|
+
end
|
94
|
+
end
|
95
|
+
values[key] = data.join("\n") if key
|
96
|
+
new values
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.read_from_file(file_name)
|
100
|
+
section = file_name.split('/')[-2]
|
101
|
+
if !File.exists? file_name
|
102
|
+
raise Tendersync::Runner::Error, "Cannot read #{file_name}"
|
103
|
+
end
|
104
|
+
File.open(file_name) { |f| self.load(section, f) }
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Can be scraped from a form
|
109
|
+
#
|
110
|
+
def self.from_form(section,form)
|
111
|
+
values = {
|
112
|
+
:document_id => form.action[%r{/faqs/(\d+)/edit},1],
|
113
|
+
:section => section
|
114
|
+
}
|
115
|
+
form.fields.each { |tf|
|
116
|
+
if field_name = tf.name[/faq\[(.*)\]/,1]
|
117
|
+
value = tf.value.map { |line| line.chomp }.join("\n")
|
118
|
+
values[field_name.intern] = value
|
119
|
+
end
|
120
|
+
}
|
121
|
+
new(values)
|
122
|
+
end
|
123
|
+
def to_form(form)
|
124
|
+
form.fields.each { |tf|
|
125
|
+
if field_name = tf.name[/faq\[(.*)\]/,1] and self.send(field_name.intern)
|
126
|
+
lines = []
|
127
|
+
self.send(field_name.intern).each_line {|line| lines << line.chomp }
|
128
|
+
tf.value = lines.join("\r\n")
|
129
|
+
end
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.each(section)
|
134
|
+
Dir.glob("#{section}/*").each { |f| yield Tendersync::Document.read_from_file(f) }
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.index_for(section_id, section_name)
|
138
|
+
new(:section => section_id,
|
139
|
+
:title => "#{section_name} Table of Contents",
|
140
|
+
:permalink => "#{section_id}-table-of-contents",
|
141
|
+
:keywords => "toc index")
|
142
|
+
end
|
143
|
+
|
144
|
+
# Update this document body with an index of all the documents in
|
145
|
+
# this section. Underneath the TOC entry for a document will be sub
|
146
|
+
# entries for each named A element.
|
147
|
+
#
|
148
|
+
# group_map is an associative array of /regex/ to "Title String" of
|
149
|
+
# a group of documents. It is used to divide up documents into
|
150
|
+
# groups within the table of contents. A document is placed in a
|
151
|
+
# TOC group based on the first regex it matches in the group map.
|
152
|
+
#
|
153
|
+
# If group_map is empty then headings will be sorted alphabetically
|
154
|
+
# and not grouped.
|
155
|
+
#
|
156
|
+
# depth s the number of nested levels to descend into a document.
|
157
|
+
def refresh_index(groups=[], depth=2)
|
158
|
+
generate_index(create_toc(groups), depth)
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def create_toc(groups)
|
164
|
+
groups += groups + [Group::Default] # array of groups
|
165
|
+
link_root = {}
|
166
|
+
self.class.each(section) do |document|
|
167
|
+
next if document.permalink =~ /-table-of-contents$/
|
168
|
+
puts "indexing #{document.permalink}..."
|
169
|
+
title = document.title
|
170
|
+
group = groups.detect { | g | title =~ g.title_regex }
|
171
|
+
doc_entry = TOCEntry.new title, document.permalink, 0
|
172
|
+
group.add doc_entry
|
173
|
+
last = doc_entry
|
174
|
+
link = nil
|
175
|
+
document.body.scan(%r{<a name=(.*?)>|^(#+)\s*(.*?)\s*$}i) do
|
176
|
+
name = $1
|
177
|
+
heading_level = $2 && $2.length
|
178
|
+
text = $3
|
179
|
+
if name
|
180
|
+
# Record the link name for the next header
|
181
|
+
link = eval(name)
|
182
|
+
elsif heading_level == 1
|
183
|
+
last = last.add(TOCEntry.new(text, link, heading_level))
|
184
|
+
elsif heading_level >= 2 # level 2
|
185
|
+
last = last.add(TOCEntry.new(text, link, heading_level))
|
186
|
+
link = nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
groups
|
191
|
+
end
|
192
|
+
|
193
|
+
def generate_index(groups, depth)
|
194
|
+
groups.reject! { | group | group.children.empty? }
|
195
|
+
# Now go through each group
|
196
|
+
io = StringIO.new
|
197
|
+
io.puts
|
198
|
+
groups.each do | group |
|
199
|
+
# Show the group heading unless there is only one group
|
200
|
+
io.puts "## #{group.name}" unless groups.size == 1
|
201
|
+
group.children.each do | doc_entry |
|
202
|
+
doc_link = doc_entry.link
|
203
|
+
io.puts "### [#{doc_entry.name}](#{doc_link})"
|
204
|
+
doc_entry.children.each do | doc_section |
|
205
|
+
doc_section.write_entries(io, depth)
|
206
|
+
end
|
207
|
+
io.puts
|
208
|
+
end
|
209
|
+
end
|
210
|
+
if $dry_run
|
211
|
+
puts io.string
|
212
|
+
else
|
213
|
+
self.body = io.string
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'optparse'
|
3
|
+
require 'tendersync/session'
|
4
|
+
require 'tendersync/document'
|
5
|
+
require 'mechanize'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
class Tendersync::Runner
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
def initialize argv
|
12
|
+
@dry_run = false
|
13
|
+
@sections = []
|
14
|
+
@groups = []
|
15
|
+
settings['groups'] ||= []
|
16
|
+
@parser = OptionParser.new do |op|
|
17
|
+
op.banner += " command\n"
|
18
|
+
op.on('-n', "dry run" ) { @dry_run = true }
|
19
|
+
op.on('-s', '--section', '=SECTION', String, "section, specify multiple separately" ) { |s| @sections << s }
|
20
|
+
op.on('-u', '--username','=EMAIL', String, "* login e-mail" ) {|str| settings['username'] = str }
|
21
|
+
op.on('-p', '--password','=PASS', String, "* password" ) {|str| settings['password'] = str }
|
22
|
+
op.on( '--docurl', '=URL', String, "* tender site URL" ) { |dir| settings['docurl'] = dir }
|
23
|
+
op.separator ""
|
24
|
+
op.separator "Indexing Options:"
|
25
|
+
op.on('-g', '--group', '=TITLE;regex', String, "*map of regex to group title for TOC groups") do | g |
|
26
|
+
pair = g.split(';')
|
27
|
+
settings['groups'] << [pair.first, pair.last]
|
28
|
+
end
|
29
|
+
op.on('-d', '--depth', '=DEPTH', String, "*Number of levels to descend into a document being indexed") { | g | settings['depth'] = g.to_i }
|
30
|
+
|
31
|
+
|
32
|
+
%Q{
|
33
|
+
* saved in .tendersync file for subsequent default
|
34
|
+
|
35
|
+
Commands:
|
36
|
+
|
37
|
+
pull [URL, URL...] -- download documents from tender; specify sections with -s, a page URL, or
|
38
|
+
nothing to download all documents
|
39
|
+
index -- create a master index of each section, writing to section/file; specify
|
40
|
+
the sections with -s options; you can organize the TOC into groups by
|
41
|
+
mapping document titles to groups via a regular expression with -g options
|
42
|
+
ls -- list files in specified session
|
43
|
+
post PATTERN -- post the matching documents to tender; use /regexp/ or glob
|
44
|
+
irb -- drops you into IRB with a tender session & related classes (for hacking/
|
45
|
+
one-time tasks). Programmers only.
|
46
|
+
create PERMALINK -- create a new tender document with the specified permalink in the section
|
47
|
+
specified by --section=... (must be only one.)
|
48
|
+
|
49
|
+
}.split(/\n/).each {|line| op.separator line.chomp }
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
@command,*@args = *@parser.parse(argv)
|
54
|
+
rescue OptionParser::InvalidOption => e
|
55
|
+
raise Error, e.message
|
56
|
+
end
|
57
|
+
|
58
|
+
@username = settings['username']
|
59
|
+
@password = settings['password']
|
60
|
+
@dochome = settings['docurl'] && settings['docurl'] =~ /^(http.*?)\/?$/ && $1
|
61
|
+
@root = settings['root']
|
62
|
+
|
63
|
+
case
|
64
|
+
when ! @username
|
65
|
+
raise Error, "Please enter a username and password. You only need to do this once."
|
66
|
+
when ! @password
|
67
|
+
raise Error, "Please enter a password. You only need to do this once."
|
68
|
+
when ! @dochome
|
69
|
+
raise Error, "Please enter a --docurl indicating the home page URL of your Tender docs.\n" +
|
70
|
+
"You only need to do this once."
|
71
|
+
else
|
72
|
+
settings.save!
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def run
|
77
|
+
$session = Tendersync::Session.new @dochome, @username, @password
|
78
|
+
$dry_run = @dry_run
|
79
|
+
case @command || 'help'
|
80
|
+
when 'help'
|
81
|
+
raise Error, @parser.to_s
|
82
|
+
when *%w[pull post create irb ls index]
|
83
|
+
send @command
|
84
|
+
else
|
85
|
+
raise Error, "Unknown command: #{@command}\n\n#{@parser}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def ls
|
92
|
+
$session.ls *sections
|
93
|
+
end
|
94
|
+
|
95
|
+
def pull
|
96
|
+
if @args.size > 0
|
97
|
+
@args.each do |url|
|
98
|
+
section = url =~ /\/faqs\/([^\/]*)\// && $1
|
99
|
+
raise Error, "Invalid URI for document: #{url}" if section.nil?
|
100
|
+
doc = Document.from_form(section, $session.edit_page_for(url).form_with(:action => /edit/))
|
101
|
+
puts " #{doc.permalink}"
|
102
|
+
doc.save unless $dry_run
|
103
|
+
end
|
104
|
+
else
|
105
|
+
sections.each do |section|
|
106
|
+
puts "pulling #{section} docs ..."
|
107
|
+
$session.pull_from_tender(section)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def post
|
113
|
+
documents = @args.collect { |doc_name|
|
114
|
+
matches = if doc_name =~ %r{/}
|
115
|
+
Dir.glob(doc_name)
|
116
|
+
else
|
117
|
+
Dir.glob("#{@root}/{#{sections.join(',')}}/#{doc_name}*")
|
118
|
+
end
|
119
|
+
if matches.empty?
|
120
|
+
puts "No documents match #{doc_name}"
|
121
|
+
else
|
122
|
+
matches.collect { |match| Tendersync::Document.read_from_file(match) }
|
123
|
+
end
|
124
|
+
}.flatten.compact
|
125
|
+
documents.each { |document|
|
126
|
+
if @dry_run
|
127
|
+
puts "would post #{document.section}/#{document.permalink} to tender."
|
128
|
+
else
|
129
|
+
$session.post(document)
|
130
|
+
end
|
131
|
+
}
|
132
|
+
end
|
133
|
+
alias push post
|
134
|
+
|
135
|
+
def create
|
136
|
+
raise Error, "You must specify exactly one section to put the document in." if sections.length != 1
|
137
|
+
raise Error, "You must specify exactly one document permalink." if @args.length != 1
|
138
|
+
section,permalink = sections.first,@args.first
|
139
|
+
filename = "#{@root}/#{section}/#{permalink}"
|
140
|
+
text = File.read(filename) rescue ""
|
141
|
+
text = "Put Text Here" if text.strip.empty?
|
142
|
+
if $dry_run
|
143
|
+
puts "would create document #{permalink}\nin #{section} as #{filename}"
|
144
|
+
puts "\ntext:\n------------\n#{text}"
|
145
|
+
else
|
146
|
+
document = $session.create_document(section,permalink,text)
|
147
|
+
document.save
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def irb
|
152
|
+
puts <<-EOF
|
153
|
+
|
154
|
+
Use $session to access the Tendersync::Session instance.
|
155
|
+
Use Tendersync::Document to manipulate documents local and remote.
|
156
|
+
|
157
|
+
Examples of crazy stuff you could try:
|
158
|
+
|
159
|
+
puts $session.all_sections.inspect
|
160
|
+
|
161
|
+
$session.pull_from_tender('troubleshooting')
|
162
|
+
|
163
|
+
$session.post(Tendersync::Document.index('docs').save)
|
164
|
+
|
165
|
+
Tendersync::Document.each { |d| puts d.body.split(/\W/).join("\\n") }
|
166
|
+
|
167
|
+
doc = Tendersync::Document.read_from_file("./docs/agent-api")
|
168
|
+
doc.body.gsub! /api/,"API"
|
169
|
+
doc.save
|
170
|
+
|
171
|
+
EOF
|
172
|
+
ARGV.clear
|
173
|
+
require 'irb'
|
174
|
+
require 'irb/completion'
|
175
|
+
$sections = sections
|
176
|
+
IRB.start
|
177
|
+
end
|
178
|
+
def index
|
179
|
+
groups = settings['groups'].map do |title,regex|
|
180
|
+
regex = eval(regex) if regex =~ %r{^/.*/[a-z]*$}
|
181
|
+
Tendersync::Document::Group.new title, Regexp.new(regex)
|
182
|
+
end
|
183
|
+
section_details = $session.all_sections
|
184
|
+
sections.each do |section|
|
185
|
+
doc = Tendersync::Document.index_for section, section_details[section]
|
186
|
+
puts "indexing #{section}: #{doc.section}/#{doc.permalink}"
|
187
|
+
doc.refresh_index groups, settings['depth'] || 2
|
188
|
+
doc.save
|
189
|
+
end
|
190
|
+
end
|
191
|
+
def sections
|
192
|
+
@sections = $session.all_sections.keys if @sections.empty?
|
193
|
+
@sections
|
194
|
+
end
|
195
|
+
def settings
|
196
|
+
case
|
197
|
+
when @settings
|
198
|
+
return @settings
|
199
|
+
when File.exists?(".tendersync")
|
200
|
+
File.open(".tendersync", "r") { |f| @settings = YAML.load(f) }
|
201
|
+
else
|
202
|
+
@settings = {}
|
203
|
+
end
|
204
|
+
def @settings.save!
|
205
|
+
File.open(".tendersync","w") do |f|
|
206
|
+
f.write(self.to_yaml)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
@settings
|
210
|
+
end
|
211
|
+
end
|