pax_global_header00006660000000000000000000000064117520434630014517gustar00rootroot0000000000000052 comment=5989046fa067937ed390b654d4898abc125a12e2 Pymacs-0.25/000077500000000000000000000000001175204346300127015ustar00rootroot00000000000000Pymacs-0.25/.gitignore000066400000000000000000000006261175204346300146750ustar00rootroot00000000000000*~ *.pyc *.elc Pymacs.py build contrib/Giorgi/Pymacs/__init__.py contrib/Giorgi/rpcTest.py contrib/Giorgi/rpcserver.py contrib/Giorgi/setup.py contrib/rebox/Pymacs/__init__.py contrib/rebox/setup.py pppp.rst pppp.pdf pymacs.el pymacs.pdf pymacs.rst pymacs.wpu tests/debug-* tests/pytest tests/setup.py tests/t11_pyfile_works.py tests/t21_helper_works.py tests/t31_elfile_works.py tests/t41_pymacs_works.py Pymacs-0.25/COPYING000066400000000000000000000431031175204346300137350ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. Pymacs-0.25/Makefile000066400000000000000000000036731175204346300143520ustar00rootroot00000000000000# Interface between Emacs Lisp and Python - Makefile. # Copyright © 2001, 2002, 2003, 2012 Progiciels Bourbeau-Pinard inc. # François Pinard , 2001. EMACS = emacs PYTHON = python RST2LATEX = rst2latex PYSETUP = $(PYTHON) setup.py PPPP = $(PYTHON) pppp -C ppppconfig.py all: $(PPPP) *.in contrib tests $(PYSETUP) build check: clean-debug $(PPPP) pymacs.el.in Pymacs.py.in tests cd tests && \ EMACS="$(EMACS)" PYTHON="$(PYTHON)" \ PYMACS_OPTIONS="-d debug-protocol -s debug-signals" \ $(PYTHON) pytest -f t $(TEST) install: $(PPPP) *.in Pymacs.py.in contrib tests $(PYSETUP) install clean: clean-debug rm -rf build* contrib/rebox/build rm -f */*py.class *.pyc */*.pyc pppp.pdf pymacs.pdf $(PPPP) -c *.in contrib tests clean-debug: rm -f tests/debug-protocol tests/debug-signals pppp.pdf: pppp.rst.in $(PPPP) pppp.rst.in rm -rf tmp-pdf mkdir tmp-pdf $(RST2LATEX) --use-latex-toc --input-encoding=UTF-8 \ pppp.rst tmp-pdf/pppp.tex cd tmp-pdf && pdflatex pppp.tex cd tmp-pdf && pdflatex pppp.tex mv -f tmp-pdf/pppp.pdf $@ rm -rf tmp-pdf pymacs.pdf: pymacs.rst.in $(PPPP) pymacs.rst.in rm -rf tmp-pdf mkdir tmp-pdf $(RST2LATEX) --use-latex-toc --input-encoding=UTF-8 \ pymacs.rst tmp-pdf/pymacs.tex cd tmp-pdf && pdflatex pymacs.tex cd tmp-pdf && pdflatex pymacs.tex mv -f tmp-pdf/pymacs.pdf $@ rm -rf tmp-pdf # The following goals for the maintainer of the Pymacs Web site. ARCHIVES = web/src/archives VERSION = `grep '^version' setup.py | sed -e "s/'$$//" -e "s/.*'//"` publish: pppp.pdf pymacs.pdf pymacs.rst find -name '*~' | xargs rm -fv version=$(VERSION) && \ git archive --format=tar --prefix=Pymacs-$$version/ HEAD . \ | gzip > $(ARCHIVES)/Pymacs-$$version.tar.gz rm -f $(ARCHIVES)/Pymacs.tar.gz version=$(VERSION) && \ ln -s Pymacs-$$version.tar.gz $(ARCHIVES)/Pymacs.tar.gz make-web -C web synchro push alcyon -d entretien/pymacs ssh alcyon 'make-web -C entretien/pymacs/web' Pymacs-0.25/PKG-INFO000066400000000000000000000004051175204346300137750ustar00rootroot00000000000000Metadata-Version: 1.0 Name: Pymacs Version: 0.23 Summary: Interface between Emacs Lisp and Python. Home-page: http://pinard.progiciels-bpi.ca Author: François Pinard Author-email: pinard@iro.umontreal.ca License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Pymacs-0.25/Pymacs.py.in000077500000000000000000001010201175204346300151110ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright © 2001, 2002, 2003, 2012 Progiciels Bourbeau-Pinard inc. # François Pinard , 2001. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ """\ Interface between Emacs Lisp and Python - Python part. Emacs may launch this module as a stand-alone program, in which case it acts as a server of Python facilities for that Emacs session, reading requests from standard input and writing replies on standard output. When used in this way, the program is called "the Pymacs helper". This module may also be usefully imported by other Python modules. See the Pymacs documentation (check `README') for more information. """ # Identification of version. package = 'Pymacs' version = '@VERSION@' if PYTHON3: import collections, os, sys def callable(value): return isinstance(value, collections.Callable) basestring = str from imp import reload else: __metaclass__ = type import os, sys def fixup_icanon(): # otherwise sys.stdin.read hangs for large inputs in emacs 24 # see comment in emacs source code sysdep.c import termios a = termios.tcgetattr(1) a[3] &= ~termios.ICANON termios.tcsetattr(1, termios.TCSANOW, a) try: import signal except ImportError: # Jython does not have signal. signal = None ## Python services for Emacs applications. class Main: debug_file = None signal_file = None def main(self, *arguments): """\ Execute Python services for Emacs, and Emacs services for Python. This program is meant to be called from Emacs, using `pymacs.el'. Debugging options: -d FILE Debug the protocol to FILE. -s FILE Trace received signals to FILE. Arguments are added to the search path for Python modules. """ # Decode options. arguments = (os.environ.get('PYMACS_OPTIONS', '').split() + list(arguments)) import getopt options, arguments = getopt.getopt(arguments, 'fd:s:') for option, value in options: if option == '-d': self.debug_file = value elif option == '-s': self.signal_file = value elif option == '-f': try: fixup_icanon() except: pass arguments.reverse() for argument in arguments: if os.path.isdir(argument): sys.path.insert(0, argument) # Inhibit signals. The Interrupt signal is temporary enabled, however, # while executing any Python code received from the Lisp side. if signal is not None: if IO_ERRORS_WITH_SIGNALS: # See the comment for IO_ERRORS_WITH_SIGNALS in ppppconfig.py. self.original_handler = signal.signal( signal.SIGINT, self.interrupt_handler) else: for counter in range(1, signal.NSIG): if counter == signal.SIGINT: self.original_handler = signal.signal( counter, self.interrupt_handler) else: try: signal.signal(counter, self.generic_handler) except RuntimeError: pass self.inhibit_quit = True if not PYTHON3: # Re-open standard input and output in binary mode. sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb') sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb') # Start protocol and services. lisp._protocol.send('version', '"%s"' % version) lisp._protocol.loop() def generic_handler(self, number, frame): if self.signal_file: handle = open(self.signal_file, 'a') handle.write('%d\n' % number) handle.close() def interrupt_handler(self, number, frame): if self.signal_file: star = (' *', '')[self.inhibit_quit] handle = open(self.signal_file, 'a') handle.write('%d%s\n' % (number, star)) handle.close() if not self.inhibit_quit: self.original_handler(number, frame) run = Main() main = run.main if OLD_EXCEPTIONS: ProtocolError = 'ProtocolError' ZombieError = 'ZombieError' else: class error(Exception): pass class ProtocolError(error): pass class ZombieError(error): pass class Protocol: # All exec's and eval's triggered from the Emacs side are all executed # within the "loop" method below, so all user context is kept as # local variables within this single routine. Different instances # of this Protocol class would yield independant evaluation contexts. # But in the usual case, there is only one such instance kept within a # Lisp_Interface instance, and the "lisp" global variable within this # module holds such a Lisp_Interface instance. def __init__(self): self.freed = [] if PYTHON3: def loop(self): # The server loop repeatedly receives a request from Emacs and # returns a response, which is either the value of the received # Python expression, or the Python traceback if an error occurs # while evaluating the expression. # The server loop may also be executed, as a recursive invocation, # in the context of Emacs serving a Python request. In which # case, we might also receive a notification from Emacs telling # that the reply has been transmitted, or that an error occurred. # A reply notification from Emacs interrupts the loop: the result # of this function is then the value returned from Emacs. done = False while not done: try: action, text = self.receive() if action == 'eval': action = 'return' try: run.inhibit_quit = False value = eval(text) finally: run.inhibit_quit = True elif action == 'exec': action = 'return' value = None try: run.inhibit_quit = False exec(text) finally: run.inhibit_quit = True elif action == 'return': done = True try: run.inhibit_quit = False value = eval(text) finally: run.inhibit_quit = True elif action == 'raise': action = 'raise' value = 'Emacs: ' + text else: raise ProtocolError("Unknown action %r" % action) except KeyboardInterrupt: if done: raise action = 'raise' value = '*Interrupted*' except ProtocolError as exception: sys.exit("Protocol error: %s\n" % exception) except: import traceback action = 'raise' if lisp.debug_on_error.value() is None: value = traceback.format_exception_only(sys.exc_type, sys.exc_value) value = ''.join(value).rstrip() else: value = traceback.format_exc() if not done: fragments = [] print_lisp(value, fragments.append, True) self.send(action, ''.join(fragments)) return value else: def loop(self): # The server loop repeatedly receives a request from Emacs and # returns a response, which is either the value of the received # Python expression, or the Python traceback if an error occurs # while evaluating the expression. # The server loop may also be executed, as a recursive invocation, # in the context of Emacs serving a Python request. In which # case, we might also receive a notification from Emacs telling # that the reply has been transmitted, or that an error occurred. # A reply notification from Emacs interrupts the loop: the result # of this function is then the value returned from Emacs. done = False while not done: try: action, text = self.receive() if action == 'eval': action = 'return' try: run.inhibit_quit = False value = eval(text) finally: run.inhibit_quit = True elif action == 'exec': action = 'return' value = None try: run.inhibit_quit = False exec(text) finally: run.inhibit_quit = True elif action == 'return': done = True try: run.inhibit_quit = False value = eval(text) finally: run.inhibit_quit = True elif action == 'raise': action = 'raise' value = 'Emacs: ' + text else: if OLD_EXCEPTIONS: raise ProtocolError, "Unknown action %r" % action else: raise ProtocolError("Unknown action %r" % action) except KeyboardInterrupt: if done: raise action = 'raise' value = '*Interrupted*' except ProtocolError, exception: sys.exit("Protocol error: %s\n" % exception) except: import traceback action = 'raise' if lisp.debug_on_error.value() is None: value = traceback.format_exception_only(sys.exc_type, sys.exc_value) value = ''.join(value).rstrip() else: value = traceback.format_exc() if not done: fragments = [] print_lisp(value, fragments.append, True) self.send(action, ''.join(fragments)) return value if PYTHON3: def receive(self): # Receive a Python expression from Emacs, return (ACTION, TEXT). prefix = sys.stdin.buffer.read(3) if not prefix or prefix[0] != ord(b'>'): raise ProtocolError("`>' expected.") while prefix[-1] != ord(b'\t'): character = sys.stdin.buffer.read(1) if not character: raise ProtocolError("Empty stdin read.") prefix += character data = sys.stdin.buffer.read(int(prefix[1:-1])) try: text = data.decode('UTF-8') except UnicodeDecodeError: #assert False, ('***', data) text = data.decode('ISO-8859-1') if run.debug_file is not None: handle = open(run.debug_file, 'a') handle.write(prefix.decode('ASCII') + text) handle.close() return text.split(None, 1) else: def receive(self): # Receive a Python expression from Emacs, return (ACTION, TEXT). prefix = sys.stdin.read(3) if not prefix or prefix[0] != '>': if OLD_EXCEPTIONS: raise ProtocolError, "`>' expected." else: raise ProtocolError("`>' expected.") while prefix[-1] != '\t': character = sys.stdin.read(1) if not character: if OLD_EXCEPTIONS: raise ProtocolError, "Empty stdin read." else: raise ProtocolError("Empty stdin read.") prefix += character text = sys.stdin.read(int(prefix[1:-1])) if run.debug_file is not None: handle = open(run.debug_file, 'a') handle.write(prefix + text) handle.close() return text.split(None, 1) if PYTHON3: def send(self, action, text): # Send ACTION and its TEXT argument to Emacs. if self.freed: # All delayed Lisp cleanup is piggied back on the transmission. text = ('(free (%s) %s %s)\n' % (' '.join(map(str, self.freed)), action, text)) self.freed = [] else: text = '(%s %s)\n' % (action, text) data = text.encode('UTF-8') prefix = '<%d\t' % len(data) if run.debug_file is not None: handle = open(run.debug_file, 'a') handle.write(prefix + text) handle.close() sys.stdout.buffer.write(prefix.encode('ASCII')) sys.stdout.buffer.write(data) sys.stdout.buffer.flush() else: def send(self, action, text): # Send ACTION and its TEXT argument to Emacs. if self.freed: # All delayed Lisp cleanup is piggied back on the transmission. text = ('(free (%s) %s %s)\n' % (' '.join(map(str, self.freed)), action, text)) self.freed = [] else: text = '(%s %s)\n' % (action, text) prefix = '<%d\t' % len(text) if run.debug_file is not None: handle = open(run.debug_file, 'a') handle.write(prefix + text) handle.close() sys.stdout.write(prefix + text) sys.stdout.flush() def pymacs_load_helper(file_without_extension, prefix): # This function imports a Python module, then returns a Lisp expression # which, when later evaluated, will install trampoline definitions # in Emacs for accessing the Python module facilities. Module, given # through FILE_WITHOUT_EXTENSION, may be a full path, yet without the # `.py' or `.pyc' suffix, in which case the directory is temporarily # added to the Python search path for the sole duration of that import. # All defined symbols on the Lisp side have have PREFIX prepended, # and have Python underlines in Python turned into dashes. If PREFIX # is None, it then defaults to the base name of MODULE with underlines # turned to dashes, followed by a dash. directory, module_name = os.path.split(file_without_extension) module_components = module_name.split('.') if prefix is None: prefix = module_components[-1].replace('_', '-') + '-' try: module = sys.modules.get(module_name) if module: reload(module) else: try: if directory: sys.path.insert(0, directory) module = __import__(module_name) finally: if directory: del sys.path[0] # Whenever MODULE_NAME is of the form [PACKAGE.]...MODULE, # __import__ returns the outer PACKAGE, not the module. for component in module_components[1:]: module = getattr(module, component) except ImportError: return None load_hook = module.__dict__.get('pymacs_load_hook') if load_hook: load_hook() interactions = module.__dict__.get('interactions', {}) if not isinstance(interactions, dict): interactions = {} arguments = [] for name, value in module.__dict__.items(): if callable(value) and value is not lisp: arguments.append(allocate_python(value)) arguments.append(lisp[prefix + name.replace('_', '-')]) try: interaction = value.interaction except AttributeError: interaction = interactions.get(value) if callable(interaction): arguments.append(allocate_python(interaction)) else: arguments.append(interaction) if arguments: return [lisp.progn, [lisp.pymacs_defuns, [lisp.quote, arguments]], module] return [lisp.quote, module] def doc_string(function): import inspect return inspect.getdoc(function) ## Garbage collection matters. # Many Python types do not have direct Lisp equivalents, and may not be # directly returned to Lisp for this reason. They are rather allocated in # a list of handles, below, and a handle index is used for communication # instead of the Python value. Whenever such a handle is freed from the # Lisp side, its index is added of a freed list for later reuse. python = [] freed_list = [] def allocate_python(value): assert not isinstance(value, str), (type(value), repr(value)) # Allocate some handle to hold VALUE, return its index. if freed_list: index = freed_list[-1] del freed_list[-1] python[index] = value else: index = len(python) python.append(value) return index def free_python(indices): # Return many handles to the pool. for index in indices: python[index] = None freed_list.append(index) def zombie_python(indices): # Ensure that some handles are _not_ in the pool. for index in indices: while index >= len(python): freed_list.append(len(python)) python.append(None) python[index] = zombie freed_list.remove(index) # Merely to make `*Pymacs*' a bit more readable. freed_list.sort() def zombie(*arguments): # This catch-all function is set as the value for any function which # disappeared with a previous Pymacs helper process, so calling # such a function from Emacs will trigger a decipherable diagnostic. diagnostic = "Object vanished when the Pymacs helper was killed" if lisp.pymacs_dreadful_zombies.value(): if OLD_EXCEPTIONS: raise ZombieError, diagnostic else: raise ZombieError(diagnostic) lisp.message(diagnostic) ## Emacs services for Python applications. class Let: def __init__(self, **keywords): # The stack holds (METHOD, DATA) pairs, where METHOD is the expected # unbound pop_* method, and DATA holds information to be restored. # METHOD may not be bound to the instance, as this would induce # reference cycles, and then, __del__ would not be called timely. self.stack = [] if keywords: self.push(**keywords) def __del__(self): self.pops() if PYTHON3: def __bool__(self): # So stylistic `if let:' executes faster. return True else: def __nonzero__(self): # So stylistic `if let:' executes faster. return True def pops(self): while self.stack: self.stack[-1][0](self) def push(self, **keywords): data = [] for name, value in keywords.items(): data.append((name, getattr(lisp, name).value())) setattr(lisp, name, value) self.stack.append((Let.pop, data)) return self def pop(self): method, data = self.stack.pop() assert method == Let.pop, (method, data) for name, value in data: setattr(lisp, name, value) def push_excursion(self): self.stack.append((Let.pop_excursion, (lisp.current_buffer(), lisp.point_marker(), lisp.mark_marker()))) return self def pop_excursion(self): method, data = self.stack.pop() assert method == Let.pop_excursion, (method, data) buffer, point_marker, mark_marker = data lisp.set_buffer(buffer) lisp.goto_char(point_marker) lisp.set_mark(mark_marker) lisp.set_marker(point_marker, None) lisp.set_marker(mark_marker, None) def push_match_data(self): self.stack.append((Let.pop_match_data, lisp.match_data())) return self def pop_match_data(self): method, data = self.stack.pop() assert method == Let.pop_match_data, (method, data) lisp.set_match_data(data) def push_restriction(self): self.stack.append((Let.pop_restriction, (lisp.point_min_marker(), lisp.point_max_marker()))) return self def pop_restriction(self): method, data = self.stack.pop() assert method == Let.pop_restriction, (method, data) point_min_marker, point_max_marker = data lisp.narrow_to_region(point_min_marker, point_max_marker) lisp.set_marker(point_min_marker, None) lisp.set_marker(point_max_marker, None) def push_selected_window(self): self.stack.append((Let.pop_selected_window, lisp.selected_window())) return self def pop_selected_window(self): method, data = self.stack.pop() assert method == Let.pop_selected_window, (method, data) lisp.select_window(data) def push_window_excursion(self): self.stack.append((Let.pop_window_excursion, lisp.current_window_configuration())) return self def pop_window_excursion(self): method, data = self.stack.pop() assert method == Let.pop_window_excursion, (method, data) lisp.set_window_configuration(data) class Symbol: def __init__(self, text): self.text = text def __repr__(self): return 'lisp[%s]' % repr(self.text) def __str__(self): return '\'' + self.text def value(self): return lisp._eval(self.text) def copy(self): return lisp._expand(self.text) def set(self, value): if value is None: lisp._eval('(setq %s nil)' % self.text) else: fragments = [] write = fragments.append write('(progn (setq %s ' % self.text) print_lisp(value, write, True) write(') nil)') lisp._eval(''.join(fragments)) def __call__(self, *arguments): fragments = [] write = fragments.append write('(%s' % self.text) for argument in arguments: write(' ') print_lisp(argument, write, True) write(')') return lisp._eval(''.join(fragments)) class Lisp: def __init__(self, index): self.index = index def __del__(self): lisp._protocol.freed.append(self.index) def __repr__(self): return ('lisp(%s)' % repr(lisp('(prin1-to-string %s)' % self))) def __str__(self): return '(aref pymacs-lisp %d)' % self.index def value(self): return self def copy(self): return lisp._expand(str(self)) class Buffer(Lisp): pass #def write(text): # # So you could do things like # # print >>lisp.current_buffer(), "Hello World" # lisp.insert(text, self) #def point(self): # return lisp.point(self) class List(Lisp): def __call__(self, *arguments): fragments = [] write = fragments.append write('(%s' % self) for argument in arguments: write(' ') print_lisp(argument, write, True) write(')') return lisp._eval(''.join(fragments)) def __len__(self): return lisp._eval('(length %s)' % self) def __getitem__(self, key): value = lisp._eval('(nth %d %s)' % (key, self)) if value is None and key >= len(self): if OLD_EXCEPTIONS: raise IndexError, key else: raise IndexError(key) return value def __setitem__(self, key, value): fragments = [] write = fragments.append write('(setcar (nthcdr %d %s) ' % (key, self)) print_lisp(value, write, True) write(')') lisp._eval(''.join(fragments)) class Table(Lisp): def __getitem__(self, key): fragments = [] write = fragments.append write('(gethash ') print_lisp(key, write, True) write(' %s)' % self) return lisp._eval(''.join(fragments)) def __setitem__(self, key, value): fragments = [] write = fragments.append write('(puthash ') print_lisp(key, write, True) write(' ') print_lisp(value, write, True) write(' %s)' % self) lisp._eval(''.join(fragments)) class Vector(Lisp): def __len__(self): return lisp._eval('(length %s)' % self) def __getitem__(self, key): return lisp._eval('(aref %s %d)' % (self, key)) def __setitem__(self, key, value): fragments = [] write = fragments.append write('(aset %s %d ' % (self, key)) print_lisp(value, write, True) write(')') lisp._eval(''.join(fragments)) class Lisp_Interface: def __init__(self): self.__dict__['_cache'] = {'nil': None} self.__dict__['_protocol'] = Protocol() def __call__(self, text): return self._eval('(progn %s)' % text) def _eval(self, text): self._protocol.send('eval', text) return self._protocol.loop() def _expand(self, text): self._protocol.send('expand', text) return self._protocol.loop() def __getattr__(self, name): if name[0] == '_': if OLD_EXCEPTIONS: raise AttributeError, name else: raise AttributeError(name) return self[name.replace('_', '-')] def __setattr__(self, name, value): if name[0] == '_': if OLD_EXCEPTIONS: raise AttributeError, name else: raise AttributeError(name) self[name.replace('_', '-')] = value def __getitem__(self, name): try: return self._cache[name] except KeyError: symbol = self._cache[name] = Symbol(name) return symbol def __setitem__(self, name, value): try: symbol = self._cache[name] except KeyError: symbol = self._cache[name] = Symbol(name) symbol.set(value) lisp = Lisp_Interface() if PYTHON3: print_lisp_quoted_specials = { ord('"'): '\\"', ord('\\'): '\\\\', ord('\b'): '\\b', ord('\f'): '\\f', ord('\n'): '\\n', ord('\r'): '\\r', ord('\t'): '\\t'} def print_lisp(value, write, quoted): if value is None: write('nil') elif isinstance(bool, type) and isinstance(value, bool): write(('nil', 't')[value]) elif isinstance(value, int): write(repr(value)) elif isinstance(value, float): write(repr(value)) elif isinstance(value, str): try: value.encode('ASCII') except UnicodeError: write('(decode-coding-string "') for byte in value.encode('UTF-8'): special = print_lisp_quoted_specials.get(byte) if special is not None: write(special) elif 32 <= byte < 127: write(chr(byte)) else: write('\\%.3o' % byte) write('" \'utf-8)') else: write('"') for character in value: special = print_lisp_quoted_specials.get(ord(character)) if special is not None: write(special) elif 32 <= ord(character) < 127: write(character) else: write('\\%.3o' % ord(character)) write('"') elif isinstance(value, list): if quoted: write("'") if len(value) == 0: write('nil') elif len(value) == 2 and value[0] == lisp.quote: write("'") print_lisp(value[1], write, False) else: write('(') print_lisp(value[0], write, False) for sub_value in value[1:]: write(' ') print_lisp(sub_value, write, False) write(')') elif isinstance(value, tuple): write('[') if len(value) > 0: print_lisp(value[0], write, False) for sub_value in value[1:]: write(' ') print_lisp(sub_value, write, False) write(']') elif isinstance(value, Lisp): write(str(value)) elif isinstance(value, Symbol): if quoted: write("'") write(value.text) elif callable(value): write('(pymacs-defun %d nil)' % allocate_python(value)) else: write('(pymacs-python %d)' % allocate_python(value)) else: print_lisp_quoted_specials = { '"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t'} def print_lisp(value, write, quoted): if value is None: write('nil') elif isinstance(bool, type) and isinstance(value, bool): write(('nil', 't')[value]) elif isinstance(value, int): write(repr(value)) elif isinstance(value, float): write(repr(value)) elif isinstance(value, basestring): multibyte = False if isinstance(value, unicode): try: value = value.encode('ASCII') except UnicodeError: value = value.encode('UTF-8') multibyte = True if multibyte: write('(decode-coding-string ') write('"') for character in value: special = print_lisp_quoted_specials.get(character) if special is not None: write(special) elif 32 <= ord(character) < 127: write(character) else: write('\\%.3o' % ord(character)) write('"') if multibyte: write(' \'utf-8)') elif isinstance(value, list): if quoted: write("'") if len(value) == 0: write('nil') elif len(value) == 2 and value[0] == lisp.quote: write("'") print_lisp(value[1], write, False) else: write('(') print_lisp(value[0], write, False) for sub_value in value[1:]: write(' ') print_lisp(sub_value, write, False) write(')') elif isinstance(value, tuple): write('[') if len(value) > 0: print_lisp(value[0], write, False) for sub_value in value[1:]: write(' ') print_lisp(sub_value, write, False) write(']') elif isinstance(value, Lisp): write(str(value)) elif isinstance(value, Symbol): if quoted: write("'") write(value.text) elif callable(value): write('(pymacs-defun %d nil)' % allocate_python(value)) else: write('(pymacs-python %d)' % allocate_python(value)) if __name__ == '__main__': main(*sys.argv[1:]) Pymacs-0.25/README000066400000000000000000000161101175204346300135600ustar00rootroot00000000000000Pymacs — Notes This Org file is being published as http://pinard.progiciels-bpi.ca/notes/ Pymacs.html, and the Pymacs README file has been derived from the HTML using w3m -dump. Table des matières • 1 README contents □ 1.1 Notes for 0.25 □ 1.2 Notes for 0.24 □ 1.3 Notes for 0.23 • 2 Informal notes □ 2.1 python-mode.el difficulty □ 2.2 Using packagers 1 README contents There exists a Belorussian translation of this file by Paul Bukhovko, and a Romanian translation of the Pymacs manual by Alexander Ovsov. Pymacs is a powerful tool which, once started from Emacs, allows both-way communication between Emacs Lisp and Python. Pymacs aims Python as an extension language for Emacs rather than the other way around, and this asymmetry is reflected in some design choices. Within Emacs Lisp code, one may load and use Python modules. Python functions may themselves use Emacs services, and handle Emacs Lisp objects kept in Emacs Lisp space. The Pymacs manual (either in HTML format or PDF format) has installation instructions, a full description of the API, pointers to documented examples, to resources, and to other Pymacs sites or projects. The distribution also includes the Poor's Python Pre-Processor (pppp) and its manual (either in HTML format or PDF format). Source files and various distributions are available through https://github.com /pinard/Pymacs/. Please report problems, comments and suggestions to François Pinard. 1.1 Notes for 0.25 2012-05-07 lun Hi everybody. Pymacs 0.25 is now available. You may fetch it as one of: • https://github.com/pinard/Pymacs/tarball/v0.25 • https://github.com/pinard/Pymacs/zipball/v0.25 depending on if you want a tar or zip archive. There installation process was modified: • Python 3 is now supported. This required new installation mechanics, and a Python pre-processor written for the circumstance (named pppp). • Pymacs now installs a single Python file instead of a Python module. This does not affect users — except maybe a few who chose to depend on undocumented internals. The specifications are pretty stable. A few additions occurred: • Variable pymacs-python-command may select which Python interpreter to use. • A pymacs-auto-restart variable lets the user decide what to do if the Pymacs helper aborts. • The Let class got a pops method which pops everything in a single call. • A new API function pymacs-autoload serves lazy imports. There also are miscellaneous changes: • Some errors have been corrected, both in the code and in the manual. • The Emacs Lisp source has been massaged so to become uploadable in ELPA's (Emacs Lisp Packages Archives). XEmacs support seems to be broken, and Jython 2.2 support does not work yet. As I am not much of a user of either, this is kept on ice currently. Interested collaborators and testers, contact me if you feel like pushing in these areas! Nice thanks to Pymacs contributors. It surely has been fun working with you all! 1.2 Notes for 0.24 Whenever I tag a version -betaN or such, it might not be fully ready for public distribution, this is a welcome defect that ELPA cannot grok such versions. Someone wanting to upload Pymacs nevertheless found his way around the limitation by renaming the version, I guess from 0.24-beta2 to 0.24. It would also have been polite to check with me first… As beta releases come before real releases, it should really have been 0.23. Anyway, Marmelade now has a Pymacs 0.24. For avoiding any more confusion, I'm skipping 0.24. Such a version does not officially exist. 1.3 Notes for 0.23 2008-02-15 ven Hello to everybody, and Emacs users in the Python community. Here is Pymacs 0.23! There has been a while, so I advise current Pymacs users to switch with caution. All reported bugs have been squashed, if we except one about Emacs quit (C-g) not being obeyed gracefully. A few suggestions have been postponed, to be pondered later. The manual is now in reST format, and everything Allout is gone. Postscript and PDF files are not anymore part of the distribution, you may find them on the Web site, or use the Makefile if you have needed tools. Examples have been moved out of the manual into a new contrib/ subdirectory, which also holds a few new contributions. The example of a Python back-end for Emacs Gnus has been deleted. Python 1.5.2 compatibility has been dropped; use Python 2.2 or better. The Pymacs manual explains installation procedure, now simplified. The pymacs-services script is gone, this should ease installing Pymacs on MS Windows. There is also a small, still naive validation suite. The communication protocol has been revised: more clarity, less magic. Zombie objects are less dreadful by default. The API now supports False and True constants, and Unicode strings (within limits set by Emacs). Special thanks to those who helped me at creating or testing this release. 2 Informal notes 2.1 python-mode.el difficulty 2012-05-07 lun After I recently acquired a new machine and installed a flurry of software on it, I was saluted with: pymacs-report-error: Pymacs helper did not start within 30 seconds The problem turns out to come from python-mode.el (a development copy), which insists on providing and using its own older copy of Pymacs. The problem shows in the Pymacs communication buffer: a failed attempt at importing Pymacs/ __init__.py. Indeed, this file does not exist anymore. Pymacs now stands as a single file on the Python side, not as a module. This yields confusion at run time. The problem vanishes if I comment out python-mode.el initialization, or more simply (thanks holmboe) if py-load-pymacs-p is set to nil. I'll talk to Andreas Röhler about this. 2.2 Using packagers 2012-05-07 lun Gleb Peregud suggests on GitHub that we prepare an ELPA/ Marmalade package for Pymacs. There is also a Python side to be addressed, and I've been lucky enough to recently meet Éric Araujo, the distutils2 / package maintainer. The time might be proper to push a bit on the idea on getting Pymacs on installers. I saved a few notes on Emacs Packagers. After having pondering them, I'll follow Gleb's advice, at least to get started and experiment. Emacs packagers do not care about Python, and Python packagers ignore Emacs Lisp installation problems. The pre-processing step in Pymacs is another source of concern. In a word, I'll save the bottle of champagne for some later time! ☺ The same that there is some complexity in installers on the Emacs Lisp side, there might be some complexity in installers on the Python side. Emacs proponents would see the need of an ELPA and dismiss the problem of properly installing on the Python side (resorting to PYTHONPATH mangling or such), Python proponents would do exactly the contrary. Seeing one side as complex, and the other side as trivially simple. My feeling is that to do nicely for everybody, we should use an Emacs installer and an Python installer. If I really was seeking simplicity, I would use neither… Auteur: François Pinard Date: 2012-05-07 17:51:54 EDT HTML generated by org-mode 6.33x in emacs 23 Pymacs-0.25/THANKS000066400000000000000000000071711175204346300136220ustar00rootroot00000000000000Pymacs has been written by François Pinard after an idea from Cedric Adjih, and much influenced by Brian McErlean. Here is the list of contributors. ======================= =================================================== Alexander Ovsov | alovsov at gmail dot com Alexander Solovyov | piranha at piranha dot org dot ua | http://piranha.org.ua/ Ali Gholami Rudi | aligrudi at gmail dot com Barton Bing | barton@abubio.us Brett | brettatoms at gmail dot com Brian McErlean | b dot mcerlean at kainos dot com Brian van den Broek | bvande at po-box dot mcgill dot ca | http://home.cc.umanitoba.ca/~broek/ Carel Fellinger | carel dot fellinger at chello dot nl Carey Evans | careye at spamcop dot net Carlos A. Rocha | carlos dot rocha at gmail dot com | http://alum.media.mit.edu/~rocha Cedric Adjih | adjih-pam at crepuscule dot com | http://www.crepuscule.com/pyemacs/ Christian Tanzer | tanzer at swing dot co dot at Christopher Petrilli | petrilli at amber dot org Dave Sellars | dsellars at windriver dot com Dirk Vleugels | dvl at 2scale dot net Eli Zaretskii | eliz at is dot elta dot co dot il Erik Allik | eallik at gmail dot com Eyal Lotem | eyal dot lotem at gmail dot com Fernando Pérez | fperez528 at yahoo dot com François Pinard | pinard at iro dot umontreal dot ca | http://pinard.progiciels-bpi.ca Gerd Möllmann | gerd at gnu dot org Giovanni Giorgi | jj at objectsroot dot com Greg Detre | gdetre at princeton dot edu | http://www.gregdetre.co.uk Grzegorz Nieradka | gregnieradka at gmail dot com Henk van Dorp | h dot d dot vandorp at alumnus dot utwente dot nl | http://home.orange.nl/vandorp Igor Nawrocki | igornaw at gmail dot com Hui Zhao | https://github.com/HynUx John Wiegley | johnw at gnu dot org Jordan Golinkoff | jgolinkoff@gmail.com Lukasz Pankowski | lupan at zamek dot gda dot pl Marcin Qrczak Kowalczyk | qrczak at knm dot org dot pl Marco Gidde | mgidde at compuserve dot de Neal Becker | ndbecker2 at gmail dot com Nirmal J. Patel | merik at cc dot gatech dot edu Paul Foley | mycroft at actrix dot gen dot nz | http://users.actrix.co.nz/mycroft/ Paul Winkler | slinkp23 at yahoo dot com Quinn Weaver | quinn at fairpath dot com | http://fairpath.com/ Richard Stallman | rms at gnu dot org Rob Wolfe | rw at smsnet dot pl Sean Fisk | sean at seanfisk dot com | http://seanfisk.com Sebastian Waschik | sebastian dot waschik at gmx dot de Skip Montanaro | skip at pobox dot com | http://www.webfast.com/~skip/ Stefan Reichör | xsteve at riic dot at Steffen Ries | steffen dot ries at sympatico dot ca Stéphane Rollandin | lecteur at zogotounga dot net | http://www.zogotounga.net/comp/index.html Steve | kallestad at gmail dot com Syver Enstad | syver dot enstad at asker dot online dot no Tim Crews | tim at crews-family dot org Ulrich Müller | ulm at gentoo dot org Willi Richert | willi at richert dot de ======================= =================================================== Pymacs-0.25/TODO000066400000000000000000000024571175204346300134010ustar00rootroot00000000000000=============== TODO for Pymacs =============== Actual bugs =========== + Work around multi-byte and Unicode + DOS line endings problem + XEmacs problems (like pop-excursion) and support Internal problems ================= + User interrupts on the Emacs side + Usability of Emacs hooks + Remaining rpymacs solutions + Possible memory leaks + Verbosity of ``(pymacs-eval "dir()")`` Documentation (more about…) =========================== + Recent installation procedure + Contrib issues (like XMLRPC) + Examples + Emacs Lisp / Python deep differences + Speed issues New features (to ponder) ======================== + Expose various close operatios + Better support of special forms + Real multi-byte and Unicode + Relate Lisp hash tables to Python dicts, maybe? + Automatic conversion of alists + Implement pseudo-primitives for indexing + Allow iterating over vectors + Jython and IronPython support + Loading, and Emacs file handlers + Comply to some Python standards (PEP 8?) + Emulator for Vim support + Emacs started and driven from Python + Emacsclient/gnuclient support + Allowing of threads Debugging ========= + Enhancements to *Pymacs* + Indent + Interpret numbers + Highlight + Python shell link to helper + Python traceback on Lisp generated errors + Have tests/pytest use cProfile if available Pymacs-0.25/contrib/000077500000000000000000000000001175204346300143415ustar00rootroot00000000000000Pymacs-0.25/contrib/Giorgi/000077500000000000000000000000001175204346300155615ustar00rootroot00000000000000Pymacs-0.25/contrib/Giorgi/ChangeLog000066400000000000000000000004521175204346300173340ustar00rootroot000000000000002007-03-06 Giovanni Giorgi * : Release 2.0 * General: Migrated to python 2.5 Found and corrected some errors/fixes for working with emacs 21.4.1 Improved documentation Added extensions and xmlrpc server * setup: Added regression tests Pymacs-0.25/contrib/Giorgi/Pymacs/000077500000000000000000000000001175204346300170155ustar00rootroot00000000000000Pymacs-0.25/contrib/Giorgi/Pymacs/__init__.py.in000066400000000000000000000021201175204346300215260ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright © 2002, 2003 Progiciels Bourbeau-Pinard inc. # François Pinard , 2002. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ """\ Interface between Emacs Lisp and Python - Module initialisation. A few symbols are moved in here so they appear to be defined at this level. """ from Pymacs import Let, lisp # Identification of version. package = 'Pymacs' version = '@VERSION@' Pymacs-0.25/contrib/Giorgi/Pymacs/utility.py000066400000000000000000000032761175204346300211020ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright 2007 Giovanni Giorgi # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. from Pymacs import lisp import time # Utility support functions class EmacsLog: def __init__(self,category): self.logBuffer="*LogBuffer*" # "*Pymacs Log Buffer*" self.category=category self.startTime=time.time() def show(self,level,msg): start=int(time.time()-self.startTime) mx=str(start)+" <"+level+"> PY "+self.category+" "+msg lisp.message(mx) #mx = mx + "\n" #lisp.set_buffer(self.logBuffer) #lisp.insert(mx) def info(self, msg): self.show("I",msg) def debug(self,msg): self.show("DEBUG",msg) # Finest debugging def debugf(self,msg): self.show("DEBUG FINER",msg) class BufferMan: def __init__(self): self.bufferName=lisp.buffer_name() self.fname=lisp.buffer_file_name() def getBufferAsText(self): f=open(self.fname,"r") text=f.read() f.close() return text def writeBuffer(self,text): f=open(self.fname,"w") f.write(text) f.close() self.reloadBuffer() def reloadBuffer(self): # ;; (switch-to-buffer bname) # ;; (revert-buffer 'IGNORE-AUTO 'NOCONFIRM) lisp.switch_to_buffer(self.bufferName) lisp.revert_buffer(lisp.IGNORE_AUTO, lisp.NOCONFIRM) log=EmacsLog("main") log.debugf("Pymacs.utility Loaded and happy to serve you") Pymacs-0.25/contrib/Giorgi/README000066400000000000000000000177501175204346300164530ustar00rootroot00000000000000.. role:: file(literal) ======================= Giovanni Giorgi's files ======================= .. contents:: .. sectnum:: This page documents the :file:`contrib/Giorgi/` subdirectory of the Pymacs distribution. First install Pymacs from the top-level of the distribution, this has the side-effect of adjusting a few files in this directory. Once this done, return to this directory, then run ``python setup.py install``. Here, you'll find miscellaneous files contributed by Giovanni Giorgi, to be sorted, documented, maybe deleted, at least pondered in one way or another. The remainder of this page comes from Giovanni's writing, waiting for Giovanni to revise its contents. Introduction ============ ChangeLog --------- Pymacs last version was 0.22, written by Francois Pinard. Giovanni Giorgi took this version and evolved it. This new development is marked "2.0" and is it always distributed under GNU General Public License 2.0 Feel free to send comments suggestions and so on to Giovanni's email address: mailto:jj@objectsroot.com What's new in Pymacs 2.0 ------------------------ Giovanni Giorgi (the Author) has taken Pymacs 0.99 and has decided to write some extensions. The final product is named Pymacs 2.0. Improved documentation and examples ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, In Pymacs 2.0 has been added a medium sized example. The documentation offers a `Troubleshooting`_ section, to solve most common issue, also for helping newbie emacs user. Improved API ,,,,,,,,,,,, Pymacs 2.0 offers a magic save_excursion(f) method which mimics the one found on lisp side. Logging Support and Regression Tests ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, The Pymacs/utility.py module offers a useful EmacsLog class. The basictests.py module offers a list of regression tests Pymacs 2.0 has been tested on:: GNU Emacs 21.4.1 (i686-redhat-linux-gnu, X toolkit, Xaw3d scroll bars) GNU Emacs 22.1.1 (i686-pc-linux-gnu, X toolkit, Xaw3d scroll bars) XMLRPC Server ,,,,,,,,,,,,, In Pymacs 2.0 has been added a XMLRPC server to play with. Is not yet production-ready! Redesigned Web Site ,,,,,,,,,,,,,,,,,,, It is provided a new website at http://blog.objectsroot.com/pymacs History ------- The ChangeLog Reports the first release of 9th September 2001. Giovanni Giorgi started working on Pymacs on 6th March 2007 for its own personal use. This is the first public release, published as final at 23th of February 2008. Evolving Emacs -------------- One of the main goal of Pymacs 2.0 is to evolve emacs. Other editors have a very poor support for scripting. The protocol specification is quite frozen, and python offers always a good retro-compatibility, so you can develop to the python site with ease. Emacs instead has a very powerful lisp engine, but it is difficult to understand and learn. Python is very easy to understand, and there are plenty of library for it. Pymacs is quite efficient and very easy to use. I have successful written some functions in Pymacs in an evening. Best, emacs is a powerful platform for accessing remote files, parsing XML stuff and so on. There are equally powerful editor: Eclipse ,,,,,,, Very powerful but difficult to program. Lacks an easy scripting language for common tasks (like small macro-recording actions). Plug-in architecture is very stable, and even if some plug-in crashes, the user is well protected. But Emacs is more stable even then eclipse! JEdit ,,,,, Very easy to script, but too decoupled. To get a minimum of emacs functionality you need to load a huge amount of plug-in. The plug-in model is good, but they don't play nicely between them. A typical bug is a "user focus war" and the frequent errors mess up things. This is unacceptable in a production environment Vim ,,, Smaller and equally powerful, it has only a problem: cryptic sequence of keys needed to learn. And typing your name after launching Vim can destroy your file in a snap! Okay I am joking. There is a well-known war between emacs and vi. And if you are reading this document, you are on my side, young Jedi :-) Magic save_excursion(f) method ------------------------------ In Pymacs 2.0 has been added a magical save_excursion() method to the lisp object. For example, instead of:: l=Let() l.push_excursion() myFantasticFunction() l.pop_excursion() You can write a more Emacs Lisp-like code:: lisp.save_excursion(myFantasticFunction) This lead to a simpler code. The new save_excursion() method executes pop_excursion even in case of errors, so myFantasticFunction() need not worry of them. Useful Extension ================ Enanched Python Mode -------------------- Under the extensions folder you find a modified version of python-mode, provided as file :file:`python-mode.diffs`. These diffs are relative to :file:`python-mode-1.0.tar.gz` as found at: http://sourceforge.net/projects/python-mode/ Last released version is dated at the end of 2005, so it seems to me quite frozen. For an usage example execute the Emacs Lisp function "pycomplete-test" Emacs Server as XMLRPC ---------------------- Warning: this is an unsafe and very very experimental feature. Is it possible to publish emacs as a python XMLRPC server. You will be virtually able to control emacs remotely, and spread the Stallman verb to the Universe :) Okay I am joking here. First of all, import into emacs the rpcserver.py found in the extensions folder:: (pymacs-load "pymacs-2.0/extensions.rpcserver") then activate it issuing the command M-x rpcserver-publish-XMLRPC-Server Emacs will freeze, waiting requests. Open a python prompt and try something like:: import xmlrpclib s = xmlrpclib.Server('http://127.0.0.1:9000') s.message("hi emacs, I am remoted hosted") s.closeConnection() You can find a more complex example in extensions/rpcTest.py Keep in mind emacs will froze while the server is running. I have not yet tested the server on a long-running procedure, so please do some tests before using it heavily. Troubleshooting =============== I see a lot of "Garbage Collecting..." messages blinking on the emacs side -------------------------------------------------------------------------- This messages aren't a problem, but can slow down a bit your emacs side. If you have plenty of RAM, try executing something like:: lisp(""" (setq gc-cons-threshold 3558000) """) pymacs-load "path-to-mymodule" seems working only the first time ----------------------------------------------------------------- This problem was reported on python 2.5 at the moment. Usually is solved putting a dot in the last part of the filename parameter. Suppose you have a package "myPackage" and a file "utils.py" inside it. You should write:: (pymacs load "/home/jj/emacs/myPackage.utils") this syntax works well every time. To avoid the "ImportError: No module named extensions2.0.pycomplete-serverice" message, remember to add an empty __init__.py file in the /home/jj/emacs/myPackage directory fp-maybe-pymacs-reload emits an error and stop working. ------------------------------------------------------- This problem is reported on emacs 21.4 on Linux Fedora Core 6. I have noticed this bug disappears if the callback function is put as the last hook:: (add-hook 'after-save-hook 'fp-maybe-pymacs-reload 'append) It seems there is a problem calling a Pymacs function inside an hook callback function. This bug is hardly to find because frequently you define only an hook for after-save-hook. Can you give me a .emacs to play with? -------------------------------------- If you use the new customization feature of the latest emacs releases, you will not need a big .emacs. First of all, my advice is to develop a separate config.el file. Load the config.el in the .emacs, because emacs tends to write at the end of .emacs its customization. Then stick with emacs, and for the first times avoid XEmacs: I have found far more packages working for emacs, and the two programs have some incompatibilities. You can find a huge list of init file at http://www.emacswiki.org/cgi-bin/wiki/CategoryDotEmacs Have fun! Pymacs-0.25/contrib/Giorgi/code-fragments000066400000000000000000000013051175204346300204010ustar00rootroot00000000000000 except ImportError, inst: ## [GG] Try to write the error on a file: f=open("/tmp/pymacs.err","w") f.write("pymacs_load_helper unrecoverable error\n") f.write(str(type(inst))+"\n") f.write(str(inst)+"\n") f.close() #return None raise # [GG] Added support for save excursion and in future for some speed ups class SmartLisp_Interface(Lisp_Interface): ## Mimics save_excursion def save_excursion(self,f): # Save the excursion and do the work: try: l=Let() l.push_excursion() f() finally: l.pop_excursion() # lisp = Lisp_Interface() lisp = SmartLisp_Interface() Pymacs-0.25/contrib/Giorgi/dotEmacs.py000066400000000000000000000032761175204346300177020ustar00rootroot00000000000000# -*- coding: utf-8 -*- # jjEmacsPythoned.py # import sys import os.path import string from Pymacs import lisp sys.path.append(".") interactions = {} def debug(msg): msg = " "+msg def t(): lisp.message(msg) lisp.set_buffer("*LogBuffer*") lisp.goto_line(lisp.point_max()) lisp.insert(msg+"\n") lisp.save_excursion(t) def doMainConfig(): # On emacs 22 enable cua mode: try: lisp.cua_mode(True) debug("Cua mode succesfully initialized") except Exception: debug("Failed Cua mode init") def testExceptionFramework(): try: lisp.give_me_an_error() except Protocol.ErrorException: debug("Errore get") def initLogs(): lisp.switch_to_buffer("*LogBuffer*") debug("Log Buffer succesfully initialized by Pymacs") ################ Callback for auto-reloading python modules if needed def get_module_name_if_python_file_handle(): fname=lisp.buffer_file_name() if fname==None: return None # check it: if fname[-3:] == '.py' and not fname.endswith("Pymacs.py")): # Ok, we have got something to do: # replace last / with a point and try it down: i=fname.rfind("/") pk=fname[:i]+"."+fname[i+1:-3] #debug("Reloading "+pk) return pk else: #say(" Nothing to do for:"+fname) return None interactions[get_module_name_if_python_file_handle]='' #### BASIC SAVE HOOK always called: def save_hook(bufferFileName): #say("Nothing to do") pass interactions[save_hook]='' def installPymacsMenu(): pass interactions[debug]='' interactions[initLogs]='' interactions[doMainConfig]='' interactions[testExceptionFramework]='' Pymacs-0.25/contrib/Giorgi/menudemo.py000066400000000000000000000005531175204346300177470ustar00rootroot00000000000000# -*- coding: utf-8 -*- import sys import os.path import string from Pymacs import lisp sys.path.append(".") interactions = {} def testVectors(): # Test vectors # Returns something like ["a" "b"] which is a emacs lisp vector return ("a", "b") def installPymacsMenu(): pass interactions[testVectors]='' interactions[installPymacsMenu]='' Pymacs-0.25/contrib/Giorgi/python-mode.diffs000066400000000000000000000203161175204346300210430ustar00rootroot00000000000000diff -ur python-mode-1.0/pycomplete.el python-mode-1.0+/pycomplete.el --- python-mode-1.0/pycomplete.el 2005-12-02 11:30:11.000000000 -0500 +++ python-mode-1.0+/pycomplete.el 2008-02-01 22:22:01.000000000 -0500 @@ -1,36 +1,45 @@ ;;; Complete symbols at point using Pymacs. - ;;; See pycomplete.py for the Python side of things and a short description ;;; of what to expect. (require 'pymacs) (require 'python-mode) -(pymacs-load "pycomplete") +(pymacs-load "/home/jj/Projects/pymacs-2.0/extensions.pycomplete") + +;;check if prev character is blank-type +(defun char-before-blank () + (save-excursion + (forward-char -1) + (looking-at "[\n\t\r]"))) (defun py-complete () (interactive) - (let ((pymacs-forget-mutability t)) - (insert (pycomplete-pycomplete (py-symbol-near-point) - (py-find-global-imports))))) + (let ((pymacs-forget-mutability t)) + (if (and + (and (eolp) (not (bolp)) + (not (char-before-blank)))) + (insert (pycomplete-pycomplete (py-symbol-near-point) (py-find-global-imports))) + (indent-for-tab-command)))) (defun py-find-global-imports () (save-excursion (let (first-class-or-def imports) (goto-char (point-min)) (setq first-class-or-def - (re-search-forward "^ *\\(def\\|class\\) " nil t)) + (re-search-forward "^ *\\(def\\|class\\) " nil t)) (goto-char (point-min)) (setq imports nil) (while (re-search-forward - "^\\(import \\|from \\([A-Za-z_][A-Za-z_0-9]*\\) import \\).*" - nil t) - (setq imports (append imports - (list (buffer-substring - (match-beginning 0) - (match-end 0)))))) + "\\(import \\|from \\([A-Za-z_][A-Za-z_0-9\\.]*\\) import \\).*" + nil t) + (setq imports (append imports + (list (buffer-substring + (match-beginning 0) + (match-end 0)))))) imports))) -(define-key py-mode-map "\M-\C-i" 'py-complete) +(define-key py-mode-map "\M-\C-i" 'py-complete) +(define-key py-mode-map "\t" 'py-complete) (provide 'pycomplete) diff -ur python-mode-1.0/pycomplete.py python-mode-1.0+/pycomplete.py --- python-mode-1.0/pycomplete.py 2005-12-02 11:30:11.000000000 -0500 +++ python-mode-1.0+/pycomplete.py 2008-02-01 22:22:01.000000000 -0500 @@ -1,4 +1,3 @@ - """ Python dot expression completion using Pymacs. @@ -6,23 +5,25 @@ (require 'pycomplete) -to your .xemacs/init.el file (untried w/ GNU Emacs so far) and have Pymacs -installed, when you hit M-TAB it will try to complete the dot expression +to your .xemacs/init.el file (.emacs for GNU Emacs) and have Pymacs +installed, when you hit TAB it will try to complete the dot expression before point. For example, given this import at the top of the file: import time -typing "time.cl" then hitting M-TAB should complete "time.clock". - -This is unlikely to be done the way Emacs completion ought to be done, but -it's a start. Perhaps someone with more Emacs mojo can take this stuff and -do it right. +typing "time.cl" then hitting TAB should complete "time.clock". See pycomplete.el for the Emacs Lisp side of things. """ - import sys import os.path +import string +from Pymacs import lisp + + +sys.path.append(".") + +ENABLE_GG_FUZZY_GUESS=True try: x = set @@ -43,8 +44,9 @@ exec stmt in globals(), locald except TypeError: raise TypeError, "invalid type: %s" % stmt - - dots = s.split(".") + except: + continue + dots = s.split(".") if not s or len(dots) == 1: keys = set() keys.update(locald.keys()) @@ -60,7 +62,7 @@ sym = None for i in range(1, len(dots)): - s = ".".join(dots[:i]) + s = ".".join(dots[:i]) try: sym = eval(s, globals(), locald) except NameError: @@ -68,22 +70,98 @@ sym = __import__(s, globals(), locald, []) except ImportError: return [] - if sym is not None: - s = dots[-1] + if sym is not None: + s = dots[-1] return [k for k in dir(sym) if k.startswith(s)] +def get_keyword_completition(s): + # Reserved keywords for Python 2.5 + keywords=""" and del from not while +as elif global or with +assert else if pass yield +break except import print +class exec in raise +continue finally is return +def for lambda try +""" + kl=keywords.split() + + return [ k+" " for k in kl if k.startswith(s)] + +def get_common_fuzzy_completition(s): + result=[] + # Then we add some *common* words + commonFuzzyWords=""" self. """ + kl=commonFuzzyWords.split() + result=[ k for k in kl if k.startswith(s)] + if len(result)==0: + dottedparts=s.split(".") + if(len(dottedparts)>=2): + mx=dottedparts[1] + # This is a very tiny list of stuff + kl=""" remove( append( join(""".split() + lisp.message("Hyper fuzzy extracted method:"+mx+" List:"+str(kl)) + result=[ k for k in kl if k.startswith(mx)] + return result + + def pycomplete(s, imports=None): completions = get_all_completions(s, imports) + + + # [GG] Extension1: + if ENABLE_GG_FUZZY_GUESS : # and len(completions)==0: + #lisp.message("Trying to complete using a fuzzy guess for:"+s) + k2=get_keyword_completition(s) + k3=get_common_fuzzy_completition(s) + # Integrate the fuzzy guess with other completitions + for k in k2: + completions.append(k) + for k in k3: + completions.append(k) + + lisp.message("DEBUG completions="+str(completions)) dots = s.split(".") - return os.path.commonprefix([k[len(dots[-1]):] for k in completions]) + result = os.path.commonprefix([k[len(dots[-1]):] for k in completions]) -if __name__ == "__main__": - print " ->", pycomplete("") + + if result == "": + if completions: + width = lisp.window_width() - 2 + colum = width / 20 + white = " " + + msg = "" + + counter = 0 + for completion in completions : + if completion.__len__() < 20 : + msg += completion + white[completion.__len__():] + counter += 1 + else : + msg += completion + white[completion.__len__() - 20:] + counter += 2 + + if counter >= colum : + counter = 0 + msg += '\n' + + else: + msg = "no completions for:"+s + + + lisp.message(msg) + return result + + + +def testPycomplete(): + print " ->", pycomplete("") print "sys.get ->", pycomplete("sys.get") print "sy ->", pycomplete("sy") print "sy (sys in context) ->", pycomplete("sy", imports=["import sys"]) print "foo. ->", pycomplete("foo.") - print "Enc (email * imported) ->", + print "Enc (email * imported) ->", print pycomplete("Enc", imports=["from email import *"]) print "E (email * imported) ->", print pycomplete("E", imports=["from email import *"]) @@ -91,6 +169,32 @@ print "Enc ->", pycomplete("Enc") print "E ->", pycomplete("E") + +def test(): + from Pymacs import lisp,utility + log=utility.log + + lisp.switch_to_buffer("*Messages*") + log.info("Testing PyComplete extension... (GG)") + log.info(" ->" + str(pycomplete(""))) + log.info("sys.get ->" + str(pycomplete("sys.get"))) + log.info("sy ->" + str(pycomplete("sy"))) + log.info("sy (sys in context) ->" + str(pycomplete("sy", imports=["import sys"]))) + log.info("foo. ->" + str(pycomplete("foo."))) + log.info("Enc (email * imported) ->" + + str(pycomplete("Enc" , imports=["from email import *"]))) + log.info("E (email * imported) ->"+ str(pycomplete("E" ,imports=["from email import *"]))) + + log.info("Enc ->" + str(pycomplete("Enc"))) + log.info("E ->" + str(pycomplete("E"))) + log.info(" Test finished") + +# Publish it: +interactions = {} +interactions[test]='' + +if __name__ == "__main__": + testPycomplete() # Local Variables : # pymacs-auto-reload : t # End : Pymacs-0.25/contrib/Giorgi/rpcTest.py.in000066400000000000000000000020501175204346300201610ustar00rootroot00000000000000# -*- coding: utf-8 -*- if PYTHON3: import os, xmlrpc.client if __name__ == "__main__": print(("My dir is:"+os.getcwd())) print("Connecting....") s = xmlrpc.client.Server('http://127.0.0.1:9000') print("Sending message...") s.message("hi emacs, I am remoted hosted") print("Opening directory:") s.find_file(os.getcwd()) s.switch_to_buffer("extensions") print("Finding python stuff") s.occur(r'\.py') print("Closing Connection") s.closeConnection() else: import os, xmlrpclib if __name__ == "__main__": print "My dir is:"+os.getcwd() print "Connecting...." s = xmlrpclib.Server('http://127.0.0.1:9000') print "Sending message..." s.message("hi emacs, I am remoted hosted") print "Opening directory:" s.find_file(os.getcwd()) s.switch_to_buffer("extensions") print "Finding python stuff" s.occur(r'\.py') print "Closing Connection" s.closeConnection() Pymacs-0.25/contrib/Giorgi/rpcserver.py.in000066400000000000000000000017331175204346300205570ustar00rootroot00000000000000# -*- coding: utf-8 -*- if PYTHON3: import xmlrpc.client from xmlrpc.server import SimpleXMLRPCServer else: import xmlrpclib from SimpleXMLRPCServer import SimpleXMLRPCServer from Pymacs import lisp import time interactions={} closeConnectionFlag=False def publish_XMLRPC_Server(): lisp.switch_to_buffer("*Messages*") # lisp.delete_other_window() lisp.message("XML RPC Started on port 9000. Ctrl-G to stop") server=SimpleXMLRPCServer(("127.0.0.1",9000)) # We will allow dotted names: server.register_function(closeConnection,"closeConnection") server.register_instance(lisp, True) #server.serve_forever() globals()['closeConnectionFlag']=False while globals()['closeConnectionFlag']==False: server.handle_request() lisp.message("Connection closed") # Support function: used to # stop serving: def closeConnection(): globals()['closeConnectionFlag']=True return 1 interactions[publish_XMLRPC_Server]='' Pymacs-0.25/contrib/Giorgi/setup.py.in000066400000000000000000000005271175204346300177040ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from distutils.core import setup setup(name='Giorgi', version='Pymacs-@VERSION@', description="Giovanni's Pymacs-based tools.", author='Giovanni Giorgi', author_email='jj@objectsroot.com', url='http://blog.objectsroot.com/projects/pymacs', py_modules=['Pymacs.utility']) Pymacs-0.25/contrib/Giorgi/tester.py000066400000000000000000000000631175204346300174400ustar00rootroot00000000000000# Script test for completition import string s="" Pymacs-0.25/contrib/Giorgi/testrun.el000066400000000000000000000005571175204346300176160ustar00rootroot00000000000000 ;; Try something like this: (add-to-list 'load-path "~/Projects/pymacs-2.0/extensions") (pymacs-load "/home/jj/Projects/pymacs-2.0/extensions.pycomplete") (load "~/Projects/pymacs-2.0/extensions/python-mode.el") (load "~/Projects/pymacs-2.0/extensions/pycomplete.el") ;; For playing with menu try: (pymacs-load "/home/jj/Projects/pymacs-2.0/extensions.menudemo") Pymacs-0.25/contrib/Perez/000077500000000000000000000000001175204346300154265ustar00rootroot00000000000000Pymacs-0.25/contrib/Perez/README000066400000000000000000000012471175204346300163120ustar00rootroot00000000000000.. role:: code(strong) .. role:: file(literal) ================================ Various examples of Pymacs usage ================================ .. contents:: .. sectnum:: This page documents the :file:`contrib/Perez/` subdirectory of the Pymacs distribution. It has been contributed by Fernando Pérez on 2002-04-09, in the form of an email to the ``python-list`` forum. Fernando writes: Since the included examples are very sparse, here's some sample code to get you going (some of it is useful, other will at least show you how to do basic stuff). These examples are available as a single Python file, see: http://pymacs.progiciels-bpi.ca/contrib/Perez/pym.py Pymacs-0.25/contrib/Perez/pym.py000066400000000000000000000241331175204346300166100ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Functions accessible from inside Emacs. Filename: pym.py Usage in Emacs: import them with M-x pymacs-load pym And then all functions here become available in Emacs as pym-function-name with every _ replaced by - in the names. """ __author__ = "Fernando Perez. " __license__= "GPL" import re from Pymacs import lisp # lisp is the global which handles lisp-based interaction with the active # Emacs buffer from which any function is called # interactions is a global dict which MUST be updated for each new function # defined which we want to be visible to Emacs. Each function must have an # entry in it with the function as key (the function *object*, NOT its name as # a string) and a string as value. At a minimum, this string will be empty, # but it can contain the names of variables to be read interactively by the # function, lisp-style. # Functions meant to be used internally only (not exposed to Emacs) don't need # an entry in interactions. interactions = {} #*************************************************************************** # WARNING: things from genutils copied verbatim here. For some reason pymacs # does not import other modules correctly (my own, it seems ok with system # stuff). def indent(str,nspaces=4,ntabs=0): """Indent a string a given number of spaces or tabstops. indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. """ ind = '\t'*ntabs+' '*nspaces outstr = '%s%s' % (ind,str.replace('\n','\n'+ind)) if outstr.endswith('\n'+ind): return outstr[:-len(ind)] else: return outstr # End of genutils copy/paste job. #*************************************************************************** # Lisp utility functions, snatched from elsewhere. def clean_undo_after(checkpoint): """\ Remove all intermediate boundaries from the Undo list since CHECKPOINT. """ lisp(""" (let ((undo-list %s)) (if (not (eq buffer-undo-list undo-list)) (let ((cursor buffer-undo-list)) (while (not (eq (cdr cursor) undo-list)) (if (car (cdr cursor)) (setq cursor (cdr cursor)) (setcdr cursor (cdr (cdr cursor))))))) nil) """ % (checkpoint or 'nil')) #*************************************************************************** # Utility functions, none of which need an interactions[] entry. def lisp_obj_info(obj): """Return various details about a lisp object as a string. Useful mainly for debugging purposes.""" info = [obj,obj.__class__,obj.index,type(obj.index),repr(obj)] info = map(str,info) info = '\n'.join(info) return info #--------------------------------------------------------------------------- def lisp_char(lisp_obj): """Return a single character string from a lisp char object. Used to extract characters from their lisp form as obtained in interactive functions with the c code. """ text_form = repr(lisp_obj) try: return re.search(r"'\?(.)'",text_form).group(1) except: return None #--------------------------------------------------------------------------- def is_yes(lisp_obj): """Check whether an interactive lisp character reply is a yes (y/Y)""" try: return lisp_char(lisp_obj).lower() == 'y' except: return 0 #--------------------------------------------------------------------------- def cut_region(mode='string'): """Return the active region and remove it from Emacs. The mode parameter (default 'string') defines whether to return the region as a string or as a list of lines (mode='list'). It is the caller's responsibility to insert the updated text at the end back in the Emacs buffer with a call to lisp.insert(...).""" start, end = lisp.point(), lisp.mark(lisp.t) # BUG: buffer_substring() can't extract regions with dos line endings (\r\n) # It dumps a traceback. region = lisp.buffer_substring(start, end) if mode == 'list': region = region.splitlines() lisp.delete_region(start, end) return region # cut_region() doesn't need an entry in interactions[] b/c it's meant to # be used internally by other functions in this module, not directly # from Emacs #--------------------------------------------------------------------------- def insert_text(text,offset=0): """Insert text in buffer and move cursor to a certain offset. If called with no offset, leaves the cursor at the current position.""" # save undo state so we can roll everything into a single operation for undo checkpoint = lisp.buffer_undo_list.value() user_pos = lisp.point() lisp.insert(text) lisp.goto_char(user_pos+offset) # Collapse all operations into a single one, for Undo. clean_undo_after(checkpoint) #--------------------------------------------------------------------------- def insert_indented_text(text,offset): """Insert indented text in buffer and move cursor to a certain offset.""" # save undo state so we can roll everything into a single operation for undo checkpoint = lisp.buffer_undo_list.value() # figure out if we are indented or not, and adapt text accordingly indent_level = get_line_offset() if indent_level > 0: text = indent(text,indent_level) # perform actual insertion with proper cursor positioning offset += indent_level lisp.beginning_of_line() user_pos = lisp.point() lisp.insert(text) lisp.goto_char(user_pos+offset) # Collapse all operations into a single one, for Undo. clean_undo_after(checkpoint) #--------------------------------------------------------------------------- def get_line_offset(): """Return number of characters cursor is offset from margin. """ user_pos = lisp.point() lisp.beginning_of_line() line_start = lisp.point() lisp.goto_char(user_pos) return user_pos - line_start # end get_line_offset() #--------------------------------------------------------------------------- def newfn_string(name,sep,end,args=''): """Template for a new function definition. Returns the string containing the definition and the integer offset for cursor positioning.""" # prepare text out = '' sep = lisp_char(sep) if sep is not None: out += '#'+sep*77+'\n' out += 'def '+name+'('+args offset = len(out) out += '):\n' out += ' """\n'*2 if is_yes(end): out += '# end '+name+'()\n' return out,offset #*************************************************************************** # 'Public' functions (exposed to Emacs). All these MUST have an interactions[] # entry def bow(): """Break a region replacing all whitespace with newlines. Originally an example in Pymacs' README.""" region = cut_region() lisp.insert('\n'.join(region.split())) # Update interactions[] for functions meant to be visible in Emacs. # Interaction strings follow some funny emacs-lisp conventions, with the first # letter being a code and the rest a prompt. Use `C-h f interactive' in Emacs # to get a description. The simplest one is a prompt for a string, which is # given as a string of the form 's'. # Will print 'name ' in the minibuffer and get a string: #interactions[deft] = 'sNew function name? ' # The c code is for characters, and the Pymacs readme says they are returned # to python as ints, but that doesn't seem to be the case. Instead I'm getting # Pymacs.Lisp objects, which have a repr() of the form "lisp('?')" where # is the returned character. interactions[bow] = '' # Note that trying to set interactions as a function attribute: # bow.interactions = '' # is NOT WORKING. The module loads in Emacs, but no functions are actually # recognized. Tested with Python 2.1, it might work with Python 2.2 #----------------------------------------------------------------------------- def dos2unix(): """Remove DOS line endings from a region. """ # Save undo state so we can roll everything into a single operation for undo checkpoint = lisp.buffer_undo_list.value() region = cut_region('list') lisp.insert('\n'.join(region)+'\n') # Collapse all operations into a single one, for Undo. clean_undo_after(checkpoint) # BUG: it's not working b/c of a bug in lisp.buffer_substring(), so let's not # activate it for now. #interactions[dos2unix] = '' #--------------------------------------------------------------------------- def newfn(name,sep,end,args=''): """Insert a template for a new function definition.""" insert_indented_text(*newfn_string(name,sep,end)) new_template = 'sNew %s name? \n'\ 'cEnter separator (RET for none): \n'\ 'cPrint end marker (y/[N])? ' interactions[newfn] = new_template % 'function' #----------------------------------------------------------------------------- def newweave(name,sep,end,use_blitz): """Insert a template for a new weave function definition. """ blitz,ending = '','' if is_yes(use_blitz): blitz = ",type_factories = blitz_type_factories" if is_yes(end): ending = "\n# end %s()" % (name,) head,offset = newfn_string(name,sep,0) head += \ ''' code = \\ """ """ return weave.inline(code,[]%(blitz)s)%(ending)s ''' % locals() insert_indented_text(head,offset) interactions[newweave] = new_template % 'weave function' interactions[newweave] += '\ncUse blitz type factories (y/[N])? ' #--------------------------------------------------------------------------- def newmeth(name,sep,end): """Insert a template for a new method definition. """ insert_indented_text(*newfn_string(name,sep,end,'self')) interactions[newmeth] = new_template % 'method' #--------------------------------------------------------------------------- def newclass(name,sep,end): """Template for new class definition. """ out = ('class %s:\n' % (name,)) + (' """\n'*2) + '\n' offset = get_line_offset()+len(out) + len (" def __init__(self") new_str = newfn_string('__init__',None,None,'self')[0] out += indent(new_str) if is_yes(end): out += '# end class '+name+'\n' insert_indented_text(out,offset) interactions[newclass] = new_template % 'class' Pymacs-0.25/contrib/README000066400000000000000000000003241175204346300152200ustar00rootroot00000000000000.. role:: file(literal) ==================================== Contents of Pymacs' :file:`contrib/` ==================================== Each subdirectory of :file`contrib/` contains its own :file:`README` file. Pymacs-0.25/contrib/Winkler/000077500000000000000000000000001175204346300157545ustar00rootroot00000000000000Pymacs-0.25/contrib/Winkler/README000066400000000000000000000042611175204346300166370ustar00rootroot00000000000000.. role:: code(strong) .. role:: file(literal) ================================ A simple example of Pymacs usage ================================ .. contents:: .. sectnum:: This page documents the :file:`contrib/Winkler/` subdirectory of the Pymacs distribution. The problem =========== This problem has been submitted by Paul Winkler, and the text below has been reformatted from our email exchanges. Let's say I have a module, call it :file:`manglers.py`, containing this simple python function:: def break_on_whitespace(some_string): words = some_string.split() return '\n'.join(words) The goal is telling Emacs about this function so that I can call it on a region of text and replace the region with the result of the call. And bind this action to a key, of course, let's say :code:`[f7]`. The Emacs buffer ought to be handled in some way. If this is not on the Emacs Lisp side, it has to be on the Python side, but we cannot escape handling the buffer. So, there is an equilibrium in the work to do for the user, that could be displaced towards Emacs Lisp or towards Python. Python side =========== Here is a first draft for the Python side of the problem:: from Pymacs import lisp def break_on_whitespace(): start = lisp.point() end = lisp.mark(True) if start > end: start, end = end, start text = lisp.buffer_substring(start, end) words = text.split() replacement = '\n'.join(words) lisp.delete_region(start, end) lisp.insert(replacement) interactions = {break_on_whitespace: ''} For various stylistic reasons, this could be rewritten into:: from Pymacs import lisp interactions = {} def break_on_whitespace(): start, end = lisp.point(), lisp.mark(True) words = lisp.buffer_substring(start, end).split() lisp.delete_region(start, end) lisp.insert('\n'.join(words)) interactions[break_on_whitespace] = '' The above relies, in particular, on the fact that for those Emacs Lisp functions used here, ``start`` and ``end`` may be given in any order. Emacs side ========== On the Emacs side, one would do:: (pymacs-load "manglers") (global-set-key [f7] 'manglers-break-on-whitespace) Pymacs-0.25/contrib/rebox/000077500000000000000000000000001175204346300154605ustar00rootroot00000000000000Pymacs-0.25/contrib/rebox/ChangeLog000066400000000000000000000122351175204346300172350ustar00rootroot000000000000002008-01-21 François Pinard * rebox.py: Support module textwrap. (Refiller, Refiller_Gnu_Fmt, Refiller_Textwrap and Refiller_Dumb): New classes. (refill_lines): Function split to use the above. 2002-01-29 François Pinard * Pymacs/rebox.py: Use an interactions map instead of the interaction attribute, so it works with earlier Python versions. * Pymacs/rebox.py: Import lisp and Let from Pymacs. 2002-01-13 François Pinard * Pymacs/rebox.py (Emacs_Rebox.emacs_engine): Expand flag value, when it is neither the - symbol nor a number. 2002-01-08 François Pinard * Pymacs/rebox.py (Template.build): Subtract margin from width just before actually rebuilding the box. Reported by Paul Provost. 2002-01-07 François Pinard * Pymacs/rebox.py (main): Implement -v option. * Pymacs/rebox.py (pymacs_load_hook): Declare set_default_style. * Pymacs/rebox.py (Emacs_Rebox.clean_undo_after): Debugged. * Pymacs/rebox.py (Template): New class. Reorgnise all code. * Pymacs/rebox.py (engine): Moved out of Rebox class. * Pymacs/rebox.py (Rebox, Batch_Rebox): Deleted, as they got empty. * Pymacs/rebox.py (Emacs_Rebox.clean_undo_after): Rewrite in Lisp. 2002-01-06 François Pinard * rebox: New file. 2002-01-03 François Pinard * Pymacs/rebox.py: New file, translated from Libit/rebox.el. 2000-09-28 François Pinard * rebox.el: Replace statistical heuristics for box style recognition by more precise checks and explicit priorities between styles. To do so, add weights to rebox-templates, replace rebox-building-data by rebox-style-data holding regexps, delete rebox-recognition-data. * rebox.el (rebox-regexp-ruler): New function. (rebox-regexp-quote): Add matching for following white space. Don't force two characters on each middle line, nor in blank rulers. Reported by Paul Provost. 2000-04-28 François Pinard * rebox.el (rebox-guess-style): When two styles have equal weight, retain the highest numbered, as it probably is the richest. Otherwise, simple C++ comments end up with a single slash. Reported by Akim Demaille. 2000-04-19 François Pinard * rebox.el: Reorganize from bottom-up into top-down. (taarna-mode): Deleted. 2000-04-18 François Pinard * rebox.el (rebox-show-style, rebox-help-string-for-language, rebox-help-string-for-quality, rebox-help-string-for-type): Deleted. (rebox-rstrip, rebox-regexp-quote, rebox-unbuild): New functions. (rebox-build): New name for rebox-reconstruct. 2000-04-15 François Pinard * rebox.el (rebox-guess-style): New function. (rebox-engine): Use it. Simplified by using template information. 2000-04-14 François Pinard * rebox.el (rebox-templates): New variable. (rebox-register-template): New function. (rebox-reconstruct): Much simplified by using the above. 2000-04-12 François Pinard * rebox.el: Rework the initial documentation block. (rebox-reconstruct): Guarantee newline at end for style 241. Reported by Marc Feeley and Paul Provost. 2000-02-22 François Pinard * rebox.el: Little speed cleanup. Avoid looking-at when easy. 2000-02-10 François Pinard * rebox.el: Adjust comment to suggest add-hook instead of setq. Reported by Akim Demaille. 2000-01-30 François Pinard * rebox.el: Prefer when, unless and cond over if and progn. Combine successive setq. * rebox.el (rebox-engine): Recognise quality for shell boxes. Reported by Akim Demaille. 1999-06-30 François Pinard * rebox.el: Add GPL comment. Reported by Paul Eggert. 1998-03-28 François Pinard * rebox.el (rebox-reconstruct): Refill a closing */ with the rest. Do not add spaces to a line which is otherwise empty. 1997-12-01 François Pinard * rebox.el (rebox-engine): Simplify two regexps, for XEmacs. Reported by Ulrich Drepper. 1997-02-17 François Pinard * rebox.el (rebox-reconstruct): Ensure indent-tabs-mode is nil. 1997-02-14 François Pinard * rebox.el: Corrected a bug demonstrated as the beginning line of a paragraph spuriously jumping right spuriously. The full match of the beginning of comment was replaced by spaces on the initial line, while only \1 needed replacement. This shortened this line, causing later nasty effects. 1996-07-10 François Pinard * rebox.el: Recognise style 241, so margin does not get doubled. Reported by Marc Feeley. 1996-07-09 François Pinard * rebox.el: Use symbolic constants for language, quality and type. 1996-06-09 François Pinard * rebox.el (rebox-find-and-narrow): Take care of a missing end of line after a comment being at end of buffer. Reported by Ulrich Drepper. Pymacs-0.25/contrib/rebox/Pymacs/000077500000000000000000000000001175204346300167145ustar00rootroot00000000000000Pymacs-0.25/contrib/rebox/Pymacs/__init__.py.in000066400000000000000000000021201175204346300214250ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright © 2002, 2003 Progiciels Bourbeau-Pinard inc. # François Pinard , 2002. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ """\ Interface between Emacs Lisp and Python - Module initialisation. A few symbols are moved in here so they appear to be defined at this level. """ from Pymacs import Let, lisp # Identification of version. package = 'Pymacs' version = '@VERSION@' Pymacs-0.25/contrib/rebox/Pymacs/rebox.py000066400000000000000000001163441175204346300204160ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright © 1991-1998, 2000, 2002, 2003 Progiciels Bourbeau-Pinard inc. # François Pinard , 1991-04. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """\ Handling of boxed comments in various box styles. The user documentation for this tool may be found at: http://pymacs.progiciels-bpi.ca/rebox.html """ ## Note: a double hash comment introduces a group of functions or methods. __metatype__ = type import re, sys ## Batch specific features. def main(*arguments): refill = True style = None tabify = False verbose = False width = 79 import getopt options, arguments = getopt.getopt(arguments, 'ns:tvw:', ['help']) for option, value in options: if option == '--help': sys.stdout.write(__doc__) sys.exit(0) elif option == '-n': refill = False elif option == '-s': style = int(value) elif option == '-t': tabify = True elif option == '-v': verbose = True elif option == '-w': width = int(value) if len(arguments) == 0: text = sys.stdin.read() elif len(arguments) == 1: handle = file(arguments[0]) text = handle.read() handle.close() else: sys.stderr.write("Invalid usage, try `rebox --help' for help.\n") sys.exit(1) old_style, new_style, text, position = engine( text, style=style, width=width, refill=refill, tabify=tabify) if text is None: sys.stderr.write("* Cannot rebox to style %d.\n" % new_style) sys.exit(1) sys.stdout.write(text) if verbose: if old_style == new_style: sys.stderr.write("Reboxed with style %d.\n" % old_style) else: sys.stderr.write("Reboxed from style %d to %d.\n" % (old_style, new_style)) ## Emacs specific features. def pymacs_load_hook(): global interactions, lisp, Let, region, comment, set_default_style from Pymacs import lisp, Let emacs_rebox = Emacs_Rebox() # Declare functions for Emacs to import. interactions = {} region = emacs_rebox.region interactions[region] = 'P' comment = emacs_rebox.comment interactions[comment] = 'P' set_default_style = emacs_rebox.set_default_style class Emacs_Rebox: def __init__(self): self.default_style = None def set_default_style(self, style): """\ Set the default style to STYLE. """ self.default_style = style def region(self, flag): """\ Rebox the boxed comment in the current region, obeying FLAG. """ self.emacs_engine(flag, self.find_region) def comment(self, flag): """\ Rebox the surrounding boxed comment, obeying FLAG. """ self.emacs_engine(flag, self.find_comment) def emacs_engine(self, flag, find_limits): """\ Rebox text while obeying FLAG. Call FIND_LIMITS to discover the extent of the boxed comment. """ # `C-u -' means that box style is to be decided interactively. if flag == lisp['-']: flag = self.ask_for_style() # If FLAG is zero or negative, only change default box style. if isinstance(flag, int) and flag <= 0: self.default_style = -flag lisp.message("Default style set to %d" % -flag) return # Decide box style and refilling. if flag is None: style = self.default_style refill = True elif isinstance(flag, int): if self.default_style is None: style = flag else: style = merge_styles(self.default_style, flag) refill = True else: flag = flag.copy() if isinstance(flag, list): style = self.default_style refill = False else: lisp.error("Unexpected flag value %s" % flag) # Prepare for reboxing. lisp.message("Reboxing...") checkpoint = lisp.buffer_undo_list.value() start, end = find_limits() text = lisp.buffer_substring(start, end) width = lisp.fill_column.value() tabify = lisp.indent_tabs_mode.value() is not None point = lisp.point() if start <= point < end: position = point - start else: position = None # Rebox the text and replace it in Emacs buffer. old_style, new_style, text, position = engine( text, style=style, width=width, refill=refill, tabify=tabify, position=position) if text is None: lisp.error("Cannot rebox to style %d" % new_style) lisp.delete_region(start, end) lisp.insert(text) if position is not None: lisp.goto_char(start + position) # Collapse all operations into a single one, for Undo. self.clean_undo_after(checkpoint) # We are finished, tell the user. if old_style == new_style: lisp.message("Reboxed with style %d" % old_style) else: lisp.message("Reboxed from style %d to %d" % (old_style, new_style)) def ask_for_style(self): """\ Request the style interactively, using the minibuffer. """ language = quality = type = None while language is None: lisp.message("\ Box language is 100-none, 200-/*, 300-//, 400-#, 500-;, 600-%%") key = lisp.read_char() if key >= ord('0') and key <= ord('6'): language = key - ord('0') while quality is None: lisp.message("\ Box quality/width is 10-simple/1, 20-rounded/2, 30-starred/3 or 40-starred/4") key = lisp.read_char() if key >= ord('0') and key <= ord('4'): quality = key - ord('0') while type is None: lisp.message("\ Box type is 1-opened, 2-half-single, 3-single, 4-half-double or 5-double") key = lisp.read_char() if key >= ord('0') and key <= ord('5'): type = key - ord('0') return 100*language + 10*quality + type def find_region(self): """\ Return the limits of the region. """ return lisp.point(), lisp.mark(lisp.t) def find_comment(self): """\ Find and return the limits of the block of comments following or enclosing the cursor, or return an error if the cursor is not within such a block of comments. Extend it as far as possible in both directions. """ let = Let().push_excursion() try: # Find the start of the current or immediately following comment. lisp.beginning_of_line() lisp.skip_chars_forward(' \t\n') lisp.beginning_of_line() if not language_matcher[0](self.remainder_of_line()): temp = lisp.point() if not lisp.re_search_forward('\\*/', None, lisp.t): lisp.error("outside any comment block") lisp.re_search_backward('/\\*') if lisp.point() > temp: lisp.error("outside any comment block") temp = lisp.point() lisp.beginning_of_line() lisp.skip_chars_forward(' \t') if lisp.point() != temp: lisp.error("text before start of comment") lisp.beginning_of_line() start = lisp.point() language = guess_language(self.remainder_of_line()) # Find the end of this comment. if language == 2: lisp.search_forward('*/') if not lisp.looking_at('[ \t]*$'): lisp.error("text after end of comment") lisp.end_of_line() if lisp.eobp(): lisp.insert('\n') else: lisp.forward_char(1) end = lisp.point() # Try to extend the comment block backwards. lisp.goto_char(start) while not lisp.bobp(): if language == 2: lisp.skip_chars_backward(' \t\n') if not lisp.looking_at('[ \t]*\n[ \t]*/\\*'): break if lisp.point() < 2: break lisp.backward_char(2) if not lisp.looking_at('\\*/'): break lisp.re_search_backward('/\\*') temp = lisp.point() lisp.beginning_of_line() lisp.skip_chars_forward(' \t') if lisp.point() != temp: break lisp.beginning_of_line() else: lisp.previous_line(1) if not language_matcher[language](self.remainder_of_line()): break start = lisp.point() # Try to extend the comment block forward. lisp.goto_char(end) while language_matcher[language](self.remainder_of_line()): if language == 2: lisp.re_search_forward('[ \t]*/\\*') lisp.re_search_forward('\\*/') if lisp.looking_at('[ \t]*$'): lisp.beginning_of_line() lisp.forward_line(1) end = lisp.point() else: lisp.forward_line(1) end = lisp.point() return start, end finally: let.pops() def remainder_of_line(self): """\ Return all characters between point and end of line in Emacs buffer. """ return lisp('''\ (buffer-substring (point) (save-excursion (skip-chars-forward "^\n") (point))) ''') def clean_undo_after_old(self, checkpoint): """\ Remove all intermediate boundaries from the Undo list since CHECKPOINT. """ # Declare some Lisp functions. car = lisp.car cdr = lisp.cdr eq = lisp.eq setcdr = lisp.setcdr # Remove any `nil' delimiter recently added to the Undo list. cursor = lisp.buffer_undo_list.value() if not eq(cursor, checkpoint): tail = cdr(cursor) while not eq(tail, checkpoint): if car(tail): cursor = tail tail = cdr(cursor) else: tail = cdr(tail) setcdr(cursor, tail) def clean_undo_after(self, checkpoint): """\ Remove all intermediate boundaries from the Undo list since CHECKPOINT. """ lisp(""" (let ((undo-list %s)) (if (not (eq buffer-undo-list undo-list)) (let ((cursor buffer-undo-list)) (while (not (eq (cdr cursor) undo-list)) (if (car (cdr cursor)) (setq cursor (cdr cursor)) (setcdr cursor (cdr (cdr cursor))))))) nil) """ % (checkpoint or 'nil')) ## Reboxing main control. def engine(text, style=None, width=79, refill=True, tabify=False, position=None): """\ Add, delete or adjust a boxed comment held in TEXT, according to STYLE. STYLE values are explained at beginning of this file. Any zero attribute in STYLE indicates that the corresponding attribute should be recovered from the currently existing box. Produced lines will not go over WIDTH columns if possible, if refilling gets done. But if REFILL is false, WIDTH is ignored. If TABIFY is true, the beginning of produced lines will have spaces replace by TABs. POSITION is either None, or a character position within TEXT. Returns four values: the old box style, the new box style, the reformatted text, and either None or the adjusted value of POSITION in the new text. The reformatted text is returned as None if the requested style does not exist. """ last_line_complete = text and text[-1] == '\n' if last_line_complete: text = text[:-1] lines = text.expandtabs().split('\n') # Decide about refilling and the box style to use. new_style = 111 old_template = guess_template(lines) new_style = merge_styles(new_style, old_template.style) if style is not None: new_style = merge_styles(new_style, style) new_template = template_registry.get(new_style) # Interrupt processing if STYLE does not exist. if not new_template: return old_template.style, new_style, None, None # Remove all previous comment marks, and left margin. if position is not None: marker = Marker() marker.save_position(text, position, old_template.characters()) lines, margin = old_template.unbuild(lines) # Ensure only one white line between paragraphs. counter = 1 while counter < len(lines) - 1: if lines[counter] == '' and lines[counter-1] == '': del lines[counter] else: counter = counter + 1 # Rebuild the boxed comment. lines = new_template.build(lines, width, refill, margin) # Retabify to the left only. if tabify: for counter in range(len(lines)): tabs = len(re.match(' *', lines[counter]).group()) / 8 lines[counter] = '\t' * tabs + lines[counter][8*tabs:] # Restore the point position. text = '\n'.join(lines) if last_line_complete: text = text + '\n' if position is not None: position = marker.get_position(text, new_template.characters()) return old_template.style, new_style, text, position def guess_language(line): """\ Guess the language in use for LINE. """ for language in range(len(language_matcher) - 1, 1, -1): if language_matcher[language](line): return language return 1 def guess_template(lines): """\ Find the heaviest box template matching LINES. """ best_template = None for template in list(template_registry.values()): if best_template is None or template > best_template: if template.match(lines): best_template = template return best_template def left_margin_size(lines): """\ Return the width of the left margin for all LINES. Ignore white lines. """ margin = None for line in lines: counter = len(re.match(' *', line).group()) if counter != len(line): if margin is None or counter < margin: margin = counter if margin is None: margin = 0 return margin def merge_styles(original, update): """\ Return style attributes as per ORIGINAL, in which attributes have been overridden by non-zero corresponding style attributes from UPDATE. """ style = [original / 100, original / 10 % 10, original % 10] merge = update / 100, update / 10 % 10, update % 10 for counter in range(3): if merge[counter]: style[counter] = merge[counter] return 100*style[0] + 10*style[1] + style[2] ## Refilling logic. def refill_lines(lines, width, cached_refiller=[]): """\ Refill LINES, trying to not produce lines having more than WIDTH columns. """ if not cached_refiller: for Refiller in Refiller_Gnu_Fmt, Refiller_Textwrap, Refiller_Dumb: refiller = Refiller() new_lines = refiller.fill(lines, width) if new_lines is not None: cached_refiller.append(refiller) return new_lines return cached_refiller[0].fill(lines, width) class Refiller: available = True def fill(self, lines, width): if self.available: new_lines = [] start = 0 while start < len(lines) and not lines[start]: start = start + 1 end = start while end < len(lines): while end < len(lines) and lines[end]: end = end + 1 new_lines = new_lines + self.fill_paragraph(lines[start:end], width) while end < len(lines) and not lines[end]: end = end + 1 if end < len(lines): new_lines.append('') start = end return new_lines class Refiller_Gnu_Fmt(Refiller): """\ Use both Knuth algorithm and protection for full stops at end of sentences. """ def fill(self, lines, width): if self.available: import tempfile, os name = tempfile.mktemp() handle = file(name, 'w') handle.write('\n'.join(lines) + '\n') handle.close() handle = os.popen('fmt -cuw %d %s' % (width, name)) text = handle.read() os.remove(name) if handle.close() is None: return [line.expandtabs() for line in text.split('\n')[:-1]] class Refiller_Textwrap(Refiller): """\ No Knuth algorithm, but protection for full stops at end of sentences. """ def __init__(self): try: from textwrap import TextWrapper except ImportError: self.available = False else: self.wrapper = TextWrapper(fix_sentence_endings=1) def fill_paragraph(self, lines, width): # FIXME: This one fills indented lines more aggressively than the # dumb refiller. I'm not sure what it the best thing to do, but # ideally, all refillers should behave more or less the same way. self.wrapper.width = width prefix = ' ' * left_margin_size(lines) self.wrapper.initial_indent = prefix self.wrapper.subsequent_indent = prefix return self.wrapper.wrap(' '.join(lines)) class Refiller_Dumb(Refiller): """\ No Knuth algorithm, nor even protection for full stops at end of sentences. """ def fill_paragraph(self, lines, width): margin = left_margin_size(lines) prefix = ' ' * margin new_lines = [] new_line = '' for line in lines: counter = len(line) - len(line.lstrip()) if counter > margin: if new_line: new_lines.append(prefix + new_line) new_line = '' indent = ' ' * (counter - margin) else: indent = '' for word in line.split(): if new_line: if len(new_line) + 1 + len(word) > width: new_lines.append(prefix + new_line) new_line = word else: new_line = new_line + ' ' + word else: new_line = indent + word indent = '' if new_line: new_lines.append(prefix + new_line) return new_lines ## Marking logic. class Marker: """\ Heuristics to simulate a marker while reformatting boxes. """ def save_position(self, text, position, ignorable): """\ Given a TEXT and a POSITION in that text, save the adjusted position by faking that all IGNORABLE characters before POSITION were removed. """ ignore = {} for character in ' \t\r\n' + ignorable: ignore[character] = None counter = 0 for character in text[:position]: if character in ignore: counter = counter + 1 self.position = position - counter def get_position(self, text, ignorable, latest=0): """\ Given a TEXT, return the value that would yield the currently saved position, if it was saved by `save_position' with IGNORABLE. Unless the position lies within a series of ignorable characters, LATEST has no effect in practice. If LATEST is true, return the biggest possible value instead of the smallest. """ ignore = {} for character in ' \t\r\n' + ignorable: ignore[character] = None counter = 0 position = 0 if latest: for character in text: if character in ignore: counter = counter + 1 else: if position == self.position: break position = position + 1 elif self.position > 0: for character in text: if character in ignore: counter = counter + 1 else: position = position + 1 if position == self.position: break return position + counter ## Template processing. class Template: def __init__(self, style, weight, lines): """\ Digest and register a single template. The template is numbered STYLE, has a parsing WEIGHT, and is described by one to three LINES. STYLE should be used only once through all `declare_template' calls. One of the lines should contain the substring `box' to represent the comment to be boxed, and if three lines are given, `box' should appear in the middle one. Lines containing only spaces are implied as necessary before and after the the `box' line, so we have three lines. Normally, all three template lines should be of the same length. If the first line is shorter, it represents a start comment string to be bundled within the first line of the comment text. If the third line is shorter, it represents an end comment string to be bundled at the end of the comment text, and refilled with it. """ assert style not in template_registry, \ "Style %d defined more than once" % style self.style = style self.weight = weight # Make it exactly three lines, with `box' in the middle. start = lines[0].find('box') if start >= 0: line1 = None line2 = lines[0] if len(lines) > 1: line3 = lines[1] else: line3 = None else: start = lines[1].find('box') if start >= 0: line1 = lines[0] line2 = lines[1] if len(lines) > 2: line3 = lines[2] else: line3 = None else: assert 0, "Erroneous template for %d style" % style end = start + len('box') # Define a few booleans. self.merge_nw = line1 is not None and len(line1) < len(line2) self.merge_se = line3 is not None and len(line3) < len(line2) # Define strings at various cardinal directions. if line1 is None: self.nw = self.nn = self.ne = None elif self.merge_nw: self.nw = line1 self.nn = self.ne = None else: if start > 0: self.nw = line1[:start] else: self.nw = None if line1[start] != ' ': self.nn = line1[start] else: self.nn = None if end < len(line1): self.ne = line1[end:].rstrip() else: self.ne = None if start > 0: self.ww = line2[:start] else: self.ww = None if end < len(line2): self.ee = line2[end:] else: self.ee = None if line3 is None: self.sw = self.ss = self.se = None elif self.merge_se: self.sw = self.ss = None self.se = line3.rstrip() else: if start > 0: self.sw = line3[:start] else: self.sw = None if line3[start] != ' ': self.ss = line3[start] else: self.ss = None if end < len(line3): self.se = line3[end:].rstrip() else: self.se = None # Define parsing regexps. if self.merge_nw: self.regexp1 = re.compile(' *' + regexp_quote(self.nw) + '.*$') elif self.nw and not self.nn and not self.ne: self.regexp1 = re.compile(' *' + regexp_quote(self.nw) + '$') elif self.nw or self.nn or self.ne: self.regexp1 = re.compile( ' *' + regexp_quote(self.nw) + regexp_ruler(self.nn) + regexp_quote(self.ne) + '$') else: self.regexp1 = None if self.ww or self.ee: self.regexp2 = re.compile( ' *' + regexp_quote(self.ww) + '.*' + regexp_quote(self.ee) + '$') else: self.regexp2 = None if self.merge_se: self.regexp3 = re.compile('.*' + regexp_quote(self.se) + '$') elif self.sw and not self.ss and not self.se: self.regexp3 = re.compile(' *' + regexp_quote(self.sw) + '$') elif self.sw or self.ss or self.se: self.regexp3 = re.compile( ' *' + regexp_quote(self.sw) + regexp_ruler(self.ss) + regexp_quote(self.se) + '$') else: self.regexp3 = None # Save results. template_registry[style] = self def __cmp__(self, other): return cmp(self.weight, other.weight) def characters(self): """\ Return a string of characters which may be used to draw the box. """ characters = '' for text in (self.nw, self.nn, self.ne, self.ww, self.ee, self.sw, self.ss, self.se): if text: for character in text: if character not in characters: characters = characters + character return characters def match(self, lines): """\ Returns true if LINES exactly match this template. """ start = 0 end = len(lines) if self.regexp1 is not None: if start == end or not self.regexp1.match(lines[start]): return 0 start = start + 1 if self.regexp3 is not None: if end == 0 or not self.regexp3.match(lines[end-1]): return 0 end = end - 1 if self.regexp2 is not None: for line in lines[start:end]: if not self.regexp2.match(line): return 0 return 1 def unbuild(self, lines): """\ Remove all comment marks from LINES, as hinted by this template. Returns the cleaned up set of lines, and the size of the left margin. """ margin = left_margin_size(lines) # Remove box style marks. start = 0 end = len(lines) if self.regexp1 is not None: lines[start] = unbuild_clean(lines[start], self.regexp1) start = start + 1 if self.regexp3 is not None: lines[end-1] = unbuild_clean(lines[end-1], self.regexp3) end = end - 1 if self.regexp2 is not None: for counter in range(start, end): lines[counter] = unbuild_clean(lines[counter], self.regexp2) # Remove the left side of the box after it turned into spaces. delta = left_margin_size(lines) - margin for counter in range(len(lines)): lines[counter] = lines[counter][delta:] # Remove leading and trailing white lines. start = 0 end = len(lines) while start < end and lines[start] == '': start = start + 1 while end > start and lines[end-1] == '': end = end - 1 return lines[start:end], margin def build(self, lines, width, refill, margin): """\ Put LINES back into a boxed comment according to this template, after having refilled them if REFILL. The box should start at column MARGIN, and the total size of each line should ideally not go over WIDTH. """ # Merge a short end delimiter now, so it gets refilled with text. if self.merge_se: if lines: lines[-1] = lines[-1] + ' ' + self.se else: lines = [self.se] # Reduce WIDTH according to left and right inserts, then refill. if self.ww: width = width - len(self.ww) if self.ee: width = width - len(self.ee) if refill: lines = refill_lines(lines, width) # Reduce WIDTH further according to the current right margin, # and excluding the left margin. maximum = 0 for line in lines: if line: if line[-1] in '.!?': length = len(line) + 1 else: length = len(line) if length > maximum: maximum = length width = maximum - margin # Construct the top line. if self.merge_nw: lines[0] = ' ' * margin + self.nw + lines[0][margin:] start = 1 elif self.nw or self.nn or self.ne: if self.nn: line = self.nn * width else: line = ' ' * width if self.nw: line = self.nw + line if self.ne: line = line + self.ne lines.insert(0, (' ' * margin + line).rstrip()) start = 1 else: start = 0 # Construct all middle lines. for counter in range(start, len(lines)): line = lines[counter][margin:] line = line + ' ' * (width - len(line)) if self.ww: line = self.ww + line if self.ee: line = line + self.ee lines[counter] = (' ' * margin + line).rstrip() # Construct the bottom line. if self.sw or self.ss or self.se and not self.merge_se: if self.ss: line = self.ss * width else: line = ' ' * width if self.sw: line = self.sw + line if self.se and not self.merge_se: line = line + self.se lines.append((' ' * margin + line).rstrip()) return lines def regexp_quote(text): """\ Return a regexp matching TEXT without its surrounding space, maybe followed by spaces. If STRING is nil, return the empty regexp. Unless spaces, the text is nested within a regexp parenthetical group. """ if text is None: return '' if text == ' ' * len(text): return ' *' return '(' + re.escape(text.strip()) + ') *' def regexp_ruler(character): """\ Return a regexp matching two or more repetitions of CHARACTER, maybe followed by spaces. Is CHARACTER is nil, return the empty regexp. Unless spaces, the ruler is nested within a regexp parenthetical group. """ if character is None: return '' if character == ' ': return ' +' return '(' + re.escape(character + character) + '+) *' def unbuild_clean(line, regexp): """\ Return LINE with all parenthetical groups in REGEXP erased and replaced by an equivalent number of spaces, except for trailing spaces, which get removed. """ match = re.match(regexp, line) groups = match.groups() for counter in range(len(groups)): if groups[counter] is not None: start, end = match.span(1 + counter) line = line[:start] + ' ' * (end - start) + line[end:] return line.rstrip() ## Template data. # Matcher functions for a comment start, indexed by numeric LANGUAGE. language_matcher = [] for pattern in (r' *(/\*|//+|#+|;+|%+)', r'', # 1 r' */\*', # 2 r' *//+', # 3 r' *#+', # 4 r' *;+', # 5 r' *%+'): # 6 language_matcher.append(re.compile(pattern).match) # Template objects, indexed by numeric style. template_registry = {} def make_generic(style, weight, lines): """\ Add various language digit to STYLE and generate one template per language, all using the same WEIGHT. Replace `?' in LINES accordingly. """ for language, character in ((300, '/'), # C++ style comments (400, '#'), # scripting languages (500, ';'), # Lisp and assembler (600, '%')): # TeX and PostScript new_style = language + style if 310 < new_style <= 319: # Disallow quality 10 with C++. continue new_lines = [] for line in lines: new_lines.append(line.replace('?', character)) Template(new_style, weight, new_lines) # Generic programming language templates. make_generic(11, 115, ('? box',)) make_generic(12, 215, ('? box ?', '? --- ?')) make_generic(13, 315, ('? --- ?', '? box ?', '? --- ?')) make_generic(14, 415, ('? box ?', '???????')) make_generic(15, 515, ('???????', '? box ?', '???????')) make_generic(16, 615, ('?????', '? box', '?????')) make_generic(17, 715, ('?????', '? box', '?????')) make_generic(21, 125, ('?? box',)) make_generic(22, 225, ('?? box ??', '?? --- ??')) make_generic(23, 325, ('?? --- ??', '?? box ??', '?? --- ??')) make_generic(24, 425, ('?? box ??', '?????????')) make_generic(25, 525, ('?????????', '?? box ??', '?????????')) make_generic(26, 526, ('??????', '?? box', '??????')) make_generic(27, 527, ('??????', '?? box', '??????')) make_generic(31, 135, ('??? box',)) make_generic(32, 235, ('??? box ???', '??? --- ???')) make_generic(33, 335, ('??? --- ???', '??? box ???', '??? --- ???')) make_generic(34, 435, ('??? box ???', '???????????')) make_generic(35, 535, ('???????????', '??? box ???', '???????????')) make_generic(36, 536, ('???????', '??? box', '???????')) make_generic(37, 537, ('???????', '??? box', '???????')) make_generic(41, 145, ('???? box',)) make_generic(42, 245, ('???? box ????', '???? --- ????')) make_generic(43, 345, ('???? --- ????', '???? box ????', '???? --- ????')) make_generic(44, 445, ('???? box ????', '?????????????')) make_generic(45, 545, ('?????????????', '???? box ????', '?????????????')) make_generic(46, 546, ('????????', '???? box', '????????')) make_generic(47, 547, ('????????', '???? box', '????????')) # Textual (non programming) templates. Template(111, 113, ('box',)) Template(112, 213, ('| box |', '+-----+')) Template(113, 313, ('+-----+', '| box |', '+-----+')) Template(114, 413, ('| box |', '*=====*')) Template(115, 513, ('*=====*', '| box |', '*=====*')) Template(116, 613, ('+----', '| box', '+----')) Template(117, 713, ('*====', '| box', '*====')) Template(121, 123, ('| box |',)) Template(122, 223, ('| box |', '`-----\'')) Template(123, 323, ('.-----.', '| box |', '`-----\'')) Template(124, 423, ('| box |', '\\=====/')) Template(125, 523, ('/=====\\', '| box |', '\\=====/')) Template(126, 623, ('.----', '| box', '`----')) Template(127, 723, ('/====', '| box', '\\====')) Template(141, 143, ('| box ',)) Template(142, 243, ('* box *', '*******')) Template(143, 343, ('*******', '* box *', '*******')) Template(144, 443, ('X box X', 'XXXXXXX')) Template(145, 543, ('XXXXXXX', 'X box X', 'XXXXXXX')) Template(146, 643, ('*****', '* box', '*****')) Template(147, 743, ('XXXXX', 'X box', 'XXXXX')) # C language templates. Template(211, 118, ('/* box */',)) Template(212, 218, ('/* box */', '/* --- */')) Template(213, 318, ('/* --- */', '/* box */', '/* --- */')) Template(214, 418, ('/* box */', '/* === */')) Template(215, 518, ('/* === */', '/* box */', '/* === */')) Template(216, 618, ('/* ---', ' box', ' ---*/')) Template(217, 718, ('/* ===', ' box', ' ===*/')) Template(221, 128, ('/* ', ' box', '*/')) Template(222, 228, ('/* .', '| box |', '`----*/')) Template(223, 328, ('/*----.', '| box |', '`----*/')) Template(224, 428, ('/* \\', '| box |', '\\====*/')) Template(225, 528, ('/*====\\', '| box |', '\\====*/')) Template(226, 628, ('/*---', '| box', '`----*/')) Template(227, 728, ('/*===', '| box', '\\====*/')) Template(231, 138, ('/* ', ' | box', ' */ ')) Template(232, 238, ('/* ', ' | box | ', ' *-----*/')) Template(233, 338, ('/*-----* ', ' | box | ', ' *-----*/')) Template(234, 438, ('/* box */', '/*-----*/')) Template(235, 538, ('/*-----*/', '/* box */', '/*-----*/')) Template(236, 638, ('/*---- ', ' | box ', ' *----*/')) Template(237, 738, ('/*----', ' box', ' ----*/')) Template(241, 148, ('/* ', ' * box', ' */ ')) Template(242, 248, ('/* * ', ' * box * ', ' *******/')) Template(243, 348, ('/******* ', ' * box * ', ' *******/')) Template(244, 448, ('/* box */', '/*******/')) Template(245, 548, ('/*******/', '/* box */', '/*******/')) Template(246, 648, ('/******* ', ' * box * ', ' *******/')) Template(247, 748, ('/****', ' box', ' *****/')) Template(251, 158, ('/* ', ' * box', ' */ ')) if __name__ == '__main__': main(*sys.argv[1:]) Pymacs-0.25/contrib/rebox/README000066400000000000000000000416531175204346300163510ustar00rootroot00000000000000.. role:: code(strong) .. role:: file(literal) ================================================ Handling of boxed comments in various box styles ================================================ .. contents:: .. sectnum:: This page documents the :file:`contrib/rebox/` subdirectory of the Pymacs distribution. First install Pymacs from the top-level of the distribution, this has the side-effect of adjusting a few files in this directory. Once this done, return to this directory, then run ``python setup.py install``. Also read `Emacs usage`_ below. Introduction ============ For comments held within boxes, it is painful to fill paragraphs, while stretching or shrinking the surrounding box "by hand", as needed. This piece of Python code eases my life on this. It may be used interactively from within Emacs through the Pymacs interface, or in batch as a script which filters a single region to be reformatted. I find only fair, while giving all sources for a package using such boxed comments, to also give the means I use for nicely modifying comments. So here they are! As a user tool ============== Box styles ---------- First, a quick reminder: ====== =============================================== Number Meaning ====== =============================================== 100 Language: unknown 200 Language: /* and \*\ / 300 Language: // 400 Language: # 500 Language: ; 600 Language: % 010 Quality: straight, or 1-wide 020 Quality: rounded, or 2-wide 030 Quality: starred, or 3-wide 040 Quality: starred, or 4-wide 001 Type: left \|-shaped border 002 Type: U-shaped border, simple lines 003 Type: O-shaped border, simple lines 004 Type: U-shaped border, doubled lines 005 Type: O-shaped border, doubled lines 006 Type: [-shaped border, simple lines 007 Type: [-shaped border, doubled lines 111 No box at all 221 Usual simple C comments ====== =============================================== Each supported box style has a number associated with it. This number is arbitrary, yet by *convention*, it holds three non-zero digits such the the hundreds digit roughly represents the programming language, the tens digit roughly represents a box quality (or weight) and the units digit roughly a box type (or figure). An unboxed comment is merely one of box styles. Language, quality and type are collectively referred to as style attributes. When rebuilding a boxed comment, attributes are selected independently of each other. They may be specified by the digits of the value given as Emacs commands argument prefix, or as the ``-s`` argument to the :code:`rebox` script when called from the shell. If there is no such prefix, or if the corresponding digit is zero, the attribute is taken from the value of the default style instead. If the corresponding digit of the default style is also zero, than the attribute is recognised and taken from the actual boxed comment, as it existed before prior to the command. The value 1, which is the simplest attribute, is ultimately taken if the parsing fails. A programming language is associated with comment delimiters. Values are 100 for none or unknown, 200 for ``/*`` and ``*/`` as in plain C, 300 for ``//`` as in C++, 400 for ``#`` as in most scripting languages, 500 for ``;`` as in Lisp, Scheme, assembler and 600 for ``%`` as in TeX, PostScript, Erlang. Box quality differs according to language. For unknown languages (100) or for the C language (200), values are 10 for simple, 20 for rounded, and 30 or 40 for starred. Simple quality boxes (10) use comment delimiters to left and right of each comment line, and also for the top or bottom line when applicable. Rounded quality boxes (20) try to suggest rounded corners in boxes. Starred quality boxes (40) mostly use a left margin of asterisks or X'es, and use them also in box surroundings. For all others languages, box quality indicates the thickness in characters of the left and right sides of the box: values are 10, 20, 30 or 40 for 1, 2, 3 or 4 characters wide. With C++, quality 10 is not useful, it is not allowed. Box type values are 1 for fully opened boxes for which boxing is done only for the left and right but not for top or bottom, 2 for half single lined boxes for which boxing is done on all sides except top, 3 for fully single lined boxes for which boxing is done on all sides, 4 for half double lined boxes which is like type 2 but more bold, or 5 for fully double lined boxes which is like type 3 but more bold. The special style 221 is for C comments between a single opening ``/*`` and a single closing ``*/``. The special style 111 deletes a box. Batch usage ----------- Usage is ``rebox [OPTION]... [FILE]``. By default, FILE is reformatted to standard output by refilling the comment up to column 79, while preserving existing boxed comment style. If FILE is not given, standard input is read. Options may be: -n Do not refill the comment inside its box, and ignore -w. -s STYLE Replace box style according to STYLE, as explained above. -t Replace initial sequence of spaces by TABs on each line. -v Echo both the old and the new box styles on standard error. -w WIDTH Try to avoid going over WIDTH columns per line. So, a single boxed comment is reformatted by invocation. :code:`vi` users, for example, would need to delimit the boxed comment first, before executing the ``!}rebox`` command (is this correct? my :code:`vi` recollection is far away). Batch usage is also slow, as internal structures have to be reinitialised at every call. Producing a box in a single style is fast, but recognising the previous style requires setting up for all possible styles. Emacs usage ----------- For most Emacs language editing modes, refilling does not make sense outside comments, one may redefine the ``M-q`` command and link it to this Pymacs module. For example, I use this in my :file:`.emacs` file:: (add-hook 'c-mode-hook 'fp-c-mode-routine) (defun fp-c-mode-routine () (local-set-key "\M-q" 'rebox-comment)) (autoload 'rebox-comment "rebox" nil t) (autoload 'rebox-region "rebox" nil t) with a "rebox.el" file having this single line:: (pymacs-load "Pymacs.rebox") Install Pymacs from https://github.com/pinard/Pymacs . The Emacs function :code:`rebox-comment` automatically discovers the extent of the boxed comment near the cursor, possibly refills the text, then adjusts the box style. When this command is executed, the cursor should be within a comment, or else it should be between two comments, in which case the command applies to the next comment. The function :code:`rebox-region` does the same, except that it takes the current region as a boxed comment. Both commands obey numeric prefixes to add or remove a box, force a particular box style, or to prevent refilling of text. Without such prefixes, the commands may deduce the current box style from the comment itself so the style is preserved. The default style initial value is nil or 0. It may be preset to another value through calling :code:`rebox-set-default-style` from Emacs Lisp, or changed to anything else though using a negative value for a prefix, in which case the default style is set to the absolute value of the prefix. A ``C-u`` prefix avoids refilling the text, but forces using the default box style. ``C-u -`` lets the user interact to select one attribute at a time. Adding new styles ----------------- Let's suppose you want to add your own boxed comment style, say:: //--------------------------------------------+ // This is the style mandated in our company. //--------------------------------------------+ You might modify :file:`rebox.py` but then, you will have to edit it whenever you get a new release of :file:`pybox.py`. Emacs users might modify their :file:`.emacs` file or their :file:`rebox.el` bootstrap, if they use one. In either cases, after the ``(pymacs-load "Pymacs.rebox")`` line, merely add:: (rebox-Template NNN MMM ["//-----+" "// box " "//-----+"]) If you use the :code:`rebox` script rather than Emacs, the simplest is to make your own. This is easy, as it is very small. For example, the above style could be implemented by using this script instead of :code:`rebox`:: #!/usr/bin/env python import sys from Pymacs.Rebox import rebox rebox.Template(226, 325, ('//-----+', '// box ', '//-----+')) rebox.main(*sys.argv[1:]) In all cases, NNN is the style three-digit number, with no zero digit. Pick any free style number, you are safe with 911 and up. MMM is the recognition priority, only used to disambiguate the style of a given boxed comments, when it matches many styles at once. Try something like 400. Raise or lower that number as needed if you observe false matches. On average, the template uses three lines of equal length. Do not worry if this implies a few trailing spaces, they will be cleaned up automatically at box generation time. The first line or the third line may be omitted to create vertically opened boxes. But the middle line may not be omitted, it ought to include the word ``box``, which will get replaced by your actual comment. If the first line is shorter than the middle one, it gets merged at the start of the comment. If the last line is shorter than the middle one, it gets merged at the end of the comment and is refilled with it. As a Pymacs example =================== This example tool comes in two parts: a batch script :file:`rebox` and a :code:`Pymacs.rebox` module. Go to the :file:`contrib/rebox/` directory of the distribution and use ``python setup.py install`` there. To check that both are properly installed, type ``rebox , 2002. """\ Handling of boxed comments in various box styles. """ import sys from Pymacs.Rebox import rebox rebox.main(*sys.argv[1:]) Pymacs-0.25/contrib/rebox/rebox.el000066400000000000000000000734601175204346300171330ustar00rootroot00000000000000;;; Handling of comment boxes in various styles. ;;; Copyright © 1991,92,93,94,95,96,97,98,00 Progiciels Bourbeau-Pinard inc. ;;; François Pinard , April 1991. ;;; This program is free software; you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 2, or (at your option) ;;; any later version. ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software Foundation, ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;;;; Introduction: ;; For comments held within boxes, it is painful to fill paragraphs, while ;; stretching or shrinking the surrounding box "by hand", as needed. This ;; piece of GNU Emacs LISP code eases my life on this. I find only fair, ;; while giving all sources for a package using such boxed comments, to also ;; give the means I use for nicely modifying comments. So here they are! ;;;; Installation: ;; For most Emacs language editing modes, refilling does not make sense ;; outside comments, one may redefine the `M-q' command and link it to this ;; file. For example, I use this in my `.emacs' file: ;; (add-hook 'c-mode-hook 'fp-c-mode-routine) ;; (defun fp-c-mode-routine () ;; (local-set-key "\M-q" 'rebox-comment)) ;; (autoload 'rebox-comment "rebox" nil t) ;; (autoload 'rebox-region "rebox" nil t) ;;;; Usage: ;; The function `rebox-comment' automatically discovers the extent of the ;; boxed comments near the cursor, possibly refills the text, then adjusts the ;; comment box style. When this command is executed, the cursor should be ;; within a comment, or else it should be between two comments, in which case ;; the command applies to the next comment. The function `rebox-region' does ;; the same, except that it takes the current region as a boxed comment. Both ;; commands obey numeric prefixes to add or remove a box, force a particular ;; box style, or to prevent refilling of text. Without such prefixes, the ;; commands may deduce the current comment box style from the comment itself ;; so the style is preserved. An unboxed comment is merely one of box styles. ;; A style is identified by three non-zero digits. The _convention_ about ;; style numbering is such the the hundreds digit roughly represents the ;; programming language, the tens digit roughly represents a box quality (or ;; weight) and the units digit roughly a box type (or figure). Language, ;; quality and types are collectively referred to as style attributes. ;; When rebuilding a boxed comment, attributes are selected independently of ;; each other. They may be specified by the digits of the value given as ;; Emacs commands argument prefix. If there is no such prefix, or if the ;; corresponding digit is zero, the attribute is taken from the value of the ;; default style instead. If the corresponding digit of the default style is ;; also zero, than the attribute is recognised and taken from the actual ;; comment box, as it existed before prior to the command. The value 1, which ;; is the simplest attribute, is ultimately taken if the recognition fails. ;; The default style initial value is nil or 0. It may be preset to another ;; value through setting `rebox-default-style' in Emacs LISP code, or changed ;; to anything else though using a negative value for a prefix, in which case ;; the default style is set to the absolute value of the prefix. ;; A `C-u' prefix avoids refilling the text, but forces using the default box ;; style. `C-u -' lets the user interact to select one attribute at a time. ;;;; Convention: ;; A programming language is associated with comment delimiters. Values are ;; 100 for none or unknown, 200 for `/*' and `*/' as in plain C, 300 for `//' ;; as in C++, 400 for `#' as in most scripting languages, 500 for `;' as in ;; LISP or assembler and 600 for `%' as in TeX or PostScript. ;; Box quality differs according to language. For unknown languages (100) or ;; for the C language (200), values are 10 for simple, 20 for rounded, and 30 ;; or 40 for starred. Simple quality boxes (10) use comment delimiters to ;; left and right of each comment line, and also for the top or bottom line ;; when applicable. Rounded quality boxes (20) try to suggest rounded corners ;; in boxes. Starred quality boxes (40) mostly use a left margin of asterisks ;; or X'es, and use them also in box surroundings. For all others languages, ;; box quality indicates the thickness in characters of the left and right ;; sides of the box: values are 10, 20, 30 or 40 for 1, 2, 3 or 4 characters ;; wide. With C++, quality 10 is not useful, you should force 20 instead. ;; Box type values are 1 for fully opened boxes for which boxing is done ;; only for the left and right but not for top or bottom, 2 for half ;; single lined boxes for which boxing is done on all sides except top, ;; 3 for fully single lined boxes for which boxing is done on all sides, ;; 4 for half double lined boxes which is like type 2 but more bold, ;; or 5 for fully double lined boxes which is like type 3 but more bold. ;; The special style 221 is for C comments between a single opening `/*' and a ;; single closing `*/'. The special style 111 deletes a box. ;;;; History: ;; I first observed rounded corners, as in style 223 boxes, in code from ;; Warren Tucker, a previous maintainer of the `shar' package. Besides very ;; special files, I was carefully avoiding to use such boxes for real work, ;; as I found them much too hard to maintain. My friend Paul Provost was ;; working at Taarna, a computer graphics place, which had boxes as part of ;; their coding standards. He asked that we try something to get out of his ;; misery, and this how `rebox.el' was originally written. I did not plan to ;; use it for myself, but Paul was so enthusiastic that I timidly started to ;; use boxes in my things, very little at first, but more and more as time ;; passed, yet not fully sure it was a good move. Later, many friends ;; spontaneously started to use this tool for real, some being very serious ;; workers. This finally convinced me that boxes are acceptable, after all. ;; Template numbering dependent data. (defvar rebox-default-style nil "*Preferred style for box comments.") ;; Box templates. First number is style, second is recognition weight. (defconst rebox-templates ;; Generic programming language templates. ;; Adding 300 replaces `?' by `/', for C++ style comments. ;; Adding 400 replaces `?' by `#', for scripting languages. ;; Adding 500 replaces `?' by ';', for LISP and assembler. ;; Adding 600 replaces `?' by `%', for TeX and PostScript. '((11 115 "? box") (12 215 "? box ?" "? --- ?") (13 315 "? --- ?" "? box ?" "? --- ?") (14 415 "? box ?" "???????") (15 515 "???????" "? box ?" "???????") (21 125 "?? box") (22 225 "?? box ??" "?? --- ??") (23 325 "?? --- ??" "?? box ??" "?? --- ??") (24 425 "?? box ??" "?????????") (25 525 "?????????" "?? box ??" "?????????") (31 135 "??? box") (32 235 "??? box ???" "??? --- ???") (33 335 "??? --- ???" "??? box ???" "??? --- ???") (34 435 "??? box ???" "???????????") (35 535 "???????????" "??? box ???" "???????????") (41 145 "???? box") (42 245 "???? box ????" "???? --- ????") (43 345 "???? --- ????" "???? box ????" "???? --- ????") (44 445 "???? box ????" "?????????????") (45 545 "?????????????" "???? box ????" "?????????????") ;; Textual (non programming) templates. (111 113 "box") (112 213 "| box |" "+-----+") (113 313 "+-----+" "| box |" "+-----+") (114 413 "| box |" "*=====*") (115 513 "*=====*" "| box |" "*=====*") (121 123 "| box |") (122 223 "| box |" "`-----'") (123 323 ".-----." "| box |" "`-----'") (124 423 "| box |" "\\=====/") (125 523 "/=====\\" "| box |" "\\=====/") (141 143 "| box ") (142 243 "* box *" "*******") (143 343 "*******" "* box *" "*******") (144 443 "X box X" "XXXXXXX") (145 543 "XXXXXXX" "X box X" "XXXXXXX") ;; C language templates. (211 118 "/* box */") (212 218 "/* box */" "/* --- */") (213 318 "/* --- */" "/* box */" "/* --- */") (214 418 "/* box */" "/* === */") (215 518 "/* === */" "/* box */" "/* === */") (221 128 "/* " " box" "*/") (222 228 "/* ." "| box |" "`----*/") (223 328 "/*----." "| box |" "`----*/") (224 428 "/* \\" "| box |" "\\====*/") (225 528 "/*====\\" "| box |" "\\====*/") (231 138 "/* " " | box" " */ ") (232 238 "/* " " | box | " " *-----*/") (233 338 "/*-----* " " | box | " " *-----*/") (234 438 "/* box */" "/*-----*/") (235 538 "/*-----*/" "/* box */" "/*-----*/") (241 148 "/* " " * box" " */ ") (242 248 "/* * " " * box * " " *******/") (243 348 "/******* " " * box * " " *******/") (244 448 "/* box */" "/*******/") (245 548 "/*******/" "/* box */" "/*******/") (251 158 "/* " " * box" " */ "))) ;; Template numbering dependent code. (defvar rebox-language-character-alist '((3 . "/") (4 . "#") (5 . ";") (6 . "%")) "Alist relating language to comment character, for generic languages.") ;;; Regexp to match the comment start, given a LANGUAGE value as index. (defvar rebox-regexp-start ["^[ \t]*\\(/\\*\\|//+\\|#+\\|;+\\|%+\\)" "^" ; 1 "^[ \t]*/\\*" ; 2 "^[ \t]*//+" ; 3 "^[ \t]*#+" ; 4 "^[ \t]*\;+" ; 5 "^[ \t]*%+" ; 6 ]) ;;; Request the style interactively, using the minibuffer. (defun rebox-ask-for-style () (let (key language quality type) (while (not language) (message "\ Box language is 100-none, 200-/*, 300-//, 400-#, 500-;, 600-%%") (setq key (read-char)) (when (and (>= key ?0) (<= key ?6)) (setq language (- key ?0)))) (while (not quality) (message "\ Box quality/width is 10-simple/1, 20-rounded/2, 30-starred/3 or 40-starred/4") (setq key (read-char)) (when (and (>= key ?0) (<= key ?4)) (setq quality (- key ?0)))) (while (not type) (message "\ Box type is 1-opened, 2-half-single, 3-single, 4-half-double or 5-double") (setq key (read-char)) (when (and (>= key ?0) (<= key ?5)) (setq type (- key ?0)))) (+ (* 100 language) (* 10 quality) type))) ;; Template ingestion. ;;; Information about registered templates. (defvar rebox-style-data nil) ;;; Register all box templates. (defun rebox-register-all-templates () (setq rebox-style-data nil) (let ((templates rebox-templates)) (while templates (let ((template (car templates))) (rebox-register-template (car template) (cadr template) (cddr template))) (setq templates (cdr templates))))) ;;; Register a single box template. (defun rebox-register-template (style weight lines) "Digest and register a single template. The template is numbered STYLE, and is described by one to three LINES. If STYLE is below 100, it is generic for a few programming languages and within lines, `?' is meant to represent the language comment character. STYLE should be used only once through all `rebox-register-template' calls. One of the lines should contain the substring `box' to represent the comment to be boxed, and if three lines are given, `box' should appear in the middle one. Lines containing only spaces are implied as necessary before and after the the `box' line, so we have three lines. Normally, all three template lines should be of the same length. If the first line is shorter, it represents a start comment string to be bundled within the first line of the comment text. If the third line is shorter, it represents an end comment string to be bundled at the end of the comment text, and refilled with it." (cond ((< style 100) (let ((pairs rebox-language-character-alist) language character) (while pairs (setq language (caar pairs) character (cdar pairs) pairs (cdr pairs)) (rebox-register-template (+ (* 100 language) style) weight (mapcar (lambda (line) (while (string-match "\?" line) (setq line (replace-match character t t line))) line) lines))))) ((assq style rebox-style-data) (error "Style %d defined more than once")) (t (let (line1 line2 line3 regexp1 regexp2 regexp3 merge-nw merge-se nw nn ne ww ee sw ss se) (if (string-match "box" (car lines)) (setq line1 nil line2 (car lines) lines (cdr lines)) (setq line1 (car lines) line2 (cadr lines) lines (cddr lines)) (unless (string-match "box" line2) (error "Erroneous template for %d style" style))) (setq line3 (and lines (car lines))) (setq merge-nw (and line1 (< (length line1) (length line2))) merge-se (and line3 (< (length line3) (length line2))) nw (cond ((not line1) nil) (merge-nw line1) ((zerop (match-beginning 0)) nil) (t (substring line1 0 (match-beginning 0)))) nn (cond ((not line1) nil) (merge-nw nil) (t (let ((x (aref line1 (match-beginning 0)))) (if (= x ? ) nil x)))) ne (cond ((not line1) nil) (merge-nw nil) ((= (match-end 0) (length line1)) nil) (t (rebox-rstrip (substring line1 (match-end 0))))) ww (cond ((zerop (match-beginning 0)) nil) (t (substring line2 0 (match-beginning 0)))) ee (cond ((= (match-end 0) (length line2)) nil) (t (rebox-rstrip (substring line2 (match-end 0))))) sw (cond ((not line3) nil) (merge-se nil) ((zerop (match-beginning 0)) nil) (t (substring line3 0 (match-beginning 0)))) ss (cond ((not line3) nil) (merge-se nil) (t (let ((x (aref line3 (match-beginning 0)))) (if (= x ? ) nil x)))) se (cond ((not line3) nil) (merge-se (rebox-rstrip line3)) ((= (match-end 0) (length line3)) nil) (t (rebox-rstrip (substring line3 (match-end 0)))))) (setq regexp1 (cond (merge-nw (concat "^[ \t]*" (rebox-regexp-quote nw) ".*\n")) ((and nw (not nn) (not ne)) (concat "^[ \t]*" (rebox-regexp-quote nw) "\n")) ((or nw nn ne) (concat "^[ \t]*" (rebox-regexp-quote nw) (rebox-regexp-ruler nn) (rebox-regexp-quote ne) "\n"))) regexp2 (and (not (string-equal (rebox-rstrip (concat ww ee)) "")) (concat "^[ \t]*" (rebox-regexp-quote ww) ".*" (rebox-regexp-quote ee) "\n")) regexp3 (cond (merge-se (concat "^.*" (rebox-regexp-quote se) "\n")) ((and sw (not ss) (not se)) (concat "^[ \t]*" (rebox-regexp-quote sw) "\n")) ((or sw ss se) (concat "^[ \t]*" (rebox-regexp-quote sw) (rebox-regexp-ruler ss) (rebox-regexp-quote se) "\n")))) (setq rebox-style-data (cons (cons style (vector weight regexp1 regexp2 regexp3 merge-nw merge-se nw nn ne ww ee sw ss se)) rebox-style-data)))))) ;; User interaction. ;;; Rebox the current region. (defun rebox-region (flag) (interactive "P") (when (eq flag '-) (setq flag (rebox-ask-for-style))) (when (rebox-validate-flag flag) (save-restriction (narrow-to-region (region-beginning) (region-end)) (rebox-engine flag)))) ;;; Rebox the surrounding comment. (defun rebox-comment (flag) (interactive "P") (when (eq flag '-) (setq flag (rebox-ask-for-style))) (when (rebox-validate-flag flag) (save-restriction (rebox-find-and-narrow) (rebox-engine flag)))) ;;; Validate FLAG and usually return t if not interrupted by errors. ;;; But if FLAG is zero or negative, then change default box style and ;;; return nil. (defun rebox-validate-flag (flag) (cond ((not (numberp flag))) ((> flag 0)) (t (setq rebox-default-style (- flag)) (message "Default style: %d" rebox-default-style) nil))) ;;; Add, delete or adjust a comment box in the narrowed buffer. ;;; Various FLAG values are explained at beginning of this file. (defun rebox-engine (flag) (let ((undo-list buffer-undo-list) (marked-point (point-marker)) (previous-margin (rebox-left-margin)) (previous-style (rebox-guess-style)) (style 111) refill) (untabify (point-min) (point-max)) ;; Decide about refilling and the box style to use. (when previous-style (setq style (rebox-merge-styles style previous-style))) (when rebox-default-style (setq style (rebox-merge-styles style rebox-default-style))) (cond ((not flag) (setq refill t)) ((listp flag) (setq refill nil)) ((numberp flag) (setq refill t style (rebox-merge-styles style flag))) (t (error "Unexpected flag value %s" flag))) (unless (assq style rebox-style-data) (error "Style %d is not known" style)) (message "Style: %d -> %d" (or previous-style 0) style) ;; Remove all previous comment marks. (when previous-style (rebox-unbuild previous-style)) ;; Remove all spurious whitespace. (goto-char (point-min)) (while (re-search-forward " +$" nil t) (replace-match "" t t)) (goto-char (point-min)) (delete-char (- (skip-chars-forward "\n"))) (goto-char (point-max)) (when (= (preceding-char) ?\n) (forward-char -1)) (delete-char (- (skip-chars-backward "\n"))) (goto-char (point-min)) (while (re-search-forward "\n\n\n+" nil t) (replace-match "\n\n" t t)) ;; Move all the text rigidly to the left for insuring that the left ;; margin stays at the same place. (let ((indent-tabs-mode nil) (actual-margin (rebox-left-margin))) (unless (= previous-margin actual-margin) (indent-rigidly (point-min) (point-max) (- previous-margin actual-margin)))) ;; Possibly refill, then build the comment box. (let ((indent-tabs-mode nil)) (rebox-build refill (rebox-left-margin) style)) ;; Retabify to the left only (adapted from tabify.el). (when indent-tabs-mode (goto-char (point-min)) (while (re-search-forward "^[ \t][ \t]+" nil t) (let ((column (current-column))) (delete-region (match-beginning 0) (point)) (indent-to column)))) ;; Restore the point position. (goto-char (marker-position marked-point)) ;; Remove all intermediate boundaries from the undo list. (unless (eq buffer-undo-list undo-list) (let ((cursor buffer-undo-list)) (while (not (eq (cdr cursor) undo-list)) (if (car (cdr cursor)) (setq cursor (cdr cursor)) (rplacd cursor (cdr (cdr cursor))))))))) ;;; Return style attributes as per DEFAULT, in which attributes have been ;;; overridden by non-zero corresponding style attributes from STYLE. (defun rebox-merge-styles (default style) (let ((default-vector (rebox-style-to-vector default)) (style-vector (rebox-style-to-vector style))) (unless (zerop (aref style-vector 0)) (aset default-vector 0 (aref style-vector 0))) (unless (zerop (aref style-vector 1)) (aset default-vector 1 (aref style-vector 1))) (unless (zerop (aref style-vector 2)) (aset default-vector 2 (aref style-vector 2))) (+ (* 100 (aref default-vector 0)) (* 10 (aref default-vector 1)) (aref default-vector 2)))) ;;; Transform a style number into a vector triplet. (defun rebox-style-to-vector (number) (vector (/ number 100) (% (/ number 10) 10) (% number 10))) ;; Classification of boxes (and also, construction data). ;;; Find the limits of the block of comments following or enclosing ;;; the cursor, or return an error if the cursor is not within such a ;;; block of comments. Extend it as far as possible in both ;;; directions, then narrow the buffer around it. (defun rebox-find-and-narrow () (save-excursion (let (start end temp language) ;; Find the start of the current or immediately following comment. (beginning-of-line) (skip-chars-forward " \t\n") (beginning-of-line) (unless (looking-at (aref rebox-regexp-start 0)) (setq temp (point)) (unless (re-search-forward "\\*/" nil t) (error "outside any comment block")) (re-search-backward "/\\*") (unless (<= (point) temp) (error "outside any comment block")) (setq temp (point)) (beginning-of-line) (skip-chars-forward " \t") (unless (= (point) temp) (error "text before start of comment")) (beginning-of-line)) (setq start (point) language (rebox-guess-language)) ;; Find the end of this comment. (when (= language 2) (search-forward "*/") (unless (looking-at "[ \t]*$") (error "text after end of comment"))) (end-of-line) (if (eobp) (insert "\n") (forward-char 1)) (setq end (point)) ;; Try to extend the comment block backwards. (goto-char start) (while (and (not (bobp)) (cond ((= language 2) (skip-chars-backward " \t\n") (when (and (looking-at "[ \t]*\n[ \t]*/\\*") (> (point) 2)) (backward-char 2) (when (looking-at "\\*/") (re-search-backward "/\\*") (setq temp (point)) (beginning-of-line) (skip-chars-forward " \t") (when (= (point) temp) (beginning-of-line) t)))) (t (previous-line 1) (looking-at (aref rebox-regexp-start language))))) (setq start (point))) ;; Try to extend the comment block forward. (goto-char end) (while (looking-at (aref rebox-regexp-start language)) (cond ((= language 2) (re-search-forward "[ \t]*/\\*") (re-search-forward "\\*/") (when (looking-at "[ \t]*$") (beginning-of-line) (forward-line 1) (setq end (point)))) (t (forward-line 1) (setq end (point))))) ;; Narrow to the whole block of comments. (narrow-to-region start end)))) ;;; Guess the language in use for the text starting at the cursor position. (defun rebox-guess-language () (let ((language 1) (index (1- (length rebox-regexp-start)))) (while (not (zerop index)) (if (looking-at (aref rebox-regexp-start index)) (setq language index index 0) (setq index (1- index)))) language)) ;; Guess the current box style from the text in the whole (narrowed) buffer. (defun rebox-guess-style () (let ((style-data rebox-style-data) best-style best-weight) ;; Let's try all styles in turn. A style has to match exactly to be ;; eligible. More heavy is a style, more prone it is to be retained. (while style-data (let* ((style (caar style-data)) (data (cdar style-data)) (weight (aref data 0)) (regexp1 (aref data 1)) (regexp2 (aref data 2)) (regexp3 (aref data 3)) (limit (cond ((and best-weight (<= weight best-weight)) nil) ((not regexp3) (point-max)) ((progn (goto-char (point-max)) (forward-line -1) (looking-at regexp3)) (point))))) (when limit (goto-char (point-min)) (cond ((not regexp1)) ((looking-at regexp1) (goto-char (match-end 0))) (t (setq limit nil))) (when (and limit regexp2) (while (and (< (point) limit) (looking-at regexp2)) (goto-char (match-end 0))) (unless (= (point) limit) (setq limit nil))) (when limit (setq best-style style best-weight weight)))) (setq style-data (cdr style-data))) best-style)) ;;; Return a regexp matching STRING without its surrounding space, maybe ;;; followed by spaces or tabs. If STRING is nil, return the empty regexp. (defun rebox-regexp-quote (string) (cond ((not string) "") (t (while (and (> (length string) 0) (= (aref string 0) ? )) (setq string (substring string 1))) (concat (regexp-quote (rebox-rstrip string)) "[ \t]*")))) ;;; Return a regexp matching two or more repetitions of CHARACTER, maybe ;;; followed by spaces or tabs. Is CHARACTER is nil, return the empty regexp. (defun rebox-regexp-ruler (character) (if character (concat (regexp-quote (make-string 2 character)) "+[ \t]*") "")) ;;; Return string with trailing spaces removed. (defun rebox-rstrip (string) (while (and (> (length string) 0) (= (aref string (1- (length string))) ? )) (setq string (substring string 0 (1- (length string))))) string) ;; Reconstruction of boxes. ;;; Remove all comment marks, using STYLE to hint at what these are. (defun rebox-unbuild (style) (let* ((data (cdr (assq style rebox-style-data))) (merge-nw (aref data 4)) (merge-se (aref data 5)) (nw (aref data 6)) (nn (aref data 7)) (ne (aref data 8)) (ww (aref data 9)) (ee (aref data 10)) (sw (aref data 11)) (ss (aref data 12)) (se (aref data 13)) (nw-regexp (and nw (concat "^[ \t]*" (rebox-regexp-quote nw)))) (ww-regexp (and ww (concat "^[ \t]*" (rebox-regexp-quote ww)))) (sw-regexp (and sw (concat "^[ \t]*" (rebox-regexp-quote sw))))) ;; Clean up first line. (goto-char (point-min)) (skip-chars-forward "\n") (end-of-line) (delete-char (- (skip-chars-backward " \t"))) (when ne (let ((start (- (point) (length ne)))) (when (and (>= start (point-min)) (string-equal ne (buffer-substring start (point)))) (delete-backward-char (length ne))))) (beginning-of-line) (if (and nw-regexp (looking-at nw-regexp)) (replace-match (make-string (- (match-end 0) (match-beginning 0)) ? )) (skip-chars-forward " \t")) (when nn (let ((count (skip-chars-forward (char-to-string nn)))) (delete-char (- count)) (insert (make-string count ? )))) ;; Clean up last line. (goto-char (point-max)) (delete-char (- (skip-chars-backward " \t\n"))) (when se (let ((start (- (point) (length se)))) (when (and (>= start (point-min)) (string-equal se (buffer-substring start (point)))) (delete-backward-char (length se))))) (insert "\n") (forward-line -1) (if (and sw-regexp (looking-at sw-regexp)) (replace-match (make-string (- (match-end 0) (match-beginning 0)) ? )) (skip-chars-forward " \t")) (when ss (let ((count (skip-chars-forward (char-to-string ss)))) (delete-char (- count)) (insert (make-string count ? )))) ;; Clean up all lines. (goto-char (point-min)) (while (not (eobp)) (end-of-line) (delete-char (- (skip-chars-backward " \t"))) (when ee (let ((start (- (point) (length ee)))) (when (and (>= start (point-min)) (string-equal ee (buffer-substring start (point)))) (delete-backward-char (length ee))))) (beginning-of-line) (if (and ww-regexp (looking-at ww-regexp)) (replace-match (make-string (- (match-end 0) (match-beginning 0)) ? )) (skip-chars-forward " \t")) (forward-line 1)))) ;;; After refilling it if REFILL is not nil, while respecting a left MARGIN, ;;; put the narrowed buffer back into a boxed comment according to box STYLE. (defun rebox-build (refill margin style) (let* ((data (cdr (assq style rebox-style-data))) (merge-nw (aref data 4)) (merge-se (aref data 5)) (nw (aref data 6)) (nn (aref data 7)) (ne (aref data 8)) (ww (aref data 9)) (ee (aref data 10)) (sw (aref data 11)) (ss (aref data 12)) (se (aref data 13)) right-margin) ;; Merge a short end delimiter now, so it gets refilled with text. (when merge-se (goto-char (1- (point-max))) (cond ((memq (preceding-char) '(? ?\t ?\n))) ((memq (preceding-char) '(?. ?! ??)) (insert " ")) (t (insert " "))) (insert se) (setq se nil)) ;; Possibly refill, and adjust margins to account for left inserts. (when refill (let ((fill-prefix (make-string margin ? )) (fill-column (- fill-column (+ (length ww) (length ee))))) (fill-region (point-min) (point-max)))) (setq right-margin (+ (rebox-right-margin) (length ww))) ;; Construct the top line. (goto-char (point-min)) (cond (merge-nw (skip-chars-forward " " (+ (point) margin)) (insert (make-string (- margin (current-column)) ? ) nw) (forward-line 1)) ((or nw nn ne) (indent-to margin) (when nw (insert nw)) (if (not (or nn ne)) (delete-char (- (skip-chars-backward " "))) (insert (make-string (- right-margin (current-column)) (or nn ? ))) (when ne (insert ne))) (insert "\n"))) ;; Construct all middle lines. (while (not (eobp)) (skip-chars-forward " " (+ (point) margin)) (when (or ww ee (/= (following-char) ?\n)) (insert (make-string (- margin (current-column)) ? )) (when ww (insert ww)) (when ee (end-of-line) (indent-to right-margin) (insert ee) (delete-char (- (skip-chars-backward " "))))) (forward-line 1)) ;; Construct the bottom line. (when (or sw ss se) (indent-to margin) (when sw (insert sw)) (if (not (or ss se)) (delete-char (- (skip-chars-backward " "))) (insert (make-string (- right-margin (current-column)) (or ss ? ))) (when se (insert se))) (insert "\n")))) ;;; Return the minimum value of the left margin of all lines, or -1 if ;;; all lines are empty. (defun rebox-left-margin () (let ((margin -1)) (goto-char (point-min)) (while (and (not (zerop margin)) (not (eobp))) (skip-chars-forward " \t") (let ((column (current-column))) (and (not (= (following-char) ?\n)) (or (< margin 0) (< column margin)) (setq margin column))) (forward-line 1)) margin)) ;;; Return the maximum value of the right margin of all lines. Any ;;; sentence ending a line has a space guaranteed before the margin. (defun rebox-right-margin () (let ((margin 0) period) (goto-char (point-min)) (while (not (eobp)) (end-of-line) (if (bobp) (setq period 0) (backward-char 1) (setq period (if (memq (following-char) '(?. ?? ?!)) 1 0)) (forward-char 1)) (setq margin (max margin (+ (current-column) period))) (forward-char 1)) margin)) ;;; Initialize the internal structures. (rebox-register-all-templates) Pymacs-0.25/contrib/rebox/setup.py.in000066400000000000000000000005631175204346300176030ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from distutils.core import setup setup(name='Rebox', version='Pymacs-@VERSION@', description="Refilling comment boxes from within Emacs.", author='François Pinard', author_email='pinard@iro.umontreal.ca', url='http://pinard.progiciels-bpi.ca', scripts=['rebox'], py_modules=['Pymacs.rebox']) Pymacs-0.25/pppp000077500000000000000000000411021175204346300136040ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright © 2010 Progiciels Bourbeau-Pinard inc. # François Pinard , 2010. """\ Poor's Python Pre-Processor (pppp). Usage: pppp -m [OPTION]... FILE1 FILE2 or: pppp [OPTION]... [FILE]... General options: -h Print this help and do nothing else -m Produce, on standard output, a merged version -c Clean files which would normally have been produced -v Be verbose about written (or deleted) files -f Force deletion of rewriting, even if files were modified Context setting options: -C FILE Evaluate Python FILE for preparing context -D name Define "name" as True -D name=expr Define "name" as the value of Python "expr" Transformation options: -o OUTPUT Collect output files into the OUTPUT directory -i INDENT Indentation step, default value is 4 -s SUFFIX Suffix to mark transformable paths, if not '.in' -p Force all files to be interpreted as Python -n Avoid trying to keep line numbers synchronized With -m, a single -Dname option is also required. FILE1 and FILE2 are merged and the result written to standard output, augmented as needed with "if name:", "if not name:" and "else:" directives, such that FILE1 is meant when "name" is False, FILE2 is meant when "name" is True. Without -cm, files go through an elementary pre-processing. If no FILE is given, standard input is transformed and written to standard output. Otherwise, if FILE is a directory, it is recursively traversed for the files it contains. A file is eligible for transformation only when it's full path name (starting at FILE and down) has a component for which there is a '.in' suffix. The file receiving a transformed file is derived removing all such '.in' suffixes, and prepending OUTPUT as a directory if specified. Output directories are created as needed. Within each file to transform, each occurrence of @name@ gets replaced by the string of the context value for "name", when such is defined. Moreover, a Python source (either when -p, or a file which name ends with .py or .py.in, or for which the first line starts with "!#" and has some "ython" string in it) is further handled as described below A Python source has all its "if EXPR:", "elif EXPR:" and corresponding "else:" lines checked (each on a single line, and without comments). If "EXPR" is a valid Python expression for which primitives are either constants or names introduced through -D options, the "if" or "elif" line is removed and succeeding lines are either shifted or removed. """ __metaclass__ = type import os, re, sys endif_pppp = '#endif (pppp)' class Main: output = None context = {} merge = False indent = 4 suffix = '.in' python = False verbose = False clean = False synclines = True force = False def main(self, *arguments): import getopt options, arguments = getopt.getopt(arguments, 'C:D:cfhi:mno:ps:v') for option, value in options: if option == '-C': exec(compile(open(value).read(), value, 'exec'), self.context) elif option == '-D': if '=' in value: name, value = value.split('=', 1) self.context[name] = eval(value, {}) else: self.context[name] = True elif option == '-c': self.clean = True elif option == '-f': self.force = True elif option == '-h': sys.stdout.write(__doc__) return elif option == '-i': self.indent = int(value) elif option == '-m': self.merge = True elif option == '-n': self.synclines = False elif option == '-o': self.output = value elif option == '-p': self.python = True elif option == '-s': self.suffix = value elif option == '-v': self.verbose = True if not self.suffix and not self.output: sys.exit("Option -o is needed with an empty suffix.") if self.merge: if len(arguments) != 2 or len(self.context) != 1: sys.exit("Try `%s -h' for help" % sys.argv[0]) self.merge_files(arguments[0], arguments[1], sys.stdout.write) elif not arguments: if not self.clean: self.transform_file( sys.stdin.name, sys.stdin, sys.stdout.write) else: for argument in arguments: if os.path.isdir(argument): self.transform_all_files(argument) elif argument.endswith(self.suffix): self.transform_all_files(argument) else: sys.stderr.write( "* %s does not end with %s, ignored.\n" % (argument, self.suffix)) def merge_files(self, file1, file2, write): left = list(open(file1)) right = list(open(file2)) block_margin = None def protect_block(margin, danger): if block_margin is None: return if margin is None: return if margin < block_margin: return if margin == block_margin and not danger: return write(' ' * block_margin + endif_pppp + '\n') def check_next(lines, lo, hi): for index in range(lo, hi): line = lines[index].rstrip() short = line.lstrip() if short: return (len(line) - len(short), short.startswith('elif ') or short.startswith('else:')) return None, False def lines_margin(lines, lo, hi): margin = None for index in range(lo, hi): line = lines[index].rstrip() short = line.lstrip() if short: width = len(line) - len(short) if margin is None: margin = width else: margin = min(margin, width) return margin or 0 def copy_lines(lines, lo, hi, prefix): for index in range(lo, hi): line = lines[index] if line.lstrip(' \t') == '\n': write(line) else: write(prefix + line) name = list(self.context.keys()).pop() import difflib matcher = difflib.SequenceMatcher(None, left, right) for (tag, low_left, high_left, low_right, high_right ) in matcher.get_opcodes(): if tag == 'equal': margin, danger = check_next(left, low_left, high_left) protect_block(margin, danger) copy_lines(left, low_left, high_left, '') block_margin = None elif tag == 'delete': margin = lines_margin(left, low_left, high_left) protect_block(margin, False) write(' ' * margin + 'if not ' + name + ':\n') copy_lines(left, low_left, high_left, ' ' * self.indent) block_margin = margin elif tag == 'insert': margin = lines_margin(right, low_right, high_right) protect_block(margin, False) write(' ' * margin + 'if ' + name + ':\n') copy_lines(right, low_right, high_right, ' ' * self.indent) block_margin = margin elif tag == 'replace': margin = min(lines_margin(left, low_left, high_left), lines_margin(right, low_right, high_right)) protect_block(margin, False) write(' ' * margin + 'if ' + name + ':\n') copy_lines(right, low_right, high_right, ' ' * self.indent) write(' ' * margin + 'else:\n') copy_lines(left, low_left, high_left, ' ' * self.indent) block_margin = margin else: assert False, tag def transform_all_files(self, input): def ensure_directory(name): base = os.path.dirname(name) if base and not os.path.isdir(base): ensure_directory(base) if self.verbose: sys.stderr.write("creating %s\n" % base) os.mkdir(base) if self.output: output = os.path.join(self.output, input) else: output = input for input, output in self.each_pair(input, output): if not self.force: if (os.path.exists(output) and os.path.getmtime(output) > os.path.getmtime(input)): sys.exit("ERROR: %s has been modified, keeping it!\n" % output) if self.clean: if os.path.exists(output): if self.verbose: sys.stderr.write("deleting %s\n" % output) os.remove(output) else: ensure_directory(output) if self.verbose: sys.stderr.write("writing %s\n" % output) self.transform_file( input, open(input), open(output, 'w').write) if output.endswith('.py'): pyc_file = output[:-2] + '.pyc' if os.path.exists(pyc_file): if self.verbose: sys.stderr.write("deleting %s\n" % pyc_file) os.remove(pyc_file) os.utime(output, (os.path.getatime(input), os.path.getmtime(input))) def each_pair(self, input, output): stack = [] output = output.replace(self.suffix + '/', '/') if output.endswith(self.suffix): output = output[:-len(self.suffix)] stack.append((input, output)) while stack: input_path, output_path = stack.pop() if os.path.isdir(input_path): for base in os.listdir(input_path): input = os.path.join(input_path, base) if base.endswith(self.suffix): output = os.path.join(output_path, base[:-len(self.suffix)]) else: output = os.path.join(output_path, base) stack.append((input, output)) elif ((self.suffix + '/') in input_path or input_path.endswith(self.suffix)): yield input_path, output_path def transform_file(self, name, lines, write): # MARGIN is the number of spaces preceding the previous "if" line. # A virtual "if True:" is assumed above and left of the whole module. margin = -1 # REMOVE is the number of leading spaces to delete on copied lines. remove = 0 # STATE drives the copying or skipping of code. When the test # expression of an "if" statement is known to be True, known to be # False, or cannot be evaluated, STATE becomes TRUE, FALSE or UNKNOWN # respectively (an "else" clause exchanges TRUE and FALSE, but leaves # UNKNOWN undisturbed). FALSE2 is a special case of FALSE, for when # some "if UNKNOWN:" is followed by "elif FALSE:", an "else:" clause # might then be needed before resuming copy. STATE is SKIP when some # "if" or "elif" clause has been known to be True, meaning that all # following clauses may be removed. STATE becomes SKIP as well for a # whole embedded "if" within some code which was already being skipped. TRUE = 'TRUE' FALSE = 'FALSE' FALSE2 = 'FALSE2' UNKNOWN = 'UNKNOWN' SKIP = 'SKIP' state = TRUE # STACK saves the previous (MARGIN, REMOVE, STATE) whenever a nested # "if" is met, and restores it when the actual margin goes left enough. # A nested "if" is ignored unless its expression can be evaluated. stack = [] def expression_value(text): try: value = eval(text, {'__builtins__': {}}, self.context) except: return UNKNOWN else: if value: return TRUE return FALSE def write_shifted(line): assert remove >= 0, (remove, line) assert line[:remove] == ' ' * remove, (remove, line) write_verbatim(line[remove:]) def write_verbatim(line): write(line) self.output_counter += line.count('\n') self.output_counter = 0 python = (self.python or name.endswith('.py') or name.endswith('.py' + self.suffix)) for input_counter, line in enumerate( self.each_substituded_line(lines)): if (input_counter == 0 and line.startswith('#!') and 'ython' in line): python = True if self.synclines: while self.output_counter < input_counter: write_verbatim('\n') if not python: write_verbatim(line) continue short = line.lstrip() if not short: if state in (TRUE, UNKNOWN): write_verbatim(line) continue width = len(line) - len(short) while width < margin: margin, remove, state = stack.pop() if width == margin: match = re.match('else: *$', short) if match: if state is TRUE: state = FALSE elif state is FALSE: state = TRUE elif state is FALSE2: write_shifted(line) state = TRUE elif state in UNKNOWN: write_shifted(line) continue match = re.match('elif (.*): *$', short) if match: if state is TRUE: state = SKIP elif state is FALSE: value = expression_value(match.group(1)) if value is UNKNOWN: remove -= self.indent write_shifted(' ' * width + 'if' + short[4:]) state = value elif state is FALSE2: value = expression_value(match.group(1)) if value is UNKNOWN: write_shifted(line) else: write_verbatim(' ' * width + 'else:\n') state = value elif state is UNKNOWN: value = expression_value(match.group(1)) if value is TRUE: write_shifted(' ' * width + 'else:\n') state = TRUE elif value is FALSE: state = FALSE2 elif value is UNKNOWN: write_shifted(line) continue margin, remove, state = stack.pop() match = re.match('if (.*): *$', short) if match: stack.append((margin, remove, state)) margin = width if state in (TRUE, UNKNOWN): value = expression_value(match.group(1)) if value is UNKNOWN: write_shifted(line) else: remove += self.indent state = value else: state = SKIP continue if state in (UNKNOWN, TRUE) and not short.startswith(endif_pppp): write_shifted(line) def each_substituded_line(self, lines): if self.context: pattern = re.compile( '@(' + '|'.join([re.escape(key) for key in self.context]) + ')@') for line in lines: yield pattern.sub(self.substitute, line) else: for line in lines: yield line def substitute(self, match): return str(self.context[match.group(1)]) run = Main() main = run.main if __name__ == '__main__': main(*sys.argv[1:]) Pymacs-0.25/pppp.rst.in000066400000000000000000000332221175204346300150210ustar00rootroot00000000000000.. role:: code(strong) .. role:: file(literal) .. role:: var(emphasis) ================================== pppp — Poor's Python Pre-Processor ================================== ---------------------------------------------------------------- (As within Pymacs @VERSION@) ---------------------------------------------------------------- :Author: François Pinard :Email: pinard@iro.umontreal.ca :Copyright: © Progiciels Bourbeau-Pinard inc., Montréal 2010 .. contents:: .. sectnum:: Introduction ============ Why pppp? --------- The Python community has long resisted the idea of a pre-processor for Python, and quite understandbly. The usual features of a pre-processor for other languages are well served at run-time in Python, alleviating the need. The advent of Python 3 changes the picture somehow, as Python 3 does not accept some Python 2 constructs, and vice-versa. In many situations, one cannot (at least without stretched stunts) write a single source file which can be compiled by both Python 2 and Python 3. The languages are so similar that it is irritating to keep sources separate: this is too much a burden for maintenance, in my opinion. This :code:`pppp` tool was written to help porting Pymacs to Python 3. I guess it could be useful for other Python programs or packages. Report problems, documentation flaws, or suggestions to François Pinard: + mailto:pinard@iro.umontreal.ca Installation ------------ There is no installation machinery for :code:`pppp`, and Pymacs does not install it either. To install it, merely pick the :file:`pppp` script from the top-level of the Pymacs distribution, either going through the main Pymacs site: + http://pymacs.progiciels-bpi.ca or more directly from GitHub: + http://github.com/pinard/Pymacs Copy that file somewhere on your search path, and make it executable. That's all to it. Pre-processor syntax ==================== There are two mechanisms in :code:`pppp`. One does in-line substitutions, the other takes care of conditional compilation. In-line substitution occurs first, one line at a time, then conditional compilation occurs on the result of the substitutions. The two mechanisms both rely on a preset *context*, which is a set of definitions. Each definition relates a name to a Python value. The context is built under the control of options given to the :code:`pppp` program. The same context is used both for substitution and conditionals. The behaviour of :code:`pppp` is currently unspecified when substitutions of a single line produces multiple lines, for which the first or any other is meant for conditional compilation. So don't do that! Substitutions ------------- Substitution is triggered on each occurrence of ``@``\ :var:`NAME`\ ``@`` in the sources. In each case, if :var:`NAME` is not the name of a context element, substitution just does not happen and the occurrence is left undisturbed — silently, without diagnostic. If substitution happens, :var:`NAME` and the surrounding ``@`` delimiters get replaced by the string of the value associated with that name within the context. Unless ``@``\ :var:`NAME`\ ``@`` sits within a Python string or a Python comment, it is invalid Python syntax. So (contrarily to conditionals described below), if the substitution notation is used, pre-processing is likely mandatory. Conditionals ------------ Conditional compilation is merely driven by usual Python ``if`` statements. However, to be considered for conditional compilation, the ``ìf``, ``elif`` or ``else`` lines should have the following colon (``:``) on the same physical line. Moreover, such lines should not use Python comments. The test expression associated with the ``if`` or any ``elif`` is evaluated using the pre-processor context. If all the variables or functions referred to by the expression are known in the context (and presuming there is no syntax error or other run-time error while evaluating the expression), the expression gets a dependable value. The ``if`` or ``elif`` line is itself removed (well, in some cases, an ``elif`` might become an ``else``), and the following block of lines is adjusted according to the expression value, likely shifted back or fully removed. Similarily, ``else`` clauses may sometimes get simplified. While it is possible to use very invalid Python syntax which, through :code:`pppp` conditional compilation, is turned into a valid Python program; users are much invited to use conditional compilation in such a way that sources meant for :code:`pppp` are directly legal Python syntax. This idea of writing conditionals as correct Python could be pushed even further. If the user manages to compute and assign the context variables at run-time in the Python program, conditional compilation for some name could be replaced by run-time checks on that name merely by *not* defining the name in the :code:`pppp` context. By doing so, the test expressions involving that name may not be resolved by the pre-processor, and the simplifications just does not occur. Invoking :code:`pppp` ===================== The :code:`pppp` command is called using the usual syntax for Unix / Linux commands:: pppp [OPTION]... [ARGUMENT]... The operating mode of the program, and the meaning of arguments, depend on some options being used or not. Option ``-c`` forces clean out mode, option ``-m`` forces merge mode. Otherwise, the program uses the pre-processing mode. The ``-h`` option is special. When given, a short help summary is written on standard output, and then, the program exits immediately. The ``-v`` option raises the verbosity level of the program, which then produces output about created directories, written files or deleted files. Setting the context ------------------- The context used for the pre-processing is initially empty. It does not even have Python builtins. It is then filled through the use of ``-C`` or ``-D`` options, which may be repeated when there are many definitions to introduce, or when there is a need to override previous settings. Option ``-D`` :var:`name` adds :var:`name` into the context, associating it with the Python value ``True``. Option ``-D`` :var:`name`\ ``=``\ :var:`expr` adds :var:`name` into the context, associating with the value of the Python expression :var:`expr`. Beware of Python characters which also have a meaning for the shell, proper quoting may be needed. Here is, for example, how to define a string while calling :code:`pppp`:: pppp -D "version='0.24-beta2'" ... While evaluating :var:`expr`, there is no restriction to the context, and builtins are indeed available. For exemple, to add the builtin :code:`ord` into the context, merely use ``-D ord=ord``. Option ``-C`` :var:`FILE` reads and evaluates :var:`FILE` as a Python source. All variables computed at the outer level then become names in the context, and the values of these variables become the values associated with the names within the context. Any function defined at the outer level of :var:`FILE` also gets available to :code:`pppp` pre-processing. Beware of uncleaned variables in :var:`FILE`. For example, an ``import sys`` creates a ``sys`` variable, which you normally clean with ``del sys`` near the end of :var:`FILE`. If you do not do so, that variable is available to the pre-processor. So if you have a line like:: if sys.version_info[:2] == (2, 7): somewhere in your :code:`pppp` source, this might be evaluated as ``True`` or ``False`` at pre-processing time rather than at run-time, and this might not be what you wanted. Pre-processing files -------------------- Without options ``-c`` nor ``-m``, the arguments to the program indicate which files are going to be pre-processed. If there is no argument at all, this is a special case by which standard input is read, pre-processed and then written to standard output. Otherwise, only eligible files are retained for pre-processing. To be eligible, the name of a file should end with ``.in``. If an argument names a directory, that directory is recursively searched to find all files with such an ``.in`` suffix. When a directory has a ``.in`` suffix (either given as an argument, or a subdirectory of a directory argument), *all* the files it contains become eligible, including all files of its subdirectories, recursively. Now, that ``.in`` suffix may be changed to something else, using the ``-s`` :var:`NAME` suffix option. The period is part of the option value. For example, ``-s '.in'`` is equivalent to not specifying it. Each eligible file is pre-processed and written on another file, the name of which is related to the name of the file being read. That name is produced by removing the ``.in`` suffix, and more precisely, by removing all ``.in`` suffixes, would they appear in directory names or file names. Moreover, the optional ``-o`` :var:`OUTPUT_DIRECTORY` option introduces a directory into which all resulting files are collected: it effectively prepends :var:`OUTPUT_DIRECTORY/` to all output names. If the suffix gets declared empty through ``-s ''``, then *all* files are eligible, and because output names would be identical to the input names, the ``-o`` option becomes mandatory. You do not have to prepare intermediate directories to receive output files. These are created on the fly, as needed. Pre-processing uses substitutions and conditionals. Substitutions automatically occur on all eligible files. Conditionals, however, only apply for files which are known to be Python sources. If option ``-p`` is given, all files are considered to be Python sources. Otherwise, a Python source has a file name which ends with ``.py`` or ``.py.in``, or appears to use a Python shebang line (the precise heuristic checks that the first line of the file starts with ``!#`` and has ``ython`` written somewhere in it). The :code:`pppp` tool assumes, by default, that the Python sources consistently use an indentation step, and that the indentation step is 4 columns. This can be changed with the ``-i`` :var:`INDENT` option. For example, ``-i 8`` means that the indentation step is 8 columns. By default, :code:`pppp` generates white lines in the pre-processed results to replace any removed lines. The idea is to guarantee usable line numbers in any later traceback, that is, numbers that refer to the correct position within the original file, before it was pre-processed. The file name would still differ by the ``.in`` suffix, of course, which is a lesser worse. Whenever, as side-effect of substitutions, a single input line yields many output lines, line synchronisation may be lost. :code:`pppp` then inhibits the production of replacement white lines until the line synchronisation is recovered. Option ``-n`` wholly inhibits the production of any white line only meant for synchronisation. Because tracebacks mention the file name after pre-processing, and not the original source before pre-processing, users are likely to inspect the resulting file, and after a while, start modifying it without realizing their mistake: a resulting file might be overwritten by a later invocation of :code:`pppp`, so loosing user's modifications. To play safe, :code:`pppp` attempts to detect this: it copies the modification time from the original into any resulting file it produces. Then, whenever a resulting file is newer than the original source, :code:`pppp` raises an error instead of deleting or rewriting it. Finally, as a way to force Python recompilation in case the resulting file becomes different, it removes an already compiled Python file, if any. If you want to force deletions or rewritings regardless, use option ``-f``. Cleaning out files ------------------ As a convenience for :file:`Makefile` writers, there is an option to help at cleaning out derived files. With ``-c`` specified, any file that would have been produced in pre-processing mode is removed instead. Of course, to be useful, the command arguments naming files or directories should be the same as those used for pre-processing. Merging versions ---------------- As a way to help prepare a Python file for :code:`pppp` pre-processing, the program offers a mode able to produce a pre-processable file out of two versions of a given Python source. For example:: pppp -mD VERSION2 script1.py script2.py > script.py.in compares :file:`script1.py` with :file:`script2.py` and produces a merged version on :file:`script.py.in`. Then, the command:: pppp -D VERSION2=False script.py.in would produce a file :file:`script.py` which is equivalent to :file:`script1.py`, while the command:: pppp -D VERSION2 script.py.in would produce a file :file:`script.py` which is equivalent to :file:`script2.py`. Whenever option ``-m`` is used, exactly one ``-D`` option provides the segregating name used in added conditionals, and two arguments tell the versions to be compared. Beware that this mode was quickly written, and stays rather crude and approximative. This is merely a way to get started. The real and patient work comes afterwards, with a text editor, to clean and fixup things, and bring the merged result closer to real Python syntax. While editing the result, you might find some ``#endif (pppp)`` lines generated here and there. These are protective measures, so the later pre-processing does not clearly produce wrong results. These lines usually indicate problematic areas, for which revision and careful refactoring is especially needed. Limitations =========== + The need of a very consistent indentation, as far as the indentation step is considered, may be too stringent a condition. It would surely be nicer if :code:`pppp` was able to adapt to the indentation in use. + This tool is easily fooled by unindented comments or multi-line strings, as it is driven only by textual line indentation. It does not follow whether a line is part of multi-line string or not. Pymacs-0.25/ppppconfig.py000066400000000000000000000033671175204346300154310ustar00rootroot00000000000000# -*- coding: utf-8 -*- # p4 configuration for Pymacs. # Overall Pymacs configuration # ============================ # VERSION is the name of the Pymacs version, as declared within setup.py. def get_version(): import re for line in open('setup.py'): match = re.match('version *= *([\'"][^\'"]*[\'"])', line) if match: return eval(match.group(1)) VERSION = get_version() del get_version # Configuration for the Emacs Lisp side # ===================================== # DEFADVICE_OK is 't' when it is safe to use defadvice. It has been reported # that, at least under Aquamacs (an MacOS X native port of Emacs), one gets # "Lisp nesting exceeds `max-lisp-eval-depth'" messages while requesting # functions documentation (we do not know why). Set this variable to 'nil' # as a way to avoid the problem. DEFADVICE_OK = 't' # PYTHON gets the command name of the Python interpreter. def get_python(): import os return os.getenv('PYTHON') or 'python' PYTHON = get_python() del get_python # Configuration for Python (Pymacs helper) # ======================================== # It has been reported that intercepting all signals (and optionally writing # a trace of them, create IO problems within the Pymacs helper itself. So for # now, IO_ERRORS_WITH_SIGNALS is blindly set to True, until I know better. # When True, only the Interrupt signal gets monitored. IO_ERRORS_WITH_SIGNALS = True # OLD_EXCEPTIONS is True for old Python or Jython versions. def get_old_exceptions(): return not isinstance(Exception, type) OLD_EXCEPTIONS = get_old_exceptions() del get_old_exceptions # PYTHON3 is True within Python 3. def get_python3(): import sys return sys.version_info[0] == 3 PYTHON3 = get_python3() del get_python3 Pymacs-0.25/pymacs.el.in000066400000000000000000001051671175204346300151360ustar00rootroot00000000000000;;; pymacs.el --- Interface between Emacs Lisp and Python ;; Copyright © 2001, 2002, 2003, 2012 Progiciels Bourbeau-Pinard inc. ;; Author: François Pinard ;; Maintainer: François Pinard ;; Created: 2001 ;; Version: @VERSION@ ;; Keywords: Python interface protocol ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ ;;; Commentary: ;; Pymacs is a powerful tool which, once started from Emacs, allows ;; both-way communication between Emacs Lisp and Python. Pymacs aims ;; Python as an extension language for Emacs rather than the other way ;; around. Visit http://pymacs.progiciels-bpi.ca to read its manual, ;; which also contains installation instructions. ;;; Code: ;; The code is organized into pages, grouping declarations by topic. ;; Such pages are introduced by a form feed and a topic description. ;;; Portability stunts. (defvar pymacs-use-hash-tables (and (fboundp 'make-hash-table) (fboundp 'gethash) (fboundp 'puthash)) "Set to t if hash tables are available.") (eval-and-compile ;; pymacs-cancel-timer (defalias 'pymacs-cancel-timer (cond ((fboundp 'cancel-timer) 'cancel-timer) ;; XEmacs case - yet having post-gc-hook, this is unused. ((fboundp 'delete-itimer) 'delete-itimer) (t 'ignore))) ;; pymacs-kill-without-query (if (fboundp 'set-process-query-on-exit-flag) (defun pymacs-kill-without-query (process) "Tell recent Emacs how to quickly destroy PROCESS while exiting." (set-process-query-on-exit-flag process nil)) (defalias 'pymacs-kill-without-query (if (fboundp 'process-kill-without-query-process) 'process-kill-without-query-process 'ignore))) ;; pymacs-multibyte-string-p (cond ((fboundp 'multibyte-string-p) (defalias 'pymacs-multibyte-string-p 'multibyte-string-p)) ((fboundp 'find-charset-string) (defun pymacs-multibyte-string-p (string) "Tell XEmacs if STRING should be handled as multibyte." (not (member (find-charset-string string) '(nil (ascii)))))) (t ;; Tell XEmacs that STRING is unibyte, when Mule is not around! (defalias 'pymacs-multibyte-string-p 'ignore))) ;; pymacs-report-error (defalias 'pymacs-report-error (symbol-function 'error)) ;; pymacs-set-buffer-multibyte (if (fboundp 'set-buffer-multibyte) (defalias 'pymacs-set-buffer-multibyte 'set-buffer-multibyte) (defun pymacs-set-buffer-multibyte (flag) "For use in Emacs 20.2 or earlier. Under XEmacs: no operation." (setq enable-multibyte-characters flag))) ;; pymacs-timerp (defalias 'pymacs-timerp (cond ((fboundp 'timerp) 'timerp) ;; XEmacs case - yet having post-gc-hook, this is unused. ((fboundp 'itimerp) 'itimerp) (t 'ignore))) ) ;;; Published variables and functions. (defvar pymacs-python-command "@PYTHON@" "Shell command used to start Python interpreter.") (defvar pymacs-load-path nil "List of additional directories to search for Python modules. The directories listed will be searched first, in the order given.") (defvar pymacs-trace-transit '(5000 . 30000) "Keep the communication buffer growing, for debugging. When this variable is nil, the `*Pymacs*' communication buffer gets erased before each communication round-trip. Setting it to `t' guarantees that the full communication is saved, which is useful for debugging. It could also be given as (KEEP . LIMIT): whenever the buffer exceeds LIMIT bytes, it is reduced to approximately KEEP bytes.") (defvar pymacs-forget-mutability nil "Transmit copies to Python instead of Lisp handles, as much as possible. When this variable is nil, most mutable objects are transmitted as handles. This variable is meant to be temporarily rebound to force copies.") (defvar pymacs-mutable-strings nil "Prefer transmitting Lisp strings to Python as handles. When this variable is nil, strings are transmitted as copies, and the Python side thus has no way for modifying the original Lisp strings. This variable is ignored whenever `forget-mutability' is set.") (defvar pymacs-timeout-at-start 30 "Maximum reasonable time, in seconds, for starting the Pymacs helper. A machine should be pretty loaded before one needs to increment this.") (defvar pymacs-timeout-at-reply 5 "Expected maximum time, in seconds, to get the first line of a reply. The status of the Pymacs helper is checked at every such timeout.") (defvar pymacs-timeout-at-line 2 "Expected maximum time, in seconds, to get another line of a reply. The status of the Pymacs helper is checked at every such timeout.") (defvar pymacs-auto-restart 'ask "Should the Pymacs helper be restarted whenever it dies? Possible values are nil, t or ask.") (defvar pymacs-dreadful-zombies nil "If zombies should trigger hard errors, whenever they get called. If `nil', calling a zombie will merely produce a diagnostic message.") ;;;###autoload (defun pymacs-load (module &optional prefix noerror) "Import the Python module named MODULE into Emacs. Each function in the Python module is made available as an Emacs function. The Lisp name of each function is the concatenation of PREFIX with the Python name, in which underlines are replaced by dashes. If PREFIX is not given, it defaults to MODULE followed by a dash. If NOERROR is not nil, do not raise error when the module is not found." (interactive (let* ((module (read-string "Python module? ")) (default (concat (car (last (split-string module "\\."))) "-")) (prefix (read-string (format "Prefix? [%s] " default) nil nil default))) (list module prefix))) (message "Pymacs loading %s..." module) (let ((lisp-code (pymacs-call "pymacs_load_helper" module prefix))) (cond (lisp-code (let ((result (eval lisp-code))) (message "Pymacs loading %s...done" module) result)) (noerror (message "Pymacs loading %s...failed" module) nil) (t (pymacs-report-error "Pymacs loading %s...failed" module))))) ;;;###autoload (defun pymacs-autoload (function module &optional prefix docstring interactive) "Pymacs's equivalent of the standard emacs facility `autoload'. Define FUNCTION to autoload from Python MODULE using PREFIX. If PREFIX is not given, it defaults to MODULE followed by a dash. Optional DOCSTRING documents FUNCTION until it gets loaded. INTERACTIVE is normally the argument to the function `interactive', t means `interactive' without arguments, nil means not interactive, which is the default." (unless (symbolp function) (error "`%s' should be a symbol" function)) (unless (stringp module) (error "`%s' should be a string" module)) (unless (pymacs-python-reference function) (defalias function `(lambda (&rest args) ,(or docstring (format "Function `%s' to be loaded from Python module `%s'" function module)) ,(cond ((eq interactive t) '(interactive)) (interactive `(interactive ,interactive))) (pymacs-load ,module ,prefix) (unless (pymacs-python-reference ',function) (error "Pymacs autoload failed to define function %s" ',function)) (apply ',function args))))) ;;;###autoload (defun pymacs-eval (text) "Compile TEXT as a Python expression, and return its value." (interactive "sPython expression? ") (let ((value (pymacs-serve-until-reply "eval" `(princ ,text)))) (when (interactive-p) (message "%S" value)) value)) ;;;###autoload (defun pymacs-exec (text) "Compile and execute TEXT as a sequence of Python statements. This functionality is experimental, and does not appear to be useful." (interactive "sPython statements? ") (let ((value (pymacs-serve-until-reply "exec" `(princ ,text)))) (when (interactive-p) (message "%S" value)) value)) ;;;###autoload (defun pymacs-call (function &rest arguments) "Return the result of calling a Python function FUNCTION over ARGUMENTS. FUNCTION is a string denoting the Python function, ARGUMENTS are separate Lisp expressions, one per argument. Immutable Lisp constants are converted to Python equivalents, other structures are converted into Lisp handles." (pymacs-serve-until-reply "eval" `(pymacs-print-for-apply ',function ',arguments))) ;;;###autoload (defun pymacs-apply (function arguments) "Return the result of calling a Python function FUNCTION over ARGUMENTS. FUNCTION is a string denoting the Python function, ARGUMENTS is a list of Lisp expressions. Immutable Lisp constants are converted to Python equivalents, other structures are converted into Lisp handles." (pymacs-serve-until-reply "eval" `(pymacs-print-for-apply ',function ',arguments))) ;;; Integration details. ;; This page tries to increase the integration seamlessness of Pymacs ;; with the reminder of Emacs. ;; Module "desktop" savagely kills `*Pymacs*' in some circumstances. ;; Let's avoid such damage. (eval-after-load 'desktop '(push "\\*Pymacs\\*" desktop-clear-preserve-buffers)) ;; Python functions and modules should ideally look like Lisp ;; functions and modules. (when @DEFADVICE_OK@ (defadvice documentation (around pymacs-ad-documentation activate) ;; Integration of doc-strings. (let* ((reference (pymacs-python-reference function)) (python-doc (when reference (pymacs-eval (format "doc_string(%s)" reference))))) (if (or reference python-doc) (setq ad-return-value (concat "It interfaces to a Python function.\n\n" (when python-doc (if raw python-doc (substitute-command-keys python-doc))))) ad-do-it)))) (defun pymacs-python-reference (object) ;; Return the text reference of a Python object if possible, else nil. (when (functionp object) (let* ((definition (indirect-function object)) (body (and (pymacs-proper-list-p definition) (> (length definition) 2) (eq (car definition) 'lambda) (cddr definition)))) (when (and body (listp (car body)) (eq (caar body) 'interactive)) ;; Skip the interactive specification of a function. (setq body (cdr body))) (when (and body ;; Advised functions start with a string. (not (stringp (car body))) ;; Python trampolines hold exactly one expression. (= (length body) 1)) (let ((expression (car body))) ;; EXPRESSION might now hold something like: ;; (pymacs-apply (quote (pymacs-python . N)) ARGUMENT-LIST) (when (and (pymacs-proper-list-p expression) (= (length expression) 3) (eq (car expression) 'pymacs-apply) (eq (car (cadr expression)) 'quote)) (setq object (cadr (cadr expression)))))))) (when (eq (car-safe object) 'pymacs-python) (format "python[%d]" (cdr object)))) ;; The following functions are experimental -- they are not satisfactory yet. (defun pymacs-file-handler (operation &rest arguments) ;; Integration of load-file, autoload, etc. ;; Emacs might want the contents of some `MODULE.el' which does not exist, ;; while there is a `MODULE.py' or `MODULE.pyc' file in the same directory. ;; The goal is to generate a virtual contents for this `MODULE.el' file, as ;; a set of Lisp trampoline functions to the Python module functions. ;; Python modules can then be loaded or autoloaded as if they were Lisp. (cond ((and (eq operation 'file-readable-p) (let ((module (substring (car arguments) 0 -3))) (or (pymacs-file-force operation arguments) (file-readable-p (concat module ".py")) (file-readable-p (concat module ".pyc")))))) ((and (eq operation 'load) (not (pymacs-file-force 'file-readable-p (list (car arguments)))) (file-readable-p (car arguments))) (let ((lisp-code (pymacs-call "pymacs_load_helper" (substring (car arguments) 0 -3) nil))) (unless lisp-code (pymacs-report-error "Python import error")) (eval lisp-code))) ((and (eq operation 'insert-file-contents) (not (pymacs-file-force 'file-readable-p (list (car arguments)))) (file-readable-p (car arguments))) (let ((lisp-code (pymacs-call "pymacs_load_helper" (substring (car arguments) 0 -3) nil))) (unless lisp-code (pymacs-report-error "Python import error")) (insert (prin1-to-string lisp-code)))) (t (pymacs-file-force operation arguments)))) (defun pymacs-file-force (operation arguments) ;; Bypass the file handler. (let ((inhibit-file-name-handlers (cons 'pymacs-file-handler (and (eq inhibit-file-name-operation operation) inhibit-file-name-handlers))) (inhibit-file-name-operation operation)) (apply operation arguments))) ;;(add-to-list 'file-name-handler-alist '("\\.el\\'" . pymacs-file-handler)) ;;; Gargabe collection of Python IDs. ;; Python objects which have no Lisp representation are allocated on the ;; Python side as `python[INDEX]', and INDEX is transmitted to Emacs, with ;; the value to use on the Lisp side for it. Whenever Lisp does not need a ;; Python object anymore, it should be freed on the Python side. The ;; following variables and functions are meant to fill this duty. (defvar pymacs-used-ids nil "List of received IDs, currently allocated on the Python side.") ;; This is set whenever the Pymacs helper successfully starts, and is ;; also used to later detect the death of a previous helper. If ;; pymacs-use-hash-tables is unset, this variable receives `t' when ;; the helper starts, so the detection works nevertheless. (defvar pymacs-weak-hash nil "Weak hash table, meant to find out which IDs are still needed.") (defvar pymacs-gc-wanted nil "Flag that it is desirable to clean up unused IDs on the Python side.") (defvar pymacs-gc-inhibit nil "Flag that a new Pymacs garbage collection should just not run now.") (defvar pymacs-gc-timer nil "Timer to trigger Pymacs garbage collection at regular time intervals. The timer is used only if `post-gc-hook' is not available.") (defun pymacs-schedule-gc (&optional xemacs-list) (unless pymacs-gc-inhibit (setq pymacs-gc-wanted t))) (defun pymacs-garbage-collect () ;; Clean up unused IDs on the Python side. (when (and pymacs-use-hash-tables (not pymacs-gc-inhibit)) (let ((pymacs-gc-inhibit t) (pymacs-forget-mutability t) (ids pymacs-used-ids) used-ids unused-ids) (while ids (let ((id (car ids))) (setq ids (cdr ids)) (if (gethash id pymacs-weak-hash) (setq used-ids (cons id used-ids)) (setq unused-ids (cons id unused-ids))))) (setq pymacs-used-ids used-ids pymacs-gc-wanted nil) (when unused-ids (let ((pymacs-forget-mutability t)) (pymacs-call "free_python" unused-ids)))))) (defun pymacs-defuns (arguments) ;; Take one argument, a list holding a number of items divisible by 3. The ;; first argument is an INDEX, the second is a NAME, the third is the ;; INTERACTION specification, and so forth. Register Python INDEX with a ;; function with that NAME and INTERACTION on the Lisp side. The strange ;; calling convention is to minimise quoting at call time. (while (>= (length arguments) 3) (let ((index (nth 0 arguments)) (name (nth 1 arguments)) (interaction (nth 2 arguments))) (fset name (pymacs-defun index interaction)) (setq arguments (nthcdr 3 arguments))))) (defun pymacs-defun (index interaction) ;; Register INDEX on the Lisp side with a Python object that is a function, ;; and return a lambda form calling that function. If the INTERACTION ;; specification is nil, the function is not interactive. Otherwise, the ;; function is interactive, INTERACTION is then either a string, or the ;; index of an argument-less Python function returning the argument list. (let ((object (pymacs-python index))) (cond ((null interaction) `(lambda (&rest arguments) (pymacs-apply ',object arguments))) ((stringp interaction) `(lambda (&rest arguments) (interactive ,interaction) (pymacs-apply ',object arguments))) (t `(lambda (&rest arguments) (interactive (pymacs-call ',(pymacs-python interaction))) (pymacs-apply ',object arguments)))))) (defun pymacs-python (index) ;; Register on the Lisp side a Python object having INDEX, and return it. ;; The result is meant to be recognised specially by `print-for-eval', and ;; in the function position by `print-for-apply'. (let ((object (cons 'pymacs-python index))) (when pymacs-use-hash-tables (puthash index object pymacs-weak-hash) (setq pymacs-used-ids (cons index pymacs-used-ids))) object)) ;;; Generating Python code. ;; Many Lisp expressions cannot fully be represented in Python, at least ;; because the object is mutable on the Lisp side. Such objects are allocated ;; somewhere into a vector of handles, and the handle index is used for ;; communication instead of the expression itself. (defvar pymacs-lisp nil "Vector of handles to hold transmitted expressions.") (defvar pymacs-freed-list nil "List of unallocated indices in Lisp.") ;; When the Python GC is done with a Lisp object, a communication occurs so to ;; free the object on the Lisp side as well. (defun pymacs-allocate-lisp (expression) ;; This function allocates some handle for an EXPRESSION, and return its ;; index. (unless pymacs-freed-list (let* ((previous pymacs-lisp) (old-size (length previous)) (new-size (if (zerop old-size) 100 (+ old-size (/ old-size 2)))) (counter new-size)) (setq pymacs-lisp (make-vector new-size nil)) (while (> counter 0) (setq counter (1- counter)) (if (< counter old-size) (aset pymacs-lisp counter (aref previous counter)) (setq pymacs-freed-list (cons counter pymacs-freed-list)))))) (let ((index (car pymacs-freed-list))) (setq pymacs-freed-list (cdr pymacs-freed-list)) (aset pymacs-lisp index expression) index)) (defun pymacs-free-lisp (indices) ;; This function is triggered from Python side for Lisp handles which lost ;; their last reference. These references should be cut on the Lisp side as ;; well, or else, the objects will never be garbage-collected. (while indices (let ((index (car indices))) (aset pymacs-lisp index nil) (setq pymacs-freed-list (cons index pymacs-freed-list) indices (cdr indices))))) (defun pymacs-print-for-apply (function arguments) ;; This function prints a Python expression calling FUNCTION, which is a ;; string naming a Python function, or a Python reference, over all its ;; ARGUMENTS, which are Lisp expressions. (let ((separator "") argument) (if (eq (car-safe function) 'pymacs-python) (princ (format "python[%d]" (cdr function))) (princ function)) (princ "(") (while arguments (setq argument (car arguments) arguments (cdr arguments)) (princ separator) (setq separator ", ") (pymacs-print-for-eval argument)) (princ ")"))) (defun pymacs-print-for-eval (expression) ;; This function prints a Python expression out of a Lisp EXPRESSION. (let (done) (cond ((not expression) (princ "None") (setq done t)) ((eq expression t) (princ "True") (setq done t)) ((numberp expression) (princ expression) (setq done t)) ((stringp expression) (when (or pymacs-forget-mutability (not pymacs-mutable-strings)) (let* ((multibyte (pymacs-multibyte-string-p expression)) (text (if multibyte (encode-coding-string expression 'utf-8) (copy-sequence expression)))) (set-text-properties 0 (length text) nil text) (princ (mapconcat 'identity (split-string (prin1-to-string text) "\n") "\\n")) (when multibyte (princ ".decode('UTF-8')"))) (setq done t))) ((symbolp expression) (let ((name (symbol-name expression))) ;; The symbol can only be transmitted when in the main oblist. (when (eq expression (intern-soft name)) (princ "lisp[") (prin1 name) (princ "]") (setq done t)))) ((vectorp expression) (when pymacs-forget-mutability (let ((limit (length expression)) (counter 0)) (princ "(") (while (< counter limit) (unless (zerop counter) (princ ", ")) (pymacs-print-for-eval (aref expression counter)) (setq counter (1+ counter))) (when (= limit 1) (princ ",")) (princ ")") (setq done t)))) ((eq (car-safe expression) 'pymacs-python) (princ "python[") (princ (cdr expression)) (princ "]") (setq done t)) ((pymacs-proper-list-p expression) (when pymacs-forget-mutability (princ "[") (pymacs-print-for-eval (car expression)) (while (setq expression (cdr expression)) (princ ", ") (pymacs-print-for-eval (car expression))) (princ "]") (setq done t)))) (unless done (let ((class (cond ((vectorp expression) "Vector") ((and pymacs-use-hash-tables (hash-table-p expression)) "Table") ((bufferp expression) "Buffer") ((pymacs-proper-list-p expression) "List") (t "Lisp")))) (princ class) (princ "(") (princ (pymacs-allocate-lisp expression)) (princ ")"))))) ;;; Communication protocol. (defvar pymacs-transit-buffer nil "Communication buffer between Emacs and Python.") ;; The principle behind the communication protocol is that it is easier to ;; generate than parse, and that each language already has its own parser. ;; So, the Emacs side generates Python text for the Python side to interpret, ;; while the Python side generates Lisp text for the Lisp side to interpret. ;; About nothing but expressions are transmitted, which are evaluated on ;; arrival. The pseudo `reply' function is meant to signal the final result ;; of a series of exchanges following a request, while the pseudo `error' ;; function is meant to explain why an exchange could not have been completed. ;; The protocol itself is rather simple, and contains human readable text ;; only. A message starts at the beginning of a line in the communication ;; buffer, either with `>' for the Lisp to Python direction, or `<' for the ;; Python to Lisp direction. This is followed by a decimal number giving the ;; length of the message text, a TAB character, and the message text itself. ;; Message direction alternates systematically between messages, it never ;; occurs that two successive messages are sent in the same direction. The ;; first message is received from the Python side, it is `(version VERSION)'. (defun pymacs-start-services () ;; This function gets called automatically, as needed. (let ((buffer (get-buffer-create "*Pymacs*"))) (with-current-buffer buffer ;; Erase the buffer in case some previous incarnation of the ;; Pymacs helper died. Otherwise, the "(goto-char (point-min))" ;; below might not find the proper synchronising reply and later ;; trigger a spurious "Protocol error" diagnostic. (erase-buffer) (buffer-disable-undo) (pymacs-set-buffer-multibyte nil) (set-buffer-file-coding-system 'raw-text) (save-match-data ;; Launch the Pymacs helper. (let ((process (apply 'start-process "pymacs" buffer (let ((python (getenv "PYMACS_PYTHON"))) (if (or (null python) (equal python "")) pymacs-python-command python)) "-c" (concat "import sys;" " from Pymacs import main;" " main(*sys.argv[1:])") (append (and (>= emacs-major-version 24) '("-f")) (mapcar 'expand-file-name pymacs-load-path))))) (pymacs-kill-without-query process) ;; Receive the synchronising reply. (while (progn (goto-char (point-min)) (not (re-search-forward "<\\([0-9]+\\)\t" nil t))) (unless (accept-process-output process pymacs-timeout-at-start) (pymacs-report-error "Pymacs helper did not start within %d seconds" pymacs-timeout-at-start))) (let ((marker (process-mark process)) (limit-position (+ (match-end 0) (string-to-number (match-string 1))))) (while (< (marker-position marker) limit-position) (unless (accept-process-output process pymacs-timeout-at-start) (pymacs-report-error "Pymacs helper probably was interrupted at start"))))) ;; Check that synchronisation occurred. (goto-char (match-end 0)) (let ((reply (read (current-buffer)))) (if (and (pymacs-proper-list-p reply) (= (length reply) 2) (eq (car reply) 'version)) (unless (string-equal (cadr reply) "@VERSION@") (pymacs-report-error "Pymacs Lisp version is @VERSION@, Python is %s" (cadr reply))) (pymacs-report-error "Pymacs got an invalid initial reply"))))) (if (not pymacs-use-hash-tables) (setq pymacs-weak-hash t) (when pymacs-used-ids ;; A previous Pymacs session occurred in this Emacs session, ;; some IDs hang around which do not correspond to anything on ;; the Python side. Python should not recycle such IDs for ;; new objects. (let ((pymacs-transit-buffer buffer) (pymacs-forget-mutability t) (pymacs-gc-inhibit t)) (pymacs-call "zombie_python" pymacs-used-ids)) (setq pymacs-used-ids nil)) (setq pymacs-weak-hash (make-hash-table :weakness 'value)) (if (boundp 'post-gc-hook) (add-hook 'post-gc-hook 'pymacs-schedule-gc) (setq pymacs-gc-timer (run-at-time 20 20 'pymacs-schedule-gc)))) ;; If nothing failed, only then declare that Pymacs has started! (setq pymacs-transit-buffer buffer))) (defun pymacs-terminate-services () ;; This function is mainly provided for documentation purposes. (interactive) (garbage-collect) (pymacs-garbage-collect) (when (or (not pymacs-used-ids) (yes-or-no-p "\ Killing the Pymacs helper might create zombie objects. Kill? ")) (cond ((boundp 'post-gc-hook) (remove-hook 'post-gc-hook 'pymacs-schedule-gc)) ((pymacs-timerp pymacs-gc-timer) (pymacs-cancel-timer pymacs-gc-timer))) (when pymacs-transit-buffer (kill-buffer pymacs-transit-buffer)) (setq pymacs-gc-inhibit nil pymacs-gc-timer nil pymacs-transit-buffer nil pymacs-lisp nil pymacs-freed-list nil))) (defun pymacs-serve-until-reply (action inserter) ;; This function builds a Python request by printing ACTION and ;; evaluating INSERTER, which itself prints an argument. It then ;; sends the request to the Pymacs helper, and serves all ;; sub-requests coming from the Python side, until either a reply or ;; an error is finally received. (unless (and pymacs-transit-buffer (buffer-name pymacs-transit-buffer) (get-buffer-process pymacs-transit-buffer)) (when pymacs-weak-hash (unless (or (eq pymacs-auto-restart t) (and (eq pymacs-auto-restart 'ask) (yes-or-no-p "The Pymacs helper died. Restart it? "))) (pymacs-report-error "There is no Pymacs helper!"))) (pymacs-start-services)) (when pymacs-gc-wanted (pymacs-garbage-collect)) (let ((inhibit-quit t) done value) (while (not done) (let ((form (pymacs-round-trip action inserter))) (setq action (car form)) (when (eq action 'free) (pymacs-free-lisp (cadr form)) (setq form (cddr form) action (car form))) (let* ((pair (pymacs-interruptible-eval (cadr form))) (success (cdr pair))) (setq value (car pair)) (cond ((eq action 'eval) (if success (setq action "return" inserter `(pymacs-print-for-eval ',value)) (setq action "raise" inserter `(let ((pymacs-forget-mutability t)) (pymacs-print-for-eval ,value))))) ((eq action 'expand) (if success (setq action "return" inserter `(let ((pymacs-forget-mutability t)) (pymacs-print-for-eval ,value))) (setq action "raise" inserter `(let ((pymacs-forget-mutability t)) (pymacs-print-for-eval ,value))))) ((eq action 'return) (if success (setq done t) (pymacs-report-error "%s" value))) ((eq action 'raise) (if success (pymacs-report-error "Python: %s" value) (pymacs-report-error "%s" value))) (t (pymacs-report-error "Protocol error: %s" form)))))) value)) (defun pymacs-round-trip (action inserter) ;; This function produces a Python request by printing and ;; evaluating INSERTER, which itself prints an argument. It sends ;; the request to the Pymacs helper, awaits for any kind of reply, ;; and returns it. (with-current-buffer pymacs-transit-buffer ;; Possibly trim the beginning of the transit buffer. (cond ((not pymacs-trace-transit) (erase-buffer)) ((consp pymacs-trace-transit) (when (> (buffer-size) (cdr pymacs-trace-transit)) (let ((cut (- (buffer-size) (car pymacs-trace-transit)))) (when (> cut 0) (save-excursion (goto-char cut) (unless (memq (preceding-char) '(0 ?\n)) (forward-line 1)) (delete-region (point-min) (point)))))))) ;; Send the request, wait for a reply, and process it. (let* ((process (get-buffer-process pymacs-transit-buffer)) (status (process-status process)) (marker (process-mark process)) (moving (= (point) marker)) send-position reply-position reply) (save-excursion (save-match-data ;; Encode request. (setq send-position (marker-position marker)) (let ((standard-output marker)) (princ action) (princ " ") (eval inserter)) (goto-char marker) (unless (= (preceding-char) ?\n) (princ "\n" marker)) ;; Send request text. (goto-char send-position) (insert (format ">%d\t" (- marker send-position))) (setq reply-position (marker-position marker)) (process-send-region process send-position marker) ;; Receive reply text. (while (and (eq status 'run) (progn (goto-char reply-position) (not (re-search-forward "<\\([0-9]+\\)\t" nil t)))) (unless (accept-process-output process pymacs-timeout-at-reply) (setq status (process-status process)))) (when (eq status 'run) (let ((limit-position (+ (match-end 0) (string-to-number (match-string 1))))) (while (and (eq status 'run) (< (marker-position marker) limit-position)) (unless (accept-process-output process pymacs-timeout-at-line) (setq status (process-status process)))))) ;; Decode reply. (if (not (eq status 'run)) (pymacs-report-error "Pymacs helper status is `%S'" status) (goto-char (match-end 0)) (setq reply (read (current-buffer)))))) (when (and moving (not pymacs-trace-transit)) (goto-char marker)) reply))) (defun pymacs-interruptible-eval (expression) ;; This function produces a pair (VALUE . SUCCESS) for EXPRESSION. ;; A cautious evaluation of EXPRESSION is attempted, and any ;; error while evaluating is caught, including Emacs quit (C-g). ;; Any Emacs quit also gets forward as a SIGINT to the Pymacs handler. ;; With SUCCESS being true, VALUE is the expression value. ;; With SUCCESS being false, VALUE is an interruption diagnostic. (condition-case info (cons (let ((inhibit-quit nil)) (eval expression)) t) (quit (setq quit-flag t) (interrupt-process pymacs-transit-buffer) (cons "*Interrupted!*" nil)) (error (cons (prin1-to-string info) nil)))) (defun pymacs-proper-list-p (expression) ;; Tell if a list is proper, id est, that it is `nil' or ends with `nil'. (cond ((not expression)) ((consp expression) (not (cdr (last expression)))))) (provide 'pymacs) ;;; pymacs.el ends here Pymacs-0.25/pymacs.rst.in000066400000000000000000002622611175204346300153450ustar00rootroot00000000000000.. role:: code(strong) .. role:: file(literal) .. role:: var(emphasis) ================================================================ Pymacs version @VERSION@ ================================================================ --------------------------- Extending Emacs with Python --------------------------- :Author: François Pinard :Email: pinard@iro.umontreal.ca :Copyright: © Progiciels Bourbeau-Pinard inc., Montréal 2003, 2008, 2010, 2012 .. contents:: .. sectnum:: .. There exists a `Romanian translation`__ of this manual. __ http://webhostinggeeks.com/science/pymacs-framework-ro .. By `Alexander Ovsov` alovsov@gmail.com Introduction ============ What is Pymacs? --------------- Pymacs is a powerful tool which, once started from Emacs, allows two-way communication between Emacs Lisp and Python. Pymacs aims to employ Python as an extension language for Emacs rather than the other way around, and this asymmetry is reflected in some design choices. Within Emacs Lisp code, one may load and use Python modules. Python functions may themselves use Emacs services, and handle Emacs Lisp objects kept in Emacs Lisp space. The goals are to write *naturally* in both languages, debug with ease, fall back gracefully on errors, and allow full cross-recursion. It is very easy to install Pymacs, as neither Emacs nor Python need to be compiled nor relinked. Emacs merely starts Python as a subprocess, and Pymacs implements a communication protocol between both processes. Report problems, documentation flaws, or suggestions to François Pinard: + mailto:pinard@iro.umontreal.ca Documentation and examples -------------------------- The main Pymacs site conveys the Pymacs documentation (you are reading its Pymacs manual right now) and distributions: + http://pymacs.progiciels-bpi.ca I expect average Pymacs users to have a deeper knowledge of Python than Emacs Lisp. People have widely varying approaches in writing :file:`.emacs` files, as far as Pymacs is concerned: + Some can go and write almost no Emacs Lisp, yet a bit is still necessary for establishing a few loading hooks. For many simple needs, one can do a lot without having to learn much. + On the other hand, for more sophisticated usages, people cannot really escape knowing the Emacs Lisp API to some extent, because they should be familiar, programming-wise, with what is a buffer, a point, a mark, etc. and what are the allowed operations on those. While Pymacs examples are no substitute for a careful reading of the Pymacs manual, the contemplation and study of others' nice works may well enligthen and deepen your understanding. A few examples are included within the Pymacs distribution, each as a subdirectory of the :file:`contrib/` directory, and each having its own :file:`README` file. These are listed below, easiest examples first: + Paul Winkler's example + http://pymacs.progiciels-bpi.ca/Winkler.html + Fernando Pérez' examples + http://pymacs.progiciels-bpi.ca/Perez.html + http://pymacs.progiciels-bpi.ca/contrib/Perez/ + Giovanni Giorgi's files + http://pymacs.progiciels-bpi.ca/Giorgi.html + http://pymacs.progiciels-bpi.ca/contrib/Giorgi/ + A reformatter for boxed comments + http://pymacs.progiciels-bpi.ca/rebox.html + http://pymacs.progiciels-bpi.ca/contrib/rebox/ A few more substantial examples of Pymacs usage have been brought to my attention, and are available externally (listed here in no particular order): + pymdev — A Python Emacs Development Module: + http://www.toolness.com/pymdev/ + Ropemacs — Features like refactoring and code-assists: + http://rope.sf.net/ropemacs.html + http://rope.sf.net/hg/ropemacs + Bicycle Repair Man — A Refactoring Tool for Python: + http://bicyclerepair.sourceforge.net/ + Emacs Freex — A personal wiki on steroids: + http://www.princeton.edu/%7Egdetre/software/freex/docs/index.html + PyJde — Java dev source code browsing features in Emacs using Python: + http://code.google.com/p/pyjde/ The QaTeX project was influenced by Pymacs, according to its author: + http://qatex.sourceforge.net/ + http://www.pytex.org/doc/eurotex2005.pdf Other resources --------------- You are welcome writing to or joining the following mailing list, where there are a few people around likely to give you feedback: + mailto:pymacs-devel@googlegroups.com + https://groups.google.com/group/pymacs-devel/ If you have no fear of wider crowds :-), there still is: + mailto:python-list@python.org There are other Web sites specifically about Pymacs. `Giovanni Giorgi`__ has one of them: + http://blog.objectsroot.com/projects/pymacs/ __ http://blog.objectsroot.com/ There is an entry for Pymacs on Freshmeat: + http://freshmeat.net/projects/pymacs/ Installation ============ Check the search paths ---------------------- You should make sure that both Emacs and Python are usable, whatever the directory happens to be the current one. This is particularly important at the time Emacs launches Python under the scene, as Python ought to be found then started. On most systems, this means setting the search path correctly. The following notes, for MS Windows, have been provided by Greg Detre. + After ``Start / Run / Cmd``, type ``python``. If this works wherever you are, then your Python installation directory is already in your system's :code:`PATH` environment variable. If that's not the case, follow the instructions here to add it: http://www.computerhope.com/issues/ch000549.htm + You may have to add the directory containing the Python scripts that you want to run through Pymacs to your :code:`PYTHONPATH` variable, in the same fashion as above. You can test this by running Python, and then:: import sys sys.path or just:: import my_python_scripts from somewhere besides your scripts directory. Edit the configuration file --------------------------- In most cases, you may safely skip this step, as it is only needed in unusual, problematic circumstances. Merely check that none of the following applies to you. + Under Aquamacs (which is a MacOS X native port of Emacs), it has been reported that one gets `Lisp nesting exceeds max-lisp-eval-depth` messages while interactively requesting the documentation for Lisp functions (we do not know why). If you have this problem, edit file :file:`ppppconfig.py`, locate the line defining :code:`DEFADVICE_OK`, make sure it gets the string ``'nil'`` as a value, instead of the string ``'t'``, then save the edited file before proceeding further. This should work around the problem. The price to pay is that you will not get the Python docstring for modules imported through Pymacs. Check if Pymacs would work -------------------------- To know, before installing Pymacs, if it would work on your system, try the validation suite by running ``make check``. The suite is fairly elementary, but nevertheless, it is able to detect some common show stoppers. To check a particular Emacs and Python combination, use ``make check EMACS=some_Emacs PYTHON=some_Python``. If ``PYTHON`` is left unset or empty, then the command for starting the Pymacs helper is ``python``. Otherwise, it may be set to give the full path of the Python executable if it exists at some location outside the program search path. It may also be given when the interpreter name is different, for exemple when the Python version is part of the program name. If ``EMACS`` is left unset or empty, then the command for starting the Emacs editor is ``emacs``. For normal Pymacs usage, Emacs is launched by the user long before Pymacs is itself started, and consequently, there is absolutely no need to tell Pymacs which Emacs is needed. For the validation suite however, it may be set to give the full path of the executable if the Emacs program exists at some location outside the program search path. It may also be given when the editor name is different, for example when the Emacs version is part of the program name, or when this is a different editor. For example, ``make check EMACS=xemacs`` runs the validation suite using ``xemacs`` for an editor. The remaining of this section may be safely be skipped for mere Pymacs installation. I did not base the validation suite on Junit (the Python unit testing framework is a re-implementation of it), but on Codespeak's pylib :file:`py.test`, which is much simpler, and still very powerful. The :code:`pylib` project is driven by Holge Kregel, but attracted some Python brains, like Armin Rigo (known for Psyco, among other things -- I think his :code:`lsprof` has also been added to Python 2.5 under the name :code:`cProfile`). This gang addresses overdone/heavy methods in Python, and do them better. Even :file:`py.test` is a bit more complex that I would want, and has (or at least had) flaws on the Unicode side, so I rewrote my own, as a simple single file. I merely translated it from French to English, to make it more distributable within Pymacs. I initially tried using Emacs stdin and stdout for communicating expressions to evaluate and getting back results, from within the validation suite. This did not prove useful so, so after some fight, I reluctantly put this avenue aside. Currently, the suite writes problems in files, for Emacs to read, and Emacs writes replies in files, for the suite to check. Busy waiting (with small sleep added in the loops) is used on both sides. This is all too heavy, and it slows down the suite. Hopefully, the suite is not run often, this is not a real problem. Install the Pymacs proper ------------------------- Pymacs is lean. Putting the documentation and administrative files aside, there is one Python file and one Emacs Lisp file to it, to be installed in turn. Always start with the Python file. + For the Python part From the top-level of the Pymacs distribution, execute ``make install``. If you do not have a Make program (Microsoft Windows?) read the ``Makefile`` file and emulate what ``make install`` does, maybe something like this:: python pppp -C ppppconfig.py pppp.rst.in pymacs.el.in \ pymacs.rst.in Pymacs.py.in contrib tests python setup.py install Without ``make install``, you might also have to combine the two first lines above into a single longer one, without the backslash. If the Python interpreter has a non-standard name or location, rather do ``make install PYTHON=Some_Python`` (see the previous section for a discussion). First, the script copies a few source files while configuring them: it presets the version string and the name of the Python interpreter, it also adapts the Python source code which might differ, for example, between Python 2 and Python 3. Second, it installs the Python file through the Python standard Distutils tool. To get an option reminder, do ``python setup.py install --help``. Consult the Distutils documentation if you need more information about this. That's normally all to it. To check that :file:`Pymacs.py` is properly installed, start an interactive Python session and type ``from Pymacs import lisp``: you should not receive any error. A special difficulty arises when the particular Python you use does not have Distutils already installed. In such a case, ``make install`` prints a warning, leaving to you the task of figuring out where the ``Pymacs/`` directory is best copied, and making that copy. + For the Emacs part This is usually done by hand now. First select some directory along the list kept in your Emacs :code:`load-path`, for which you have write access, and copy file :file:`pymacs.el` in that directory. If you want speed, you should ideally byte-compile this file. To do so, go to that directory, launch Emacs, then give the command ``M-x byte-compile-file RET pymacs.el RET``. If for some reason you intend to such commands often, you could create a little script to do so. Here is an example of such a script, assuming here that you use Emacs and want to install in directory :file:`~/share/emacs/lisp/`:: #!/bin/bash cp pymacs.el ~/share/emacs/lisp/ emacs -batch -eval '(byte-compile-file "~/share/emacs/lisp/pymacs.el")' You should be done now. To check that :file:`pymacs.el` is properly installed, return to your usual directories, start Emacs and give it the command ``M-x load-library RET pymacs RET``: you should not receive any error. Some features from previous Pymacs releases have been dropped: + Environment variable ``PYMACS_EMACS`` is gone, and environment variable ``PYMACS_PYTHON`` is usually not needed. + There used to be a script for installing the Emacs Lisp file. As it was difficult to get it right in all circumstances; the script grew an interactive mode and lot of options. This is just not worth the complexity, so this script is now gone. + Examples were all installed automatically, but at least for some of them, this was more pollution than help. You may browse the contents of the :file:`contrib/` directory to learn about available examples. Prepare your :file:`.emacs` file -------------------------------- The :file:`.emacs` file is not given in the distribution, you likely have one already in your home directory. You need to add these lines:: (autoload 'pymacs-apply "pymacs") (autoload 'pymacs-call "pymacs") (autoload 'pymacs-eval "pymacs" nil t) (autoload 'pymacs-exec "pymacs" nil t) (autoload 'pymacs-load "pymacs" nil t) (autoload 'pymacs-autoload "pymacs") ;;(eval-after-load "pymacs" ;; '(add-to-list 'pymacs-load-path YOUR-PYMACS-DIRECTORY")) If you plan to use a special directory to hold your own Pymacs code in Python, which should be searched prior to the usual Python import search path, then uncomment the last two lines (by removing the semi-colons) and replace :var:`YOUR-PYMACS-DIRECTORY` by the name of your special directory. If the file :file:`~/.emacs` does not exist, merely create it with the above lines. You are now all set to use Pymacs. To check this, start a fresh Emacs session, and type ``M-x pymacs-eval RET``. Emacs should prompt you for a Python expression. Try ``repr(2L**111) RET`` (rather use ``repr(2**111) RET`` if you are using Python 3). The mini buffer should display `"2596148429267413814265248164610048L"` (yet there is no ``L`` suffix in Python 3). Let's do a second test. Whether in the same Emacs session or not, ``M-x pymacs-load RET`` should prompt you for a Python module name. Reply ``os RET RET`` (the second ``RET`` is for accepting the default prefix). This should have the effect of importing the Python :code:`os` module within Emacs. Typing ``M-: (os-getcwd) RET`` should echo the current directory in the message buffer, as returned by the :code:`os.getcwd` Python function. Porting and caveats ------------------- Pymacs has been initially developed on Linux, Python 1.5.2, and Emacs 20, and is currently developed using Python 2.6, Python 3.1, Emacs 23.1 and XEmacs 21.4. It is expected to work out of the box on many flavours of Unix, MS Windows and Mac OSX, and also on many version of Python, Emacs and XEmacs. From Pymacs 0.23 and upwards, Python 2.2 or better is likely needed, and for the Pymacs proper, I rely on testers or users for portability issues. However, the validation suite itself requires Python 2.6 or better, someone might choose to contribute the back porting. Python 3.1 support has been added for Pymacs 0.24. Pymacs uses Emacs weak hash tables. It can run without them, but then, complex Python objects transmitted to Emacs will tie Python memory forever. It should not be a practical problem in most simple cases. Some later versions of Emacs 20 silently create ordinary tables when asked for weak hash tables. Older Emacses do not have hash tables. In earlier versions, Pymacs was installing a :file:`Pymacs` Python package holding a single :file:`pymacs.py` file (besides the mandatory :file:`__init__.py`). This is now replaced by a single :file:`Pymacs.py` file, and because of the capitalisation, the API did not need to change. Emacs Lisp structures and Python objects ======================================== Conversions ----------- Whenever Emacs Lisp calls Python functions giving them arguments, these arguments are Emacs Lisp structures that should be converted into Python objects in some way. Conversely, whenever Python calls Emacs Lisp functions, the arguments are Python objects that should be received as Emacs Lisp structures. We need some conventions for doing such conversions. Conversions generally transmit mutable Emacs Lisp structures as mutable objects on the Python side, in such a way that transforming the object in Python will effectively transform the structure on the Emacs Lisp side (strings are handled a bit specially however, see below). The other way around, Python objects transmitted to Emacs Lisp often loose their mutability, so transforming the Emacs Lisp structure is not reflected on the Python side. Pymacs sticks to standard Emacs Lisp, it explicitly avoids various Emacs Lisp extensions. One goal for many Pymacs users is taking some distance from Emacs Lisp, so Pymacs is not overly pushing users deeper into it. Simple objects -------------- Emacs Lisp :code:`nil` and the equivalent Emacs Lisp ``()`` yield Python :code:`None`. Python :code:`None`, Python :code:`False` and the Python empty list ``[]`` are returned as :code:`nil` in Emacs Lisp. Notice the assymetry, in that three different Python objects are mapped into a single Emacs Lisp object. So, neither :code:`False` nor ``[]`` are likely produced by automatic conversions from Emacs Lisp to Python. Emacs Lisp :code:`t` yields Python :code:`True`. Python :code:`True` is returned as :code:`t` in Emacs Lisp. Emacs Lisp numbers, either integer or floating, are converted in equivalent Python numbers. Emacs Lisp characters are really numbers and yield Python numbers. In the other direction, Python numbers are converted into Emacs Lisp numbers, with the exception of long Python integers and complex numbers. Emacs Lisp strings are usually converted into equivalent Python strings. As Python strings do not have text properties, these are not reflected. This may be changed by setting the :code:`pymacs-mutable-strings` option: if this variable is not :code:`nil`, Emacs Lisp strings are then transmitted opaquely. Python strings are always converted into Emacs Lisp strings. Python releases before version 3 make a distinction between Unicode and narrow strings: Unicode strings are then produced on the Python side for Emacs Lisp multi-byte strings, but only when they do not fit in ASCII, otherwise Python narrow strings are produced. Conversely, Emacs Lisp multi-byte strings are produced for Python strings, but only when they do not fit ASCII, otherwise Emacs Lisp uni-byte strings are produced. Currently, Pymacs behaviour is undefined for users wandering outside the limits of Emacs' :code:`utf-8` coding system. Emacs Lisp symbols yield ``lisp[STRING]`` notations on the Python side, where :var:`STRING` names the symbol. In the other direction, Python ``lisp[STRING]`` corresponds to an Emacs Lisp symbol printed with that :var:`STRING` which, of course, should then be a valid Emacs Lisp symbol name. As a convenience, ``lisp.SYMBOL`` on the Python side yields an Emacs Lisp symbol with underscores replaced with hyphens; this convention is welcome, as Emacs Lisp programmers commonly prefer using dashes, where Python programmers use underlines. Of course, this ``lisp.SYMBOL`` notation is only usable when the :var:`SYMBOL` is a valid Python identifier, while not being a Python keyword. Sequences --------- The case of strings has been discussed in the previous section. Proper Emacs Lisp lists, those for which the :code:`cdr` of last cell is :code:`nil`, are normally transmitted opaquely to Python. If :code:`pymacs-forget-mutability` is set, or if Python later asks for these to be expanded, proper Emacs Lisp lists get converted into Python lists, if we except the empty list, which is always converted as Python :code:`None`. In the other direction, Python lists are always converted into proper Emacs Lisp lists. Emacs Lisp vectors are normally transmitted opaquely to Python. However, if :code:`pymacs-forget-mutability` is set, or if Python later asks for these to be expanded, Emacs Lisp vectors get converted into Python tuples. In the other direction, Python tuples are always converted into Emacs Lisp vectors. Remember the rule: `Round parentheses correspond to square brackets!`. It works for lists, vectors, tuples, seen from either Emacs Lisp or Python. The above choices were debatable. Since Emacs Lisp proper lists and Python lists are the bread-and-butter of algorithms modifying structures, at least in my experience, I guess they are more naturally mapped into one another, this spares many casts in practice. While in Python, the most usual idiom for growing lists is appending to their end, the most usual idiom in Emacs Lisp to grow a list is by cons'ing new items at its beginning:: (setq accumulator (cons 'new-item accumulator)) or more simply:: (push 'new-item accumulator) So, in case speed is especially important and many modifications happen in a row on the same side, while order of elements ought to be preserved, some ``(nreverse ...)`` on the Emacs Lisp side or ``.reverse()`` on the Python side might be needed. Surely, proper lists in Emacs Lisp and lists in Python are the normal structure for which length is easily modified. We cannot so easily change the size of a vector, the same as it is a bit more of a stunt to *modify* a tuple. The shape of these objects is fixed. Mapping vectors to tuples, which is admittedly strange, will only be done if the Python side requests an expanded copy, otherwise an opaque Emacs Lisp object is seen in Python. In the other direction, whenever an Emacs Lisp vector is needed, one has to write ``tuple(python_list)`` while transmitting the object. Such transmissions are most probably to be unusual, as people are not going to blindly transmit whole big structures back and forth between Emacs and Python, they would rather do it once in a while only, and do only local modifications afterwards. The infrequent casting to :code:`tuple` for getting an Emacs Lisp vector seems to suggest that we did a reasonable compromise. In Python, both tuples and lists have O(1) access, so there is no real speed consideration there. Emacs Lisp is different: vectors have O(1) access while lists have O(N) access. The rigidity of Emacs Lisp vectors is such that people do not resort to vectors unless there is a speed issue, so in real Emacs Lisp practice, vectors are used rather parsimoniously. So much, in fact, that Emacs Lisp vectors are overloaded for what they are not meant: for example, very small vectors are used to represent X events in key-maps, programmers only want to test vectors for their type, or users just like bracketed syntax. The speed of access is hardly an issue then. Opaque objects -------------- Emacs Lisp handles ,,,,,,,,,,,,,,,,,, When a Python function is called from Emacs Lisp, the function arguments have already been converted to Python types from Emacs Lisp types and the function result is going to be converted back to Emacs Lisp. Several Emacs Lisp objects do not have Python equivalents, like for Emacs windows, buffers, markers, overlays, etc. It is nevertheless useful to pass them to Python functions, hoping that these Python functions will *operate* on these Emacs Lisp objects. Of course, the Python side may not itself modify such objects, it has to call for Emacs services to do so. Emacs Lisp handles are a mean to ease this communication. Whenever an Emacs Lisp object may not be converted to a Python object, an Emacs Lisp handle is created and used instead. Whenever that Emacs Lisp handle is returned into Emacs Lisp from a Python function, or is used as an argument to an Emacs Lisp function from Python, the original Emacs Lisp object behind the Emacs Lisp handle is automatically retrieved. Emacs Lisp handles are either instances of the internal :code:`Lisp` class, or of one of its subclasses. If :var:`OBJECT` is an Emacs Lisp handle, and if the underlying Emacs Lisp object is an Emacs Lisp sequence, then whenever ``OBJECT[INDEX]``, ``OBJECT[INDEX] = VALUE`` and ``len(OBJECT)`` are meaningful, these may be used to fetch or alter an element of the sequence directly in Emacs Lisp space. Also, if :var:`OBJECT` corresponds to an Emacs Lisp function, ``OBJECT(ARGUMENTS)`` may be used to apply the Emacs Lisp function over the given arguments. Since arguments have been evaluated the Python way on the Python side, it would be conceptual overkill evaluating them again the Emacs Lisp way on the Emacs Lisp side, so Pymacs manage to quote arguments for defeating Emacs Lisp evaluation. The same logic applies the other way around. Emacs Lisp handles have a ``value()`` method, which merely returns self. They also have a ``copy()`` method, which tries to *open the box* if possible. Emacs Lisp proper lists are turned into Python lists, Emacs Lisp vectors are turned into Python tuples. Then, modifying the structure of the copy on the Python side has no effect on the Emacs Lisp side. For Emacs Lisp handles, ``str()`` returns an Emacs Lisp representation of the handle which should be :code:`eq` to the original object if read back and evaluated in Emacs Lisp. ``repr()`` returns a Python representation of the expanded Emacs Lisp object. If that Emacs Lisp object has an Emacs Lisp representation which Emacs Lisp could read back, then ``repr()`` value is such that it could be read back and evaluated in Python as well, this would result in another object which is :code:`equal` to the original, but not necessarily :code:`eq`. Python handles ,,,,,,,,,,,,,, The same as Emacs Lisp handles are useful for handling Emacs Lisp objects on the Python side, Python handles are useful for handling Python objects on the Emacs Lisp side. Many Python objects do not have direct Emacs Lisp equivalents, including long integers, complex numbers, modules, classes, instances and surely a lot of others. When such are being transmitted to the Emacs Lisp side, Pymacs use Python handles. These are automatically recovered into the original Python objects whenever transmitted back to Python, either as arguments to a Python function, as the Python function itself, or as the return value of an Emacs Lisp function called from Python. The objects represented by these Python handles may be inspected or modified using the basic library of Python functions. For example, in:: (pymacs-exec "import re") (setq matcher (pymacs-eval "re.compile('PATTERN').match")) (pymacs-call matcher ARGUMENT) the :code:`setq` line above could be decomposed into:: (setq compiled (pymacs-eval "re.compile('PATTERN')") matcher (pymacs-call "getattr" compiled "match")) This example shows that one may use :code:`pymacs-call` with :code:`getattr` as the function, to get a wanted attribute for a Python object. Usage on the Emacs Lisp side ============================ Special Emacs Lisp functions ---------------------------- Pymacs is mainly launched and used through a few special functions, among all those added by Pymacs for Emacs Lisp. These few imported functions are listed and detailed in the following subsections. They really are the preferred way to call Python services with Pymacs. Even then, we do not expect that :code:`pymacs-exec`, :code:`pymacs-eval`, :code:`pymacs-call` or :code:`pymacs-apply` will be much used, if ever, in most Pymacs applications. In practice, the Emacs Lisp side of a Pymacs application might call either :code:`pymacs-autoload` or :code:`pymacs-load` a few times for linking into the Python modules, with the indirect effect of defining trampoline functions for these modules on the Emacs Lisp side, which can later be called like usual Emacs Lisp functions. :code:`pymacs-exec` ,,,,,,,,,,,,,,,,,,, Function ``(pymacs-exec TEXT)`` gets :var:`TEXT` executed as a Python statement, and its value is always :code:`nil`. So, this function may only be useful because of its possible side effects on the Python side. This function may also be called interactively:: M-x pymacs-exec RET TEXT RET :code:`pymacs-eval` ,,,,,,,,,,,,,,,,,,, Function ``(pymacs-eval TEXT)`` gets :var:`TEXT` evaluated as a Python expression, and returns the value of that expression converted back to Emacs Lisp. This function may also be called interactively:: M-x pymacs-eval RET TEXT RET :code:`pymacs-call` ,,,,,,,,,,,,,,,,,,, Function ``(pymacs-call FUNCTION ARGUMENT...)`` will get Python to apply the given :var:`FUNCTION` over zero or more :var:`ARGUMENT`. :var:`FUNCTION` is either a string holding Python source code for a function (like a mere name, or even an expression), or else, a Python handle previously received from Python, and hopefully holding a callable Python object. Each :var:`ARGUMENT` gets separately converted to Python before the function is called. :code:`pymacs-call` returns the resulting value of the function call, converted back to Emacs Lisp. :code:`pymacs-apply` ,,,,,,,,,,,,,,,,,,,, Function ``(pymacs-apply FUNCTION ARGUMENTS)`` will get Python to apply the given :var:`FUNCTION` over the given :var:`ARGUMENTS`. :var:`ARGUMENTS` is a list containing all arguments, or :code:`nil` if there is none. Besides arguments being bundled together instead of given separately, the function acts pretty much like :code:`pymacs-call`. :code:`pymacs-load` ,,,,,,,,,,,,,,,,,,, Function ``(pymacs-load MODULE PREFIX)`` imports the Python :var:`MODULE` into Emacs Lisp space. :var:`MODULE` is the name of the file containing the module, without any :file:`.py` or :file:`.pyc` extension. If the directory part is omitted in :var:`MODULE`, the module will be looked into the current Python search path. Dot notation may be used when the module is part of a package. Each top-level function in the module produces a trampoline function in Emacs Lisp having the same name, except that underlines in Python names are turned into dashes in Emacs Lisp, and that :var:`PREFIX` is uniformly added before the Emacs Lisp name (as a way to avoid name clashes). :var:`PREFIX` may be omitted, in which case it defaults to base name of :var:`MODULE` with underlines turned into dashes, and followed by a dash. Note that :code:`pymacs-load` has the effect of declaring the module variables and methods on the Emacs Lisp side, but it does *not* declare anything on the Python side. Of course, Python imports the module before making it available for Emacs, but there is no Pymacs ready variable on the Python side holding that module. If you need to import :var:`MODULE` in a variable on the Python side, the proper incantation is ``(pymacs-exec "import MODULE")``. And of course, this latter statement does not declare anything on the Emacs Lisp side. Whenever :code:`pymacs_load_hook` is defined in the loaded Python module, :code:`pymacs-load` calls it without arguments, but before creating the Emacs view for that module. So, the :code:`pymacs_load_hook` function may create new definitions or even add :code:`interaction` attributes to functions. The return value of a successful :code:`pymacs-load` is the module object. An optional third argument, :var:`noerror`, when given and not :code:`nil`, will have :code:`pymacs-load` to return :code:`nil` instead of raising an error, if the Python module could not be found. When later calling one of these trampoline functions, all provided arguments are converted to Python and transmitted, and the function return value is later converted back to Emacs Lisp. It is left to the Python side to check for argument consistency. However, for an interactive function, the interaction specification drives some checking on the Emacs Lisp side. Currently, there is no provision for collecting keyword arguments in Emacs Lisp. This function may also be called interactively:: M-x pymacs-load RET MODULE RET PREFIX RET If you find yourself using :code:`pymacs-call` a lot for builtin Python functions, you might rather elect to import all Python builtin functions and definitions directly into Emacs Lisp space, and call them directly afterwards. Here is a recipe (use the first line for Python 2, or the second line for Python 3):: M-x pymacs-load RET __builtin__ RET py- RET M-x pymacs-load RET builtins RET py- RET After such a command, calling the function ``py-getattr``, say, with an opaque Python object and with a string naming an attribute, returns the value of that attribute for that object. :code:`pymacs-autoload` ,,,,,,,,,,,,,,,,,,,,,,, Function ``(pymacs-autoload FUNCTION MODULE PREFIX DOCSTRING INTERACTIVE)`` is meant to mimic the functionality of the standard Emacs :code:`autoload` function. It declares :var:`FUNCTION` to be autoloaded from the specified Python :var:`MODULE`. The :code:`pymacs-load` for this module is delayed until :var:`FUNCTION` is actually called. Of course, if there are many such functions declared as autoloading the module, calling any of them will then load the module and resolve the autoloading for all of them at once. For the meaning of the optional :var:`PREFIX` argument, see the documentation for the :code:`pymacs-load` function above. Before the function gets loaded for real, Emacs may still provide a documentation for it, which the user gives through the contents of the optional :var:`DOCSTRING`. Emacs also needs to know if the function may be called interactively and, when this is the case, the arguments it may accept. If the :var:`INTERACTIVE` argument is not provided, or when it is nil, the function is not known to be interactive. A value of :code:`t` for :var:`INTERACTIVE` means that the function is interactive, but has no arguments. Otherwise, :var:`INTERACTIVE` receives a description of the interaction to interactively get the function arguments. See the Emacs documentation for function :code:`autoload` and :code:`interactive` for more information. If, at the moment of the :code:`pymacs-autoload` call, :var:`FUNCTION` is already related to a loaded Python function, the autoloading declaration is ignored. Here are examples of usage for the :code:`pymacs-autoload` function:: (pymacs-autoload 'os-getenv "os" nil nil "sEnv name: ") (pymacs-autoload 'posix-getenv "os" "posix-" nil '(list (read-string "Env name: "))) The second example could be written more simply as in the first example. Moreover, both examples of an :var:INTERACTIVE argument are merely given here for illustration, as the real :code:`os-getenv` function is *not* interactive. Special Emacs Lisp variables ---------------------------- Users could alter the inner working of Pymacs through a few variables, these are all documented here. Except for :code:`pymacs-python-command` and :code:`pymacs-load-path`, which should be set before calling any Pymacs function, the value of these variables can be changed at any time. :code:`pymacs-python-command` ,,,,,,,,,,,,,,,,,,,,,,,,,,,,, This variable is initialized with the Python executable that was used at installation time. It tells Emacs about the Python interpreter to launch far starting the Pymacs helper. The value of this variable may be overridden by setting the ``PYMACS_PYTHON`` environment variable, yet in practice, for newer versions of Pymacs, this is rarely needed. While the Python part of Pymacs is pre-processed and yields different sources for Python 2 and Python 3 (among other possibilities), the Emacs part of Pymacs is mostly configured at run time for various Emacs versions, so the same Emacs source is likely to work unaltered, would it be for different versions of Emacs and for different versions of Python. So it makes sense, at least in some special circumstances, giving the capability of selecting a specific Python interpreter by programmatical means within Emacs. :code:`pymacs-load-path` ,,,,,,,,,,,,,,,,,,,,,,,, Users might want to use special directories for holding their Python modules, when these modules are meant to be used from Emacs. Best is to preset :code:`pymacs-load-path`, :code:`nil` by default, to a list of these directory names. (Tilde expansions and such occur automatically.) Here is how it works. The first time Pymacs is needed from Emacs, a Pymacs helper is automatically started as an Emacs subprocess, and given as arguments all strings in the :code:`pymacs-load-path` list. These arguments are added at the beginning of :code:`sys.path`, or moved at the beginning if they were already on :code:`sys.path`. So in practice, nothing is removed from :code:`sys.path`. :code:`pymacs-trace-transit` ,,,,,,,,,,,,,,,,,,,,,,,,,,,, The :code:`*Pymacs*` buffer, within Emacs, holds a trace of transactions between Emacs and Python. When :code:`pymacs-trace-transit` is :code:`nil`, the buffer only holds the last bi-directional transaction (a request and a reply). In this case, it gets erased before each and every transaction. If that variable is :code:`t`, all transactions are kept. This could be useful for debugging, but the drawback is that this buffer could grow big over time, to the point of diminishing Emacs performance. As a compromise, that variable may also be a cons cell of integers ``(KEEP . LIMIT)``, in which case the buffer is reduced to approximately :var:`KEEP` bytes whenever its size exceeds :var:`LIMIT` bytes, by deleting an integral number of lines from its beginning. The default setting for :code:`pymacs-trace-transit` is ``(5000 . 30000)``. :code:`pymacs-forget-mutability` ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, The default behaviour of Pymacs is to transmit Emacs Lisp objects to Python in such a way that they are fully modifiable from the Python side, would it mean triggering Emacs Lisp functions to act on them. When :code:`pymacs-forget-mutability` is not :code:`nil`, the behaviour is changed, and the flexibility is lost. Pymacs then tries to expand proper lists and vectors as full copies when transmitting them on the Python side. This variable, seen as a user setting, is best left to :code:`nil`. It may be temporarily overridden within some functions, when deemed useful. There is no corresponding variable from objects transmitted to Emacs from Python. Pymacs automatically expands what gets transmitted. Mutability is preserved only as a side-effect of not having a natural Emacs Lisp representation for the Python object. This asymmetry is on purpose, yet debatable. Maybe Pymacs could have a variable telling that mutability is important for Python objects? That would give Pymacs users the capability of restoring the symmetry somewhat, yet so far, in our experience, this has never been needed. :code:`pymacs-mutable-strings` ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, Strictly speaking, Emacs Lisp strings are mutable. Yet, it does not come naturally to a Python programmer to modify a string *in-place*, as Python strings are never mutable. When :code:`pymacs-mutable-strings` is :code:`nil`, which is the default setting, Emacs Lisp strings are transmitted to Python as Python strings, and so, loose their mutability. Moreover, text properties are not reflected on the Python side. But if that variable is not :code:`nil`, Emacs Lisp strings are rather passed as Emacs Lisp handles. This variable is ignored whenever :code:`pymacs-forget-mutability` is set. Timeout variables ,,,,,,,,,,,,,,,,, Emacs needs to protect itself a bit, in case the Pymacs service program, which handles the Python side of requests, would not start correctly, or maybe later die unexpectedly. So, whenever Emacs reads data coming from that program, it sets a time limit, and take some action whenever that time limit expires. All times are expressed in seconds. The :code:`pymacs-timeout-at-start` variable defaults to 30 seconds, this time should only be increased if a given machine is so heavily loaded that the Pymacs service program has not enough of 30 seconds to start, in which case Pymacs refuses to work, with an appropriate message in the mini buffer. The two remaining timeout variables almost never need to be changed in practice. When Emacs is expecting a reply from Python, it might repeatedly check the status of the Pymacs service program when that reply is not received fast enough, just to make sure that this program did not die. The :code:`pymacs-timeout-at-reply` variable, which defaults to 5, says how many seconds to wait without checking, while expecting the first line of a reply. The :code:`pymacs-timeout-at-line` variable, which defaults to 2, says how many seconds to wait without checking, while expecting a line of the reply after the first. :code:`pymacs-auto-restart` ,,,,,,,,,,,,,,,,,,,,,,,,,,, The Pymacs helper process is started as soon as it is needed, and gets associated with the :code:`*Pymacs*` buffer. When that buffer is killed, as it occurs automatically whenever the Emacs session is ending, the Pymacs helper process is killed as well. Any other disappearance of the helper is unexpected, and might be the consequence of some error in the Python side of the user application (or a Pymacs bug, maybe!). When the Pymacs helper dies, all useful Python objects it might contain also die with it. So, after an unexpected death, there might now exist dangling references in Emacs Lisp space towards vanished Python objects, and using these references may be fatal to the application. When the Pymacs helper dies, the safest thing to do is stopping all Pymacs functionality and even exiting Emacs. On the other hand, it is not always practical having to restart everything in such cases: the user knows best, and is the one who ultimately decides. The Pymacs helper death is detected at the time a new Pymacs request gets initiated from the Emacs side. Pymacs could not do much without a Pymacs helper, so it has either to restart a new Pymacs helper, or abort the Pymacs request. The variable :code:`pymacs-auto-restart` controls how this is done. The possible values are: + ``nil`` — the Pymacs request is unconditionally aborted, + ``t`` — a new Pymacs helper is silently launched, and the previous helper death might well go unnoticed, + ``'ask`` — the user interactively decides whether to restart the Pymacs helper or not. This is the default value. :code:`pymacs-dreadful-zombies` ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, When a Pymacs helper gets restarted in a given Emacs session, brand new Python objects may be created within that new helper. There is not enough information kept on the Emacs Lisp side for the new Pymacs helper to recreate the useful Python objects which disappeared. However, there is enough machinery to recover all their slot numbers (all references to opaque Python objects from Emacs Lisp space are transmitted in form of object slot numbers). The new Pymacs helper is given the list of all previous slot numbers still referenced from the Emacs side, and is then careful at never allocating a new Python object using an old slot number, as this might possibly create fatal confusion. All the previous slots are initialized with so-called *zombies* on the Python side. If Emacs later calls a vanished Python object, this merely awakes its zombie, which will then make some noise, then fall asleep again. The noise has the form of a diagnostic within the ``*Messages*`` buffer, sometimes visible in the mini-buffer too, at least when the mini-buffer is not simultaneously used for some other purpose. Zombies get more dreadful if :code:`pymacs-dreadful-zombies` is set to a non-:code:`nil` value. In this case, calling a vanished Python object raises an error that will eventually interrupt the current computation. Such a behaviour might be useful for debugging purposes, or for making sure that no call to a vanished Python object goes unnoticed. In previous Pymacs releases, zombies were always dreadful, under the assumption that calling a vanished object is a real error. However, it could cause irritation in some circumstances, like when associated with frequently triggered Emacs Lisp hook functions. That's why that, by default, zombies have been finally turned into more innocuous beings! Usage on the Python side ======================== Python setup ------------ For Python modules meant to be used from Emacs and which receive nothing but Emacs :code:`nil`, numbers or strings, or return nothing but Python :code:`None`, numbers or strings, then Pymacs requires little or no setup. Otherwise, use ``from Pymacs import lisp`` at the start of your module. If you need more Pymacs features, like the :code:`Let` class, then write ``from Pymacs import lisp, Let``. The Pymacs helper runs Python code to serve the Emacs side, and it is blocked waiting until Emacs sends a request. Until the Pymacs helper returns a reply, Emacs is blocked in turn, yet fully listening to serve eventual Python sub-requests, etc. So, either Emacs or the Pymacs helper is active at a given instant, but never both at once. Unless Emacs has sent a request to the Pymacs helper and is expecting a reply, it is just not listening to receive Python requests. So, any other Python thread may not asynchronously use Pymacs to get Emacs services. The design of the Python application should be such that the communication is always be channelled from the main Python thread. When Pymacs starts, all process signals are inhibited on the Python side. Yet, :code:`SIGINT` gets re-enabled while running user functions. If the user elects to reactivate some other signal in her Python code, she should do so as to not damage or severe the communication protocol. Emacs Lisp symbols ------------------ :code:`lisp` is a special object which has useful built-in magic. Its attributes do nothing but represent Emacs Lisp symbols, created on the fly as needed (symbols also have their built-in magic). As special cases, ``lisp.nil`` or ``lisp["nil"]`` are the same as :code:`None`, and ``lisp.t`` or ``lisp["t"]`` are the same as :code:`True`. Otherwise, both ``lisp.SYMBOL`` and ``lisp[STRING]`` yield objects of the internal :code:`Symbol` type. These are genuine Python objects, that could be referred to by simple Python variables. One may write ``quote = lisp.quote``, for example, and use ``quote`` afterwards to mean that Emacs Lisp symbol. If a Python function received an Emacs Lisp symbol as an argument, it can check with ``==`` if that argument is ``lisp.never`` or ``lisp.ask``, say. A Python function may well choose to return some symbol, like ``lisp.always``. In Python, writing ``lisp.SYMBOL = VALUE`` or ``lisp[STRING] = VALUE`` does assign :var:`VALUE` to the corresponding symbol in Emacs Lisp space. Beware that in such cases, the ``lisp.`` prefix may not be spared. After ``result = lisp.result``, one cannot hope that a later ``result = 3`` will have any effect in the Emacs Lisp space: this would merely change the Python variable ``result``, which was a reference to a :code:`Symbol` instance, so it is now a reference to the number 3. The :code:`Symbol` class has ``value()`` and ``copy()`` methods. One can use either ``lisp.SYMBOL.value()`` or ``lisp.SYMBOL.copy()`` to access the Emacs Lisp value of a symbol, after conversion to some Python object, of course. However, if ``value()`` would have given an Emacs Lisp handle, ``lisp.SYMBOL.copy()`` has the effect of ``lisp.SYMBOL.value().copy()``, that is, it returns the value of the symbol as opened as possible. A symbol may also be used as if it was a Python function, in which case it really names an Emacs Lisp function that should be applied over the following function arguments. The result of the Emacs Lisp function becomes the value of the call, with all due conversions of course. Dynamic bindings ---------------- As Emacs Lisp uses dynamic bindings, it is common that Emacs Lisp programs use :code:`let` for temporarily setting new values for some Emacs Lisp variables having global scope. These variables recover their previous value automatically when the :code:`let` gets completed, even if an error occurs which interrupts the normal flow of execution. Pymacs has a :code:`Let` class to represent such temporary settings. Suppose for example that you want to recover the value of ``lisp.mark()`` when the transient mark mode is active on the Emacs Lisp side. One could surely use ``lisp.mark(True)`` to *force* reading the mark in such cases, but for the sake of illustration, let's ignore that, and temporarily deactivate transient mark mode instead. This could be done this way:: try: let = Let() let.push(transient_mark_mode=None) ... USER CODE ... finally: let.pop() ``let.push()`` accepts any number of keywords arguments. Each keyword name is interpreted as an Emacs Lisp symbol written the Pymacs way, with underlines. The value of that Emacs Lisp symbol is saved on the Python side, and the value of the keyword becomes the new temporary value for this Emacs Lisp symbol. A later ``let.pop()`` restores the previous value for all symbols which were saved together at the time of the corresponding ``let.push()``. There may be more than one ``let.push()`` call for a single :code:`Let` instance, they stack within that instance. Each ``let.pop()`` will undo one and only one ``let.push()`` from the stack, in the reverse order or the pushes. A single call to ``let.pops()`` automatically does all pending ``let.pop()`` at once, in the correct reverse order. When the :code:`Let` instance disappears, either because the programmer does ``del let`` or ``let = None``, or just because the Python :code:`let` variable goes out of scope, ``let.pops()`` gets executed under the scene, so the :code:`try`/:code:`finally` statement may be omitted in practice. For this omission to work flawlessly, the programmer should be careful at not keeping extra references to the :code:`Let` instance. The constructor call ``let = Let()`` also has an implied initial ``.push()`` over all given arguments, given there is any, so the explicit ``let.push()`` may be omitted as well. In practice, this sums up and the above code could be reduced to a mere:: let = Let(transient_mark_mode=None) ... USER CODE ... Be careful at assigning the result of the constructor to some Python variable. Otherwise, the instance might disappear immediately after having been created, restoring the Emacs Lisp variable much too soon. Any variable to be bound with :code:`Let` should have been bound in advance on the Emacs Lisp side. This restriction usually does no kind of harm. Yet, it will likely be lifted in some later version of Pymacs. The :code:`Let` class has other methods meant for some macros which are common in Emacs Lisp programming, in the spirit of :code:`let` bindings. These method names look like ``push_*`` or ``pop_*``, where Emacs Lisp macros are ``save-*``. One has to use the matching ``pop_*`` for undoing the effect of a given ``push_*`` rather than a mere ``.pop()``: the Python code is clearer, this also ensures that things are undone in the proper order. The same :code:`Let` instance may use many ``push_*`` methods, their effects nest. ``push_excursion()`` and ``pop_excursion()`` save and restore the current buffer, point and mark. ``push_match_data()`` and ``pop_match_data()`` save and restore the state of the last regular expression match. ``push_restriction()`` and ``pop_restriction()`` save and restore the current narrowing limits. ``push_selected_window()`` and ``pop_selected_window()`` save and restore the fact that a window holds the cursor. ``push_window_excursion()`` and ``pop_window_excursion()`` save and restore the current window configuration in the Emacs display. As a convenience, ``let.push()`` and all other ``push_*`` methods return the :code:`Let` instance. This helps chaining various ``push_*`` right after the instance generation. For example, one may write:: let = Let().push_excursion() if True: ... USER CODE ... del let The ``if True:`` (use ``if 1:`` with older Python releases, some people might prefer writing ``if let:`` anyway), has the only goal of indenting :var:`USER CODE`, so the scope of the :code:`let` variable is made very explicit. This is purely stylistic, and not at all necessary. The last ``del let`` might be omitted in a few circumstances, for example if the excursion lasts until the end of the Python function. Raw Emacs Lisp expressions -------------------------- Pymacs offers a device for evaluating a raw Emacs Lisp expression, or a sequence of such, expressed as a string. One merely uses :code:`lisp` as a function, like this:: lisp(''' ... POSSIBLY-LONG-SEQUENCE-OF-LISP-EXPRESSIONS ... ''') The Emacs Lisp value of the last or only expression in the sequence becomes the value of the :code:`lisp` call, after conversion back to Python. User interaction ---------------- Emacs functions have the concept of user interaction for completing the specification of their arguments while being called. This happens only when a function is interactively called by the user, it does not happen when a function is directly called by another. As Python does not have a corresponding facility, a bit of trickery was needed to retrofit that facility on the Python side. After loading a Python module but prior to creating an Emacs view for this module, Pymacs decides whether loaded functions will be interactively callable from Emacs, or not. Whenever a function has an :code:`interaction` attribute, this attribute holds the Emacs interaction specification for this function. The specification is either another Python function or a string. In the former case, that other function is called without arguments and should, maybe after having consulted the user, return a list of the actual arguments to be used for the original function. In the latter case, the specification string is used verbatim as the argument to the ``(interactive ...)`` function on the Emacs side. To get a short reminder about how this string is interpreted on the Emacs side, try ``C-h f interactive RET`` within Emacs. Here is an example where an empty string is used to specify that an interactive has no arguments:: from Pymacs import lisp def hello_world(): "`Hello world' from Python." lisp.insert("Hello from Python!") hello_world.interaction = '' .. ` Versions of Python released before the integration of PEP 232 do not allow users to add attributes to functions, so there is a fall-back mechanism. Let's presume that a given function does not have an :code:`interaction` attribute as explained above. If the Python module contains an :code:`interactions` global variable which is a dictionary, if that dictionary has an entry for the given function with a value other than :code:`None`, that function is going to be interactive on the Emacs side. Here is how the preceding example should be written for an older version of Python, or when portability is at premium:: from Pymacs import lisp interactions = {} def hello_world(): "`Hello world' from Python." lisp.insert("Hello from Python!") interactions[hello_world] = '' One might wonder why we do not merely use ``lisp.interactive(...)`` from within Python. There is some magic in the Emacs Lisp interpreter itself, looking for that call *before* the function is actually entered, this explains why ``(interactive ...)`` has to appear first in an Emacs Lisp :code:`defun`. Pymacs could try to scan the already compiled form of the Python code, seeking for ``lisp.interactive``, but as the evaluation of :code:`lisp.interactive` arguments could get arbitrarily complex, it would a real challenge un-compiling that evaluation into Emacs Lisp. Key bindings ------------ An interactive function may be bound to a key sequence. To translate bindings like ``C-x w``, say, one might have to know a bit more how Emacs Lisp processes string escapes like ``\C-x`` or ``\M-\C-x`` in Emacs Lisp, and emulate it within Python strings, since Python does not have such escapes. ``\C-L``, where L is an upper case letter, produces a character which ordinal is the result of subtracting 0x40 from ordinal of ``L``. ``\M-`` has the ordinal one gets by adding 0x80 to the ordinal of following described character. So people can use self-inserting non-ASCII characters, ``\M-`` is given another representation, which is to replace the addition of 0x80 by prefixing with Escape, that is 0x1b. So ``\C-x`` in Emacs is ``\x18`` in Python. This is easily found, using an interactive Python session, by giving it: ``chr(ord('X') - ord('A') + 1)``. An easier way would be using the :code:`kbd` function on the Emacs Lisp side, like with ``lisp.kbd('C-x w')`` or ``lisp.kbd('M-')``. To bind the F1 key to the :code:`helper` function in some :code:`module`:: lisp.global_set_key((lisp.f1,), lisp.module_helper) ``(item,)`` is a Python tuple yielding an Emacs Lisp vector. ``lisp.f1`` translates to the Emacs Lisp symbol :code:`f1`. So, Python ``(lisp.f1,)`` is Emacs Lisp ``[f1]``. Keys like ``[M-f2]`` might require some more ingenuity, one may write either ``(lisp['M-f2'],)`` or ``(lisp.M_f2,)`` on the Python side. Debugging ========= Finding bugs in a program is an art, which may be difficult enough already when there is a single process and a single language. Pymacs involves a part (usually short) written in Emacs Lisp and another part (usually more substantial) written in Python, each running in their own process. Both processes communicate which each other. Moreover, to get debugging hints, Emacs is often the necessary door by which the programming user may catch glimpses on what is happening on both sides. To effectively debug Pymacs code, one benefits from having some familiarity with the communication protocol, and also from knowing how to observe both sides of this protocol at once. The usual way is through the :code:`*Pymacs*` buffer within Emacs, which shows an Emacs view the whole protocol. One may also view by forcing the Pymacs helper to save a trace file, which shows a Python view the whole protocol — unless there are communication errors, this should tell the same story as with the :code:`*Pymacs*` buffer. These few topics are developed in the three following sections. The remaining sections address more specific issues about Emacs Lisp or Python debugging. The communication protocol -------------------------- The Pymacs communication protocol is rather simple deep down, merely using evaluation on arrival on both sides. All the rest is recursion trickery over that simple idea. + It is more easy to generate than to parse. Moreover, Emacs has a Lisp parser and Python has a Python parser. So, when preparing a message to the Pymacs helper, Emacs generates Python code for Python to parse, and when preparing a message for Emacs, Python generates Emacs Lisp expressions for Emacs to parse. + Messages are exchanged in strictly alternating directions (from Python to Emacs, from Emacs to Python, etc.), the first message being sent by the Pymacs helper (from Python to Emacs) just after it started, identifying the current Pymacs version. + Messages in both directions have a similar envelope. Each physical message has a prefix, the message contents, and a newline. The prefix starts with either ``<`` or ``>`` to mark the directionality, is followed by the decimal expression of the contents length counted in characters, and terminates with a single horizontal tab. The count excludes the prefix, but includes the newline. + In each direction, messages are made up of two elements: an action keyword and a single argument (yet the argument may sometimes be complex). As a special case, memory cleanup messages from Python to Emacs use four elements: the atom :code:`free`, a list of slot numbers to free, and then the real action and argument. This is because the cleanup is delayed and piggy-backed over some other message. + For Emacs originated messages, the action and the argument are separated by a space. For Python originated messages, the action and the argument are made into a Lisp list. + Most actions in the following table are available in both directions, unless noted. The first three actions *start* a new level of Pymacs evaluation, the two remaining actions end the current level. + :code:`eval` requests the evaluation of its expression argument. + :code:`exec` requests the execution of its statement argument (this may only be received on the Python side). + :code:`expand` requests the opening of an Emacs Lisp structure (this may only be received on the Emacs side). + :code:`return` represents the normal reply to a request, the argument holds the value to be returned (:code:`nil` in case of :code:`exec`). + :code:`raise` represents the error reply to a request, the argument then holds a diagnostic string. Python evaluation is done in the context of the :code:`Pymacs.pymacs` module. On the Emacs Lisp side, there is no concept of module name spaces, so we internally use the ``pymacs-`` prefix as an attempt to stay clean. Users should ideally refrain from naming their Emacs Lisp objects with a ``pymacs-`` prefix. The protocol may be fragile to interruption requests, so it tries to recognize each message action before evaluation is attempted. The idea (not fully implemented yet) is to make the protocol part immune to interruptions, but to allow evaluations themselves to be interrupted. The :code:`*Pymacs*` buffer --------------------------- Emacs and Python are two separate processes (well, each may use more than one process). Pymacs implements a simple communication protocol between both, and does whatever needed so the programmers do not have to worry about details. The main debugging tool is the communication buffer between Emacs and Python, which is named :code:`*Pymacs*`. As it is sometimes helpful to understand the communication protocol, it is briefly explained here, using an artificially complex example to do so. Consider (this example assumes Python 2):: (pymacs-eval "lisp('(pymacs-eval \"repr(2L**111)\")')") "2596148429267413814265248164610048L" Here, Emacs asks Python to ask Emacs to ask Python for a simple bignum computation. Note that Emacs does not natively know how to handle big integers, nor has an internal representation for them. This is why I use the :code:`repr` function, so Python returns a string representation of the result, instead of the result itself. Here is a trace for this example. Imagine that Emacs stands on the left and that Python stands on the right. The ``<`` character flags a message going from Python to Emacs, while the ``>`` character flags a message going from Emacs to Python. The number gives the length of the message, including the end of line. (Acute readers may notice that the first number is incorrect, as the version number gets replaced in the example while this manual is being produced.) :: <22 (version "@VERSION@") >43 eval lisp('(pymacs-eval "repr(2L**111)")') <45 (eval (progn (pymacs-eval "repr(2L**111)"))) >19 eval repr(2L**111) <47 (return "2596148429267413814265248164610048L") >45 return "2596148429267413814265248164610048L" <47 (return "2596148429267413814265248164610048L") Part of the protocol manages memory, and this management generates some extra-noise in the :code:`*Pymacs*` buffer. Whenever Emacs passes a structure to Python, an extra pointer is generated on the Emacs side to inhibit garbage collection by Emacs. Python garbage collector detects when the received structure is no longer needed on the Python side, at which time the next communication will tell Emacs to remove the extra pointer. It works symmetrically as well, that is, whenever Python passes a structure to Emacs, an extra Python reference is generated to inhibit garbage collection on the Python side. Emacs garbage collector detects when the received structure is no longer needed on the Emacs side, after which Python will be told to remove the extra reference. For efficiency, those allocation-related messages are delayed, merged and batched together within the next communication having another purpose. Variable :code:`pymacs-trace-transit` may be modified for controlling how and when the :code:`*Pymacs*` buffer, or parts thereof, get erased. By default, this buffer gets erased before each transaction. To make good debugging use of it, first set :code:`pymacs-trace-transit` to either :code:`t` or to some ``(KEEP . LIMIT)``. Debugging the Pymacs helper --------------------------- The Pymacs helper is a Python program which accepts options and arguments. The available options, which are only meant for debugging, are: -d FILE Debug the protocol to FILE -s FILE Trace received signals to FILE + The ``-d`` option saves a copy of the communication protocol in the given file, as seen from the Pymacs helper. The file should be fairly identical to the contents of the :code:`*Pymacs*` buffer within Emacs. + The ``-s`` option monitors most signals received by the Pymacs helper and logs them in the given file. Each log line merely contains a signal number, possibly followed by a star if the interruption was allowed in. Besides logging, signals are usually ignored. The arguments list directories to be added at the beginning of the Python module search path, and whenever Emacs launches the Pymacs helper, the contents of the Emacs Lisp :code:`pymacs-load-path` variable is turned into this argument list. The Pymacs helper options may be set through the :code:`PYMACS_OPTIONS` environment variable. For example, one could execute something like:: export PYMACS_OPTIONS='-d /tmp/pymacs-debug -s /tmp/pymacs-signals' in a shell (presuming :code:`bash` here) and start Emacs from that shell. Then, when Emacs launches the Pymacs helper, the above options are transmitted to it. Emacs usual debugging --------------------- If cross-calls between Emacs Lisp and Python nest deeply, an error will raise successive exceptions alternatively on both sides as requests unstack, and the diagnostic gets transmitted back and forth, slightly growing as we go. So, errors will eventually be reported by Emacs. I made no kind of effort to transmit the Emacs Lisp back trace on the Python side, as I do not see a purpose for it: all debugging is done within Emacs windows anyway. On recent Emacses, the Python back trace gets displayed in the mini-buffer, and the Emacs Lisp back trace is simultaneously shown in the :code:`*Backtrace*` window. One useful thing is to allow to mini-buffer to grow big, so it has more chance to fully contain the Python back trace, the last lines of which are often especially useful. Here, I use:: (setq resize-mini-windows t max-mini-window-height .85) in my :file:`.emacs` file, so the mini-buffer may use 85% of the screen, and quickly shrinks when fewer lines are needed. The mini-buffer contents disappear at the next keystroke, but you can recover the Python back trace by looking at the end of the :code:`*Messages*` buffer. In which case the :code:`ffap` package in Emacs may be yet another friend! From the :code:`*Messages*` buffer, once :code:`ffap` activated, merely put the cursor on the file name of a Python module from the back trace, and ``C-x C-f RET`` will quickly open that source for you. Python usual debugging ---------------------- A common way to debug a Python script is to spread it with :code:`print` commands. When such a Python script is executed under Pymacs control, these :code:`print` statements display the results right within the :code:`*Pymacs*` buffer, and may be observed there. As such output gets intermixed with the Pymacs protocol itself, never ever print the symbol ``<``, immediately followed by the expression of a decimal number, immediately followed by a horizontal tab (``\t``). If you were doing so, the communication protocol would get pretty mixed up, and Pymacs would break. But you do not have to worry much about this: the forbidden sequence is unlikely in practice, would it be only because people do not often use horizontal tabs anymore — oh, tabs were once undoubtedly popular, but this was many years ago… Auto-reloading on save ---------------------- I found useful to automatically :code:`pymacs-load` some Python files whenever they get saved from Emacs. This can be decided on a per-file or per-directory basis. To get a particular Python file to be reloaded automatically on save, add the following lines at the end:: # Local Variables: # pymacs-auto-reload: t # End: Here is an example of automatic reloading on a per-directory basis. The code below assumes that Python files meant for Pymacs are kept in :file:`~/share/emacs/python`:: (defun fp-maybe-pymacs-reload () (let ((pymacsdir (expand-file-name "~/share/emacs/python/"))) (when (and (string-equal (file-name-directory buffer-file-name) pymacsdir) (string-match "\\.py\\'" buffer-file-name)) (pymacs-load (substring buffer-file-name 0 -3))))) (add-hook 'after-save-hook 'fp-maybe-pymacs-reload) Administrative miscellany ========================= Development history ------------------- I once hungered for a Python-extensible editor, so much so that I pondered the idea of dropping Emacs for other avenues, but found nothing much convincing. Moreover, looking at all Lisp extensions I'd made for myself, and considering all those superb tools written by others, all of which are now part of my computer life, it would have been a huge undertaking for me to reprogram these all in Python. So, when I began to see that something like Pymacs was possible, I felt strongly motivated! :-) Pymacs draws on previous work of Cedric Adjih that enabled the running of Python as a process separate from Emacs. See http://www.crepuscule.com/pyemacs/, or write Cedric at mailto:adjih-pam@crepuscule.com. Cedric presented :code:`pyemacs` to me as a proof of concept. As I simplified that concept a bit, I dropped the ``e`` in ``pyemacs`` :-). Cedric also previously wrote patches for linking Python right into XEmacs, but abandoned the idea, as he found out that his patches were unmaintainable over the evolution of both Python and XEmacs. As Brian McErlean independently and simultaneously wrote a tool similar to this one, we decided to merge our projects. In an amusing coincidence, he even chose :code:`pymacs` as a name. Brian paid good attention to complex details that escaped my courage, so his help and collaboration have been beneficial. You may reach Brian at mailto:brianmce@crosswinds.net. The initial throw at Pymacs has been written on 2001-09-05, and releases in the 0.x series followed in a rapid pace for a few months, and Pymacs soon became stable. Reported bugs or suggestions were minor, and the feature set was fairly usable from the start. For a long while, there was not enough new material to warrant other releases. Later, someone begged me to consider Vim, and not only Emacs, for some tools I was then writing (in the area of musical scores). Looking at Vim more closely, I discovered that it is a worth editor, with Python nicely integrated, enough for me to switch. In a `Web article`__ (which many enjoyed, as they told me), I detailed my feelings on these matters. __ http://pinard.progiciels-bpi.ca/opinions/editors.html I switched from Emacs to Vim in my day-to-day habits, and because of this, felt that Pymacs needed a more credible maintainer than me. Syver Enstad, who was an enthusiastic user and competent contributor, was kind enough to accept the duty (2003-10). Syver then became unavailable, to the point I could not contact him in years. I would loathe to see myself interfering with an official maintainer, but after I decided to return to some moderate Emacs usage, and because of the long silence, I considered resuming Pymacs maintenance (2007-11), and did it (2008-01). Giovanni Giorgi once (2007-03) wanted to expand on Pymacs and publish it on his own, and later felt like maintaining it whole (late 2007-12). I rather suggested an attempt at collaborative maintenance, and this experiment is still going on... Should it come with Emacs? -------------------------- Gerd Möllman, who was maintaining Emacs at the time of Pymacs birth and development, retrofitted (2001-09) the idea of a :code:`post-gc-hook` from XEmacs, as a way to facilitate memory management within Pymacs. Richard Stallman once suggested (2001-10) that Pymacs be distributed within Emacs, and while discussing the details of this, I underlined small technical difficulties about Emacs installing the Python parts, and the need of a convention about where to install Python files meant for Pymacs. As Richard felt, at the time, very overwhelmed with other duties, no decision was taken and the integration went nowhere. After Gerd resigned as an Emacs maintainer, someone from the Emacs development team wrote again (2002-01) asking information about how to integrate Pymacs. It was easy for me to write a good and thorough summary, after all these discussions with Richard. And that's the end of the story: I never heard of it again. :-) The future of Pymacs -------------------- Some people suggested important internal Pymacs changes. In my opinion, new bigger features are better implemented in a careful way, first as examples or contributions, and moved closer to internal integration depending on how users use or appreciate them. For now, Pymacs should concentrate at doing its own humble job well, and resist bloat. Before Pymacs closes to some version 1.0, some specifications should be revisited, user suggestions pondered, porting matters documented. The test suite should grow up, we should collect more examples. Pymacs should aim seamless integration with :file:`.el` files and with transparent :code:`autoload` (my little tries were not so successful). On the Python side, Pymacs *might* fake primitives like :code:`getindex` and :code:`putindex`, and better support iterators and some newer Python features. Pymacs is not much geared towards Python threads. It is not clear yet if it would be reasonably tractable to better support them. Technical miscellany ==================== Known bugs or limitations ------------------------- What is the difference between a bug and a limitation? *Limitations* are either bugs not worth repairing, or else, bugs that we do not know yet how to repair. While documenting a bug is indeed a way to postpone its solution, it does not necessarily turns it into a limitation. On a mailing list I once closely followed, a few maintainers were getting very, very upset whenever the word *bug* happened to be used in any message, especially if the bug was documented. A distinguished member on this list (William N. Venable) coined the wonderful word *unfelicity*, as a way to discuss problems while avoiding human damage. Such delicacies are surely unneeded for Pymacs. A bug is a bug! Needed control on stack unwinding ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, As Ali Gholami Rudi nicely summarized it (2008-02-12): `Lisp programmers could use` :code:`inhibit-quit` `at various levels of recursion, and use Pymacs at these various levels. As an Emacs` :code:`quit` `might propagate out of the stack, but stopping at various levels of it when the Lisp programmers took measures for it, I think there is no choice that finding some mechanism by which Python will unstack in parallel with Emacs, that is, no more and no less, so if Emacs resumes processing at some intermediate level, Python should be ready at the exact corresponding level on its side.` By doing ``pymacs-eval "(time.sleep(10))"``, and quitting, I once saw that: + Emacs does not interrupt at once, and if :code:`inhibit-quit` remains set while Emacs waits for the Pymacs helper, this is surely not user friendly! + At the end of the wait, I get a spurious IO error (I do not know where it comes from). Possible memory leak ,,,,,,,,,,,,,,,,,,,, Memory may leak in some theoretical circumstances (I say theoretical, because no one ever reported this as being an actual problem). As Richard Stallman once put it (2002-08): `I wonder, though, can this` [memory management] `technique fully handle cycles that run between Lisp and Python? Suppose Lisp object A refers to Python object B, which refers to Lisp object A, and suppose nothing else refers to either one of them. Will you succeed in recognizing these two objects as garbage?` Death from a Ctrl-C ,,,,,,,,,,,,,,,,,,, Ali Gholami Rudi notices (2008-02-20) that Pymacs dies over:: M-x pymacs-eval RET lisp.kbd('C-c r r') RET as there is a ``Ctrl-C`` in the value returned from Emacs. Suggestions to ponder --------------------- Python-driven Pymacs ,,,,,,,,,,,,,,,,,,,, I guess the most important improvement we could think to Pymacs would be some machinery by which Python programs, started outside Emacs, could access Pymacs, once it started. That could be useful at least for testing or debugging, and maybe for more serious work as well. These are mere thoughts, I do not plan working at this soon, unless I have an actual need. But if the challenge interests someone, please go ahead! Here is how it could go. Pymacs has a Python interpreter running as a sub-process of Emacs. In fact, Emacs loads :file:`pymacs.el`, which in turn gets Python to execute :file:`Pymacs.py`, and both communicate afterwards. :file:`Pymacs.py` is only active whenever :file:`pymacs.el` calls it, otherwise it is blocked. :file:`Pymacs.py` could, under some option, start another thread within itself. The initial thread would block waiting for Emacs, as usual. The second thread would block waiting to serve any Python client wanting to access Emacs. When this occurs, the second thread would queue a request for the first thread, and then send a signal to Emacs so it triggers a Pymacs communication. At each communication opportunity, the first thread on the Python side might fully service the queue from the second thread. Autoloading interface ,,,,,,,,,,,,,,,,,,,,, I once tried better interfacing to :code:`autoload`, and failed. It got more intricate that I thought it would be. I might revisit this, but in low priority. In the meantime, one may use a small :file:`.el` file, like this one, on the Emacs load path:: # File zorglub.el — just load zorglub.py. (pymacs-load "zorglub") (provide 'zorglub) and then use either one of:: (require 'zorglub) ; in Lisp lisp.require(lisp.zorglub) # in Python at the beginning of body for any function needing functions from :file:`zorglub.py`. One may also write one or many:: (autoload 'FUNCTION-NAME "zorglub" nil t) to indirectly autoload :file:`zorglub.py` as needed. Handling more special forms ,,,,,,,,,,,,,,,,,,,,,,,,,,, The discussion started about the lack of specific Pymacs support, on the Python side, for the Emacs Lisp :code:`setq-default` function. People also mentioned :code:`defvar` and :code:`defcustom`, but there are really many other special forms in Emacs Lisp. (A special form is any expression form in which all arguments are not all blindly evaluated before the function actually enters. The function then receives the arguments unevaluated, and it is its responsibility to choose which arguments should be evaluated, and when.) The fact is that, besides :code:`setq` and some forms of :code:`defun`, functions, few special forms are supported in Pymacs. One may think of :code:`let`, functions like :code:`save-excursion`, etc. But that's all, and maybe debatable as too much already. The real problem to solve is supporting special forms (and macros) at Pymacs level. If we create special cases in Pymacs for each special form we happen to stumble upon, Pymacs might loose its elegance, and so, we have to stay a bit careful. All special forms require that the user somehow defeat the fact that Pymacs evaluate all function arguments before calling a Lisp function. I realise it might be a subtle point for people unfamiliar with Lisp. :code:`apply` on the Lisp side applies a function on a list of arguments, so the trick is to evaluate on the Python side something yielding a list, the contents of which are to be actual arguments. I'm not fully sure this is the good direction to take, even if easy — I mean here, that the real problem to solve is something else. On a related matter, Ali Gholami Rudi suggested that Pymacs supports Emacs so-called *keyword arguments*, and even provide a simple patch to do so:: diff --git a/Pymacs.py b/Pymacs.py --- a/Pymacs.py +++ b/Pymacs.py @@ -453,13 +453,16 @@ write(') nil)') lisp._eval(''.join(fragments)) - def __call__(self, *arguments): + def __call__(self, *arguments, **keywords): fragments = [] write = fragments.append write('(%s' % self.text) for argument in arguments: write(' ') print_lisp(argument, write, True) + for kwd, value in keywords.items(): + write(' :%s ' % kwd) + print_lisp(value, write, True) write(')') return lisp._eval(''.join(fragments)) So far that I understand, there are just no keyword arguments in Emacs. Keywords might be nothing but a mirage created by :code:`defcustom` only (maybe through :code:`define-minor-mode`) and :code:`defstruct` -- is there any other usage for keywords? So I wonder if this unusual trickery, not even a real part of Emacs Lisp, is important enough to warrant modifying something as fundamental as :code:`__call__` in Pymacs. Part of my reluctance might also come from my (unsubstanciated) fear that the above change would slow down the hearth of Pymacs. For now at least, users are invited to use ``lisp(...)`` for all other special forms. It's simple, it's rather safe. Things like:: lisp('(setq-default %s %s)' % (name, value)) are not so horrible... :-) Deep down, ``lisp()`` calls are what Pymacs do all the time under the table, all the rest are bits of sugar. What would be needed is a visit to this special form support with wider eyes and mind, come up with a general unifying solution, rather than multiplying special cases. Support for Python dictionaries ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, While Pymacs mirrors Python tuples and lists into Emacs Lisp vectors and lists, it has nothing currently to reflect Python dictionaries. It has been suggested to use Emacs Lisp alists to do so, but this does not seem adequate to me. Pymacs 0.0 and 0.1 did convert Python dicts to Emacs Lisp alists. This was a mere toy to get experience with the Pymacs mechanics, not a serious idea. Despite I wanted *something* for Python dicts, this choice was not very satisfying: + Dicts access speed are O(1); alists are O(N). + Dicts have no intrinsic order; alists are really a sequence. + Dicts have no duplicate keys; alists may have shadows. The last two points, in particular, have the consequence that one cannot convert back and forth from Lisp and have results which compare with ``(equal ...)``. This makes the equivalence especially ugly. Proper lists and vectors in Lisp can be converted back and forth to Python and be ``(equal ...)``, so those equivalences are bearable. The dict conversion was withdrawn in Pymacs 0.2; I thought I should better postpone until a better idea pops up, than let users develop habits with something wrong and doomed to be replaced. Emacs Lisp hash tables (as in Emacs 21) could be an acceptable equivalent for Python dicts. This is what Brian McErlean did, and suggests. My only reservation is about the Python need for non-mutable keys, something which Emacs does not guarantee. As by default, from Lisp to Python, references are transmitted instead of contents, this would be a possible problem only when an expanded copy is requested from the Python side. This would never be a problem going from Python to Emacs, so far as I understand things now. A nicer :code:`*Pymacs*` buffer ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, We might improve how the :code:`*Pymacs*` communication buffer looks. Let's sketch this quickly, in any case, I'm not sure how worth this is. The buffer might be turned into a more fully featured Emacs mode, so it can benefit from highlighting and colourisation, and other goodies. The first thing would be to install font-lock definitions. The second thing would be to use indenting to show the proper nesting of calls between Emacs and Python, in both directions. I would prefer this to be done as a display feature, not as part of the communication protocol. A third thing would be to automatically interpret object numbers on both sides, replacing them with clearer text whenever possible — this information may often be deduced from earlier communications. Finally, that mode could allow for some inspection on Pymacs object and status, and maybe also to control the external Python server described in another suggestion in this series, if it ever gets implemented. Speed issues ------------ Shoot out projects compare the relative speed of many popular languages, and the relative merits of Lisp and Python might interest Pymacs users. The first URL points to a version oriented towards Win32 systems, the second is more recent but Debian-oriented: + http://dada.perl.it/shootout/index.html + http://shootout.alioth.debian.org/ I've not heard of any Python to Lisp compiler. Lisp may be slow or fast depending on how one uses it, and how much one uses declarations. Some Lisp systems have really excellent compilers, that give very fast code when properly hinted. Python itself may be slow or fast, once again depending on how one uses it. With the proper bend, one can develop the habit of writing Python which shows honest speed. And there is always Pyrex (and the very similar Cython), which is Python complemented with explicit declarations (a bit like some Lisp implementations), and which can buy a lot of speed. This is quite likely that one can have fast programs while using Python, or a mix of Python and either Pyrex or Cython (or even Psyco sometimes), that is, within Python paradigms, without feeling any need of resorting to Lisp. If Python looks like being slow while being used with Emacs, the problem probably lies in Emacs-Python communication which Pymacs implements. One has to learn how to do the proper compromises for having less communications. (In that regard, Vim and Python are really linked together, so Python in Vim is likely faster than Pymacs for someone who does not pay special attention to such matters.) Ali Gholami Rudi also writes (2008-02): `Well, there seems to be lots of overhead when transferring large strings. Transferring them requires:` 1. `escaping characters in the strings` 2. `putting them in` :code:`*Pymacs*` `buffer` 3. `sending the region to Python process` 4. `evaluating the Python string in Python-side (involves compiling)` `In my experiments, transferring a ~5k-line file takes more than a second on a relatively new computer (data from` :code:`rope-dev`\ `). Improving that probably requires a new protocol that does not use Python eval and has an optional debug buffer. Probably few applications need to transfer large strings to Python but if they do, it is quite slow.` All in all, speed may sometimes become a real issue for Pymacs. I once wrote within http://pinard.progiciels-bpi.ca/opinions/editors.html : `While Pymacs is elegant in my opinion, one cannot effectively use Pymacs (the Python part) without knowing at least the specification of many Lisp functions, and I found that it requires some doing for a Pymacs developer to decouple the Emacs interaction part from the purer algorithmic part in applications. Moreover, if you do not consider speed issues, they bite you.` Vim-related thoughts -------------------- Emacs Lisp is deeply soldered into Emacs internals. Vim has its own language, which people sometimes call Vimscript, similarly tightened into Vim. My feeling is that Emacs Lisp allows for a more intimate handling of edit buffers and external processes than Vimscript does, yet this intimacy has a price in complexity, so all totalled, they may be perceived as comparable for most practical purposes. Pymacs allows customising Emacs with Python instead of Emacs Lisp, and then runs Python as a process external to Emacs, with a communication protocol between both processes. Python may be built into Vim, and then both Python and Vim use a single process. The same as Pymacs gives access to almost all of Emacs Lisp, Python within Vim gives access to almost all of Vimscript, but with a much smaller overhead than Pymacs. Pymacs is not Emacs Lisp, and Python in Vim is not Vimscript either, tweaks are needed in both cases for accessing some of the underlying scripting facilities. Pymacs is rather elegant, Python in Vim is rather clean. Python itself is both elegant and clean, but one strong point of Python for me is the legibility, which builds deeper roots on the clean side than on the elegant side. All in all, despite I know how debatable it can be, I guess I now have a prejudice towards Python in Vim. I figured out a simple way to have the same Python source usable both within Pymacs or Vim. However, Emacs is byte oriented, while Vim is line oriented. In a few Pymacs applications of mine, I internally toggle between line orientation and byte orientation, keeping both for speed most probably, while I see things would be a bit simpler (and maybe slower) if I was pushing myself on the line-oriented side. Each of Emacs and Vim have their own logic and elegance, and it is probable that we loose overall if we try to emulate one with the other. The idea traversed me to convert all the few Pymacs examples so they work both for Pymacs and Vim, and through the documentation, publicise how people writing Python extensions could write them for both editors at once. Yet, while doing so, one has to stretch either towards Emacs or Vim, and I guess I would favour Vim over Emacs when the time comes to evaluate efficiency-related choices. I also thought about writing a Pymacs module for running Python scripts already written for Vim, by offering a compatibility layer. The complexity of this might be unbounded, I should study actual Python scripts for Vim before knowing better if this is thinkable or not. Pymacs-0.25/pymacs.wpr000066400000000000000000000010301175204346300147210ustar00rootroot00000000000000#!wing #!version=4.0 ################################################################## # Wing IDE project file # ################################################################## [project attributes] proj.directory-list = [{'dirloc': loc('.'), 'excludes': (), 'filter': '*', 'include_hidden': False, 'recursive': True, 'watch_for_changes': True}] proj.file-type = 'shared' Pymacs-0.25/setup.py000066400000000000000000000014741175204346300144210ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import sys try: from distutils.core import setup except ImportError: sys.stderr.write("""\ .------------------------------------------------------------------------. | It seems that the package Distutils is not available for this Python. | | You might fetch and install Distutils and retry your command, or else, | | figure out where the Pymacs/ directory should go, and make that copy. | `------------------------------------------------------------------------' """) sys.exit(1) package = 'Pymacs' version = '0.25' setup(name=package, version=version, description="Interface between Emacs Lisp and Python", author='François Pinard', author_email='pinard@iro.umontreal.ca', url='http://pymacs.progiciels-bpi.ca', py_modules=['Pymacs']) Pymacs-0.25/tests/000077500000000000000000000000001175204346300140435ustar00rootroot00000000000000Pymacs-0.25/tests/pytest.in000066400000000000000000000465121175204346300157330ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright © 2005 Progiciels Bourbeau-Pinard inc. # François Pinard , 2005. """\ Execute a validation suite built in pylib's py.test style. Usage: pytest [OPTION]... [PATH]... Options: -h Print this help and exit right away. -v Produce one line per test, instead of a dot per test. -n Do not capture stdout nor stderr. -p Profile validation suite (through "lsprof"). -f PREFIX Use PREFIX instead of "test_" for file names. -s FILE Save ordinals of failed tests, one per line. -o ORDINALS Only consider tests having these execution ordinals. -k PATTERN Only retain tests which names match PATTERN. -x PATTERN Exclude tests which names match PATTERN. -l LIMIT Stop the validation suite after LIMIT errors. If -l is not used, the validation will stop after 10 errors. ORDINALS is a sequence of comma-separated integers. Options -k, -o and -x may be repeated; then a test should match at least one of -k options if any, one of -o options is any, and none of -x options. If PATH names a file, the name should match "test_.*\.py". If PATH names a directory, it is recursively searched to find so matching file names. If no PATH are given, the current directory is implied. Test progression is first displayed on standard error. Then, unless -s is selected, failed tests are detailed on stdout and, if there is at least one such failed test, the return status of this program is non-zero. """ # This tool implements a minimal set of specifications stolen from the # excellent Codespeak's py.test, at a time I really needed py.test to be # more Unicode-aware. if PYTHON3: import inspect, io, locale, os, sys, time, traceback else: # Jython misses the yield keyword. from __future__ import generators try: import locale except ImportError: # Jython misses this module. locale = None try: sorted except NameError: # Jython misses this function. def sorted(sequence): sequence = list(sequence) sequence.sort() return sequence __metaclass__ = type import inspect, os, sys, time, traceback from StringIO import StringIO # How many displayable characters in an output line. WIDTH = 79 class Limit_Reached(Exception): pass class Main: prefix = 'test_' pattern = [] exclusion = [] ordinals = [] verbose = False profile = False limit = 10 capture = True save = None # For handling setup/teardown laziness. delayed_setup_module = None delayed_setup_class = None did_tests_in_module = False did_tests_in_class = False def main(self, *arguments): if sys.getdefaultencoding() == 'ascii': sys.stdout = Friendly_StreamWriter(sys.stdout) sys.stderr = Friendly_StreamWriter(sys.stderr) import getopt options, arguments = getopt.getopt(arguments, 'f:hk:l:no:ps:vx:') for option, value in options: if option == '-f': self.prefix = value elif option == '-h': sys.stdout.write(__doc__) return elif option == '-k': self.pattern.append(value) elif option == '-l': self.limit = int(value) elif option == '-n': self.capture = False elif option == '-o': self.ordinals += [ int(text) for text in value.replace(',', ' ').split()] elif option == '-p': self.profile = True elif option == '-s': self.save = value elif option == '-v': self.verbose = True elif option == '-x': self.exclusion.append(value) if not arguments: arguments = '.', if self.pattern: import re self.pattern = re.compile('|'.join(self.pattern)) else: self.pattern = None if self.exclusion: import re self.exclusion = re.compile('|'.join(self.exclusion)) else: self.exclusion = None write = sys.stderr.write self.failures = [] self.total_count = 0 self.skip_count = 0 start_time = time.time() if self.profile: try: import lsprof except ImportError: write("WARNING: profiler unavailable.\n") self.profiler = None else: self.profiler = lsprof.Profiler() else: self.profiler = None try: try: for argument in arguments: for file_name in self.each_file(argument): self.identifier = file_name self.column = 0 self.counter = 0 directory, base = os.path.split(file_name) sys.path.insert(0, directory) try: try: module = __import__(base[:-3]) except ImportError: if self.save: self.failures.append(self.total_count + 1) else: if PYTHON3: tracing = io.StringIO() else: tracing = StringIO() traceback.print_exc(file=tracing) self.failures.append( (self.total_count + 1, file_name, None, None, None, None, str(tracing.getvalue()))) else: self.handle_module(file_name, module) finally: del sys.path[0] if self.counter and not self.verbose: text = '(%d)' % self.counter if self.column + 1 + len(text) >= WIDTH: write('\n%5d ' % self.counter) else: text = ' ' + text write(text + '\n') except KeyboardInterrupt: if not self.verbose: write('\n') write('\n*** INTERRUPTION! ***\n') except Limit_Reached: if not self.verbose: write('\n') if not self.save: if len(self.failures) == 1: write('\n*** ONE ERROR ALREADY! ***\n') else: write('\n*** %d ERRORS ALREADY! ***\n' % self.limit) finally: if self.profiler is not None: stats = lsprof.Stats(self.profiler.getstats()) stats.sort('inlinetime') write('\n') stats.pprint(15) if self.failures: if len(self.failures) == 1: text = "one FAILED test" else: text = "%d FAILED tests" % len(self.failures) first = False else: text = '' first = True good_count = (self.total_count - self.skip_count - len(self.failures)) if good_count: if first: if good_count == 1: text += "one good test" else: text += "%d good tests" % good_count first = False else: if good_count == 1: text += ", one good" else: text += ", %d good" % good_count if self.skip_count: if first: if self.skip_count == 1: text += "one skipped test" else: text += "%d skipped tests" % self.skip_count first = False else: if self.skip_count == 1: text += ", one skipped" else: text += ", %d skipped" % self.skip_count if first: text = "No test" summary = ("\nSummary: %s in %.2f seconds.\n" % (text, time.time() - start_time)) write(summary) if self.save: handle = file(self.save, 'w') for ordinal in self.failures: handle.write('%d\n' % ordinal) handle.close() else: write = sys.stdout.write for (ordinal, prefix, function, arguments, stdout, stderr, tracing) in self.failures: write('\n' + '=' * WIDTH + '\n') write('%d. %s\n' % (ordinal, prefix)) if function and function.__name__ != os.path.basename(prefix): write(" Fonction %s\n" % function.__name__) if arguments: for counter, argument in enumerate(arguments): write(" Arg %d = %r\n" % (counter + 1, argument)) for buffer, titre in ((stdout, 'STDOUT'), (stderr, 'STDERR')): if buffer: write('\n' + titre + ' >>>\n') write(buffer) if not buffer.endswith('\n'): write('\n') write('-' * WIDTH + '\n') write(tracing) if self.failures: write(summary) sys.exit(1) def each_file(self, path): if os.path.isdir(path): stack = [path] while stack: directory = stack.pop(0) for base in sorted(os.listdir(directory)): file_name = os.path.join(directory, base) if os.path.isdir(file_name): stack.append(file_name) elif base.startswith(self.prefix) and base.endswith('.py'): yield file_name else: directory, base = os.path.split(path) if base.startswith(self.prefix) and base.endswith('.py'): yield path def handle_module(self, prefix, module): collection = [] for name, objet in inspect.getmembers(module): if name.startswith('Test') and inspect.isclass(objet): if getattr(object, 'disabled', False): continue minimum = None for _, method in inspect.getmembers(objet, inspect.ismethod): if PYTHON3: number = method.__func__.__code__.co_firstlineno else: number = method.im_func.func_code.co_firstlineno if minimum is None or number < minimum: minimum = number if minimum is not None: collection.append((minimum, name, objet, False)) elif name.startswith('test_') and inspect.isfunction(objet): if PYTHON3: code = objet.__code__ else: code = objet.func_code collection.append((code.co_firstlineno, name, objet, bool(code.co_flags & 32))) if not collection: return self.delayed_setup_module = None self.did_tests_in_module = False if hasattr(module, 'setup_module'): self.delayed_setup_module = module.setup_module, module for _, name, objet, generator in sorted(collection): self.delayed_setup_class = None self.did_tests_in_class = False if inspect.isclass(objet): if not getattr(object, 'disabled', False): self.handle_class(prefix + '/' + name, objet) else: self.handle_function(prefix + '/' + name, objet, generator, None) if self.did_tests_in_module and hasattr(module, 'teardown_module'): module.teardown_module(module) def handle_class(self, prefix, classe): collection = [] for name, method in inspect.getmembers(classe, inspect.ismethod): if name.startswith('test_'): if PYTHON3: code = method.__func__.__code__ else: code = method.im_func.func_code collection.append((code.co_firstlineno, name, method, bool(code.co_flags & 32))) if not collection: return instance = classe() if hasattr(instance, 'setup_class'): self.delayed_setup_module = module.setup_class, classe for _, name, method, generator in sorted(collection): self.handle_function(prefix + '/' + name, getattr(instance, name), generator, instance) if self.did_tests_in_class and hasattr(instance, 'teardown_class'): instance.teardown_class(classe) def handle_function(self, prefix, function, generator, instance): if generator: for counter, arguments in enumerate(function()): if PYTHON3: self.launch_test(prefix + '/' + str(counter + 1), arguments[0], arguments[1:], instance) else: self.launch_test(prefix + '/' + unicode(counter + 1), arguments[0], arguments[1:], instance) else: self.launch_test(prefix, function, (), instance) def launch_test(self, prefix, function, arguments, instance): # Check if this test should be retained. if (self.exclusion is not None and self.exclusion.search(prefix) or self.pattern is not None and not self.pattern.search(prefix) or self.ordinals and self.total_count+1 not in self.ordinals): self.mark_progression(prefix, None) return # This test should definitely be executed. if self.delayed_setup_module is not None: self.delayed_setup_module[0](self.delayed_setup_module[1]) self.delayed_setup_module = None if self.delayed_setup_class is not None: self.delayed_setup_class[0](self.delayed_setup_class[1]) self.delayed_setup_class = None if instance is not None and hasattr(instance, 'setup_method'): instance.setup_method(function) if self.capture: saved_stdout = sys.stdout saved_stderr = sys.stderr if PYTHON3: stdout = sys.stdout = io.StringIO() stderr = sys.stderr = io.StringIO() else: stdout = sys.stdout = StringIO() stderr = sys.stderr = StringIO() self.activate_profiling() try: try: function(*arguments) except KeyboardInterrupt: exception = sys.exc_info() raise except: exception = sys.exc_info() else: exception = None finally: self.deactivate_profiling() if self.capture: sys.stdout = saved_stdout sys.stderr = saved_stderr stdout = stdout.getvalue() stderr = stderr.getvalue() else: stdout = None stderr = None if exception is None: self.mark_progression(prefix, True) else: self.mark_progression(prefix, False) if self.save: self.failures.append(self.total_count) else: if PYTHON3: tracing = io.StringIO() else: tracing = StringIO() traceback.print_exception(*exception, file=tracing) self.failures.append( (self.total_count, prefix, function, arguments, stdout, stderr, str(tracing.getvalue()))) if instance is not None and hasattr(instance, 'teardown_method'): instance.teardown_method(function) self.did_tests_in_class = True self.did_tests_in_module = True if exception is not None and len(self.failures) == self.limit: raise Limit_Reached def mark_progression(self, prefix, succes): self.total_count += 1 if succes is None: self.skip_count += 1 else: write = sys.stderr.write if self.verbose: write('%5d. [%s] %s\n' % (self.total_count, prefix, ('FAILED', 'ok')[succes])) else: if self.column == WIDTH: write('\n') self.column = 0 if not self.column: if self.counter: text = '%5d ' % (self.counter + 1) else: text = self.identifier + ' ' write(text) self.column = len(text) if PYTHON3: write('E·'[succes]) else: write(u'E·'[succes]) self.column += 1 self.counter += 1 def activate_profiling(self): if self.profiler is not None: self.profiler.enable(subcalls=True, builtins=True) def deactivate_profiling(self): if self.profiler is not None: self.profiler.disable() class Friendly_StreamWriter: # Avoid some Unicode nightmares, by allowing both unicode and # UTF-8 str strings to be written (given our sources are all UTF-8). def __init__(self, stream): if locale is None: encoding = 'UTF-8' else: encoding = locale.getpreferredencoding() import codecs writer = codecs.getwriter(encoding) self.stream = writer(stream, 'backslashreplace') def write(self, text): if PYTHON3: if not isinstance(text, str): text = str(text, 'UTF-8') else: if not isinstance(text, unicode): text = unicode(text, 'UTF-8') self.stream.write(text) def writelines(self, lines): for line in lines: self.write(line) run = Main() main = run.main class ExceptionExpected(Exception): pass def raises(expected, *args, **kws): try: if PYTHON3: if isinstance(args[0], str) and not kws: eval(args[0]) else: args[0](*args[1:], **kws) else: if isinstance(args[0], unicode) and not kws: eval(args[0]) else: args[0](*args[1:], **kws) except expected: return else: raise ExceptionExpected("Exception did not happen.") if __name__ == '__main__': main(*sys.argv[1:]) Pymacs-0.25/tests/setup.el000066400000000000000000000022721175204346300155300ustar00rootroot00000000000000; Emacs side of the testing protocol. (push ".." load-path) (load "pymacs.el" nil t) (defun run-one-request () (let ((buffer (get-buffer-create "*Tests*"))) (with-current-buffer buffer (buffer-disable-undo) (set-buffer-multibyte t) (set-buffer-file-coding-system 'utf-8-unix) (insert-file-contents "_request") (let ((lisp-code (read (current-buffer))) (standard-output (current-buffer))) (delete-region (point-min) (point-max)) (eval lisp-code)) (write-region nil nil "_reply" nil 0)))) (defun run-all-requests () (let ((buffer (get-buffer-create "*Tests*"))) (with-current-buffer buffer (buffer-disable-undo) (set-buffer-multibyte t) (set-buffer-file-coding-system 'utf-8-unix) (while t (while (file-exists-p "_reply") (sleep-for .005)) (insert-file-contents "_request") (let ((lisp-code (read (current-buffer))) (standard-output (current-buffer))) (delete-region (point-min) (point-max)) (eval lisp-code)) (write-region nil nil "_reply" nil 0) (delete-region (point-min) (point-max)) (delete-file "_request"))))) Pymacs-0.25/tests/setup.py.in000066400000000000000000000153311175204346300161650ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Python side of the testing protocol. if PYTHON3: import os, signal, subprocess else: __metaclass__ = type import os try: import signal except ImportError: # Jython misses this module. signal = None try: import subprocess except ImportError: # Jython misses this module. subprocess = None # Make sure that ../Pymacs will be found within this process. import sys if '..' not in sys.path: sys.path.insert(0, '..') import Pymacs lisp = Pymacs.lisp class Launch: # Make sure that ../Pymacs will be found in external processes. def __init__(self): self.pythonpath_saved = os.environ.get('PYTHONPATH') os.environ['PYTHONPATH'] = '..' def __del__(self): if self.pythonpath_saved is None: del os.environ['PYTHONPATH'] else: os.environ['PYTHONPATH'] = self.pythonpath_saved class Emacs(Launch): # Requests towards Emacs are written to file "_request", while # replies from Emacs are read from file "_reply". We call Emacs # attention by erasing "_reply", and Emacs calls our attention by # erasing "_request". These rules guarantee that no file is ever # read by one side before it has been fully written by the other. # Busy waiting, with built-in delays, is used on both sides. popen = None def __init__(self): Launch.__init__(self) self.cleanup() import atexit atexit.register(self.cleanup) emacs = os.environ.get('EMACS') or 'emacs' self.command = emacs, '-batch', '--no-site', '-q', '-l', 'setup.el' if subprocess is None: self.command = self.command + ('-f', 'run-one-request') else: self.command = self.command + ('-f', 'run-all-requests') def cleanup(self): if self.popen is not None: self.popen.poll() if self.popen.returncode is None: if signal is not None: os.kill(self.popen.pid, signal.SIGINT) os.waitpid(self.popen.pid, 0) self.popen = None if os.path.exists('_request'): os.remove('_request') if os.path.exists('_reply'): os.remove('_reply') def receive(self): if subprocess is None: handle = open('_reply') buffer = handle.read() handle.close() else: import time while os.path.exists('_request'): self.popen.poll() assert self.popen.returncode is None, self.popen.returncode time.sleep(0.005) self.popen.poll() assert self.popen.returncode is None, self.popen.returncode if PYTHON3: handle = open('_reply', newline='') else: handle = open('_reply') buffer = handle.read() handle.close() return buffer def send(self, text): if PYTHON3: handle = open('_request', 'w', newline='') else: handle = open('_request', 'w') handle.write(text) handle.close() if subprocess is None: status = os.system(' '.join(self.command)) assert status == 0, status else: if os.path.exists('_reply'): os.remove('_reply') if self.popen is None: self.popen = subprocess.Popen(self.command) self.popen.poll() assert self.popen.returncode is None, self.popen.returncode def start_emacs(): Emacs.services = Emacs() def stop_emacs(): Emacs.services.cleanup() def ask_emacs(text, printer=None): if printer is not None: text = '(%s %s)' % (printer, text) Emacs.services.send(text) return Emacs.services.receive() class Python(Launch): def __init__(self): Launch.__init__(self) # Start a Pymacs helper subprocess for executing Python code. import subprocess self.process = subprocess.Popen( [os.environ.get('PYTHON') or 'python', '-c', 'from Pymacs import main; main(\'..\')'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) text = self.receive() assert text == '(version "@VERSION@")\n', repr(text) if PYTHON3: def receive(self): # Receive a Lisp expression from the Pymacs helper. stdout = self.process.stdout data = stdout.read(3) if not data or data[0] != ord(b'<'): if data == b'Tra': # Likely a traceback, and the Pymacs helper terminated. diagnostic = ( 'got:\n' + (data + stdout.read()).decode('UTF-8')) else: diagnostic = 'got ' + repr(data) raise Pymacs.ProtocolError("'<' expected, %s\n" % diagnostic) while data[-1] != ord('\t'): data += stdout.read(1) return str(stdout.read(int(data[1:-1])), 'UTF-8') else: def receive(self): # Receive a Lisp expression from the Pymacs helper. stdout = self.process.stdout data = stdout.read(3) if not data or data[0] != '<': if data == 'Tra': # Likely a traceback, and the Pymacs helper terminated. diagnostic = 'got:\n' + data + stdout.read() else: diagnostic = 'got ' + repr(data) raise Pymacs.ProtocolError("'<' expected, %s\n" % diagnostic) while data[-1] != '\t': data += stdout.read(1) return stdout.read(int(data[1:-1])) if PYTHON3: def send(self, text): # Send TEXT, a Python expression, to the Pymacs helper. stdin = self.process.stdin data = text.encode('UTF-8') if text[-1] == '\n': stdin.write(('>%d\t' % len(data)).encode('ASCII')) stdin.write(data) else: stdin.write(('>%d\t' % (len(data) + 1)).encode('ASCII')) stdin.write(data) stdin.write(b'\n') stdin.flush() else: def send(self, text): # Send TEXT, a Python expression, to the Pymacs helper. stdin = self.process.stdin if text[-1] == '\n': stdin.write('>%d\t%s' % (len(text), text)) else: stdin.write('>%d\t%s\n' % (len(text) + 1, text)) stdin.flush() def start_python(): Python.services = Python() def stop_python(): Python.services.process.kill() def ask_python(text): Python.services.send(text) return Python.services.receive() Pymacs-0.25/tests/t01_pppp_works.py000066400000000000000000000543771175204346300173250ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Checking if the Poor's Python Pre-Processor works. exec(compile(open('../pppp').read(), '../pppp', 'exec')) def setup_module(module): run.synclines = False run.context = {'TRUE': True, 'FALSE': False} def validate(input, expected): def validate1(input, expected): fragments = [] run.transform_file('pppp.py', input.splitlines(True), fragments.append) output = ''.join(fragments) assert output == expected, (output, expected) validate1(input, expected) prefix = ' ' * run.indent validate1( ''.join([prefix + line for line in input.splitlines(True)]), ''.join([prefix + line for line in expected.splitlines(True)])) def test_none(): yield (validate, '', '') yield (validate, 'line1\n', 'line1\n') yield (validate, 'line1\n' 'line2\n', 'line1\n' 'line2\n') def test_yes(): yield (validate, 'if TRUE:\n' ' line1\n' 'line2\n' 'line3\n', 'line1\n' 'line2\n' 'line3\n') yield (validate, 'if TRUE:\n' ' line1\n' ' line2\n' 'line3\n', 'line1\n' 'line2\n' 'line3\n') yield (validate, 'if TRUE:\n' ' line1\n' ' line2\n' ' line3\n', 'line1\n' 'line2\n' 'line3\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'line3\n', 'line1\n' 'line2\n' 'line3\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' ' line3\n', 'line1\n' 'line2\n' 'line3\n') yield (validate, 'line1\n' 'line2\n' 'if TRUE:\n' ' line3\n', 'line1\n' 'line2\n' 'line3\n') yield (validate, 'if TRUE:\n' ' line1\n' 'if TRUE:\n' ' line2\n' 'line3\n', 'line1\n' 'line2\n' 'line3\n') yield (validate, 'if TRUE:\n' ' line1\n' 'line2\n' 'if TRUE:\n' ' line3\n', 'line1\n' 'line2\n' 'line3\n') yield (validate, 'if TRUE:\n' ' line1\n' 'if TRUE:\n' ' line2\n' 'if TRUE:\n' ' line3\n', 'line1\n' 'line2\n' 'line3\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'if TRUE:\n' ' line3\n', 'line1\n' 'line2\n' 'line3\n') def test_no(): yield (validate, 'if FALSE:\n' ' line1\n' 'line2\n' 'line3\n', 'line2\n' 'line3\n') yield (validate, 'if FALSE:\n' ' line1\n' ' line2\n' 'line3\n', 'line3\n') yield (validate, 'if FALSE:\n' ' line1\n' ' line2\n' ' line3\n', '') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'line3\n', 'line1\n' 'line3\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' ' line3\n', 'line1\n') yield (validate, 'line1\n' 'line2\n' 'if FALSE:\n' ' line3\n', 'line1\n' 'line2\n') yield (validate, 'if FALSE:\n' ' line1\n' 'if FALSE:\n' ' line2\n' 'line3\n', 'line3\n') yield (validate, 'if FALSE:\n' ' line1\n' 'line2\n' 'if FALSE:\n' ' line3\n', 'line2\n') yield (validate, 'if FALSE:\n' ' line1\n' 'if FALSE:\n' ' line2\n' 'if FALSE:\n' ' line3\n', '') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'if FALSE:\n' ' line3\n', 'line1\n') def test_unknown(): yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'line3\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'line3\n') yield (validate, 'if UNKNOWN:\n' ' line1\n' 'if UNKNOWN:\n' ' line2\n' 'if UNKNOWN:\n' ' line3\n', 'if UNKNOWN:\n' ' line1\n' 'if UNKNOWN:\n' ' line2\n' 'if UNKNOWN:\n' ' line3\n') def test_yes_else(): yield (validate, 'if TRUE:\n' ' line1\n' 'else:\n' ' line2\n' 'line3\n', 'line1\n' 'line3\n') yield (validate, 'if TRUE:\n' ' line1\n' ' line2\n' 'else:\n' ' line3\n', 'line1\n' 'line2\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'else:\n' ' line3\n', 'line1\n' 'line2\n') yield (validate, 'if TRUE:\n' ' line1\n' 'if TRUE:\n' ' line2\n' 'else:\n' ' line3\n', 'line1\n' 'line2\n') yield (validate, 'if TRUE:\n' ' line1\n' 'else:\n' ' line2\n' 'if TRUE:\n' ' line3\n', 'line1\n' 'line3\n') def test_no_else(): yield (validate, 'if FALSE:\n' ' line1\n' 'else:\n' ' line2\n' 'line3\n', 'line2\n' 'line3\n') yield (validate, 'if FALSE:\n' ' line1\n' ' line2\n' 'else:\n' ' line3\n', 'line3\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'else:\n' ' line3\n', 'line1\n' 'line3\n') yield (validate, 'if FALSE:\n' ' line1\n' 'if FALSE:\n' ' line2\n' 'else:\n' ' line3\n', 'line3\n') yield (validate, 'if FALSE:\n' ' line1\n' 'else:\n' ' line2\n' 'if FALSE:\n' ' line3\n', 'line2\n') def test_unknown_else(): yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'else:\n' ' line3\n' 'line4\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'else:\n' ' line3\n' 'line4\n') def test_elif(): yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'elif TRUE:\n' ' line3\n' 'elif TRUE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line2\n' 'line6\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'elif TRUE:\n' ' line3\n' 'elif FALSE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line2\n' 'line6\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'elif TRUE:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line2\n' 'line6\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'elif FALSE:\n' ' line3\n' 'elif TRUE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line2\n' 'line6\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'elif FALSE:\n' ' line3\n' 'elif FALSE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line2\n' 'line6\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'elif FALSE:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line2\n' 'line6\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif TRUE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line2\n' 'line6\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif FALSE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line2\n' 'line6\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line2\n' 'line6\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'elif TRUE:\n' ' line3\n' 'elif TRUE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line3\n' 'line6\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'elif TRUE:\n' ' line3\n' 'elif FALSE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line3\n' 'line6\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'elif TRUE:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line3\n' 'line6\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'elif FALSE:\n' ' line3\n' 'elif TRUE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line4\n' 'line6\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'elif FALSE:\n' ' line3\n' 'elif FALSE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'line5\n' 'line6\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'elif FALSE:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif TRUE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line3\n' 'else:\n' ' line4\n' 'line6\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif FALSE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line3\n' 'else:\n' ' line5\n' 'line6\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif TRUE:\n' ' line3\n' 'elif TRUE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'else:\n' ' line3\n' 'line6\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif TRUE:\n' ' line3\n' 'elif FALSE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'else:\n' ' line3\n' 'line6\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif TRUE:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'else:\n' ' line3\n' 'line6\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif FALSE:\n' ' line3\n' 'elif TRUE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'else:\n' ' line4\n' 'line6\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif FALSE:\n' ' line3\n' 'elif FALSE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'else:\n' ' line5\n' 'line6\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif FALSE:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif TRUE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'else:\n' ' line4\n' 'line6\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif FALSE:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'else:\n' ' line5\n' 'line6\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' 'elif UNKNOWN:\n' ' line3\n' 'elif UNKNOWN:\n' ' line4\n' 'else:\n' ' line5\n' 'line6\n') def test_nesting(): yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' ' if TRUE:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n', 'line1\n' 'line2\n' 'line3\n' 'line5\n' 'line7\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' ' if FALSE:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n', 'line1\n' 'line2\n' 'line4\n' 'line5\n' 'line7\n') yield (validate, 'line1\n' 'if TRUE:\n' ' line2\n' ' if UNKNOWN:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n', 'line1\n' 'line2\n' 'if UNKNOWN:\n' ' line3\n' 'else:\n' ' line4\n' 'line5\n' 'line7\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' ' if TRUE:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n', 'line1\n' 'line6\n' 'line7\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' ' if FALSE:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n', 'line1\n' 'line6\n' 'line7\n') yield (validate, 'line1\n' 'if FALSE:\n' ' line2\n' ' if UNKNOWN:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n', 'line1\n' 'line6\n' 'line7\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' ' if TRUE:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' ' line3\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' ' if FALSE:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n') yield (validate, 'line1\n' 'if UNKNOWN:\n' ' line2\n' ' if UNKNOWN:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n', 'line1\n' 'if UNKNOWN:\n' ' line2\n' ' if UNKNOWN:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'else:\n' ' line6\n' 'line7\n') def test_regression(): yield (validate, 'if TRUE:\n' ' line1\n' 'else:\n' ' line2\n' ' if FALSE:\n' ' line3\n' ' else:\n' ' line4\n' ' line5\n' 'line6\n', 'line1\n' 'line6\n') Pymacs-0.25/tests/t10_pyfile_loads.py000066400000000000000000000001301175204346300175450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Checking if Pymacs.py loads. def test_1(): import setup Pymacs-0.25/tests/t11_pyfile_works.py.in000066400000000000000000000065071175204346300202340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Checking if Pymacs.py works (Emacs and the Pymacs helper are not used). import re import setup import Pymacs lisp = Pymacs.lisp def test_print_lisp(): def validate(input, quoted, expected): fragments = [] Pymacs.print_lisp(input, fragments.append, quoted) output = re.sub(r'\(pymacs-(defun|python) [0-9]*', r'(pymacs-\1 0', ''.join(fragments)) assert output == expected, (output, expected) if isinstance(bool, type): false_string = 'nil' true_string = 't' else: false_string = '0' true_string = '1' tests = [] tests += [(False, None, 'nil'), (False, False, false_string), (False, True, true_string), (False, 3, '3'), (False, 0, '0'), (False, -3, '-3'), (False, 3., '3.0'), (False, 0., '0.0'), (False, -3., '-3.0'), (False, '', '""'), (False, 'a', '"a"'), (False, 'byz', '"byz"'), (False, 'c\'bz', '"c\'bz"'), (False, 'd"z', r'"d\"z"'), (False, 'e\\bz', r'"e\\bz"'), (False, 'f\bz', r'"f\bz"'), (False, 'g\fz', r'"g\fz"'), (False, 'h\nz', r'"h\nz"'), (False, 'i\rz', r'"i\rz"'), (False, 'j\r\nz', r'"j\r\nz"'), (False, 'k\tz', r'"k\tz"'), (False, 'l\x1bz', r'"l\033z"')] if PYTHON3: tests += [(False, 'p', '"p"'), (False, 'qyz', '"qyz"'), (False, 'rêvé', (r'(decode-coding-string "r\303\252v\303\251"' ' \'utf-8)')), (False, 's—z!', (r'(decode-coding-string "s\342\200\224z!"' ' \'utf-8)'))] else: tests += [(False, u'p', '"p"'), (False, u'qyz', '"qyz"'), (False, u'rêvé', (r'(decode-coding-string "r\303\252v\303\251"' ' \'utf-8)')), (False, u's—z!', (r'(decode-coding-string "s\342\200\224z!"' ' \'utf-8)'))] tests += [(False, (), '[]'), (False, (0,), '[0]'), (False, (0.0,), '[0.0]'), (False, ('a',), '["a"]'), (False, (0, 0.0, "a"), '[0 0.0 "a"]'), (True, [], 'nil'), (True, [0], '(0)'), (True, [0.0], '(0.0)'), (True, ['a'], '("a")'), (True, [0, 0.0, "a"], '(0 0.0 "a")'), (False, lisp['nil'], 'nil'), (True, lisp['t'], 't'), (True, lisp['ab_cd'], 'ab_cd'), (True, lisp['ab-cd'], 'ab-cd'), (True, lisp['lambda'], 'lambda'), (False, lisp.nil, 'nil'), (True, lisp.t, 't'), (True, lisp.ab_cd, 'ab-cd')] # TODO: Lisp and derivatives tests += [(False, ord, '(pymacs-defun 0 nil)'), (False, object(), '(pymacs-python 0)')] for quotable, input, output in tests: yield validate, input, False, output if quotable: yield validate, input, True, '\'' + output else: yield validate, input, True, output Pymacs-0.25/tests/t20_helper_loads.py000066400000000000000000000002161175204346300175420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Checking if the Pymacs helper loads. import setup def test_1(): setup.start_python() setup.stop_python() Pymacs-0.25/tests/t21_helper_works.py.in000066400000000000000000000067331175204346300202250ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Checking if the Pymacs helper works. import re import setup from Pymacs import lisp def setup_module(module): setup.start_python() def teardown_module(module): setup.stop_python() def test_1(): def validate(input, expected): output = re.sub(r'\(pymacs-(defun|python) [0-9]*', r'(pymacs-\1 0', setup.ask_python('eval ' + input)) assert output == expected, (output, expected) if isinstance(bool, type): false_string = 'nil' true_string = 't' else: false_string = '0' true_string = '1' tests = [] tests += [(False, None, 'nil'), (False, False, false_string), (False, True, true_string), (False, 3, '3'), (False, 0, '0'), (False, -3, '-3'), (False, 3., '3.0'), (False, 0., '0.0'), (False, -3., '-3.0'), (False, '', '""'), (False, 'a', '"a"'), (False, 'byz', '"byz"'), (False, 'c\'bz', '"c\'bz"'), (False, 'd"z', r'"d\"z"'), (False, 'e\\bz', r'"e\\bz"'), (False, 'f\bz', r'"f\bz"'), (False, 'g\fz', r'"g\fz"'), (False, 'h\nz', r'"h\nz"'), (False, 'i\rz', r'"i\rz"'), (False, 'j\r\nz', r'"j\r\nz"'), (False, 'k\tz', r'"k\tz"'), (False, 'l\x1bz', r'"l\033z"')] if PYTHON3: tests += [(False, 'p', '"p"'), (False, 'qyz', '"qyz"'), (False, 'rêvé', (r'(decode-coding-string "r\303\252v\303\251"' ' \'utf-8)')), (False, 's—z!', (r'(decode-coding-string "s\342\200\224z!"' ' \'utf-8)'))] else: tests += [(False, u'p', '"p"'), (False, u'qyz', '"qyz"'), (False, u'rêvé', (r'(decode-coding-string "r\303\252v\303\251"' ' \'utf-8)')), (False, u's—z!', (r'(decode-coding-string "s\342\200\224z!"' ' \'utf-8)'))] tests += [(False, (), '[]'), (False, (0,), '[0]'), (False, (0.0,), '[0.0]'), (False, ('a',), '["a"]'), (False, (0, 0.0, "a"), '[0 0.0 "a"]'), (True, [], 'nil'), (True, [0], '(0)'), (True, [0.0], '(0.0)'), (True, ['a'], '("a")'), (True, [0, 0.0, "a"], '(0 0.0 "a")'), (False, lisp['nil'], 'nil'), (True, lisp['t'], 't'), (True, lisp['ab_cd'], 'ab_cd'), (True, lisp['ab-cd'], 'ab-cd'), (True, lisp['lambda'], 'lambda'), (False, lisp.nil, 'nil'), (True, lisp.t, 't'), (True, lisp.ab_cd, 'ab-cd')] # TODO: Lisp and derivatives for quotable, input, output in tests: if quotable: yield validate, repr(input), '(return \'%s)\n' % output else: yield validate, repr(input), '(return %s)\n' % output tests = [('ord', '(pymacs-defun 0 nil)'), ('object()', '(pymacs-python 0)')] for input, output in tests: yield validate, input, '(return %s)\n' % output def test_2(): value = setup.ask_python('eval 3 + 5\n') assert value == '(return 8)\n', repr(value) Pymacs-0.25/tests/t30_elfile_loads.py000066400000000000000000000002041175204346300175210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Checking if pymacs.el loads. import setup def test_1(): setup.start_emacs() setup.stop_emacs() Pymacs-0.25/tests/t31_elfile_works.py.in000066400000000000000000000131531175204346300202010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Checking if pymacs.el works (the Pymacs helper is not used). import re import setup import Pymacs lisp = Pymacs.lisp def setup_module(module): setup.start_emacs() def teardown_module(module): setup.stop_emacs() def test_1(): def validate(input, expected): output = setup.ask_emacs(input, 'prin1') output = re.sub(r'\(pymacs-(defun|python) [0-9]*', r'(pymacs-\1 0', output) assert output == expected, (output, expected) if isinstance(bool, type): false_string = 'nil' true_string = 't' else: false_string = '0' true_string = '1' tests = [] tests += [(False, None, 'nil'), (False, False, false_string), (False, True, true_string), (False, 3, '3'), (False, 0, '0'), (False, -3, '-3'), (False, 3., '3.0'), (False, 0., '0.0'), (False, -3., '-3.0'), (False, '', '""'), (False, 'a', '"a"'), (False, 'byz', '"byz"'), (False, 'c\'bz', '"c\'bz"'), (False, 'd"z', r'"d\"z"'), (False, 'e\\bz', r'"e\\bz"'), (False, 'f\bz', '"f\bz"'), (False, 'g\fz', '"g\fz"'), (False, 'h\nz', '"h\nz"'), (False, 'i\rz', '"i\rz"'), (False, 'j\r\nz', '"j\r\nz"'), (False, 'k\tz', '"k\tz"'), (False, 'l\x1bz', '"l\x1bz"')] if PYTHON3: tests += [(False, 'p', '"p"'), (False, 'qyz', '"qyz"'), (False, 'rêvé', '"rêvé"'), (False, 's—z!', '"s—z!"')] else: tests += [(False, u'p', '"p"'), (False, u'qyz', '"qyz"'), (False, u'rêvé', '"r\303\252v\303\251"'), (False, u's—z!', '"s\342\200\224z!"')] tests += [(False, (), '[]'), (False, (0,), '[0]'), (False, (0.0,), '[0.0]'), (False, ('a',), '["a"]'), (False, (0, 0.0, "a"), '[0 0.0 "a"]'), (True, [], 'nil'), (True, [0], '(0)'), (True, [0.0], '(0.0)'), (True, ['a'], '("a")'), (True, [0, 0.0, "a"], '(0 0.0 "a")'), (False, lisp['nil'], 'nil'), (True, lisp['t'], 't'), (True, lisp['ab_cd'], 'ab_cd'), (True, lisp['ab-cd'], 'ab-cd'), (True, lisp['lambda'], 'lambda'), (False, lisp.nil, 'nil'), (True, lisp.t, 't'), (True, lisp.ab_cd, 'ab-cd')] # TODO: Lisp and derivatives for quotable, input, output in tests: fragments = [] Pymacs.print_lisp(input, fragments.append, quotable) yield validate, ''.join(fragments), output tests = [(ord, '(pymacs-defun 0 nil)'), (object(), '(pymacs-python 0)')] for input, output in tests: fragments = [] Pymacs.print_lisp(input, fragments.append, True) yield validate, '\'' + ''.join(fragments), output def test_2(): def validate(input, expected): output = setup.ask_emacs( input, '(lambda (expression)\n' ' (let ((pymacs-forget-mutability t))\n' ' (pymacs-print-for-eval expression)))\n') output = re.sub(r'\(pymacs-(defun|python) [0-9]*', r'(pymacs-\1 0', output) assert output == expected, (output, expected) tests = [] tests += [(False, None, 'None'), (False, False, 'None'), (False, True, 'True'), (False, 3, '3'), (False, 0, '0'), (False, -3, '-3'), (False, 3., '3.0'), (False, 0., '0.0'), (False, -3., '-3.0'), (False, '', '""'), (False, 'a', '"a"'), (False, 'byz', '"byz"'), (False, 'c\'bz', '"c\'bz"'), (False, 'd"z', r'"d\"z"'), (False, 'e\\bz', r'"e\\bz"'), (False, 'f\bz', '"f\x08z"'), (False, 'g\fz', '"g\x0cz"'), (False, 'h\nz', r'"h\nz"'), (False, 'i\rz', '"i\rz"'), (False, 'j\r\nz', '"j\r\\nz"'), (False, 'k\tz', '"k\tz"'), (False, 'l\x1bz', '"l\x1bz"'), (False, (), '()'), (False, (0,), '(0,)'), (False, (0.0,), '(0.0,)'), (False, ('a',), '("a",)'), (False, (0, 0.0, "a"), '(0, 0.0, "a")'), (True, [], 'None'), (True, [0], '[0]'), (True, [0.0], '[0.0]'), (True, ['a'], '["a"]'), (True, [0, 0.0, "a"], '[0, 0.0, "a"]'), (False, lisp['nil'], 'None'), (True, lisp['t'], 'True'), (True, lisp['ab_cd'], 'lisp["ab_cd"]'), (True, lisp['ab-cd'], 'lisp["ab-cd"]'), (True, lisp['lambda'], 'lisp["lambda"]'), (False, lisp.nil, 'None'), (True, lisp.t, 'True'), (True, lisp.ab_cd, 'lisp["ab-cd"]')] # TODO: Lisp and derivatives for quotable, input, output in tests: fragments = [] Pymacs.print_lisp(input, fragments.append, quotable) yield validate, ''.join(fragments), output #for input, output in ( # (ord, '(pymacs-defun 0 nil)'), # (object(), '(pymacs-python 0)'), # ): # fragments = [] # Pymacs.print_lisp(input, fragments.append, True) # yield validate, '\'' + ''.join(fragments), output Pymacs-0.25/tests/t40_pymacs_loads.py000066400000000000000000000006101175204346300175570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Checking if Emacs loads the Python helper. import setup def test_1(): setup.start_emacs() output = setup.ask_emacs(('(progn\n' ' (pymacs-start-services)\n' ' (not (null pymacs-transit-buffer)))\n'), 'prin1') assert output == 't', output setup.stop_emacs() Pymacs-0.25/tests/t41_pymacs_works.py.in000066400000000000000000000077661175204346300202530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Checking if the whole Pymacs works together. import re import setup import Pymacs lisp = Pymacs.lisp def setup_module(module): setup.start_emacs() def teardown_module(module): setup.stop_emacs() def test_1(): def validate(input, expected): output = re.sub(r'\(pymacs-(defun|python) \. [0-9]*\)', r'(pymacs-\1 . 0)', setup.ask_emacs('(pymacs-eval %s)' % input, 'prin1')) assert output == expected, (output, expected) tests = [] tests += [(False, None, 'nil'), (False, False, 'nil'), (False, True, 't'), (False, 3, '3'), (False, 0, '0'), (False, -3, '-3'), (False, 3., '3.0'), (False, 0., '0.0'), (False, -3., '-3.0'), (False, '', '""'), (False, 'a', '"a"'), (False, 'byz', '"byz"'), (False, 'c\'bz', '"c\'bz"'), (False, 'd"z', r'"d\"z"'), (False, 'e\\bz', r'"e\\bz"'), (False, 'f\bz', '"f\bz"'), (False, 'g\fz', '"g\fz"'), (False, 'h\nz', '"h\nz"'), (False, 'i\rz', '"i\rz"'), (False, 'j\r\nz', '"j\r\nz"'), (False, 'k\tz', '"k\tz"'), (False, 'l\x1bz', '"l\x1bz"')] if PYTHON3: tests += [(False, 'p', '"p"'), (False, 'qyz', '"qyz"'), (False, 'rêvé', '"rêvé"'), (False, 's—z!', '"s—z!"')] else: tests += [(False, u'p', '"p"'), (False, u'qyz', '"qyz"'), (False, u'rêvé', '"r\303\252v\303\251"'), (False, u's—z!', '"s\342\200\224z!"')] tests += [(False, (), '[]'), (False, (0,), '[0]'), (False, (0.0,), '[0.0]'), (False, ('a',), '["a"]'), (False, (0, 0.0, "a"), '[0 0.0 "a"]'), (True, [], 'nil'), (True, [0], '(0)'), (True, [0.0], '(0.0)'), (True, ['a'], '("a")'), (True, [0, 0.0, "a"], '(0 0.0 "a")'), (False, lisp['nil'], 'nil'), (True, lisp['t'], 't'), (True, lisp['ab_cd'], 'ab_cd'), (True, lisp['ab-cd'], 'ab-cd'), (True, lisp['lambda'], 'lambda'), (False, lisp.nil, 'nil'), (True, lisp.t, 't'), (True, lisp.ab_cd, 'ab-cd')] # TODO: Lisp and derivatives for quotable, input, output in tests: fragments = [] if PYTHON3: Pymacs.print_lisp(ascii(input), fragments.append, quotable) else: Pymacs.print_lisp(repr(input), fragments.append, quotable) yield validate, ''.join(fragments), output tests = [('ord', ('(lambda (&rest arguments)' ' (pymacs-apply (quote (pymacs-python . 0))' ' arguments))')), ('object()', '(pymacs-python . 0)')] for input, output in tests: fragments = [] Pymacs.print_lisp(input, fragments.append, True) yield validate, '\'' + ''.join(fragments), output def test_2(): def validate(input, expected): output = setup.ask_emacs(input, 'prin1') assert output == expected, (output, expected) yield validate, '(pymacs-eval "3 + 5")', '8' yield (validate, ('(progn (pymacs-exec "def f(): pass")\n' ' (pymacs-eval "lisp.apply(f, None)"))\n'), 'nil') if PYTHON3: yield (validate, '(pymacs-eval "lisp(\'(pymacs-eval \\"repr(2**111)\\")\')")', '"2596148429267413814265248164610048"') else: yield (validate, '(pymacs-eval "lisp(\'(pymacs-eval \\"repr(2L**111)\\")\')")', '"2596148429267413814265248164610048L"') def test_3(): setup.ask_emacs('(pymacs-exec "import os\nimport sys")') def test_4(): # Try ``list.buffer_string()`` in a multi-byte buffer. pass