pindent.py :  » Mobile » Python-for-PalmOS » Python-1.5.2+reduced-1.0 » Tools » scripts » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Mobile » Python for PalmOS 
Python for PalmOS » Python 1.5.2 reduced 1.0 » Tools » scripts » pindent.py
#! /usr/bin/env python

# This file contains a class and a main program that perform two
# related (though complimentary) formatting operations on Python
# programs.  When called as "pindend -c", it takes a valid Python
# program as input and outputs a version augmented with block-closing
# comments.  When called as "pindent -r" it assumes its input is a
# Python program with block-closing comments but with its indentation
# messed up, and outputs a properly indented version.

# A "block-closing comment" is a comment of the form '# end <keyword>'
# where <keyword> is the keyword that opened the block.  If the
# opening keyword is 'def' or 'class', the function or class name may
# be repeated in the block-closing comment as well.  Here is an
# example of a program fully augmented with block-closing comments:

# def foobar(a, b):
#    if a == b:
#        a = a+1
#    elif a < b:
#        b = b-1
#        if b > a: a = a-1
#        # end if
#    else:
#        print 'oops!'
#    # end if
# # end def foobar

# Note that only the last part of an if...elif...else... block needs a
# block-closing comment; the same is true for other compound
# statements (e.g. try...except).  Also note that "short-form" blocks
# like the second 'if' in the example must be closed as well;
# otherwise the 'else' in the example would be ambiguous (remember
# that indentation is not significant when interpreting block-closing
# comments).

# Both operations are idempotent (i.e. applied to their own output
# they yield an identical result).  Running first "pindent -c" and
# then "pindent -r" on a valid Python program produces a program that
# is semantically identical to the input (though its indentation may
# be different).

# Other options:
# -s stepsize: set the indentation step size (default 8)
# -t tabsize : set the number of spaces a tab character is worth (default 8)
# file ...   : input file(s) (default standard input)
# The results always go to standard output

# Caveats:
# - comments ending in a backslash will be mistaken for continued lines
# - continuations using backslash are always left unchanged
# - continuations inside parentheses are not extra indented by -r
#   but must be indented for -c to work correctly (this breaks
#   idempotency!)
# - continued lines inside triple-quoted strings are totally garbled

# Secret feature:
# - On input, a block may also be closed with an "end statement" --
#   this is a block-closing comment without the '#' sign.

# Possible improvements:
# - check syntax based on transitions in 'next' table
# - better error reporting
# - better error recovery
# - check identifier after class/def

# The following wishes need a more complete tokenization of the source:
# - Don't get fooled by comments ending in backslash
# - reindent continuation lines indicated by backslash
# - handle continuation lines inside parentheses/braces/brackets
# - handle triple quoted strings spanning lines
# - realign comments
# - optionally do much more thorough reformatting, a la C indent

# Defaults
STEPSIZE = 8
TABSIZE = 8

import os
import re
import string
import sys

next = {}
next['if'] = next['elif'] = 'elif', 'else', 'end'
next['while'] = next['for'] = 'else', 'end'
next['try'] = 'except', 'finally'
next['except'] = 'except', 'else', 'end'
next['else'] = next['finally'] = next['def'] = next['class'] = 'end'
next['end'] = ()
start = 'if', 'while', 'for', 'try', 'def', 'class'

class PythonIndenter:

  def __init__(self, fpi = sys.stdin, fpo = sys.stdout,
         indentsize = STEPSIZE, tabsize = TABSIZE):
    self.fpi = fpi
    self.fpo = fpo
    self.indentsize = indentsize
    self.tabsize = tabsize
    self.lineno = 0
    self.write = fpo.write
    self.kwprog = re.compile(
      r'^\s*(?P<kw>[a-z]+)'
      r'(\s+(?P<id>[a-zA-Z_]\w*))?'
      r'[^\w]')
    self.endprog = re.compile(
      r'^\s*#?\s*end\s+(?P<kw>[a-z]+)'
      r'(\s+(?P<id>[a-zA-Z_]\w*))?'
      r'[^\w]')
    self.wsprog = re.compile(r'^[ \t]*')
  # end def __init__

  def readline(self):
    line = self.fpi.readline()
    if line: self.lineno = self.lineno + 1
    # end if
    return line
  # end def readline

  def error(self, fmt, *args):
    if args: fmt = fmt % args
    # end if
    sys.stderr.write('Error at line %d: %s\n' % (self.lineno, fmt))
    self.write('### %s ###\n' % fmt)
  # end def error

  def getline(self):
    line = self.readline()
    while line[-2:] == '\\\n':
      line2 = self.readline()
      if not line2: break
      # end if
      line = line + line2
    # end while
    return line
  # end def getline

  def putline(self, line, indent = None):
    if indent is None:
      self.write(line)
      return
    # end if
    tabs, spaces = divmod(indent*self.indentsize, self.tabsize)
    i = 0
    m = self.wsprog.match(line)
    if m: i = m.end()
    # end if
    self.write('\t'*tabs + ' '*spaces + line[i:])
  # end def putline

  def reformat(self):
    stack = []
    while 1:
      line = self.getline()
      if not line: break  # EOF
      # end if
      m = self.endprog.match(line)
      if m:
        kw = 'end'
        kw2 = m.group('kw')
        if not stack:
          self.error('unexpected end')
        elif stack[-1][0] != kw2:
          self.error('unmatched end')
        # end if
        del stack[-1:]
        self.putline(line, len(stack))
        continue
      # end if
      m = self.kwprog.match(line)
      if m:
        kw = m.group('kw')
        if kw in start:
          self.putline(line, len(stack))
          stack.append((kw, kw))
          continue
        # end if
        if next.has_key(kw) and stack:
          self.putline(line, len(stack)-1)
          kwa, kwb = stack[-1]
          stack[-1] = kwa, kw
          continue
        # end if
      # end if
      self.putline(line, len(stack))
    # end while
    if stack:
      self.error('unterminated keywords')
      for kwa, kwb in stack:
        self.write('\t%s\n' % kwa)
      # end for
    # end if
  # end def reformat

  def complete(self):
    self.indentsize = 1
    stack = []
    todo = []
    current, firstkw, lastkw, topid = 0, '', '', ''
    while 1:
      line = self.getline()
      i = 0
      m = self.wsprog.match(line)
      if m: i = m.end()
      # end if
      m = self.endprog.match(line)
      if m:
        thiskw = 'end'
        endkw = m.group('kw')
        thisid = m.group('id')
      else:
        m = self.kwprog.match(line)
        if m:
          thiskw = m.group('kw')
          if not next.has_key(thiskw):
            thiskw = ''
          # end if
          if thiskw in ('def', 'class'):
            thisid = m.group('id')
          else:
            thisid = ''
          # end if
        elif line[i:i+1] in ('\n', '#'):
          todo.append(line)
          continue
        else:
          thiskw = ''
        # end if
      # end if
      indent = len(string.expandtabs(line[:i], self.tabsize))
      while indent < current:
        if firstkw:
          if topid:
            s = '# end %s %s\n' % (
              firstkw, topid)
          else:
            s = '# end %s\n' % firstkw
          # end if
          self.putline(s, current)
          firstkw = lastkw = ''
        # end if
        current, firstkw, lastkw, topid = stack[-1]
        del stack[-1]
      # end while
      if indent == current and firstkw:
        if thiskw == 'end':
          if endkw != firstkw:
            self.error('mismatched end')
          # end if
          firstkw = lastkw = ''
        elif not thiskw or thiskw in start:
          if topid:
            s = '# end %s %s\n' % (
              firstkw, topid)
          else:
            s = '# end %s\n' % firstkw
          # end if
          self.putline(s, current)
          firstkw = lastkw = topid = ''
        # end if
      # end if
      if indent > current:
        stack.append((current, firstkw, lastkw, topid))
        if thiskw and thiskw not in start:
          # error
          thiskw = ''
        # end if
        current, firstkw, lastkw, topid = \
           indent, thiskw, thiskw, thisid
      # end if
      if thiskw:
        if thiskw in start:
          firstkw = lastkw = thiskw
          topid = thisid
        else:
          lastkw = thiskw
        # end if
      # end if
      for l in todo: self.write(l)
      # end for
      todo = []
      if not line: break
      # end if
      self.write(line)
    # end while
  # end def complete

# end class PythonIndenter

# Simplified user interface
# - xxx_filter(input, output): read and write file objects
# - xxx_string(s): take and return string object
# - xxx_file(filename): process file in place, return true iff changed

def complete_filter(input= sys.stdin, output = sys.stdout,
        stepsize = STEPSIZE, tabsize = TABSIZE):
  pi = PythonIndenter(input, output, stepsize, tabsize)
  pi.complete()
# end def complete_filter

def reformat_filter(input = sys.stdin, output = sys.stdout,
        stepsize = STEPSIZE, tabsize = TABSIZE):
  pi = PythonIndenter(input, output, stepsize, tabsize)
  pi.reformat()
# end def reformat

class StringReader:
  def __init__(self, buf):
    self.buf = buf
    self.pos = 0
    self.len = len(self.buf)
  # end def __init__
  def read(self, n = 0):
    if n <= 0:
      n = self.len - self.pos
    else:
      n = min(n, self.len - self.pos)
    # end if
    r = self.buf[self.pos : self.pos + n]
    self.pos = self.pos + n
    return r
  # end def read
  def readline(self):
    i = string.find(self.buf, '\n', self.pos)
    return self.read(i + 1 - self.pos)
  # end def readline
  def readlines(self):
    lines = []
    line = self.readline()
    while line:
      lines.append(line)
      line = self.readline()
    # end while
    return lines
  # end def readlines
  # seek/tell etc. are left as an exercise for the reader
# end class StringReader

class StringWriter:
  def __init__(self):
    self.buf = ''
  # end def __init__
  def write(self, s):
    self.buf = self.buf + s
  # end def write
  def getvalue(self):
    return self.buf
  # end def getvalue
# end class StringWriter

def complete_string(source, stepsize = STEPSIZE, tabsize = TABSIZE):
  input = StringReader(source)
  output = StringWriter()
  pi = PythonIndenter(input, output, stepsize, tabsize)
  pi.complete()
  return output.getvalue()
# end def complete_string

def reformat_string(source, stepsize = STEPSIZE, tabsize = TABSIZE):
  input = StringReader(source)
  output = StringWriter()
  pi = PythonIndenter(input, output, stepsize, tabsize)
  pi.reformat()
  return output.getvalue()
# end def reformat_string

def complete_file(filename, stepsize = STEPSIZE, tabsize = TABSIZE):
  source = open(filename, 'r').read()
  result = complete_string(source, stepsize, tabsize)
  if source == result: return 0
  # end if
  import os
  try: os.rename(filename, filename + '~')
  except os.error: pass
  # end try
  f = open(filename, 'w')
  f.write(result)
  f.close()
  return 1
# end def complete_file

def reformat_file(filename, stepsize = STEPSIZE, tabsize = TABSIZE):
  source = open(filename, 'r').read()
  result = reformat_string(source, stepsize, tabsize)
  if source == result: return 0
  # end if
  import os
  os.rename(filename, filename + '~')
  f = open(filename, 'w')
  f.write(result)
  f.close()
  return 1
# end def reformat_file

# Test program when called as a script

usage = """
usage: pindent (-c|-r) [-s stepsize] [-t tabsize] [file] ...
-c         : complete a correctly indented program (add #end directives)
-r         : reformat a completed program (use #end directives)
-s stepsize: indentation step (default %(STEPSIZE)d)
-t tabsize : the worth in spaces of a tab (default %(TABSIZE)d)
[file] ... : files are changed in place, with backups in file~
If no files are specified or a single - is given,
the program acts as a filter (reads stdin, writes stdout).
""" % vars()

def test():
  import getopt
  try:
    opts, args = getopt.getopt(sys.argv[1:], 'crs:t:')
  except getopt.error, msg:
    sys.stderr.write('Error: %s\n' % msg)
    sys.stderr.write(usage)
    sys.exit(2)
  # end try
  action = None
  stepsize = STEPSIZE
  tabsize = TABSIZE
  for o, a in opts:
    if o == '-c':
      action = 'complete'
    elif o == '-r':
      action = 'reformat'
    elif o == '-s':
      stepsize = string.atoi(a)
    elif o == '-t':
      tabsize = string.atoi(a)
    # end if
  # end for
  if not action:
    sys.stderr.write(
      'You must specify -c(omplete) or -r(eformat)\n')
    sys.stderr.write(usage)
    sys.exit(2)
  # end if
  if not args or args == ['-']:
    action = eval(action + '_filter')
    action(sys.stdin, sys.stdout, stepsize, tabsize)
  else:
    action = eval(action + '_file')
    for file in args:
      action(file, stepsize, tabsize)
    # end for
  # end if
# end def test

if __name__ == '__main__':
  test()
# end if
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.