home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pyth_os2.zip / python-1.0.2 / Demo / stdwin / python.py < prev    next >
Text File  |  1994-01-26  |  12KB  |  448 lines

  1. #! /usr/local/bin/python
  2.  
  3. # A STDWIN-based front end for the Python interpreter.
  4. #
  5. # This is useful if you want to avoid console I/O and instead
  6. # use text windows to issue commands to the interpreter.
  7. #
  8. # It supports multiple interpreter windows, each with its own context.
  9. #
  10. # BUGS AND CAVEATS:
  11. #
  12. # This was written long ago as a demonstration, and slightly hacked to
  13. # keep it up-to-date, but never as an industry-strength alternative
  14. # interface to Python.  It should be rewritten using more classes, and
  15. # merged with something like wdb.
  16. #
  17. # Although this supports multiple windows, the whole application
  18. # is deaf and dumb when a command is running in one window.
  19. #
  20. # Interrupt is (ab)used to signal EOF on input requests.
  21. #
  22. # On UNIX (using X11), interrupts typed in the window will not be
  23. # seen until the next input or output operation.  When you are stuck
  24. # in an infinite loop, try typing ^C in the shell window where you
  25. # started this interpreter.  (On the Mac, interrupts work normally.)
  26.  
  27.  
  28. import sys
  29. import stdwin
  30. from stdwinevents import *
  31. import rand
  32. import mainloop
  33. import os
  34.  
  35.  
  36. # Stack of windows waiting for [raw_]input().
  37. # Element [0] is the top.
  38. # If there are multiple windows waiting for input, only the
  39. # one on top of the stack can accept input, because the way
  40. # raw_input() is implemented (using recursive mainloop() calls).
  41. #
  42. inputwindows = []
  43.  
  44.  
  45. # Exception raised when input is available
  46. #
  47. InputAvailable = 'input available for raw_input (not an error)'
  48.  
  49.  
  50. # Main program -- create the window and call the mainloop
  51. #
  52. def main():
  53.     # Hack so 'import python' won't load another copy
  54.     # of this if we were loaded though 'python python.py'.
  55.     # (Should really look at sys.argv[0]...)
  56.     if 'inputwindows' in dir(sys.modules['__main__']) and \
  57.             sys.modules['__main__'].inputwindows is inputwindows:
  58.         sys.modules['python'] = sys.modules['__main__']
  59.     #
  60.     win = makewindow()
  61.     mainloop.mainloop()
  62.  
  63.  
  64. # Create a new window
  65. #
  66. def makewindow():
  67.     # stdwin.setdefscrollbars(0, 1) # Not in Python 0.9.1
  68.     # stdwin.setfont('monaco') # Not on UNIX! and not Python 0.9.1
  69.     # width, height = stdwin.textwidth('in')*40, stdwin.lineheight()*24
  70.     # stdwin.setdefwinsize(width, height)
  71.     win = stdwin.open('Python interpreter ready')
  72.     win.editor = win.textcreate((0,0), win.getwinsize())
  73.     win.globals = {}        # Dictionary for user's globals
  74.     win.command = ''        # Partially read command
  75.     win.busy = 0            # Ready to accept a command
  76.     win.auto = 1            # [CR] executes command
  77.     win.insertOutput = 1        # Insert output at focus
  78.     win.insertError = 1        # Insert error output at focus
  79.     win.setwincursor('ibeam')
  80.     win.filename = ''        # Empty if no file for this window
  81.     makefilemenu(win)
  82.     makeeditmenu(win)
  83.     win.dispatch = pdispatch    # Event dispatch function
  84.     mainloop.register(win)
  85.     return win
  86.  
  87.  
  88. # Make a 'File' menu
  89. #
  90. def makefilemenu(win):
  91.     win.filemenu = mp = win.menucreate('File')
  92.     mp.callback = []
  93.     additem(mp, 'New', 'N', do_new)
  94.     additem(mp, 'Open...', 'O', do_open)
  95.     additem(mp, '', '',  None)
  96.     additem(mp, 'Close', 'W', do_close)
  97.     additem(mp, 'Save', 'S', do_save)
  98.     additem(mp, 'Save as...', '', do_saveas)
  99.     additem(mp, '', '', None)
  100.     additem(mp, 'Quit', 'Q', do_quit)
  101.  
  102.  
  103. # Make an 'Edit' menu
  104. #
  105. def makeeditmenu(win):
  106.     win.editmenu = mp = win.menucreate('Edit')
  107.     mp.callback = []
  108.     additem(mp, 'Cut', 'X', do_cut)
  109.     additem(mp, 'Copy', 'C', do_copy)
  110.     additem(mp, 'Paste', 'V', do_paste)
  111.     additem(mp, 'Clear', '',  do_clear)
  112.     additem(mp, '', '', None)
  113.     win.iauto = len(mp.callback)
  114.     additem(mp, 'Autoexecute', '', do_auto)
  115.     mp.check(win.iauto, win.auto)
  116.     win.insertOutputNum = len(mp.callback)
  117.     additem(mp, 'Insert Output', '', do_insertOutputOption)
  118.     win.insertErrorNum = len(mp.callback)
  119.     additem(mp, 'Insert Error', '', do_insertErrorOption)
  120.     additem(mp, 'Exec', '\r', do_exec)
  121.  
  122.  
  123. # Helper to add a menu item and callback function
  124. #
  125. def additem(mp, text, shortcut, handler):
  126.     if shortcut:
  127.         mp.additem(text, shortcut)
  128.     else:
  129.         mp.additem(text)
  130.     mp.callback.append(handler)
  131.  
  132.  
  133. # Dispatch a single event to the interpreter.
  134. # Resize events cause a resize of the editor.
  135. # Some events are treated specially.
  136. # Most other events are passed directly to the editor.
  137. #
  138. def pdispatch(event):
  139.     type, win, detail = event
  140.     if not win:
  141.         win = stdwin.getactive()
  142.         if not win: return
  143.     if type == WE_CLOSE:
  144.         do_close(win)
  145.         return
  146.     elif type == WE_SIZE:
  147.         win.editor.move((0, 0), win.getwinsize())
  148.     elif type == WE_COMMAND and detail == WC_RETURN:
  149.         if win.auto:
  150.             do_exec(win)
  151.         else:
  152.             void = win.editor.event(event)
  153.     elif type == WE_COMMAND and detail == WC_CANCEL:
  154.         if win.busy:
  155.             raise KeyboardInterrupt
  156.         else:
  157.             win.command = ''
  158.             settitle(win)
  159.     elif type == WE_MENU:
  160.         mp, item = detail
  161.         mp.callback[item](win)
  162.     else:
  163.         void = win.editor.event(event)
  164.     if win in mainloop.windows:
  165.         # May have been deleted by close...
  166.         win.setdocsize(0, win.editor.getrect()[1][1])
  167.         if type in (WE_CHAR, WE_COMMAND):
  168.             win.editor.setfocus(win.editor.getfocus())
  169.  
  170.  
  171. # Helper to set the title of the window
  172. #
  173. def settitle(win):
  174.     if win.filename == '':
  175.         win.settitle('Python interpreter ready')
  176.     else:
  177.         win.settitle(win.filename)
  178.  
  179.  
  180. # Helper to replace the text of the focus
  181. #
  182. def replace(win, text):
  183.     win.editor.replace(text)
  184.     # Resize the window to display the text
  185.     win.setdocsize(0, win.editor.getrect()[1][1]) # update the size before
  186.     win.editor.setfocus(win.editor.getfocus()) # move focus to the change
  187.  
  188.  
  189. # File menu handlers
  190. #
  191. def do_new(win):
  192.     win = makewindow()
  193. #
  194. def do_open(win):
  195.     try:
  196.         filename = stdwin.askfile('Open file', '', 0)
  197.         win = makewindow()
  198.         win.filename = filename
  199.         win.editor.replace(open(filename, 'r').read())
  200.         win.editor.setfocus(0, 0)
  201.         win.settitle(win.filename)
  202.         #
  203.     except KeyboardInterrupt:
  204.         pass            # Don't give an error on cancel
  205. #
  206. def do_save(win):
  207.     try:
  208.         if win.filename == '':
  209.             win.filename = stdwin.askfile('Open file', '', 1)
  210.         f = open(win.filename, 'w')
  211.         f.write(win.editor.gettext())
  212.         #
  213.     except KeyboardInterrupt:
  214.         pass            # Don't give an error on cancel
  215.     
  216. def do_saveas(win):
  217.     currentFilename = win.filename
  218.     win.filename = ''
  219.     do_save(win)        # Use do_save with empty filename
  220.     if win.filename == '':    # Restore the name if do_save did not set it
  221.         win.filename = currentFilename
  222. #
  223. def do_close(win):
  224.     if win.busy:
  225.         stdwin.message('Can\'t close busy window')
  226.         return        # need to fail if quitting??
  227.     win.editor = None # Break circular reference
  228.     #del win.editmenu    # What about the filemenu??
  229.     mainloop.unregister(win)
  230.     win.close()
  231. #
  232. def do_quit(win):
  233.     # Call win.dispatch instead of do_close because there
  234.     # may be 'alien' windows in the list.
  235.     for win in mainloop.windows[:]:
  236.         mainloop.dispatch((WE_CLOSE, win, None))
  237.         # need to catch failed close
  238.  
  239.  
  240. # Edit menu handlers
  241. #
  242. def do_cut(win):
  243.     text = win.editor.getfocustext()
  244.     if not text:
  245.         stdwin.fleep()
  246.         return
  247.     stdwin.setcutbuffer(0, text)
  248.     replace(win, '')
  249. #
  250. def do_copy(win):
  251.     text = win.editor.getfocustext()
  252.     if not text:
  253.         stdwin.fleep()
  254.         return
  255.     stdwin.setcutbuffer(0, text)
  256. #
  257. def do_paste(win):
  258.     text = stdwin.getcutbuffer(0)
  259.     if not text:
  260.         stdwin.fleep()
  261.         return
  262.     replace(win, text)
  263. #
  264. def do_clear(win):
  265.     replace(win, '')
  266.  
  267.  
  268. # These would be better in a preferences dialog:
  269. #
  270. def do_auto(win):
  271.     win.auto = (not win.auto)
  272.     win.editmenu.check(win.iauto, win.auto)
  273. #
  274. def do_insertOutputOption(win):
  275.     win.insertOutput = (not win.insertOutput)
  276.     title = ['Append Output', 'Insert Output'][win.insertOutput]
  277.     win.editmenu.setitem(win.insertOutputNum, title)
  278. #
  279. def do_insertErrorOption(win):
  280.     win.insertError = (not win.insertError)
  281.     title = ['Error Dialog', 'Insert Error'][win.insertError]
  282.     win.editmenu.setitem(win.insertErrorNum, title)
  283.  
  284.  
  285. # Extract a command from the editor and execute it, or pass input to
  286. # an interpreter waiting for it.
  287. # Incomplete commands are merely placed in the window's command buffer.
  288. # All exceptions occurring during the execution are caught and reported.
  289. # (Tracebacks are currently not possible, as the interpreter does not
  290. # save the traceback pointer until it reaches its outermost level.)
  291. #
  292. def do_exec(win):
  293.     if win.busy:
  294.         if win not in inputwindows:
  295.             stdwin.message('Can\'t run recursive commands')
  296.             return
  297.         if win <> inputwindows[0]:
  298.             stdwin.message('Please complete recursive input first')
  299.             return
  300.     #
  301.     # Set text to the string to execute.
  302.     a, b = win.editor.getfocus()
  303.     alltext = win.editor.gettext()
  304.     n = len(alltext)
  305.     if a == b:
  306.         # There is no selected text, just an insert point;
  307.         # so execute the current line.
  308.         while 0 < a and alltext[a-1] <> '\n': # Find beginning of line
  309.             a = a-1
  310.         while b < n and alltext[b] <> '\n': # Find end of line after b
  311.             b = b+1
  312.         text = alltext[a:b] + '\n'
  313.     else:
  314.         # Execute exactly the selected text.
  315.         text = win.editor.getfocustext()
  316.         if text[-1:] <> '\n': # Make sure text ends with \n
  317.             text = text + '\n'
  318.         while b < n and alltext[b] <> '\n': # Find end of line after b
  319.             b = b+1
  320.     #
  321.     # Set the focus to expect the output, since there is always something.
  322.     # Output will be inserted at end of line after current focus,
  323.     # or appended to the end of the text.
  324.     b = [n, b][win.insertOutput]
  325.     win.editor.setfocus(b, b)
  326.     #
  327.     # Make sure there is a preceeding newline.
  328.     if alltext[b-1:b] <> '\n':
  329.         win.editor.replace('\n')
  330.     #
  331.     #
  332.     if win.busy:
  333.         # Send it to raw_input() below
  334.         raise InputAvailable, text
  335.     #
  336.     # Like the real Python interpreter, we want to execute
  337.     # single-line commands immediately, but save multi-line
  338.     # commands until they are terminated by a blank line.
  339.     # Unlike the real Python interpreter, we don't do any syntax
  340.     # checking while saving up parts of a multi-line command.
  341.     #
  342.     # The current heuristic to determine whether a command is
  343.     # the first line of a multi-line command simply checks whether
  344.     # the command ends in a colon (followed by a newline).
  345.     # This is not very robust (comments and continuations will
  346.     # confuse it), but it is usable, and simple to implement.
  347.     # (It even has the advantage that single-line loops etc.
  348.     # don't need te be terminated by a blank line.)
  349.     #
  350.     if win.command:
  351.         # Already continuing
  352.         win.command = win.command + text
  353.         if win.command[-2:] <> '\n\n':
  354.             win.settitle('Unfinished command...')
  355.             return # Need more...
  356.     else:
  357.         # New command
  358.         win.command = text
  359.         if text[-2:] == ':\n':
  360.             win.settitle('Unfinished command...')
  361.             return
  362.     command = win.command
  363.     win.command = ''
  364.     win.settitle('Executing command...')
  365.     #
  366.     # Some hacks:
  367.     # - The standard files are replaced by an IOWindow instance.
  368.     # - A 2nd argument to exec() is used to specify the directory
  369.     #   holding the user's global variables.  (If this wasn't done,
  370.     #   the exec would be executed in the current local environment,
  371.     #   and the user's assignments to globals would be lost...)
  372.     #
  373.     save_stdin = sys.stdin
  374.     save_stdout = sys.stdout
  375.     save_stderr = sys.stderr
  376.     try:
  377.         sys.stdin = sys.stdout = sys.stderr = IOWindow(win)
  378.         win.busy = 1
  379.         try:
  380.             exec(command, win.globals)
  381.         except KeyboardInterrupt:
  382.             print '[Interrupt]'
  383.         except:
  384.             msg = sys.exc_type
  385.             if sys.exc_value <> None:
  386.                 msg = msg + ': ' + `sys.exc_value`
  387.             if win.insertError:
  388.                 stdwin.fleep()
  389.                 replace(win, msg + '\n')
  390.             else:
  391.                 win.settitle('Unhandled exception')
  392.                 stdwin.message(msg)
  393.     finally:
  394.         # Restore redirected I/O in *all* cases
  395.         win.busy = 0
  396.         sys.stderr = save_stderr
  397.         sys.stdout = save_stdout
  398.         sys.stdin = save_stdin
  399.         settitle(win)
  400.  
  401.  
  402. # Class emulating file I/O from/to a window
  403. #
  404. class IOWindow:
  405.     #
  406.     def __init__(self, win):
  407.         self.win = win
  408.     #
  409.     def readline(self, *unused_args):
  410.         n = len(inputwindows)
  411.         save_title = self.win.gettitle()
  412.         title = n*'(' + 'Requesting input...' + ')'*n
  413.         self.win.settitle(title)
  414.         inputwindows.insert(0, self.win)
  415.         try:
  416.             try:
  417.                 mainloop.mainloop()
  418.             finally:
  419.                 del inputwindows[0]
  420.                 self.win.settitle(save_title)
  421.         except InputAvailable, val: # See do_exec above
  422.             return val
  423.         except KeyboardInterrupt:
  424.             raise EOFError # Until we have a "send EOF" key
  425.         # If we didn't catch InputAvailable, something's wrong...
  426.         raise EOFError
  427.     #
  428.     def write(self, text):
  429.         mainloop.check()
  430.         replace(self.win, text)
  431.         mainloop.check()
  432.  
  433.  
  434. # Currently unused function to test a command's syntax without executing it
  435. #
  436. def testsyntax(s):
  437.     import string
  438.     lines = string.splitfields(s, '\n')
  439.     for i in range(len(lines)): lines[i] = '\t' + lines[i]
  440.     lines.insert(0, 'if 0:')
  441.     lines.append('')
  442.     exec(string.joinfields(lines, '\n'))
  443.  
  444.  
  445. # Call the main program
  446. #
  447. main()
  448.