paul@3 | 1 | # -*- coding: iso-8859-1 -*- |
paul@3 | 2 | """ |
paul@3 | 3 | MoinMoin - SectionBreakout |
paul@3 | 4 | |
paul@3 | 5 | Break sections out of a page, making new pages for each of the sections and |
paul@3 | 6 | replacing them with Include macros. |
paul@3 | 7 | |
paul@3 | 8 | @copyright: 2011 Paul Boddie <paul@boddie.org.uk> |
paul@3 | 9 | @license: GNU GPL, see COPYING for details. |
paul@3 | 10 | """ |
paul@3 | 11 | |
paul@3 | 12 | Dependencies = ['pages'] |
paul@3 | 13 | |
paul@3 | 14 | from MoinMoin.action import ActionBase |
paul@3 | 15 | from MoinMoin.PageEditor import PageEditor |
paul@4 | 16 | from MoinContentSupport import * |
paul@3 | 17 | import re |
paul@3 | 18 | |
paul@3 | 19 | # Action class and supporting functions. |
paul@3 | 20 | |
paul@3 | 21 | class SectionBreakout(ActionBase, ActionSupport): |
paul@3 | 22 | |
paul@3 | 23 | "An action breaking sections out of pages." |
paul@3 | 24 | |
paul@3 | 25 | def get_form_html(self, buttons_html): |
paul@3 | 26 | _ = self._ |
paul@3 | 27 | request = self.request |
paul@3 | 28 | page = self.page |
paul@3 | 29 | form = self.get_form() |
paul@3 | 30 | |
paul@3 | 31 | level = int(form.get("level", ["2"])[0]) |
paul@3 | 32 | |
paul@3 | 33 | # Acquire heading details from the page. |
paul@3 | 34 | |
paul@3 | 35 | body = page.get_raw_body() |
paul@3 | 36 | heading_details = getHeadingDetails(body, level, level) |
paul@3 | 37 | |
paul@3 | 38 | d = { |
paul@3 | 39 | "buttons_html" : buttons_html, |
paul@3 | 40 | "heading_level_label" : escape(_("Heading level")), |
paul@3 | 41 | "found_headings_label" : escape(_("Headings found in page")), |
paul@3 | 42 | "preview_label" : escape(_("Preview")), |
paul@3 | 43 | "level" : escattr(level), |
paul@3 | 44 | } |
paul@3 | 45 | |
paul@3 | 46 | html = u''' |
paul@3 | 47 | <table> |
paul@3 | 48 | <tr> |
paul@3 | 49 | <td class="label">%(heading_level_label)s</td> |
paul@3 | 50 | <td><input type="text" name="level" value="%(level)s" size="2" /></td> |
paul@3 | 51 | </tr> |
paul@3 | 52 | <tr> |
paul@3 | 53 | <td class="label">%(found_headings_label)s</td> |
paul@3 | 54 | <td>''' % d |
paul@3 | 55 | |
paul@3 | 56 | for heading, level, span in heading_details: |
paul@3 | 57 | html += "%s<br />" % heading |
paul@3 | 58 | |
paul@3 | 59 | html += ''' |
paul@3 | 60 | </td> |
paul@3 | 61 | </tr> |
paul@3 | 62 | <tr> |
paul@3 | 63 | <td></td> |
paul@3 | 64 | <td class="buttons"><input type="submit" value="%(preview_label)s" />%(buttons_html)s</td> |
paul@3 | 65 | </tr> |
paul@3 | 66 | </table> |
paul@3 | 67 | ''' % d |
paul@3 | 68 | |
paul@3 | 69 | return html |
paul@3 | 70 | |
paul@3 | 71 | def do_action(self): |
paul@3 | 72 | |
paul@3 | 73 | "Create the new event." |
paul@3 | 74 | |
paul@3 | 75 | _ = self._ |
paul@3 | 76 | form = self.get_form() |
paul@3 | 77 | |
paul@3 | 78 | # A heading level must be provided. |
paul@3 | 79 | |
paul@3 | 80 | level = form.get("level", [None])[0] |
paul@3 | 81 | |
paul@3 | 82 | if not level: |
paul@3 | 83 | return 0, _("No heading level specified.") |
paul@3 | 84 | |
paul@3 | 85 | return self.break_out_headings(int(level)) |
paul@3 | 86 | |
paul@3 | 87 | def render_success(self, msg, msgtype=None): |
paul@3 | 88 | |
paul@3 | 89 | """ |
paul@3 | 90 | Render neither 'msg' nor 'msgtype' since redirection should occur |
paul@3 | 91 | instead. |
paul@3 | 92 | NOTE: msgtype is optional because MoinMoin 1.5.x does not support it. |
paul@3 | 93 | """ |
paul@3 | 94 | |
paul@3 | 95 | pass |
paul@3 | 96 | |
paul@3 | 97 | def break_out_headings(self, level): |
paul@3 | 98 | |
paul@3 | 99 | """ |
paul@3 | 100 | Break out headings at the given 'level' from the current page. |
paul@3 | 101 | """ |
paul@3 | 102 | |
paul@3 | 103 | _ = self._ |
paul@3 | 104 | request = self.request |
paul@3 | 105 | page = self.page |
paul@3 | 106 | formatter = request.formatter |
paul@3 | 107 | |
paul@3 | 108 | # Acquire all heading details from the page. |
paul@3 | 109 | |
paul@3 | 110 | page_body = page.get_raw_body() |
paul@4 | 111 | categories = getCategoryMembership(page_body) |
paul@4 | 112 | |
paul@3 | 113 | regions = [] |
paul@3 | 114 | current_region_start = None |
paul@3 | 115 | |
paul@3 | 116 | for heading, found_level, (start, end) in getHeadingDetails(page_body): |
paul@3 | 117 | |
paul@3 | 118 | # Upon finding a suitable heading, begin a new region to be broken |
paul@3 | 119 | # out. |
paul@3 | 120 | |
paul@3 | 121 | if current_region_start is None and found_level >= level: |
paul@3 | 122 | current_region_start = heading, start |
paul@3 | 123 | |
paul@3 | 124 | # Upon finding a higher-level heading, end any open region. |
paul@3 | 125 | |
paul@3 | 126 | elif current_region_start is not None and found_level <= level: |
paul@3 | 127 | regions.append(current_region_start + (start,)) |
paul@3 | 128 | |
paul@3 | 129 | # For headings at the requested level, open a new region. |
paul@3 | 130 | |
paul@3 | 131 | if found_level == level: |
paul@3 | 132 | current_region_start = heading, start |
paul@3 | 133 | else: |
paul@3 | 134 | current_region_start = None |
paul@3 | 135 | |
paul@3 | 136 | # End any open region. |
paul@3 | 137 | |
paul@3 | 138 | else: |
paul@3 | 139 | if current_region_start is not None: |
paul@3 | 140 | regions.append(current_region_start + (len(page_body),)) |
paul@3 | 141 | |
paul@3 | 142 | # Make new pages for each region, rebuilding the current page body. |
paul@3 | 143 | |
paul@3 | 144 | retained_regions = [] |
paul@3 | 145 | retained_region_start = 0 |
paul@4 | 146 | new_page_names = {} |
paul@3 | 147 | |
paul@3 | 148 | for heading, start, end in regions: |
paul@3 | 149 | |
paul@3 | 150 | # Combine the page name and the heading to make a subpage. |
paul@3 | 151 | |
paul@3 | 152 | new_page_name = "%s/%s" % (page.page_name, heading) |
paul@3 | 153 | |
paul@4 | 154 | # Distinguish between pages which use the same heading. |
paul@4 | 155 | |
paul@4 | 156 | n = new_page_names.get(new_page_name, 0) |
paul@4 | 157 | if n: |
paul@4 | 158 | new_page_names[new_page_name] = n + 1 |
paul@4 | 159 | new_page_name += " (%d)" % (n + 1) |
paul@4 | 160 | else: |
paul@4 | 161 | new_page_names[new_page_name] = n + 1 |
paul@4 | 162 | |
paul@3 | 163 | # Open the page for editing. |
paul@3 | 164 | |
paul@3 | 165 | new_page = PageEditor(request, new_page_name) |
paul@4 | 166 | new_page_body = page_body[start:end] |
paul@4 | 167 | new_page_categories = getCategoryMembership(new_page_body) |
paul@3 | 168 | |
paul@4 | 169 | # Add categories if the parent page has any. |
paul@3 | 170 | |
paul@4 | 171 | if new_page_categories != categories: |
paul@4 | 172 | new_page_body += getCategoryDeclaration(categories) |
paul@4 | 173 | |
paul@4 | 174 | # Save the new page. |
paul@4 | 175 | |
paul@4 | 176 | new_page.saveText(new_page_body, 0) |
paul@3 | 177 | |
paul@3 | 178 | # Retain the preceding region for the current page. |
paul@3 | 179 | |
paul@3 | 180 | retained_regions.append(page_body[retained_region_start:start]) |
paul@3 | 181 | |
paul@3 | 182 | # Insert Include macros for the broken out text. |
paul@3 | 183 | |
paul@3 | 184 | retained_regions.append("<<Include(%s,,editlink)>>\n" % new_page_name) |
paul@3 | 185 | |
paul@3 | 186 | # Start a new region to retain. |
paul@3 | 187 | |
paul@3 | 188 | retained_region_start = end |
paul@3 | 189 | |
paul@3 | 190 | # Retain any remaining text. |
paul@3 | 191 | |
paul@3 | 192 | else: |
paul@3 | 193 | retained_regions.append(page_body[retained_region_start:]) |
paul@3 | 194 | |
paul@3 | 195 | # Edit the current page. |
paul@3 | 196 | |
paul@3 | 197 | edited_page = PageEditor(request, page.page_name) |
paul@4 | 198 | edited_page_body = "".join(retained_regions) |
paul@4 | 199 | edited_page_categories = getCategoryMembership(edited_page_body) |
paul@4 | 200 | |
paul@4 | 201 | # Add categories if the parent page should have any, but these were |
paul@4 | 202 | # broken out. |
paul@4 | 203 | |
paul@4 | 204 | if edited_page_categories != categories: |
paul@4 | 205 | edited_page_body += getCategoryDeclaration(categories) |
paul@4 | 206 | |
paul@4 | 207 | # Save the current page. |
paul@4 | 208 | |
paul@4 | 209 | edited_page.saveText(edited_page_body, 0) |
paul@3 | 210 | |
paul@3 | 211 | # NOTE: Perhaps show a message upon failure. |
paul@3 | 212 | |
paul@3 | 213 | request.http_redirect(page.url(request)) |
paul@3 | 214 | return 1, None |
paul@3 | 215 | |
paul@3 | 216 | # Action function. |
paul@3 | 217 | |
paul@3 | 218 | def execute(pagename, request): |
paul@3 | 219 | SectionBreakout(pagename, request).render() |
paul@3 | 220 | |
paul@3 | 221 | # vim: tabstop=4 expandtab shiftwidth=4 |