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.
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  :url: "http://your.redmine.url/"
3
- :project_slug: "project_identifier"
3
+ :projects:
4
+ - "project_one"
5
+ - "project_two"
4
6
  :username: "username"
5
7
  :password: "password"
6
8
  :data_dir: "~/redsync"
@@ -0,0 +1,7 @@
1
+ require 'date'
2
+
3
+ class << nil
4
+ def to_datetime
5
+ DateTime.civil
6
+ end
7
+ end
@@ -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/sync_stat'
14
+ require 'redsync/wiki'
15
+ require 'redsync/wiki_page'
14
16
 
15
17
 
16
18
  class Redsync
17
19
 
18
- class Result
19
- DOWNLOADED = 1
20
- SKIPPED_UNKNOWN = 2
21
- SKIPPED_OLD = 4
22
- UPLOADED = 8
23
- CREATED = 16
24
- ERROR_ON_UPLOAD = 32
25
- ERROR_ON_CREATE = 64
26
- end
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
- @config = {}
31
- @config.merge! options
32
- @config[:data_dir] = File.expand_path(@config[:data_dir])
33
- @config[:conflicts_dir] = File.join(@config[:data_dir], "__redsync_conflicts__")
34
- puts "Using data dir: #{@config[:data_dir]}"
35
-
36
- @config[:url] = @config[:url].match(/(.*?)\/?$/)[1]
37
- @config[:login_url] = @config[:url] + "/login"
38
- @config[:wiki_base_url] = @config[:url] + "/projects/" + @config[:project_slug] + "/wiki"
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
- unless File.exist? @config[:data_dir]
51
- puts "Creating data dir"
52
- FileUtils.mkdir(@config[:data_dir])
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 #{@config[:username]} to #{@config[:login_url]}..."
59
- page = @agent.get(@config[:login_url])
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 = @config[:username]
62
- login_form.field_with(:name => "password").value = @config[:password]
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 downsync
75
- @syncstat.refresh
76
- puts "\nDownsync:"
77
- statuses = {
78
- Result::DOWNLOADED => 0,
79
- }
80
- @syncstat.remote_updated_page_names.each do |pagename|
81
- statuses[download(pagename)] += 1
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 download(pagename)
88
- stat = @syncstat.for(pagename)
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
- puts "--Download #{pagename}"
91
- page = @agent.get(stat[:url] + "/edit")
92
- File.open(stat[:local_file], "w+:UTF-8") { |f| f.write(page.search("textarea")[0].text) }
93
- @syncstat.update(pagename, :downloaded_at => File.stat(stat[:local_file]).mtime.to_datetime)
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 upsync
100
- puts "\nUpsync:"
101
- results = {
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 upload(pagename, create = false)
123
- now = DateTime.now
124
- stat = @syncstat.for(pagename)
125
- puts (create ? "--Create #{pagename}" : "--Upload #{pagename}")
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
- if errors.any?
134
- print "--Error: #{pagename}: "
135
- puts errors
136
- return (create ? Result::ERROR_ON_CREATE : Result::ERROR_ON_UPLOAD)
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
@@ -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
- time do
16
- redsync.downsync unless @options[:uponly]
17
- redsync.upsync unless @options[:downonly]
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", "--[no-]verbose", "Output verbose logs") do |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("-u", "--upsync-only", "Upsync only, don't downsync") do |v|
36
- @options[:uponly] = v
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("-d", "--downsync-only", "Downsync only, don't upsync") do |v|
39
- @options[:downonly] = v
46
+ opts.on("-i", "--interactive", "Interactive mode (irb)") do |v|
47
+ @options[:run_mode] = :interactive
40
48
  end
41
- opts.on("-D", "--debug", "Debug mode. Requires ruby-debug19") do |v|
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 if result && block
86
+ (result && block) ? block.call : result
78
87
  end
79
88
 
80
89
 
@@ -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}.txt")
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(/(.*)\.txt$/)[1]
62
+ name = name.first.match(/(.*)\.#{extension}$/)[1]
63
63
  next if @stat[name]
64
64
 
65
- local_file = File.join(@config[:data_dir], "#{name}.txt")
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,
@@ -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.1.2
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-07 00:00:00.000000000Z
12
+ date: 2011-12-09 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mechanize
16
- requirement: &2160416520 !ruby/object:Gem::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: *2160416520
24
+ version_requirements: *2155972500
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activesupport
27
- requirement: &2160416120 !ruby/object:Gem::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: *2160416120
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