1.1 --- a/DateSupport.py Mon Nov 11 13:42:06 2013 +0100
1.2 +++ b/DateSupport.py Tue Jan 21 18:26:01 2014 +0100
1.3 @@ -2,7 +2,7 @@
1.4 """
1.5 MoinMoin - DateSupport library (derived from EventAggregatorSupport)
1.6
1.7 - @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk>
1.8 + @copyright: 2008, 2009, 2010, 2011, 2012, 2013, 2014 by Paul Boddie <paul@boddie.org.uk>
1.9 @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.10 """
1.11
1.12 @@ -392,7 +392,7 @@
1.13 def __str__(self):
1.14 return Date.__str__(self) + self.time_string()
1.15
1.16 - def time_string(self, zone_as_offset=False, time_prefix=" ", zone_prefix=" "):
1.17 + def time_string(self, zone_as_offset=False, time_prefix=" ", zone_prefix=" ", zone_separator=":"):
1.18 if self.has_time():
1.19 data = self.as_tuple()
1.20 time_str = "%s%02d:%02d" % ((time_prefix,) + data[3:5])
1.21 @@ -402,13 +402,23 @@
1.22 if zone_as_offset:
1.23 utc_offset = self.utc_offset()
1.24 if utc_offset:
1.25 - time_str += "%s%+03d:%02d" % ((zone_prefix,) + utc_offset)
1.26 + time_str += "%s%+03d%s%02d" % (zone_prefix, utc_offset[0], zone_separator, utc_offset[1])
1.27 else:
1.28 time_str += "%s%s" % (zone_prefix, data[6])
1.29 return time_str
1.30 else:
1.31 return ""
1.32
1.33 + def as_RFC2822_datetime_string(self):
1.34 + weekday = calendar.weekday(*self.data[:3])
1.35 + return "%s, %02d %s %04d %s" % (
1.36 + getDayLabel(weekday)[:3],
1.37 + self.data[2],
1.38 + getMonthLabel(self.data[1])[:3],
1.39 + self.data[0],
1.40 + self.time_string(zone_as_offset=True, time_prefix="", zone_prefix=" ", zone_separator="")
1.41 + )
1.42 +
1.43 def as_HTTP_datetime_string(self):
1.44 weekday = calendar.weekday(*self.data[:3])
1.45 return "%s, %02d %s %04d %02d:%02d:%02d GMT" % ((
2.1 --- a/ItemSupport.py Mon Nov 11 13:42:06 2013 +0100
2.2 +++ b/ItemSupport.py Tue Jan 21 18:26:01 2014 +0100
2.3 @@ -2,135 +2,65 @@
2.4 """
2.5 MoinMoin - ItemSupport library
2.6
2.7 - @copyright: 2013 by Paul Boddie <paul@boddie.org.uk>
2.8 + @copyright: 2013, 2014 by Paul Boddie <paul@boddie.org.uk>
2.9 @license: GNU GPL (v2 or later), see COPYING.txt for details.
2.10 """
2.11
2.12 +from MoinMoin.Page import Page
2.13 +from MoinMoin.PageEditor import PageEditor
2.14 +from MoinMoin.security import Permissions
2.15 from MoinMoin.util import lock
2.16 +from MoinSupport import getMetadata, getPagesForSearch
2.17 import os
2.18
2.19 # Content storage support.
2.20
2.21 -class ItemDirectoryStore:
2.22 +class SpecialPermissionsForPage(Permissions):
2.23 +
2.24 + "Permit saving of ACL-enabled pages."
2.25 +
2.26 + def __init__(self, user, pagename):
2.27 + Permissions.__init__(self, user)
2.28 + self.pagename = pagename
2.29
2.30 - "A directory-based item store."
2.31 + def admin(self, pagename):
2.32 + return pagename == self.pagename
2.33 +
2.34 + write = admin
2.35 +
2.36 +class ReadPermissionsForSubpages(Permissions):
2.37 +
2.38 + "Permit listing of ACL-affected subpages."
2.39
2.40 - def __init__(self, path, lock_dir):
2.41 + def __init__(self, user, pagename):
2.42 + Permissions.__init__(self, user)
2.43 + self.pagename = pagename
2.44 +
2.45 + def read(self, pagename):
2.46 + return pagename.startswith("%s/" % self.pagename)
2.47 +
2.48 +# Underlying storage mechanisms.
2.49
2.50 - "Initialise an item store for the given 'path' and 'lock_dir'."
2.51 +class GeneralItemStore:
2.52 +
2.53 + "Common item store functionality."
2.54
2.55 - self.path = path
2.56 - self.next_path = os.path.join(self.path, "next")
2.57 + def __init__(self, lock_dir):
2.58 +
2.59 + "Initialise an item store with the given 'lock_dir' guarding access."
2.60 +
2.61 self.lock_dir = lock_dir
2.62 self.writelock = lock.WriteLock(lock_dir)
2.63 self.readlock = lock.ReadLock(lock_dir)
2.64
2.65 - def mtime(self):
2.66 -
2.67 - "Return the last modified time of the item store directory."
2.68 -
2.69 - return os.path.getmtime(self.path)
2.70 -
2.71 - def get_next(self):
2.72 -
2.73 - "Return the next item number."
2.74 -
2.75 - next = self.read_next()
2.76 - if next is None:
2.77 - next = self.deduce_next()
2.78 - self.write_next(next)
2.79 - return next
2.80 -
2.81 - def get_keys(self):
2.82 -
2.83 - "Return the item keys."
2.84 -
2.85 - return [int(filename) for filename in os.listdir(self.path) if filename.isdigit()]
2.86 -
2.87 def deduce_next(self):
2.88
2.89 "Deduce the next item number from the existing item files."
2.90
2.91 return max(self.get_keys() or [-1]) + 1
2.92
2.93 - def read_next(self):
2.94 -
2.95 - "Read the next item number from a special file."
2.96 -
2.97 - if not os.path.exists(self.next_path):
2.98 - return None
2.99 -
2.100 - f = open(self.next_path)
2.101 - try:
2.102 - try:
2.103 - return int(f.read())
2.104 - except ValueError:
2.105 - return None
2.106 - finally:
2.107 - f.close()
2.108 -
2.109 - def write_next(self, next):
2.110 -
2.111 - "Write the 'next' item number to a special file."
2.112 -
2.113 - f = open(self.next_path, "w")
2.114 - try:
2.115 - f.write(str(next))
2.116 - finally:
2.117 - f.close()
2.118 -
2.119 - def write_item(self, item, next):
2.120 -
2.121 - "Write the given 'item' to a file with the given 'next' item number."
2.122 -
2.123 - f = open(self.get_item_path(next), "w")
2.124 - try:
2.125 - f.write(item)
2.126 - finally:
2.127 - f.close()
2.128 -
2.129 - def read_item(self, number):
2.130 -
2.131 - "Read the item with the given item 'number'."
2.132 -
2.133 - f = open(self.get_item_path(number))
2.134 - try:
2.135 - return f.read()
2.136 - finally:
2.137 - f.close()
2.138 -
2.139 - def remove_item(self, number):
2.140 -
2.141 - "Remove the item with the given item 'number'."
2.142 -
2.143 - os.remove(self.get_item_path(number))
2.144 -
2.145 - def get_item_path(self, number):
2.146 -
2.147 - "Get the path for the given item 'number'."
2.148 -
2.149 - path = os.path.abspath(os.path.join(self.path, str(number)))
2.150 - basepath = os.path.join(self.path, "")
2.151 -
2.152 - if os.path.commonprefix([path, basepath]) != basepath:
2.153 - raise OSError, path
2.154 -
2.155 - return path
2.156 -
2.157 # High-level methods.
2.158
2.159 - def append(self, item):
2.160 -
2.161 - "Append the given 'item' to the store."
2.162 -
2.163 - self.writelock.acquire()
2.164 - try:
2.165 - next = self.get_next()
2.166 - self.write_item(item, next)
2.167 - self.write_next(next + 1)
2.168 - finally:
2.169 - self.writelock.release()
2.170 -
2.171 def __len__(self):
2.172
2.173 """
2.174 @@ -194,6 +124,239 @@
2.175 finally:
2.176 self.writelock.release()
2.177
2.178 +class DirectoryItemStore(GeneralItemStore):
2.179 +
2.180 + "A directory-based item store."
2.181 +
2.182 + def __init__(self, path, lock_dir):
2.183 +
2.184 + "Initialise an item store for the given 'path' and 'lock_dir'."
2.185 +
2.186 + self.path = path
2.187 + self.next_path = os.path.join(self.path, "next")
2.188 + self.lock_dir = lock_dir
2.189 + self.writelock = lock.WriteLock(lock_dir)
2.190 + self.readlock = lock.ReadLock(lock_dir)
2.191 +
2.192 + def mtime(self):
2.193 +
2.194 + "Return the last modified time of the item store directory."
2.195 +
2.196 + return os.path.getmtime(self.path)
2.197 +
2.198 + def get_next(self):
2.199 +
2.200 + "Return the next item number."
2.201 +
2.202 + next = self.read_next()
2.203 + if next is None:
2.204 + next = self.deduce_next()
2.205 + self.write_next(next)
2.206 + return next
2.207 +
2.208 + def get_keys(self):
2.209 +
2.210 + "Return the item keys."
2.211 +
2.212 + return [int(filename) for filename in os.listdir(self.path) if filename.isdigit()]
2.213 +
2.214 + def read_next(self):
2.215 +
2.216 + "Read the next item number from a special file."
2.217 +
2.218 + if not os.path.exists(self.next_path):
2.219 + return None
2.220 +
2.221 + f = open(self.next_path)
2.222 + try:
2.223 + try:
2.224 + return int(f.read())
2.225 + except ValueError:
2.226 + return None
2.227 + finally:
2.228 + f.close()
2.229 +
2.230 + def write_next(self, next):
2.231 +
2.232 + "Write the 'next' item number to a special file."
2.233 +
2.234 + f = open(self.next_path, "w")
2.235 + try:
2.236 + f.write(str(next))
2.237 + finally:
2.238 + f.close()
2.239 +
2.240 + def write_item(self, item, next):
2.241 +
2.242 + "Write the given 'item' to a file with the given 'next' item number."
2.243 +
2.244 + f = open(self.get_item_path(next), "wb")
2.245 + try:
2.246 + f.write(item)
2.247 + finally:
2.248 + f.close()
2.249 +
2.250 + def read_item(self, number):
2.251 +
2.252 + "Read the item with the given item 'number'."
2.253 +
2.254 + f = open(self.get_item_path(number), "rb")
2.255 + try:
2.256 + return f.read()
2.257 + finally:
2.258 + f.close()
2.259 +
2.260 + def remove_item(self, number):
2.261 +
2.262 + "Remove the item with the given item 'number'."
2.263 +
2.264 + os.remove(self.get_item_path(number))
2.265 +
2.266 + def get_item_path(self, number):
2.267 +
2.268 + "Get the path for the given item 'number'."
2.269 +
2.270 + path = os.path.abspath(os.path.join(self.path, str(number)))
2.271 + basepath = os.path.join(self.path, "")
2.272 +
2.273 + if os.path.commonprefix([path, basepath]) != basepath:
2.274 + raise OSError, path
2.275 +
2.276 + return path
2.277 +
2.278 + # High-level methods.
2.279 +
2.280 + def append(self, item):
2.281 +
2.282 + "Append the given 'item' to the store."
2.283 +
2.284 + self.writelock.acquire()
2.285 + try:
2.286 + next = self.get_next()
2.287 + self.write_item(item, next)
2.288 + self.write_next(next + 1)
2.289 + finally:
2.290 + self.writelock.release()
2.291 +
2.292 +class SubpageItemStore(GeneralItemStore):
2.293 +
2.294 + "A subpage-based item store."
2.295 +
2.296 + def __init__(self, page, lock_dir):
2.297 +
2.298 + "Initialise an item store for subpages under the given 'page'."
2.299 +
2.300 + GeneralItemStore.__init__(self, lock_dir)
2.301 + self.page = page
2.302 +
2.303 + def mtime(self):
2.304 +
2.305 + "Return the last modified time of the item store."
2.306 +
2.307 + keys = self.get_keys()
2.308 + if not keys:
2.309 + page = self.page
2.310 + else:
2.311 + page = Page(self.page.request, self.get_item_path(max(keys)))
2.312 +
2.313 + return wikiutil.version2timestamp(
2.314 + getMetadata(page)["last-modified"]
2.315 + )
2.316 +
2.317 + def get_next(self):
2.318 +
2.319 + "Return the next item number."
2.320 +
2.321 + return self.deduce_next()
2.322 +
2.323 + def get_keys(self):
2.324 +
2.325 + "Return the item keys."
2.326 +
2.327 + request = self.page.request
2.328 +
2.329 + # Collect the strict subpages of the parent page.
2.330 +
2.331 + leafnames = []
2.332 + parentname = self.page.page_name
2.333 +
2.334 + # To list pages whose ACLs may prevent access, a special policy is required.
2.335 +
2.336 + may = request.user.may
2.337 + request.user.may = ReadPermissionsForSubpages(request.user, parentname)
2.338 +
2.339 + try:
2.340 + for page in getPagesForSearch("title:regex:^%s/" % parentname, self.page.request):
2.341 + basename, leafname = page.page_name.rsplit("/", 1)
2.342 +
2.343 + # Only collect numbered pages immediately below the parent.
2.344 +
2.345 + if basename == parentname and leafname.isdigit():
2.346 + leafnames.append(int(leafname))
2.347 +
2.348 + return leafnames
2.349 +
2.350 + # Restore the original policy.
2.351 +
2.352 + finally:
2.353 + request.user.may = may
2.354 +
2.355 + def write_item(self, item, next):
2.356 +
2.357 + "Write the given 'item' to a file with the given 'next' item number."
2.358 +
2.359 + request = self.page.request
2.360 + pagename = self.get_item_path(next)
2.361 +
2.362 + # To add a page with an ACL, a special policy is required.
2.363 +
2.364 + may = request.user.may
2.365 + request.user.may = SpecialPermissionsForPage(request.user, pagename)
2.366 +
2.367 + # Attempt to save the page, copying any ACL.
2.368 +
2.369 + try:
2.370 + page = PageEditor(request, pagename)
2.371 + page.saveText(item, 0)
2.372 +
2.373 + # Restore the original policy.
2.374 +
2.375 + finally:
2.376 + request.user.may = may
2.377 +
2.378 + def read_item(self, number):
2.379 +
2.380 + "Read the item with the given item 'number'."
2.381 +
2.382 + page = Page(self.page.request, self.get_item_path(number))
2.383 + return page.get_raw_body()
2.384 +
2.385 + def remove_item(self, number):
2.386 +
2.387 + "Remove the item with the given item 'number'."
2.388 +
2.389 + page = PageEditor(self.page.request, self.get_item_path(number))
2.390 + page.deletePage()
2.391 +
2.392 + def get_item_path(self, number):
2.393 +
2.394 + "Get the path for the given item 'number'."
2.395 +
2.396 + return "%s/%s" % (self.page.page_name, number)
2.397 +
2.398 + # High-level methods.
2.399 +
2.400 + def append(self, item):
2.401 +
2.402 + "Append the given 'item' to the store."
2.403 +
2.404 + self.writelock.acquire()
2.405 + try:
2.406 + next = self.get_next()
2.407 + self.write_item(item, next)
2.408 + finally:
2.409 + self.writelock.release()
2.410 +
2.411 class ItemIterator:
2.412
2.413 "An iterator over items in a store."
2.414 @@ -242,4 +405,139 @@
2.415 def __iter__(self):
2.416 return self
2.417
2.418 +def getDirectoryItemStoreForPage(page, item_dir, lock_dir):
2.419 +
2.420 + """
2.421 + A convenience function returning a directory-based store for the given
2.422 + 'page', using the given 'item_dir' and 'lock_dir'.
2.423 + """
2.424 +
2.425 + item_dir_path = tuple(item_dir.split("/"))
2.426 + lock_dir_path = tuple(lock_dir.split("/"))
2.427 + return DirectoryItemStore(page.getPagePath(*item_dir_path), page.getPagePath(*lock_dir_path))
2.428 +
2.429 +def getSubpageItemStoreForPage(page, lock_dir):
2.430 +
2.431 + """
2.432 + A convenience function returning a subpage-based store for the given
2.433 + 'page', using the given 'lock_dir'.
2.434 + """
2.435 +
2.436 + lock_dir_path = tuple(lock_dir.split("/"))
2.437 + return SubpageItemStore(page, page.getPagePath(*lock_dir_path))
2.438 +
2.439 +# Page-oriented item store classes.
2.440 +
2.441 +class ItemStoreBase:
2.442 +
2.443 + "Access item stores via pages, observing page access restrictions."
2.444 +
2.445 + def __init__(self, page, store):
2.446 + self.page = page
2.447 + self.store = store
2.448 +
2.449 + def can_write(self):
2.450 +
2.451 + """
2.452 + Return whether the user associated with the request can write to the
2.453 + page owning this store.
2.454 + """
2.455 +
2.456 + user = self.page.request.user
2.457 + return user and user.may.write(self.page.page_name)
2.458 +
2.459 + def can_read(self):
2.460 +
2.461 + """
2.462 + Return whether the user associated with the request can read from the
2.463 + page owning this store.
2.464 + """
2.465 +
2.466 + user = self.page.request.user
2.467 + return user and user.may.read(self.page.page_name)
2.468 +
2.469 + def can_delete(self):
2.470 +
2.471 + """
2.472 + Return whether the user associated with the request can delete the
2.473 + page owning this store.
2.474 + """
2.475 +
2.476 + user = self.page.request.user
2.477 + return user and user.may.delete(self.page.page_name)
2.478 +
2.479 + # Store-specific methods.
2.480 +
2.481 + def mtime(self):
2.482 + return self.store.mtime()
2.483 +
2.484 + # High-level methods.
2.485 +
2.486 + def keys(self):
2.487 +
2.488 + "Return a list of keys for items in the store."
2.489 +
2.490 + if not self.can_read():
2.491 + return 0
2.492 +
2.493 + return self.store.keys()
2.494 +
2.495 + def append(self, item):
2.496 +
2.497 + "Append the given 'item' to the store."
2.498 +
2.499 + if not self.can_write():
2.500 + return
2.501 +
2.502 + self.store.append(item)
2.503 +
2.504 + def __len__(self):
2.505 +
2.506 + "Return the number of items in the store."
2.507 +
2.508 + if not self.can_read():
2.509 + return 0
2.510 +
2.511 + return len(self.store)
2.512 +
2.513 + def __getitem__(self, number):
2.514 +
2.515 + "Return the item with the given 'number'."
2.516 +
2.517 + if not self.can_read():
2.518 + raise IndexError, number
2.519 +
2.520 + return self.store.__getitem__(number)
2.521 +
2.522 + def __delitem__(self, number):
2.523 +
2.524 + "Remove the item with the given 'number'."
2.525 +
2.526 + if not self.can_delete():
2.527 + return
2.528 +
2.529 + return self.store.__delitem__(number)
2.530 +
2.531 + def __iter__(self):
2.532 + return self.store.__iter__()
2.533 +
2.534 + def next(self):
2.535 + return self.store.next()
2.536 +
2.537 +# Convenience store classes.
2.538 +
2.539 +class ItemStore(ItemStoreBase):
2.540 +
2.541 + "Store items in a directory via a page."
2.542 +
2.543 + def __init__(self, page, item_dir="items", lock_dir="item_locks"):
2.544 + ItemStoreBase.__init__(self, page, getDirectoryItemStoreForPage(page, item_dir, lock_dir))
2.545 +
2.546 +class ItemSubpageStore(ItemStoreBase):
2.547 +
2.548 + "Store items in subpages of a page."
2.549 +
2.550 + def __init__(self, page, lock_dir="item_locks"):
2.551 + ItemStoreBase.__init__(self, page, getSubpageItemStoreForPage(page, lock_dir))
2.552 +
2.553 # vim: tabstop=4 expandtab shiftwidth=4
3.1 --- a/MoinSupport.py Mon Nov 11 13:42:06 2013 +0100
3.2 +++ b/MoinSupport.py Tue Jan 21 18:26:01 2014 +0100
3.3 @@ -2,7 +2,7 @@
3.4 """
3.5 MoinMoin - MoinSupport library (derived from EventAggregatorSupport)
3.6
3.7 - @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk>
3.8 + @copyright: 2008, 2009, 2010, 2011, 2012, 2013, 2014 by Paul Boddie <paul@boddie.org.uk>
3.9 @copyright: 2000-2004 Juergen Hermann <jh@web.de>
3.10 2004 by Florian Festi
3.11 2006 by Mikko Virkkil
3.12 @@ -13,10 +13,8 @@
3.13 """
3.14
3.15 from DateSupport import *
3.16 -from ItemSupport import ItemDirectoryStore
3.17 from MoinMoin.parser import text_moin_wiki
3.18 from MoinMoin.Page import Page
3.19 -from MoinMoin.util import lock
3.20 from MoinMoin import config, search, wikiutil
3.21 from shlex import shlex
3.22 import re
3.23 @@ -36,7 +34,7 @@
3.24 except ImportError:
3.25 pass
3.26
3.27 -__version__ = "0.4.1"
3.28 +__version__ = "0.5"
3.29
3.30 # Extraction of shared fragments.
3.31
3.32 @@ -192,6 +190,12 @@
3.33 else:
3.34 return None
3.35
3.36 +def groupHasMember(request, groupname, username):
3.37 + if hasattr(request.dicts, "has_member"):
3.38 + return request.dicts.has_member(groupname, username)
3.39 + else:
3.40 + return username in request.groups.get(groupname, [])
3.41 +
3.42 # Searching-related functions.
3.43
3.44 def getPagesFromResults(result_pages, request):
3.45 @@ -912,6 +916,17 @@
3.46 buf.close()
3.47 return unicode(text, "utf-8")
3.48
3.49 +class RawParser:
3.50 +
3.51 + "A parser that just formats everything as text."
3.52 +
3.53 + def __init__(self, raw, request, **kw):
3.54 + self.raw = raw
3.55 + self.request = request
3.56 +
3.57 + def format(self, fmt, write=None):
3.58 + (write or self.request.write)(fmt.text(self.raw))
3.59 +
3.60 # Finding components for content types.
3.61
3.62 def getParsersForContentType(cfg, mimetype):
3.63 @@ -1091,87 +1106,4 @@
3.64 else:
3.65 return title
3.66
3.67 -# Content storage support.
3.68 -
3.69 -class ItemStore(ItemDirectoryStore):
3.70 -
3.71 - "A page-specific item store."
3.72 -
3.73 - def __init__(self, page, item_dir="items", lock_dir="item_locks"):
3.74 -
3.75 - "Initialise an item store for the given 'page'."
3.76 -
3.77 - item_dir_path = tuple(item_dir.split("/"))
3.78 - lock_dir_path = tuple(lock_dir.split("/"))
3.79 - ItemDirectoryStore.__init__(self, page.getPagePath(*item_dir_path), page.getPagePath(*lock_dir_path))
3.80 - self.page = page
3.81 -
3.82 - def can_write(self):
3.83 -
3.84 - """
3.85 - Return whether the user associated with the request can write to the
3.86 - page owning this store.
3.87 - """
3.88 -
3.89 - user = self.page.request.user
3.90 - return user and user.may.write(self.page.page_name)
3.91 -
3.92 - def can_read(self):
3.93 -
3.94 - """
3.95 - Return whether the user associated with the request can read from the
3.96 - page owning this store.
3.97 - """
3.98 -
3.99 - user = self.page.request.user
3.100 - return user and user.may.read(self.page.page_name)
3.101 -
3.102 - def can_delete(self):
3.103 -
3.104 - """
3.105 - Return whether the user associated with the request can delete the
3.106 - page owning this store.
3.107 - """
3.108 -
3.109 - user = self.page.request.user
3.110 - return user and user.may.delete(self.page.page_name)
3.111 -
3.112 - # High-level methods.
3.113 -
3.114 - def append(self, item):
3.115 -
3.116 - "Append the given 'item' to the store."
3.117 -
3.118 - if not self.can_write():
3.119 - return
3.120 -
3.121 - ItemDirectoryStore.append(self, item)
3.122 -
3.123 - def __len__(self):
3.124 -
3.125 - "Return the number of items in the store."
3.126 -
3.127 - if not self.can_read():
3.128 - return 0
3.129 -
3.130 - return ItemDirectoryStore.__len__(self)
3.131 -
3.132 - def __getitem__(self, number):
3.133 -
3.134 - "Return the item with the given 'number'."
3.135 -
3.136 - if not self.can_read():
3.137 - raise IndexError, number
3.138 -
3.139 - return ItemDirectoryStore.__getitem__(self, number)
3.140 -
3.141 - def __delitem__(self, number):
3.142 -
3.143 - "Remove the item with the given 'number'."
3.144 -
3.145 - if not self.can_delete():
3.146 - return
3.147 -
3.148 - return ItemDirectoryStore.__delitem__(self, number)
3.149 -
3.150 # vim: tabstop=4 expandtab shiftwidth=4
4.1 --- a/PKG-INFO Mon Nov 11 13:42:06 2013 +0100
4.2 +++ b/PKG-INFO Tue Jan 21 18:26:01 2014 +0100
4.3 @@ -1,12 +1,12 @@
4.4 Metadata-Version: 1.1
4.5 Name: MoinSupport
4.6 -Version: 0.4.1
4.7 +Version: 0.5
4.8 Author: Paul Boddie
4.9 Author-email: paul at boddie org uk
4.10 Maintainer: Paul Boddie
4.11 Maintainer-email: paul at boddie org uk
4.12 Home-page: http://hgweb.boddie.org.uk/MoinSupport
4.13 -Download-url: http://hgweb.boddie.org.uk/MoinSupport/archive/rel-0-4-1.tar.bz2
4.14 +Download-url: http://hgweb.boddie.org.uk/MoinSupport/archive/rel-0-5.tar.bz2
4.15 Summary: Support libraries for MoinMoin extensions
4.16 License: GPL (version 2 or later)
4.17 Description: The MoinSupport distribution provides libraries handling datetime
5.1 --- a/README.txt Mon Nov 11 13:42:06 2013 +0100
5.2 +++ b/README.txt Tue Jan 21 18:26:01 2014 +0100
5.3 @@ -5,8 +5,7 @@
5.4 extensions. Some of the provided modules can be used independently of
5.5 MoinMoin, such as the ContentTypeSupport, DateSupport, GeneralSupport,
5.6 LocationSupport and ViewSupport modules which do not themselves import any
5.7 -MoinMoin functionality. The ItemSupport module only imports file-locking
5.8 -functionality from MoinMoin and could potentially be used independently.
5.9 +MoinMoin functionality.
5.10
5.11 Installation
5.12 ------------
5.13 @@ -64,6 +63,17 @@
5.14 If time zone handling is not required, pytz need not be installed. It is,
5.15 however, highly recommended that pytz be installed.
5.16
5.17 +New in MoinSupport 0.5 (Changes since MoinSupport 0.4.1)
5.18 +--------------------------------------------------------
5.19 +
5.20 + * Moved ItemStore and related functionality into ItemSupport.
5.21 + * Added support for subpage-based item stores.
5.22 + * Added groupHasMember from ApproveChanges.
5.23 + * Added the TokenSupport module to try and have a reliable shell-like
5.24 + tokeniser.
5.25 + * Added RFC 2822 datetime formatting.
5.26 + * Added a "raw" parser which just formats its input as text.
5.27 +
5.28 New in MoinSupport 0.4.1 (Changes since MoinSupport 0.4)
5.29 --------------------------------------------------------
5.30
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/TokenSupport.py Tue Jan 21 18:26:01 2014 +0100
6.3 @@ -0,0 +1,109 @@
6.4 +# -*- coding: iso-8859-1 -*-
6.5 +"""
6.6 + MoinMoin - TokenSupport library
6.7 +
6.8 + @copyright: 2013 by Paul Boddie <paul@boddie.org.uk>
6.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
6.10 +"""
6.11 +
6.12 +import re
6.13 +
6.14 +identifier_expr = re.compile(
6.15 + """(?P<non_literal>[^'" ]+)"""
6.16 + "|"
6.17 + "(?P<spaces> +)"
6.18 + "|"
6.19 + "(?P<literal1>'[^']*')"
6.20 + "|"
6.21 + '(?P<literal2>"[^"]*")'
6.22 + )
6.23 +
6.24 +def getIdentifiers(s, doubling=False):
6.25 +
6.26 + """
6.27 + Return 's' containing space-separated quoted identifiers, parsed into
6.28 + regions that hold the individual identifiers. The optional 'doubling'
6.29 + argument can be used to support convenient quote doubling to reproduce
6.30 + single quote characters.
6.31 +
6.32 + Quoting of identifiers can be done using the single-quote and double-quote
6.33 + characters in order to include spaces within identifiers. For example:
6.34 +
6.35 + 'contains space'
6.36 + -> contains space (a single identifier)
6.37 +
6.38 + Where one kind of quote (or apostrophe) is to be included in an identifier,
6.39 + the other quoting character can be used to delimit the identifier. For
6.40 + example:
6.41 +
6.42 + "Python's syntax"
6.43 + -> Python's syntax (a single identifier)
6.44 +
6.45 + Where the 'doubling' argument is set to a true value, a quote character can
6.46 + be doubled to include it in an identifier. For example:
6.47 +
6.48 + Python''s syntax
6.49 + -> Python's syntax (a single identifier)
6.50 +
6.51 + Where a mixture of quotes is required in a single identifier, adjacent
6.52 + quoted regions can be used. For example:
6.53 +
6.54 + "Python's "'"intuitive" syntax'
6.55 + -> "Python's " (region #1)
6.56 + + '"intuitive" syntax' (region #2)
6.57 + -> Python's "intuitive" syntax (a single identifier)
6.58 +
6.59 + Where unquoted regions are adjacent to quoted regions, the regions are
6.60 + combined. For example:
6.61 +
6.62 + "Python's "intuitive" syntax"
6.63 + -> "Python's " (region #1)
6.64 + + intuitive (region #2)
6.65 + + " syntax" (region #3)
6.66 + -> Python's intuitive syntax (a single identifier)
6.67 + """
6.68 +
6.69 + regions = []
6.70 + in_literal = False
6.71 +
6.72 + for match in identifier_expr.finditer(s):
6.73 + non_literal, spaces, literal1, literal2 = match.groups()
6.74 +
6.75 + identifier = None
6.76 +
6.77 + # Spaces prevent continuation of identifier regions.
6.78 +
6.79 + if spaces:
6.80 + in_literal = False
6.81 +
6.82 + # Unquoted regions contribute to the current identifier.
6.83 +
6.84 + if non_literal and non_literal.strip():
6.85 + identifier = non_literal.strip()
6.86 +
6.87 + # Quoted regions also contribute to the current identifier.
6.88 +
6.89 + for s in (literal1, literal2):
6.90 + if s is not None:
6.91 +
6.92 + # Either strip the quoting or for empty regions, adopt the
6.93 + # quote character.
6.94 +
6.95 + if not doubling or len(s) > 2:
6.96 + identifier = s[1:-1]
6.97 + elif doubling:
6.98 + identifier = s[0]
6.99 +
6.100 + # Either continue or add an identifier, and indicate possible
6.101 + # continuation.
6.102 +
6.103 + if identifier:
6.104 + if in_literal:
6.105 + regions[-1] += identifier
6.106 + else:
6.107 + regions.append(identifier)
6.108 + in_literal = True
6.109 +
6.110 + return regions
6.111 +
6.112 +# vim: tabstop=4 expandtab shiftwidth=4
7.1 --- a/setup.py Mon Nov 11 13:42:06 2013 +0100
7.2 +++ b/setup.py Tue Jan 21 18:26:01 2014 +0100
7.3 @@ -8,8 +8,9 @@
7.4 author = "Paul Boddie",
7.5 author_email = "paul@boddie.org.uk",
7.6 url = "http://hgweb.boddie.org.uk/MoinSupport",
7.7 - version = "0.4.1",
7.8 + version = "0.5",
7.9 py_modules = ["ContentTypeSupport", "DateSupport", "GeneralSupport",
7.10 "ItemSupport", "LocationSupport", "MoinDateSupport",
7.11 - "MoinRemoteSupport", "MoinSupport", "ViewSupport"]
7.12 + "MoinRemoteSupport", "MoinSupport", "TokenSupport",
7.13 + "ViewSupport"]
7.14 )
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/tests/test_tokens.py Tue Jan 21 18:26:01 2014 +0100
8.3 @@ -0,0 +1,19 @@
8.4 +#!/usr/bin/env python
8.5 +
8.6 +from TokenSupport import getIdentifiers
8.7 +
8.8 +tests = [
8.9 + (1, False, """'contains space'""", ["contains space"]),
8.10 + (2, False, """contains space""", ["contains", "space"]),
8.11 + (1, False, '''"Python's syntax"''', ["Python's syntax"]),
8.12 + (2, False, """Python''s syntax""", ["Pythons", "syntax"]),
8.13 + (2, True, """Python''s syntax""", ["Python's", "syntax"]),
8.14 + (1, False, '''"Python's "'"intuitive" syntax' ''', ['''Python's "intuitive" syntax''']),
8.15 + (1, False, '''"Python's "intuitive" syntax" ''', ['''Python's intuitive syntax''']),
8.16 + ]
8.17 +
8.18 +for n, doubling, s, e in tests:
8.19 + l = getIdentifiers(s, doubling)
8.20 + print l == e, l, "==", e, len(l) == n, len(l), "==", n, "<-", doubling, s
8.21 +
8.22 +# vim: tabstop=4 expandtab shiftwidth=4