I'm working to the following little project: https://github.com/AndreaCrotti/project-organizer
Which in short aims to manage many different projects much more easily. One of the useful things is a way to detect automatically the kind of project that I'm working on, to set up correctly some commands.
At the moment I'm using a classmethod "match" function and a detect function which iterates over the various "match". I'm sure there might be a better design for this, but can't find it.
Any ideas?
class ProjectType(object):
build_cmd = ""
@classmethod
def match(cls, _):
return True
class PythonProject(ProjectType):
build_cmd = "python setup.py develop --user"
@classmethod
def match(cls, base):
return path.isfile(path.join(base, 'setup.py'))
class AutoconfProject(ProjectType):
#TODO: there should be also a way to configure it
build_cmd = "./configure && make -j3"
@classmethod
def match(cls, base):
markers = ('configure.in', 'configure.ac', 'makefile.am')
return any(path.isfile(path.join(base, x)) for x in markers)
class MakefileOnly(ProjectType):
build_cmd = "make"
@classmethod
def match(cls, base):
# if we can count on the order the first check is not useful
return (not AutoconfProject.match(base)) and \
(path.isfile(path.join(base, 'Makefile')))
def detect_project_type(path):
prj_types = (PythonProject, AutoconfProject, MakefileOnly, ProjectType)
for p in prj_types:
if p.match(path):
return p()
2 Answers 2
This was a reasonable use of a factory function as a class method.
One possible improvement is to have all the classes inherit from a single parent class which would have a single classmethod that incorporated all the logic in detect_project_type.
Perhaps something like this would work:
class ProjectType(object):
build_cmd = ""
markers = []
@classmethod
def make_project(cls, path):
prj_types = (PythonProject, AutoconfProject, MakefileOnly, ProjectType)
for p in prj_types:
markers = p.markers
if any(path.isfile(path.join(path, x)) for x in markers):
return p()
class PythonProject(ProjectType):
build_cmd = "python setup.py develop --user"
markers = ['setup.py']
class AutoconfProject(ProjectType):
#TODO: there should be also a way to configure it
build_cmd = "./configure && make -j3"
markers = ['configure.in', 'configure.ac', 'makefile.am']
class MakefileOnly(ProjectType):
build_cmd = "make"
markers = ['Makefile']
2 Comments
make_project sit outside the classes as just a plain function. It allows the naming of everything to be a bit more natural IMO. Also, while "the complexity has to go somewhere", I don't think I like the idea that the tuple of all child classes is information held within the parent class.To me this looks fine, though i would do two improvements
1- Use metaclass to automatically collect all ProjectTypes instead of manually listing them, that will avoid missing some project type by mistake or having a wrong order e.g.
class ProjectTypeManger(type):
klasses = []
def __new__(meta, classname, bases, classDict):
klass = type.__new__(meta, classname, bases, classDict)
meta.klasses.append(klass)
return klass
@classmethod
def detect_project_type(meta, path):
for p in meta.klasses:
if p.match(path):
return p()
class ProjectType(object):
__metaclass__ = ProjectTypeManger
build_cmd = ""
@classmethod
def match(cls, _):
return None
2- match method should return the object itself instead of true/false, that way class can configure object anyway it wants + you can call baseclass match method, e.g. MakefileOnly can derive from AutoconfProject so that it first checks base class for any matches, but not sure if such inheritance makes sense
class MakefileOnly(AutoconfProject):
build_cmd = "make"
@classmethod
def match(cls, base):
ret = super(MakefileOnly, cls).match(base)
if ret is not None:
return ret
if path.isfile(path.join(base, 'Makefile')):
return cls()
return None