subtrigger 0.2.7 → 0.3.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 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