[Pythonmac-SIG] cheol (version 0.5)

Gordon Worley redbird@rbisland.cx
Fri, 20 Jul 2001 13:51:58 -0400


Okay, here is the latest version.  This one will follow symlinks and 
prints verbose messages if you want (they aren't very pretty, but 
they get the info out there).  Also, I put it under the Python 
License, version 2.1, but since I've never used this license before, 
I hope I've done it the right way.

Now, it still needs a Mac interface if this is going to be really 
useful (for example, as it stands you can't use this in Classic). 
So, someone, please, let's collaborate.  I'll help you make the 
modifications to do pattern matching if needed, but I'll leave the 
GUI up to you.  I'm serious here, I'm not going to do the GUI myself, 
so either someone else does it or it won't get done at all.

On a side note, this code is pretty much complete, aside from maybe a 
better dosification algorithm (viz. a safe one) and cleaner verbosity.

Oh, finally, on tab conversions, the string objects have an expandtab 
method if I'm reading the documentation correctly, and a very simple 
regular expression should be able to detab, so maybe tomorrow I'll 
have a chtab program ready (it will basically be this code, but the 
part that changes line endings will change tabs instead).

#!/usr/bin/env python

"""
cheol:  change end of line character
Copyright (C) 2000-2001 Gordon Worley

This program is free software; you can redistribute it and/or modify
it under the terms of the Python License, version 2.1.

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
Python License, version 2.1 for more details.

For a copy of the Python License, version 2.1, visit
<http://www.python.org/>.

To contact me, please visit my Web site at <http://homepage.mac.com/
redbird/> or e-mail me at <redbird@rbisland.cx>.

#history

0.5 - added support for following links and verbosity.  Lot of work.
0.3.5 - removed pattern matching since not unixy
0.3 - added help and ability to convert to mac and dos.  Name changed
       to cheol.py from unixify.pl.
0.1 - added recursion, uses Jurgen Hermann's FileMorpher
0.0 - just converts the given files
"""

__version__ = "cheol 0.5, Copyright 2000-2001 Gordon Worley via the 
Python License, version 2.1.\nType -h for help"
#fn = filename

#this first part is not by me, but put right in this code
#so that everything can stay in one file :-)
#by Jurgen Hermann, from Python Cookbook (ASPN)

import os, string

def replaceFile(oldname, newname):
	""" Rename file 'oldname' to 'newname'.
	"""
	if os.name == 'nt' and os.path.exists(oldname):
		# POSIX rename does an atomic replace, WIN32 rename 
does not. :-(
		try:
			os.remove(newname)
		except OSError, exc:
			import errno
			if exc.errno != errno.ENOENT: raise exc

	# rename it
	os.rename(oldname, newname)


class FileMorpher:
	""" A class that enables a client to securely update an existing file,
		including the ability to make an automated backup version.
	"""

	def __init__(self, filename, **kw):
		""" The constructor takes the filename and some options.

			backup -- boolean indicating whether you want 
a backup file

				(default is yes)
		"""
		self.filename = filename
		self.do_backup = kw.get('backup', 0)

		self.stream = None
		self.basename, ext = os.path.splitext(self.filename)


	def __del__(self):
		if self.stream:
			# Remove open temp file
			self.__close()
			os.remove(self.__tempfile())


	def __tempfile(self):
		return self.basename + ".tmp"


	def __close(self):
		""" Close temp stream, if open.
		"""
		if self.stream:
			self.stream.close()
			self.stream = None


	def load(self):
		""" Load the content of the original file into a string and
			return it. All I/O exceptions are passed through.
		"""
		file = open(self.filename, "rt")
		try:
			content = file.read()
		finally:
			file.close()

		return content


	def save(self, content):
		""" Save new content, using a temporary file.
		"""
		file = self.opentemp()
		file.write(content)
		self.commit()


	def opentemp(self):
		""" Open a temporary file for writing and return an 
open stream.
		"""
		assert not self.stream, "Write stream already open"

		self.stream = open(self.__tempfile(), "wt")

		return self.stream


	def commit(self):
		""" Close the open temp stream and replace the original file,
			optionally making a backup copy.
		"""
		assert self.stream, "Write stream not open"

		# close temp file
		self.__close()

		# do optional backup and rename temp file to the correct name
		if self.do_backup:
			replaceFile(self.filename, self.basename + ".bak")
		replaceFile(self.__tempfile(), self.filename)

#end part not by me
#begin part by me

def convert(fn, mode, is_recv, follow_links, verbose):
	if is_recv:
		if os.path.isdir(fn) and not os.path.islink(fn):
			if verbose:
				print "%s/:" % fn
			os.chdir(fn)
			fns = os.listdir("./")
			for afn in fns:
				convert(afn, mode, is_recv, 
follow_links, verbose)
			os.chdir("..")
			if verbose:
				print "../:"
		elif os.path.isdir(fn) and os.path.islink(fn) and 
os.path.islink(fn) <= follow_links:
			jfn = os.readlink(fn)
			if verbose:
				print "%s/ (%s/):" % (fn, jfn)
			fns = os.listdir(fn)
			for afn in fns:
				convert(os.path.join(jfn, afn), mode, 
is_recv, follow_links, verbose)
			if verbose:
				print "../:"
	if not os.path.isdir(fn):
		if os.path.islink(fn) and os.path.islink(fn) <= follow_links:
			tmp = fn
			fn = os.readlink(fn)
			if verbose:
				print "converting %s (%s)" % (tmp, fn)
		elif verbose:
			print "converting %s" % fn
		f = FileMorpher(fn)
		temp = f.load()
		if mode == 1:
			temp = string.replace(temp, '\n\r', '\n')
			temp = string.replace(temp, '\r', '\n')
		elif mode == 0:
			temp = string.replace(temp, '\n\r', '\r')
			temp = string.replace(temp, '\n', '\r')
		elif mode == 2:
			#this code could be dangerous, but I don't do these
			#conversions often enough to care :-P
			temp = string.replace(temp, '\r', '\n')
			temp = string.replace(temp, '\n', '\n\r')
		stream = f.opentemp()
		stream.write(temp)
		f.commit()

help = """\
cheol:	Converts EOL characters

%s [options] <path ...>
  options:
    -r : recursive
    -l : follow links
    -v : verbose
    -m : macify line endings
    -u : unixify line endings
    -d : dosify line endings
    -h : print help
  path is a file or directory
"""

if __name__ == '__main__':
	import sys, getopt
	try:
		opts, args = getopt.getopt(sys.argv[1:], "hlvmudr")
	except:
		print "That's not an option.  Type -h for help."
		sys.exit(1)
	mode = 1 #default is unix, 0 is mac, 2 is dos
	is_recv = 0 #default isn't recursive
	follow_links = 0 #default don't follow links
	verbose = 0
	for opt in opts:
		if opt[0] == "-r":
			is_recv = 1
		elif opt[0] == "-l":
			follow_links = 1
		elif opt[0] == "-v":
			verbose = 1
		elif opt[0] == "-m":
			mode = 0;
		elif opt[0] == "-u":
			mode = 1;
		elif opt[0] == "-d":
			mode = 2;
		elif opt[0] == "-h":
			print help % sys.argv[0]
			sys.exit(0)
	if not args:
		print __version__
	for arg in args:
		convert(arg, mode, is_recv, follow_links, verbose)
-- 
Gordon Worley                     `When I use a word,' Humpty Dumpty
http://homepage.mac.com/redbird/   said, `it means just what I choose
redbird@rbisland.cx                it to mean--neither more nor less.'
PGP:  0xBBD3B003                                  --Lewis Carroll