| 1 | import os |
|---|
| 2 | import repository_hook_system.listener as listener |
|---|
| 3 | |
|---|
| 4 | from repository_hook_system.interface import IRepositoryHookSetup |
|---|
| 5 | from repository_hook_system.listener import command_line |
|---|
| 6 | from repository_hook_system.listener import option_parser |
|---|
| 7 | from trac.core import * |
|---|
| 8 | from utils import command_line_args |
|---|
| 9 | from utils import iswritable |
|---|
| 10 | |
|---|
| 11 | class FileSystemHooks(Component): |
|---|
| 12 | """ |
|---|
| 13 | Implementation of IRepositoryHookSetup for hooks that live on the |
|---|
| 14 | filesystem. Currently, the filenames associated with the hooks must |
|---|
| 15 | be the same as the hook names |
|---|
| 16 | """ |
|---|
| 17 | |
|---|
| 18 | implements(IRepositoryHookSetup) |
|---|
| 19 | abstract = True |
|---|
| 20 | mode = 0750 # mode to write hook files |
|---|
| 21 | |
|---|
| 22 | ### these methods must be implemented by the provider class |
|---|
| 23 | |
|---|
| 24 | def filename(self, hookname): |
|---|
| 25 | raise NotImplementedError |
|---|
| 26 | |
|---|
| 27 | def args(self): |
|---|
| 28 | raise NotImplementedError |
|---|
| 29 | |
|---|
| 30 | ### methods for manipulating the files |
|---|
| 31 | |
|---|
| 32 | def file_contents(self, hookname): |
|---|
| 33 | """ |
|---|
| 34 | return the lines of the file for a given hook, |
|---|
| 35 | or None if the file does not yet exist |
|---|
| 36 | """ |
|---|
| 37 | filename = self.filename(hookname) |
|---|
| 38 | if not os.path.exists(filename): |
|---|
| 39 | return None |
|---|
| 40 | |
|---|
| 41 | f = file(filename) |
|---|
| 42 | retval = [ i.rstrip() for i in f.readlines() ] |
|---|
| 43 | f.close() |
|---|
| 44 | return retval |
|---|
| 45 | |
|---|
| 46 | def marker(self): |
|---|
| 47 | """marker to place in the file to identify the hook""" |
|---|
| 48 | return "# trac repository hook system" |
|---|
| 49 | |
|---|
| 50 | def projects_enabled(self, hookname): |
|---|
| 51 | """ |
|---|
| 52 | returns enabled projects, or None if the stub is not found |
|---|
| 53 | returns a tuple of (lines, index, list_of_projects) when found |
|---|
| 54 | this won't work properly if the command line is used more than once |
|---|
| 55 | in the file |
|---|
| 56 | """ |
|---|
| 57 | lines = self.file_contents(hookname) |
|---|
| 58 | if lines is None: |
|---|
| 59 | return None |
|---|
| 60 | |
|---|
| 61 | retval = None |
|---|
| 62 | invoker = listener.filename() |
|---|
| 63 | for index, line in enumerate(lines): |
|---|
| 64 | if ' %s ' % invoker in line and not line.strip().startswith('#'): |
|---|
| 65 | if retval is not None: |
|---|
| 66 | # TODO: raise an error indicate that multiple invocations |
|---|
| 67 | # detected in the hook file |
|---|
| 68 | pass |
|---|
| 69 | args = command_line_args(line) |
|---|
| 70 | parser = option_parser() |
|---|
| 71 | options, args = parser.parse_args(args) |
|---|
| 72 | retval = index, lines, options.projects |
|---|
| 73 | |
|---|
| 74 | return retval |
|---|
| 75 | |
|---|
| 76 | def create(self, hookname): |
|---|
| 77 | """create the stub for given hook and return the file object""" |
|---|
| 78 | |
|---|
| 79 | filename = self.filename(hookname) |
|---|
| 80 | try: |
|---|
| 81 | os.mknod(filename, self.mode) |
|---|
| 82 | except: # won't work on windows |
|---|
| 83 | pass |
|---|
| 84 | f = file(filename, 'w') |
|---|
| 85 | print >> f, "#!/bin/bash" |
|---|
| 86 | return f |
|---|
| 87 | |
|---|
| 88 | ### methods for IRepositoryHookSetup |
|---|
| 89 | |
|---|
| 90 | def enable(self, hookname): |
|---|
| 91 | # TODO: remove multiple blank lines when writing |
|---|
| 92 | |
|---|
| 93 | if self.is_enabled(hookname): |
|---|
| 94 | return # nothing to do |
|---|
| 95 | |
|---|
| 96 | if not self.can_enable(hookname): |
|---|
| 97 | return # XXX err more gracefully |
|---|
| 98 | |
|---|
| 99 | def print_hook(f): |
|---|
| 100 | print >> f, '%s%s' % (os.linesep, self.marker()) |
|---|
| 101 | print >> f, command_line(self.env.path, hookname, *self.args()) |
|---|
| 102 | |
|---|
| 103 | filename = self.filename(hookname) |
|---|
| 104 | if os.path.exists(filename): |
|---|
| 105 | projects = self.projects_enabled(hookname) |
|---|
| 106 | if projects is None: |
|---|
| 107 | f = file(filename, 'a') |
|---|
| 108 | print_hook(f) |
|---|
| 109 | else: |
|---|
| 110 | project = os.path.realpath(self.env.path) |
|---|
| 111 | index, lines, projects = projects |
|---|
| 112 | projects.append(project) |
|---|
| 113 | lines[index] = command_line(projects, hookname, *self.args()) |
|---|
| 114 | f = file(filename, 'w') |
|---|
| 115 | for line in lines: |
|---|
| 116 | print >> f, line |
|---|
| 117 | else: |
|---|
| 118 | f = self.create(hookname) |
|---|
| 119 | print_hook(f) |
|---|
| 120 | |
|---|
| 121 | f.close() |
|---|
| 122 | |
|---|
| 123 | def disable(self, hookname): |
|---|
| 124 | if not self.is_enabled(hookname): |
|---|
| 125 | return |
|---|
| 126 | index, lines, projects = self.projects_enabled(hookname) |
|---|
| 127 | projects = [ os.path.realpath(project) |
|---|
| 128 | for project in projects ] |
|---|
| 129 | project = os.path.realpath(self.env.path) |
|---|
| 130 | |
|---|
| 131 | projects.remove(project) |
|---|
| 132 | if projects: |
|---|
| 133 | lines[index] = command_line(projects, hookname, *self.args()) |
|---|
| 134 | else: |
|---|
| 135 | lines.pop(index) |
|---|
| 136 | # TODO: list bounds checking |
|---|
| 137 | if lines[index-1] == self.marker(): |
|---|
| 138 | index = index-1 |
|---|
| 139 | lines.pop(index) |
|---|
| 140 | if not lines[index-1].strip(): |
|---|
| 141 | lines.pop(index-1) |
|---|
| 142 | |
|---|
| 143 | f = file(self.filename(hookname), 'w') |
|---|
| 144 | for line in lines: |
|---|
| 145 | print >> f, line |
|---|
| 146 | f.close() |
|---|
| 147 | |
|---|
| 148 | def is_enabled(self, hookname): |
|---|
| 149 | if os.path.exists(self.filename(hookname)): |
|---|
| 150 | projects = self.projects_enabled(hookname) |
|---|
| 151 | if projects is not None: |
|---|
| 152 | index, lines, projects = projects |
|---|
| 153 | projects = [ os.path.realpath(project) for project in projects ] |
|---|
| 154 | project = os.path.realpath(self.env.path) |
|---|
| 155 | if project in projects: |
|---|
| 156 | return True |
|---|
| 157 | return False |
|---|
| 158 | |
|---|
| 159 | def can_enable(self, hookname): |
|---|
| 160 | return iswritable(self.filename(hookname)) |
|---|