redsync 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.textile +4 -16
- data/bin/redsync +2 -2
- data/config.yml.dist +2 -3
- data/lib/redsync.rb +15 -86
- data/lib/redsync/cli.rb +1 -5
- data/lib/redsync/local_wiki.rb +53 -0
- data/lib/redsync/project.rb +139 -0
- data/lib/redsync/remote_wiki.rb +61 -0
- data/lib/redsync/wiki.rb +3 -10
- data/lib/redsync/wiki_page.rb +5 -183
- metadata +27 -32
- data/lib/datetime_nil_compare.rb +0 -7
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 2aa4de80baafdcadc5c491f40027c3f8e82be33a
|
|
4
|
+
data.tar.gz: 30a6132b64547025e55dd3c9af86b4b526de9cd3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8028468044e10c025ec1782cdc240b2266e8a996fd33831eb2211f4ac384647ba643628570045b7c46c439f7d985dae83827e8ea87d21a36ad9f0d77e5c6ce8d
|
|
7
|
+
data.tar.gz: 4aba4790ed8efb2429fa5b29b6d774dad038460bd78e8003b01a7986e0063b40c65de1ef49ad6fc4830559347981ee4cd67676a13178206f797e735664b5c5a9
|
data/README.textile
CHANGED
|
@@ -4,7 +4,7 @@ h2. What's this?
|
|
|
4
4
|
|
|
5
5
|
Sync your Redmine's wiki contents to your local filesystem.
|
|
6
6
|
Edit them while offline, then upsync them to Redmine.
|
|
7
|
-
(Requires Ruby
|
|
7
|
+
(Requires Ruby 2.0)
|
|
8
8
|
|
|
9
9
|
h2. Install
|
|
10
10
|
|
|
@@ -16,12 +16,11 @@ gem install redsync
|
|
|
16
16
|
h2. Usage
|
|
17
17
|
|
|
18
18
|
<pre>
|
|
19
|
-
$ redsync --help
|
|
20
19
|
Usage: redsync [options]
|
|
21
|
-
-v, --
|
|
20
|
+
-v, --verbose Output verbose logs
|
|
22
21
|
-c, --config FILE Use specified config file instead of ~/redsync.yml
|
|
23
|
-
-
|
|
24
|
-
-
|
|
22
|
+
-s, --status Status check. No uploads or downloads will happen
|
|
23
|
+
-i, --interactive Interactive mode (pry)
|
|
25
24
|
</pre>
|
|
26
25
|
|
|
27
26
|
|
|
@@ -36,11 +35,6 @@ This software has NOT BEEN TESTED :(
|
|
|
36
35
|
Use it at your own risk.
|
|
37
36
|
Please beware that it may not work on all versions of Redmine (it's working fine with my installation of 1.2-stable)
|
|
38
37
|
|
|
39
|
-
h3. Time zones
|
|
40
|
-
|
|
41
|
-
Redsync assumes that your local timezone is the same as your Redmine timezone.
|
|
42
|
-
Sign in and go to "My Account" to change it.
|
|
43
|
-
|
|
44
38
|
h3. Conflicts
|
|
45
39
|
|
|
46
40
|
Redsync does not deal with conflicts well.
|
|
@@ -48,12 +42,6 @@ In fact, it does not deal with conflics AT ALL.
|
|
|
48
42
|
A default run of redsync will downsync first, overwriting any local changes if pages are updated on Redmine.
|
|
49
43
|
Then it will upsync any remaining changes.
|
|
50
44
|
|
|
51
|
-
h3. Deletions
|
|
52
|
-
|
|
53
|
-
Redsy does not delete anything.
|
|
54
|
-
It doesn't delete local files when remote wiki pages are gone.
|
|
55
|
-
It doesn't delete remote wiki pages when local files are gone either.
|
|
56
|
-
|
|
57
45
|
|
|
58
46
|
h2. License
|
|
59
47
|
|
data/bin/redsync
CHANGED
data/config.yml.dist
CHANGED
data/lib/redsync.rb
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
# encoding:UTF-8
|
|
2
2
|
|
|
3
|
-
require 'rubygems'
|
|
4
3
|
require 'fileutils'
|
|
5
4
|
require 'uri'
|
|
6
5
|
require 'yaml'
|
|
7
|
-
require 'date'
|
|
8
|
-
require 'iconv'
|
|
9
6
|
require 'mechanize'
|
|
10
|
-
require '
|
|
11
|
-
require 'ir_b'
|
|
7
|
+
require 'pry'
|
|
12
8
|
|
|
13
9
|
require 'redsync/cli'
|
|
14
|
-
require 'redsync/
|
|
15
|
-
require 'redsync/wiki_page'
|
|
10
|
+
require 'redsync/project'
|
|
16
11
|
|
|
17
12
|
|
|
18
13
|
class Redsync
|
|
@@ -32,101 +27,35 @@ class Redsync
|
|
|
32
27
|
# :data_dir => Directory to read/write. Required.
|
|
33
28
|
# :extension => Filename extensions. Defaults to "txt"
|
|
34
29
|
def initialize(options)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@password = options[:password]
|
|
43
|
-
@data_dir = File.expand_path(options[:data_dir])
|
|
44
|
-
@extension = options[:extension]
|
|
45
|
-
|
|
46
|
-
@login_url = @url + "/login"
|
|
47
|
-
|
|
48
|
-
initialize_system_files
|
|
49
|
-
|
|
50
|
-
@agent = Mechanize.new
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def initialize_system_files
|
|
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)
|
|
30
|
+
@projects = options[:projects].map do |pj|
|
|
31
|
+
Project.new(
|
|
32
|
+
:url => options[:url].sub(/^(.*?)\/?$/, '\1') + "/projects/#{pj}/wiki",
|
|
33
|
+
:api_key => options[:api_key],
|
|
34
|
+
:data_dir => File.join(options[:data_dir], pj),
|
|
35
|
+
:extension => options[:extension]
|
|
36
|
+
)
|
|
60
37
|
end
|
|
61
|
-
end
|
|
62
38
|
|
|
63
|
-
|
|
64
|
-
def login
|
|
65
|
-
puts "Logging in as #{@username} to #{@login_url}..."
|
|
66
|
-
page = @agent.get(@login_url)
|
|
67
|
-
login_form = page.form_with(:action => "/login")
|
|
68
|
-
login_form.field_with(:name => "username").value = @username
|
|
69
|
-
login_form.field_with(:name => "password").value = @password
|
|
70
|
-
result_page = login_form.submit
|
|
71
|
-
if result_page.search("a.logout").any?
|
|
72
|
-
puts "Logged in successfully."
|
|
73
|
-
instantiate_wikis
|
|
74
|
-
return true
|
|
75
|
-
else
|
|
76
|
-
puts "Login failed."
|
|
77
|
-
return false
|
|
78
|
-
end
|
|
79
|
-
end
|
|
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
|
|
91
|
-
end
|
|
39
|
+
@agent = Mechanize.new
|
|
92
40
|
end
|
|
93
41
|
|
|
94
42
|
|
|
95
43
|
def status_check
|
|
96
|
-
@projects.each do |
|
|
97
|
-
|
|
98
|
-
wiki.load_pages_cache
|
|
99
|
-
wiki.scan
|
|
100
|
-
|
|
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"
|
|
44
|
+
@projects.each do |pj|
|
|
45
|
+
pj.status_check
|
|
104
46
|
end
|
|
105
47
|
end
|
|
106
48
|
|
|
107
49
|
|
|
108
50
|
def sync_all
|
|
109
|
-
@projects.each do |
|
|
110
|
-
sync
|
|
51
|
+
@projects.each do |pj|
|
|
52
|
+
pj.sync
|
|
111
53
|
end
|
|
112
54
|
end
|
|
113
55
|
|
|
114
56
|
|
|
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
|
|
122
|
-
|
|
123
|
-
|
|
124
57
|
def interactive
|
|
125
|
-
|
|
126
|
-
wiki.load_pages_cache
|
|
127
|
-
wiki.scan
|
|
128
|
-
end
|
|
129
|
-
ir b
|
|
58
|
+
binding.pry
|
|
130
59
|
end
|
|
131
60
|
|
|
132
61
|
|
data/lib/redsync/cli.rb
CHANGED
|
@@ -11,7 +11,6 @@ class Redsync
|
|
|
11
11
|
check_config_file
|
|
12
12
|
|
|
13
13
|
redsync = Redsync.new(YAML.load_file(@options.delete(:config_file)).merge(@options))
|
|
14
|
-
exit unless redsync.login
|
|
15
14
|
|
|
16
15
|
case @options[:run_mode]
|
|
17
16
|
when :full_sync
|
|
@@ -43,12 +42,9 @@ class Redsync
|
|
|
43
42
|
opts.on("-s", "--status", "Status check. No uploads or downloads will happen") do |v|
|
|
44
43
|
@options[:run_mode] = :status_check
|
|
45
44
|
end
|
|
46
|
-
opts.on("-i", "--interactive", "Interactive mode (
|
|
45
|
+
opts.on("-i", "--interactive", "Interactive mode (pry)") do |v|
|
|
47
46
|
@options[:run_mode] = :interactive
|
|
48
47
|
end
|
|
49
|
-
opts.on("-D", "--debugger", "Debug mode. Requires ruby-debug19") do |v|
|
|
50
|
-
@options[:debug] = v
|
|
51
|
-
end
|
|
52
48
|
end.parse!
|
|
53
49
|
|
|
54
50
|
if @options[:debug]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
class Redsync
|
|
2
|
+
class LocalWiki
|
|
3
|
+
|
|
4
|
+
def initialize(options)
|
|
5
|
+
@data_dir = options[:data_dir]
|
|
6
|
+
@extension = options[:extension]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def list
|
|
11
|
+
unless @pages
|
|
12
|
+
@pages = {}
|
|
13
|
+
Dir["#{@data_dir}/**/*.#{@extension}"].each do |file|
|
|
14
|
+
name = File.basename(file, "." + @extension)
|
|
15
|
+
name = name.encode("UTF-8", "UTF-8-MAC", :invalid => :replace, :undef => :replace) if RUBY_PLATFORM =~ /darwin/
|
|
16
|
+
@pages[name] = WikiPage.new(
|
|
17
|
+
:name => name,
|
|
18
|
+
:mtime => File.mtime(file)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
@pages.values
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get(name)
|
|
28
|
+
list unless @pages
|
|
29
|
+
|
|
30
|
+
return nil if @pages[name].nil?
|
|
31
|
+
|
|
32
|
+
unless @pages[name].content
|
|
33
|
+
@pages[name].content = File.read(path_for(name))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
@pages[name]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def write(name, content)
|
|
41
|
+
File.open(path_for(name), "w+:UTF-8") do |f|
|
|
42
|
+
f.write(content)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def path_for(name)
|
|
48
|
+
name = name.encode("UTF-8-MAC", "UTF-8", :invalid => :replace, :undef => :replace) if RUBY_PLATFORM =~ /darwin/
|
|
49
|
+
File.join(@data_dir, name + "." + @extension)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'redsync/local_wiki'
|
|
4
|
+
require 'redsync/remote_wiki'
|
|
5
|
+
require 'redsync/wiki_page'
|
|
6
|
+
|
|
7
|
+
class Redsync
|
|
8
|
+
class Project
|
|
9
|
+
|
|
10
|
+
def initialize(options)
|
|
11
|
+
@url = options[:url]
|
|
12
|
+
@api_key = options[:api_key]
|
|
13
|
+
@data_dir = File.expand_path(options[:data_dir])
|
|
14
|
+
@extension = options[:extension] || "txt"
|
|
15
|
+
|
|
16
|
+
initialize_system_files
|
|
17
|
+
|
|
18
|
+
@syncstat = {}
|
|
19
|
+
@syncdone = {}
|
|
20
|
+
@syncstat_file = File.join(@data_dir, "__redsync_syncstat.yml")
|
|
21
|
+
load_syncstat
|
|
22
|
+
|
|
23
|
+
@remote_wiki = RemoteWiki.new(url: @url, api_key: @api_key)
|
|
24
|
+
@local_wiki = LocalWiki.new(data_dir: @data_dir, extension: @extension)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def initialize_system_files
|
|
29
|
+
unless File.exist? @data_dir
|
|
30
|
+
puts "Creating #{@data_dir}"
|
|
31
|
+
FileUtils.mkdir_p(@data_dir)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_syncstat
|
|
37
|
+
@syncstat = {}
|
|
38
|
+
@syncdone = {}
|
|
39
|
+
return unless File.exist? @syncstat_file
|
|
40
|
+
@syncstat = YAML.load_file(@syncstat_file)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def save_syncstat
|
|
45
|
+
File.open(@syncstat_file, "w+:UTF-8") do |f|
|
|
46
|
+
f.write(@syncstat.to_yaml)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def status_check
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def sync
|
|
56
|
+
downsync
|
|
57
|
+
upsync
|
|
58
|
+
cleanup
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def downsync
|
|
63
|
+
@remote_wiki.list.each do |page|
|
|
64
|
+
next if @syncdone[page.name]
|
|
65
|
+
if @syncstat[page.name].nil? || page.mtime > @syncstat[page.name] && page.mtime > @local_wiki.get(page.name).mtime
|
|
66
|
+
download(page.name)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def upsync
|
|
73
|
+
@local_wiki.list.each do |page|
|
|
74
|
+
next if @syncdone[page.name]
|
|
75
|
+
if @syncstat[page.name].nil? || page.mtime > @syncstat[page.name] && page.mtime > @remote_wiki.get(page.name).mtime
|
|
76
|
+
upload(page.name)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def cleanup
|
|
83
|
+
@syncstat.each do |name, timestamp|
|
|
84
|
+
next if @syncdone[name]
|
|
85
|
+
if @local_wiki.get(name).nil? && @remote_wiki.get(name)
|
|
86
|
+
delete_remote(name)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if @local_wiki.get(name) && @remote_wiki.get(name).nil?
|
|
90
|
+
delete_local(name)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if @local_wiki.get(name).nil? && @remote_wiki.get(name).nil?
|
|
94
|
+
@syncstat.delete(name)
|
|
95
|
+
save_syncstat
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def download(name)
|
|
102
|
+
puts "Downloading:\t#{name}"
|
|
103
|
+
@local_wiki.write(name, @remote_wiki.get(name).content)
|
|
104
|
+
@syncstat[name] = Time.new
|
|
105
|
+
@syncdone[name] = true
|
|
106
|
+
save_syncstat
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def upload(name)
|
|
111
|
+
puts "Uploading:\t#{name}"
|
|
112
|
+
@remote_wiki.write(name, @local_wiki.get(name).content)
|
|
113
|
+
@syncstat[name] = Time.new
|
|
114
|
+
@syncdone[name] = true
|
|
115
|
+
save_syncstat
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def delete_local(name)
|
|
120
|
+
puts "Deleted on redmine:\t#{name}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def delete_remote(name)
|
|
125
|
+
puts "Deleted locally:\t#{name}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def to_s
|
|
130
|
+
str = "#<Redsync::Project"
|
|
131
|
+
str << " url = \"#{@url}\"\n"
|
|
132
|
+
str << " data_dir = \"#{@data_dir}\"\n"
|
|
133
|
+
str << " extension = \"#{@extension}\"\n"
|
|
134
|
+
str << " pages = #{@syncstat.count}\n"
|
|
135
|
+
str << ">"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require 'mechanize'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
class Redsync
|
|
5
|
+
class RemoteWiki
|
|
6
|
+
def initialize(options)
|
|
7
|
+
@url = options[:url]
|
|
8
|
+
@api_key = options[:api_key]
|
|
9
|
+
@agent = Mechanize.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def list
|
|
13
|
+
unless @pages
|
|
14
|
+
@pages = {}
|
|
15
|
+
|
|
16
|
+
index = @agent.get(@url + "/index.xml?key=#{@api_key}")
|
|
17
|
+
index.xml.at("wiki_pages").children.each do |node|
|
|
18
|
+
name = node.at("title").text
|
|
19
|
+
@pages[name] = WikiPage.new(
|
|
20
|
+
:name => name,
|
|
21
|
+
:mtime => Time.parse(node.at("updated_on").text),
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@pages.values
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get(name)
|
|
30
|
+
list unless @pages
|
|
31
|
+
|
|
32
|
+
return nil if @pages[name].nil?
|
|
33
|
+
|
|
34
|
+
unless @pages[name].content
|
|
35
|
+
page = @agent.get(@url + "/#{URI.encode(name)}.xml?key=#{@api_key}")
|
|
36
|
+
@pages[name].content = page.at("text").text
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@pages[name]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def write(name, content)
|
|
44
|
+
doc = Nokogiri::XML::Document.new
|
|
45
|
+
wiki_page = Nokogiri::XML::Node.new("wiki_page", doc)
|
|
46
|
+
|
|
47
|
+
text = Nokogiri::XML::Node.new("text", doc)
|
|
48
|
+
text.content = content
|
|
49
|
+
|
|
50
|
+
comments = Nokogiri::XML::Node.new("comments", doc)
|
|
51
|
+
comments.content = "Uploaded by Redsync"
|
|
52
|
+
|
|
53
|
+
doc << wiki_page
|
|
54
|
+
wiki_page << text
|
|
55
|
+
wiki_page << comments
|
|
56
|
+
|
|
57
|
+
@agent.put(@url + "/#{URI.encode(name)}.xml?key=#{@api_key}", doc.to_s, 'Content-Type' => "text/xml")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
data/lib/redsync/wiki.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
require 'uri'
|
|
2
2
|
require 'mechanize'
|
|
3
3
|
require 'yaml'
|
|
4
|
-
require 'iconv'
|
|
5
4
|
|
|
6
5
|
class Redsync
|
|
7
6
|
class Wiki
|
|
8
7
|
|
|
9
8
|
attr_reader :url,
|
|
9
|
+
:api_key,
|
|
10
10
|
:data_dir,
|
|
11
11
|
:extension
|
|
12
12
|
|
|
@@ -17,13 +17,11 @@ class Redsync
|
|
|
17
17
|
# :extension => File extensions for page files. Defaults to "txt"
|
|
18
18
|
def initialize(options)
|
|
19
19
|
@url = options[:url].match(/(.*?)\/?$/)[1]
|
|
20
|
+
@api_key = options[:api_key]
|
|
20
21
|
@data_dir = File.expand_path(options[:data_dir])
|
|
21
22
|
@extension = options[:extension]
|
|
22
23
|
|
|
23
24
|
@agent = Mechanize.new
|
|
24
|
-
options[:cookies].each do |cookie|
|
|
25
|
-
@agent.cookie_jar.add(URI.parse(@url), cookie)
|
|
26
|
-
end
|
|
27
25
|
|
|
28
26
|
@pages_cache = {}
|
|
29
27
|
@pages_cache_file = File.join(@data_dir, "__redsync_pages_cache.yml")
|
|
@@ -32,10 +30,6 @@ class Redsync
|
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
|
|
35
|
-
def cookies
|
|
36
|
-
@agent.cookie_jar.cookies(URI.parse(@url))
|
|
37
|
-
end
|
|
38
|
-
|
|
39
33
|
def initialize_system_files
|
|
40
34
|
unless File.exist? @data_dir
|
|
41
35
|
puts "Creating #{@data_dir}"
|
|
@@ -107,7 +101,6 @@ class Redsync
|
|
|
107
101
|
|
|
108
102
|
def scan_remote
|
|
109
103
|
webpage = @agent.get(@url + "/date_index")
|
|
110
|
-
now = DateTime.now
|
|
111
104
|
|
|
112
105
|
# Get remote and local update times using remote list
|
|
113
106
|
webpage.search("#content h3").each do |h3|
|
|
@@ -127,7 +120,7 @@ class Redsync
|
|
|
127
120
|
next if File.directory? file
|
|
128
121
|
next if file =~ /^__redsync_/
|
|
129
122
|
page_name = file.match(/([^\/\\]+?)\.#{@extension}$/)[1]
|
|
130
|
-
page_name =
|
|
123
|
+
page_name = page_name.encode("UTF-8-MAC", "UTF-8", :invalid => :replace, :undef => :replace) if RUBY_PLATFORM =~ /darwin/
|
|
131
124
|
next if pages[page_name]
|
|
132
125
|
pp file
|
|
133
126
|
wiki_page = WikiPage.new(self, page_name)
|
data/lib/redsync/wiki_page.rb
CHANGED
|
@@ -1,189 +1,11 @@
|
|
|
1
|
-
require 'fileutils'
|
|
2
|
-
require 'date'
|
|
3
|
-
require 'active_support/all'
|
|
4
|
-
require 'redsync/wiki'
|
|
5
|
-
|
|
6
1
|
class Redsync
|
|
7
2
|
class WikiPage
|
|
8
|
-
|
|
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
|
|
3
|
+
attr_accessor :name, :mtime, :content
|
|
53
4
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
5
|
+
def initialize(options)
|
|
6
|
+
@name = options[:name]
|
|
7
|
+
@mtime = options[:mtime]
|
|
8
|
+
@content = options[:content]
|
|
127
9
|
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
10
|
end
|
|
189
11
|
end
|
metadata
CHANGED
|
@@ -1,49 +1,43 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: redsync
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 0.3.0
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- merikonjatta
|
|
9
8
|
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 2014-03-15 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: mechanize
|
|
16
|
-
requirement:
|
|
17
|
-
none: false
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
18
16
|
requirements:
|
|
19
|
-
- - ~>
|
|
17
|
+
- - "~>"
|
|
20
18
|
- !ruby/object:Gem::Version
|
|
21
|
-
version:
|
|
19
|
+
version: 2.7.3
|
|
22
20
|
type: :runtime
|
|
23
21
|
prerelease: false
|
|
24
|
-
version_requirements:
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 2.7.3
|
|
25
27
|
- !ruby/object:Gem::Dependency
|
|
26
|
-
name:
|
|
27
|
-
requirement:
|
|
28
|
-
none: false
|
|
28
|
+
name: pry
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
30
|
requirements:
|
|
30
|
-
- -
|
|
31
|
+
- - ">="
|
|
31
32
|
- !ruby/object:Gem::Version
|
|
32
33
|
version: '0'
|
|
33
34
|
type: :runtime
|
|
34
35
|
prerelease: false
|
|
35
|
-
version_requirements:
|
|
36
|
-
- !ruby/object:Gem::Dependency
|
|
37
|
-
name: ir_b
|
|
38
|
-
requirement: &2155971560 !ruby/object:Gem::Requirement
|
|
39
|
-
none: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
37
|
requirements:
|
|
41
|
-
- -
|
|
38
|
+
- - ">="
|
|
42
39
|
- !ruby/object:Gem::Version
|
|
43
40
|
version: '0'
|
|
44
|
-
type: :runtime
|
|
45
|
-
prerelease: false
|
|
46
|
-
version_requirements: *2155971560
|
|
47
41
|
description: Sync Redmine's wiki pages to your local filesystem. Edit as you like,
|
|
48
42
|
then upsync.
|
|
49
43
|
email: merikonjatta@gmail.com
|
|
@@ -52,37 +46,38 @@ executables:
|
|
|
52
46
|
extensions: []
|
|
53
47
|
extra_rdoc_files: []
|
|
54
48
|
files:
|
|
55
|
-
-
|
|
49
|
+
- README.textile
|
|
50
|
+
- bin/redsync
|
|
51
|
+
- config.yml.dist
|
|
52
|
+
- lib/redsync.rb
|
|
56
53
|
- lib/redsync/cli.rb
|
|
54
|
+
- lib/redsync/local_wiki.rb
|
|
55
|
+
- lib/redsync/project.rb
|
|
56
|
+
- lib/redsync/remote_wiki.rb
|
|
57
57
|
- lib/redsync/sync_stat.rb
|
|
58
58
|
- lib/redsync/wiki.rb
|
|
59
59
|
- lib/redsync/wiki_page.rb
|
|
60
|
-
- lib/redsync.rb
|
|
61
|
-
- README.textile
|
|
62
|
-
- config.yml.dist
|
|
63
|
-
- bin/redsync
|
|
64
60
|
homepage: http://github.com/merikonjatta/redsync
|
|
65
61
|
licenses: []
|
|
62
|
+
metadata: {}
|
|
66
63
|
post_install_message:
|
|
67
64
|
rdoc_options: []
|
|
68
65
|
require_paths:
|
|
69
66
|
- lib
|
|
70
67
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
|
-
none: false
|
|
72
68
|
requirements:
|
|
73
|
-
- -
|
|
69
|
+
- - ">="
|
|
74
70
|
- !ruby/object:Gem::Version
|
|
75
71
|
version: '0'
|
|
76
72
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
|
-
none: false
|
|
78
73
|
requirements:
|
|
79
|
-
- -
|
|
74
|
+
- - ">="
|
|
80
75
|
- !ruby/object:Gem::Version
|
|
81
76
|
version: '0'
|
|
82
77
|
requirements: []
|
|
83
78
|
rubyforge_project:
|
|
84
|
-
rubygems_version:
|
|
79
|
+
rubygems_version: 2.2.2
|
|
85
80
|
signing_key:
|
|
86
|
-
specification_version:
|
|
81
|
+
specification_version: 4
|
|
87
82
|
summary: Sync Redmine's wiki pages to your local filesystem.
|
|
88
83
|
test_files: []
|