Merge gnetlist fixes from branch 'xorn'
[geda-gaf.git] / xorn / src / command / gnetlist.in
blob58322405ae1a425d0a03925eeeabfb3e3ac2aa47
1 #!/usr/bin/env python2.7
2 # gnetlist - wrapper script for invoking the gEDA netlister
3 # Copyright (C) 1998-2010 Ales Hvezda
4 # Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
5 # Copyright (C) 2013-2020 Roland Lutz
7 # This program is free software; you can redistribute it and/or modify
8 # it 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.
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
15 # GNU General Public License for more details.
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 Foundation,
19 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 ## \file gnetlist.in
22 ## Wrapper script for invoking the gEDA netlister.
24 # <tt>xorn netlist</tt> doesn't read the old-style Guile configuration
25 # files \c gafrc and \c gnetlistrc and doesn't support the
26 # Guile-specific command line options \c -l, \c -m, and \c -c.
27 # This wrapper script runs the configuration files, detects the
28 # Guile-specific command line options and translates them into a
29 # <tt>xorn netlist</tt> invocation and can be used as a drop-in
30 # replacement for the old <tt>gnetlist</tt> binary in most situations.
32 # It does *not* read the new-style configuration file \c geda.conf,
33 # and it does *not* support Guile functions as a symbol library.
35 pyexecdir = '@pyexecdir@'
36 XORN = '@XORN@'
37 gedadatadir = '@gedadatadir@'
38 gedasysconfdir = '@gedasysconfdir@'
39 backenddir = '@backenddir@'
41 import getopt, gettext, os, sys
42 from gettext import gettext as _
43 sys.path.insert(0, pyexecdir)
44 import xorn.command
45 import xorn.config
46 import xorn.guile
48 APPEND, PREPEND = xrange(2)
51 ## Expand environment variables in a string.
53 # This function returns the passed string with environment variables
54 # expanded.
56 # The invocations of environment variables MUST be in the form \c
57 # '${variable_name}'; \c '$variable_name' is not valid here.
58 # Environment variable names can consist solely of letters, digits and
59 # \c '_'.  It is possible to escape a \c '$' character in the string
60 # as \c '$$'.
62 # Prints error messages to stderr and leaves the malformed and bad
63 # variable names in the returned string.
65 def expand_env_variables(s):
66     result = ''
67     i = 0
69     while True:
70         try:
71             # look for next variable name
72             j = s.index('$', i)
73         except ValueError:
74             # no variable left
75             result += s[i:]
76             return result
78         result += s[i:j]
80         if j + 1 >= len(s):     # '$' is the last character in the string
81             result += '$'
82             return result
83         if s[j + 1] == '$':     # an escaped '$'
84             result += s[j + 1]
85             i = j + 2
86             continue
87         if s[j + 1] != '{':     # an isolated '$', put it in output
88             result += '$'
89             i = j + 1
90             continue
92         # discard "${"
93         i = j + 2
95         # look for the end of the variable name
96         try:
97             j = s.index('}', i)
98         except ValueError:
99             # problem: no closing '}' to variable
100             sys.stderr.write(_("Found malformed environment variable "
101                                "in '%s'\n") % s)
102             result += s[i - 2:]  # include "${"
103             return result
105         # test characters of variable name
106         bad_characters = [ch for ch in s[i:j]
107                           if not ch.isalnum() and ch != '_']
108         if bad_characters:
109             # illegal character detected in variable name
110             sys.stderr.write(_("Found bad character(s) [%s] "
111                                "in variable name.\n") % ''.join(bad_characters))
112             result += s[i - 2:j + 1]  # include "${" and "}"
113             i = j + 1
114             continue
116         # extract variable name from string and expand it
117         try:
118             result += os.environ[s[i:j]]
119         except KeyError:
120             pass
121         i = j + 1
123 ## Helper function for checking the type of an API function argument.
125 # When constructing an error message, \a fun and \a i are used to
126 # indicate the API function and argument index, respectively.
128 # \throws TypeError if \a arg is not an instance of \a t or of a
129 #                   subclass thereof
131 def check_argument_type(fun, i, arg, t):
132     if not isinstance(arg, t):
133         raise TypeError, '"%s" argument %d must be %s, not %s' % (
134             fun, i, t.__name__, arg.__class__.__name__)
136 ## Helper function for checking whether an API function argument is
137 ## callable.
139 # When constructing an error message, \a fun and \a i are used to
140 # indicate the API function and argument index, respectively.
142 # \throws TypeError if \a arg is not callable
144 def _check_argument_callable(fun, i, arg):
145     if not callable(arg):
146         raise TypeError, "'%s' argument %d must be callable, " \
147                          "but '%s' object is not" % (
148             fun, i, arg.__class__.__name__)
150 # ========================== Configuration options ===========================
152 # general gafrc options
153 symbol_library = []
154 source_library = []
156 # general gafrc options which are not relevant for netlisting
157 bitmap_directory = False
158 bus_ripper_symname = False
159 attribute_promotion = False
160 promote_invisible = False
161 keep_invisible = False
162 always_promote_attributes = False
163 make_backup_files = False
165 # gnetlistrc options
166 hierarchy_refdes_mangle = True
167 hierarchy_netname_mangle = True
168 hierarchy_netattrib_mangle = True
169 hierarchy_refdes_separator = '/'
170 hierarchy_netname_separator = '/'
171 hierarchy_netattrib_separator = '/'
172 hierarchy_refdes_order = APPEND
173 hierarchy_netname_order = APPEND
174 hierarchy_netattrib_order = APPEND
176 # deprecated gnetlistrc options
177 prefer_netname_attribute = False
178 default_net_name = 'unnamed_net'
179 default_bus_name = 'unnamed_bus'
180 traverse_hierarchy = True
182 # -------------------------- RC functions: general ---------------------------
184 ## Add a directory to the Guile load path.
186 # Prepends \a path to the Guile system \c '%load-path', after
187 # expanding environment variables.
189 # \param [in] path  path to be added
191 # \returns \c True
193 def rc_scheme_directory(path):
194     check_argument_type('scheme-directory', 1, path, basestring)
196     # take care of any shell variables
197     path = expand_env_variables(path)
199     load_path = list(xorn.guile.lookup('%load-path'))
200     load_path.insert(0, path)
201     xorn.guile.define('%load-path', load_path)
203     return True
205 # \returns \c True
207 def rc_world_size(width, height, border):
208     return True  # not implemented
210 def rc_print_color_map(map = None):
211     if map is None:
212         raise NotImplementedError
214 def rc_gnetlist_version(version):
215     check_argument_type('gnetlist-version', 1, version, basestring)
216     return False  # this is Xorn, so the versions never match
218 # ------------------------- RC functions: libraries --------------------------
220 # \param [in] name  optional descriptive name for library directory
221 #                   \e (ignored)
223 # \returns \c True on success, \c False otherwise
225 def rc_component_library(path, name = None):
226     check_argument_type('component-library', 1, path, basestring)
227     if name is not None:
228         check_argument_type('component-library', 2, name, basestring)
230     # take care of any shell variables
231     path = expand_env_variables(path)
233     # invalid path?
234     if not os.path.isdir(path):
235         sys.stderr.write(
236             _("Invalid path [%s] passed to component-library\n") % path)
237         return False
239     if os.path.isabs(path):
240         symbol_library.extend(['--symbol-library', path])
241     else:
242         symbol_library.extend(['--symbol-library',
243                                os.path.join(os.getcwd(), path)])
245     return True
247 # \param [in] prefix  optional prefix for library names \e (ignored)
249 # \returns \c True on success, \c False otherwise
251 def rc_component_library_search(path, prefix = None):
252     check_argument_type('component-library-search', 1, path, basestring)
253     if prefix is not None:
254         check_argument_type('component-library-search', 2, prefix, basestring)
256     # take care of any shell variables
257     path = expand_env_variables(path)
259     # invalid path?
260     if not os.path.isdir(path):
261         sys.stderr.write(
262             _("Invalid path [%s] passed to component-library-search\n") % path)
263         return False
265     if os.path.isabs(path):
266         symbol_library.extend(['--symbol-library-search', path])
267     else:
268         symbol_library.extend(['--symbol-library-search',
269                                os.path.join(os.getcwd(), path)])
271     return True
273 ## Add a library command.
275 # Add a command to the component library.
277 # \param [in] listcmd  command to get a list of symbols
278 # \param [in] getcmd   command to get a symbol from the library
279 # \param [in] name     optional descriptive name for component source
281 # \returns \c True
283 def rc_component_library_command(listcmd, getcmd, name):
284     check_argument_type('component-library-command', 1, listcmd, basestring)
285     check_argument_type('component-library-command', 2, getcmd, basestring)
286     check_argument_type('component-library-command', 3, name, basestring)
288     # take care of any shell variables
289     # \bug this may be a security risk!
290     listcmd = expand_env_variables(listcmd)
291     getcmd = expand_env_variables(getcmd)
293     symbol_library.extend(['--symbol-library-command',
294                            '%s:%s:%s' % (listcmd, getcmd, name)])
295     return True
297 ## Add a library function.
299 # Add a set of Guile procedures for listing and generating symbols.
301 # \param [in] listfunc  a procedure which takes no arguments and
302 #                       returns a list of component names
303 # \param [in] getfunc   a procedure which takes a component name as an
304 #                       argument and returns a symbol encoded in a
305 #                       string in gEDA format, or \c False if the
306 #                       component name is unknown
307 # \param [in] name      a descriptive name for this component source
309 # \returns \c True on success, \c False otherwise
311 # \warning This function is not implemented.
313 def rc_component_library_funcs(listfunc, getfunc, name):
314     check_argument_callable('component-library-funcs', 1, listfunc)
315     check_argument_callable('component-library-funcs', 2, getfunc)
316     check_argument_type('component-library-funcs', 3, name, basestring)
318     raise NotImplementedError
320 # \returns \c True
322 def rc_reset_component_library():
323     del symbol_library[:]
324     return True
326 # \returns \c True on success, \c False otherwise
328 def rc_source_library(path):
329     check_argument_type('source-library', 1, path, basestring)
331     # take care of any shell variables
332     path = expand_env_variables(path)
334     # invalid path?
335     if not os.path.isdir(path):
336         sys.stderr.write(
337             _("Invalid path [%s] passed to source-library\n") % path)
338         return False
340     if os.path.isabs(path):
341         source_library.extend(['--source-library', path])
342     else:
343         source_library.extend(['--source-library',
344                                os.path.join(os.getcwd(), path)])
346     return True
348 # \returns \c True on success, \c False otherwise
350 def rc_source_library_search(path):
351     check_argument_type('source-library-search', 1, path, basestring)
353     # take care of any shell variables
354     path = expand_env_variables(path)
356     # invalid path?
357     if not os.path.isdir(path):
358         sys.stderr.write(
359             _("Invalid path [%s] passed to source-library-search\n") % path)
360         return False
362     if os.path.isabs(path):
363         source_library.extend(['--source-library-search', path])
364     else:
365         source_library.extend(['--source-library-search',
366                                os.path.join(os.getcwd(), path)])
368     return True
370 # \returns \c True
372 def rc_reset_source_library():
373     del source_library[:]
374     return True
376 # ----------------------------------------------------------------------------
378 # \returns \c True on success, \c False otherwise
380 def rc_bitmap_directory(path):
381     check_argument_type('bitmap-directory', 1, path, basestring)
383     # take care of any shell variables
384     path = expand_env_variables(path)
386     # invalid path?
387     if not os.path.isdir(path):
388         sys.stderr.write(
389             _("Invalid path [%s] passed to bitmap-directory\n") % path)
390         return False
392     global bitmap_directory
393     bitmap_directory = path
395     return True
397 # \returns \c True
399 def rc_bus_ripper_symname(symname):
400     check_argument_type('bus-ripper-symname', 1, symname, basestring)
402     global bus_ripper_symname
403     bus_ripper_symname = symname
405     return True
407 def rc_attribute_promotion(mode):
408     check_argument_type('attribute-promotion', 1, mode, basestring)
409     try:
410         global attribute_promotion
411         attribute_promotion = { 'enabled': True, 'disabled': False }[mode]
412         return True
413     except KeyError:
414         sys.stderr.write(
415             _("Invalid mode [%s] passed to attribute-promotion\n") % mode)
416         return False
418 def rc_promote_invisible(mode):
419     check_argument_type('promote-invisible', 1, mode, basestring)
420     try:
421         global promote_invisible
422         promote_invisible = { 'enabled': True, 'disabled': False }[mode]
423         return True
424     except KeyError:
425         sys.stderr.write(
426             _("Invalid mode [%s] passed to promote-invisible\n") % mode)
427         return False
429 def rc_keep_invisible(mode):
430     check_argument_type('keep-invisible', 1, mode, basestring)
431     try:
432         global keep_invisible
433         keep_invisible = { 'enabled': True, 'disabled': False }[mode]
434         return True
435     except KeyError:
436         sys.stderr.write(
437             _("Invalid mode [%s] passed to keep-invisible\n") % mode)
438         return False
440 # \returns \c True
442 def rc_always_promote_attributes(attrlist):
443     global always_promote_attributes
445     if isinstance(attrlist, basestring):
446         sys.stderr.write(
447             _("WARNING: using a string for 'always-promote-attributes' is "
448               "deprecated. Use a list of strings instead\n"))
450         # convert the space separated strings into a list
451         always_promote_attributes = [
452             attr for attr in attrlist.split(' ') if attr]
453     else:
454         check_argument_type('always-promote-attributes', 1, attrlist, tuple)
456         for i, attr in enumerate(attrlist):
457             if not isinstance(attr, basestring):
458                 raise TypeError, "'always-promote-attributes' argument 1 " \
459                                  "index %d must be basestring, not %s" % (
460                     i, attr.__class__.__name__)
462         always_promote_attributes = list(attrlist)
464     return True
466 ## Enable the creation of backup files when saving.
468 # If enabled then a backup file, of the form \c "example.sch~", is
469 # created when saving a file.
471 # \param [in] mode  \c "enabled" or \c "disabled"
473 # \returns \c False if \a mode is not a valid mode, \c True if it is
475 def rc_make_backup_files(mode):
476     check_argument_type('make-backup-files', 1, mode, basestring)
477     try:
478         global make_backup_files
479         make_backup_files = { 'enabled': True, 'disabled': False }[mode]
480         return True
481     except KeyError:
482         sys.stderr.write(
483             _("Invalid mode [%s] passed to make-backup-files\n") % mode)
484         return False
486 # ----------------------------------------------------------------------------
488 def rc_hierarchy_uref_mangle(mode):
489     check_argument_type('hierarchy-uref-mangle', 1, mode, basestring)
490     try:
491         global hierarchy_refdes_mangle
492         hierarchy_refdes_mangle = { 'enabled': True, 'disabled': False }[mode]
493         return True
494     except KeyError:
495         sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
496                            % (mode, 'hierarchy-uref-mangle'))
497         return False
499 def rc_hierarchy_netname_mangle(mode):
500     check_argument_type('hierarchy-netname-mangle', 1, mode, basestring)
501     try:
502         global hierarchy_netname_mangle
503         hierarchy_netname_mangle = { 'enabled': True, 'disabled': False }[mode]
504         return True
505     except KeyError:
506         sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
507                            % (mode, 'hierarchy-netname-mangle'))
508         return False
510 def rc_hierarchy_netattrib_mangle(mode):
511     check_argument_type('hierarchy-netattrib-mangle', 1, mode, basestring)
512     try:
513         global hierarchy_netattrib_mangle
514         hierarchy_netattrib_mangle = {
515             'enabled': True, 'disabled': False }[mode]
516         return True
517     except KeyError:
518         sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
519                            % (mode, 'hierarchy-netattrib-mangle'))
520         return False
522 def rc_hierarchy_uref_separator(name):
523     check_argument_type('hierarchy-uref-separator', 1, name, basestring)
525     global hierarchy_refdes_separator
526     hierarchy_refdes_separator = name
528     return True
530 def rc_hierarchy_netname_separator(name):
531     check_argument_type('hierarchy-netname-separator', 1, name, basestring)
533     global hierarchy_netname_separator
534     hierarchy_netname_separator = name
536     return True
538 def rc_hierarchy_netattrib_separator(name):
539     check_argument_type('hierarchy-netattrib-separator', 1, name, basestring)
541     global hierarchy_netattrib_separator
542     hierarchy_netattrib_separator = name
544     return True
546 def rc_hierarchy_netattrib_order(mode):
547     check_argument_type('hierarchy-netattrib-order', 1, mode, basestring)
548     try:
549         global hierarchy_netattrib_order
550         hierarchy_netattrib_order = {
551             'prepend': PREPEND, 'append': APPEND }[mode]
552         return True
553     except KeyError:
554         sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
555                            % (mode, 'hierarchy-netattrib-order'))
556         return False
558 def rc_hierarchy_netname_order(mode):
559     check_argument_type('hierarchy-netname-order', 1, mode, basestring)
560     try:
561         global hierarchy_netname_order
562         hierarchy_netname_order = {
563             'prepend': PREPEND, 'append': APPEND }[mode]
564         return True
565     except KeyError:
566         sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
567                            % (mode, 'hierarchy-netname-order'))
568         return False
570 def rc_hierarchy_uref_order(mode):
571     check_argument_type('hierarchy-uref-order', 1, mode, basestring)
572     try:
573         global hierarchy_refdes_order
574         hierarchy_refdes_order = { 'prepend': PREPEND, 'append': APPEND }[mode]
575         return True
576     except KeyError:
577         sys.stderr.write(_("Invalid mode [%s] passed to %s\n")
578                            % (mode, 'hierarchy-uref-order'))
579         return False
581 # ----------------------------------------------------------------------------
583 def rc_unnamed_netname(name):
584     check_argument_type('unnamed-netname', 1, name, basestring)
585     global default_net_name
586     default_net_name = name
588 def rc_unnamed_busname(name):
589     check_argument_type('unnamed-busname', 1, name, basestring)
590     global default_bus_name
591     default_bus_name = name
593 def rc_hierarchy_traversal(mode):
594     check_argument_type('hierarchy-traversal', 1, mode, basestring)
595     global traverse_hierarchy
596     traverse_hierarchy = mode == 'enabled'
598 def rc_net_naming_priority(mode):
599     check_argument_type('net-naming-priority', 1, mode, basestring)
600     global prefer_netname_attribute
601     prefer_netname_attribute = mode == 'netname'
603 # ============================================================================
605 ## Path to gEDA data shared between all users.
607 # If the \c GEDADATA environment variable is set, this value is used;
608 # otherwise, the configured path is used.
610 if 'GEDADATA' in os.environ and os.environ['GEDADATA']:
611     sys_data_path = os.environ['GEDADATA']
612 else:
613     sys_data_path = gedadatadir
615 ## Path to gEDA configuration shared between all users.
617 # If the \c GEDADATARC environment variable is set, this value is
618 # used; otherwise, the configured path is used.  Finally fallback to
619 # using the system data path.
621 if 'GEDADATARC' in os.environ and os.environ['GEDADATARC']:
622     sys_config_path = os.environ['GEDADATARC']
623 elif gedasysconfdir:
624     sys_config_path = gedasysconfdir
625 else:
626     sys_config_path = sys_data_path
628 ## Directory with the gEDA user configuration.
630 # Path to be searched for the current user's gEDA configuration.
631 # Currently defaults to a directory \c ".gEDA" in the user's home
632 # directory.
634 user_config_path = os.path.join(os.environ['HOME'], '.gEDA')
636 # ----------------------------------------------------------------------------
638 ## Implement the following functions to add support for geda.conf files.
640 def eda_config_is_loaded(context):
641     return False
643 def eda_config_load(context):
644     return False
646 def eda_config_get_filename(context):
647     return False
649 def eda_config_scm_from_config(context):
650     return False
652 def eda_config_get_system_context():
653     return False
655 def eda_config_get_user_context():
656     return False
658 def eda_config_get_context_for_path(path):
659     return False
661 def eda_config_get_context_for_file(path):
662     return False
664 # ----------------------------------------------------------------------------
666 ## Load an RC file.
668 # Loads and runs the Scheme initialisation file \a name.
670 # \param name     filename of the RC file to load
671 # \param context  configuration context to use while loading
673 def rc1_parse_file(name, context):
674     # skip missing files, directories etc.
675     if not os.path.isfile(name):
676         return
678     # If the configuration wasn't loaded yet, attempt to load it.
679     # Config loading is on a best-effort basis; if we fail, just print
680     # a warning.
682     if not eda_config_is_loaded(context):
683         eda_config_load(context)
685     # Normalizing filenames is hard to do right; don't even try it.
686     #name = f_normalize_filename(name)
688     if '"' in name or '\\' in name:
689         sys.stderr.write(
690             _("ERROR: Illegal character in RC file name: %s\n") % name)
691         sys.exit(1)
693     # Attempt to load the RC file, if it hasn't been loaded already.
694     try:
695         xorn.guile.eval_string(
696             '(with-fluids'
697             ' ((rc-filename-fluid "%s")'
698             '  (rc-config-fluid (eda-config-scm-from-config %s)))'
699             ' (primitive-load "%s"))' % (name, '#f', name))
700     except xorn.guile.GuileError:
701         sys.stderr.write(_("Failed to load RC file [%s]\n") % name)
702         sys.exit(1)
704     #sys.stderr.write(_("Loaded RC file [%s]\n") % name)
706 ## Return a list of RC file names and associated configuration
707 ## contexts.
709 # Returns a list of pairs <tt>(name, context)</tt> for each Scheme
710 # initialisation files to process: system, user and local (current
711 # working directory), first with the default \c "gafrc" basename and
712 # then with the basename \a rcname, if \a rcname is not \c None;
713 # additionally, \a rcfile if \a rcfile is not \c None.
715 # \param rcname  RC file basename, or \c None
716 # \param rcfile  specific RC file path, or \c None
718 def rc1_files(rcname, rcfile):
719     # Load RC files in order.
720     result = [
721         (os.path.join(sys_config_path, 'system-gafrc'),
722          eda_config_get_system_context()),
723         (os.path.join(user_config_path, 'gafrc'),
724          eda_config_get_user_context()),
725         ('gafrc', eda_config_get_context_for_path('gafrc'))]
727     # Next application-specific rcname.
728     if rcname is not None:
729         result += [
730             (os.path.join(sys_config_path, 'system-' + rcname),
731              eda_config_get_system_context()),
732             (os.path.join(user_config_path, rcname),
733              eda_config_get_user_context()),
734             (rcname, eda_config_get_context_for_path(rcname))]
736     # Finally, optional additional RC file.  Specifically use the
737     # current working directory's configuration context here, no matter
738     # where the rc file is located on disk.
739     if rcfile is not None:
740         result.append((rcfile, eda_config_get_context_for_file(False)))
742     return result
744 ## General RC file parsing function.
746 # Attempt to load and run system, user and local (current working
747 # directory) Scheme initialisation files, first with the default \c
748 # "gafrc" basename and then with the basename \a rcname, if \a rcname
749 # is not \c None.  Additionally, attempt to load and run \a rcfile if
750 # \a rcfile is not \c None.
752 # \param rcname  RC file basename, or \c None
753 # \param rcfile  specific RC file path, or \c None
755 def rc1_parse(rcname, rcfile):
756     for name, context in rc1_files(rcname, rcfile):
757         rc1_parse_file(name, context)
759         # Re-export function component-library-search (this is
760         # overwritten by geda.scm)
761         xorn.guile.define('component-library-search',
762                           rc_component_library_search)
764 # ----------------------------------------------------------------------------
766 for name, value in {
767         'scheme-directory': rc_scheme_directory,
768         'world-size': rc_world_size,
769         'print-color-map': rc_print_color_map,
770         'gnetlist-version': rc_gnetlist_version,
772         'component-library': rc_component_library,
773         'component-library-search': rc_component_library_search,
774         'component-library-command': rc_component_library_command,
775         'component-library-funcs': rc_component_library_funcs,
776         'reset-component-library': rc_reset_component_library,
777         'source-library': rc_source_library,
778         'source-library-search': rc_source_library_search,
779         'reset-source-library': rc_reset_source_library,
781         'bitmap-directory': rc_bitmap_directory,
782         'bus-ripper-symname': rc_bus_ripper_symname,
783         'attribute-promotion': rc_attribute_promotion,
784         'promote-invisible': rc_promote_invisible,
785         'keep-invisible': rc_keep_invisible,
786         'always-promote-attributes': rc_always_promote_attributes,
787         'make-backup-files': rc_make_backup_files,
789         'hierarchy-uref-mangle': rc_hierarchy_uref_mangle,
790         'hierarchy-netname-mangle': rc_hierarchy_netname_mangle,
791         'hierarchy-netattrib-mangle': rc_hierarchy_netattrib_mangle,
792         'hierarchy-uref-separator': rc_hierarchy_uref_separator,
793         'hierarchy-netname-separator': rc_hierarchy_netname_separator,
794         'hierarchy-netattrib-separator': rc_hierarchy_netattrib_separator,
795         'hierarchy-netattrib-order': rc_hierarchy_netattrib_order,
796         'hierarchy-netname-order': rc_hierarchy_netname_order,
797         'hierarchy-uref-order': rc_hierarchy_uref_order,
799         'unnamed-netname': rc_unnamed_netname,
800         'unnamed-busname': rc_unnamed_busname,
801         'hierarchy-traversal': rc_hierarchy_traversal,
802         'net-naming-priority': rc_net_naming_priority,
804         'eda-config-is-loaded': eda_config_is_loaded,
805         'eda-config-load': eda_config_load,
806         'eda-config-get-filename': eda_config_get_filename,
807         'eda-config-scm-from-config': eda_config_scm_from_config,
808         'eda-config-get-system-context': eda_config_get_system_context,
809         'eda-config-get-user-context': eda_config_get_user_context,
810         'eda-config-get-context-for-path': eda_config_get_context_for_path,
811         'eda-config-get-context-for-file': eda_config_get_context_for_file,
812     }.iteritems():
813     xorn.guile.define(name, value)
815 xorn.guile.eval_string('''
816 (define rc-filename-fluid (make-unbound-fluid))
817 (define rc-config-fluid (make-unbound-fluid))
819 ;;; Get the filename of the RC file being evaluated.
821 ;;; \returns the full path to the RC file if the interpreter can
822 ;;;          resolve the filename, otherwise throws an error
824 (define (rc-filename) (fluid-ref rc-filename-fluid))
826 ;;; Get a configuration context for the current RC file.
828 ;;; Returns the configuration context applicable to the RC file being
829 ;;; evaluated.  This function is intended to support gEDA transition
830 ;;; from functions in RC files to static configuration files.
832 ;;; \returns an EdaConfig smob
834 (define (rc-config) (fluid-ref rc-config-fluid))
835 ''')
837 # ----------------------------------------------------------------------------
839 ## Get a sorted list of available backends.
841 # Returns a list of available netlister backends by searching for
842 # files in each of the directories in the current Guile \c %load-path.
843 # A file is considered to be a netlister backend if its name begins
844 # with \c "gnet-" and ends with \c ".scm".
846 def list_backends():
847     # Look up the current Guile %load-path
848     load_path = xorn.guile.lookup('%load-path')
850     backend_names = set()
852     # add-to-load-path adds paths twice (once when it is compiled and
853     # once when it is run), so we need to filter out duplicate paths.
854     visited_dirnames = set()
856     for dir_name in load_path:
857         if dir_name in visited_dirnames:
858             continue
859         visited_dirnames.add(dir_name)
861         try:
862             d_names = os.listdir(dir_name)
863         except OSError as e:
864             #sys.stderr.write(_("Can't open directory %s: %s\n")
865             #                 % (dir_name, e.strerror))
866             continue
868         for d_name in d_names:
869             # Check that filename has the right format to be a backend
870             if d_name.startswith('gnet-') and d_name.endswith('.scm'):
871                 # Remove prefix & suffix.  Add to list of backend names.
872                 backend_names.add(d_name[5:-4])
874     # Sort the list of backends
875     return sorted(backend_names)
877 def main():
878     gettext.bindtextdomain(xorn.config.PACKAGE, xorn.config.localedir)
879     gettext.textdomain(xorn.config.PACKAGE)
881     try:
882         options, args = getopt.getopt(
883             xorn.command.args, 'c:g:hil:L:m:o:O:p:qvV', [
884                 'quiet', 'verbose', 'interactive', 'report-gui',
885                 'list-backends', 'help', 'version'])
886     except getopt.GetoptError as e:
887         xorn.command.invalid_arguments(e.msg)
889     quiet_mode = False
890     verbose_mode = False
891     output_filename = 'output.net'
892     guile_proc = None           # Guile netlist backend to use
893     python_backend = None       # Python netlist backend to use
894     interactive_mode = False    # enter interactive Scheme REPL after loading
895     backend_options = []        # option strings passed to backend
897     evaluate_at_startup = []
898     extra_load_paths = [os.path.join(sys_data_path, 'scheme')]
899     load_before_backend = []
900     load_after_backend = []
902     report_gui = False
903     list_backends_ = False
905     for option, value in options:
906         if option == '-q' or option == '--quiet':
907             quiet_mode = True
908         elif option == '-v' or option == '--verbose':
909             verbose_mode = True
910         elif option == '-o':
911             output_filename = value
912         elif option == '-L':
913             # Argument is a directory to add to the Scheme load path
914             extra_load_paths.append(value)
915         elif option == '-g':
916             guile_proc = value
917         elif option == '-p':
918             python_backend = value.replace('-', '_')
919         elif option == '-O':
920             backend_options.append(value)
921         elif option == '-l':
922             # Argument is filename of a Scheme script to be run before
923             # loading gnetlist backend
924             load_before_backend.append(value)
925         elif option == '-m':
926             # Argument is filename of a Scheme script to be run after
927             # loading gnetlist backend
928             load_after_backend.append(value)
929         elif option == '-c':
930             # Evaluate Scheme expression at startup
931             evaluate_at_startup.append(value)
932         elif option == '-i' or option == '--interactive':
933             interactive_mode = True
934         elif option == '--report-gui':
935             report_gui = True
936         elif option == '--list-backends':
937             list_backends_ = True
939         elif option == '-h' or option == '--help':
940             sys.stdout.write('''\
941 Usage: %s [OPTION]... -g BACKEND [--] FILE...
942        %s [OPTION]... -i [--] FILE...
943 Generate a netlist from one or more gEDA schematic files.
945   -q                    quiet mode
946   -v, --verbose         verbose mode
947   -o FILE               filename for netlist data output
948   -L DIR                add DIR to Python and Scheme search path
949   -p BACKEND            specify Python netlist backend to use
950   -g BACKEND            specify Scheme netlist backend to use
951   -O STRING             pass an option string to backend
952   -l FILE               load Scheme file before loading backend
953   -m FILE               load Scheme file after loading backend
954   -c EXPR               evaluate Scheme expression at startup
955   -i                    enter interactive Python interpreter after loading
956       --report-gui      report warnings and errors in GUI dialog
957       --list-backends   print a list of available netlist backends
958   -h, --help            help; this message
959   -V, --version         show version information
960   --                    treat all remaining arguments as filenames
962 Report %s bugs to %s
963 gEDA/gaf homepage: http://www.geda-project.org/
964 ''' % (xorn.command.program_name, xorn.command.program_name,
965        xorn.config.PACKAGE_NAME, xorn.config.PACKAGE_BUGREPORT))
966             return
968         elif option == '-V' or option == '--version':
969             sys.stdout.write('''\
970 gnetlist - gEDA netlister (%s)
971 Copyright (C) 1998-2020 gEDA developers
973 This program is free software; you can redistribute it and/or
974 modify it under the terms of the GNU General Public License
975 as published by the Free Software Foundation; either version 2
976 of the License, or (at your option) any later version.
978 This program is distributed in the hope that it will be useful,
979 but WITHOUT ANY WARRANTY; without even the implied warranty of
980 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
981 GNU General Public License for more details.
982 ''' % xorn.config.PACKAGE_STRING)
983             return
985     if guile_proc is not None and python_backend is not None:
986         sys.stderr.write(
987             _("Options `-g BACKEND' and `-p BACKEND' are mutually exclusive\n"))
988         sys.exit(1)
990     if guile_proc is None and python_backend is None \
991             and not interactive_mode and not list_backends_:
992         sys.stderr.write(
993             _("You gave neither backend to execute nor interactive mode!\n"))
994         sys.exit(1)
996     # --------------------------------------------------------------------
998     # 1. Evaluate expressions passed as `-c' options
999     for value in evaluate_at_startup:
1000         try:
1001             xorn.guile.eval_string(value)
1002         except xorn.guile.GuileError:
1003             sys.stderr.write(_("An error occurred while evaluating a "
1004                                "`-c' option:\n%s\n") % value)
1005             sys.exit(1)
1007     # 2. Add paths passed as `-L' options to the Guile load path.
1008     for extra_load_path in extra_load_paths:
1009         if '"' in extra_load_path or '\\' in extra_load_path:
1010             sys.stderr.write(_("Illegal character in extra load path:\n"
1011                                "  %s\n") % extra_load_path)
1012             sys.exit(1)
1013         xorn.guile.eval_string('(add-to-load-path "%s")' % extra_load_path)
1015     # 3. Load RC files
1016     if '"' in sys_data_path or '\\' in sys_data_path:
1017         sys.stderr.write(_("ERROR: Illegal character in "
1018                            "system data path: %s\n") % sys_data_path)
1019         sys.exit(1)
1021     if '"' in sys_config_path or '\\' in sys_config_path:
1022         sys.stderr.write(_("ERROR: Illegal character in "
1023                            "system config path: %s\n") % sys_config_path)
1024         sys.exit(1)
1026     xorn.guile.eval_string('''
1027 (define (eval-protected exp)
1028   (eval exp (interaction-environment)))
1029     ''')
1031     xorn.guile.eval_string('''
1032 (define-module (geda core os))
1033 (define-public (%%platform) '(linux))
1034 (define-public (%%sys-data-dirs) '("%s"))
1035 (define-public (%%sys-config-dirs) '("%s"))
1036 (define-public (%%user-data-dir) "%s")
1037 (define-public (%%user-config-dir) "%s")
1039 ''' % (sys_data_path, sys_config_path, user_config_path, user_config_path))
1041     # prevent loading of actual "geda deprecated" module
1042     xorn.guile.eval_string('(define-module (geda deprecated)) #f')
1044     xorn.guile.define('use-legacy-frontend', False)
1045     rc1_parse('gnetlistrc', None)
1047     # 4. Print a list of available netlist backends if requested.
1048     if list_backends_:
1049         import gaf.netlist.backend
1050         gaf.netlist.backend.load_path.insert(0, backenddir)
1051         for extra_load_path in extra_load_paths[1:]:
1052             gaf.netlist.backend.load_path.insert(0, extra_load_path)
1054         python_backends = set(name.replace('_', '-')
1055                               for name in gaf.netlist.backend.list_backends())
1056         guile_backends = set(list_backends())
1058         sys.stdout.write(_("List of available backends:\n\n"))
1059         for name in sorted(set.union(guile_backends, python_backends)):
1060             sys.stdout.write("  [%s%s]\t%s\n" % (
1061                 'P' if name in python_backends else ' ',
1062                 'G' if name in guile_backends else ' ', name))
1063         sys.stdout.write("\n")
1064         sys.exit(0)
1066     # 5. Evaluate the first set of Scheme expressions before we load
1067     #    any schematic files
1068     guile_sources = load_before_backend[:]
1070     # 6. Load basic gnetlist functions
1071     search_load_path = xorn.guile.lookup('%search-load-path')
1072     fn = search_load_path('gnetlist.scm')
1073     if fn is False:
1074         sys.stderr.write(_(
1075             "ERROR: Could not find `gnetlist.scm' in load path.\n"))
1076         sys.exit(1)
1077     guile_sources.append(fn)
1079     # Anything up to and including the `-m' options may influence the
1080     # library paths, but loading some backends (systemc, verilog) at
1081     # this point causes trouble.  So don't run backend and `-m'
1082     # options here and hope neither sets any vital paths.
1083     for guile_source in guile_sources:
1084         try:
1085             xorn.guile.load(guile_source)
1086         except xorn.guile.GuileError:
1087             sys.stderr.write(_("Error loading \"%s\"\n") % guile_source)
1088             sys.exit(1)
1090     if guile_proc is not None:
1091         # 7. Load backend code
1092         fn = search_load_path('gnet-%s.scm' % guile_proc)
1093         if fn is False:
1094             sys.stderr.write(_(
1095                 "ERROR: Could not find backend `%s' in load path.\n\n"
1096                 "Run `%s --list-backends' for a full list of available "
1097                   "backends.\n") % (guile_proc, xorn.command.program_name))
1098             sys.exit(1)
1099         guile_sources.append(fn)
1101         # 8. Evaluate second set of Scheme expressions
1102         guile_sources += load_after_backend
1103         # `-m' options aren't executed unless `-g' is specified
1105     # 9. Run post-traverse code
1106     fn = search_load_path('gnetlist-post.scm')
1107     if fn is False:
1108         sys.stderr.write(_(
1109             "ERROR: Could not find `gnetlist-post.scm' in load path.\n"))
1110         sys.exit(1)
1111     guile_sources.append(fn)
1113     # --------------------------------------------------------------------
1115     cmd = [XORN, 'netlist'] + symbol_library + source_library
1117     if prefer_netname_attribute:
1118         cmd.append('--net-naming-priority=netname-attribute')
1119     if default_net_name != 'unnamed_net':
1120         cmd.append('--default-net-name=' + default_net_name)
1121     if default_bus_name != 'unnamed_bus':
1122         cmd.append('--default-bus-name=' + default_bus_name)
1123     if not traverse_hierarchy:
1124         cmd.append('--dont-traverse-hierarchy')
1125     if not hierarchy_refdes_mangle:
1126         cmd.append('--hierarchy-refdes-mangle=disabled')
1127     if not hierarchy_netname_mangle:
1128         cmd.append('--hierarchy-netname-mangle=disabled')
1129     if not hierarchy_netattrib_mangle:
1130         cmd.append('--hierarchy-netattrib-mangle=disabled')
1131     if hierarchy_refdes_separator != '/':
1132         cmd.append(
1133             '--hierarchy-refdes-separator=' + hierarchy_refdes_separator)
1134     if hierarchy_refdes_order != APPEND:
1135         cmd.append('--hierarchy-refdes-order=prepend')
1136     if hierarchy_netname_separator != '/':
1137         cmd.append(
1138             '--hierarchy-netname-separator=' + hierarchy_netname_separator)
1139     if hierarchy_netname_order != APPEND:
1140         cmd.append('--hierarchy-netname-order=prepend')
1142     if python_backend is None:
1143         cmd += ['-g', 'guile']
1145         for value in evaluate_at_startup:
1146             cmd += ['-O', 'eval=' + value]
1147         for value in extra_load_paths:
1148             cmd += ['-O', 'add-to-load-path=' + value]
1149         for value in guile_sources:
1150             cmd += ['-O', 'load=' + value]
1152         if guile_proc is not None and not interactive_mode:
1153             # if both `-i' and `-g' are specified, the backend is
1154             # loaded but not executed (don't ask me, that's what the
1155             # old gnetlist code does)
1156             cmd += ['-O', 'guile-proc=' + guile_proc]
1158             if guile_proc.startswith('spice'):
1159                 cmd += ['-O', 'use-spice-netnames']
1161         if verbose_mode:
1162             cmd += ['-O', 'verbosity=1']
1163         elif quiet_mode:
1164             cmd += ['-O', 'verbosity=-1']
1166         if backend_options:
1167             cmd += ['-O', '--']
1168         for backend_option in backend_options:
1169             cmd += ['-O', backend_option]
1170     else:
1171         for extra_load_path in extra_load_paths[1:]:
1172             cmd += ['-L', extra_load_path]
1173         cmd += ['-g', python_backend]
1174         for backend_option in backend_options:
1175             cmd += ['-O', backend_option]
1177     if guile_proc is not None and guile_proc.startswith('drc'):
1178         cmd.append('--ignore-errors')
1180     if guile_proc == 'pcbfwd' or report_gui:
1181         cmd.append('--report-gui')
1183     if not quiet_mode:
1184         # gnetlist prints schematic names unless `-q' has been specified
1185         cmd.append('-v')
1187     if interactive_mode:
1188         cmd += ['-i']
1190     if output_filename != '-':
1191         cmd += ['-o', output_filename]
1193     cmd += args
1195     if verbose_mode:
1196         sys.stderr.write('%s\n' % ' '.join(cmd))
1198     os.execv(cmd[0], cmd)
1200 if __name__ == '__main__':
1201     main()