ursm-ditz 0.4

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/Changelog ADDED
@@ -0,0 +1,35 @@
1
+ == 0.4 / 2008-07-27
2
+ * bugfix: HOME environment variable now correctly detected on windows
3
+ * hooks loaded from both home directory and project directory
4
+ * added bash shell completion
5
+ * plugin architecture for tighter SCM integration, etc
6
+ * 'ditz grep' should also grep against comments, log messages, etc
7
+ * added man page
8
+ * removed ditz-convert-from-monolith
9
+ * lots of HTML output tweaking
10
+ == 0.3 / 2008-06-04
11
+ * readline support for all text entry
12
+ * hook system. Use ditz -l to see possible hooks.
13
+ * new commands: archive, shortlog, set-component
14
+ * improved commands: log, assign, add-release
15
+ * new issue type: 'tasks'
16
+ * 'ditz' by itself shows the todo list
17
+ * zsh tab completion for subcommands
18
+ * local config can now specify bugs directory location
19
+ * issue name interpolation now on all issue fields
20
+ * bugfix: various HTML generation bugs
21
+ * bugfix: ditz now works from project subdirectories
22
+ * bugfix: removed UNIX-specific environment variable assumptions
23
+ == 0.2 / 2008-04-11
24
+ * bugfix: store each issue in a separate file to avoid false conflicts
25
+ * added per-command help
26
+ * added 'log' command for recent activity
27
+ * added better exception handling---turn into pretty error messages
28
+ * added text-area commands like /edit, /reset, etc
29
+ * all times now stored in UTC
30
+ == 0.1.2 / 2008-04-04
31
+ * bugfix: add_reference very broken
32
+ == 0.1.1 / 2008-04-04
33
+ * bugfix: bugfix/feature question always returns feature
34
+ == 0.1 / 2008-04-02
35
+ * Initial release!
data/README.txt ADDED
@@ -0,0 +1,127 @@
1
+ == ditz
2
+
3
+ by William Morgan <wmorgan-ditz at the masanjin dot nets>
4
+
5
+ http://ditz.rubyforge.org
6
+
7
+ == DESCRIPTION
8
+
9
+ Ditz is a simple, light-weight distributed issue tracker designed to work with
10
+ distributed version control systems like git, darcs, Mercurial, and Bazaar. It
11
+ can also be used with centralized systems like SVN.
12
+
13
+ Ditz maintains an issue database directory on disk, with files written in a
14
+ line-based and human-editable format. This directory can be kept under version
15
+ control, alongside project code.
16
+
17
+ There are several different ways to use ditz:
18
+
19
+ 1. Treat issue change the same as code change: include it as part of commits,
20
+ and merge it with changes from other developers, resolving conflicts in the
21
+ usual manner.
22
+ 2. Keep the issue database in the repository but in a separate branch. Issue
23
+ changes can be managed by your VCS, but is not tied directly to code
24
+ commits.
25
+ 3. Keep the issue database separate and not under VCS at all.
26
+
27
+ Ditz provides a simple, console-based interface for creating and updating the
28
+ issue database file, and some rudimentary static HTML generation capabilities
29
+ for producing world-readable status pages (for a demo, see the ditz ditz page).
30
+ It currently offers no central public method of bug submission.
31
+
32
+ == SYNOPSIS
33
+
34
+ # set up project. creates the bugs.yaml file.
35
+ 1. ditz init
36
+ 2. ditz add-release
37
+
38
+ # add an issue
39
+ 3. ditz add
40
+
41
+ # where am i?
42
+ 4. ditz status
43
+ 5. ditz todo (or simply "ditz")
44
+
45
+ # do work
46
+ 6. write code
47
+ 7. ditz close <issue-id>
48
+ 8. commit
49
+ 9. goto 3
50
+
51
+ # finished!
52
+ 10. ditz release <release-name>
53
+
54
+ == THE DITZ DATA MODEL
55
+
56
+ Ditz includes the bare minimum set of features necessary for open-source
57
+ development. Features like time spent, priority, assignment of tasks to
58
+ developers, due dates, etc. are purposely excluded.
59
+
60
+ A ditz project consists of issues, releases and components.
61
+
62
+ Issues:
63
+ Issues are the fundamental currency of issue tracking. A ditz issue is either
64
+ a feature or a bug, but this distinction doesn't affect anything other than
65
+ how they're displayed.
66
+
67
+ Each issue belongs to exactly one component, and is part of zero or one
68
+ releases.
69
+
70
+ Each issues has an exportable id, in the form of 40 random hex characters.
71
+ This id is "guaranteed" to be unique across all possible issues and
72
+ developers, present and future. Issue ids are typically not exposed to the
73
+ user.
74
+
75
+ Issues also have a non-exportable name, which is short and human-readable.
76
+ All ditz commands use issue names instead of issue ids. Issue ids may change
77
+ in certain circumstances, specifically after a "ditz drop" command.
78
+
79
+ Components:
80
+ There is always one "general" component, named after the project itself. In
81
+ the simplest case, this is the only component, and the user is never bothered
82
+ with the question of which component to assign an issue to.
83
+
84
+ Components simply provide a way of organizing issues, and have no real
85
+ functionality. Issues are assigned names derived form the component they're
86
+ assigned to.
87
+
88
+ Releases:
89
+ A release is the primary grouping mechanism for issues. Status commands like
90
+ "ditz status" and "ditz todo" always group issues by release. When a release
91
+ is 100% complete, it can be marked as released, in which case the associated
92
+ issues will cease appearing in ditz status and todo messages.
93
+
94
+ == FUTURE WORK
95
+
96
+ In future releases, Ditz will have a plugin architecture to allow tighter
97
+ integration with specific SCMs and developer communication channels. (See
98
+ http://ditz.rubyforge.org/ditz/issue-0704dafe4aef96279364013aba177a0971d425cb.html)
99
+
100
+ == LEARNING MORE
101
+
102
+ * ditz help
103
+ * find $DITZ_INSTALL_DIR -type f | xargs cat
104
+
105
+ == REQUIREMENTS
106
+
107
+ * trollop >= 1.8.2
108
+
109
+ == INSTALLATION
110
+
111
+ Download tarballs from http://rubyforge.org/projects/ditz/, or command your
112
+ computer to "gem install ditz".
113
+
114
+ == LICENSE
115
+
116
+ Copyright (c) 2008 William Morgan.
117
+
118
+ This program is free software: you can redistribute it and/or modify
119
+ it under the terms of the GNU General Public License as published by
120
+ the Free Software Foundation, either version 3 of the License, or
121
+ (at your option) any later version.
122
+
123
+ This program is distributed in the hope that it will be useful,
124
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
125
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
126
+ GNU General Public License for more details.
127
+
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+
4
+ $:.unshift "lib"
5
+ require 'ditz'
6
+
7
+ class Hoe
8
+ def extra_deps; @extra_deps.reject { |x| Array(x).first == "hoe" } end
9
+ end # thanks to "Mike H"
10
+
11
+ Hoe.new('ditz', Ditz::VERSION) do |p|
12
+ p.rubyforge_name = 'ditz'
13
+ p.author = "William Morgan"
14
+ p.summary = "A simple issue tracker designed to integrate well with distributed version control systems like git and darcs. State is saved to a YAML file kept under version control, allowing issues to be closed/added/modified as part of a commit."
15
+
16
+ p.description = p.paragraphs_of('README.txt', 4..11).join("\n\n").gsub(/== SYNOPSIS/, "Synopsis:")
17
+ p.url = "http://ditz.rubyforge.org"
18
+ p.changes = p.paragraphs_of('Changelog', 0..0).join("\n\n")
19
+ p.email = "wmorgan-ditz@masanjin.net"
20
+ end
21
+
22
+ WWW_FILES = FileList["www/*"] + %w(README.txt)
23
+
24
+ task :upload_webpage => WWW_FILES do |t|
25
+ sh "rsync -essh -cavz #{t.prerequisites * ' '} wmorgan@rubyforge.org:/var/www/gforge-projects/ditz/"
26
+ end
27
+
28
+ task :upload_report do |t|
29
+ sh "ruby -Ilib bin/ditz html ditz"
30
+ sh "rsync -essh -cavz ditz wmorgan@rubyforge.org:/var/www/gforge-projects/ditz/"
31
+ end
32
+
33
+ # vim: syntax=ruby
data/ReleaseNotes ADDED
@@ -0,0 +1,50 @@
1
+ 0.4
2
+ ---
3
+ - Command-line completion scripts are now included for bash and zsh. To
4
+ activate, source the relevant file in $GEMDIR/ditz-0.4/contrib/completion/.
5
+
6
+ - Hooks can now be set on a per-project basis. Make a .ditz/hooks directory in
7
+ your project root and place them there. These will be loaded after any
8
+ hooks in ~/.ditz/hooks, so they can override or simply supplement.
9
+
10
+ - The plugin system is done. There's currently one plugin, for git integration.
11
+ To enable it, add the line "- git" in a .ditz-plugins file in your project
12
+ root. The git plugin currently has the following features:
13
+
14
+ - Issues can have a git branch assigned to them with "ditz set-branch".
15
+ - Git commit messages can have a Ditz-issue: header auto-filled if you
16
+ commit with "ditz commit <issue>" (i.e. instead of git commit).
17
+ - In both HTML and screen output, commits from the assigned branch, and
18
+ commits with the corresponding Ditz-issue: header in the log message,
19
+ will be listed for each issue.
20
+
21
+ Note that the plugin system is independent of the hook system. In order
22
+ to auto-add ditz files to the git index upon modification, you must set
23
+ up hooks. Example hooks for git are at:
24
+ http://hackety.org/2008/06/26/gitHooksForDitz.html
25
+
26
+ Also note that as soon as a feature branch is merged back into master, ditz
27
+ loses the ability to distinguish its commits. So the Ditz-issue: approach
28
+ is probably better if you want a long-term record.
29
+
30
+ 0.3
31
+ ---
32
+ Ditz now works from project subdirectories, and you can have a .ditz-config in
33
+ the project root for project-specific configuration. (This is not merged with
34
+ the global config, so this file overrides everything in ~/.ditz-config.)
35
+
36
+ You can specify an :issue_dir key in this file, which can be a relative path to
37
+ the directory containing project.yaml. So if you want to rename that directory,
38
+ or keep it somewhere else, now you can.
39
+
40
+ There's also a new hook system for plugging in your own code. Run ditz -l to
41
+ see a list of available hooks.
42
+
43
+ 0.2
44
+ ---
45
+
46
+ In ditz 0.2, we store issues per file. This avoids many unnecessary conflicts
47
+ that occur in the single-file case.
48
+
49
+ To upgrade your bugs.yaml to a bugs/ directory, you must run
50
+ ditz-convert-from-monolith.
data/bin/ditz ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/ruby1.8
2
+
3
+ ## requires are split in two for efficiency reasons: ditz should be really
4
+ ## fast when using it for completion.
5
+ $KCODE = "u"
6
+
7
+ require 'operator'
8
+ op = Ditz::Operator.new
9
+
10
+ ## a secret option for shell completion
11
+ if ARGV.include? '--commands'
12
+ puts op.class.operations.map { |name, _| name }
13
+ exit 0
14
+ end
15
+
16
+ require 'rubygems' rescue LoadError nil
17
+ require 'fileutils'
18
+ require 'pathname'
19
+ require 'trollop'; include Trollop
20
+ require "ditz"
21
+ require "vendor/yaml_waml"
22
+
23
+ PROJECT_FN = "project.yaml"
24
+ CONFIG_FN = ".ditz-config"
25
+ PLUGIN_FN = ".ditz-plugins"
26
+
27
+ config_dir = Ditz::find_dir_containing CONFIG_FN
28
+ plugin_dir = Ditz::find_dir_containing PLUGIN_FN
29
+
30
+ $opts = options do
31
+ version "ditz #{Ditz::VERSION}"
32
+ opt :issue_dir, "Issue database dir", :default => "bugs"
33
+ opt :config_file, "Configuration file", :default => File.join(config_dir || ".", CONFIG_FN)
34
+ opt :plugins_file, "Plugins file", :default => File.join(plugin_dir || ".", PLUGIN_FN)
35
+ opt :verbose, "Verbose output", :default => false
36
+ opt :no_comment, "Skip asking for a comment", :default => false
37
+ opt :list_hooks, "List all hooks and descriptions, and quit.", :short => 'l', :default => false
38
+ stop_on_unknown
39
+ end
40
+
41
+ Ditz::HookManager.register :startup, <<EOS
42
+ Executes at startup
43
+
44
+ Variables: project, config
45
+ No return value.
46
+ EOS
47
+
48
+ Ditz::HookManager.register :after_add, <<EOS
49
+ Executes before terminating if new issue files has been created.
50
+ Basically you want to instruct your SCM that these files has
51
+ been added.
52
+
53
+ Variables: project, config, issues
54
+ No return value.
55
+ EOS
56
+
57
+ Ditz::HookManager.register :after_delete, <<EOS
58
+ Executes before terminating if new issue files has been deleted.
59
+ Basically you want to instruct your SCM that these files has
60
+ been deleted.
61
+
62
+ Variables: project, config, issues
63
+ No return value.
64
+ EOS
65
+
66
+ Ditz::HookManager.register :after_update, <<EOS
67
+ Executes before terminating if new issue files has been updated.
68
+ You may want to instruct your SCM about these changes.
69
+ Note that new issues are not considered updated.
70
+
71
+ Variables: project, config, issues
72
+ No return value.
73
+ EOS
74
+
75
+ if $opts[:list_hooks]
76
+ Ditz::HookManager.print_hooks
77
+ exit 0
78
+ end
79
+
80
+ plugins = begin
81
+ Ditz::debug "loading plugins from #{$opts[:plugins_file]}"
82
+ YAML::load_file$opts[:plugins_file]
83
+ rescue SystemCallError => e
84
+ Ditz::debug "can't load plugins file: #{e.message}"
85
+ []
86
+ end
87
+
88
+ plugins.each do |p|
89
+ fn = Ditz::find_ditz_file "plugins/#{p}.rb"
90
+ Ditz::debug "loading plugin #{p.inspect} from #{fn}"
91
+ load fn
92
+ end
93
+
94
+ config = begin
95
+ Ditz::debug "loading config from #{$opts[:config_file]}"
96
+ Ditz::Config.from $opts[:config_file]
97
+ rescue SystemCallError => e
98
+ if ARGV.member? "<options>"
99
+ ## special case here. if we're asking for tab completion, and the config
100
+ ## file doesn't exist, don't do the interactive building. just make a
101
+ ## fake empty one and carry on.
102
+ Ditz::Config.new
103
+ else
104
+ puts <<EOS
105
+ I wasn't able to find a configuration file #{$opts[:config_file]}.
106
+ We'll set it up right now.
107
+ EOS
108
+ Ditz::Config.create_interactively.save! $opts[:config_file]
109
+ end
110
+ end
111
+
112
+ cmd = ARGV.shift || "todo"
113
+ issue_dir = Pathname.new(config.issue_dir || $opts[:issue_dir])
114
+
115
+ case cmd # some special commands not handled by Ditz::Operator
116
+ when "init"
117
+ die "#{issue_dir} directory already exists" if issue_dir.exist?
118
+ issue_dir.mkdir
119
+ fn = issue_dir + PROJECT_FN
120
+ project = op.init
121
+ project.save! fn
122
+ puts "Ok, #{issue_dir} directory created successfully."
123
+ exit
124
+ when "help"
125
+ op.do "help", nil, nil, ARGV
126
+ exit
127
+ end
128
+
129
+ project_root = Ditz::find_dir_containing(issue_dir + PROJECT_FN)
130
+ die "No #{issue_dir} directory---use 'ditz init' to initialize" unless project_root
131
+ project_root += issue_dir
132
+
133
+ project = begin
134
+ fn = project_root + PROJECT_FN
135
+ Ditz::debug "loading project from #{fn}"
136
+ project = Ditz::Project.from fn
137
+
138
+ fn = project_root + "issue-*.yaml"
139
+ Ditz::debug "loading issues from #{fn}"
140
+ project.issues = Dir[fn].map { |fn| Ditz::Issue.from fn }
141
+ Ditz::debug "found #{project.issues.size} issues"
142
+ project
143
+ rescue SystemCallError, Ditz::Project::Error => e
144
+ die "#{e.message} (use 'init' to initialize)"
145
+ end
146
+
147
+ project.validate!
148
+ project.issues.each { |p| p.project = project}
149
+ project.assign_issue_names!
150
+
151
+ unless op.has_operation? cmd
152
+ die "no such command: #{cmd}"
153
+ end
154
+
155
+ Ditz::HookManager.run :startup, project, config
156
+
157
+ Ditz::debug "executing command #{cmd}"
158
+ begin
159
+ op.do cmd, project, config, ARGV
160
+ rescue Ditz::Operator::Error, Ditz::Release::Error, Ditz::Project::Error, Ditz::Issue::Error => e
161
+ $stderr.puts "Error: #{e.message}"
162
+ exit(-1)
163
+ rescue Errno::EPIPE, Interrupt
164
+ puts
165
+ exit 1
166
+ end
167
+
168
+ ## save project.yaml
169
+ dirty = project.each_modelobject { |o| break true if o.changed? } || false
170
+ if dirty
171
+ fn = project_root + PROJECT_FN
172
+ Ditz::debug "project is dirty, saving #{fn}"
173
+ project.save! fn
174
+ end
175
+
176
+ ## project issues are not model fields proper, so they must be
177
+ ## saved independently.
178
+ changed_issues = project.issues.select { |i| i.changed? }
179
+ changed_issues.each do |i|
180
+ i.pathname ||= (project_root + "issue-#{i.id}.yaml")
181
+ i.project ||= project # hack: not set on new issues
182
+ Ditz::debug "issue #{i.name} is dirty, saving #{i.pathname}"
183
+ i.save! i.pathname
184
+ end
185
+
186
+ project.deleted_issues.each do |i|
187
+ fn = i.pathname
188
+ Ditz::debug "issue #{i.name} has been deleted, deleting #{fn}"
189
+ FileUtils.rm fn
190
+ end
191
+
192
+ unless project.added_issues.empty?
193
+ unless Ditz::HookManager.run :after_add, project, config, project.added_issues
194
+ puts "You may have to inform your SCM that the following files have been added:"
195
+ project.added_issues.each { |i| puts " " + i.pathname }
196
+ end
197
+ end
198
+
199
+ unless project.deleted_issues.empty?
200
+ unless Ditz::HookManager.run :after_delete, project, config, project.deleted_issues
201
+ puts "You may have to inform your SCM that the following files have been deleted:"
202
+ project.deleted_issues.each { |i| puts " " + i.pathname }
203
+ end
204
+ end
205
+
206
+ changed_not_added_issues = changed_issues - project.added_issues
207
+ unless changed_not_added_issues.empty?
208
+ Ditz::HookManager.run :after_update, project, config, changed_not_added_issues
209
+ end
210
+
211
+ config.save! $opts[:config_file] if config.changed?
212
+
213
+ # vim: syntax=ruby
@@ -0,0 +1,29 @@
1
+ #compdef ditz
2
+
3
+ ME=ditz
4
+ COMMANDS=--commands
5
+ OPTIONS='<options>'
6
+
7
+ if (($CURRENT == 2)); then
8
+ # We're completing the first word after the tool: the command.
9
+ _wanted command expl "$ME command" \
10
+ compadd -- $( "$ME" "$COMMANDS" )
11
+ else
12
+ # Find the options/files/URL/etc. for the current command by using the tool itself.
13
+ case "${words[$CURRENT]}"; in
14
+ -*)
15
+ _wanted args expl "Arguments for $ME ${words[2]}" \
16
+ compadd -- $( "$ME" "${words[2]}" "$OPTIONS" ; _files )
17
+ ;;
18
+ ht*|ft*)
19
+ _arguments '*:URL:_urls'
20
+ ;;
21
+ /*|./*|\~*|../*)
22
+ _arguments '*:file:_files'
23
+ ;;
24
+ *)
25
+ _wanted args expl "Arguments for $ME ${words[2]}" \
26
+ compadd -- $( "$ME" "${words[2]}" "$OPTIONS" )
27
+ ;;
28
+ esac
29
+ fi
@@ -0,0 +1,22 @@
1
+ # ditz bash completion
2
+ #
3
+ # author: Christian Garbs
4
+ #
5
+ # based on bzr.simple by Martin Pool
6
+
7
+ _ditz()
8
+ {
9
+ cur=${COMP_WORDS[COMP_CWORD]}
10
+ if [ $COMP_CWORD -eq 1 ]; then
11
+ COMPREPLY=( $( compgen -W "$(ditz --commands)" $cur ) )
12
+ elif [ $COMP_CWORD -eq 2 ]; then
13
+ cmd=${COMP_WORDS[1]}
14
+ COMPREPLY=( $( compgen -W "$(ditz "$cmd" '<options>' 2>/dev/null)" $cur ) )
15
+ elif [ $COMP_CWORD -eq 3 ]; then
16
+ cmd=${COMP_WORDS[1]}
17
+ parm1=${COMP_WORDS[2]}
18
+ COMPREPLY=( $( compgen -W "$(ditz "$cmd" "$parm1" '<options>' 2>/dev/null)" $cur ) )
19
+ fi
20
+ }
21
+
22
+ complete -F _ditz -o default ditz
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
+ <head>
6
+ <title>Component <%= component.name %></title>
7
+ <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
8
+ <link rel="stylesheet" href="style.css" type="text/css" />
9
+ </head>
10
+
11
+ <body>
12
+
13
+ <div><%= link_to "index", "&laquo; #{project.name} project page" %></div>
14
+
15
+ <h1><%= project.name %> component: <%= component.name %></h1>
16
+
17
+ <%= render "issue_table", :show_component => false, :show_release => true %>
18
+
19
+ <p class="footer">Generated by <a
20
+ href="http://ditz.rubyforge.org/">ditz</a>.</p>
21
+ </body>
22
+ </html>
data/lib/ditz.rb ADDED
@@ -0,0 +1,56 @@
1
+ module Ditz
2
+
3
+ VERSION = "0.4"
4
+
5
+ def debug s
6
+ puts "# #{s}" if $opts[:verbose]
7
+ end
8
+ module_function :debug
9
+
10
+ def self.has_readline?
11
+ @has_readline
12
+ end
13
+
14
+ def self.has_readline= val
15
+ @has_readline = val
16
+ end
17
+
18
+ begin
19
+ Ditz::has_readline = false
20
+ require 'readline'
21
+ Ditz::has_readline = true
22
+ rescue LoadError
23
+ # do nothing
24
+ end
25
+
26
+ def home_dir
27
+ @home ||=
28
+ ENV["HOME"] || (ENV["HOMEDRIVE"] && ENV["HOMEPATH"] ? ENV["HOMEDRIVE"] + ENV["HOMEPATH"] : nil) || begin
29
+ $stderr.puts "warning: can't determine home directory, using '.'"
30
+ "."
31
+ end
32
+ end
33
+
34
+ ## helper for recursive search
35
+ def find_dir_containing target, start=Pathname.new(".")
36
+ return start if (start + target).exist?
37
+ unless start.parent.realpath == start.realpath
38
+ find_dir_containing target, start.parent
39
+ end
40
+ end
41
+
42
+ ## my brilliant solution to the 'gem datadir' problem
43
+ def find_ditz_file fn
44
+ dir = $:.find { |p| File.exist? File.expand_path(File.join(p, fn)) }
45
+ raise "can't find #{fn} in any load path" unless dir
46
+ File.expand_path File.join(dir, fn)
47
+ end
48
+
49
+ module_function :home_dir, :find_dir_containing, :find_ditz_file
50
+ end
51
+
52
+ require 'model-objects'
53
+ require 'operator'
54
+ require 'views'
55
+ require 'hook'
56
+
data/lib/hook.rb ADDED
@@ -0,0 +1,67 @@
1
+ module Ditz
2
+ class HookManager
3
+ def initialize
4
+ @descs = {}
5
+ @blocks = {}
6
+ end
7
+
8
+ @@instance = nil
9
+ def self.method_missing m, *a, &b
10
+ @@instance ||= self.new
11
+ @@instance.send m, *a, &b
12
+ end
13
+
14
+ def register name, desc
15
+ @descs[name] = desc
16
+ @blocks[name] = []
17
+ end
18
+
19
+ def on *names, &block
20
+ names.each do |name|
21
+ raise "unregistered hook #{name.inspect}" unless @descs[name]
22
+ @blocks[name] << block
23
+ end
24
+ end
25
+
26
+ def run name, *args
27
+ raise "unregistered hook #{name.inspect}" unless @descs[name]
28
+ blocks = hooks_for name
29
+ return false if blocks.empty?
30
+ blocks.each { |block| block[*args] }
31
+ true
32
+ end
33
+
34
+ def print_hooks f=$stdout
35
+ puts <<EOS
36
+ Ditz has #{@descs.size} registered hooks:
37
+
38
+ EOS
39
+
40
+ @descs.map{ |k,v| [k.to_s,v] }.sort.each do |name, desc|
41
+ f.puts <<EOS
42
+ #{name}
43
+ #{"-" * name.length}
44
+ #{desc}
45
+ EOS
46
+ end
47
+ end
48
+
49
+ def enabled? name; !hooks_for(name).empty? end
50
+
51
+ def hooks_for name
52
+ if @blocks[name].nil? || @blocks[name].empty?
53
+ dirs = [Ditz::home_dir, Ditz::find_dir_containing(".ditz")].compact.map do |d|
54
+ File.join d, ".ditz", "hooks"
55
+ end
56
+ Ditz::debug "looking for hooks in #{dirs.join(" and ")}"
57
+ files = dirs.map { |d| Dir[File.join(d, "*.rb")] }.flatten
58
+ files.each do |fn|
59
+ Ditz::debug "loading hook file #{fn}"
60
+ load fn
61
+ end
62
+ end
63
+
64
+ @blocks[name] || []
65
+ end
66
+ end
67
+ end