Skip to content

Commit e94a06d

Browse files
author
Richard Jones
committed
added StringHTMLProperty wrapped() method to wrap long lines in issue display
also fixed a circular import in roundup.support
1 parent 1c2c91b commit e94a06d

File tree

4 files changed

+143
-3
lines changed

4 files changed

+143
-3
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ are given with the most recent entry first.
55
Feature:
66
- full timezone support (sf patch 1465296)
77
- handle connection loss when responding to web requests
8+
- added StringHTMLProperty wrapped() method to wrap long lines in issue
9+
display
810

911
Fixed:
1012
- Verbose option for import and export (sf bug 1505645)

roundup/cgi/templating.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from __future__ import nested_scopes
2121

2222
import sys, cgi, urllib, os, re, os.path, time, errno, mimetypes, csv
23-
import calendar
23+
import calendar, textwrap
2424

2525
from roundup import hyperdb, date, support
2626
from roundup import i18n
@@ -1248,6 +1248,34 @@ def plain(self, escape=0, hyperlink=0):
12481248
s = self.hyper_re.sub(self._hyper_repl, s)
12491249
return s
12501250

1251+
def wrapped(self, escape=1, hyperlink=1):
1252+
'''Render a "wrapped" representation of the property.
1253+
1254+
We wrap long lines at 80 columns on the nearest whitespace. Lines
1255+
with no whitespace are not broken to force wrapping.
1256+
1257+
Note that unlike plain() we default wrapped() to have the escaping
1258+
and hyperlinking turned on since that's the most common usage.
1259+
1260+
- "escape" turns on/off HTML quoting
1261+
- "hyperlink" turns on/off in-text hyperlinking of URLs, email
1262+
addresses and designators
1263+
'''
1264+
if not self.is_view_ok():
1265+
return self._('[hidden]')
1266+
1267+
if self._value is None:
1268+
return ''
1269+
s = support.wrap(str(self._value), width=80)
1270+
if escape:
1271+
s = cgi.escape(s)
1272+
if hyperlink:
1273+
# no, we *must* escape this text
1274+
if not escape:
1275+
s = cgi.escape(s)
1276+
s = self.hyper_re.sub(self._hyper_repl, s)
1277+
return s
1278+
12511279
def stext(self, escape=0, hyperlink=1):
12521280
''' Render the value of the property as StructuredText.
12531281

roundup/support.py

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
__docformat__ = 'restructuredtext'
66

7-
import os, time, sys
8-
import hyperdb
7+
import os, time, sys, re
98

109
from sets import Set
1110

@@ -155,6 +154,7 @@ def append(self, name):
155154
"""Append a property to self.children. Will create a new
156155
propclass for the child.
157156
"""
157+
import hyperdb
158158
if name in self.propnames:
159159
return self.propnames [name]
160160
propclass = self.props [name]
@@ -192,4 +192,97 @@ def __iter__(self):
192192
for c in p:
193193
yield c
194194

195+
LEFT = 'left'
196+
LEFTN = 'left no strip'
197+
RIGHT = 'right'
198+
CENTER = 'center'
199+
200+
def align(line, width=70, alignment=LEFTN):
201+
''' Code from http://www.faqts.com/knowledge_base/view.phtml/aid/4476 '''
202+
if alignment == CENTER:
203+
line = line.strip()
204+
space = width - len(line)
205+
return ' '*(space/2) + line + ' '*(space/2 + space%2)
206+
elif alignment == RIGHT:
207+
line = line.rstrip()
208+
space = width - len(line)
209+
return ' '*space + line
210+
else:
211+
if alignment == LEFT:
212+
line = line.lstrip()
213+
space = width - len(line)
214+
return line + ' '*space
215+
216+
217+
def format_line(columns, positions, contents, spacer=' | ',
218+
collapse_whitespace=True, wsre=re.compile(r'\s+')):
219+
''' Fill up a single row with data from the contents '''
220+
l = []
221+
data = 0
222+
for i in range(len(columns)):
223+
width, alignment = columns[i]
224+
content = contents[i]
225+
col = ''
226+
while positions[i] < len(content):
227+
word = content[positions[i]]
228+
# if we hit a newline, honor it
229+
if '\n' in word:
230+
# chomp
231+
positions[i] += 1
232+
break
233+
234+
# make sure this word fits
235+
if col and len(word) + len(col) > width:
236+
break
237+
238+
# no whitespace at start-of-line
239+
if collapse_whitespace and wsre.match(word) and not col:
240+
# chomp
241+
positions[i] += 1
242+
continue
243+
244+
col += word
245+
# chomp
246+
positions[i] += 1
247+
if col:
248+
data = 1
249+
col = align(col, width, alignment)
250+
l.append(col)
251+
252+
if not data:
253+
return ''
254+
return spacer.join(l).rstrip()
255+
256+
257+
def format_columns(columns, contents, spacer=' | ', collapse_whitespace=True,
258+
splitre=re.compile(r'(\n|\r\n|\r|[ \t]+|\S+)')):
259+
''' Format the contents into columns, with 'spacing' between the
260+
columns
261+
'''
262+
assert len(columns) == len(contents), \
263+
'columns and contents must be same length'
264+
265+
# split the text into words, spaces/tabs and newlines
266+
for i in range(len(contents)):
267+
contents[i] = splitre.findall(contents[i])
268+
269+
# now process line by line
270+
l = []
271+
positions = [0]*len(contents)
272+
while 1:
273+
l.append(format_line(columns, positions, contents, spacer,
274+
collapse_whitespace))
275+
276+
# are we done?
277+
for i in range(len(contents)):
278+
if positions[i] < len(contents[i]):
279+
break
280+
else:
281+
break
282+
return '\n'.join(l)
283+
284+
def wrap(text, width=75, alignment=LEFTN):
285+
return format_columns(((width, alignment),), [text],
286+
collapse_whitespace=False)
287+
195288
# vim: set et sts=4 sw=4 :

test/test_textfmt.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import unittest
2+
3+
from roundup.support import wrap
4+
5+
class WrapTestCase(unittest.TestCase):
6+
def testWrap(self):
7+
lorem = '''Lorem ipsum dolor sit amet, consectetuer adipiscing elit.'''
8+
wrapped = '''Lorem ipsum dolor
9+
sit amet,
10+
consectetuer
11+
adipiscing elit.'''
12+
self.assertEquals(wrap(lorem, 20), wrapped)
13+
14+
def test_suite():
15+
suite = unittest.TestSuite()
16+
suite.addTest(unittest.makeSuite(WrapTestCase))
17+
return suite

0 commit comments

Comments
 (0)