4 import distutils.version
9 ## -- Global Functions --
11 def getInstallLines(vs):
12 """Retrieves a list of lines from the version directory that
13 can be passed to Deployment.parse()"""
14 if os.path.isfile(vs):
15 return fileinput.input([vs])
16 return fileinput.input([vs + "/" + f for f in os.listdir(vs)])
18 def parse_install_lines(show, options, yield_errors = False):
19 if not show: show = applications()
20 show = frozenset(show)
21 for line in getInstallLines(options.versions_path):
24 d = Deployment.parse(line)
25 name = d.application.name
26 except NoSuchApplication as e:
31 # we consider this a worse error
32 logging.warning("Error with '%s'" % line.rstrip())
35 if name + "-" + str(d.version) in show or name in show:
42 ## -- Model Objects --
44 class Deployment(object):
45 """Represents a deployment of an autoinstall; i.e. a concrete
46 directory in web_scripts that has .scripts-version in it."""
47 def __init__(self, location, log=None, version=None):
48 """ `location` Location of the deployment
49 `version` ApplicationVersion of the app (this is cached info)
50 `log` DeployLog of the app"""
51 self.location = location
52 self._app_version = version
55 def read(self, file, force = False):
56 """Reads a file's contents and stuffs it in a cache"""
57 if force or file not in self._read_cache:
58 f = open(os.path.join(self.location, file))
59 self._read_cache[file] = f.read()
61 return self._read_cache[file]
63 return self.application.extract(self)
64 def updateVersion(self, version=None):
65 """`version` Version string to update to, or leave out to simply
66 force the creation of .scripts/version file"""
68 version = str(self.version)
70 self._app_version = self.application.makeVersion(version)
71 f = open(os.path.join(self.scripts_dir, 'version'), 'w')
72 f.write(self.application.name + '-' + version + "\n")
74 def scriptsifyVersion(self):
75 """At the end of a migration, writes out the current version
76 with -scripts appended to .scripts/version"""
77 self.updateVersion(str(self.version) + '-scripts')
79 def scripts_dir(self):
80 return os.path.join(self.location, '.scripts')
82 def version_file(self):
83 return os.path.join(self.location, '.scripts-version')
85 def application(self):
86 return self.app_version.application
90 self._log = log.DeployLog.load(self.version_file)
94 """Returns the distutils Version of the deployment"""
95 return self.app_version.version
97 def app_version(self, force = False):
98 """Returns the ApplicationVersion of the deployment"""
99 if self._app_version and not force: return self._app_version
100 else: return self.log[-1].version
103 """Parses a line from the results of parallel-find.pl.
104 This will work out of the box with fileinput, see
108 location, deploydir = line.split(":")
110 return Deployment(line) # lazy loaded version
111 return Deployment(location, version=ApplicationVersion.parse(deploydir, location))
113 class Application(object):
114 """Represents the generic notion of an application, i.e.
115 mediawiki or phpbb."""
116 def __init__(self, name):
119 self._extractors = {}
121 def repository(self):
122 """Returns the Git repository that would contain this application."""
123 repo = os.path.join("/afs/athena.mit.edu/contrib/scripts/git/autoinstalls", self.name + ".git")
124 if not os.path.isdir(repo):
125 raise NoRepositoryError(app)
127 def makeVersion(self, version):
128 if version not in self.versions:
129 self.versions[version] = ApplicationVersion(distutils.version.LooseVersion(version), self)
130 return self.versions[version]
131 def extract(self, deployment):
132 """Extracts wizard variables from a deployment."""
134 for k,extractor in self.extractors.items():
135 result[k] = extractor(deployment)
138 def extractors(self):
142 """Makes an application, but uses the correct subtype if available."""
144 __import__("wizard.app." + name)
145 return getattr(wizard.app, name).Application(name)
147 return Application(name)
149 class ApplicationVersion(object):
150 """Represents an abstract notion of a version for an application"""
151 def __init__(self, version, application):
152 """ `version` Instance of distutils.LooseVersion
153 `application` Instance of Application
154 WARNING: Please don't call this directly; instead, use getVersion()
155 on the application you want, so that this version gets registered."""
156 self.version = version
157 self.application = application
159 def scripts_tag(self):
160 """Returns the name of the Git tag for this version"""
161 # XXX: This assumes that there's only a -scripts version
162 # which will not be true in the future. Unfortunately, finding
163 # the "true" latest version is computationally expensive
164 return "v%s-scripts" % self.version
166 return cmp(x.version, y.version)
168 def parse(deploydir,location,applookup=None):
169 # The version of the deployment, will be:
170 # /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z for old style installs
171 name = deploydir.split("/")[-1]
173 if name.find(" ") != -1:
174 raw_app, raw_version = name.split(" ")
175 version = raw_version[1:] # remove leading v
176 app, _ = raw_app.split(".") # remove trailing .git
177 elif name.find("-") != -1:
178 app, _, version = name.partition("-")
182 except ValueError: # mostly from the a, b = foo.split(' ')
183 raise DeploymentParseError(deploydir, location)
184 if not applookup: applookup = applications()
186 # defer to the application for version creation
187 return applookup[app].makeVersion(version)
189 raise NoSuchApplication(app, location)
193 class Error(Exception):
194 """Base error class for this module"""
197 class NoSuchApplication(Error):
198 def __init__(self, name, location):
200 self.location = location
202 return "ERROR: Unrecognized app '%s' at %s" % (self.name, self.location)
204 class DeploymentParseError(Error):
205 def __init__(self, malformed, location):
206 self.malformed = malformed
207 self.location = location
209 return """ERROR: Unparseable '%s' at %s""" % (self.malformed, self.location)
211 class NoRepositoryError(Error):
212 def __init__(self, app):
214 self.location = "unknown"
218 ERROR: Could not find repository for this application. Have
219 you converted the repository over? Is the name %s
220 the same as the name of the .git folder?
223 # If you want, you can wrap this up into a registry and access things
224 # through that, but it's not really necessary
227 "mediawiki", "wordpress", "joomla", "e107", "gallery2",
228 "phpBB", "advancedbook", "phpical", "trac", "turbogears", "django",
229 # these are technically deprecated
230 "advancedpoll", "gallery",
235 """Hash table for looking up string application name to instance"""
237 if not _applications:
238 _applications = dict([(n,Application.make(n)) for n in application_list ])