Tuesday, March 3, 2009

A TextExpander snippet to paste quoted text

I prefer the inline replying posting style. So a frequent workflow for me is copying text, pasting it into BBEdit, applying "Increase Quote Level", "Rewrap quoted text...", "Select All", "Copy" and then pasting the text into (typically) Mailplane, where I finish composing my email. (Or if I'm running Outlook in Fusion, I may compose the email entirely in BBEdit and paste it as a whole back into Outlook.) Each time I do this, I get that "there's got to be a better way" itch. (But then I get back to what I'm working on and forget about it until next time.)

Yesterday I was reading Dan Frakes' "Plain Clip revisited" MacGem post, which explains how to use the free (donations excepted) Plain Clip utility with TextExpander to automatically paste whatever is in the clipboard with any formatting information removed when you type "ptp" (which is the abbreviation he chose to assign to this "snippet" in TextExpander). I need to do that occasionally (and again, I've been doing it by pasting into BBEdit and then copying back to the clipboard), so I downloaded Plain Clip and a free trial of TextExpander and began setting it up. Dan Frakes uses an AppleScript to run Plain Clip through the command-line. But I noticed I can define a shell script in TextExpander so I don't need to bother with an AppleScript "do shell script" wrapper. But I couldn't get it to work. I quickly found a link in the comments to Gordon Meyer's "A tip for using Plain Clip with TextExpander" blog post, which solved the problem for me.

And that's when a light bulb finally went off in (over?) my head. If I created a simple command-line utility to do word wrapping, I could create a TextExpander snippet to paste quoted text. And about 30 minutes later that's just what I had.

When I need to create a command-line utility, I almost always turn to Python. And as is so often the case with Python's "batteries included" nature, I found a textwrap module that provided almost everything I needed.
#!/usr/bin/env python
"""
This script wraps the textwrap module with a command-line interface.

"""

import optparse
import sys
from textwrap import TextWrapper


DEFAULT_WIDTH = 70
DEFAULT_QUOTE_STRING = '> '


def main():
"""The entry function."""
parser = optparse.OptionParser(description='Text wrapper', prog='wwrap')
parser.add_option('-w', '--width',
action='store',
metavar='NUM',
default='%d' % DEFAULT_WIDTH,
help='maximum length of wrapped lines; defaults to 70')
parser.add_option('-q', '--quote',
action='store_true',
default=False,
help="add '%s' to start of each wrapped line"
% DEFAULT_QUOTE_STRING)
parser.add_option('-r', '--remove',
action='store',
metavar='TXT',
help='remove TXT from start of each line prior to'
' wrapping')
parser.add_option('--removequotes',
action='store_true',
default=False,
help="remove '%s' from the start of each line prior"
" to wrapping" % DEFAULT_QUOTE_STRING)

options, arguments = parser.parse_args()

if not options.width:
print "I don't know how the width wasn't specified, since it's"
print "supposed to default to %d. Aborting." % DEFAULT_WIDTH
return -1

try:
width = int(options.width)
except ValueError:
print "ERROR: the width specified ('%s') is not a number" % (
options.width)
return -1

if options.remove and options.removequotes:
print "ERROR: cannot use both --remove and --removequotes options"
return -1

if options.removequotes:
options.remove = DEFAULT_QUOTE_STRING

wrapper = TextWrapper()
if options.quote:
wrapper.initial_indent = DEFAULT_QUOTE_STRING
wrapper.subsequent_indent = DEFAULT_QUOTE_STRING
wrapper.width = width

lines = sys.stdin.read().split('\n')
if options.remove:
for i, line in enumerate(lines):
if line.startswith(options.remove):
lines[i] = line[len(options.remove):]

print wrapper.fill('\n'.join(lines))

return 0




if __name__ == '__main__':
sys.exit(main())
Note that even though you can't see the code at the end of long lines, it's there. Just copy it all and paste it into your favorite text editor to read it all.

This code should be quite self-explanatory. As is (also) so often the case with Python, the "meat" of these 81 lines are the 7 lines starting with line 67 where I read in the text to be wrapped from stdin. All the preceeding lines are for handling the command-line options.

I put this in a file called "wwrap" and did a `chmod +x` on it. I then created a symbolic link to it in /usr/local/bin/, and I was ready to create my TextExpander snippet.

















Now I can just type 'qtp' and the quoted, wrapped form of whatever text is in the clipboard is pasted in.

I'm inspired to look for more workflows I can simplify with TextExpander and possibly Python. (I'm going to start by trying to figure out how to re-create the "Create tr.im shortened URL" in Dan Frakes' TextExpander screenshot.)

1 comment:

Unknown said...

here is an alternative shell script (for a TextExpander snippet) to paste quoted text from the clipboard (stripped of style by the "Plain Clip" application), using the Unix commands "fmt" (to wrap long lines) and "sed" (to add the quotes); the call to "perl" is added because "pbpaste" messes up the line endings:

#!/bin/bash
/Applications/Utilities/Plain\ Clip.app/pc -i -w
pbpaste | perl -pe 's/\r\n?/\n/g' | fmt -m -n -p | sed -e 's/^/\> /'