redsync 0.1.2 → 0.2.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/config.yml.dist +3 -1
- data/lib/datetime_nil_compare.rb +7 -0
- data/lib/redsync.rb +84 -87
- data/lib/redsync/cli.rb +19 -10
- data/lib/redsync/sync_stat.rb +3 -3
- data/lib/redsync/wiki.rb +168 -0
- data/lib/redsync/wiki_page.rb +189 -0
- metadata +20 -6
data/config.yml.dist
CHANGED
data/lib/redsync.rb
CHANGED
|
@@ -8,61 +8,69 @@ require 'date'
|
|
|
8
8
|
require 'iconv'
|
|
9
9
|
require 'mechanize'
|
|
10
10
|
require 'active_support/all'
|
|
11
|
+
require 'ir_b'
|
|
11
12
|
|
|
12
13
|
require 'redsync/cli'
|
|
13
|
-
require 'redsync/
|
|
14
|
+
require 'redsync/wiki'
|
|
15
|
+
require 'redsync/wiki_page'
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class Redsync
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
attr_reader :url,
|
|
21
|
+
:projects,
|
|
22
|
+
:username,
|
|
23
|
+
:data_dir,
|
|
24
|
+
:extension,
|
|
25
|
+
:wikis
|
|
26
|
+
|
|
27
|
+
# Valid options:
|
|
28
|
+
# :url => Redmine's base URL. Required.
|
|
29
|
+
# :projects => List of target projects. Required.
|
|
30
|
+
# :username => Redmine username. Required.
|
|
31
|
+
# :password => Redmine password. Required.
|
|
32
|
+
# :data_dir => Directory to read/write. Required.
|
|
33
|
+
# :extension => Filename extensions. Defaults to "txt"
|
|
29
34
|
def initialize(options)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@
|
|
37
|
-
@
|
|
38
|
-
@
|
|
35
|
+
options = {
|
|
36
|
+
:extension => "txt",
|
|
37
|
+
}.merge(options)
|
|
38
|
+
|
|
39
|
+
@url = options[:url].match(/(.*?)\/?$/)[1]
|
|
40
|
+
@projects = options[:projects]
|
|
41
|
+
@username = options[:username]
|
|
42
|
+
@password = options[:password]
|
|
43
|
+
@data_dir = File.expand_path(options[:data_dir])
|
|
44
|
+
@extension = options[:extension]
|
|
45
|
+
|
|
46
|
+
@login_url = @url + "/login"
|
|
39
47
|
|
|
40
48
|
initialize_system_files
|
|
41
49
|
|
|
42
50
|
@agent = Mechanize.new
|
|
43
|
-
@syncstat = SyncStat.new(@config, @agent)
|
|
44
|
-
|
|
45
|
-
@logged_in = false
|
|
46
51
|
end
|
|
47
52
|
|
|
48
53
|
|
|
49
54
|
def initialize_system_files
|
|
50
|
-
|
|
51
|
-
puts "
|
|
52
|
-
|
|
55
|
+
if File.exist? @data_dir
|
|
56
|
+
puts "Using data dir: #{@data_dir}"
|
|
57
|
+
else
|
|
58
|
+
puts "Creating #{@data_dir}"
|
|
59
|
+
FileUtils.mkdir(@data_dir)
|
|
53
60
|
end
|
|
54
61
|
end
|
|
55
62
|
|
|
56
63
|
|
|
57
64
|
def login
|
|
58
|
-
puts "Logging in as #{@
|
|
59
|
-
page = @agent.get(@
|
|
65
|
+
puts "Logging in as #{@username} to #{@login_url}..."
|
|
66
|
+
page = @agent.get(@login_url)
|
|
60
67
|
login_form = page.form_with(:action => "/login")
|
|
61
|
-
login_form.field_with(:name => "username").value = @
|
|
62
|
-
login_form.field_with(:name => "password").value = @
|
|
68
|
+
login_form.field_with(:name => "username").value = @username
|
|
69
|
+
login_form.field_with(:name => "password").value = @password
|
|
63
70
|
result_page = login_form.submit
|
|
64
71
|
if result_page.search("a.logout").any?
|
|
65
72
|
puts "Logged in successfully."
|
|
73
|
+
instantiate_wikis
|
|
66
74
|
return true
|
|
67
75
|
else
|
|
68
76
|
puts "Login failed."
|
|
@@ -71,76 +79,65 @@ class Redsync
|
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
|
|
74
|
-
def
|
|
75
|
-
@
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
def instantiate_wikis
|
|
83
|
+
@wikis = @projects.inject({}) do |sum, project_identifier|
|
|
84
|
+
sum[project_identifier] = Wiki.new({
|
|
85
|
+
:url => @url + "/projects/" + project_identifier + "/wiki",
|
|
86
|
+
:cookies => @agent.cookie_jar.cookies(URI.parse(@url)),
|
|
87
|
+
:data_dir => File.join(@data_dir, project_identifier),
|
|
88
|
+
:extension => @extension
|
|
89
|
+
})
|
|
90
|
+
sum
|
|
82
91
|
end
|
|
83
|
-
puts "Downloaded #{statuses[Result::DOWNLOADED]} pages."
|
|
84
92
|
end
|
|
85
93
|
|
|
86
94
|
|
|
87
|
-
def
|
|
88
|
-
|
|
95
|
+
def status_check
|
|
96
|
+
@projects.each do |project_identifier|
|
|
97
|
+
wiki = @wikis[project_identifier]
|
|
98
|
+
wiki.load_pages_cache
|
|
99
|
+
wiki.scan
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return Result::DOWNLOADED
|
|
101
|
+
puts "#{wiki.pages_to_download.length} pages to download"
|
|
102
|
+
puts "#{wiki.pages_to_create.length} pages to create"
|
|
103
|
+
puts "#{wiki.pages_to_upload.length} pages to upload"
|
|
104
|
+
end
|
|
96
105
|
end
|
|
97
106
|
|
|
98
107
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Result::UPLOADED => 0,
|
|
103
|
-
Result::CREATED => 0,
|
|
104
|
-
Result::ERROR_ON_UPLOAD => 0,
|
|
105
|
-
Result::ERROR_ON_CREATE => 0,
|
|
106
|
-
}
|
|
107
|
-
@syncstat.new_page_names.each do |pagename|
|
|
108
|
-
results[upload(pagename, true)] += 1
|
|
108
|
+
def sync_all
|
|
109
|
+
@projects.each do |project_identifier|
|
|
110
|
+
sync(project_identifier)
|
|
109
111
|
end
|
|
110
|
-
@syncstat.local_updated_page_names.each do |pagename|
|
|
111
|
-
results[upload(pagename)] += 1
|
|
112
|
-
end
|
|
113
|
-
print "Created #{results[Result::CREATED]} pages."
|
|
114
|
-
print " (#{results[Result::ERROR_ON_CREATE]} errors)" if results[Result::ERROR_ON_CREATE] > 0
|
|
115
|
-
print "\n"
|
|
116
|
-
print "Uploaded #{results[Result::UPLOADED]} pages."
|
|
117
|
-
print " (#{results[Result::ERROR_ON_UPLOAD]} errors)" if results[Result::ERROR_ON_UPLOAD] > 0
|
|
118
|
-
print "\n"
|
|
119
112
|
end
|
|
120
113
|
|
|
121
114
|
|
|
122
|
-
def
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
115
|
+
def sync(project_identifier)
|
|
116
|
+
wiki = @wikis[project_identifier]
|
|
117
|
+
wiki.load_pages_cache
|
|
118
|
+
wiki.scan
|
|
119
|
+
wiki.downsync
|
|
120
|
+
wiki.upsync
|
|
121
|
+
end
|
|
126
122
|
|
|
127
|
-
page = @agent.get(@config[:wiki_base_url] + "/" + pagename + "/edit")
|
|
128
|
-
form = page.form_with(:id=>"wiki_form")
|
|
129
|
-
form.field_with(:name=>"content[text]").value = File.open(stat[:local_file], "r:UTF-8").read
|
|
130
|
-
result_page = form.submit
|
|
131
|
-
errors = result_page.search("#errorExplanation li").map{|li|li.text}
|
|
132
123
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
else
|
|
138
|
-
now = DateTime.now
|
|
139
|
-
@syncstat.update(pagename, {
|
|
140
|
-
:downloaded_at => now,
|
|
141
|
-
:remote_updated_at => now
|
|
142
|
-
})
|
|
143
|
-
return (create ? Result::CREATED : Result::UPLOADED)
|
|
124
|
+
def interactive
|
|
125
|
+
wikis.each do |project_identifier, wiki|
|
|
126
|
+
wiki.load_pages_cache
|
|
127
|
+
wiki.scan
|
|
144
128
|
end
|
|
129
|
+
ir b
|
|
145
130
|
end
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def to_s
|
|
134
|
+
str = "#<Redsync"
|
|
135
|
+
str << " url = \"#{@url}\"\n"
|
|
136
|
+
str << " username = \"#{@username}\"\n"
|
|
137
|
+
str << " projects = \"#{@projects}\"\n"
|
|
138
|
+
str << " data_dir = \"#{@data_dir}\"\n"
|
|
139
|
+
str << " extension = \"#{@extension}\"\n"
|
|
140
|
+
str << ">"
|
|
141
|
+
end
|
|
142
|
+
|
|
146
143
|
end
|
data/lib/redsync/cli.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'optparse'
|
|
2
2
|
require 'yaml'
|
|
3
|
+
require 'redsync'
|
|
3
4
|
|
|
4
5
|
class Redsync
|
|
5
6
|
class CLI
|
|
@@ -12,33 +13,40 @@ class Redsync
|
|
|
12
13
|
redsync = Redsync.new(YAML.load_file(@options.delete(:config_file)).merge(@options))
|
|
13
14
|
exit unless redsync.login
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
case @options[:run_mode]
|
|
17
|
+
when :full_sync
|
|
18
|
+
time do
|
|
19
|
+
redsync.sync_all
|
|
20
|
+
end
|
|
21
|
+
when :interactive
|
|
22
|
+
redsync.interactive
|
|
23
|
+
when :status_check
|
|
24
|
+
redsync.status_check
|
|
18
25
|
end
|
|
19
26
|
end
|
|
20
27
|
|
|
21
28
|
|
|
22
29
|
def parse_options
|
|
23
30
|
@options = {
|
|
31
|
+
:run_mode => :full_sync,
|
|
24
32
|
:config_file => "~/redsync.yml",
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
OptionParser.new do |opts|
|
|
28
36
|
opts.banner = "Usage: redsync [options]"
|
|
29
|
-
opts.on("-v", "--
|
|
37
|
+
opts.on("-v", "--verbose", "Output verbose logs") do |v|
|
|
30
38
|
@options[:verbose] = v
|
|
31
39
|
end
|
|
32
40
|
opts.on("-c", "--config FILE", "Use specified config file instead of ~/redsync.yml") do |file|
|
|
33
41
|
@options[:config_file] = file
|
|
34
42
|
end
|
|
35
|
-
opts.on("-
|
|
36
|
-
@options[:
|
|
43
|
+
opts.on("-s", "--status", "Status check. No uploads or downloads will happen") do |v|
|
|
44
|
+
@options[:run_mode] = :status_check
|
|
37
45
|
end
|
|
38
|
-
opts.on("-
|
|
39
|
-
@options[:
|
|
46
|
+
opts.on("-i", "--interactive", "Interactive mode (irb)") do |v|
|
|
47
|
+
@options[:run_mode] = :interactive
|
|
40
48
|
end
|
|
41
|
-
opts.on("-D", "--
|
|
49
|
+
opts.on("-D", "--debugger", "Debug mode. Requires ruby-debug19") do |v|
|
|
42
50
|
@options[:debug] = v
|
|
43
51
|
end
|
|
44
52
|
end.parse!
|
|
@@ -46,6 +54,7 @@ class Redsync
|
|
|
46
54
|
if @options[:debug]
|
|
47
55
|
require 'ruby-debug'
|
|
48
56
|
Debugger.settings[:autoeval] = true
|
|
57
|
+
Debugger.settings[:reload_source_on_change] = true
|
|
49
58
|
end
|
|
50
59
|
|
|
51
60
|
@options[:config_file] = File.expand_path(@options[:config_file])
|
|
@@ -74,7 +83,7 @@ class Redsync
|
|
|
74
83
|
else
|
|
75
84
|
c =~ /^y/i
|
|
76
85
|
end
|
|
77
|
-
block.call
|
|
86
|
+
(result && block) ? block.call : result
|
|
78
87
|
end
|
|
79
88
|
|
|
80
89
|
|
data/lib/redsync/sync_stat.rb
CHANGED
|
@@ -26,7 +26,7 @@ class Redsync
|
|
|
26
26
|
links.each do |link|
|
|
27
27
|
url = @config[:url] + link.attr("href")
|
|
28
28
|
name = URI.decode(url.match(/^#{@config[:wiki_base_url]}\/(.*)$/)[1]).force_encoding("UTF-8")
|
|
29
|
-
local_file = File.join(@config[:data_dir], "#{name}
|
|
29
|
+
local_file = File.join(@config[:data_dir], "#{name}.#{@config[:extension]}")
|
|
30
30
|
|
|
31
31
|
remote_updated_at = DateTime.parse(h3.text + "T00:00:00" + now.zone)
|
|
32
32
|
|
|
@@ -59,10 +59,10 @@ class Redsync
|
|
|
59
59
|
next if File.directory?(fullpath)
|
|
60
60
|
next if file =~ /^__redsync_/
|
|
61
61
|
name = Iconv.iconv("UTF-8", "UTF-8-MAC", file) if RUBY_PLATFORM =~ /darwin/
|
|
62
|
-
name = name.first.match(/(.*)
|
|
62
|
+
name = name.first.match(/(.*)\.#{extension}$/)[1]
|
|
63
63
|
next if @stat[name]
|
|
64
64
|
|
|
65
|
-
local_file = File.join(@config[:data_dir], "#{name}
|
|
65
|
+
local_file = File.join(@config[:data_dir], "#{name}.#{extension}")
|
|
66
66
|
local_updated_at = File.stat(local_file).mtime.to_datetime
|
|
67
67
|
update(name, {
|
|
68
68
|
:name => name,
|
data/lib/redsync/wiki.rb
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
require 'mechanize'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'iconv'
|
|
5
|
+
|
|
6
|
+
class Redsync
|
|
7
|
+
class Wiki
|
|
8
|
+
|
|
9
|
+
attr_reader :url,
|
|
10
|
+
:data_dir,
|
|
11
|
+
:extension
|
|
12
|
+
|
|
13
|
+
# Valid option values:
|
|
14
|
+
# :url => This wiki's base url. Required
|
|
15
|
+
# :cookies => Mechanize::Cookie objects that's already logged in to Redmine. Required
|
|
16
|
+
# :data_dir => Directory to read/write to. Required.
|
|
17
|
+
# :extension => File extensions for page files. Defaults to "txt"
|
|
18
|
+
def initialize(options)
|
|
19
|
+
@url = options[:url].match(/(.*?)\/?$/)[1]
|
|
20
|
+
@data_dir = File.expand_path(options[:data_dir])
|
|
21
|
+
@extension = options[:extension]
|
|
22
|
+
|
|
23
|
+
@agent = Mechanize.new
|
|
24
|
+
options[:cookies].each do |cookie|
|
|
25
|
+
@agent.cookie_jar.add(URI.parse(@url), cookie)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@pages_cache = {}
|
|
29
|
+
@pages_cache_file = File.join(@data_dir, "__redsync_pages_cache.yml")
|
|
30
|
+
|
|
31
|
+
initialize_system_files
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def cookies
|
|
36
|
+
@agent.cookie_jar.cookies(URI.parse(@url))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def initialize_system_files
|
|
40
|
+
unless File.exist? @data_dir
|
|
41
|
+
puts "Creating #{@data_dir}"
|
|
42
|
+
FileUtils.mkdir(@data_dir)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def pages
|
|
48
|
+
@pages_cache
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def downsync
|
|
53
|
+
queue = pages_to_download
|
|
54
|
+
queue.each_with_index do |page, i|
|
|
55
|
+
puts "--Download (#{i+1} of #{queue.count}) #{page.name}"
|
|
56
|
+
page.download
|
|
57
|
+
end
|
|
58
|
+
self.write_pages_cache
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def upsync
|
|
63
|
+
queue = pages_to_create
|
|
64
|
+
queue.each_with_index do |page, i|
|
|
65
|
+
puts "--Create (#{i+1} of #{queue.count}) #{page.name}"
|
|
66
|
+
page.upload
|
|
67
|
+
end
|
|
68
|
+
self.write_pages_cache
|
|
69
|
+
|
|
70
|
+
queue = pages_to_upload
|
|
71
|
+
queue.each_with_index do |page, i|
|
|
72
|
+
puts "--Upload (#{i+1} of #{queue.count}) #{page.name}"
|
|
73
|
+
page.upload
|
|
74
|
+
end
|
|
75
|
+
self.write_pages_cache
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def pages_to_download
|
|
80
|
+
list = []
|
|
81
|
+
list += @pages_cache.values.select { |page| page.exists_in == :remote_only }
|
|
82
|
+
list += @pages_cache.values.select { |page| page.exists_in == :both && !page.synced_at }
|
|
83
|
+
list += @pages_cache.values.select { |page| page.exists_in == :both && page.synced_at && (page.remote_updated_at > page.synced_at) }
|
|
84
|
+
list
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def pages_to_create
|
|
89
|
+
list = []
|
|
90
|
+
list += @pages_cache.values.select { |page| page.exists_in == :local_only }
|
|
91
|
+
list
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def pages_to_upload
|
|
96
|
+
list = []
|
|
97
|
+
list += @pages_cache.values.select { |page| page.exists_in == :both && (page.local_updated_at > page.synced_at) }
|
|
98
|
+
list
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def scan
|
|
103
|
+
scan_remote
|
|
104
|
+
scan_local
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def scan_remote
|
|
109
|
+
webpage = @agent.get(@url + "/date_index")
|
|
110
|
+
now = DateTime.now
|
|
111
|
+
|
|
112
|
+
# Get remote and local update times using remote list
|
|
113
|
+
webpage.search("#content h3").each do |h3|
|
|
114
|
+
links = h3.next_element.search("a")
|
|
115
|
+
links.each do |link|
|
|
116
|
+
url = URI.parse(@url).merge(link.attr("href")).to_s
|
|
117
|
+
wiki_page = WikiPage.new(self, url)
|
|
118
|
+
@pages_cache[wiki_page.name] = wiki_page unless @pages_cache[wiki_page.name]
|
|
119
|
+
@pages_cache[wiki_page.name].remote_updated_at = h3.text
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def scan_local
|
|
126
|
+
Dir.entries(@data_dir).each do |file|
|
|
127
|
+
next if File.directory? file
|
|
128
|
+
next if file =~ /^__redsync_/
|
|
129
|
+
page_name = file.match(/([^\/\\]+?)\.#{@extension}$/)[1]
|
|
130
|
+
page_name = Iconv.iconv("UTF-8", "UTF-8-MAC", page_name).first if RUBY_PLATFORM =~ /darwin/
|
|
131
|
+
next if pages[page_name]
|
|
132
|
+
pp file
|
|
133
|
+
wiki_page = WikiPage.new(self, page_name)
|
|
134
|
+
pages[page_name] = wiki_page
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def write_pages_cache
|
|
140
|
+
File.open(@pages_cache_file, "w+:UTF-8") do |f|
|
|
141
|
+
f.write(self.pages.values.map{ |page| page.to_hash}.to_yaml)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def load_pages_cache
|
|
147
|
+
return unless File.exist? @pages_cache_file
|
|
148
|
+
@pages_cache = {}
|
|
149
|
+
YAML.load_file(@pages_cache_file).each do |page_hash|
|
|
150
|
+
wiki_page = WikiPage.new(self, page_hash[:name])
|
|
151
|
+
wiki_page.remote_updated_at = page_hash[:remote_updated_at]
|
|
152
|
+
wiki_page.synced_at = page_hash[:synced_at]
|
|
153
|
+
@pages_cache[page_hash[:name]] = wiki_page
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def to_s
|
|
159
|
+
str = "#<Redsync::Wiki"
|
|
160
|
+
str << " url = \"#{@url}\"\n"
|
|
161
|
+
str << " data_dir = \"#{@data_dir}\"\n"
|
|
162
|
+
str << " extension = \"#{@extension}\"\n"
|
|
163
|
+
str << " pages = #{@pages_cache.count}\n"
|
|
164
|
+
str << ">"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'date'
|
|
3
|
+
require 'active_support/all'
|
|
4
|
+
require 'redsync/wiki'
|
|
5
|
+
|
|
6
|
+
class Redsync
|
|
7
|
+
class WikiPage
|
|
8
|
+
attr_reader :name,
|
|
9
|
+
:local_file,
|
|
10
|
+
:url,
|
|
11
|
+
:local_updated_at
|
|
12
|
+
attr_accessor :remote_updated_at,
|
|
13
|
+
:synced_at
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def initialize(wiki, name_or_url_or_fullpath)
|
|
17
|
+
@wiki = wiki
|
|
18
|
+
|
|
19
|
+
if name_or_url_or_fullpath =~ /^#{@wiki.data_dir}\/(.*)$/
|
|
20
|
+
@local_file = name_or_url_or_fullpath
|
|
21
|
+
@name = $1
|
|
22
|
+
@url = @wiki.url + "/" + @name
|
|
23
|
+
elsif name_or_url_or_fullpath =~ /^#{@wiki.url}\/(.*)$/
|
|
24
|
+
@url = name_or_url_or_fullpath
|
|
25
|
+
@name = URI.decode($1)
|
|
26
|
+
@local_file = File.join(@wiki.data_dir, "#{@name}.#{@wiki.extension}")
|
|
27
|
+
else
|
|
28
|
+
@name = name_or_url_or_fullpath
|
|
29
|
+
@local_file = File.join(@wiki.data_dir, "#{@name}.#{@wiki.extension}")
|
|
30
|
+
@url = @wiki.url + "/" + URI.encode(@name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@agent = Mechanize.new
|
|
34
|
+
@wiki.cookies.each do |cookie|
|
|
35
|
+
@agent.cookie_jar.add(URI.parse(@url), cookie)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Returns one of :remote_only, :local_only, :both or :nowhere
|
|
41
|
+
# (:nowhere should never happen...)
|
|
42
|
+
def exists_in
|
|
43
|
+
if self.local_exists? && self.remote_exists?
|
|
44
|
+
return :both
|
|
45
|
+
elsif self.local_exists?
|
|
46
|
+
return :local_only
|
|
47
|
+
elsif self.remote_exists?
|
|
48
|
+
return :remote_only
|
|
49
|
+
else
|
|
50
|
+
return :nowhere
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def local_exists?
|
|
56
|
+
File.exist? @local_file
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def local_updated_at
|
|
61
|
+
if local_exists?
|
|
62
|
+
File.stat(@local_file).mtime.to_datetime
|
|
63
|
+
else
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def remote_exists?
|
|
70
|
+
!remote_updated_at.nil?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def remote_updated_at
|
|
75
|
+
at = @remote_updated_at
|
|
76
|
+
now = DateTime.now
|
|
77
|
+
if at && ([at.year, at.month, at.day, at.hour, at.minute, at.second] == [now.year, now.month, now.day, 0, 0, 0])
|
|
78
|
+
return @remote_updated_at = history[0][:timestamp]
|
|
79
|
+
else
|
|
80
|
+
return at
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def remote_updated_at=(value)
|
|
86
|
+
if value
|
|
87
|
+
value = DateTime.parse(value.to_s)
|
|
88
|
+
value = DateTime.parse(value.to_s(:db) + DateTime.now.zone) if value.utc?
|
|
89
|
+
@remote_updated_at = value
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def synced_at
|
|
95
|
+
@synced_at
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def synced_at=(value)
|
|
100
|
+
if value
|
|
101
|
+
value = DateTime.parse(value.to_s)
|
|
102
|
+
value = DateTime.parse(value.to_s(:db) + DateTime.now.zone) if value.utc?
|
|
103
|
+
@synced_at = value
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def history
|
|
109
|
+
puts "--Getting page history for #{@name}"
|
|
110
|
+
now = DateTime.now
|
|
111
|
+
history = []
|
|
112
|
+
page = @agent.get(@url + "/history")
|
|
113
|
+
page.search("table.wiki-page-versions tbody tr").each do |tr|
|
|
114
|
+
timestamp = DateTime.parse(tr.search("td")[3].text + now.zone)
|
|
115
|
+
author_name = tr.search("td")[4].text.strip
|
|
116
|
+
history << {
|
|
117
|
+
:timestamp => timestamp,
|
|
118
|
+
:author_name => author_name
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
history
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def read
|
|
125
|
+
page = @agent.get(@url + "/edit")
|
|
126
|
+
page.search("textarea")[0].text
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def download_to(file)
|
|
131
|
+
File.open(file, "w+:UTF-8") { |f| f.write(self.read) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def download
|
|
136
|
+
download_to(@local_file)
|
|
137
|
+
self.synced_at = self.local_updated_at
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def write(text)
|
|
142
|
+
now = DateTime.now
|
|
143
|
+
page = @agent.get(@url + "/edit")
|
|
144
|
+
form = page.form_with(:id=>"wiki_form")
|
|
145
|
+
form.field_with(:name=>"content[text]").value = text
|
|
146
|
+
result_page = form.submit
|
|
147
|
+
errors = result_page.search("#errorExplanation li").map{ |li| li.text}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def write_from_file(file)
|
|
152
|
+
write(File.open(file, "r:UTF-8").read)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def upload
|
|
157
|
+
write_from_file(@local_file)
|
|
158
|
+
self.synced_at = self.remote_updated_at = DateTime.now
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def to_hash
|
|
163
|
+
{
|
|
164
|
+
:name => @name,
|
|
165
|
+
:url => @url,
|
|
166
|
+
:local_file => @local_file,
|
|
167
|
+
:remote_updated_at => @remote_updated_at,
|
|
168
|
+
:local_exists => local_exists?,
|
|
169
|
+
:synced_at => @synced_at,
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def to_s
|
|
175
|
+
str = "#<Redsync::WikiPage"
|
|
176
|
+
str << " name = \"#{name}\"\n"
|
|
177
|
+
str << " url = \"#{url}\"\n"
|
|
178
|
+
str << " local_file = \"#{local_file}\"\n"
|
|
179
|
+
str << " remote_exists? = #{remote_exists?}\n"
|
|
180
|
+
str << " remote_updated_at = #{@remote_updated_at ? @remote_updated_at : "<never>"}\n"
|
|
181
|
+
str << " local_exists? = #{local_exists?}\n"
|
|
182
|
+
str << " local_updated_at = #{local_updated_at ? local_updated_at : "<never>"}\n"
|
|
183
|
+
str << " synced_at = #{@synced_at ? @synced_at : "<never>"}\n"
|
|
184
|
+
str << ">"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
end
|
|
189
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: redsync
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,11 +9,11 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2011-12-
|
|
12
|
+
date: 2011-12-09 00:00:00.000000000Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: mechanize
|
|
16
|
-
requirement: &
|
|
16
|
+
requirement: &2155972500 !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
19
|
- - ~>
|
|
@@ -21,10 +21,10 @@ dependencies:
|
|
|
21
21
|
version: '2.0'
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
|
-
version_requirements: *
|
|
24
|
+
version_requirements: *2155972500
|
|
25
25
|
- !ruby/object:Gem::Dependency
|
|
26
26
|
name: activesupport
|
|
27
|
-
requirement: &
|
|
27
|
+
requirement: &2155972080 !ruby/object:Gem::Requirement
|
|
28
28
|
none: false
|
|
29
29
|
requirements:
|
|
30
30
|
- - ! '>='
|
|
@@ -32,7 +32,18 @@ dependencies:
|
|
|
32
32
|
version: '0'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
|
-
version_requirements: *
|
|
35
|
+
version_requirements: *2155972080
|
|
36
|
+
- !ruby/object:Gem::Dependency
|
|
37
|
+
name: ir_b
|
|
38
|
+
requirement: &2155971560 !ruby/object:Gem::Requirement
|
|
39
|
+
none: false
|
|
40
|
+
requirements:
|
|
41
|
+
- - ! '>='
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '0'
|
|
44
|
+
type: :runtime
|
|
45
|
+
prerelease: false
|
|
46
|
+
version_requirements: *2155971560
|
|
36
47
|
description: Sync Redmine's wiki pages to your local filesystem. Edit as you like,
|
|
37
48
|
then upsync.
|
|
38
49
|
email: merikonjatta@gmail.com
|
|
@@ -41,8 +52,11 @@ executables:
|
|
|
41
52
|
extensions: []
|
|
42
53
|
extra_rdoc_files: []
|
|
43
54
|
files:
|
|
55
|
+
- lib/datetime_nil_compare.rb
|
|
44
56
|
- lib/redsync/cli.rb
|
|
45
57
|
- lib/redsync/sync_stat.rb
|
|
58
|
+
- lib/redsync/wiki.rb
|
|
59
|
+
- lib/redsync/wiki_page.rb
|
|
46
60
|
- lib/redsync.rb
|
|
47
61
|
- README.textile
|
|
48
62
|
- config.yml.dist
|