dm-svn 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/.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