Xorn
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Pages
clib.py
Go to the documentation of this file.
1 # xorn.geda - Python library for manipulating gEDA files
2 # Copyright (C) 1998-2010 Ales Hvezda
3 # Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
4 # Copyright (C) 2013-2016 Roland Lutz
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 
20 ## \namespace xorn.geda.clib
21 ## The component library system
22 #
23 # The <b>component library</b> is made up of a number of <b>component
24 # sources</b>, each of which in turn makes available a number of
25 # component <b>symbols</b>. Each source is represented by a Python
26 # object which implements the methods \c list() to retrieve a list of
27 # symbol names, and \c get(symbol) to retrieve the symbol data for a
28 # given symbol name.
29 #
30 # There are two predefined source types: a DirectorySource represents
31 # a directory on disk containing symbol files, and a CommandSource
32 # represents a command in the system \c PATH which can generate gEDA
33 # symbol data (e.g. from a database).
34 #
35 # Each symbol is identified by its name, which is stored in the saved
36 # schematic file. The name must be valid for storage in a gEDA
37 # schematic file as the "basename" of a "component" object. For
38 # symbols from directory sources, the filename of the symbol is taken
39 # as the symbol name. For a command source, the name may be any
40 # permissible string. Guidelines to follow:
41 #
42 # -# Do not begin a symbol name with "EMBEDDED"
43 # -# Do not use whitespace, or any of the characters "/", ":", "!",
44 # "*", or "?".
45 # -# Try to use unique names.
46 #
47 # The component database may be queried using \ref search. A revision
48 # object containing the symbol data may be obtained using \ref
49 # get_symbol. If the source of a symbol isn't known, the symbol data
50 # may be requested using the convenience function \ref lookup_symbol.
51 
52 import collections, fnmatch, os, shlex, stat, subprocess, sys
53 from gettext import gettext as _
54 import xorn.geda.read
55 import xorn.geda.ref
56 import xorn.proxy
57 import xorn.storage
58 
59 ## Decide based on filename whether a file in a directory source is
60 ## considered a symbol.
61 
62 def sym_filename_filter(basename):
63  return basename.lower().endswith('.sym') or \
64  basename.lower().endswith('.sym.xml')
65 
66 ## Named tuple class for storing data about a particular component source.
67 
68 Source = collections.namedtuple('Source', ['callback', 'symbols', 'name'])
69 
70 ## List of source triples for all known component sources.
71 
72 _sources = []
73 
74 ## Cache for search results of \ref search.
75 #
76 # The key of the hashtable is a pair <tt>(pattern, glob)</tt>
77 # describing the search that was carried out, and the value is a list
78 # of pairs <tt>(source, symbol)</tt>.
79 
80 _search_cache = {}
81 
82 ## Symbol data cache.
83 #
84 # The key of the hashtable is a pair <tt>(source, symbol)</tt>, and
85 # the value is the data.
86 
87 _symbol_cache = {}
88 
89 ## Whether to load pixmaps referenced by symbols.
90 #
91 # This should be set before reading any symbols. Otherwise, cached
92 # symbols loaded with the wrong \a load_pixmaps flag may be returned.
93 
94 load_pixmaps = False
95 
96 
97 ## Source object representing a directory of symbol files.
98 #
99 # This class allows a directory which contains one or more symbol
100 # files in gEDA format to be used as a component source. Only files
101 # ending in ".sym" (case insensitive) are considered to be symbol
102 # files. Symbol files with filenames starting with a period "." are
103 # ignored.
104 
106  def __init__(self, directory, recursive):
107  ## Path to directory
108  self.directory = directory
109  ## Whether to recurse into subdirectories.
110  self.recursive = recursive
111 
112  ## Scan the directory for symbols.
113 
114  def list(self):
115  if not self.recursive:
116  # skip subdirectories and anything else that isn't a
117  # regular file (this is what libgeda does)
118  entries = (entry for entry in os.listdir(self.directory)
119  if stat.S_ISREG(
120  os.stat(os.path.join(self.directory, entry)).st_mode))
121  else:
122  entries = (entry for dirpath, dirnames, filenames
123  in sorted(os.walk(self.directory))
124  for entry in sorted(filenames))
125 
126  return (entry for entry in entries
127  # skip hidden files ("." and ".." are excluded by
128  # os.walk but not by os.listdir)
129  if entry[0] != '.'
130  # skip filenames which don't have the right suffix
131  and sym_filename_filter(entry))
132 
133  ## Get symbol data for a given symbol name.
134 
135  def get(self, symbol):
136  if not self.recursive:
137  path = os.path.join(self.directory, symbol)
138  if not os.path.isfile(path): # resolves symlinks
139  path = None
140  else:
141  path = None
142  for dirpath, dirnames, filenames in \
143  sorted(os.walk(self.directory)):
144  if symbol in filenames:
145  path = os.path.join(dirpath, symbol)
146  break
147 
148  if path is not None:
149  return xorn.geda.read.read(path, load_pixmaps = load_pixmaps)
150 
151  raise ValueError, 'symbol "%s" not found in library' % symbol
152 
153 
154 ## Source object representing a pair of symbol-generating commands.
155 #
156 # This class allows a program or pair of programs in the system search
157 # path which can generate symbols to be used as a component source.
158 #
159 # \a list_cmd and \a get_cmd should be pre-tokenized invocations
160 # consisting of an executable name followed by any arguments required.
161 # Executables are resolved using the current \c PATH.
162 #
163 # The list command will be executed with no additional arguments, and
164 # should output a list of available symbol names on the stdandard
165 # output. The get command will have a symbol name appended to it as
166 # the final argument, and should output gEDA symbol data on standard
167 # output.
168 #
169 # If the command cannot successfully complete, it should exit with
170 # non-zero exit status. Anything it has output on stdout will be
171 # ignored, so stderr should be used for diagnostics.
172 
174  def __init__(self, list_cmd, get_cmd):
175  ## Command and arguments for listing available symbols
176  self.list_cmd = list_cmd
177  ## Command and arguments for retrieving symbol data
178  self.get_cmd = get_cmd
179 
180  ## Poll the library command for symbols.
181  #
182  # Runs the library command, requesting a list of available
183  # symbols, and returns the new list.
184 
185  def list(self):
186  # Run the command to get the list of symbols
187  lines = _run_source_command(shlex.split(self.list_cmd),
188  lambda f: f.readlines())
189 
190  for line in lines:
191  if not line or line[-1] != '\n':
192  raise ValueError, "Missing newline at end of command output"
193  if line == '\n':
194  raise ValueError, "Command printed an empty line"
195  if line[0] == '.':
196  raise ValueError, "Command printed line starting with '.'"
197 
198  return (line[:-1] for line in lines)
199 
200  ## Get symbol data for a given symbol name.
201 
202  def get(self, symbol):
203  return _run_source_command(
204  shlex.split(self.get_cmd) + [symbol],
205  lambda f: xorn.geda.read.read_file(
206  f, '<pipe>', load_pixmaps = load_pixmaps))
207 
208 
209 ## Execute a library command.
210 #
211 # Executes the command given by the first item of the argument
212 # sequence \a args, using the system search path to find the program
213 # to execute. Calls the function \a callback with a file-like object
214 # representing the pipe connected to the command's standard output as
215 # a single argument. Once \a callback finishes, discards all data
216 # left on the pipe and waits for the program to terminate.
217 #
218 # \returns the value returned by \a callback
219 #
220 # \throws ValueError if the command returns a non-zero exit status or
221 # is terminated by a signal
222 
223 def _run_source_command(args, callback):
224  p = subprocess.Popen(
225  args, bufsize = 4096, executable = executable,
226  stdout = subprocess.PIPE, close_fds = True) # cwd = virtual_cwd
227 
228  try:
229  return callback(p.stdout)
230  finally:
231  try:
232  p.read() # avoid deadlock
233  finally:
234  p.wait()
235  if p.returncode < 0:
236  raise ValueError, "Library command failed [%s]: " \
237  "Uncaught signal %i" % (args[0], -p.returncode)
238  if p.returncode != 0:
239  raise ValueError, "Library command failed [%s]: "\
240  "returned exit status %d" % (args[0], p.returncode)
241 
242 ## Update list of symbols available from a component source.
243 #
244 # Calls \c source.callback.list() and performs type and uniqueness
245 # checks on the returned list. If no errors are found, replaces the
246 # list of available symbols with the returned list.
247 #
248 # \throws TypeError if \a symbols is not iterable
249 # \throws TypeError if \a symbols yields an item that isn't a string
250 # \throws ValueError if \a symbols yields a duplicate item
251 
252 def _update_symbol_list(source):
253  try:
254  symbols = list(source.callback.list())
255  except TypeError:
256  raise TypeError, "Failed to scan library [%s]: " \
257  "Python function returned non-list" % source.name
258 
259  found = set()
260  for symbol in symbols:
261  if not isinstance(symbol, str) and \
262  not isinstance(symbol, unicode):
263  raise TypeError, "Non-string symbol name " \
264  "while scanning library [%s]" % source.name
265  if symbol in found:
266  raise ValueError, "Duplicate symbol name " \
267  "while scanning library [%s]: %s" % (symbol, source.name)
268  found.add(symbol)
269 
270  symbols.sort()
271  source.symbols[:] = symbols
272 
273 
274 ## Add a component source to the library.
275 #
276 # \a callback must implement two methods: \c callback.list() to return
277 # a list of symbol names, and \c callback.get(symbol) to return the
278 # symbol data for a given a symbol name.
279 #
280 # \param callback source object which implements \c list and \c get
281 # \param name unique descriptive name for the component source
282 #
283 # \throws ValueError if another source with this name already exists
284 
285 def add_source(callback, name):
286  if name is None:
287  raise ValueError, "Cannot add source: name not specified"
288  for source in _sources:
289  if source.name == name:
290  raise ValueError, "There is already a source called '%s'" % name
291 
292  # Sources added later get scanned earlier
293  source = Source(callback, [], name)
294  _update_symbol_list(source)
295  _sources.insert(0, source)
296 
297  _search_cache.clear()
298  _symbol_cache.clear()
299 
300 ## Get a component source by name.
301 #
302 # Iterates through the known component sources, checking if there is a
303 # source with the given \a name.
304 #
305 # \throws ValueError if no matching source was found
306 
307 def lookup_source(name):
308  for source in _sources:
309  if source.name == name:
310  return source
311 
312  raise ValueError
313 
314 ## Make sure a source name is unique.
315 #
316 # Checks if a source with the given \a name already exists. If it
317 # does, appends a number in angular brackets to the source name making
318 # it unique. If \a name is not already in use, returns it as is.
319 
321  i = 0
322  newname = name
323 
324  while True:
325  try:
326  lookup_source(newname)
327  except ValueError:
328  break
329  i += 1
330  newname = '%s<%i>' % (name, i)
331 
332  return newname
333 
334 ## Rescan all available component libraries.
335 #
336 # Resets the list of symbols available from each source, and
337 # repopulates it from scratch. Useful e.g. for checking for new
338 # symbols.
339 
340 def refresh():
341  for source in _sources:
342  _update_symbol_list(source)
343 
344  _search_cache.clear()
345  _symbol_cache.clear()
346 
347 ## Remove all component library sources.
348 
349 def reset():
350  del _sources[:]
351  _search_cache.clear()
352  _symbol_cache.clear()
353 
354 
355 ## Get symbol object for a given source object and symbol name.
356 #
357 # Returns a xorn.geda.ref.Symbol object containing the symbol called
358 # \a symbol from the component source \a source.
359 #
360 # \throws ValueError if the source object's \c get function doesn't
361 # return a xorn.storage.Revision or
362 # xorn.proxy.RevisionProxy instance
363 
364 def get_symbol(source, symbol):
365  assert source is not None
366  assert symbol is not None
367 
368  # First, try the cache.
369  try:
370  return _symbol_cache[id(source), symbol]
371  except KeyError:
372  pass
373 
374  # If the symbol wasn't found in the cache, get it directly.
375  data = source.callback.get(symbol)
376  if isinstance(data, xorn.proxy.RevisionProxy):
377  data = data.rev
378  if not isinstance(data, xorn.storage.Revision):
379  raise ValueError, "Failed to load symbol data [%s] " \
380  "from source [%s]" % (symbol, source.name)
381  data.finalize()
382 
383  symbol_ = xorn.geda.ref.Symbol(symbol, data, False)
384 
385  # Cache the symbol data
386  _symbol_cache[id(source), symbol] = symbol_
387 
388  return symbol_
389 
390 ## Invalidate cached data about a symbol.
391 
392 def invalidate_symbol_data(source, symbol):
393  try:
394  del _symbol_cache[id(source), symbol]
395  except KeyError:
396  pass
397 
398 
399 ## Find all symbols matching a pattern.
400 #
401 # Searches the library, returning all symbols whose names match \a
402 # pattern. If \a glob is \c True, then \a pattern is assumed to be a
403 # glob pattern; otherwise, only exact matches are returned.
404 #
405 # \returns a list of pairs <tt>(source, symbol)</tt>
406 
407 def search(pattern, glob = False):
408  # Check to see if the query is already in the cache
409  try:
410  return _search_cache[pattern, glob]
411  except KeyError:
412  pass
413 
414  result = []
415  for source in _sources:
416  if glob:
417  for symbol in fnmatch.filter(source.symbols, pattern):
418  result.append((source, symbol))
419  elif pattern in source.symbols:
420  result.append((source, pattern))
421 
422  _search_cache[pattern, glob] = result
423  return result[:]
424 
425 ## Get source for a given symbol name.
426 #
427 # Returns the source of the first symbol found with the given \a name.
428 # If more than one matching symbol is found, emits a warning to stderr.
429 #
430 # \throws ValueError if the component was not found
431 
433  symlist = search(name)
434 
435  if not symlist:
436  raise ValueError, "Component [%s] was not found in the " \
437  "component library" % name
438 
439  if len(symlist) > 1:
440  sys.stderr.write(_("More than one component found "
441  "with name [%s]\n")% name)
442 
443  source, symbol = symlist[0]
444  assert symbol == name
445  return source
446 
447 ## Get symbol object for a given symbol name.
448 #
449 # Returns the xorn.geda.ref.Symbol object for the first symbol found
450 # with the given name. This is a helper function for the schematic
451 # load system, as it will always want to load symbols given only their
452 # name.
453 #
454 # \throws ValueError if the component was not found
455 
456 def lookup_symbol(name):
457  return get_symbol(lookup_symbol_source(name), name)
458 
459 
460 ## Return a list of symbols used in a revision.
461 #
462 # The list is free of duplicates and preserves the order of the
463 # symbols as they appear first in the file. Each symbol is
464 # represented by its actual xorn.geda.ref.Symbol object.
465 
466 def used_symbols0(rev):
467  symbols = []
468  for ob in rev.all_objects():
469  data = ob.data()
470  if isinstance(data, xorn.storage.Component) \
471  and data.symbol not in symbols:
472  symbols.append(data.symbol)
473  return symbols
474 
475 ## Return a list of symbols used in a revision.
476 #
477 # Scan a revision looking for symbols, look them up in the library,
478 # and return them as a list. Each symbol is represented by a pair
479 # <tt>(source, symbol)</tt>. The returned list only contains symbols
480 # that were found in the library and is sorted in alphabetical order.
481 #
482 # \bug Only includes components which are not embedded, but they
483 # should (probably) also appear in the list.
484 
485 def used_symbols1(rev):
486  result = []
487 
488  for ob in rev.all_objects():
489  data = ob.data()
490  if not isinstance(data, xorn.storage.Component):
491  continue
492 
493  # ignore embedded symbols
494  if data.symbol.embedded:
495  continue
496 
497  # Since we're not looking at embedded symbols, the first
498  # component with the given name will be the one we need.
499  # N.b. we don't use lookup_symbol_source() because it's
500  # spammeh.
501  symlist = search(data.symbol.basename)
502  if not symlist:
503  continue
504  result.append(symlist[0])
505 
506  result.sort(key = lambda (source, symbol): symbol)
507  return result
def list
Scan the directory for symbols.
Definition: clib.py:114
Reading schematic/symbol files.
Definition: read.py:1
Source object representing a directory of symbol files.
Definition: clib.py:105
def used_symbols0
Return a list of symbols used in a revision.
Definition: clib.py:466
High-level revision proxy class.
Definition: proxy.py:24
def lookup_symbol_source
Get source for a given symbol name.
Definition: clib.py:432
def reset
Remove all component library sources.
Definition: clib.py:349
Source object representing a pair of symbol-generating commands.
Definition: clib.py:173
def get_symbol
Get symbol object for a given source object and symbol name.
Definition: clib.py:364
def read
Read a symbol or schematic file.
Definition: read.py:71
def lookup_source
Get a component source by name.
Definition: clib.py:307
def sym_filename_filter
Decide based on filename whether a file in a directory source is considered a symbol.
Definition: clib.py:62
def invalidate_symbol_data
Invalidate cached data about a symbol.
Definition: clib.py:392
Schematic component.
Definition: storage.py:527
def uniquify_source_name
Make sure a source name is unique.
Definition: clib.py:320
High-level proxy classes for the storage backend.
Definition: proxy.py:1
A particular state of the contents of a file.
Definition: storage.py:35
Referenced symbols and pixmaps.
Definition: ref.py:1
directory
Path to directory.
Definition: clib.py:108
def get
Get symbol data for a given symbol name.
Definition: clib.py:135
def list
Poll the library command for symbols.
Definition: clib.py:185
def add_source
Add a component source to the library.
Definition: clib.py:285
Xorn storage backend.
Definition: storage.py:1
def search
Find all symbols matching a pattern.
Definition: clib.py:407
def used_symbols1
Return a list of symbols used in a revision.
Definition: clib.py:485
def lookup_symbol
Get symbol object for a given symbol name.
Definition: clib.py:456
recursive
Whether to recurse into subdirectories.
Definition: clib.py:110
list_cmd
Command and arguments for listing available symbols.
Definition: clib.py:176
def get
Get symbol data for a given symbol name.
Definition: clib.py:202
tuple Source
Named tuple class for storing data about a particular component source.
Definition: clib.py:68
get_cmd
Command and arguments for retrieving symbol data.
Definition: clib.py:178
def read_file
Read a symbol or schematic file from a file object.
Definition: read.py:105
def refresh
Rescan all available component libraries.
Definition: clib.py:340