subtrigger 0.2.7 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -19,3 +19,5 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
+ .yardoc
23
+ doc
data/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # Subtrigger
2
+
3
+ A simple DSL for defining callbacks to be fired as Subversion hooks with
4
+ built-in support for inspecting the repository and sending out e-mails.
5
+
6
+ ## Example Usage
7
+
8
+ This library is intended for use as a Subversion post-commit hook. It allows
9
+ you to define callbacks that fire when certain conditions on a revision are
10
+ met. Simply require Subtrigger and define your rules:
11
+
12
+ require 'subtrigger'
13
+
14
+ on /deploy to (\w+)/ do |revision, matches|
15
+ puts "Should deploy to #{matches[:message].first}"
16
+ end
17
+
18
+ Save this as a file in your Subversion repository, like
19
+ `/path/to/repo/hooks/deploy.rb`. Then in your Subversion commit hook
20
+ file (`/path/to/repo/hooks/post-commit`) simply call the file using
21
+ Ruby:
22
+
23
+ /usr/bin/ruby -rubygems /path/to/repo/hooks/deploy.rb "$1" "$2"
24
+
25
+ You could define triggers to…
26
+
27
+ * Send out confirmation e-mails to specific developers
28
+ * Auto-update a working copy on a production server
29
+ * Create a back-up archive
30
+ * Whatever else you can do with Ruby…
31
+
32
+ ## Detailed usage
33
+
34
+ ### Matchers
35
+
36
+ The default usage in the example above uses a regular expression which by
37
+ default will be matched against the log message of the revision that
38
+ triggers the hook. But you can test both other attributes and with other
39
+ objects (basically anything that responds to `#===`).
40
+
41
+ # Test on author name
42
+ # You can use `:author`, `:message`, `:date`,
43
+ # `:number`
44
+ on :author => /john|graham|michael|terry/ do
45
+ puts 'Always look on the bright side of life!'
46
+ end
47
+
48
+ # Test using a matcher object
49
+ class EvenNumberMatcher
50
+ def ===(revision)
51
+ revision.number % 2 == 0
52
+ end
53
+ end
54
+ on :number => EvenNumberMatcher.new do
55
+ puts 'The revision number is an even number'
56
+ end
57
+
58
+ ### Sending e-mails
59
+
60
+ Subtrigger uses Pony to enable the sending of e-mails straigt from your
61
+ triggers. This means you can send an e-mail when a branch is created, just
62
+ to name an example.
63
+
64
+ on /confirm via email/ do
65
+ mail :to => 'me@example.com',
66
+ :from => 'svn@example.com',
67
+ :subject => 'E-mail confirmation of commit',
68
+ :body => 'Your commit has been saved.'
69
+ end
70
+
71
+ ### Inline templates
72
+
73
+ To remove long strings from your templates you can define templates right
74
+ in your rules file.
75
+
76
+ on /confirm via email/ do |r|
77
+ mail :to => 'me@example.com',
78
+ :from => 'svn@example.com',
79
+ :subject => 'E-mail confirmation of commit',
80
+ :body => template('E-mail confirmation', r.number)
81
+ end
82
+ __END__
83
+ @@ E-mail confirmation
84
+ Your commit (%s) has been saved
85
+ @@ Other template
86
+ ...
87
+
88
+ This will result in an e-mail like `Your commit (5299) has been
89
+ saved`.
90
+
91
+ ### Running the triggers
92
+
93
+ Note that all triggers are run when your rules file ends (using the global
94
+ `at_exit` callback). There's no need to explicitly start this process
95
+ yourself.
96
+
97
+ ## Warnings
98
+
99
+ Note that subversion calls its hooks in an empty environment, so there's
100
+ no `$PATH` or anything. Always use full absolute paths. Also, hooks are
101
+ notoriously hard to debug, so make sure to write some debugging information
102
+ somewhere so you know what is going on.
103
+
104
+ ## Changes
105
+
106
+ Note that Subtrigger is still in early stages of development.
107
+ Until it hits 1.0 there are bound to be major changes.
108
+
109
+ ### 0.3.0
110
+
111
+ * Complete rewrite of the system
112
+ * Improved documentation
113
+ * Improved test coverage
114
+ * Added matching on more revision attributes
115
+ * Added custom matcher objects
116
+ * Use Pony for e-mail
117
+ * Cleaner rules files
118
+
119
+ ## Credits
120
+
121
+ * **Author**: Arjan van der Gaag
122
+ * **E-mail**: arjan@arjanvandergaag.nl
123
+ * **URL**: http://arjanvandergaag.nl
124
+ * **Source**: http://github.com/avdgaag/subtrigger
125
+
126
+ ## Note on Patches/Pull Requests
127
+
128
+ * Fork the project.
129
+ * Make your feature addition or bug fix.
130
+ * Add tests for it. This is important so I don't break it in a
131
+ future version unintentionally.
132
+ * Commit, do not mess with rakefile, version, or history.
133
+ (if you want to have your own version, that is fine but bump version in a
134
+ commit by itself I can ignore when I pull)
135
+ * Send me a pull request. Bonus points for topic branches.
136
+
137
+ ## License
138
+
139
+ Copyright (c) 2010 Arjan van der Gaag
140
+
141
+ Permission is hereby granted, free of charge, to any person obtaining
142
+ a copy of this software and associated documentation files (the
143
+ "Software"), to deal in the Software without restriction, including
144
+ without limitation the rights to use, copy, modify, merge, publish,
145
+ distribute, sublicense, and/or sell copies of the Software, and to
146
+ permit persons to whom the Software is furnished to do so, subject to
147
+ the following conditions:
148
+
149
+ The above copyright notice and this permission notice shall be
150
+ included in all copies or substantial portions of the Software.
151
+
152
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
153
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
154
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
155
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
156
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
157
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
158
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -12,6 +12,7 @@ begin
12
12
  gem.authors = ["Arjan van der Gaag"]
13
13
  gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
14
  gem.add_development_dependency "mocha", ">= 0"
15
+ gem.add_dependency 'pony'
15
16
  end
16
17
  Jeweler::GemcutterTasks.new
17
18
  rescue LoadError
@@ -42,12 +43,5 @@ task :test => :check_dependencies
42
43
 
43
44
  task :default => :test
44
45
 
45
- require 'rake/rdoctask'
46
- Rake::RDocTask.new do |rdoc|
47
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
-
49
- rdoc.rdoc_dir = 'rdoc'
50
- rdoc.title = "subtrigger #{version}"
51
- rdoc.rdoc_files.include('README*')
52
- rdoc.rdoc_files.include('lib/**/*.rb')
53
- end
46
+ require 'yard'
47
+ YARD::Rake::YardocTask.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.7
1
+ 0.3.0
data/lib/subtrigger.rb CHANGED
@@ -1,133 +1,98 @@
1
- $:.unshift File.dirname(__FILE__)
2
- # = Subtrigger
3
- #
4
- # Subtrigger is a tiny tool for firing callback methods based on triggers in
5
- # subversion log messages.
6
- #
7
- # == Example
8
- #
9
- # When somebody makes a commit:
10
- #
11
- # r5410 "Added a sitemap to the website [deploy]"
12
- # A /www.website.tld/trunk/sitemap.xml
13
- #
14
- # ...then you can trigger the deployment of that project to a staging server
15
- # using a +Trigger+:
16
- #
17
- # Subtrigger.on(/\[deploy\]/) do |matches, repo|
18
- # # do some smart stuff here
19
- # end
20
- #
21
- # Your trigger has access to the captured groups in its regular expression
22
- # matcher, and to all the <tt>svnlook</tt> information from the repository
23
- # at the revision that fired the hook. This gives you access to changed paths,
24
- # its author, date, etc.
25
- #
26
- # == E-mail notifications
27
- #
28
- # Subtrigger allows you to send notification e-mails to developers:
29
- #
30
- # # in your trigger:
31
- # Subtrigger::Email.new(:to => "#{repo.author}@company.tld",
32
- # :from => 'svn@company.tld',
33
- # :subject => 'Trigger notification',
34
- # :body => "Dear #{repo.author}, ...")
35
- #
36
- # == Usage
37
- #
38
- # This library is intended to be used as a Subversion post-commit hook.
39
- # The best way to use it to create a post-commit hook file that requires this
40
- # library, sets up one or more triggers and than fires the processing.
41
- #
42
- # Here's an example:
43
- #
44
- # #!/usr/local/bin/ruby
45
- # require 'rubygems'
46
- # require 'subtrigger'
47
- # Subtrigger.configure { |c|
48
- # c.svn = '/usr/bin/svn'
49
- # }.on(/foo/) { |matches, repo|
50
- # puts "#{repo.author} comitted foo!"
51
- # }.on(/bar/) { |matches, repo|
52
- # puts "#{repo.author} comitted bar!"
53
- # }.run(*ARGV)
54
- #
55
- # === Command Line Usage
56
- #
57
- # There is a command-line tool available for running Subtrigger. It simply
58
- # requires the Subtrigger library and calls +run+. Most of the time, you'll
59
- # want to write your own script and <tt>require 'subtrigger'</tt> yourself.
60
- #
61
- # You can run <tt>subtrigger -v</tt> to see the currently installed version
62
- # of Subtrigger.
63
- #
64
- # === Configuration
65
- #
66
- # Since subversion usually calls its hooks in an empty environment (even
67
- # without a $PATH) you might need to make some settings manually:
68
- #
69
- # Subtrigger.svn = '/path/to/svn'
70
- # Subtrigger.sendmail = '/path/to/sendmail'
71
- # Subtrigger.svn_args = ''
72
- #
73
- # You can alternatively use a block:
74
- #
75
- # Subtrigger.configure do |c|
76
- # c.svn = '/path/to/svn'
77
- # end
78
- #
79
- # This will return Subtrigger itself, so you can chain further commands.
80
- #
81
- # The <tt>svn_args</tt> setting is a string appended to every +svn+ command.
82
- # This allows you to, for example, set a custom username and password. You
83
- # might also want to apply the <tt>--non-interactive</tt> argument. For
84
- # example:
85
- #
86
- # Subtrigger.svn_args = '--username my_name --password secret --non-interactive'
87
- #
88
- # Make sure your gems are installed and the correct permissions are set. Note
89
- # that Subversion hooks generate no output, so run your hooks manually for
90
- # testing purposes.
1
+ # Libraries
2
+ begin
3
+ require 'pony'
4
+ rescue LoadError
5
+ puts 'WARNING: Subtrigger requires Pony to send e-mails.'
6
+ end
7
+ require 'time'
8
+
9
+ # Load internals
10
+ require 'subtrigger/dsl'
11
+ require 'subtrigger/revision'
12
+ require 'subtrigger/rule'
13
+ require 'subtrigger/template'
14
+ require 'subtrigger/path'
15
+
91
16
  module Subtrigger
92
- attr_accessor :svn, :sendmail, :svn_args
93
17
 
94
- def reset
95
- self.svn = nil
96
- self.sendmail = nil
97
- self.svn_args = nil
18
+ # Standard exception for all Subtrigger exceptions
19
+ Exception = Class.new(Exception)
20
+
21
+ # Return the current version number as defined in ./VERSION
22
+ # @return [String] version number (e.g. '0.3.1')
23
+ def version
24
+ File.read(File.join(File.dirname(__FILE__), '..', 'VERSION'))
98
25
  end
99
26
 
100
- def configure(&block)
101
- yield self and return self
27
+ # Run the current file of rules.
28
+ #
29
+ # This method is called after all the rules have been defined. It takes
30
+ # the command line arguments that come from subversion.
31
+ #
32
+ # @param [String] repository_path is the path to the repository to query
33
+ # @param [String] revision_number is the revision number that triggered the
34
+ # hook.
35
+ # @return nil
36
+ def run(repository_path, revision_number)
37
+ Template.parse(DATA.read)
38
+ rev = Revision.new(
39
+ revision_number,
40
+ svnlook('info', repository_path),
41
+ svnlook('dirs-changed', repository_path)
42
+ )
43
+ Rule.matching(rev).each { |r| r.run(rev) }
102
44
  end
103
45
 
104
- # Output the version number for this gem by reading /VERSION
105
- def version
106
- File.read(File.join(File.dirname(__FILE__), *%w{.. VERSION}))
46
+ # Make a system call to <tt>svn</tt> with the given arguments. The
47
+ # executable that used is the first found by {Path#to}.
48
+ #
49
+ # @example Using multiple arguments
50
+ # svn 'update', 'foo', '--ignore-externals'
51
+ # # => '/usr/bin/svn update foo --ignore-externals'
52
+ # @return [String] output from the command
53
+ def svn(*args)
54
+ `svn #{[*args].join(' ')}`
107
55
  end
108
56
 
109
- # This is the main spark in the program.
110
- # It runs all available triggers on the repository object created with the
111
- # two command line arguments: the path to the repository and its revision
112
- # number.
57
+ # Make a system call to <tt>svnlook</tt> with the given arguments. The
58
+ # executable # that used is the first found in
59
+ # <tt>POSSIBLE_PATHS</tt>.
113
60
  #
114
- # If an exception occurs, the program will quit with its error message.
115
- def run(*args)
116
- Trigger.run(Repository.new(*args))
117
- rescue Exception => e
118
- puts "Error: #{e}" and exit(1)
61
+ # @example Using multiple arguments
62
+ # svnlook 'youngest', '/path/to/repo'
63
+ # # => '/usr/bin/svnlook youngest /path/to/repo
64
+ # @return [String] output from the command
65
+ def svnlook(*args)
66
+ `svnlook #{[*args].join(' ')}`
67
+ end
68
+
69
+ # @see Path#initialize
70
+ # @return [Path] The 'global' Path object
71
+ def path
72
+ @path ||= Path.new
119
73
  end
120
74
 
121
- # Define a new +Trigger+ object -- shortcut method to
122
- # <tt>Trigger#define</tt>. To enable method chaining this method returns
123
- # itself.
124
- def on(pattern, &block)
125
- Trigger.define(pattern, &block)
126
- self
75
+ private
76
+
77
+ # Override Kernel#` to prefix our commands with the path to subversion
78
+ # @param [String] arg is the command to run
79
+ # @return [String] the command's output
80
+ # @todo maybe build a check to only prefix the path when actually calling
81
+ # svn or svnlook or something.
82
+ def `(arg)
83
+ super(path_to('svn') + '/' + arg)
127
84
  end
85
+
128
86
  extend self
129
87
  end
130
88
 
131
- require 'subtrigger/email'
132
- require 'subtrigger/trigger'
133
- require 'subtrigger/repository'
89
+ # Make the DSL available in the top-level domain
90
+ include Subtrigger::Dsl
91
+
92
+ # At the end of the rules file perfrom the actual {Subtrigger#run}
93
+ at_exit do
94
+ unless $prevent_subtrigger_run
95
+ raise ArgumentError unless ARGV.size == 2
96
+ Subtrigger.run(*ARGV)
97
+ end
98
+ end
@@ -0,0 +1,42 @@
1
+ module Subtrigger
2
+ # The Dsl module provides some nice-looking methods that can be used to
3
+ # perform the most important Subtrigger operations.
4
+ #
5
+ # This is intended to be included in the top-level namespace, so a script
6
+ # can call these functions directly.
7
+ #
8
+ # @author Arjan van der Gaag
9
+ # @since 0.3.0
10
+ module Dsl
11
+ # Define a new trigger on incoming Revision.
12
+ #
13
+ # @see Rule#initialize
14
+ # @return [nil]
15
+ def on(*args, &block)
16
+ Rule.new(*args, &block)
17
+ end
18
+
19
+ # Create and deliver a new Mail object using Pony. See its documentation
20
+ # for more information.
21
+ # @return [nil]
22
+ def mail(*args)
23
+ ::Pony.mail(*args)
24
+ end
25
+
26
+ # Call Subversion commands using the configured svn executable.
27
+ #
28
+ # @see Subtrigger#svn
29
+ # @return [String] the command's output
30
+ def svn(*args)
31
+ Subtrigger.svn(*args)
32
+ end
33
+
34
+ # Get a template defined inline and format it using the given arguments.
35
+ #
36
+ # @see Template#format
37
+ # @return [String] the formatted template
38
+ def template(name, *format_arguments)
39
+ Template.find(name).format [*format_arguments]
40
+ end
41
+ end
42
+ end