tendersync 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|