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