1.1 --- a/MoinContentSupport.py Mon Oct 04 01:03:07 2010 +0200
1.2 +++ b/MoinContentSupport.py Sun Feb 20 21:15:05 2011 +0100
1.3 @@ -2,14 +2,45 @@
1.4 """
1.5 MoinMoin - MoinContentSupport library
1.6
1.7 - @copyright: 2008, 2009, 2010 by Paul Boddie <paul@boddie.org.uk>
1.8 + @copyright: 2008, 2009, 2010, 2011 by Paul Boddie <paul@boddie.org.uk>
1.9 @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
1.10 2005-2008 MoinMoin:ThomasWaldmann.
1.11 @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.12 """
1.13
1.14 +from MoinMoin.wikiutil import escape
1.15 +import re
1.16 +
1.17 __version__ = "0.1"
1.18
1.19 +# Regular expressions.
1.20 +# NOTE: These overlap with ImprovedMoinSearch.
1.21 +
1.22 +heading_regexp = re.compile(r"^(?P<level>=+)\s*(?P<heading>.*?)\s*(?P=level)$", re.UNICODE | re.MULTILINE)
1.23 +
1.24 +def getHeadingDetails(body, min_level=None, max_level=None):
1.25 +
1.26 + """
1.27 + Return heading details from the given 'body' for headings with the given
1.28 + 'min_level' to 'max_level' range. Specifying None or omitting 'max_level' or
1.29 + 'min_level' removes the appropriate constraint on the range.
1.30 +
1.31 + A list of tuples containing the heading, the level, and the span (the start
1.32 + offset and the end offset as a tuple) is returned.
1.33 + """
1.34 +
1.35 + headings = []
1.36 +
1.37 + for match in heading_regexp.finditer(body):
1.38 + level = len(match.group("level"))
1.39 +
1.40 + if (min_level is None or min_level <= level) and \
1.41 + (max_level is None or level <= max_level):
1.42 +
1.43 + headings.append((match.group("heading"), level, match.span()))
1.44 +
1.45 + return headings
1.46 +
1.47 # Utility classes and associated functions.
1.48 # NOTE: These are a subset of EventAggregatorSupport.
1.49
1.50 @@ -67,4 +98,7 @@
1.51 parts = header.split(":")
1.52 self.request.headers.add(parts[0], ":".join(parts[1:]))
1.53
1.54 +def escattr(s):
1.55 + return escape(s, 1)
1.56 +
1.57 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/actions/AddLinkToPage.py Mon Oct 04 01:03:07 2010 +0200
2.2 +++ b/actions/AddLinkToPage.py Sun Feb 20 21:15:05 2011 +0100
2.3 @@ -11,10 +11,9 @@
2.4
2.5 Dependencies = ['pages']
2.6
2.7 -from MoinMoin import wikiutil
2.8 from MoinMoin.action import ActionBase
2.9 from MoinMoin.PageEditor import PageEditor
2.10 -from MoinContentSupport import ActionSupport
2.11 +from MoinContentSupport import ActionSupport, escape, escattr
2.12 import urllib
2.13 import re
2.14
2.15 @@ -77,7 +76,11 @@
2.16
2.17 "Get information from the given 'link'."
2.18
2.19 - # NOTE: Insist on remote URLs!
2.20 + # Insist on remote URLs!
2.21 + # NOTE: This could probably be done better.
2.22 +
2.23 + if not link.startswith("http:"):
2.24 + return None
2.25
2.26 try:
2.27 f = urllib.urlopen(link)
2.28 @@ -85,7 +88,7 @@
2.29 return None
2.30
2.31 try:
2.32 - s = f.read()
2.33 + s = get_text(f.read())
2.34
2.35 # Look for metadata.
2.36
2.37 @@ -101,7 +104,7 @@
2.38 intro = content
2.39
2.40 if title and intro:
2.41 - return get_text(title), get_text(intro)
2.42 + return title, intro
2.43
2.44 # Look for titles/headings and accompanying text.
2.45
2.46 @@ -117,11 +120,11 @@
2.47 for intro_match in paragraph_pattern.finditer(s[end:]):
2.48 intro = get_flattened_content(intro_match.group("text")).strip()
2.49 if intro:
2.50 - return get_text(title), get_text(intro)
2.51 + return title, intro
2.52 finally:
2.53 f.close()
2.54
2.55 - return get_text(first_title), u""
2.56 + return first_title, u""
2.57
2.58 def get_flattened_content(s):
2.59
2.60 @@ -134,7 +137,7 @@
2.61 l.append(s[last:start])
2.62 last = end
2.63 l.append(s[last:])
2.64 - return get_text("".join(l).replace("\n", " "))
2.65 + return "".join(l).replace("\n", " ")
2.66
2.67 # Action class and supporting functions.
2.68
2.69 @@ -148,15 +151,28 @@
2.70 page = self.page
2.71 form = self.get_form()
2.72
2.73 - identifier = form.get("identifier", [None])[0]
2.74 - link = form.get("link", [None])[0]
2.75 + identifier = form.get("identifier", [""])[0]
2.76 + identifier_list = []
2.77 +
2.78 + if not identifier:
2.79 +
2.80 + # Show all macro identifiers by parsing the page.
2.81 +
2.82 + page_body = page.get_raw_body()
2.83 +
2.84 + for match in macro_pattern.finditer(page_body):
2.85 + found_identifier = match.group("identifier")
2.86 + identifier_list.append('<option value="%s">%s</option>' % (
2.87 + escattr(found_identifier), escape(found_identifier)))
2.88 +
2.89 + link = form.get("link", [""])[0]
2.90 insert_before = form.get('insert_before', [""])[0]
2.91 title = ""
2.92 introduction = ""
2.93
2.94 # Acquire information from the link.
2.95
2.96 - if link is not None:
2.97 + if link:
2.98 link_info = get_link_info(link)
2.99
2.100 # NOTE: Perhaps show a message upon success/failure.
2.101 @@ -165,31 +181,59 @@
2.102 title, introduction = link_info
2.103
2.104 d = {
2.105 - "identifier" : wikiutil.escape(identifier, 1),
2.106 - "insert_before" : insert_before and "true" or "",
2.107 - "link" : wikiutil.escape(link, 1),
2.108 - "title" : wikiutil.escape(title, 1),
2.109 - "intro" : wikiutil.escape(introduction, 1),
2.110 - "url_label" : wikiutil.escape(_("URL")),
2.111 - "title_label" : wikiutil.escape(_("Title")),
2.112 - "intro_label" : wikiutil.escape(_("Introduction")),
2.113 - "description_label" : wikiutil.escape(_("Description")),
2.114 - "submit_label" : wikiutil.escape(_("Submit link")),
2.115 - "script_name" : request.getScriptname(),
2.116 - "page_url" : wikiutil.quoteWikinameURL(page.page_name)
2.117 + "buttons_html" : buttons_html,
2.118 + "identifiers_label" : escape(_("Add to position...")),
2.119 + "identifier" : escattr(identifier),
2.120 + "insert_before" : insert_before and "true" or "",
2.121 + "link" : escattr(link),
2.122 + "title" : escattr(title),
2.123 + "intro" : escattr(introduction),
2.124 + "url_label" : escape(_("URL")),
2.125 + "title_label" : escape(_("Title")),
2.126 + "intro_label" : escape(_("Introduction")),
2.127 + "description_label" : escape(_("Description")),
2.128 + "preview_label" : escape(_("Preview link")),
2.129 }
2.130
2.131 - html = u'''
2.132 -<form class="macro" method="POST" action="%(script_name)s/%(page_url)s">
2.133 - <input type="hidden" name="identifier" value="%(identifier)s" />
2.134 - <input type="hidden" name="doit" value="1" />
2.135 + # Given an identifier, preserve the state in a hidden field.
2.136 +
2.137 + if not identifier_list:
2.138 + html = '''
2.139 + <input type="hidden" name="identifier" value="%(identifier)s" />''' % d
2.140 + else:
2.141 + html = ''
2.142 +
2.143 + # Start the rest of the form.
2.144 +
2.145 + html += u'''
2.146 <input type="hidden" name="insert_before" value="%(insert_before)s" />
2.147 - <input type="hidden" name="action" value="AddLinkToPage" />
2.148 <table>
2.149 <tr>
2.150 <td class="label">%(url_label)s</td>
2.151 <td><input type="text" name="link" value="%(link)s" size="40" /></td>
2.152 - </tr>
2.153 + </tr>''' % d
2.154 +
2.155 + # Given no identifier, show the choice of insertion positions.
2.156 +
2.157 + if identifier_list:
2.158 + html += '''
2.159 + <tr>
2.160 + <td class="label">%(identifiers_label)s</td>
2.161 + <td>
2.162 + <select name="identifier">''' % d
2.163 +
2.164 + for identifier_option in identifier_list:
2.165 + html += '''
2.166 + %s''' % identifier_option
2.167 +
2.168 + html += '''
2.169 + </select>
2.170 + </td>
2.171 + </tr>'''
2.172 +
2.173 + # Finish the form.
2.174 +
2.175 + html += '''
2.176 <tr>
2.177 <td class="label">%(title_label)s</td>
2.178 <td><input type="text" name="title" value="%(title)s" size="40" /></td>
2.179 @@ -203,10 +247,12 @@
2.180 <td><input type="text" name="description" size="40" /></td>
2.181 </tr>
2.182 <tr>
2.183 - <td colspan="2"><input type="submit" value="%(submit_label)s" /></td>
2.184 + <td></td>
2.185 + <td class="buttons">
2.186 + <input type="submit" value="%(preview_label)s" />%(buttons_html)s
2.187 + </td>
2.188 </tr>
2.189 - </table>
2.190 -</form>''' % d
2.191 + </table>''' % d
2.192
2.193 return html
2.194
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/actions/SectionBreakout.py Sun Feb 20 21:15:05 2011 +0100
3.3 @@ -0,0 +1,191 @@
3.4 +# -*- coding: iso-8859-1 -*-
3.5 +"""
3.6 + MoinMoin - SectionBreakout
3.7 +
3.8 + Break sections out of a page, making new pages for each of the sections and
3.9 + replacing them with Include macros.
3.10 +
3.11 + @copyright: 2011 Paul Boddie <paul@boddie.org.uk>
3.12 + @license: GNU GPL, see COPYING for details.
3.13 +"""
3.14 +
3.15 +Dependencies = ['pages']
3.16 +
3.17 +from MoinMoin.action import ActionBase
3.18 +from MoinMoin.PageEditor import PageEditor
3.19 +from MoinContentSupport import ActionSupport, getHeadingDetails, escape, escattr
3.20 +import re
3.21 +
3.22 +# Action class and supporting functions.
3.23 +
3.24 +class SectionBreakout(ActionBase, ActionSupport):
3.25 +
3.26 + "An action breaking sections out of pages."
3.27 +
3.28 + def get_form_html(self, buttons_html):
3.29 + _ = self._
3.30 + request = self.request
3.31 + page = self.page
3.32 + form = self.get_form()
3.33 +
3.34 + level = int(form.get("level", ["2"])[0])
3.35 +
3.36 + # Acquire heading details from the page.
3.37 +
3.38 + body = page.get_raw_body()
3.39 + heading_details = getHeadingDetails(body, level, level)
3.40 +
3.41 + d = {
3.42 + "buttons_html" : buttons_html,
3.43 + "heading_level_label" : escape(_("Heading level")),
3.44 + "found_headings_label" : escape(_("Headings found in page")),
3.45 + "preview_label" : escape(_("Preview")),
3.46 + "level" : escattr(level),
3.47 + }
3.48 +
3.49 + html = u'''
3.50 + <table>
3.51 + <tr>
3.52 + <td class="label">%(heading_level_label)s</td>
3.53 + <td><input type="text" name="level" value="%(level)s" size="2" /></td>
3.54 + </tr>
3.55 + <tr>
3.56 + <td class="label">%(found_headings_label)s</td>
3.57 + <td>''' % d
3.58 +
3.59 + for heading, level, span in heading_details:
3.60 + html += "%s<br />" % heading
3.61 +
3.62 + html += '''
3.63 + </td>
3.64 + </tr>
3.65 + <tr>
3.66 + <td></td>
3.67 + <td class="buttons"><input type="submit" value="%(preview_label)s" />%(buttons_html)s</td>
3.68 + </tr>
3.69 + </table>
3.70 +''' % d
3.71 +
3.72 + return html
3.73 +
3.74 + def do_action(self):
3.75 +
3.76 + "Create the new event."
3.77 +
3.78 + _ = self._
3.79 + form = self.get_form()
3.80 +
3.81 + # A heading level must be provided.
3.82 +
3.83 + level = form.get("level", [None])[0]
3.84 +
3.85 + if not level:
3.86 + return 0, _("No heading level specified.")
3.87 +
3.88 + return self.break_out_headings(int(level))
3.89 +
3.90 + def render_success(self, msg, msgtype=None):
3.91 +
3.92 + """
3.93 + Render neither 'msg' nor 'msgtype' since redirection should occur
3.94 + instead.
3.95 + NOTE: msgtype is optional because MoinMoin 1.5.x does not support it.
3.96 + """
3.97 +
3.98 + pass
3.99 +
3.100 + def break_out_headings(self, level):
3.101 +
3.102 + """
3.103 + Break out headings at the given 'level' from the current page.
3.104 + """
3.105 +
3.106 + _ = self._
3.107 + request = self.request
3.108 + page = self.page
3.109 + formatter = request.formatter
3.110 +
3.111 + # Acquire all heading details from the page.
3.112 +
3.113 + page_body = page.get_raw_body()
3.114 + regions = []
3.115 + current_region_start = None
3.116 +
3.117 + for heading, found_level, (start, end) in getHeadingDetails(page_body):
3.118 +
3.119 + # Upon finding a suitable heading, begin a new region to be broken
3.120 + # out.
3.121 +
3.122 + if current_region_start is None and found_level >= level:
3.123 + current_region_start = heading, start
3.124 +
3.125 + # Upon finding a higher-level heading, end any open region.
3.126 +
3.127 + elif current_region_start is not None and found_level <= level:
3.128 + regions.append(current_region_start + (start,))
3.129 +
3.130 + # For headings at the requested level, open a new region.
3.131 +
3.132 + if found_level == level:
3.133 + current_region_start = heading, start
3.134 + else:
3.135 + current_region_start = None
3.136 +
3.137 + # End any open region.
3.138 +
3.139 + else:
3.140 + if current_region_start is not None:
3.141 + regions.append(current_region_start + (len(page_body),))
3.142 +
3.143 + # Make new pages for each region, rebuilding the current page body.
3.144 +
3.145 + retained_regions = []
3.146 + retained_region_start = 0
3.147 +
3.148 + for heading, start, end in regions:
3.149 +
3.150 + # Combine the page name and the heading to make a subpage.
3.151 +
3.152 + new_page_name = "%s/%s" % (page.page_name, heading)
3.153 +
3.154 + # Open the page for editing.
3.155 +
3.156 + new_page = PageEditor(request, new_page_name)
3.157 +
3.158 + # Save the new version of the page.
3.159 +
3.160 + new_page.saveText(page_body[start:end], 0)
3.161 +
3.162 + # Retain the preceding region for the current page.
3.163 +
3.164 + retained_regions.append(page_body[retained_region_start:start])
3.165 +
3.166 + # Insert Include macros for the broken out text.
3.167 +
3.168 + retained_regions.append("<<Include(%s,,editlink)>>\n" % new_page_name)
3.169 +
3.170 + # Start a new region to retain.
3.171 +
3.172 + retained_region_start = end
3.173 +
3.174 + # Retain any remaining text.
3.175 +
3.176 + else:
3.177 + retained_regions.append(page_body[retained_region_start:])
3.178 +
3.179 + # Edit the current page.
3.180 +
3.181 + edited_page = PageEditor(request, page.page_name)
3.182 + edited_page.saveText("".join(retained_regions), 0)
3.183 +
3.184 + # NOTE: Perhaps show a message upon failure.
3.185 +
3.186 + request.http_redirect(page.url(request))
3.187 + return 1, None
3.188 +
3.189 +# Action function.
3.190 +
3.191 +def execute(pagename, request):
3.192 + SectionBreakout(pagename, request).render()
3.193 +
3.194 +# vim: tabstop=4 expandtab shiftwidth=4