Partial fix to #22. Laydi will require a project directory to run. If the

project directory does not exist, i.e. if a new project should be created, the
new -n switch is required. New syntax to run laydi is now
{{{
laydi [options] </path/to/project>
}}}
This commit is contained in:
Einar Ryeng 2011-03-06 23:08:09 +00:00
parent b5d2e8e181
commit dc8da8823e
2 changed files with 314 additions and 142 deletions

View File

@ -3,7 +3,7 @@
from getopt import getopt from getopt import getopt
import os import os
import sys import sys
from laydi import laydi, projectview, workflow, main from laydi import laydi, project, projectview, workflow, main
#import workflows #import workflows
from laydi import cfgparse from laydi import cfgparse
import optparse import optparse
@ -73,11 +73,20 @@ def parse_options():
action='store_true', action='store_true',
help='Generate configuration file ~/.laydi if it does not exist.') help='Generate configuration file ~/.laydi if it does not exist.')
op.add_option('-n', '--new-project',
action='store_true',
help='Create new project directory.')
for cf in conf_files: for cf in conf_files:
if os.path.isfile(cf): if os.path.isfile(cf):
cp.add_file(cf) cp.add_file(cf)
return cp.parse(op) options, params = cp.parse(op)
if len(params) != 1:
print "error: project directory must be specified."
sys.exit(1)
return options, params
if __name__ == '__main__': if __name__ == '__main__':
import gtk import gtk
@ -86,6 +95,8 @@ if __name__ == '__main__':
gnome.program_init(PROGRAM_NAME, VERSION) gnome.program_init(PROGRAM_NAME, VERSION)
options, params = parse_options() options, params = parse_options()
## Workflow setup
main.options = options main.options = options
for dir in main.options.workflowdir.split(';'): for dir in main.options.workflowdir.split(';'):
@ -112,8 +123,18 @@ if __name__ == '__main__':
main.set_options(options) main.set_options(options)
app = laydi.LaydiApp() app = laydi.LaydiApp()
## Project setup
prjroot = params[0]
if not project.is_project_directory(prjroot):
if options.new_project:
project.make_project_directory(prjroot)
else:
print "error: project directory not found: %s" % prjroot
print "notice: use the -n option to make a new project"
sys.exit(2)
main.set_application(app) main.set_application(app)
main.set_projectview(projectview.ProjectView()) main.set_projectview(projectview.ProjectView(prjroot))
app.set_projectview(main.projectview) app.set_projectview(main.projectview)
app.show() app.show()

View File

@ -1,154 +1,305 @@
import os import os, os.path
import scipy import sys
import gobject import configobj
import gtk import time
import laydi
import logger, dataset, plots, main
class Project: import dataset
"""A Project contains datasets, selections etc.
The project, of which the application has only one at any given time, NAME = "laydi-cmd"
is the container for all datasets, plots and selections in use. The data VERSION = "0.1.0"
in the project is organized in a gtk.TreeStrore that is displayed in the PROJECT_VERSION_STRING = "Laydi project version 1"
navigator.
def is_project_directory(dirname):
"""Verifies that a directory is a laydi project"""
if not os.path.isdir(dirname):
return False
## Verify that the version is correct.
version_fn = os.path.join(dirname, "VERSION")
if not os.path.exists(version_fn):
return False
fd = open(version_fn)
line = fd.readline()
fd.close()
if line.strip() != PROJECT_VERSION_STRING:
return False
## Require directories to be present.
if not os.path.isdir(os.path.join(dirname, "annotations")):
return False
if not os.path.isdir(os.path.join(dirname, "data")):
return False
if not os.path.isdir(os.path.join(dirname, "selections")):
return False
if not os.path.isdir(os.path.join(dirname, "exports")):
return False
## If no tests failed, return True
return True
def make_project_directory(dirname, force=False):
"""Creates a project directory
force: ignore that directory exists and proceed anyway.
""" """
if os.path.exists(dirname) and not force:
return False
def __init__(self, name="Testing"): rootdir = dirname
self.data_tree = gtk.TreeStore(str, anndir = os.path.join(dirname, "annotations")
str, seldir = os.path.join(dirname, "selections")
object, datadir = os.path.join(dirname, "data")
str, exportdir = os.path.join(dirname, "exports")
str, version_file_path = os.path.join(dirname, "VERSION")
gobject.TYPE_OBJECT,
float)
self.name = name os.makedirs(rootdir)
self.dim_names = [] for d in [anndir, seldir, datadir, exportdir]:
self._selection_observers = [] os.mkdir(d)
self._dataset_observers = []
self.current_data = []
self.datasets = []
self.sel_obj = dataset.Selection('Current Selection')
self.selections = []
self._last_selection = None
self._dataset_iter_map = {}
def add_selection_observer(self, observer): fd = open(version_file_path, "w")
self._selection_observers.append(observer) print >> fd, PROJECT_VERSION_STRING
observer.selection_changed(None, self.get_selection()) fd.close()
def notify_selection_listeners(self, dim_name):
"""Notifies observers"""
for observer in self._selection_observers:
observer.selection_changed(dim_name, self.get_selection())
def add_dataset_observer(self, observer):
self._dataset_observers.append(observer)
observer.dataset_changed()
def notify_dataset_listeners(self): class Universe(object):
"""Notifies observers when new datasets are added""" """A Universe is a collection of all existing identifiers in a set of datasets"""
for observer in self._dataset_observers:
observer.dataset_changed()
def set_selection(self, dim_name, selection): def __init__(self):
"""Sets a current selection and notify observers""" self.refcount = {}
self.sel_obj[dim_name] = set(selection)
self.notify_selection_listeners(dim_name)
self._last_selection = selection
def get_selection(self): def register_dim(self, dim):
"""Returns the current selection object""" """Increase reference count for identifiers in Dimension object dim"""
return self.sel_obj d = self.refcount.get(dim.name, None)
if d == None:
d = {}
self.refcount[dim.name] = d
for i in dim:
d[i] = d.get(i, 0) + 1
def delete_data(self, it): def register_ds(self, ds):
"""Delete elements from the project.""" """Increase reference count for identifiers in all Dimensions of dataset ds"""
child = self.data_tree.iter_children(it) for dim in ds.dims:
while child != None: self.register_dim(dim)
c = self.data_tree.iter_next(child)
self.delete_data(child)
child = c
main.application.main_view.remove_view(self.data_tree.get(it, 2)[0])
self.data_tree.remove(it)
def add_data(self, parents, data, fun='Function'): def unregister_dim(self, dim):
"""Adds a set of data and plots to the navigator. """Update reference count for identifiers in Dimension object dim
Update reference count for identifiers in Dimension object dim, and remove all
This method is usually called after a Function in a workflow identifiers with a reference count of 0, as they do not (by definition) exist
has finished and returns its output.""" any longer.
if len(parents) > 0:
parent_iter = self._dataset_iter_map[parents[0]]
else:
parent_iter = None
# Add the function node to the tree
icon = laydi.icon_factory.get("folder_grey")
it = self.data_tree_insert(parent_iter, fun, None, None, "black", icon)
# Add all returned datasets/plots/selections
for d in data:
# Any kind of dataset
if isinstance(d, dataset.Dataset):
if isinstance(d, dataset.GraphDataset):
icon = laydi.icon_factory.get("graph_dataset")
elif isinstance(d, dataset.CategoryDataset):
icon = laydi.icon_factory.get("category_dataset")
else:
icon = laydi.icon_factory.get("dataset")
self.add_dataset(d)
self.data_tree_insert(it, d.get_name(), d, None, "black", icon)
# Any kind of plot
elif isinstance(d, plots.Plot):
icon = laydi.icon_factory.get("line_plot")
self.data_tree_insert(it, d.get_title(), d, None, "black", icon)
d.set_selection_listener(self.set_selection)
self._selection_observers.append(d)
# Selections are not added to the data tree
elif isinstance(d, dataset.Selection):
self.add_selection(d)
def data_tree_insert(self, parent, text, data, bg, fg, icon, selected = 0):
"""Inserts data into the tree view.
@param text: The title of the object.
@param data: A dataset, plot or function object.
@param bg: Background color.
@param fg: Foreground (font) color.
@param icon: Pixmap icon.
""" """
tree = self.data_tree ids = self.refcount[dim.name]
it = tree.append(parent) for i in dim:
tree[it] = [text, type(data), data, bg, fg, icon, selected] refcount = ids[i]
self._dataset_iter_map[data] = it if refcount == 1:
return it ids.pop(i)
else:
ids[i] -= 1
if len(ids) == 0:
self.refcount.pop(dim.name)
def add_dataset(self, dataset):
"""Appends a new Dataset to the project."""
logger.log('debug','Adding dataset: %s' %dataset.get_name())
self.datasets.append(dataset)
for dim_name in dataset.get_all_dims():
if dim_name not in self.dim_names:
self.dim_names.append(dim_name)
self.sel_obj[dim_name] = set()
self.notify_selection_listeners(dim_name)
self.notify_dataset_listeners()
def add_selection(self, selection): def unregister_ds(self, ds):
"""Adds a new selection to the project.""" """Update reference count for identifiers along Dimensions in Dataset ds.
self.selections.append(selection) Update reference count for identifiers along all Dimensions in
self.notify_dataset_listeners() Dataset ds, and remove all identifiers with a reference count of 0,
as they do not (by definition) exist any longer.
"""
for dim in ds:
self.register_dim(dim)
def object_at(self, path): def register(self, obj):
"""Returns the object at a given path in the tree.""" if isinstance(obj, Dataset):
it = self.get_iter(path) self.register_ds(obj)
obj = self[it][2] else:
if obj: self.register_dim(obj)
obj.show()
return obj
#def set_current_data(self, obj): def unregister(self, obj):
# self.current_data = obj if isinstance(obj, Dataset):
self.unregister_ds(obj)
else:
self.unregister_dim(obj)
def __getent___(self, dimname):
return set(self.references[dimname].keys())
def __iter__(self):
return self.references.keys().__iter__()
class Dimension(object):
"""A Dimension represents the set of identifiers an object has along an axis.
"""
def __init__(self, name, ids=[]):
self.name = name
self.idset = set(ids)
self.idlist = list(ids)
if len(self.idset) != len(self.idlist):
raise Exception("Duplicate identifiers are not allowed")
def __getitem__(self, element):
return self.idlist[element]
def __getslice__(self, start, end):
return self.idlist[start:end]
def __contains__(self, element):
return self.idset.__contains__(element)
def __str__(self):
return "%s: %s" % (self.name, str(self.idlist))
def __len__(self):
return len(self.idlist)
def __iter__(self):
return iter(self.idlist)
def intersection(self, dim):
return self.idset.intersection(dim.idset)
def as_tuple(self):
return (self.name, self.idlist)
class Directory(object):
def __init__(self, path):
self.path = path
self.files = set()
self.timestamp = -1
self.update()
def update(self):
now = time.time()
newfiles = set(os.listdir(self.path))
for fn in newfiles - self.files:
if os.path.isdir(os.path.join(self.path, fn)):
self.dir_created(fn)
else:
self.file_created(fn)
for fn in self.files - newfiles:
if os.path.isdir(os.path.join(self.path, fn)):
self.dir_deleted(fn)
else:
self.file_removed(fn)
for fn in self.files.intersection(newfiles):
filepath = os.path.join(self.path, fn)
if os.path.getctime(filepath) >= self.timestamp:
if os.path.isdir(filepath):
self.dir_changed(fn)
else:
self.file_changed(fn)
self.files = newfiles
self.timestamp = now
def file_created(self, fn):
print "file created: %s" % fn
pass
def file_changed(self, fn):
print "file changed: %s" % fn
pass
def file_removed(self, fn):
print "file removed: %s" % fn
pass
def dir_created(self, fn):
print "directory created: %s" % fn
pass
def dir_changed(self, fn):
print "directory changed: %s" % fn
pass
def dir_removed(self, fn):
print "directory removed: %s" % fn
pass
class DataDirectory(Directory):
def __init__(self, dirname, project):
self.project = project
self.datasets= []
self.dsfiles = {}
Directory.__init__(self, dirname)
def file_created(self, fn):
"""Called from update() when new files are created.
Load new datasets that have appeared since last update.
"""
filepath = os.path.join(self.path, fn)
name, ext = os.path.splitext(fn)
if ext == ".ftsv":
ds = dataset.read_ftsv(filepath)
self.datasets.append(ds)
self.dsfiles[fn] = ds
def file_changed(self, fn):
"""Called from update() when files are changed.
Delete old dataset and load the new one when dataset files
have been changed.
"""
filepath = os.path.join(self.path, fn)
name, ext = os.path.splitext(fn)
print self.datasets
if ext == ".ftsv":
oldds = self.dsfiles[fn]
self.datasets.remove(oldds)
ds = dataset.read_ftsv(filepath)
self.datasets.append(ds)
self.dsfiles[fn] = ds
print self.datasets
def file_removed(self, fn):
"""Called from update() when a file is deleted
Removes the associated dataset if a dataset file is removed.
"""
filepath = os.path.join(self.path, fn)
name, ext = os.path.splitext(fn)
if ext == ".ftsv":
ds = self.dsfiles[fn]
self.datasets.remove(ds)
self.dsfiles.pop(fn)
def dir_created(self, fn):
"""Called from update() when a subdirectory is created.
Instantiate new handlers for the directory if possible.
"""
filepath = os.path.join(self.path, fn)
def SelectionDirectory(object):
def __init__(self, dirname):
pass
class Project(object):
def __init__(self, dirname):
"""Opens a project directory. The directory must exist and be a valid project."""
## Set path names.
self.rootdir = dirname
self.anndir = os.path.join(dirname, "annotations")
self.seldir = os.path.join(dirname, "selections")
self.datadir = os.path.join(dirname, "data")
self.exportdir = os.path.join(dirname, "exports")
version_file_path = os.path.join(dirname, "VERSION")
self.universe = Universe()
self.data = DataDirectory(self.datadir, self)
def update(self):
print "updating project"
self.data.update()