def test__render_data_diff(self): item_name = 'Html_Item' fqname = split_fqname(item_name) empty_html = '<span></span>' html = '<span>\ud55c</span>' meta = {CONTENTTYPE: 'text/html;charset=utf-8'} item = Item.create(item_name) item._save(meta, empty_html) item = Item.create(item_name) # Unicode test, html escaping rev1 = update_item(item_name, meta, html) rev2 = update_item(item_name, {}, ' ') result = Text._render_data_diff(item.content, rev1, rev2, fqname=fqname) assert escape(html) in result # Unicode test, whitespace rev1 = update_item(item_name, {}, '\n\n') rev2 = update_item(item_name, {}, '\n \n') result = Text._render_data_diff(item.content, rev1, rev2, fqname=fqname) assert '<span> </span>' in result # If fairly similar diffs are correctly spanned or not, also check indent rev1 = update_item(item_name, {}, 'One Two Three Four\nSix\n\ud55c') rev2 = update_item(item_name, {}, 'Two Three Seven Four\nSix\n\ud55c') result = Text._render_data_diff(item.content, rev1, rev2, fqname=fqname) assert '<span>One </span>Two Three Four' in result assert 'Two Three <span>Seven </span>Four' in result # Check for diff_html.diff return types assert reduce(lambda x, y: x and y, [isinstance(i[1], str) and isinstance(i[3], str) for i in diff_html.diff('One Two Three Four\nSix\n', 'Two Three Seven Four\nSix Seven\n')], True)
def test_namespaces(self): item_name_n = 'normal' item = self.imw[item_name_n] rev_n = item.store_revision(dict( name=[ item_name_n, ], contenttype='text/plain;charset=utf-8'), BytesIO(item_name_n.encode('utf-8')), return_rev=True) item_name_u = '%s/user' % NAMESPACE_USERS fqname_u = split_fqname(item_name_u) item = self.imw.get_item(**fqname_u.query) rev_u = item.store_revision(dict( name=[fqname_u.value], namespace=fqname_u.namespace, contenttype='text/plain;charset=utf-8'), BytesIO(item_name_u.encode('utf-8')), return_rev=True) item = self.imw[item_name_n] rev_n = item.get_revision(rev_n.revid) assert rev_n.meta[NAMESPACE] == '' assert rev_n.meta[NAME] == [ item_name_n, ] item = self.imw[item_name_u] rev_u = item.get_revision(rev_u.revid) assert rev_u.meta[NAMESPACE] == NAMESPACE_USERS assert rev_u.meta[NAME] == [item_name_u.split('/')[1]]
def location_breadcrumbs(self, fqname): """ Split the incoming fqname into segments. Reassemble into a list of tuples. If the fqname has a namespace, the first tuple's segment_name will have the namespace as a prefix. :rtype: list :returns: location breadcrumbs items in tuple (segment_name, fq_name, exists) """ breadcrumbs = [] current_item = '' if not isinstance(fqname, CompositeName): fqname = split_fqname(fqname) if fqname.field != NAME_EXACT: return [ (fqname, fqname, bool(self.storage.get_item(**fqname.query))) ] # flaskg.unprotected_storage.get_item(**fqname.query) namespace = segment1_namespace = fqname.namespace item_name = fqname.value if not item_name: return breadcrumbs for segment in item_name.split('/'): current_item += segment fq_current = CompositeName(namespace, NAME_EXACT, current_item) fq_segment = CompositeName(segment1_namespace, NAME_EXACT, segment) breadcrumbs.append( (fq_segment, fq_current, bool(self.storage.get_item(**fq_current.query)))) current_item += '/' segment1_namespace = '' return breadcrumbs
def modify_acl(item_name): fqname = split_fqname(item_name) item = Item.create(item_name) meta = dict(item.meta) old_acl = meta.get(ACL, '') new_acl = request.form.get(fqname.fullname) is_valid = acl_validate(new_acl) if is_valid: if new_acl in ('Empty', ''): meta[ACL] = '' elif new_acl == 'None' and ACL in meta: del (meta[ACL]) else: meta[ACL] = new_acl try: item._save(meta=meta) except AccessDenied: # superuser viewed item acl report and tried to change acl but lacked admin permission flash( L_("Failed! Not authorized.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=old_acl), "error") return redirect(url_for('.item_acl_report')) flash( L_("Success! ACL saved.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "info") else: flash( L_("Nothing changed, invalid ACL.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "error") return redirect(url_for('.item_acl_report'))
def subitem_index(self, fqname): """ Get a list of subitems for the given fqname :rtype: list :returns: list of item tuples (item_name, item_title, item_mime_type, has_children) """ from moin.items import Item if not isinstance(fqname, CompositeName): fqname = split_fqname(fqname) item = Item.create(fqname.fullname) return item.get_mixed_index()
def test_split_fqname(self): app.cfg.namespace_mapping = [('', 'default_backend'), ('ns1/', 'default_backend'), ('ns1/ns2/', 'other_backend')] tests = [('ns1/ns2/@itemid/SomeItemID', ('ns1/ns2', 'itemid', 'SomeItemID')), ('ns3/@itemid/SomeItemID', ('', 'name_exact', 'ns3/@itemid/SomeItemID')), ('Page', ('', 'name_exact', 'Page')), ('ns1/ns2/@tags/SomeTag', ('ns1/ns2', 'tags', 'SomeTag')), ('@tags/SomeTag', ('', 'tags', 'SomeTag')), ('ns1/ns2/@notid', ('ns1/ns2', 'name_exact', '@notid')), ('ns1/ns2/ns3/Thisisapagename/ns4', ('ns1/ns2', 'name_exact', 'ns3/Thisisapagename/ns4')), ] for url, (namespace, field, pagename) in tests: assert split_fqname(url) == (namespace, field, pagename)
def may(self, fqname, capability, usernames=None): if usernames is not None and isinstance(usernames, (bytes, str)): # we got a single username (maybe bytes), make a list of str: if isinstance(usernames, bytes): usernames = usernames.decode('utf-8') usernames = [usernames, ] # TODO Make sure that fqname must be a CompositeName class instance, not unicode or list. fqname = fqname[0] if isinstance(fqname, list) else fqname if isinstance(fqname, str): fqname = split_fqname(fqname) item = self.get_item(**fqname.query) allowed = item.allows(capability, user_names=usernames) return allowed
def do_show(self, revid): """ Show a blog item and a list of its blog entries below it. If tag GET-parameter is defined, the list of blog entries consists only of those entries that contain the tag value in their lists of tags. """ # for now it is just one tag=value, later it could be tag=value1&tag=value2&... tag = request.values.get('tag') prefix = self.name + '/' current_timestamp = int(time.time()) terms = [ Term(WIKINAME, app.cfg.interwikiname), # Only blog entry itemtypes Term(ITEMTYPE, ITEMTYPE_BLOG_ENTRY), # Only sub items of this item Prefix(NAME_EXACT, prefix), ] if tag: terms.append(Term(TAGS, tag)) query = And(terms) def ptime_sort_key(searcher, docnum): """ Compute the publication time key for blog entries sorting. If PTIME is not defined, we use MTIME. """ fields = searcher.stored_fields(docnum) ptime = fields.get(PTIME, fields[MTIME]) return ptime ptime_sort_facet = FunctionFacet(ptime_sort_key) revs = flaskg.storage.search(query, sortedby=ptime_sort_facet, reverse=True, limit=None) blog_entry_items = [ Item.create(rev.name, rev_id=rev.revid) for rev in revs ] return render_template( 'blog/main.html', item_name=self.name, fqname=split_fqname(self.name), blog_item=self, blog_entry_items=blog_entry_items, tag=tag, item=self, )
def create(cls, name=u'', itemtype=None, contenttype=None, rev_id=CURRENT, item=None): """ Create a highlevel Item by looking up :name or directly wrapping :item and extract the Revision designated by :rev_id revision. The highlevel Item is created by creating an instance of Content subclass according to the item's contenttype metadata entry; The :contenttype argument can be used to override contenttype. It is used only when handling +convert (when deciding the contenttype of target item), +modify (when creating a new item whose contenttype is not yet decided), +diff and +diffraw (to coerce the Content to a common super-contenttype of both revisions). After that the Content instance, an instance of Item subclass is created according to the item's itemtype metadata entry, and the previously created Content instance is assigned to its content property. """ fqname = split_fqname(name) if fqname.field not in UFIELDS: # Need a unique key to extract stored item. raise FieldNotUniqueError("field {0} is not in UFIELDS".format( fqname.field)) rev = get_storage_revision(fqname, itemtype, contenttype, rev_id, item) contenttype = rev.meta.get(CONTENTTYPE) or contenttype logging.debug( "Item {0!r}, got contenttype {1!r} from revision meta".format( name, contenttype)) # logging.debug("Item %r, rev meta dict: %r" % (name, dict(rev.meta))) # XXX Cannot pass item=item to Content.__init__ via # content_registry.get yet, have to patch it later. content = Content.create(contenttype) itemtype = rev.meta.get(ITEMTYPE) or itemtype or ITEMTYPE_DEFAULT logging.debug( "Item {0!r}, got itemtype {1!r} from revision meta".format( name, itemtype)) item = item_registry.get(itemtype, fqname, rev=rev, content=content) logging.debug("Item class {0!r} handles {1!r}".format( item.__class__, itemtype)) content.item = item return item
def run(self, name, meta_file, data_file, revid): fqname = split_fqname(name) item = app.storage.get_item(**fqname.query) rev = item[revid] meta = json.dumps(dict(rev.meta), sort_keys=True, indent=2, ensure_ascii=False) meta = meta.encode('utf-8') meta_lines = meta.split('\n') meta_lines = [x.rstrip() for x in meta_lines] meta = '\n'.join(meta_lines) with open(meta_file, 'wb') as mf: mf.write(meta) data = rev.data.read().replace('\r\n', '\n') with open(data_file, 'wb') as df: df.write(data)
def modify_acl(item_name): fqname = split_fqname(item_name) item = Item.create(item_name) meta = dict(item.meta) new_acl = request.form.get(fqname.fullname) is_valid = acl_validate(new_acl) if is_valid: if new_acl in ('Empty', ''): meta[ACL] = '' elif new_acl == 'None' and ACL in meta: del(meta[ACL]) else: meta[ACL] = new_acl item._save(meta=meta) flash(L_("Success! ACL saved.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "info") else: flash(L_("Nothing changed, invalid ACL.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "error") return redirect(url_for('.item_acl_report'))
def run(self, name, meta_file, data_file, revid): fqname = split_fqname(name) item = app.storage.get_item(**fqname.query) rev = item[revid] meta = json.dumps(dict(rev.meta), sort_keys=True, indent=2, ensure_ascii=False) with open(meta_file, 'w', encoding='utf-8') as mf: mf.write(meta) if 'charset' in rev.meta['contenttype']: # input data will have \r\n line endings, output will have platform dependent line endings charset = rev.meta['contenttype'].split('charset=')[1] data = rev.data.read().decode(charset) lines = data.splitlines() lines = '\n'.join(lines) with open(data_file, 'w', encoding=charset) as df: df.write(lines) return data = rev.data.read() with open(data_file, 'wb') as df: df.write(data)
def navibar(self, fqname): """ Assemble the navibar :rtype: list :returns: list of tuples (css_class, url, link_text, title) """ if not isinstance(fqname, CompositeName): fqname = split_fqname(fqname) item_name = fqname.value current = item_name # Process config navi_bar items = [] for cls, endpoint, args, link_text, title in self.cfg.navi_bar: if endpoint == "frontend.show_root": endpoint = "frontend.show_item" root_fqname = fqname.get_root_fqname() default_root = app.cfg.root_mapping.get( NAMESPACE_DEFAULT, app.cfg.default_root) args[ 'item_name'] = root_fqname.fullname if fqname.namespace != NAMESPACE_ALL else default_root # override link_text to show untranslated <default_root> itemname or <namespace>/<default_root> link_text = args['item_name'] elif endpoint in [ "frontend.global_history", "frontend.global_tags" ]: args['namespace'] = fqname.namespace if fqname and fqname.namespace: link_text = '{0}/{1}'.format(fqname.namespace, link_text) elif endpoint == "frontend.index": args['item_name'] = fqname.namespace if fqname and fqname.namespace: link_text = '{0}/{1}'.format(fqname.namespace, link_text) elif endpoint == "admin.index" and not getattr( flaskg.user.may, SUPERUSER)(): continue items.append((cls, url_for(endpoint, **args), link_text, title)) # Add user links to wiki links. for text in self.user.quicklinks: url, link_text, title = self.split_navilink(text) items.append(('userlink', url, link_text, title)) # Add sister pages (see http://usemod.com/cgi-bin/mb.pl?SisterSitesImplementationGuide ) for sistername, sisterurl in self.cfg.sistersites: if is_local_wiki(sistername): items.append(('sisterwiki current', sisterurl, sistername, '')) else: cid = cache_key(usage="SisterSites", sistername=sistername) sisteritems = app.cache.get(cid) if sisteritems is None: uo = urllib.request.URLopener() uo.version = 'MoinMoin SisterItem list fetcher 1.0' try: sisteritems = {} f = uo.open(sisterurl) for line in f: line = line.strip() try: item_url, item_name = line.split(' ', 1) sisteritems[item_name.decode( 'utf-8')] = item_url except Exception: pass # ignore invalid lines f.close() app.cache.set(cid, sisteritems) logging.info( "Site: {0!r} Status: Updated. Pages: {1}".format( sistername, len(sisteritems))) except IOError as err: (title, code, msg, headers) = err.args # code e.g. 304 logging.warning( "Site: {0!r} Status: Not updated.".format( sistername)) logging.exception("exception was:") if current in sisteritems: url = sisteritems[current] items.append(('sisterwiki', url, sistername, '')) return items
def custom_setup(self): self.imw = flaskg.unprotected_storage self.item_name = "foo" self.fqname = split_fqname(self.item_name)
def macro(self, content, arguments, page_url, alternative): # defaults item = None startswith = "" regex = None ordered = False display = "FullPath" skiptag = "" # process input args = [] if arguments: args = arguments[0].split(',') for arg in args: try: key, val = [x.strip() for x in arg.split('=')] except ValueError: raise ValueError(_('ItemList macro: Argument "%s" does not follow <key>=<val> format (arguments, if more than one, must be comma-separated).' % arg)) if len(val) < 2 or (val[0] != "'" and val[0] != '"') and val[-1] != val[0]: raise ValueError(_("ItemList macro: The key's value must be bracketed by matching quotes.")) val = val[1:-1] # strip out the doublequote characters if key == "item": item = val elif key == "startswith": startswith = val elif key == "regex": regex = val elif key == "ordered": if val == "False": ordered = False elif val == "True": ordered = True else: raise ValueError(_('ItemList macro: The value must be "True" or "False". (got "%s")' % val)) elif key == "display": display = val # let 'create_pagelink_list' throw an exception if needed elif key == "skiptag": skiptag = val else: raise KeyError(_('ItemList macro: Unrecognized key "%s".' % key)) # use curr item if not specified if item is None: item = request.path[1:] if item.startswith('+modify/'): item = item.split('/', 1)[1] # verify item exists and current user has read permission if item != "": if not flaskg.storage.get_item(**(split_fqname(item).query)): # if user lacks read permission, a 403 error was thrown on line above raise LookupError(_('ItemList macro: The specified item "%s" does not exist.' % item)) # process subitems children = self.get_item_names(item, startswith=startswith, skiptag=skiptag) if regex: try: regex_re = re.compile(regex, re.IGNORECASE) except re.error as err: raise ValueError(_("ItemList macro: Error in regex {0!r}: {1}".format(regex, err))) newlist = [] for child in children: if regex_re.search(child): newlist.append(child) children = newlist if not children: empty_list = moin_page.list(attrib={moin_page.item_label_generate: ordered and 'ordered' or 'unordered'}) item_body = moin_page.list_item_body(children=[_("<ItemList macro: No matching items were found.>")]) item = moin_page.list_item(children=[item_body]) empty_list.append(item) return empty_list return self.create_pagelink_list(children, ordered, display)
def macro(self, content, arguments, page_url, alternative): # defaults item = None startswith = "" regex = None ordered = False display = "FullPath" # process input args = [] if arguments: args = arguments[0].split(',') for arg in args: try: key, val = [x.strip() for x in arg.split('=')] except ValueError: raise ValueError( _('Argument "%s" does not follow <key>=<val> format (arguments, if more than one, must be comma-separated).' % arg)) if len(val) < 2 or (val[0] != "'" and val[0] != '"') and val[-1] != val[0]: raise ValueError( _("The key's value must be bracketed by matching quotes.")) val = val[1:-1] # strip out the doublequote characters if key == "item": item = val elif key == "startswith": startswith = val elif key == "regex": regex = val elif key == "ordered": if val == "False": ordered = False elif val == "True": ordered = True else: raise ValueError( _('The value must be "True" or "False". (got "%s")' % val)) elif key == "display": display = val # let 'create_pagelink_list' throw an exception if needed else: raise KeyError(_('Unrecognized key "%s".' % key)) # use curr page if not specified if item is None: item = request.path[1:] # test if item doesn't exist (potentially due to user's ACL, but that doesn't matter) if item != "": # why are we retaining this behavior from PagenameList? if not flaskg.storage.get_item(**(split_fqname(item).query)): raise LookupError( _('The specified item "%s" does not exist.' % item)) # process child pages children = self.get_item_names(item, startswith) if regex: try: regex_re = re.compile(regex, re.IGNORECASE) except re.error as err: raise ValueError( _("Error in regex {0!r}: {1}".format(regex, err))) newlist = [] for child in children: if regex_re.search(child): newlist.append(child) children = newlist if not children: empty_list = moin_page.list( attrib={ moin_page.item_label_generate: ordered and 'ordered' or 'unordered' }) item_body = moin_page.list_item_body( children=[_("<No matching pages were found>")]) item = moin_page.list_item(children=[item_body]) empty_list.append(item) return empty_list return self.create_pagelink_list(children, ordered, display)
def macro(self, content, arguments, page_url, alternative): # find page name of current page # to be able to use it as a default page name this_page = request.path[1:] if this_page.startswith('+modify/'): this_page = this_page.split('/', 1)[1] # get arguments from the macro if available, # arguments will be None for <<MonthCalendar>> # and <<MonthCalendar()>> args = arguments[0] if arguments else "" # get default arguments for year and month # and current day for calendar styling currentyear = datetime.now().year currentmonth = datetime.now().month currentday = datetime.now().day # parse and check arguments, # set default values if necessary parmpagename, parmyear, parmmonth, parmoffset, parmheight6, anniversary = \ parseargs(args, this_page, currentyear, currentmonth, 0, False, False) year, month = yearmonthplusoffset(parmyear, parmmonth, parmoffset) # get the calendar monthcal = calendar.monthcalendar(year, month) # european / US differences months = (_('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')) # Set things up for Monday or Sunday as the first day of the week if calendar.firstweekday() == calendar.MONDAY: wkend = (5, 6) wkdays = (_('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat'), _('Sun')) if calendar.firstweekday() == calendar.SUNDAY: wkend = (0, 6) wkdays = (_('Sun'), _('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat')) calcaption = "{} {}".format(months[month - 1], year) calhead = [] r7 = range(7) for wkday in r7: wday = _(wkdays[wkday]) if wkday in wkend: day_class = "cal-weekend" else: day_class = "cal-workday" calhead.append((wday, day_class)) # parmheight6 true: show 6 week rows even if month has 5 weeks only if parmheight6: while len(monthcal) < 6: monthcal = monthcal + [[0, 0, 0, 0, 0, 0, 0]] calrows = [] for week in monthcal: calweek = [] for wkday in r7: day = week[wkday] day_addr = '' day_class = 'cal-emptyday' if not day: # u'\xa0' is a non-breaking space (just like # in html) to make sure empty cells have the same # height as rows with content calweek.append((u'\xa0', None, 'cal-invalidday')) else: # we only process the first calendar (or item name) # mentioned in the macro parameters (in case separate # pages are separated by "*") page = parmpagename[0] if anniversary: link = "{:s}/{:02d}-{:02d}".format(page, month, day) else: link = "{:s}/{:4d}-{:02d}-{:02d}".format( page, year, month, day) day_addr = link if flaskg.storage.get_item( **(split_fqname(day_addr).query)): day_class = "cal-usedday" if day == currentday and month == currentmonth and year == currentyear: day_class = "cal-today" calweek.append((str(day), day_addr, day_class)) calrows.append(calweek) ret = build_dom_calendar_table(rows=calrows, head=calhead, caption=calcaption, cls='calendar') return ret