dm-svn 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ .DS_Store
2
+ log/*
3
+ tmp/*
4
+ TAGS
5
+ *~
6
+ .#*
7
+ schema/schema.rb
8
+ schema/*_structure.sql
9
+ schema/*.sqlite3
10
+ schema/*.sqlite
11
+ schema/*.db
12
+ *.sqlite
13
+ *.sqlite3
14
+ *.db
15
+ src/*
16
+ .hgignore
17
+ .hg/*
18
+ .svn/*
19
+ .project
20
+ .loadpath
21
+ lib/wistle/tmp/*
22
+ config/recaptcha.yml
23
+ *qt_temp*
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "dm-svn"
8
+ gem.summary = %Q{Sync content from a Subversion repository to a DataMapper model}
9
+ gem.description = %Q{dm-svn allows you to store data in a Subversion
10
+ repository, then sync that data to a DataMapper model (for example, to a
11
+ relational database. Essentially, it allows you app quicker access to the
12
+ Subversion data.}
13
+ gem.email = "jmorgan@morgancreative.net"
14
+ gem.homepage = "http://github.com/jm81/dm-svn"
15
+ gem.authors = ["Jared Morgan"]
16
+ gem.add_dependency('dm-core', '>= 0.10.0')
17
+ gem.add_dependency('dm-aggregates', '>= 0.10.0')
18
+ gem.add_dependency('dm-validations', '>= 0.10.0')
19
+ gem.add_dependency('jm81-svn-fixture', '>= 0.1.1')
20
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
25
+ end
26
+
27
+ require 'spec/rake/spectask'
28
+ Spec::Rake::SpecTask.new(:spec) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.spec_files = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
34
+ spec.libs << 'lib' << 'spec'
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "dm-svn #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
56
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
data/dm-svn.gemspec ADDED
@@ -0,0 +1,85 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{dm-svn}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jared Morgan"]
12
+ s.date = %q{2009-10-11}
13
+ s.description = %q{dm-svn allows you to store data in a Subversion
14
+ repository, then sync that data to a DataMapper model (for example, to a
15
+ relational database. Essentially, it allows you app quicker access to the
16
+ Subversion data.}
17
+ s.email = %q{jmorgan@morgancreative.net}
18
+ s.files = [
19
+ ".gitignore",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "dm-svn.gemspec",
23
+ "lib/dm-svn.rb",
24
+ "lib/dm-svn/config.rb",
25
+ "lib/dm-svn/model.rb",
26
+ "lib/dm-svn/svn.rb",
27
+ "lib/dm-svn/svn/categorized.rb",
28
+ "lib/dm-svn/svn/changeset.rb",
29
+ "lib/dm-svn/svn/node.rb",
30
+ "lib/dm-svn/svn/sync.rb",
31
+ "spec/dm-svn/config_spec.rb",
32
+ "spec/dm-svn/database.yml",
33
+ "spec/dm-svn/fixtures/articles_comments.rb",
34
+ "spec/dm-svn/mock_models.rb",
35
+ "spec/dm-svn/model_spec.rb",
36
+ "spec/dm-svn/spec_helper.rb",
37
+ "spec/dm-svn/svn/categorized_spec.rb",
38
+ "spec/dm-svn/svn/changeset_spec.rb",
39
+ "spec/dm-svn/svn/node_spec.rb",
40
+ "spec/dm-svn/svn/sync_spec.rb",
41
+ "spec/dm-svn/svn_spec.rb",
42
+ "spec/spec.opts",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/jm81/dm-svn}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = %q{1.3.5}
49
+ s.summary = %q{Sync content from a Subversion repository to a DataMapper model}
50
+ s.test_files = [
51
+ "spec/dm-svn/config_spec.rb",
52
+ "spec/dm-svn/fixtures/articles_comments.rb",
53
+ "spec/dm-svn/mock_models.rb",
54
+ "spec/dm-svn/model_spec.rb",
55
+ "spec/dm-svn/spec_helper.rb",
56
+ "spec/dm-svn/svn/categorized_spec.rb",
57
+ "spec/dm-svn/svn/changeset_spec.rb",
58
+ "spec/dm-svn/svn/node_spec.rb",
59
+ "spec/dm-svn/svn/sync_spec.rb",
60
+ "spec/dm-svn/svn_spec.rb",
61
+ "spec/spec_helper.rb"
62
+ ]
63
+
64
+ if s.respond_to? :specification_version then
65
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
66
+ s.specification_version = 3
67
+
68
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
69
+ s.add_runtime_dependency(%q<dm-core>, [">= 0.10.0"])
70
+ s.add_runtime_dependency(%q<dm-aggregates>, [">= 0.10.0"])
71
+ s.add_runtime_dependency(%q<dm-validations>, [">= 0.10.0"])
72
+ s.add_runtime_dependency(%q<jm81-svn-fixture>, [">= 0.1.1"])
73
+ else
74
+ s.add_dependency(%q<dm-core>, [">= 0.10.0"])
75
+ s.add_dependency(%q<dm-aggregates>, [">= 0.10.0"])
76
+ s.add_dependency(%q<dm-validations>, [">= 0.10.0"])
77
+ s.add_dependency(%q<jm81-svn-fixture>, [">= 0.1.1"])
78
+ end
79
+ else
80
+ s.add_dependency(%q<dm-core>, [">= 0.10.0"])
81
+ s.add_dependency(%q<dm-aggregates>, [">= 0.10.0"])
82
+ s.add_dependency(%q<dm-validations>, [">= 0.10.0"])
83
+ s.add_dependency(%q<jm81-svn-fixture>, [">= 0.1.1"])
84
+ end
85
+ end
data/lib/dm-svn.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'dm-core'
3
+ require 'dm-aggregates' # Only needed by specs, but this seems the easiest place to require.
4
+ require 'dm-validations'
5
+ require 'svn/client'
6
+
7
+ module DmSvn
8
+ VERSION = '0.2.0'
9
+ end
10
+
11
+ require 'dm-svn/config'
12
+ require 'dm-svn/svn'
13
+ require 'dm-svn/model'
@@ -0,0 +1,38 @@
1
+ module DmSvn
2
+ class Config
3
+ OPTS = [:uri, :username, :password,
4
+ :body_property, :property_prefix, :extension]
5
+
6
+ attr_accessor *OPTS
7
+ attr_accessor :path_from_root # Used by Sync.
8
+
9
+ def initialize
10
+ # Set defaults
11
+ @body_property = 'body'
12
+ @property_prefix = 'ws:'
13
+ @extension = 'txt'
14
+
15
+ # Try to set variables from database.yml.
16
+ # The location of database.yml should, I suppose, be configurable.
17
+ # Oh, well.
18
+ if Object.const_defined?("RAILS_ROOT")
19
+ f = "#{RAILS_ROOT}/config/database.yml"
20
+ env = Kernel.const_defined?("RAILS_ENV") ? RAILS_ENV : "development"
21
+ elsif Object.const_defined?("Merb")
22
+ f = "#{Merb.root}/config/database.yml"
23
+ env = Merb.env.to_sym || :development
24
+ end
25
+
26
+ if f
27
+ config = YAML.load(IO.read(f))[env]
28
+ OPTS.each do |field|
29
+ config_field = config["svn_#{field}"] || config["svn_#{field}".to_sym]
30
+ if config_field
31
+ instance_variable_set("@#{field}", config_field)
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ module DmSvn
2
+ class Model
3
+ include DataMapper::Resource
4
+
5
+ property :id, Serial
6
+ property :name, String
7
+ property :revision, Integer
8
+
9
+ # DmSvn::Config object
10
+ attr_accessor :config
11
+ end
12
+ end
data/lib/dm-svn/svn.rb ADDED
@@ -0,0 +1,144 @@
1
+ module DmSvn
2
+ module Svn
3
+
4
+ class << self
5
+ def included(klass) # Set a few 'magic' properties
6
+ klass.extend(ClassMethods)
7
+
8
+ # svn_name could be just a name, or a full path, always excluding
9
+ # extension. If directories are stored in a model (not yet supported),
10
+ # it contains the full path (from the config.uri).
11
+ klass.property :svn_name, DataMapper::Types::Text, :lazy => false
12
+ klass.property :svn_created_at, DateTime
13
+ klass.property :svn_updated_at, DateTime
14
+ klass.property :svn_created_rev, String
15
+ klass.property :svn_updated_rev, String
16
+ klass.property :svn_created_by, String
17
+ klass.property :svn_updated_by, String
18
+
19
+ # On create, set svn_created_* attrs based on svn_updated_* attrs
20
+ klass.before :create do
21
+ attribute_set(:svn_created_at, svn_updated_at)
22
+ attribute_set(:svn_created_rev, svn_updated_rev)
23
+ attribute_set(:svn_created_by, svn_updated_by)
24
+ end
25
+ end
26
+ end
27
+
28
+ # +name+ could be reasonably used for another property, but may normally
29
+ # be assumed to be the 'svn_name'.
30
+ def name
31
+ @svn_name
32
+ end
33
+
34
+ # The path from the svn root for the model. For the moment, just an alias
35
+ # of +svn_name+.
36
+ def path
37
+ @svn_name
38
+ end
39
+
40
+ # Set the path. This may be responsible for moving the record to a different
41
+ # parent, etc.
42
+ def path=(value)
43
+ attribute_set(:svn_name, value)
44
+ end
45
+
46
+ # Move to a different path and save
47
+ def move_to(new_path)
48
+ self.path = new_path
49
+ self.save
50
+ end
51
+
52
+ # Update properties (body and other properties) from a DmSvn::Svn::Node
53
+ # or similar (expects #body as a String and #properties as a Hash).
54
+ # This method calls #save.
55
+ def update_from_svn(node)
56
+ attribute_set(self.class.config.body_property, node.body) if node.body
57
+ self.path = node.short_path
58
+
59
+ node.properties.each do | attr, value |
60
+ if self.respond_to?("#{attr}=")
61
+ self.__send__("#{attr}=", value)
62
+ end
63
+ end
64
+
65
+ if !valid?
66
+ puts "Invalid #{node.short_path} at revision #{node.revision}"
67
+ puts " - " + errors.full_messages.join(".\n - ")
68
+ end
69
+
70
+ save
71
+ end
72
+
73
+ module ClassMethods
74
+ def config
75
+ @config ||= Config.new
76
+ end
77
+
78
+ # Override belongs_to to add +:svn+ option. If :svn => true is
79
+ # included in the options, SvnSync will also sync the +belongs_to+ model.
80
+ # For example, <code>belongs_to :category, :svn => true</code>, means
81
+ # that the Category model will also be updated by SvnSync, and be based on
82
+ # folders. Folders can have svn properties set, and/or a meta.yml file
83
+ # with properties.
84
+ # def belongs_to(name, options={})
85
+ #
86
+ # end
87
+
88
+ # Override DataMapper's +property+ class method to accept as an option
89
+ # +body_property+. Setting this option tells DmSvn::Svn that this field
90
+ # will store the contents of the repository file.
91
+ def property(name, type, options = {})
92
+ if options.delete(:body_property)
93
+ config.body_property = name.to_s
94
+ end
95
+
96
+ super(name, type, options)
97
+ end
98
+
99
+ # DataMapper uses +repository+, so prepend "svn_"
100
+ def svn_repository
101
+ return @svn_repository if @svn_repository
102
+
103
+ @svn_repository = DmSvn::Model.first(:name => self.name)
104
+ @svn_repository ||= DmSvn::Model.create(:name => self.name, :revision => 0)
105
+ @svn_repository.config = config
106
+ @svn_repository
107
+ end
108
+
109
+ def sync
110
+ DmSvn::Svn::Sync.new(svn_repository).run
111
+ end
112
+
113
+ # Override normal get behavior to try to get based on path if the argument
114
+ # is a String. Extra args are ignored by default.
115
+ def get(path_or_id, *args)
116
+ if path_or_id.is_a?(String)
117
+ get_by_path(path_or_id)
118
+ else
119
+ super
120
+ end
121
+ end
122
+
123
+ # Try to get by path. If not, create a new record and so set its path.
124
+ def get_or_create(path)
125
+ i = get_by_path(path)
126
+ return i if i
127
+
128
+ i = create
129
+ i.path = path
130
+ i.save
131
+ return i
132
+ end
133
+
134
+ def get_by_path(path)
135
+ first(:svn_name => path)
136
+ end
137
+ end
138
+
139
+ end
140
+ end
141
+
142
+ %w{sync changeset node categorized}.each do |f|
143
+ require "dm-svn/svn/#{f}"
144
+ end
@@ -0,0 +1,113 @@
1
+ module DmSvn
2
+ module Svn
3
+
4
+ module ClassMethods
5
+
6
+ # Override belongs_to to add a :dm-svn option if :dm-svn => true, include
7
+ # Categorized and set up @svn_category and @svn_category_model instance
8
+ # methods.
9
+ def belongs_to(what, options = {})
10
+ svn = options.delete(:svn)
11
+ if svn
12
+ @svn_category = what
13
+ @svn_category_model = options[:class_name] || what.to_s.camel_case
14
+ include(DmSvn::Svn::Categorized)
15
+ end
16
+
17
+ super
18
+ end
19
+
20
+ # Method name for accessing the parent instance.
21
+ def svn_category
22
+ @svn_category
23
+ end
24
+
25
+ # Name of the parent model class (as a String)
26
+ def svn_category_model
27
+ @svn_category_model
28
+ end
29
+
30
+ end
31
+
32
+ # This module is including when belongs_to is called with :dm-svn => true.
33
+ # It overrides #path and #path= to take into account categories (folders in
34
+ # the Subversion repository). It also overrides .get to accept get_parent
35
+ # argument.
36
+ module Categorized
37
+ class << self
38
+ def included(klass)
39
+ klass.extend(ClassMethods)
40
+ end
41
+ end
42
+
43
+ # The path from the svn root for the model. Includes any folders.
44
+ def path
45
+ cat = self.send(self.class.svn_category)
46
+
47
+ if cat && !cat.path.blank?
48
+ return cat.path + "/" + @svn_name
49
+ end
50
+
51
+ return @svn_name
52
+ end
53
+
54
+ # Set the path. This is responsible for moving the record to a different
55
+ # parent, etc.
56
+ def path=(value)
57
+ value = value[1..-1] while value[0..0] == "/"
58
+ ary = value.split("/")
59
+ immediate = ary.pop
60
+ parent = ary.join("/")
61
+
62
+ if parent.blank?
63
+ self.send("#{self.class.svn_category}=", nil)
64
+ else
65
+ category_model = Object.const_get(self.class.svn_category_model)
66
+ category = category_model.get_or_create(parent)
67
+ self.send("#{self.class.svn_category}=", category)
68
+ end
69
+
70
+ attribute_set(:svn_name, immediate)
71
+ end
72
+
73
+ module ClassMethods
74
+
75
+ # Get by path, which gets parent (possibly recursively) first.
76
+ def get_by_path(value)
77
+ value = value[1..-1] while value[0..0] == "/"
78
+ ary = value.split("/")
79
+ immediate = ary.pop
80
+ parent = ary.join("/")
81
+
82
+ if parent.blank?
83
+ first(:svn_name => immediate, "#{self.svn_category}_id".to_sym => nil)
84
+ else
85
+ category_model = Object.const_get(self.svn_category_model)
86
+ category = category_model.get_by_path(parent)
87
+ return nil if category.nil?
88
+ first(:svn_name => immediate, "#{self.svn_category}_id".to_sym => category.id)
89
+ end
90
+ end
91
+
92
+ # Add get_parent argument. If true and a path is not found for the
93
+ # model, try to find path in parent model (if any).
94
+ def get(path_or_id, get_parent = false)
95
+ if path_or_id.is_a?(String)
96
+ i = get_by_path(path_or_id)
97
+ if i || !get_parent
98
+ return i
99
+ else # if get_parent
100
+ category_model = Object.const_get(@svn_category_model)
101
+ return nil if category_model == self.class
102
+ category_model.get_by_path(path_or_id)
103
+ end
104
+ else
105
+ super(path_or_id)
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+ end
113
+ end