hallettj-cloudrcs 0.0.1

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.
@@ -0,0 +1,89 @@
1
+ module CloudRCS
2
+
3
+ # A primitive patch type that represents that a file has been moved
4
+ # or renamed.
5
+ class Move < PrimitivePatch
6
+ validates_presence_of :path, :original_path
7
+
8
+ def after_initialize
9
+ verify_path_prefix
10
+ verify_original_path_prefix
11
+ end
12
+
13
+ def to_s
14
+ "move #{self.class.escape_path(original_path)} #{self.class.escape_path(path)}"
15
+ end
16
+
17
+ # The inverse patch moves the file back to its original location.
18
+ def inverse
19
+ Move.new(:original_path => path, :path => original_path)
20
+ end
21
+
22
+ def commute(patch)
23
+ if patch.is_a? Move
24
+ if patch.original_path == self.new_path
25
+ raise CommuteException(true, "Conflict: cannot commute move patches that affect the same file.")
26
+ elsif patch.new_path == self.original_path
27
+ raise CommuteException(true, "Conflict: commuting these move patches would result in two files with the same name.")
28
+
29
+ elsif patch.new_path == self.new_path
30
+ raise CommuteException(true, "Conflict: cannot commute move patches that affect the same files.")
31
+
32
+ else
33
+ patch1 = Move.new(:path => patch.path, :original_path => patch.original_path)
34
+ patch2 = Move.new(:path => self.path, :original_path => self.original_path)
35
+ end
36
+
37
+ elsif patch.is_a? Addfile and patch.path == self.original_path
38
+ raise CommuteException(true, "Conflict: move and addfile are order-dependent in this case.")
39
+
40
+ elsif patch.is_a? Rmfile and patch.path == self.new_path
41
+ raise CommuteException(true, "Conflict: move and rmfile are order-dependent in this case.")
42
+
43
+ # If the other patch is something like a Hunk or a Binary, and
44
+ # it operates on the file path that this patch moves a file to,
45
+ # then the commuted version of that patch should have a file
46
+ # path that matches the original_path of this patch.
47
+ elsif patch.path == self.new_path
48
+ patch1 = patch.clone
49
+ patch1.path = self.original_path
50
+ patch2 = self.clone
51
+
52
+ else
53
+ patch1 = patch.clone
54
+ patch2 = self.clone
55
+ end
56
+
57
+ return patch1, patch2
58
+ end
59
+
60
+ def apply_to(file)
61
+ if file.path == original_path
62
+ file.path = new_path
63
+ end
64
+ return file
65
+ end
66
+
67
+ class << self
68
+
69
+ def generate(orig_file, changed_file)
70
+ return if orig_file.nil? or changed_file.nil?
71
+ if orig_file.path != changed_file.path
72
+ return Move.new(:original_path => orig_file.path, :path => changed_file.path)
73
+ end
74
+ end
75
+
76
+ def parse(contents)
77
+ unless contents =~ /^move\s+(\S+)\s+(\S+)\s*$/
78
+ raise "Failed to parse move patch: \"#{contents}\""
79
+ end
80
+ Move.new(:original_path => unescape_path($1), :path => unescape_path($2))
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+
87
+ PATCH_TYPES << Move
88
+
89
+ end
@@ -0,0 +1,63 @@
1
+ module CloudRCS
2
+
3
+ # A primitive patch type that represents the deletion of a file.
4
+ class Rmfile < PrimitivePatch
5
+ validates_presence_of :path
6
+
7
+ def after_initialize
8
+ verify_path_prefix
9
+ end
10
+
11
+ def to_s
12
+ "rmfile #{self.class.escape_path(path)}"
13
+ end
14
+
15
+ def inverse
16
+ Addfile.new(:path => path)
17
+ end
18
+
19
+ def commute(patch)
20
+ if patch.is_a? Rmfile and patch.path == self.path
21
+ raise CommuteException(true, "Conflict: cannot remove the same file twice.")
22
+ elsif patch.is_a? Addfile and patch.path == self.path
23
+ raise CommuteException(true, "Conflict: commuting rmfile and addfile yields two files with the same name.")
24
+ elsif patch.is_a? Move and patch.path == self.path
25
+ raise CommuteException(true, "Conflict: commuting rmfile and move yields two files with the same name.")
26
+ else
27
+ patch1 = patch.clone
28
+ patch2 = self.clone
29
+ end
30
+ return patch1, patch2
31
+ end
32
+
33
+ def apply_to(file)
34
+ return file unless file and file.path == path
35
+ return nil # Returning nil simulates deletion.
36
+ end
37
+
38
+ class << self
39
+
40
+ def priority
41
+ 90
42
+ end
43
+
44
+ def generate(orig_file, changed_file)
45
+ if changed_file.nil? and not orig_file.nil?
46
+ return Rmfile.new(:path => orig_file.path)
47
+ end
48
+ end
49
+
50
+ def parse(contents)
51
+ unless contents =~ /^rmfile\s+(\S+)\s*$/
52
+ raise "Failed to parse rmfile patch: #{contents}"
53
+ end
54
+ Rmfile.new(:path => unescape_path($1))
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ PATCH_TYPES << Rmfile
62
+
63
+ end
@@ -0,0 +1,147 @@
1
+ module CloudRCS
2
+
3
+ # PrimitivePatch acts as an intermediary between Patch and the
4
+ # primitive patch types. It allows primitive patches to inherit
5
+ # methods from Patch, and to use the same table. But it also allows
6
+ # for defining behavior that differs from Patch but that is
7
+ # automatically inherited by all primitive patch types.
8
+
9
+ class PrimitivePatch < ActiveRecord::Base
10
+ PATH_PREFIX = "./"
11
+
12
+ # Primitive patches belong to a named patch and a file. They also
13
+ # use the acts_as_list plugin to maintain a specific order within
14
+ # the named patch.
15
+ belongs_to :patch
16
+ # belongs_to :file, :polymorphic => true
17
+
18
+ # validates_presence_of :patch_id
19
+ # validates_presence_of :file_id
20
+
21
+ acts_as_list :column => :rank, :scope => :patch_id
22
+
23
+ def apply!
24
+ target_file = locate_file(original_path || path)
25
+ old_target = target_file
26
+ target_file = apply_to(target_file)
27
+ if target_file.nil?
28
+ old_target.destroy
29
+ else
30
+ target_file.save
31
+ end
32
+ # update_attribute(:file, target_file)
33
+ return target_file
34
+ end
35
+
36
+ def named_patch?; false; end
37
+ def primitive_patch?; true; end
38
+
39
+ # def locate_file(path)
40
+ # raise "You must override the locate_file method for PrimitivePatch."
41
+ # self.class.file_class.locate(path)
42
+ # end
43
+
44
+ def apply_to(file)
45
+ override "apply_to(file)"
46
+ end
47
+
48
+ def inverse
49
+ override "inverse"
50
+ end
51
+
52
+ def commute(patch)
53
+ override "commute(patch)"
54
+ end
55
+
56
+ def to_s
57
+ override "to_s"
58
+ end
59
+
60
+ def to_a
61
+ [self]
62
+ end
63
+
64
+ def new_path; path; end
65
+
66
+ protected
67
+
68
+ # Most primitive patches contain a file path. The darcs patch format
69
+ # may require a different path prefix than CloudFiles do; so this
70
+ # method makes the conversion if required.
71
+ def verify_path_prefix
72
+ unless path =~ /^#{PATH_PREFIX}/
73
+ path = PATH_PREFIX + path unless path.blank?
74
+ end
75
+ end
76
+ def verify_original_path_prefix
77
+ unless original_path =~ /^#{PATH_PREFIX}/
78
+ original_path = PATH_PREFIX + original_path unless original_path.blank?
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def override(method)
85
+ raise "Method '#{method}' should be overridden by each patch type."
86
+ end
87
+
88
+ class << self
89
+
90
+ # Patch type priority represents the relative likelihood that a
91
+ # patch type will cause conflicts if it is applied before other
92
+ # patch types. Patch types with a high priority value are likely
93
+ # to cause conflicts if they are applied early, and so should be
94
+ # deferred until after other patch types have done their thing.
95
+ #
96
+ # Default priority is 50.
97
+ def priority
98
+ return 50
99
+ end
100
+
101
+ def generate(orig_file, changed_file)
102
+ override "generate"
103
+ end
104
+
105
+ def parse(contents)
106
+ override "parse"
107
+ end
108
+
109
+ def merge(patch_a, patch_b)
110
+ patch_b_prime = commute(patch_a.inverse, patch_b).first
111
+ return patch_a, patch_b_prime
112
+ end
113
+
114
+ # Returns class of :file association. Won't work with polymorphism.
115
+ # def file_class
116
+ # reflect_on_association(:file).class_name.constantize
117
+ # end
118
+
119
+ # Replace special charecters in file paths with ASCII codes
120
+ # bounded by backslashes.
121
+ def escape_path(path)
122
+ # The backslash will be re-interpolated by the regular
123
+ # expression; so four backslashes in the string definition are
124
+ # necessary instead of two.
125
+ special_chars = ['\\\\',' ']
126
+ path.gsub(/#{special_chars.join('|')}/) { |match| "\\#{match[0]}\\" }
127
+ end
128
+
129
+ # Replace escaped characters with the original versions. Escaped
130
+ # characters are of the format, /\\(\d{2,3})\\/; where the digits
131
+ # enclosed in backslashes represent the ASCII code of the original
132
+ # character.
133
+ def unescape_path(path)
134
+ path.gsub(/\\(\d{2,3})\\/) { $1.to_i.chr }
135
+ end
136
+
137
+ private
138
+
139
+ def override(method)
140
+ raise "Class method '#{method}' should be overridden by each patch type."
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+
147
+ end
data/lib/cloudrcs.rb ADDED
@@ -0,0 +1,12 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ gem 'activerecord', '>= 2.0'
5
+ gem 'diff-lcs', '>= 1.1'
6
+
7
+ require 'activerecord'
8
+ require 'diff/lcs'
9
+
10
+ require 'acts_as_list'
11
+ require 'digest/sha1'
12
+ require 'cloud_rcs'
@@ -0,0 +1,9 @@
1
+ module Cloudrcs #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/cloudrcs.rb'}"
9
+ puts "Loading cloudrcs gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
data/script/txt2html ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ GEM_NAME = 'cloudrcs' # what ppl will type to install your gem
4
+ RUBYFORGE_PROJECT = 'cloudrcs'
5
+
6
+ require 'rubygems'
7
+ begin
8
+ require 'newgem'
9
+ require 'rubyforge'
10
+ rescue LoadError
11
+ puts "\n\nGenerating the website requires the newgem RubyGem"
12
+ puts "Install: gem install newgem\n\n"
13
+ exit(1)
14
+ end
15
+ require 'redcloth'
16
+ require 'syntax/convertors/html'
17
+ require 'erb'
18
+ require File.dirname(__FILE__) + "/../lib/#{GEM_NAME}/version.rb"
19
+
20
+ version = Cloudrcs::VERSION::STRING
21
+ download = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
22
+
23
+ def rubyforge_project_id
24
+ RubyForge.new.autoconfig["group_ids"][RUBYFORGE_PROJECT]
25
+ end
26
+
27
+ class Fixnum
28
+ def ordinal
29
+ # teens
30
+ return 'th' if (10..19).include?(self % 100)
31
+ # others
32
+ case self % 10
33
+ when 1: return 'st'
34
+ when 2: return 'nd'
35
+ when 3: return 'rd'
36
+ else return 'th'
37
+ end
38
+ end
39
+ end
40
+
41
+ class Time
42
+ def pretty
43
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
44
+ end
45
+ end
46
+
47
+ def convert_syntax(syntax, source)
48
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
49
+ end
50
+
51
+ if ARGV.length >= 1
52
+ src, template = ARGV
53
+ template ||= File.join(File.dirname(__FILE__), '/../website/template.html.erb')
54
+ else
55
+ puts("Usage: #{File.split($0).last} source.txt [template.html.erb] > output.html")
56
+ exit!
57
+ end
58
+
59
+ template = ERB.new(File.open(template).read)
60
+
61
+ title = nil
62
+ body = nil
63
+ File.open(src) do |fsrc|
64
+ title_text = fsrc.readline
65
+ body_text_template = fsrc.read
66
+ body_text = ERB.new(body_text_template).result(binding)
67
+ syntax_items = []
68
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
69
+ ident = syntax_items.length
70
+ element, syntax, source = $1, $2, $3
71
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
72
+ "syntax-temp-#{ident}"
73
+ }
74
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
75
+ body = RedCloth.new(body_text).to_html
76
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
77
+ end
78
+ stat = File.stat(src)
79
+ created = stat.ctime
80
+ modified = stat.mtime
81
+
82
+ $stdout << template.result(binding)