Modules |
Files |
Inheritance Tree |
Inheritance Graph |
Name Index |
Config
File: Synopsis/Core/Project.py
1| # $Id: Project.py,v 1.10 2002/11/19 03:44:08 chalky Exp $
2| #
3| # This file is a part of Synopsis.
4| # Copyright (C) 2000, 2001 Stefan Seefeld
5| # Copyright (C) 2000, 2001 Stephen Davies
6| #
7| # Synopsis is free software; you can redistribute it and/or modify it
8| # under the terms of the GNU General Public License as published by
9| # the Free Software Foundation; either version 2 of the License, or
10| # (at your option) any later version.
11| #
12| # This program is distributed in the hope that it will be useful,
13| # but WITHOUT ANY WARRANTY; without even the implied warranty of
14| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15| # General Public License for more details.
16| #
17| # You should have received a copy of the GNU General Public License
18| # along with this program; if not, write to the Free Software
19| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20| # 02111-1307, USA.
21| #
22| # $Log: Project.py,v $
23| # Revision 1.10 2002/11/19 03:44:08 chalky
24| # Changed SourcePath to SourceRule, included an Exclude rule
25| #
26| # Revision 1.9 2002/10/27 12:08:25 chalky
27| # Add a project verbose flag, used by the Executor
28| #
29| # Revision 1.8 2002/09/28 06:17:01 chalky
30| # Added is_project
31| #
32| # Revision 1.7 2002/09/20 10:36:40 chalky
33| # Write the comment to the top of the project file
34| #
35| # Revision 1.6 2002/06/22 07:03:27 chalky
36| # Updates to GUI - better editing of Actions, and can now execute some.
37| #
38| # Revision 1.5 2002/06/12 12:58:09 chalky
39| # Added remove channel facility.
40| #
41| # Revision 1.4 2002/04/26 01:21:13 chalky
42| # Bugs and cleanups
43| #
44| # Revision 1.3 2001/11/07 05:58:21 chalky
45| # Reorganised UI, opening a .syn file now builds a simple project to view it
46| #
47| # Revision 1.2 2001/11/06 08:47:11 chalky
48| # Silly bug, arrows, channels are saved
49| #
50| # Revision 1.1 2001/11/05 06:52:11 chalky
51| # Major backside ui changes
52| #
53|
54| import sys, types
55| from Synopsis.Core import Util
56| from Action import *
57|
58| def is_project_file(filename):
59| f = open(filename, "rt")
60| is_project = (f.readline() == '"""Synopsis Project File v1\n')
61| f.close()
62| return is_project
63|
64| class Project:
65| """Encapsulates a single project. A project is a set of actions connected
66| by channels - each action may have one or more inputs and outputs which
67| are other actions. A project also has project-wide configuration, such as
68| the data directory."""
69| def __init__(self):
70| self.__data_dir = './'
71| self.__filename = None
72| self.__actions = ProjectActions(self)
73| self.__name = 'New Project'
74| self.__default_formatter = None
75| self.__verbose = 0
76| def filename(self):
77| "Returns the filename of this project, or None if not set yet"
78| return self.__filename
79| def set_filename(self, filename):
80| "Sets the filename of this file"
81| self.__filename = filename
82| def data_dir(self): return self.__data_dir
83| def set_data_dir(self, dir): self.__data_dir = dir
84|
85| def name(self): return self.__name
86| def set_name(self, name): self.__name = name
87|
88| def verbose(self): return self.__verbose
89| def set_verbose(self, verbose): self.__verbose = verbose
90|
91| def default_formatter(self): return self.__default_formatter
92| def set_default_formatter(self, action):
93| if isinstance(action, FormatAction) or action is None:
94| self.__default_formatter = action
95|
96| def actions(self):
97| "Returns a ProjectActions object"
98| return self.__actions
99|
100| def save(self):
101| if self.__filename is None:
102| raise Exception, 'Project has no filename'
103| file = open(self.__filename, 'w')
104| writer = ProjectWriter(file)
105|
106| writer.write_top(
107| '"""Synopsis Project File v1\n'+
108| 'This file is auto-generated. If you must make changes by hand, '+
109| 'please make a backup just in case."""\n')
110| # write project stuff like name, base dir, etc here
111| writer.write_item(self)
112|
113| writer.flush()
114| file.close()
115|
116| def load(self, filename):
117| ProjectReader(self).read(filename)
118|
119| class ProjectActions:
120| """Manages the actions in a project.
121|
122| Clients can register for events related to the actions. The events
123| supported by the listener interface are:
124|
125| def action_added(self, action):
126| def action_moved(self, action):
127| def action_removed(self, action):
128| def channel_added(self, source, dest):
129| def channel_removed(self, source, dest):
130| """
131|
132| def __init__(self, project):
133| """Constructor"""
134| self.project = project
135| self.__actions = []
136| self.__action_names = {}
137| self.__listeners = []
138|
139| def actions(self):
140| """Returns the list of actions in this project Actions. The list
141| returned should be considered read-only"""
142| return self.__actions
143|
144| def get_action(self, name):
145| """Returns the Action object by name. This method uses a dictionary
146| lookup so should be preferred to iteration. Returns None if the name
147| is not found."""
148| if not self.__action_names.has_key(name): return None
149| return self.__action_names[name]
150|
151| def add_listener(self, l):
152| """Adds a listener to this Projec Actions' events. The listener may
153| implement any of the supported methods and will receive those events.
154| @see ProjectActions for more info."""
155| self.__listeners.append(l)
156|
157| def __fire(self, signal, *args):
158| """Fires the given event to all listeners"""
159| for l in self.__listeners:
160| if hasattr(l, signal):
161| apply(getattr(l, signal), args)
162|
163| def add_action(self, action):
164| """Adds the given action to this project"""
165| self.check_name(action)
166| self.__actions.append(action)
167| self.__action_names[action.name()] = action
168| self.__fire('action_added', action)
169|
170| def move_action(self, action, x, y):
171| """Moves the given action to the given screen coordinates"""
172| action.move_to(x, y)
173| self.__fire('action_moved', action)
174|
175| def move_action_by(self, action, dx, dy):
176| """Moves the given action by the given screen delta-coordinates"""
177| action.move_by(dx, dy)
178| self.__fire('action_moved', action)
179|
180| def remove_action(self, action):
181| """Removes the given action, and destroys all channels to/from it"""
182| if action not in self.__actions: return
183| # Remove all channels
184| for src in tuple(action.inputs()):
185| self.remove_channel(src, action)
186| for dst in tuple(action.outputs()):
187| self.remove_channel(action, dst)
188| # Remove action
189| self.__actions.remove(action)
190| del self.__action_names[action.name()]
191| # Check default_formatter
192| if action is self.project.default_formatter():
193| self.project.set_default_formatter(None)
194| self.__fire('action_removed', action)
195|
196| def rename_action(self, action, newname):
197| """Renames the given action to the given name"""
198| del self.__action_names[action.name()]
199| action.set_name(newname)
200| self.__action_names[newname] = action
201|
202| def add_channel(self, source, dest):
203| """Adds a "channel" between two actions. Causes the output of the
204| first to be connected to the input of the second. The event
205| 'channel_added' is fired."""
206| if dest in source.outputs() and source in dest.inputs(): return
207| source.outputs().append(dest)
208| dest.inputs().append(source)
209| self.__fire('channel_added', source, dest)
210|
211| def remove_channel(self, source, dest):
212| """Removes a "channel" between two actions. If the channel doesn't
213| exist, it is silently ignored. The event 'channel_removed' is
214| fired."""
215| if dest not in source.outputs() and source not in dest.inputs():
216| return
217| source.outputs().remove(dest)
218| dest.inputs().remove(source)
219| self.__fire('channel_removed', source, dest)
220|
221| def action_changed(self, action):
222| """Indicates that the given action has changed, so that listeners can
223| update themselves"""
224| self.__fire('action_changed', action)
225|
226| def check_name(self, action):
227| "Checks the name, and renames if necessary"
228| if not self.__action_names.has_key(action.name()): return
229| copy = 1
230| name = action.name()
231| newname = '%s (%d)'%(name, copy)
232| while self.__action_names.has_key(newname):
233| copy = copy + 1
234| newname = '%s (%d)'%(name, copy)
235| action.set_name(newname)
236|
237| def is_valid_channel(self, source, dest):
238| """Returns true if the given source-dest pair would form a valid
239| channel.
240| Invalid pairs (eg: formatter->formatter) return false.
241| Cyclic channels are also disallowed."""
242| valid_pairs = {
243| SourceAction : (ParserAction,),
244| ParserAction : (CacherAction,LinkerAction,FormatAction),
245| LinkerAction : (CacherAction,LinkerAction,FormatAction),
246| CacherAction : (CacherAction,LinkerAction,FormatAction),
247| FormatAction : ()
248| }
249| if not isinstance(source, Action) or not isinstance(dest, Action):
250| return None
251| # First, check valid pair
252| if not valid_pairs.has_key(source.__class__):
253| return None
254| if dest.__class__ not in valid_pairs[source.__class__]:
255| return None
256| # Second, check that dest isn't already output of source
257| if dest in source.outputs() or dest is source:
258| return None
259| # Third, check cycles: descend input tree checking for dest
260| check = list(source.inputs())
261| while len(check):
262| action = check.pop(0)
263| if action is dest:
264| return None
265| check.extend(action.inputs())
266| # If got this far, combination is valid!
267| return 1
268|
269|
270|
271| class ProjectWriter (Util.PyWriter):
272| def write_Project(self, project):
273| self.write("class Project:\n")
274| self.indent()
275| # write project stuff like name, base dir, etc here
276| self.write_attr('name', project.name())
277| self.write_attr('data_dir', project.data_dir())
278| self.write_attr('verbose', project.verbose())
279| self.write_item(project.actions())
280| dfm = project.default_formatter()
281| if dfm: self.write_attr('default_formatter', dfm.name())
282| self.outdent()
283|
284| def write_ProjectActions(self, actions):
285| self.write_attr('actions', self.long(actions.actions()))
286| channels = []
287| for source in actions.actions():
288| for dest in source.outputs():
289| channels.append( (source.name(), dest.name()) )
290| self.write_attr('channels', self.long(channels))
291|
292| def write_SimpleSourceRule(self, rule):
293| self.write_list((rule.type, rule.files))
294|
295| def write_GlobSourceRule(self, rule):
296| self.write_list((rule.type, rule.dirs, rule.glob, rule.recursive))
297|
298| def write_ExcludeSourceRule(self, rule):
299| self.write_list((rule.type, rule.glob))
300|
301| def write_SourceAction(self, action):
302| self.write_list(('SourceAction',
303| action.x(), action.y(), action.name(),
304| self.long(action.rules()) ))
305|
306| def write_ParserAction(self, action):
307| self.write_list(('ParserAction',
308| action.x(), action.y(), action.name(),
309| self.flatten_struct(action.config()) ))
310|
311| def write_LinkerAction(self, action):
312| self.write_list(('LinkerAction',
313| action.x(), action.y(), action.name(),
314| self.flatten_struct(action.config()) ))
315|
316| def write_FormatAction(self, action):
317| self.write_list(('FormatAction',
318| action.x(), action.y(), action.name(),
319| self.flatten_struct(action.config()) ))
320|
321| def write_CacherAction(self, action):
322| self.write_list(('CacherAction',
323| action.x(), action.y(), action.name(),
324| action.dir, action.file))
325|
326| class ProjectReader:
327| """A class that reads projects"""
328| def __init__(self, project):
329| self.project = project
330| def read(self, filename):
331| # Execute the file
332| exec_dict = {}
333| execfile(filename, exec_dict)
334| # Get 'Project' attribute
335| if not exec_dict.has_key('Project'):
336| raise Exception, 'Project load failed: Project attribute not found'
337| project = exec_dict['Project']
338| # Read attributes
339| self.project.set_filename(filename)
340| self.read_Project(project)
341| def read_Project(self, proj_obj):
342| project = self.project
343| # Read project stuff like name
344| if hasattr(proj_obj, 'name'): project.set_name(proj_obj.name)
345| if hasattr(proj_obj, 'verbose'): project.set_verbose(proj_obj.verbose)
346| if hasattr(proj_obj, 'data_dir'): project.set_data_dir(proj_obj.data_dir)
347| self.read_ProjectActions(proj_obj)
348| if hasattr(proj_obj, 'default_formatter'):
349| action = project.actions().get_action(proj_obj.default_formatter)
350| if action: project.set_default_formatter(action)
351| def read_ProjectActions(self, project_obj):
352| for action in project_obj.actions:
353| t = action[0]
354| if t in ('SourceAction', 'CacherAction', 'FormatAction',
355| 'ParserAction', 'LinkerAction'):
356| getattr(self, 'read_'+t)(action)
357| else:
358| raise Exception, 'Unknown action type: %s'%action[0]
359| if hasattr(project_obj, 'channels'):
360| actions = self.project.actions()
361| for source, dest in project_obj.channels:
362| src_obj = actions.get_action(source)
363| dst_obj = actions.get_action(dest)
364| actions.add_channel(src_obj, dst_obj)
365|
366| def read_SourceAction(self, action):
367| type, x, y, name, rules = action
368| self.action = SourceAction(x, y, name)
369| self.project.actions().add_action(self.action)
370| for rule in rules: self.read_SourceRule(rule)
371|
372| def read_ParserAction(self, action):
373| type, x, y, name, config = action
374| self.action = ParserAction(x, y, name)
375| self.project.actions().add_action(self.action)
376| self.action.set_config(config)
377|
378| def read_LinkerAction(self, action):
379| type, x, y, name, config = action
380| self.action = LinkerAction(x, y, name)
381| self.project.actions().add_action(self.action)
382| self.action.set_config(config)
383|
384| def read_FormatAction(self, action):
385| type, x, y, name, config = action
386| self.action = FormatAction(x, y, name)
387| self.project.actions().add_action(self.action)
388| self.action.set_config(config)
389|
390| def read_SourceRule(self, rule):
391| rules = self.action.rules()
392| if rule[0] == 'Simple':
393| if type(rule[1]) == types.ListType:
394| rules.append(SimpleSourceRule(rule[1]))
395| else:
396| # Backwards compat.
397| rules.append(SimpleSourceRule([rule[1]]))
398| elif rule[0] == 'Glob':
399| rules.append(GlobSourceRule(rule[1], rule[2], rule[3]))
400| elif rule[0] == 'Exclude':
401| rules.append(ExcludeSourceRule(rule[1]))
402| elif rule[0] == 'Dir':
403| # Backwards compat.
404| rules.append(GlobSourceRule([rule[1]], rule[2], 0))
405| elif rule[0] == 'Base':
406| # Backwards compat.
407| rules.append(GlobSourceRule([rule[1]], rule[2], 1))
408| else:
409| print "Warning: unknown source rule:",repr(rule)
410|
411| def read_CacherAction(self, action):
412| type, x, y, name, dir, file = action
413| self.action = CacherAction(x, y, name)
414| self.project.actions().add_action(self.action)
415| self.action.dir = dir
416| self.action.file = file
417|
418|