python-wikkid_0.4.orig/.bzrignore0000644000000000000000000000005211547716242014135 0ustar00build dist Wikkid.egg-info *.egg MANIFEST python-wikkid_0.4.orig/.testr.conf0000644000000000000000000000025714160153722014220 0ustar00[DEFAULT] test_command=PYTHONPATH=`pwd`:$PYTHONPATH python3 -m subunit.run discover wikkid.tests $IDOPTION $LISTOPT test_id_option=--load-list $IDFILE test_list_option=--list python-wikkid_0.4.orig/Changes.txt0000644000000000000000000000100114160200415014217 0ustar00======= Changes ======= 0.3 --- * Add Git support * Drop support for Python 2.x and add support for Python 3.x. * Add docker file. 0.2 --- * Changed from twisted.web to webob WSGI for serving the branches * Show who last modified the file on the wiki page * Add a Home.txt so wikkid works nicely on itself * Fix the setup packaging dependencies * Add textile and markdown formatters * Add the ability to use a branch rather than a working tree * Adds a simple man page for wikkid-serve * Fixed for python 2.7 python-wikkid_0.4.orig/Dockerfile0000644000000000000000000000102214160172355014116 0ustar00FROM debian:sid-slim RUN apt update && apt install --no-install-recommends -y python3 python3-markdown python3-pygments python3-textile python3-pip python3-configobj python3-docutils python3-dulwich python3-jinja2 python3-patiencediff python3-pygments python3-twisted python3-webob python3-zope.interface cython3 python3-dev build-essential && pip3 install breezy && apt clean && mkdir -p /logs ADD . /opt/wikkid ENV PYTHONPATH=/opt/wikkid EXPOSE 8080/tcp ENTRYPOINT ["/usr/bin/python3", "/opt/wikkid/bin/wikkid-serve", "/data"] python-wikkid_0.4.orig/Hacking.txt0000644000000000000000000000473511514702505014243 0ustar00============== Wikkid Hacking ============== Everyone wants a wiki. Wiki's remember changes. It makes sense to have a real DVCS backed wiki. Design Goals ------------ I believe it is better to start with the ideals. So here goes... * It should be easy and simple to download the source and use as a Bazaar plugin * Have full test coverage. * Use other libraries rather than rolling our own. * The rendering of pages needs to be abstracted from the http serving to allow the plugin to be used elsewhere as a library (like Launchpad). * Use zope interfaces to define clean boundaries between the path traversal, rendering, and store access. Directory Outline ----------------- * **wikkid.contrib**: Third party libraries that aren't suitably packaged * **wikkid.filestore**: Implementations of the underlying filestore. Currently there is the Bazaar based one, and a simplistic filestore that just uses some in-memory dictionaries. * **wikkid.formatter**: The different formatters used to render the text files as HTML. Currently there are formatters for Creold, ReST, textile, markdown and Pygments. * **wikkid.interface**: All the zope interface definitions are in this module. * **wikkid.model**: The model objects representing the different things that you end up hitting through file traversal, like missing pages, directories, binary files and of course, wiki pages. * **wikkid.skin**: Currently only the default skin, and code around the loading and using of skins. * **wikkid.tests**: All the tests live here. * **wikkid.user**: Code specific to how we handle users. Pretty simplistic right now. * **wikkid.view**: The views on the model objects. These are the things that actually make the magic happen. Getting Involved ---------------- Contributing to Wikkid is easy. Get a local copy of the trunk branch_, make your changes, and push your branch to Launchpad_. If you don't have a Launchpad account, follow the instructions to `create a new user`_ and add an SSH key. .. _branch: https://launchpad.net/+branch/wikkid .. _Launchpad: https://launchpad.net .. _`create a new user`: https://help.launchpad.net/YourAccount/NewAccount Once your branch is on Launchpad, propose the branch for merging, and the other developers will get notified automagically. Authentication -------------- Use the `repoze.who`_ library. This acts as a WSGI middleware and most importantly, it is written and maintained by someone else. .. _`repoze.who`: http://static.repoze.org/whodocs/ python-wikkid_0.4.orig/INSTALL0000644000000000000000000000130714160200415013150 0ustar00Installing Wikkid Wiki ====================== Hopefully we'll get this packaged for at least Ubuntu and Debian, and some setup tools for those that like it like that. Dependancies ------------ * dulwich - for the Git filestore * breezy - for the Bazaar filestore and merging * python-beautifulsoup - for testing of html (soon) * python-docutils - for the ReST formatter * python-jinja2 - for the templating * python-markdown - for the markdown formatter * python-pygments - for the syntax hilighting * python-testtools - for running the test suite (optional) * python-textile - for the textile formatter. * python-webob - for the WSGI application bits * python-zope.interface - for the interfaces python-wikkid_0.4.orig/LICENCE0000644000000000000000000010333011372503456013120 0ustar00 GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . python-wikkid_0.4.orig/MANIFEST.in0000644000000000000000000000023611603364577013700 0ustar00include INSTALL LICENCE Hacking.txt Home.txt NEWS ToDo.txt Makefile MANIFEST.in global-include *.html *.gif *.png *.css *.ico recursive-include wikkid/skin * python-wikkid_0.4.orig/Makefile0000644000000000000000000000067714336241273013604 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). all: check check: python3 -m testtools.run wikkid.tests.test_suite clean: @find . -name '*.py[co]' -print0 | xargs -r0 $(RM) @find . -name '*~' -print0 | xargs -r0 $(RM) .PHONY: check clean docker: docker build -t ghcr.io/wikkid-team/wikkid . docker push ghcr.io/wikkid-team/wikkid python-wikkid_0.4.orig/README.rst0000644000000000000000000000240114160214637013615 0ustar00================ Wikkid Wiki Home ================ Wikkid is a wiki that uses Git or Bazaar as a way to store the content. Principles of Wikkid -------------------- * Will run using any Git_ or Bazaar_ branch, not just one it has created * Provides a Breezy plugin to simplify the serving of a branch * When run locally Wikkid will use the current users configured identity * Can be used as a public facing wiki with multiple users * Can be used as a library in other Python_ applications .. _Bazaar: https://bazaar.canonical.com .. _Git: https://git-scm.com/ .. _Python: https://python.org Quickstart ---------- To run from source, type something like:: $ python3 setup.py develop # install dependencies $ git init /tmp/wiki $ ./bin/wikkid-serve /tmp/wiki $ sensible-browser http://localhost:8080/ Or, using docker:: $ docker run -p 8080:8080 -v /path/to/some/repo:/data \ ghcr.io/wikkid-team/wikkid $ sensible-browser http://localhost:8080/ Now what? --------- * Join the `developer's mailing list`_ * Read the hacking_ document * Browse the source_ * Look through the `To Do`_ list and fix something .. _`developer's mailing list`: https://launchpad.net/~wikkid-dev .. _hacking: Hacking.txt .. _source: /+listing .. _`To Do`: ToDo.txt python-wikkid_0.4.orig/ToDo.txt0000644000000000000000000000411114160202764013532 0ustar00A location for general notes ============================ * Add a nice error page. * Add in a file id for text editing so we can track edits during page moves * Add in the ability to move pages * Lock pages as read-only (see also merging) Is this really locked, or is it a restricted ACL? * ACLs for editing (see also merging) * Define formatter in the first line of wiki text # * This is mostly done now, although not really documented. * AJAX editing of source files (avoid page refreshes) * Add a better page for attempting to list a missing directory * Extend the identification of files without extensions to try a pygments lexer. If one isn't found, or if we find ReST, use the rest formatter, if it finds other wiki formats we know about, use those, otherwise format using the lexer it finds, and if none found, then plain
 (or treat as wiki
  page, yet to decide).

* Allow specific extensions for certain formatters (.rst, .md, .mdwn, etc)

* Support a readonly mode


Standalone requirements
-----------------------

* Sessions

* Login and logout

* SQLite storage engine for usernames and passwords?

* Subscriptions?

* Generate a nonce for new users, and validate email addresses
  (hmm... requires incoming email processing, not that easy for standalone
  deployments, and not needed for launchpad integration)


Running behind a reverse proxy
------------------------------

Support getting credentials from elsewhere, and updating author accordingly.


Launchpad integration
---------------------

* A plan for spam!  Really needed as we have spammers

* How to handle merges
  Page merge hooks?



Ideas not yet fully formed
--------------------------

* Lazy loading of formatters (to reduce install dependancies)

* Customize default rendering engine

* Extend creole to allow specifying format of embedded code

* Look for a wiki-media python library for formatting wiki-media text
  Or write one.

* Soundex filename search based on base_name of the files, used when
  hitting a missing file to offer suggestions.
python-wikkid_0.4.orig/bin/0000755000000000000000000000000011377157600012704 5ustar00python-wikkid_0.4.orig/disperse.conf0000644000000000000000000000034114336237474014625 0ustar00# See https://github.com/jelmer/disperse
timeout_days: 5
tag_name: "v$VERSION"
verify_command: "make check"
update_version {
  path: "wikkid/__init__.py"
  match: "^version = \"(.*)\"$"
  new_line: "version = \"$VERSION\""
}
python-wikkid_0.4.orig/plugin/0000755000000000000000000000000011374724564013440 5ustar00python-wikkid_0.4.orig/pyproject.toml0000644000000000000000000000012114336176502015042 0ustar00[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
python-wikkid_0.4.orig/setup.cfg0000644000000000000000000000152214336241273013753 0ustar00[metadata]
name = Wikkid
description = VCS-backed wiki
long_description = A wiki that is backed by Gitr or Bazaar that allows local branching of the wiki
    for later merging. Also doesn't have any page locks and uses three way merging.
version = attr:wikkid.version
author = Wikkid Developers
author_email = wikkid-dev@lists.launchpad.net
url = https://launchpad.net/wikkid
project_urls =
    Repository=https://github.com/wikkid-team/wikkid

[options]
scripts = bin/wikkid-serve
packages = find:
package_dir = breezy.plugins.wikkid=plugin
include_package_data = True
install_requires =
    breezy
    docutils
    dulwich
    jinja2
    merge3
    pygments
    twisted
    webob
    zope.interface
tests_require =
    testtools
    bs4
    lxml

[options.package_data]
wikkid/skin =
    default/*.html
    default/favicon.ico
    default/static/*
python-wikkid_0.4.orig/setup.py0000755000000000000000000000015314336241273013646 0ustar00#!/usr/bin/python3
from setuptools import setup
setup(data_files=[('share/man/man1', ['wikkid-serve.1'])])
python-wikkid_0.4.orig/wikkid-serve.10000644000000000000000000000146111521016061014606 0ustar00.TH WIKKID-SERVE "1" "January 2011" "wikkid-serve 0.2dev" "User Commands"
.SH NAME
wikkid-serve \- Serve a wiki from a Bazaar branch
.SH SYNOPSIS
.B wikkid-serve
[\fIoptions\fR] \fI\fR
.SH DESCRIPTION
Run a stand-alone Wikkid Wiki server.
.SH OPTIONS
.TP
\fB\-\-version\fR
show program's version number and exit
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-\-host\fR=\fIHOST\fR
The interface to listen on. Defaults to 'localhost'
.TP
\fB\-\-port\fR=\fIPORT\fR
The port to listen on.  Defaults to 8080.
.TP
\fB\-\-default\-format\fR=\fIDEFAULT_FORMAT\fR
Specify the default wiki format to use. Defaults to
\&'rest'
.SH "LICENSE"
Wikkid is licensed under the
.B GNU Affero General Public License
.SH "SEE ALSO"
The wikkid home page can be found at
.B https://launchpad.net/wikkid
python-wikkid_0.4.orig/wikkid/0000755000000000000000000000000011343270653013413 5ustar00python-wikkid_0.4.orig/bin/_wikkid_path.py0000644000000000000000000000062011377162340015706 0ustar00#! /usr/bin/python
#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""Add the parent directory to the python path to run the scripts."""

import sys
import os.path

current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.insert(0, parent_dir)
python-wikkid_0.4.orig/bin/wikkid-serve0000755000000000000000000000704614160200415015227 0ustar00#! /usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""The server class for the wiki."""

try:
    import _wikkid_path
except ImportError:
    # Not running from a branch.
    pass

import logging
import optparse
import sys

from wikkid import version
from wikkid.app import WikkidApp
from wikkid.context import (
    DEFAULT_FORMAT,
    DEFAULT_HOST,
    DEFAULT_PORT,
    ExecutionContext,
    )


def setup_logging():
    """Set up a logger sending to stderr."""
    handler = logging.StreamHandler(sys.stderr)
    fmt = '%(asctime)s %(levelname)-7s %(message)s'
    formatter = logging.Formatter(
        fmt=fmt, datefmt="%Y-%m-%d %H:%M:%S")
    handler.setFormatter(formatter)
    root = logging.getLogger()
    root.addHandler(handler)


def main(args):

    usage = "Usage: %prog [options] "
    parser = optparse.OptionParser(
        usage=usage, description="Run a Wikkid Wiki server.", version=version)
    parser.add_option('--format', type='choice', default='bzr',
        choices=['bzr', 'git'], help=("Default repository format to use."))
    parser.add_option(
        '--host', type='string', default=DEFAULT_HOST,
        help=('The interface to listen on. Defaults to %r' % DEFAULT_HOST))
    parser.add_option(
        '--port', type='int', default=DEFAULT_PORT,
        help=('The port to listen on.  Defaults to %s.' % DEFAULT_PORT))
    parser.add_option(
        '--default-format', type='string', default=DEFAULT_FORMAT,
        help=("Specify the default wiki format to use. Defaults to %r"
              % DEFAULT_FORMAT))
    parser.add_option(
        '--script-name',
        help=('The SCRIPT_NAME for the environment.  This is the prefix for the URLs'))
    options, args = parser.parse_args(sys.argv[1:])

    execution_context = ExecutionContext(
        host=options.host,
        port=options.port,
        default_format=options.default_format,
        script_name=options.script_name)

    if len(args) == 0:
        print("No branch location specified.")
        parser.print_usage()
        sys.exit(1)
    if len(args) > 1:
        print("Unexpected positional args: %s" % args[1:])
        parser.print_usage()
        sys.exit(1)
    branch = args[0]
    setup_logging()
    logger = logging.getLogger('wikkid')
    logger.setLevel(logging.INFO)

    if options.format == 'bzr':
        from breezy.workingtree import WorkingTree
        from wikkid.filestore.bzr import FileStore
        from wikkid.user.bzr import LocalBazaarUserMiddleware
        import breezy.bzr
        import breezy.git

        working_tree = WorkingTree.open(branch)
        logger.info('Using: %s', working_tree)
        filestore = FileStore(working_tree)
    elif options.format == 'git':
        from wikkid.filestore.git import FileStore
        from wikkid.user.git import LocalGitUserMiddleware
        filestore = FileStore.from_path(branch)

    app = WikkidApp(filestore=filestore, execution_context=execution_context)
    if options.format == 'bzr':
        app = LocalBazaarUserMiddleware(app, working_tree.branch)
    elif options.format == 'git':
        app = LocalGitUserMiddleware(app, filestore.repo)
    from wsgiref.simple_server import make_server
    httpd = make_server(options.host, options.port, app)
    logger.info('Serving on http://%s:%s', options.host, options.port)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        logger.info('Done.')


if __name__ == "__main__":
    main(sys.argv)
python-wikkid_0.4.orig/plugin/__init__.py0000644000000000000000000000022714160200415015526 0ustar00from breezy.commands import plugin_cmds

plugin_cmds.register_lazy('cmd_wikkid', ['wiki'],
                          'breezy.plugins.wikkid.commands')
python-wikkid_0.4.orig/plugin/commands.py0000644000000000000000000000311314160200415015565 0ustar00import logging
import sys

from breezy.commands import Command
from breezy.option import Option

from breezy.workingtree import WorkingTree

from wikkid.app import WikkidApp
from wikkid.filestore.bzr import FileStore
from wikkid.user.bzr import LocalBazaarUserMiddleware

DEFAULT_PORT = 8080


def setup_logging():
    """Set up a logger sending to stderr."""
    handler = logging.StreamHandler(sys.stderr)
    fmt = '%(asctime)s %(levelname)-7s %(message)s'
    formatter = logging.Formatter(
        fmt=fmt, datefmt="%Y-%m-%d %H:%M:%S")
    handler.setFormatter(formatter)
    root = logging.getLogger()
    root.addHandler(handler)


class cmd_wikkid(Command):
    """Serve branch as a wiki using wikkid."""
    aliases = ['wiki']
    takes_args = ['branch?']
    takes_options = [
        Option(
            'port',
            help='Port to listen, defaults to 8080.',
            type=int,
            short_name='p')
        ]

    def run(self, branch=u'.', port=8080):
        setup_logging()
        logger = logging.getLogger('wikkid')
        logger.setLevel(logging.DEBUG)

        working_tree = WorkingTree.open(branch)
        logger.info('Using: %s', working_tree)
        filestore = FileStore(working_tree)

        app = WikkidApp(filestore)
        app = LocalBazaarUserMiddleware(app, working_tree.branch)
        from wsgiref.simple_server import make_server
        httpd = make_server('localhost', port, app)
        logger.info('Serving on http://localhost:%s', port)
        try:
            httpd.serve_forever()
        except KeyboardInterrupt:
            logger.info('Done.')
python-wikkid_0.4.orig/wikkid/__init__.py0000644000000000000000000000030514336241273015523 0ustar00#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""A Distributed Wiki."""


version = "0.4"
python-wikkid_0.4.orig/wikkid/app.py0000644000000000000000000001100414160200415014526 0ustar00# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""A WSGI application for Wikkid."""

import logging
import mimetypes
import os.path
import urllib.parse
from wsgiref.util import shift_path_info

from breezy import urlutils
from webob import Request, Response
from webob.exc import HTTPException, HTTPNotFound

from wikkid.context import ExecutionContext
from wikkid.dispatcher import get_view
from wikkid.fileutils import FileIterable
from wikkid.model.factory import ResourceFactory
from wikkid.skin.loader import Skin
from wikkid.view.urls import parse_url


def serve_file(filename):
    if os.path.exists(filename):
        basename = urlutils.basename(filename)
        content_type = mimetypes.guess_type(basename)[0]

        res = Response(content_type=content_type, conditional_response=True)
        res.app_iter = FileIterable(filename)
        res.content_length = os.path.getsize(filename)
        res.last_modified = os.path.getmtime(filename)
        # Todo: is this the best value for the etag?
        # perhaps md5 would be a better alternative
        res.etag = '%s-%s-%s' % (
            os.path.getmtime(filename), os.path.getsize(filename),
            hash(filename))
        return res

    else:
        return HTTPNotFound()


class WikkidApp(object):
    """The main wikkid application."""

    def __init__(self, filestore, skin_name=None, execution_context=None):
        if execution_context is None:
            execution_context = ExecutionContext()
        self.execution_context = execution_context
        self.filestore = filestore
        self.resource_factory = ResourceFactory(self.filestore)
        # Need to load the initial templates for the skin.
        if skin_name is None:
            skin_name = 'default'
        self.skin = Skin(skin_name)
        self.logger = logging.getLogger('wikkid')

    def preprocess_environ(self, environ):
        request = Request(environ)
        path = urllib.parse.unquote(request.path)
        script_name = self.execution_context.script_name
        # Firstly check to see if the path is the same as the script_name
        if path != script_name and not path.startswith(script_name + '/'):
            raise HTTPNotFound()

        shifted_prefix = ''
        while shifted_prefix != script_name:
            shifted = shift_path_info(environ)
            shifted_prefix = '{0}/{1}'.format(shifted_prefix, shifted)
        # Now we are just interested in the path_info having ignored the
        # script name.
        path = urllib.parse.unquote(request.path_info)
        if path == '':
            path = '/'  # Explicitly be the root (we need the /)
        return request, path

    def _get_view(self, request, path):
        """Get the view for the path specified."""
        resource_path, action = parse_url(path)
        model = self.resource_factory.get_resource_at_path(resource_path)
        return get_view(model, action, request, self.execution_context)

    def process_call(self, environ):
        """The actual implementation of dealing with the call."""
        # TODO: reject requests that aren't GET or POST
        try:
            request, path = self.preprocess_environ(environ)
        except HTTPException as e:
            return e

        if path == '/favicon.ico':
            if self.skin.favicon is not None:
                return serve_file(self.skin.favicon)
            else:
                return HTTPNotFound()

        if path.startswith('/static/'):
            if self.skin.static_dir is not None:
                static_dir = self.skin.static_dir.rstrip(os.sep) + os.sep
                static_file = os.path.abspath(
                    urlutils.joinpath(static_dir, path[8:]))
                if static_file.startswith(static_dir):
                    return serve_file(static_file)
                else:
                    return HTTPNotFound()
            else:
                return HTTPNotFound()

        try:
            view = self._get_view(request, path)
            return view.render(self.skin)
        except HTTPException as e:
            return e

    def get_view(self, environ):
        """Allow an app user to get the wikkid view for a particular call."""
        request, path = self.preprocess_environ(environ)
        return self._get_view(request, path)

    def __call__(self, environ, start_response):
        """The WSGI bit."""
        response = self.process_call(environ)
        return response(environ, start_response)
python-wikkid_0.4.orig/wikkid/context.py0000644000000000000000000000247711766323257015473 0ustar00# -*- coding: utf-8 -*-
#
# Copyright (C) 2010-2012 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""A means of storing execution context."""

DEFAULT_FORMAT = 'rest'
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8080


class ExecutionContext(object):
    """Store run-time execution context data.

    This is the Encapsulate Context pattern.
    """

    def __init__(self, host=None, port=None, default_format=None,
                 script_name=None):
        """Create an execution context for the application.

        :param host: The hostname that content is being served from.
        :param port: The port that is being listened on.
        :param default_format: The default wiki format for pages that
            don't specify any.
        """
        if host is None:
            host = DEFAULT_HOST
        if port is None:
            port = DEFAULT_PORT
        if default_format is None:
            default_format = DEFAULT_FORMAT
        self.host = host
        self.port = port
        self.default_format = default_format
        # TODO: make sure the script_name if set starts with a slash and
        # doesn't finish with one.
        if script_name is None:
            script_name = ''
        self.script_name = script_name.rstrip('/')
python-wikkid_0.4.orig/wikkid/contrib/0000755000000000000000000000000011371760442015054 5ustar00python-wikkid_0.4.orig/wikkid/dispatcher.py0000644000000000000000000000625714160200415016112 0ustar00#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""The dispatcher for wikkid views.

When this module is loaded, it will automagically load all the other views in
this directory.  The views inherit from the BaseView which has a metaclass
which registers the view with the dispatcher.
"""

import os

from breezy.urlutils import dirname, joinpath

from zope.interface import providedBy

from wikkid.context import ExecutionContext

# The view registry needs to map an Interface and a name to a class.
_VIEW_REGISTRY = {}


def get_view(obj, view_name, request, ec=ExecutionContext()):
    """Get the most relevant view for the object for the specified name.

    Iterate through the provided interfaces of the object and look in the view
    registry for a view.
    """
    interfaces = providedBy(obj)
    for interface in interfaces:
        try:
            klass = _VIEW_REGISTRY[(interface, view_name)]
            instance = klass(obj, request, ec)
            instance.initialize()
            return instance
        except KeyError:
            pass
    # For example, if someone asked for 'raw' view on a directory or binary
    # object.
    return None


def register_view(view_class):
    """Register the view."""
    interface = getattr(view_class, 'for_interface', None)
    view_name = getattr(view_class, 'name', None)
    default_view = getattr(view_class, 'is_default', False)

    if view_name is None or interface is None:
        # Don't register.
        return
    key = (interface, view_name)
    assert key not in _VIEW_REGISTRY, "key already registered: %r" % (key,)
    _VIEW_REGISTRY[key] = view_class
    if default_view:
        _VIEW_REGISTRY[(interface, None)] = view_class


def unregister_view(view_class):
    """Unregister the view."""
    interface = getattr(view_class, 'for_interface', None)
    view_name = getattr(view_class, 'name', None)
    default_view = getattr(view_class, 'is_default', False)

    if view_name is None or interface is None:
        # Don't register.
        return
    key = (interface, view_name)
    assert _VIEW_REGISTRY[key] is view_class, \
        "key registered with different class: %r: %r != %r" % (
            key, _VIEW_REGISTRY[key], view_class)
    del _VIEW_REGISTRY[key]
    if default_view:
        del _VIEW_REGISTRY[(interface, None)]


# We know that the controller (whatever that is going to end up being) will
# load this module to get the 'get_view' function.  None of the other view
# modules should be explicitly loaded anywhere else (possible exceptions may
# occur, so this isn't a hard rule).
#
# So... when this module is loaded, we want to load the other modules in the
# wikkid.view package so that when the classes are parsed, they register
# themselves with the view registry.

def load_view_modules():
    curr_dir = os.path.abspath(dirname(__file__))
    view_dir = joinpath(curr_dir, 'view')
    py_files = [
        filename for filename in os.listdir(view_dir)
        if filename.endswith('.py') and not filename.startswith('__')]
    for filename in py_files:
        __import__('wikkid.view.%s' % filename[:-3])


load_view_modules()
python-wikkid_0.4.orig/wikkid/filestore/0000755000000000000000000000000011361276452015412 5ustar00python-wikkid_0.4.orig/wikkid/fileutils.py0000644000000000000000000000320114160200415015746 0ustar00#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""A method of iterating over file contents

This stops us reading the entire file into memory when we serve it.
"""


class FileIterable(object):
    """An iterable file-like object."""
    def __init__(self, filename, start=None, stop=None):
        self.filename = filename
        self.start = start
        self.stop = stop

    def __iter__(self):
        return FileIterator(self.filename, self.start, self.stop)

    def app_iter_range(self, start, stop):
        return self.__class__(self.filename, start, stop)


class FileIterator(object):
    """Iterate over a file.

    FileIterable provides a simple file iterator, optionally allowing the
    user to specify start and end ranges for the file.
    """

    chunk_size = 4096

    def __init__(self, filename, start, stop):
        self.filename = filename
        self.fileobj = open(self.filename, 'rb')
        if start:
            self.fileobj.seek(start)
        if stop is not None:
            self.length = stop - start
        else:
            self.length = None

    def __iter__(self):
        return self

    def __next__(self):
        if self.length is not None and self.length <= 0:
            raise StopIteration
        chunk = self.fileobj.read(self.chunk_size)
        if not chunk:
            raise StopIteration
        if self.length is not None:
            self.length -= len(chunk)
            if self.length < 0:
                # Chop off the extra:
                chunk = chunk[:self.length]
        return chunk
python-wikkid_0.4.orig/wikkid/formatter/0000755000000000000000000000000011353614774015425 5ustar00python-wikkid_0.4.orig/wikkid/interface/0000755000000000000000000000000011362277756015367 5ustar00python-wikkid_0.4.orig/wikkid/model/0000755000000000000000000000000011361275416014515 5ustar00python-wikkid_0.4.orig/wikkid/skin/0000755000000000000000000000000011361301511014343 5ustar00python-wikkid_0.4.orig/wikkid/tests/0000755000000000000000000000000011343270653014555 5ustar00python-wikkid_0.4.orig/wikkid/user/0000755000000000000000000000000011361277140014367 5ustar00python-wikkid_0.4.orig/wikkid/view/0000755000000000000000000000000011361275607014371 5ustar00python-wikkid_0.4.orig/wikkid/contrib/__init__.py0000644000000000000000000000031611372502521017156 0ustar00#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""For packages that are not adequately packaged."""
python-wikkid_0.4.orig/wikkid/filestore/__init__.py0000644000000000000000000000113414160200415017504 0ustar00#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""Wikkid filestores.

The volatile filestore is primarily used for tests, and the bzr filestore is
where all the main work happens.
"""


class FileExists(Exception):
    """A file was found where a directory is wanted."""


class UpdateConflicts(Exception):
    """Conflicts were found during updating."""
    def __init__(self, content, basis_rev):
        Exception.__init__(self)
        self.content = content
        self.basis_rev = basis_rev
python-wikkid_0.4.orig/wikkid/filestore/basefile.py0000644000000000000000000000142614160200415017523 0ustar00#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""Base classes for other filestores to use."""

import mimetypes

import breezy.urlutils as urlutils

from wikkid.interface.filestore import FileType


class BaseFile(object):
    """Provide common fields and methods and properties for files."""

    def __init__(self, path):
        self.path = path
        self.base_name = urlutils.basename(path)
        self._mimetype = mimetypes.guess_type(self.base_name)[0]

    @property
    def mimetype(self):
        """If the file_type is a directory, return None."""
        if self.file_type == FileType.DIRECTORY:
            return None
        else:
            return self._mimetype
python-wikkid_0.4.orig/wikkid/filestore/bzr.py0000644000000000000000000003123014336241273016556 0ustar00#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""A bzr backed filestore."""

from datetime import datetime
import logging

from zope.interface import implementer
from merge3 import Merge3

from breezy.bzr.generate_ids import gen_file_id
from breezy.errors import BinaryFile
from breezy.osutils import splitpath, split_lines
from breezy.revision import NULL_REVISION
from breezy.textfile import check_text_lines
from breezy.transform import FinalPaths, MalformedTransform
from breezy.urlutils import basename, dirname, joinpath

from wikkid.filestore import FileExists, UpdateConflicts
from wikkid.filestore.basefile import BaseFile
from wikkid.interface.filestore import FileType, IFile, IFileStore


def normalize_line_endings(content, ending=b'\n'):
    return ending.join(content.splitlines())


def get_line_ending(lines):
    """Work out the line ending used in lines."""
    if len(lines) == 0:
        return b'\n'
    first = lines[0]
    if first.endswith(b'\r\n'):
        return b'\r\n'
    # Default to \n if there are no line endings.
    return b'\n'


def get_commit_message(commit_message):
    if commit_message is None or commit_message.strip() == '':
        return 'No description of change given.'
    return commit_message


def normalize_content(content):
    # Default to simple '\n' line endings.
    content = normalize_line_endings(content)
    # Make sure the content ends with a new-line.  This makes
    # end of file conflicts nicer.
    if not content.endswith(b'\n'):
        content += b'\n'
    return content


def iter_paths(path):
    path_segments = splitpath(path)
    while len(path_segments) > 0:
        tail = path_segments.pop()
        if len(path_segments) == 0:
            yield '', tail
        else:
            yield joinpath(*path_segments), tail


def create_parents(tt, path, trans_id):
    prev_trans_id = trans_id
    for parent_path, tail in iter_paths(path):
        trans_id = tt.trans_id_tree_path(parent_path)
        if tt.tree_kind(trans_id) is not None:
            break
        tt.adjust_path(tail, trans_id, prev_trans_id)
        tt.create_directory(trans_id)
        tt.version_file(trans_id=trans_id, file_id=gen_file_id(basename(path)))
        prev_trans_id = trans_id


@implementer(IFileStore)
class FileStore(object):
    """Wraps a Bazaar branch to be a filestore."""

    def __init__(self, tree):
        self.tree = tree
        self.branch = tree.branch
        self.logger = logging.getLogger('wikkid')

    def basis_tree(self):
        return self.tree.basis_tree()

    def get_file(self, path):
        """Return an object representing the file at specified path."""
        if not self.tree.is_versioned(path):
            return None
        else:
            return File(self, path)

    def update_file(self, path, content, author, parent_revision,
                    commit_message=None):
        """Update the file at the specified path with the content.

        This is going to be really interesting when we need to deal with
        conflicts.
        """
        commit_message = get_commit_message(commit_message)
        if parent_revision is None:
            parent_revision = NULL_REVISION
        # Firstly we want to lock the tree for writing.
        with self.tree.lock_write():
            # Look to see if the path is there.  If it is then we are doing an
            # update.  If it isn't we are doing an add.
            if self.tree.is_versioned(path):
                # What if a parent_revision hasn't been set?
                self._update_file(
                    path, content, author, parent_revision,
                    commit_message)
            else:
                self._add_file(path, content, author, commit_message)

    def _ensure_directory_or_nonexistant(self, dir_path):
        """Ensure the dir_path defines a directory or doesn't exist.

        Walk up the dir_path and make sure that the path either doesn't exist
        at all, or is a directory.  The purpose of this is to make sure we
        don't try to add a file in a directory where the directory has the
        same name as an existing file.
        """
        check = []
        while dir_path:
            check.append(dir_path)
            dir_path = dirname(dir_path)
        while len(check):
            f = self.get_file(check.pop())
            if f is not None:
                if not f.is_directory:
                    raise FileExists(
                        '%s exists and is not a directory' % f.path)

    def _add_file(self, path, content, author, commit_message):
        """Add a new file at the specified path with the content.

        Then commit this new file with the specified commit_message.
        """
        content = normalize_content(content)
        t = self.tree.controldir.root_transport
        # Get a transport for the path we want.
        self._ensure_directory_or_nonexistant(dirname(path))
        t = t.clone(dirname(path))
        t.create_prefix()
        # Put the file there.
        # TODO: UTF-8 encode text files?
        t.put_bytes(basename(path), content)
        self.tree.smart_add([t.local_abspath('.')])
        self.tree.commit(
            message=commit_message,
            authors=[author])

    def _get_final_text(self, content, f, parent_revision):
        current_rev = f.last_modified_in_revision
        wt = self.tree
        current_lines = wt.get_file_lines(f.path)
        basis = self.branch.repository.revision_tree(parent_revision)
        basis_lines = basis.get_file_lines(f.path)
        # need to break content into lines.
        ending = get_line_ending(current_lines)
        # If the content doesn't end with a new line, add one.
        new_lines = split_lines(content)
        # Look at the end of the first string.
        new_ending = get_line_ending(new_lines)
        if ending != new_ending:
            # I know this is horribly inefficient, but lets get it working
            # first.
            content = normalize_line_endings(content, ending)
            new_lines = split_lines(content)
        if len(new_lines) > 0 and not new_lines[-1].endswith(ending):
            new_lines[-1] += ending
        merge = Merge3(basis_lines, new_lines, current_lines)
        result = b''.join(merge.merge_lines())  # or merge_regions or whatever
        conflicted = (b'>>>>>>>' + ending) in result
        if conflicted:
            raise UpdateConflicts(result, current_rev)
        return result

    def _update_file(self, path, content, author, parent_revision,
                     commit_message):
        """Update an existing file with the content.

        This method merges the changes in based on the parent revision.
        """
        f = File(self, path)
        wt = self.tree
        with wt.lock_write():
            result = self._get_final_text(content, f, parent_revision)
            wt.controldir.root_transport.put_bytes(path, result)
            wt.commit(
                message=commit_message, authors=[author],
                specific_files=[path])

    def list_directory(self, directory_path):
        """Return a list of File objects for in the directory path.

        If the path doesn't exist, returns None.  If the path exists but is
        empty, an empty list is returned.  Otherwise a list of File objects in
        that directory.
        """
        if directory_path is not None:
            directory = self.get_file(directory_path)
            if directory is None or directory.file_type != FileType.DIRECTORY:
                return None
        listing = []
        wt = self.tree
        with wt.lock_read():
            for fp, fc, fkind, entry in wt.list_files(
                    from_dir=directory_path, recursive=False):
                if fc != 'V':
                    # If the file isn't versioned, skip it.
                    continue
                if directory_path is None:
                    file_path = fp
                else:
                    file_path = joinpath(directory_path, fp)
                listing.append(File(self, file_path))
            return listing


@implementer(IFile)
class File(BaseFile):
    """Represents a file in the Bazaar branch."""

    def __init__(self, filestore, path):
        BaseFile.__init__(self, path)
        self.filestore = filestore
        # This isn't entirely necessary.
        self.tree = self.filestore.tree
        self.file_type = self._get_filetype()
        self._last_modified_in_revision = None

    def _get_filetype(self):
        """Work out the filetype based on the mimetype if possible."""
        with self.tree.lock_read():
            is_directory = ('directory' == self.tree.kind(self.path))
            if is_directory:
                return FileType.DIRECTORY
            else:
                if self._mimetype is None:
                    binary = self._is_binary
                else:
                    binary = not self._mimetype.startswith('text/')
                if binary:
                    return FileType.BINARY_FILE
                else:
                    return FileType.TEXT_FILE

    def get_content(self):
        with self.tree.lock_read():
            # basis_tree is a revision tree, queries the repositry.
            # to get the stuff off the filesystem use the working tree
            # which needs to start with that.  WorkingTree.open('.').
            # branch = tree.branch.
            return self.tree.get_file_text(self.path)

    @property
    def last_modified_in_revision(self):
        if self._last_modified_in_revision is None:
            try:
                self._last_modified_in_revision = self.tree.get_file_revision(
                    self.path)
            except AttributeError:
                bt = self.tree.basis_tree()
                self._last_modified_in_revision = bt.get_file_revision(
                    self.path)
        return self._last_modified_in_revision

    @property
    def last_modified_by(self):
        """Return the first author for the revision."""
        repo = self.filestore.branch.repository
        rev = repo.get_revision(self.last_modified_in_revision)
        return rev.get_apparent_authors()[0]

    @property
    def last_modified_date(self):
        """Return the last modified date for the revision."""
        repo = self.filestore.branch.repository
        rev = repo.get_revision(self.last_modified_in_revision)
        return datetime.utcfromtimestamp(rev.timestamp)

    @property
    def _is_binary(self):
        """True if the file is binary."""
        try:
            with self.tree.lock_read():
                lines = self.tree.get_file_lines(self.path)
                check_text_lines(lines)
            return False
        except BinaryFile:
            return True

    @property
    def is_directory(self):
        """Is this file a directory?"""
        return 'directory' == self.tree.kind(self.path)

    def update(self, content, user):
        raise NotImplementedError()


class BranchFileStore(FileStore):

    def __init__(self, branch):
        self.branch = branch
        self.tree = branch.basis_tree()
        self.logger = logging.getLogger('wikkid')

    def basis_tree(self):
        return self.tree

    def update_file(self, path, content, author, parent_revision,
                    commit_message=None):
        commit_message = get_commit_message(commit_message)
        with self.branch.lock_write():
            if self.tree.is_versioned(path):
                f = File(self, path)
                content = self._get_final_text(content, f, parent_revision)
            else:
                content = normalize_content(content)
            if not isinstance(content, bytes):
                raise TypeError(content)
            with self.tree.preview_transform() as tt:
                trans_id = tt.trans_id_tree_path(path)
                if tt.tree_kind(trans_id) is not None:
                    tt.delete_contents(trans_id)
                else:
                    name = splitpath(path)[-1]
                    tt.version_file(
                        trans_id=trans_id, file_id=gen_file_id(name))
                    create_parents(tt, path, trans_id)
                tt.create_file([content], trans_id)
                try:
                    tt.commit(self.branch, commit_message, authors=[author])
                except MalformedTransform as e:
                    for conflict in e.conflicts:
                        if conflict[0] == 'non-directory parent':
                            path = FinalPaths(tt).get_path(trans_id)
                            raise FileExists(
                                '%s exists and is not a directory' %
                                conflict[1])
                    raise

            self.tree = self.branch.basis_tree()
python-wikkid_0.4.orig/wikkid/filestore/git.py0000644000000000000000000001567114160200415016543 0ustar00#
# Copyright (C) 2012 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""A git filestore using Dulwich.

"""

import datetime
import mimetypes

from dulwich.objects import Blob, Tree, ZERO_SHA
from dulwich.object_store import tree_lookup_path
from dulwich.repo import Repo
from dulwich.walk import Walker
import posixpath
import stat

from zope.interface import implementer

from wikkid.filestore import FileExists, UpdateConflicts
from wikkid.interface.filestore import FileType, IFile, IFileStore


@implementer(IFileStore)
class FileStore(object):
    """A filestore that just uses an internal map to store data."""

    _encoding = 'utf-8'

    @classmethod
    def from_path(cls, path):
        return cls(Repo(path))

    def __init__(self, repo, ref=b'HEAD'):
        """Repo is a dulwich repository."""
        self.repo = repo
        self.ref = ref

    @property
    def store(self):
        return self.repo.object_store

    def _get_root(self, revision=None):
        if revision is None:
            try:
                revision = self.repo.refs[self.ref]
            except KeyError:
                revision = ZERO_SHA
        try:
            return (revision, self.repo[revision].tree)
        except KeyError:
            return None, None

    def get_file(self, path):
        """Return an object representing the file."""
        commit_id, root_id = self._get_root()
        if root_id is None:
            return None
        try:
            (mode, sha) = tree_lookup_path(
                self.store.__getitem__,
                root_id, path.encode(self._encoding))
        except KeyError:
            return None
        return File(
            self.store, mode, sha, path, commit_id, encoding=self._encoding)

    def update_file(self, path, content, author, parent_revision,
                    commit_message=None):
        """The `author` is updating the file at `path` with `content`."""
        commit_id, root_id = self._get_root()
        if root_id is None:
            root_tree = Tree()
        else:
            root_tree = self.store[root_id]
        # Find all tree objects involved
        tree = root_tree
        trees = [root_tree]
        elements = path.strip(posixpath.sep).split(posixpath.sep)
        for el in elements[:-1]:
            try:
                (mode, sha) = tree[el.encode(self._encoding)]
            except KeyError:
                tree = Tree()
            else:
                if not stat.S_ISDIR(mode):
                    raise FileExists(
                        "File %s exists and is not a directory" % el)
                tree = self.store[sha]
            trees.append(tree)
        if elements[-1] in tree:
            (old_mode, old_sha) = tree[elements[-1]]
            if stat.S_ISDIR(old_mode):
                raise FileExists("File %s exists and is a directory" % path)
            if old_sha != parent_revision and parent_revision is not None:
                raise UpdateConflicts(
                    "File conflict %s != %s" % (
                        old_sha, parent_revision), old_sha)
        if not isinstance(content, bytes):
            raise TypeError(content)
        blob = Blob.from_string(content)
        child = (stat.S_IFREG | 0o644, blob.id)
        self.store.add_object(blob)
        assert len(trees) == len(elements)
        for tree, name in zip(reversed(trees), reversed(elements)):
            assert name != ""
            tree[name.encode(self._encoding)] = child
            self.store.add_object(tree)
            child = (stat.S_IFDIR, tree.id)
        if commit_message is None:
            commit_message = ""
        if author is not None:
            author = author.encode(self._encoding)
        self.repo.do_commit(
            ref=self.ref, message=commit_message.encode(self._encoding),
            author=author, tree=child[1])

    def list_directory(self, directory_path):
        """Return a list of File objects for in the directory path.

        If the path doesn't exist, returns None.  If the path exists but is
        empty, an empty list is returned.  Otherwise a list of File objects in
        that directory.
        """
        if directory_path is None:
            directory_path = ''
        else:
            directory_path = directory_path.strip(posixpath.sep)
        commit_id, root_id = self._get_root()
        if directory_path == '':
            sha = root_id
            mode = stat.S_IFDIR
        else:
            if root_id is None:
                return None
            try:
                (mode, sha) = tree_lookup_path(
                    self.store.__getitem__,
                    root_id, directory_path.encode(self._encoding))
            except KeyError:
                return None
        if mode is not None and stat.S_ISDIR(mode):
            ret = []
            for (name, mode, sha) in self.store[sha].items():
                ret.append(
                    File(self.store, mode, sha,
                         posixpath.join(
                             directory_path, name.decode(self._encoding)),
                         commit_id, encoding=self._encoding))
            return ret
        else:
            return None


@implementer(IFile)
class File(object):
    """A Git file object."""

    def __init__(self, store, mode, sha, path, commit_sha, encoding):
        self.store = store
        self.encoding = encoding
        self.mode = mode
        self.sha = sha
        self.path = path
        self.commit_sha = commit_sha
        self.base_name = posixpath.basename(path)
        self.mimetype = mimetypes.guess_type(self.base_name)[0]

    @property
    def file_type(self):
        """Work out the filetype based on the mimetype if possible."""
        if self._is_directory:
            return FileType.DIRECTORY
        else:
            if self.mimetype is None:
                binary = self._is_binary
            else:
                binary = not self.mimetype.startswith('text/')
            if binary:
                return FileType.BINARY_FILE
            else:
                return FileType.TEXT_FILE

    def get_content(self):
        o = self.store[self.sha]
        if isinstance(o, Blob):
            return o.data
        else:
            return None

    @property
    def _is_directory(self):
        return stat.S_ISDIR(self.mode)

    @property
    def _is_binary(self):
        return b'\0' in self.get_content()

    def _get_last_modified_commit(self):
        walker = Walker(
            self.store, include=[self.commit_sha],
            paths=[self.path.encode(self.encoding)])
        return next(iter(walker)).commit

    @property
    def last_modified_in_revision(self):
        return self.sha

    @property
    def last_modified_by(self):
        return self._get_last_modified_commit().author.decode(self.encoding)

    @property
    def last_modified_date(self):
        c = self._get_last_modified_commit()
        return datetime.datetime.utcfromtimestamp(c.commit_time)
python-wikkid_0.4.orig/wikkid/filestore/volatile.py0000644000000000000000000001142414160200415017567 0ustar00#
# Copyright (C) 2010 Wikkid Developers.
#
# This software is licensed under the GNU Affero General Public License
# version 3 (see the file LICENSE).

"""A volatile filestore.

Used primarily for test purposes, this class should be a fully functional
filestore, albiet one that doesn't remember anything persistently.
"""

from datetime import datetime
from itertools import count

from breezy.urlutils import dirname
from zope.interface import implementer

from wikkid.filestore import FileExists
from wikkid.filestore.basefile import BaseFile
from wikkid.interface.filestore import FileType, IFile, IFileStore


@implementer(IFileStore)
class FileStore(object):
    """A filestore that just uses an internal map to store data."""

    def __init__(self, files=None):
        """Files is a list of tuples.

        If the content is None, the path is assumed to be a directory.  If the
        content contains a null character, the file is considered binary.
        """
        self._integer = count(1)
        self.file_id_map = {}
        self.path_map = {}
        if files is None:
            files = []
        user = 'First User {0}
".format(cgi.escape(text)) python-wikkid_0.4.orig/wikkid/formatter/registry.py0000644000000000000000000000462614160200415017635 0ustar00# -*- coding: utf-8 -*- # # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The formatter registry is able to return formatters by name.""" import re from wikkid.formatter.pygmentsformatter import PygmentsFormatter from wikkid.formatter.restformatter import RestructuredTextFormatter # Both textile and markdown are optional. try: from wikkid.formatter.markdownformatter import MarkdownFormatter has_markdown = True except ImportError: has_markdown = False try: from wikkid.formatter.textileformatter import TextileFormatter has_textile = True except ImportError: has_textile = False class FormatterRegistry(object): """Has a dictionary of formatters based on name.""" def __init__(self): self.formatters = { 'pygments': PygmentsFormatter(), 'rest': RestructuredTextFormatter(), } if has_markdown: self.formatters['markdown'] = MarkdownFormatter() if has_textile: self.formatters['textile'] = TextileFormatter() def __getitem__(self, formatter): return self.formatters[formatter] formatter_registry = FormatterRegistry() def get_formatter(name): """Get a formatter by name.""" return formatter_registry[name] FORMAT_MATCHER = re.compile(r'^#\s+(\w+).*$') def get_wiki_formatter(content: str, default_formatter: str) -> str: """Choose a wiki formatter based on the first line of content. Args: content: The content of the file. default_formatter: The name of the default formatter. The first line of the content may specify a formatter using the form: # formatter-name For example: # pygments The first line must start with a # and the first word must specify a formatter name. If niether of those match, the default_formatter is returned. If the default_formatter doesn't exist, a key error is raised. """ end_of_line = content.find('\n') match = FORMAT_MATCHER.match(content[:end_of_line]) if match is not None: try: name = match.group(1).lower() formatter = formatter_registry[name] return content[end_of_line + 1:], formatter except KeyError: # Fall through to returning the default. pass return content, formatter_registry[default_formatter] python-wikkid_0.4.orig/wikkid/formatter/restformatter.py0000644000000000000000000000132414160200415020656 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """A text to html formatter using reStuctured Text.""" from docutils.core import publish_parts from zope.interface import implementer from wikkid.interface.formatter import ITextFormatter @implementer(ITextFormatter) class RestructuredTextFormatter(object): """Format text as HTML using restructured text.""" def format(self, filename, text): """Format the text. I'm almost 100% positive that this method needs more args. """ parts = publish_parts(text, writer_name='html') return parts['html_title'] + parts['body'] python-wikkid_0.4.orig/wikkid/formatter/textileformatter.py0000644000000000000000000000107314160200415021360 0ustar00# -*- coding: utf-8 -*- # # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """A text to html formatter using textile.""" from textile import textile from zope.interface import implementer from wikkid.interface.formatter import ITextFormatter @implementer(ITextFormatter) class TextileFormatter(object): """Format source files as HTML using textile.""" def format(self, filename, text): """Format the text. """ return textile(text) python-wikkid_0.4.orig/wikkid/interface/__init__.py0000644000000000000000000000027711372503456017474 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Interfaces for the Wikkid Wiki.""" python-wikkid_0.4.orig/wikkid/interface/filestore.py0000644000000000000000000000606014160200415017710 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Interfaces relating to filestores.""" from zope.interface import Attribute, Interface class IFileStore(Interface): """A file store is how pages are accessed and updated. This interface defines methods for getting file contents, checking to see if the file exists, updating files, checking differences, and many other wiki type thiings. """ def get_file(path): """Return an object representing the file at specified path.""" def update_file(path, content, author, parent_revision, commit_message=None): """Update a text file at the given path with the content. :param path: The path of the file. :param content: The content of the file. :param author: Who is doing the updating. :type author: String :param parent_revision: The revision that the user was editing when they made the changes. For a new revision this parameter will be None. :type parent_revision: String :param commit_message: An optional commit message. If one isn't provided, then some sensible default will be used. """ def list_directory(path): """Return a list of IFile objects. Each of the IFile objects will be directly in the directory specified by the path. If the specified path is None, the files in the root directory of the branch are returned. If the specified path doesn't exist, None is returned. If the specified path exists but has no files, an empty list is returned. """ class FileType(object): """Package lazr.enum and use an Enumerated Type.""" MISSING = 1 # The file at the address does not exist. WIKI_PAGE = 2 # The resource is a wiki page. DIRECTORY = 3 # The resource is a directory. TEXT_FILE = 4 # A text file that isn't a wiki page. BINARY_FILE = 5 # A (most likely) binary file. class IFile(Interface): """A file from the file store.""" path = Attribute( "The full path of the page with respect to the root of the " "file store.") base_name = Attribute("The last part of the path.") file_type = Attribute("Soon to be a Choice with a lazr.enum.") mimetype = Attribute( "The guessed mimetype for the file. Directories don't have a " "mimetype.") last_modified_in_revision = Attribute( "The revision id of the last revision that this file was " "modified in.") last_modified_by = Attribute("The person who last modified the file.") last_modified_date = Attribute( 'The timestamp of the revision that last modified the file. ' 'This is a naive datetime object in UTC.') def get_content(): """Get the contents of the file. :return: None if the file doesn't yet exist, or u'' if the file is empty, otherwise the unicode content of the file. """ python-wikkid_0.4.orig/wikkid/interface/formatter.py0000644000000000000000000000064211372503456017734 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Interface for the text formatters.""" from zope.interface import Interface class ITextFormatter(Interface): """A text formatter takes plain text and makes HTML of some form.""" def format(filename, text): """Takes text, and returns HTML.""" python-wikkid_0.4.orig/wikkid/interface/resource.py0000644000000000000000000000643711410375636017570 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Interfaces for the different resource types.""" from zope.interface import Attribute, Interface class IDefaultPage(Interface): """A marker interface for the default wiki page.""" class IResource(Interface): """The base resource interface.""" path = Attribute('The full path for the resource.') preferred_path = Attribute('The preferred path for the resource.') write_filename = Attribute( 'The full path of the file to write to in the filestore. ' 'This is either the filename as it directly corresponds to the ' 'path, or a related wiki page location.') root_resource = Attribute( 'The root resource is the object that represents the root of the wiki.' ) default_resource = Attribute( 'The default resource is the default wiki page.' ) class IUpdatableResource(IResource): """Reflects either a file either missing or actual.""" def put_bytes(bytes, committer, rev_id, commit_msg): """Update the content of the resource with the bytes specified. :param bytes: A byte string reflecting the new file content. :param committer: The committer string that will be used. :param rev_id: The base revision id for the text being edited. None when adding a new file. :param commit_msg: The message to associate with this edit. """ class IFileResource(IUpdatableResource): """A resource that relates to a file in the filestore.""" # TODO: think of a better variable name. file_resource = Attribute( 'An IFile representing the file in the filestore.') mimetype = Attribute('The mimetype of the file.') def get_bytes(): """Returns the bytes of the binary file.""" last_modified_in_revision = Attribute( 'The revision id where the file was last modified.') last_modified_by = Attribute( 'The author of the revision that last modified the file.') last_modified_date = Attribute( 'The timestamp of the revision that last modified the file. ' 'This is a naive datetime object in UTC.') class IDirectoryResource(IResource): """A resource that relates to a file in the filestore.""" def get_dir_name(): """Get the full directory name. This is the full path for the directory from the root. """ # TODO: think of a better variable name. dir_resource = Attribute( 'An IFile representing the directory in the filestore.') def get_listing(): """Returns a list of objects.""" class IRootResource(IDirectoryResource): """A special resource relating to the root object in the wiki.""" has_home_page = Attribute( 'True if there is a home wiki page defined.') class IBinaryFile(IFileResource): """A marker interface for binary files.""" class ITextFile(IFileResource): """A marker interface for text files.""" class IWikiTextFile(ITextFile): """A marker interface for a wiki text file.""" class ISourceTextFile(ITextFile): """A marker interface for a non-wiki text file.""" class IMissingResource(IResource): """A resource that doesn't exist in the filestore.""" python-wikkid_0.4.orig/wikkid/interface/user.py0000644000000000000000000000146711372503456016715 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Interfaces relating to users of the wiki.""" from zope.interface import Attribute, Interface class IUserFactory(Interface): """A factory to create `IUser`s.""" def create(request): """Returns an `IUser`.""" class IUser(Interface): """Information about the editing user. Note: probably implementations - test identity - bzr identity - anonymous identity - launchpad identity - session identity """ email = Attribute("The user's email adderss.") display_name = Attribute( "The name that is shown through the user interface.") committer_id = Attribute("The user's name and email address.") python-wikkid_0.4.orig/wikkid/model/__init__.py0000644000000000000000000000036411372503456016631 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Model classes for wikkid. Model objects are those objects that the views hang off. """ python-wikkid_0.4.orig/wikkid/model/baseresource.py0000644000000000000000000000257614160200415017546 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The base resource class.""" import breezy.urlutils as urlutils from wikkid.interface.resource import IRootResource class BaseResource(object): """Information about a resource.""" def __init__(self, server, path, write_filename, file_resource, dir_resource): self.factory = server self.path = path self.write_filename = write_filename self.file_resource = file_resource self.dir_resource = dir_resource @property def preferred_path(self): return self.factory.get_preferred_path(self.path) @property def base_name(self): return urlutils.basename(self.path) @property def dir_name(self): return urlutils.dirname(self.path) @property def parent(self): if IRootResource.providedBy(self): return None return self.factory.get_resource_at_path(self.dir_name) @property def default_resource(self): """Any resource should be able to get to the default resource.""" return self.factory.get_default_resource() @property def root_resource(self): """Any resource should be able to get to the root resource.""" return self.factory.get_resource_at_path('/') python-wikkid_0.4.orig/wikkid/model/binary.py0000644000000000000000000000114214160200415016334 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The binary resource class. A binary resource is a file that isn't text. This is primarily guessed using the mimetype library. """ from zope.interface import implementer from wikkid.model.file import FileResource from wikkid.interface.resource import IBinaryFile @implementer(IBinaryFile) class BinaryResource(FileResource): """A binary resource is a non-text file.""" def __repr__(self): return "" % self.path python-wikkid_0.4.orig/wikkid/model/directory.py0000644000000000000000000000333514160200415017062 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The directory resource class. A directory resource is one where the path specifies a directory in the filestore. """ from zope.interface import implementer from wikkid.model.missing import MissingResource from wikkid.interface.filestore import FileType from wikkid.interface.resource import IDirectoryResource class DirectoryMethods(object): """Directory methods are used by DirectoryResource and WikiTextFile. The methods are only valid on WikiTextFile objects when there is a directory with the same name as the wiki file without the '.txt' """ def get_dir_name(self): return self.dir_resource.path def get_listing(self): """Return a list of resources that are in this directory.""" dir_name = self.get_dir_name() filestore = self.factory.filestore listing = [] for entry in filestore.list_directory(dir_name): if entry.file_type == FileType.DIRECTORY: file_resource = None dir_resource = entry else: file_resource = entry dir_resource = None file_path = entry.path listing.append( self.factory.get_resource( '/' + file_path, file_path, file_resource, dir_resource)) return listing @implementer(IDirectoryResource) class DirectoryResource(MissingResource, DirectoryMethods): """A directory in the filestore. By definition, a directory is also a missing wiki page. """ def __repr__(self): return "" % self.path python-wikkid_0.4.orig/wikkid/model/factory.py0000644000000000000000000001115514160200415016524 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The server class for the wiki.""" import logging from breezy.urlutils import basename, dirname, joinpath from zope.interface import directlyProvides from wikkid.interface.filestore import FileType from wikkid.interface.resource import IDefaultPage from wikkid.model.binary import BinaryResource from wikkid.model.directory import DirectoryResource from wikkid.model.missing import MissingResource from wikkid.model.root import RootResource from wikkid.model.sourcetext import SourceTextFile from wikkid.model.wikitext import WikiTextFile class ResourceFactory(object): """Factory to create the model objects used by the views.""" DEFAULT_PATH = 'Home' def __init__(self, filestore): """Construct the factory. :param filestore: An `IFileStore` instance. :param user_factory: A factory to create users. :param skin_name: The name of a skin to use. """ self.filestore = filestore self.logger = logging.getLogger('wikkid') def get_resource(self, path, file_path, file_resource, dir_resource): """Return the correct type of resource based on the params.""" filename = basename(file_path) if path == '/': return RootResource( self, path, file_path, file_resource, None) elif file_resource is not None: # We are pointing at a file. file_type = file_resource.file_type if file_type == FileType.BINARY_FILE: # Binary resources have no associated directory. return BinaryResource( self, path, file_path, file_resource, None) # This is known to be not entirely right. if filename.endswith('.txt') or '.' not in file_resource.base_name: return WikiTextFile( self, path, file_path, file_resource, dir_resource) else: return SourceTextFile( self, path, file_path, file_resource, None) elif dir_resource is not None: return DirectoryResource( self, path, file_path, None, dir_resource) else: return MissingResource( self, path, file_path, None, None) def get_default_resource(self): """Return the Home resource.""" return self.get_resource_at_path('/' + self.DEFAULT_PATH) def get_root_resource(self): """Return the root resource.""" return self.get_resource_at_path('/') def get_resource_at_path(self, path): """Get the resource from the filestore for the specified path. The path starts with a slash as proveded through the url traversal, the filestore does not expect nor want a leading slash. It is the responsibility of this method to remove the leading slash. """ assert path.startswith('/') file_path = path[1:] is_default = False if file_path == '' or file_path == self.DEFAULT_PATH: file_path = self.DEFAULT_PATH is_default = True dir_resource = None file_resource = self.filestore.get_file(file_path) # If the resource exists and is a file, we are done. if file_resource is not None: if file_resource.file_type != FileType.DIRECTORY: return self.get_resource(path, file_path, file_resource, None) else: dir_resource = file_resource file_resource = None if '.' not in basename(file_path): file_path += '.txt' file_resource = self.filestore.get_file(file_path) resource = self.get_resource( path, file_path, file_resource, dir_resource) if is_default: directlyProvides(resource, IDefaultPage) return resource def get_preferred_path(self, path): """Get the preferred path for the path passed in. If the path ends with '.txt' and doesn't have any other '.'s in the basename, then we prefer to access that file without the '.txt'. If the resulting path is the default path, then the preferred path should be '/Home' providing Home is the default path.. """ filename = basename(path) if filename.endswith('.txt'): filename = filename[:-4] if path == '/': return '/' + self.DEFAULT_PATH elif '.' in filename: return path else: return joinpath(dirname(path), filename) python-wikkid_0.4.orig/wikkid/model/file.py0000644000000000000000000000261514160200415015775 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The model class for file resources.""" from zope.interface import implementer from wikkid.model.baseresource import BaseResource from wikkid.interface.resource import IFileResource, IUpdatableResource from wikkid.user.bzr import create_bzr_user_from_author_string @implementer(IUpdatableResource) class UpdatableResource(BaseResource): """Reflects either a file either missing or actual.""" def put_bytes(self, content: bytes, committer, rev_id, commit_msg): """Update the file resource.""" self.factory.filestore.update_file( self.write_filename, content, committer, rev_id, commit_msg) @implementer(IFileResource) class FileResource(UpdatableResource): """Anything that relates to all files.""" @property def mimetype(self): return self.file_resource.mimetype @property def last_modified_in_revision(self): return self.file_resource.last_modified_in_revision @property def last_modified_date(self): return self.file_resource.last_modified_date @property def last_modified_by(self): return create_bzr_user_from_author_string( self.file_resource.last_modified_by) def get_bytes(self): return self.file_resource.get_content() python-wikkid_0.4.orig/wikkid/model/missing.py0000644000000000000000000000137314160200415016527 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The missing resource class. A missing resource is a file that has been asked for that doesn't exist. A model object exists for a missing resource as in a wiki you can have views on things that aren't there, like a page asking if you want to make a wiki page there. """ from zope.interface import implementer from wikkid.model.file import UpdatableResource from wikkid.interface.resource import IMissingResource @implementer(IMissingResource) class MissingResource(UpdatableResource): """Information about a resource.""" def __repr__(self): return "" % self.path python-wikkid_0.4.orig/wikkid/model/root.py0000644000000000000000000000214014160200415016032 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The root resource class. The root resource represents the object at the root of the wiki path. Currently this just refers to '/', but it is expected that at some stage in the not too distant future the server will support a wiki root where it is not the root path. """ from zope.interface import implementer from wikkid.model.directory import DirectoryResource from wikkid.interface.resource import IRootResource @implementer(IRootResource) class RootResource(DirectoryResource): """The root of the wiki. Some special wiki views hang off the root resource and not others. A root resource is also a directory resource where the directory is the root of the filesystem. """ def get_dir_name(self): return None @property def has_home_page(self): return self.file_resource is not None def __repr__(self): return "" @property def preferred_path(self): return '/' python-wikkid_0.4.orig/wikkid/model/sourcetext.py0000644000000000000000000000075214160200415017263 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The source text class. A source text file is a text file that isn't a wiki file. """ from zope.interface import implementer from wikkid.model.textfile import TextFile from wikkid.interface.resource import ISourceTextFile @implementer(ISourceTextFile) class SourceTextFile(TextFile): """A text file that isn't a wiki page.""" python-wikkid_0.4.orig/wikkid/model/textfile.py0000644000000000000000000000104214160200415016673 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The source text class. A source text file is a text file that isn't a wiki file. """ from zope.interface import implementer from wikkid.model.file import FileResource from wikkid.interface.resource import ITextFile @implementer(ITextFile) class TextFile(FileResource): """A text file that isn't a wiki page.""" def __repr__(self): return "" % self.path python-wikkid_0.4.orig/wikkid/model/wikitext.py0000644000000000000000000000176414160200415016732 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The wiki text class. A text file that contains text that will be formatted into HTML using one of the formatters. """ from zope.interface import directlyProvides, implementer from wikkid.model.directory import DirectoryMethods from wikkid.model.textfile import TextFile from wikkid.interface.resource import IDirectoryResource, IWikiTextFile @implementer(IWikiTextFile) class WikiTextFile(TextFile, DirectoryMethods): """A text file that represents a wiki page.""" def __init__(self, server, path, write_filename, file_resource, dir_resource): super(WikiTextFile, self).__init__( server, path, write_filename, file_resource, dir_resource) if dir_resource is not None: directlyProvides(self, IDirectoryResource) def __repr__(self): return "" % self.path python-wikkid_0.4.orig/wikkid/skin/__init__.py0000644000000000000000000000037511372503456016477 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The skins that are provided for Wikkid Wiki. The are the following defined skins: default """ python-wikkid_0.4.orig/wikkid/skin/default/0000755000000000000000000000000011361301511015767 5ustar00python-wikkid_0.4.orig/wikkid/skin/loader.py0000644000000000000000000000416014160200415016165 0ustar00# -*- coding: utf-8 -*- # # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The skin is the outer look and feel of the wikkid wiki pages. It is intended that the user will be able to use one of the pre-defined skins (of which there is only the default right now) or provide a directory to their own skin as a command line argument. """ import logging import os.path import breezy.urlutils as urlutils from jinja2 import Environment, PackageLoader class Skin(object): """A Wikkid wiki skin.""" def __init__(self, skin_name=None): """Load the required templates.""" # Need to load the initial templates for the skin. if skin_name is None: skin_name = 'default' self.logger = logging.getLogger('wikkid') # TODO: if we are using a user defined directory for the skin, here is # where we'd use a different loader. loader = PackageLoader('wikkid.skin', skin_name) self.env = Environment(loader=loader) self.templates = { 'view_page': self.env.get_template('page.html'), 'edit_page': self.env.get_template('edit.html'), 'view_directory': self.env.get_template('directory_listing.html'), 'missing': self.env.get_template('missing-page.html'), 'missing-dir': self.env.get_template('missing-directory.html') } module_location = urlutils.dirname(__file__) self.dir_name = urlutils.joinpath(module_location, skin_name) def get_template(self, template_name): return self.templates[template_name] @property def favicon(self): location = os.path.abspath( urlutils.joinpath(self.dir_name, 'favicon.ico')) if os.path.exists(location): return location else: return None @property def static_dir(self): location = os.path.abspath( urlutils.joinpath(self.dir_name, 'static')) if os.path.exists(location): return location else: return None python-wikkid_0.4.orig/wikkid/skin/default/base.html0000644000000000000000000000317614160200415017577 0ustar00 {% block head %} {% block title %}{% endblock %} - Wikkid {% endblock %}
{% block content %}{% endblock %}
{% block footer %}{% endblock %}
python-wikkid_0.4.orig/wikkid/skin/default/directory_listing.html0000644000000000000000000000064511374463474022444 0ustar00{% extends "base.html" %} {% block title %}{{ view.title }}{% endblock %} {% block content %} {% for item in view.items %} {% endfor %}
{{ item.name }}
{{ view.content }} {% endblock %} {% block commands %} {% if view.user %} {% endif %} {% endblock %} python-wikkid_0.4.orig/wikkid/skin/default/edit.html0000644000000000000000000000150614160200415017605 0ustar00{% extends "base.html" %} {% block title %}{{ view.title }}{% endblock %} {% block content %} {% if view.message %}
{{ view.message }}
{% endif %}
Describe the change: {% if view.rev_id %} {% endif %}
or Cancel
{% if view.preview_content %}
{{ view.preview_content }}
{% endif %} {% endblock %} python-wikkid_0.4.orig/wikkid/skin/default/favicon.ico0000644000000000000000000000762411374642735020145 0ustar00‰PNG  IHDR@@ªiqÞ pHYsÄÄ•+FIDATxœíšyUÕÇ?¿sï[úõ[è¦Wz£éf³ÙQ@AˆhРƒ™—Ä%ÉŒQ˘šr2‹cœ˜ãLª¦fbÆŠ&êÔŒ£È`A‰@TD šÞè~Ýï3¼ûî»oé¦ÙL¦†_Õ«{ïY~÷ü¾¿õœûà£ÿ×$èœI2yòÜã> ÷êBsþè0Üc‰Çk>»MXQÐ1P¥ †€DÑ#*†QZù¢ m>~Jç]ëLšÿ†¥Ôú©TMh5‘1h5‘á© c$ â删ÎÕm0’ºzïÇp/m¦-ÂþŒeM­—¥ETV7¡ÌDŒ5 d:¨0®,ÆÀàQ˜Ã@ƒ S‰sÅiOO…Ì}êî#ÛM'Ëá3²ÂÐÃ}³ÁºäRŒª)†Hi1GsY}dé_ÛýôÈvÚÛ' ÷ê³j†¥~j¢3u"‹0R–ѦÎ,N”GSi-:í‚Y}é¹î|qÜÀ8¼œ+&Õ.&BÊB&PiæïŸLà AŒ^ Ö]`&¤2H¡]Ö=}^rÍ;Í;ÍËyN»G®KTF&Ÿq õ×^ˆè r!Áäj\ ,´«eïØ}äŒñ ×_ ð–4QÈ ‚O=…uiMl˜ëEÉHÇŒ3Ó(ºEÑéšQ´%´9&ŠV­MûêÇ¡¿}Ù¾±6ñêV±›"ã'¬ƒ„M1¢<#ªCØÆÆÆFÜÅ{-Ããë2€x5ŸeÞéq&ÅG¼nvñeIÞó`ž'ÐV<äûnGÄÂàU÷e:í[)e(å¶Þ{Sè•$»}‡´’€±)MF¨N–0‰žD–ȵ‡÷€fäÐÓ¯RJ¥hgÄ €!Ý.Ú¹wÛŸ-DKÓvuv(®úxµh;?*y–Á=h•L™tjQ©¹×tÙ\¨Ïéoˆà çý:€`#bÄ‹I Î=NŸÉ'C+Ï.i:¬Úx,²šç"›èU½ÙB»?ùa<@¥ï5LªÞ U‘ße`•Î>šâ•Ñ8Z2@zžsÇ•U}6$1l¼ÃÄVÓnue„¦Ð^Í;÷E\Ñò´—g €še >W8q…Mi?m=…@Z¡pvàŸí´÷óDt ª‹,Ó/†× fÔ¥2¶&;Ú𦙘ÄkÞ9.‘7{Li¹Âçÿ,ÅOÑ^ë/oΘvnðj^4XÀ„êå2¶¼ÃËljá‘#·CZ·ŒK;™ècLªì+%ŠÃB$&Ä»³óϪ:ê'L„öCøxÛ&2û‚•Eõ¨ £Ù÷ö[ûtoV¿²lFLMhÈPŒÑì~k=]­GRÝv#`¡½DNQTéevó“¹@¦Ò ˆ‰ôÈ IñÝ‹qÊ^2Õ§{Ä)–Œ‚¡ªNqä`2‹ñøK¯bá÷#"twuòÏ7/¦}Lj¤b†?TÌ7~º–â!ecØ÷áN¸j*a+ÒØ9 ùÒ÷ÃöÐZóÝo~½ñ)”ÃCY8EN¡ô—SNÏhx‡9Íoä Ü›’™/IQ}2Šþ¹.1¬^åòeçÆÕ$ûz(*3â’«œ"+EMÌ%Š£jGâoŸa ¤ÏÛ ££ƒ_½öRÖ Ž­$“Þòâ€Ç÷ý ®œø„ˆt÷ €³ÙŽMÝ“Îó'ŠþÞ,QÓÀ¡wp`ç6÷yæeWÐI&Xœ7çO\kPJ1~Þ$uÊŠ"1FΘïö¿´f5%ñ#YsÚκŸëÌÆ6ÆÕ?‘·H/"ÒªÊ/Y—6?úK¿Y¢¤Ô¢8’Ÿ·½¸Ü½ohh Ú<€`8Æð)³²ÆŠgÍ£G¥@>yÁpÔí_ýìr†…²ßì œE§´ÑÈå ‹ü~@ìªùÿ­"çõcê™çÜ, Bå°|+xwÝ/èîhsŸ[.YLRªF¶0´¶€D"áö:ovy/[ê¶ïÝ»—ƒ¿Yïú~šB:@ÁÈ/ž¶1•\uÁCéÚ@°cë­ŠyûD‹ÇÜÅ-¥@y,FPµÃóhûÝ~>þÍ&÷yÚ% 豊=ë2”³‰Ø²e öí S?uÅ%e4z,äÕW^¦4q,Èøs*@ß§­bÁ¸uTÇ~7¹"Òc—ÍþOìØ€>OV{ê}ÛlT:™à·kV`Œ`xÓH"M;g¡;æçÏ<Íûë~î>Oš¿˜š‰³ˆ–W) yeå“”æÖ¢Æëê¦rÍÒiŠˆªtÚ3ªäüîþ ¢ô>A<.!TSCù/رþ9â]æ_w+c8~ü8»6¿Â®×W¡“©4:}æE4.X†8h~òÉ'Üþz*â{ÈBÂOžÆÓÂ+‹&o¢®|cÂ_h«U·äÜè/žgr-Cƒß'44æ×ÄÝm­¼÷š»ûdÞâ«]ávìØþôöïØJ«S1÷òEîø_¬\A} /¯"jüý”¿š*zY2ý{"’?y D$©Êç>ª"#7ú»[âBY±†æ1¾‚/ùíÚ©j2ÅßMe^YK™ÝGwû1ö8±ÂÛŸH$ØðËå Г±ˆ?Ù;B‹ÏßDSõK _«¨l…U·t¿dE(м.#š-Á|~»·l õÀž¼ö/üQ³íkVäõoÛ¶ ½ÿÝ‚ /3ÅX"…w„ãê’,uŸˆôž"rܪYð3 V›~ŸÎábEM]!78Ê®Íë\+سgínwµýÑ–×èéÌ.cxyÕó”«xÁ…Wèy~¿2,ý"5e¯žHø~PÑÑXuWÉÞK¦2L[ƒK„–ñ…Ý০ýØQâñ8?{ôF„3€Ä»:øÕ/ÿË­ 8Àºå)ÌŽa:R8ÌÝÁ•þˆ$ Ï̦O3’Gßù÷ÞW–|Í$:R#Sbã¹O·§¿Mtvi~ôÃNzz ð+©å÷ÓÛ×Gß¡= /Jd•¶âÒ­§+)t¶µRÞ}XÿÇÀmñ™Ô&KpaŠŠàŸný1 .ø‹þ Ÿ\ê×TIÓ­Æe=YiÐ=3ôHq]"RŒ]XmVë'T´î¤¶s7¡d–𦷇ȑ÷©jÝI³>TXx B€RS”½çW–ÍÛÏE-÷ Vø÷­†/,™I¿Ç[üäœ:ÁqÒ2 mUé0AY©oTm‚eŸ»GŠ‹œ ¯­J'>h5,é`€èŸ›%êjmjjÏÞ9Ù(]ê|–s"tÀ‚¯/YEðÇN–× ,€íVÓµ+ ”÷T–c >K8ÿÿY±Û(šÍ²ŽºÿtÞQæNúËÁ>/1*6ò~{ô-å¦ÀÌw‚ÂY¢¥ÅGEÅ`ð=9ª4!ª(ÎøÿÄæ7-ùŽD£… †Ð`Wø¾~õÃe2Ú÷n“É;%²•pñœà·‚)¦•~QÙÃ]7,§¶òÑSå7(DĨâªíq·í+käºDÎÖ-ŒjöÑØxæ¾EŒ fhJóá Ü}Ëv¦M¸ýDõþ@4h‘ƒVÍ‚TíBãš¾[z?§áf Û>7?ˆ¯Ÿbæ¤ÈÀTSA| n¿~—Îü²ˆ ü‡„ÐÉ9©/ô¨5áŽ×%X‘ýûËêlæ\\`ƒp’T‚ŸÙ¦ ±nºæW^~­ïœ.ß“@Dº­’±ß¶ZnmU„œ,áÄéh9ïÔÍ@XHa_n^ÖÁMv‹DCëO™¡—÷ÉN‘7íQ_ü7Uwy&úkÉw ÏÁ©ÏR,Z¢¡áÔâÁt)£¥¨ ¾ýç­Üôå¯JqñòϤ<§2ÉÕ­ê[}Ý :÷ j€@W·æÅUÇyûݾÜD…'0>ášÒyø¾uÇÇL?ÿzñû_;•5÷ûŽShŒ™”Ü÷ÒÚĺ¯EÇs@ “.Íhmظ¹‡ ¯ÇéééŸ.º8ÂÜ)wéàüWRYþ-É?T8M:­,m‰›ï>üãäÖ´!™!͹k0bhkÓlÝÖËôÑÖ®ÑΗ¬PH5Æfòù~*§^×l¹ÿ¬àON'Õ D§€16‰ø¿ömþî×ôÎÇ…í绞?^á‚Òu\“H‚²R[•%ª!8k峪¨îšÁœìœ*vfŒ)¦¯óéĦ¿ù|r÷ R©€Áî_îRÿxwÚ}Eø§>ÔjW_~¡ˆì<Ý5D§]¬‹H¾ðWìÿ°Ñj\’óipY"k/…oÌ_%ìŠù}¶…‡3€ˆÂ¾Úžuÿëªåˆø³ÿPU`Û\p/oôÚ×|óCX¾Ÿœ‰µpíg’™1¦|$¹ë™…É·¾'&~”ܸçú žÀžp7¾7<‰üŠˆ> =ÃtÆwìÆ˜"àïuÇÞÛô–Šô¾5˜äñì¸~³AJÇbO¹/a•M{ ¥î‘Ž_ré¬\c0ü>úÎD½{%zÿ:Lû‡¸;%©˜‚jX„]¿è¾Èw€‡E$yöÿwÈ4Æ|ɳÖ$â‡MOkR·ï1ºó€Iövõ$“}»Œ1ÿbŒ©ÿC­ñ,]fȱˆ:`PôŸ»D¤m ¹çè£st6é3¹þa$@>IEND®B`‚python-wikkid_0.4.orig/wikkid/skin/default/missing-directory.html0000644000000000000000000000101711464732643022351 0ustar00{% extends "base.html" %} {% block title %}{{ view.title }}{% endblock %} {% block content %}

The directory you are looking for does not exist.

Browse the wiki content

{% endblock %} {% block commands %} {% if view.user %} Edit {% endif %} {% if context.get_dir_name %} Browse {% endif %} {% endblock %} python-wikkid_0.4.orig/wikkid/skin/default/missing-page.html0000644000000000000000000000115111410375636021254 0ustar00{% extends "base.html" %} {% block title %}{{ view.title }}{% endblock %} {% block content %}

The page you are looking for isn't here.

Create a new page: {{ view.title }}

Browse the wiki content

{% endblock %} {% block commands %} {% if view.user %} Edit {% endif %} {% if context.get_dir_name %} Browse {% endif %} {% endblock %} python-wikkid_0.4.orig/wikkid/skin/default/page.html0000644000000000000000000000106611410375636017612 0ustar00{% extends "base.html" %} {% block title %}{{ view.title }}{% endblock %} {% block content %}{{ view.content }} {% endblock %} {% block commands %} {% if view.user %} Edit {% endif %} Browse {% endblock %} {% block footer %}

Last edited by {{ view.last_modified_by.display_name }} on {{ view.last_modified_date }}

{% endblock %} python-wikkid_0.4.orig/wikkid/skin/default/static/0000755000000000000000000000000011361301511017256 5ustar00python-wikkid_0.4.orig/wikkid/skin/default/static/attach.gif0000644000000000000000000000104711376034245021230 0ustar00GIF89a æ±±±¸¸¸ùùù«««¨¨¨ÖÖÖgggèèè‘‘‘yyy\\\ôôôüüüªªª¦¦¦```óóópppæææÎÎΈˆˆššš¯¯¯‹‹‹ÕÕÕœœœ···ìììçç碢¢ÞÞÞÉÉɤ¤¤dddÌÌ̳³³€€€–––fff²²²TTTööö———ïïïÔÔÔVVV©©©“““¬¬¬ããㆆ†§§§¶¶¶­­­ŒŒŒ[[[êêêýýýµµµåååää䟟ŸÁÁÁYYYëëëîîîòòònnnøøøñññ®®®þþþÿÿÿ!ù, „€L‚ƒ‚ 15„„ 7("8‹L=L ‹KJ‚@)„K9/H:6;ƒK3J*! #.‚K>%$  ƒÄ 0ÊJKA'G + ƒDF‚B2I„&‚?E‹-4 <”L,CJë(;python-wikkid_0.4.orig/wikkid/skin/default/static/bg-header.gif0000644000000000000000000000076411376034245021607 0ustar00GIF89aOæðÌ3ôÙNõÜUôÙOõÜTöÞXòÔCößZ÷ãm÷âføår÷äpøåsøævùêˆõÛSùé‚ùé„öàaðÍ5ðÎ6ñÎ8ðË1òÒ@õÚRöábóÕGðÌ6ôØKó×IòÒ?÷áe÷ãiïË0ñÑ=øèòÑ>ïÊ/òÓBñÐ;ñÐ:óÔEöÞYöß[÷âg÷äqøæwñÎ7ùé…ùê†ñÏ9÷ãl÷ácóÖH÷âhøç}ùéƒôÚQó×Köà^óÕEòÓAøæyøè€øèðÌ4õÝWùê‡÷änøçzöà_øæxôØMðÌ2÷ãköß]øåtøç|!ù,OQ€C108@?#7MME>G. L - D3J 6, 4F;K+*B9H:5<)&=$"'(2/AI!%;python-wikkid_0.4.orig/wikkid/skin/default/static/bg-navbar.gif0000644000000000000000000000014111376034245021615 0ustar00GIF89a³ñññþþþáááøøøåååïïïìììéééöööãããýýýúúúçççôôôÿÿÿ!ù,P$ÂŽ) ¡±Tp`(Š;python-wikkid_0.4.orig/wikkid/skin/default/static/btn-login.gif0000644000000000000000000000316411376034245021657 0ustar00GIF89aC÷óg’ ’ÉöÅŽD¨96²š mŠ• ˜ ƒ‡M¢Eð÷ï ‹ŠÈ„ŽÌ†ŠÆƒ‰È‚ƒÅ|O§FL¬AM¥EtÇlˆÁƒP­GâòáV³JPªF, €Äy‰ÄƒxÀpŒÊ„Ë…R®GT²I^¯U–ÎñùðsÀha¶We·\S°G=—5ˆÃƒs¾jÅä—ʒ_µV3¢&”Í?¬.Áñ¼÷ûöÅäÁÔìÑÄãÁ véõèÒèЋɄ‡½‚üþüK¯<‘χ¤ÕŸ6¢)¬Ù§ rŽz¹t°èª‡¿‚§Ö¢C¬7Æw0–&lÃbšÐ“ºîµ#ˆ pÄáÁj½cl¶e‡Ñ€*žSÀ; t– –kÀb-‹&˜ ¯Úª™Þãòâª×¥F­:\ªU€Ôp"™;§/yËp›Ò”˜ ÈåÅT¯Jáñߘ̓,Ÿ O­D&›ˆ‹–•ÞˆŒæóå’ÔŒ‘؈F«:;´Ö‰6¡*4¤& î÷í& ‚Ãò¾” Qº?9®#–P³F¸î²Î‡J¯?–}p¼g—>š6¹î´A§6Ý–†Ç]³SIª>R¿9K´;áðà¥âŸ§ã¡hÀ^ÐéÍ(¿ñ¹” ~‰Àâ¼²Û­ØíÖrÆi;³ ÇæÃ 'œ,§yÄrL¤E8¥*ÓêÐi½a¨×£6¯Âó½” vŸÒ™k²d1¬jÈWH­;Š1™'ŠšÜ”jÁanÃez&¢[¸L"“–Ç’pÅg8±/“&L¢F p\ÄF°ê¨eÈPL²?p½fŸÞ˜ºß¶{UµJ™Ð“:§+@ 6wÊn— F¸0j¿a¨æ ~Óm&›c¾Z4° j1®/«{ m$ !,¨ t q)¦'£ wÿÿÿÿÿÿ!ùó,Cÿç (P‘ºXȰ¡Ã‡#>¬TâÇ‚1öÆšŸN†ˆI²¤É“(Mz 6,N$P1¢”솨;дô1À³§ÏŸ@ƒ ªÉÙmY@Ä‚†[àT›JµªÕ«X³jµÊ«‰ˆ˜ÒÜxÆ­œÙ³hÓª]˶-[eѾ JŠ·ZæòêÝË·¯ß¿€ïD KŒ±[̸±ãÇ#Kž¼Ø•<8€èçÏ C‹Mº´iЃ*¼0M ¼×°cËžM»¶íÛ²‹ÝÂÑ#Ü€Mê‚ N|ˆ1âÈ“Ûb#¹sçl¨ZDiõëØ³¯“×H»÷ìBV ÿùN^;5>í9À¾½û÷äI€¯B‚“íë«è€íC‡þõ"Àg {ÛÌPAj,ààƒF¸€<$D¨C kpB, \Hš "ÊÈ|H¨âƒ\”°  Ä(ãŒ4* Ï3® 4 !O:® @+7¶ O FÒÒã56)ã(,(LTViå•È•ÈSe9tY¥–ȳA™P©%–lZeÝ çœtÖI€<Ð9< '<¬°Èóò`À,¶*«ë´·ÖÁ‚)e¼ÂÀ¶Üvë-„î$Ûê¢A0œ+8A¼é‚»Ì·ørë 8(¢:,ðÀŒŽ ò ƒÎò¸`ðÃ#ÑÆ<,`È9g¬ñÆw|É>„Ç$“LŠAÌ ÌÄãòË0Ç,óÌ4×lsÌtÔ0^àÒÎÏ@-ôÐDmôÑ@ƒò@¨‹;PG-õÔTWmõÕXC½D`ÄÄ_¤#öØd—möÙh§­v3À–Q.LAÎÜt×m÷Ýxç­wÞGQòÃR*³pŒ;nøáˆ'®øâŒžN!t ¸@dd $<Àæœwîùç ‡î9õõõúí³ÛØÌÔÑÀøøø÷÷öÔÇíìèöööÕÉ”ùóÕÒÏÁíêÜÜÓ«ÏÀxÓϺÊȽòòñúúùÕÎ«ÛØÉÏȪØ×ÐÔлÚѪÚÔ·óóóÔεÜÔ®ùùùÓÑÈÔÃxåãÖÒ̳Ú×ÇØÐ¬ççâÑÅŽÜÕ¸êèÜÐͽψíìêÖάÒκÙÖÊëéÞÓ̪ÍȱÓÐÀüüüÔÈ’ÖÓÂÿÒ þÙ0ÿÕöá‚þá_÷æøç™ýì¡þÜGÿÿÿ!ù>, ƒÀƒ`0h‘4€$Aù|‚€4CÅ®±àà|ì¾àðWDð…pè´½)øXº¸|Ed…‚¶ ùÿ|*<<†‰Š†99’“”9+&=)=›œ›./6¤¥¦6%'#7­®¯7 5·¸¹5>2 3ÃÄ3 NA;python-wikkid_0.4.orig/wikkid/skin/default/static/ico_folder_up.gif0000644000000000000000000000177111374463352022604 0ustar00GIF89a çRœ TVžv•)t—7dŸ&`¤d£(b¥"†–Mh£0tžIh¥-Fh§-w£1y¡@‡Ÿ,|£)y P‰ 2‰¡-{¥:y¦?„¥.r«:zªCˆ¨L¦¦o¸'•¯'¥«+œ°%‡µ;½¯¢®‡«¯f“¼3´´.À±3Æ´Æ8˜À=²´t­µ}¾¹2¬¶“»ºqѽ*Áº‚ÎÂk¹Ã£ÏˆÔÃxÝÇ<ËÅ›ÑÆéÊ+ÔÇÈÆ»ãË>ÛÉsÔÈ’ÏȪÍȱÎÍ~ÕÉ”ËÉ·àÍRíÍ,ÏɰÎÊ«ùÎ ÙˉÍʽáÏRéÏ@ÎË»àÍvÓ̪ÔЂÒ̳ÐͽÕΫÿÒ ÒκãÐwÓϺÒÏÁýÓØÐ¬ÔлÓпÓÐÀåÒyÔÑÀÿÕÓÑÈÜÓ«ÖÓÂäÔ‘ÜÔ®ÜÕ¸óØZþÙ0ë×|ÙÖÊèØ“Ú×ÇØ×ÐÛØÉÛØÌþÜGëÛ•ñÜüß^õàÞßÏþá_ñà™öá‚öåœ÷æøç™ççâúêŸûêŸìéÛëéÞíêÜýì¡úí³íìèíìêððïùóÕóóóõõõööõööö÷÷öøøøùùùüüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ùÿ, Ö ½ùòE D‰&ZÔèÑ¿oIDJ—‹]¾¼!äðÊž C~dÓè_<(éÈIcEF'^ÒQô¯Šž›yî¬yÑ€D2qšzæÐ¡1X¸¸˜ `Áˆ!Jv´)´E } l`à„ ~¬º™bG‡Ž%.dà@Ã"sÎ<1‡->XP ‚ LNô¤šÃ9* 0€F ê0Ò¦2˜,(tHBŒ•¡Bš4J¨ÁÂпGÄñAÛŽ+VÜøã0 ;python-wikkid_0.4.orig/wikkid/skin/default/static/icon-blank.gif0000644000000000000000000000011011376034245021767 0ustar00GIF89a ‘àÿÿ{°ÿÿÿ!ù, ”˜¦€d1:3ÂFó…GH)&¥b;python-wikkid_0.4.orig/wikkid/skin/default/static/icon-mailto.gif0000644000000000000000000000010711376034245022173 0ustar00GIF89a ‘àÿÿ{°ÿÿÿ!ù, ŒbÀ½‚˜*Z5aÕŠCÎPBBƒ6;python-wikkid_0.4.orig/wikkid/skin/default/static/logo.png0000644000000000000000000002256611376034245020754 0ustar00‰PNG  IHDRµ< ìû¹tEXtSoftwareAdobe ImageReadyqÉe<%IDATxÚì]xÕÚþffkz#¡¡ ‚t¤#E@AAðb÷*"6®ìõŠ"¢‚ ÈU) b¹,A ((%€4A ˜H ¤íÎüß;;³;»™M²Iàòß»çÉy²;åÌ™sÞó÷+ç¬pju©Ià,*¤pV?{33œ÷^+x>*|^²ýq@¡¹SKè²3ÍhPqœá?Ñ“q±÷3þkßÕD½píÃý÷ ¾ÿŠ(ñ—¾>¿óŠæÏü¸8¾†/âø~)AV+GB_“l(Wð|¦ üy ÿ?C¤½W•’“fŠßÑÝâçÏŸËJ………4hÐ Z²d íØ±ƒÚ·oON§ù=yyy4räHzÿý÷MÏ/[¶Œ†Nñññ•ªõéÓ§Õº|òÉ'¦çW¬XA¤¸¸¸J•öìYºí¶Ûè•W^1=¿nÝ:êÙ³'ÅÄÄT¹rssiÑ¢Ed¡ /I ÀØssÎõ9×á\—ÀMŠÃd€2›ŠW¬ŠŠgu@Ó:˜µSÞAIt+ŸÄ_þΟ×R8ýפ ÔÎÍ8·ÖrSÎ97`ú‹/˜ŠGª›SAì=h°ç«ØžÔŠóÇœ{qÞQ=ã1œþA §qîÊy­-…ÑÆóìa:Õüxá˜l ,Šï˜~^1–~¿òÕ3É$ˆO‘Ë=¼*`>)dÓLaO1ö0ªþ‡@Îy £©ƒ‰I·á—¼^ê 脨Q Ñw¯B¾c^‘,š€Xð—ÚH2‘„”àhÅÇ·W¶ÏȧéHAÙÂÒú¿ÔàÆƒ9g$]Æ t”ÒÉü(Ή ujüXÐ.Vß½‚v¯¢”cäÙ\)‘bløþJ€eGÑÔœ…T¬¸È&„Aý_ j‘ËUÆroO`¶õ‚Ò€¢Ò”BðÉ ¤¶Ÿô–ƒHtí~¦€A  ‘³ì&:™]¹wµÑ&ú•V”üBÁFÔ jpÞÚþ(S?Ÿä\l8^ ‘[L%œ€ùLéó—xj’Ì4éØÉªXÉ¢Ø}¦<¨z1^E ÊRZ `/瑹tö|ÏV³zëîð ˜_øeÓYŠ'Gµéú@ 1s s’¢(õ|€å#[u>ÍmZ!ÿÈ‹D‘Žœ¦|I.Þd=Ø2Ó~HƒmŠ… ªÿ#ØÑ²“’Ü1”"ÇQ‚;Š9)Òíäb4å Åòì ’:ÏÖÿ›ñ쀙AÑ$x¿zóŠÊ¯C´ÅF÷gͧYg7P¼j*§ Ôøÿ–Ê}U< mU¼þ AP4šñÁÓ…6»@¿ï‘iùLæ3Â-^ñ­h×(êÁ%©oa &ÉÑTËOµ\ñÔÀ•L©®šds;5z¢×¢Q?¦ýW­[Ad§V«.@,“Ÿ:PªË|>’_¿{ý¿ÈUAiçDFÓ_¾Bÿ<ù5Å[£ÃHº@-Ȫòu7­È‚÷¸Uí®Ÿ)ð:“KôÅ7åc€;<|Y4Ñ¥Ìe‹yô‡”KŠM¡–ä1r5,I¡VÅ U€Gº£ÉçL1RÃ?é)û(Ž™"jöÙÍ÷ÄÛsixúOTèªX«Y­tpßoôÁk«(Zt’ÆÑ&©U±p@㙵h Ø:ÐeAzš”¶ ´qµ›ïQXb‡HG Ö]‘$Í]îbézRÊ£Ò_´Á‘¥JïæÅõ©sa:S–D„VA+—¶E›*¢OÊ22åi_ëµHÞJý__ôO:zô(%$$„QtÁIjEêÀ mìq^(>+˜Qo#MbI½¤ˆèÈ>Å;ÃWÉ^ˆŠDºWî8ƒûˆÚèÜM—7¦NéT»¸¦vµ\Žh¬¼nê3¼” ]ˆÃ¨|»Ú[©˜Á]PR~E#ôÁ¢hö¬7+oNçZRË% I‘¼ÜY¥:w|à¨4Ïùœå™e­7׫˜Jh}m±í¡ÖũԷàJ,Jæç»4ª$ðlwËJ§ ÏŸ®IÔ³á"²ð=¶r^†_Vv•ЋÏO£Â‚BŠŒŒ #è‚”ÔÖÚµ©ä(ƒX4ÄMø(†bª1Á#µ «C¤˜Ä@ ¬z“]±Q‘PBëí;i—óõ/hGδ$ÉeóXLJñl%ˆô8VPLÔ¯É)Šu ÈÏú!ä6„Ôy­ +Å7ÜyíÚ›ô… j)©·Íuøm,‘šÂ Ié<[“htëˆê³P(>I +w¶Ñx.¸·D6:í.¢%Îïi‡ã]Û“â‹4`“ÊAbHT¥€_®ã¯(1â7?%‘]P\HG÷gQZ£4¾·Òæµëè£ÅK("""”ªwæü$çzªêàáW{DQ|šÿÿpm*gp,·aø•(O§«šÑ޵ å«\N+¿¸ÊÊy2çC'dJ’ôÿß]p€×Lû¯x,Q.ç,‹%©ß ס¹)¥šî­ß5”ª81Hmuv÷HD¥D ÄAµz0Î)¨½à†uÅ-Ð6ñˆÿ”FéAiùiž>×éHP ùŽÁê‘EÔ¡ÞjÊÉ'Õœ§šêxpó ôô“OÒO»~¥Ï>YÎEy”Ùi3§SIII(Õ…8ŸÇ¹‰ßD!Ͳ³³;òÇ~œ·i‡ÑIM ¸–' Ûëüñn28»*‘0`Ös®eRþ»üñv  *”0áùœ¯ÓOœ8Ñ–ÿ÷åüG5ÁàNÎÿ,… I*²X,·ˆ¢3õˆ`Kñ¸Š:V°l8[’’EÕV­œGÏ(‰C¶Ñ 9ÞÊ í;5¨UL0©¸`x|.æAÐ5õ$µª½Œ¢xTƱôM‰¡¬CY4õ‘‡èÙ§Ql”¿ýÙn³{mø!H”ú¥”b$GeÅ€ÆÊ«h]‡q¹\èÄþføå—_ˆ;²"u©hCù7òÇaU,?ÚÐjùü®ˆ‰¿Âì¦íÛ·«íbjjvP–e»Ûí~ÁB–Èâ½®Û]x\"Ä.è¦VݼüòËäp8*\Nà W|Ò®T` V½ôÒK˜)ªZ»FŠbDÚ61¶ÝAQ4a&øIeÁèÌÐÎùŒ"‚Š“äÚçWRû›o$:-¥‘|T£LI͹ˆécÓD]~€2ròóhÎìÙ4|Ô5túT%VÑöŒÆ-.V)j.wò |Ʋ&äüü|õ?–PqÊWªÐpÏ<󌺄©¬Á§Õ£RiÚ´iôÇ•ÔpAAú þ|s&€j|W¼Ÿö®gïŸ>}:íß¿¿Ú@íÃ÷©T£÷.×wš´ªmC^#¯Û9(Ï–Ï#PJ‘©ˆëœY@ʵq‹YœšcF”5KH1Úp¸\žþÓu…tÓM7©kôbãâ(Âé$¥Š#ÔjµRbb"íÝ»W¹ãŽ;n1b„´aÆî §HjÑ¢õë×Ξ¹•}Ö6bcTTT™ƒ«AƒtìØ±ßé·ß~£ ”Y>€ûÔSOÑÂ… ióæÍ§ž}öÙqün³ýõ×–<Ð ¶víÚQ=¾áË?7Þ»eËzûí·Ë,¿Ò V™R|»ÏÉ^ûJ*>¡†Ûé€U¼ñ̸aÎÓíÔ¢Gj‹ü=¹¶D’åÜ[@‚sl˜˜EOÄ«`ˆQûQöWa›Žaª;²ý¿KH95jä(úüóϽžÁªN8h€ú£>¢ñãÇSffæM›6òðÃ3Çj—|ñÅì*Ù`sçÎUéÄ,”Ú‡zˆî¿ÿþ%ö{ï½§zLË*ebÑl­Zµh̘1´~ýú_kÖ¬y?³yÖ˜‚çžæw=4dÈÅÈ£‡Jþù§ êP CÙvjt´½æJK^%®ƒó¬dòrQš^;5insÝáøÅ9' tŠyµôX &0‡Î*(¤X÷A*f@Ë\ÙZB5¦xuày©I+ø}šŸeñÝïW­¢ŒŒ {|||KMÉ‘ ãä7öoå<dë*£ ãAb ¶‡|ðÒK/¥Ù³gÇq'Ö?~—‡p]beF`@ØÔ•õáÇéÃ?TM‹  Í´ÒLiÆzeɹcâĉôè£Rlll…ÊÇ`”—ÖÊG%ÑF5Œåó{ç¼ï°­X±"yíڵɯ¿þz!/Ф¹…naP{-+|:`0›¶„E¨'çvšÅÈ©õÉQΰüÄÏù’Aí,~¨í$Õèõ­ûЊfÚÓ%±v*ìÔjܽàµiËÌ:bãEJH)'Û¥Jìó™$~\n¡=vœÎ(_yƒ©d5¨ÅA÷&]J÷7õX;TnÀ7ÜØã£í¿eí}íµàsÓøèD‰ð§ÓéœÊgy4V·¿£Y%¼/ÍIµk×>ÈŠæpnü-’$½ÏŸûdeeyEeQQ‘µM›6ßñÇqÜ©†úÎ :räˆ>»ÀW?ƒó(£2ær¹PÓ,EÇEGGRžt3¦9sæ¨ôC¬Ó9_¯Ys¼å3XϦ¤¤Ü¼{÷îùºd–ºÿæç´Øµk—`yµ¥yóæj&Ãb‹c=óCœ»¹¹!U;‡ö=[žéQÇ@‰T£G†à¬Ï€-ñ7ëA6>ûë^‚jÖsFT£Ö†O«a¬\A‡$Q¬`¥-Ç 6:¥Ósîí´©K¼Çn]ÈmÚ¶Kê&ìܺŽ?~n¢é`‘¤Ä={öÌÌËËëäÑè¤Ë ,uêÔ©wÜPî`€¬7æÇØ£õÌÐÁ ´§¿‡*©÷íÛéoŒ;™¨ÎhYhÛ¶m4ƒ®_(Ôôµ×^3Ò¼çMF@ëïÙ²eËæì—ã3§HX«Ãð®Qœ¬LŽçsêÂæœœzî¹çˆš^Ôow§ ØÏ¸NW&¨I°×XÁÒú$±ÂI¬Jc-¤YU µPWÕbªeÔ†ŽX·¡Ä\òü[A0±$XDjaqÀõæÏ ‰NŸÌ¡¯¢rˆ.ª ’I4¦ËZ‡ïÌžSØÐ`|¼ø§Ÿ~vîÜys›l3>̓úö…Ÿ¾fP[‚Y Ñ9%…ú¾‘6ÀÓÿnb¯U©Ã¸qãðõKíYJ¸B+õ›`öž°ˆŒ;_ÿ­•/”U¦6£¨†…‰ÚNœ'·U ¦Ÿà}‘C™iŒ…í°Ô¾YðŠâ•Ä^ɬ\Ðnx…±R¯¾DëBR+L¼DêaW«€‹Ü´ñÔa*Â@Íj±‚Øeæ—_ag +SË‚òt¾nåÊ•5®è=ë`¦˜ñ”L]»vÝ™››»å—_~±ÂÞ]é믿6NÛÓxn)®uëÖ4lØ0Pœ/C- Ò@ žÙ{¦§§cg¨ üuy(åÙƒò5ã£Áî™3gTŠR¿~}5cÂ|‹‹¡~傚ĸÖóÅøö\j¡ÅðHkMJË©-{Î‚ÄÆŠT³ŽD!…j¥ }ÅZT_pRqÀZC4Fææ-ô[=ÖCþÖ+“â—Ë/ µ5ÜÕå•Í NÓc¬3`®KMM]ùÃ?lÞ¼Y ¶XeÌgšÔ‚Gr´™tƒd„å…g¸­Ý ô ªåktŠó8³òjÌ<Ó½&òûï¿ë’·Ì´~ýzúöÛoA= Ð’Ù€IJJ¢§Ÿ~š6lØpè›o¾ùˆÚÛ|ï¿–/_¾aÊ”)‡ëÕ«WR¥òSkô÷–:CŸü±Çº¡‡¢ja§²bØŽÀãa„8²ÙJm,Ѿ,7 p^ ÚÑ…gÊÖÖ8Ú_”ïç¦CcŸ<ò­m@”~SßwŽ?–)wH'FosXaÇŽÝYÙ1J¼!Á¦Kì;Çiè ?[Tª™ÉuäÙKÅ/Aš]rÉ%4bĈ-ÀèÞ½{iÖ¬YF[f‚EEëïœÛžà¹-hôèÑØÑê_0¯¾új…öŸ†³ˆÐ>Ü.]ÌÚÒùå—_vó,󬦠æê”ªnݺ°ïGggg¿7cÆŒ+ƒíïÈeŽHu¯^.:ë°$v›8å„R1 º)±"V¯H6ÛùåÕVQŠƒ;ÂB-5T£M)pŠ}÷ó¦"rО}Y{è矆T' <ˆ¤QQQˆä{0˜íR20a: Â#X‘Á‹ºhVždâ6×=­C† Án•Su@ëöiôÇ‚ NÏ›7ïDYæÉR]ˆ¨½@ª5ð,(ˆÏmî³ ÑòÒA¥"îb…j×µ¨¦=·ëü¦»löÄŸzYUˇàBAcnÚø£ýäÉ“í!‰X×ä†íjÌÀÎFg­Y³«¡æŽî¨Mý¥$dçÎA=V¡3à-«.> Åo¼¡<ñÄèØö\¯ed()Ú¸qcHѺ–’ò¬(þüùôümÓZ+ÿ"3®%Sýüu©nþC°âHf3ÚƒãòË/€_<gÔ¨QtÏ=÷¨×V˜Ski³”:j I1*Yö…Lö¥S­!þßä„‘jÁe~EµÌLT<=µD;u⨠€WƒF0 iÓ¦M½5 õ4 ·jÕJ…àä«V­²ð±oìlÚìÓ§žµœ–ŸU!žY^Âó·mÛðÜÌ”iw&f‹Íž÷üóÏSrr2ìÖªýûÇ,Ór€òwïÞ Û÷õ<à—ð{¬7S‚aA9ˆ7aN‹PX5Œïb8.øSšY‚"5mÚ2+ÐÙtõÕWÓâÅ‹Õ:”§˜šRÄÄKßSz‘ºQñHbÁ ™uÅ1P‰„3/½……„óè+‡¤®¡xâlL3Ø“ýÆŸ>uAš¬^½º+wN ïefvºóÎ;ÿlݺõä½&(ܘ ¦=Ìî­Q£@½Bï_Ñȶò!,,©®à2¯Ô@á¼[JJ ½ûî»tÅWpo깬€'HG€z×®]—3_Ž4£pÄ@ê3µ™Ë‡^5Jï¯Ãl–Á{ .üœ#ü5Ï8˜˜Ž¨uD*9슯¬ic·hó‚5 "x9´ÁN­h6mXAXZ§¦Y(6N Y>÷€Ͱ*Ó§6Ê\Ô]ЧÁF® ­[·ÎyìØ±¿aŠ5ãÃÞÍ šn”l(–µð&<õÄVÓŒg¶iÓRæ[þz¼ZßQ‹%Aä­ú÷ë®»VšcÌu0Ú•A)˜:•¹RG·9+mÍ\ûä˜1c`±¸…´U8Øà<###ÔÐWÁŒOë„È)2¬ÂÁ ï©!$ Ò Îkõž/%uò3ïùgÁÏó¨F.ô#RÓt+•+çEJDZ’˜á%ºÕè»V,L[YcÈ@AÐi{öìÁT>‰îâÀ©»Gà–Yò¼Ç@.ää?üðC£ƒ>ÀåHf¦<–x â³Õa¸™+jF«°®ÀNÕgDГÛo¿}9ƒîµÏ>ûLÑê§Ÿ~J1)–U>ò¯¿þJ&Lø÷µ×^;}éÒ¥n}Ð#²±’3’R†Ò*Ö!ÔÐÔà²\´¾/5¾i¿«Ÿ,ûÛ©eÝ c 5±ˆ5kηC¨Ôöt¡$7?8–A„½ÙÕÕ垈¼R¹MF5$*OÑ) 6{ îß_u.åï'¹!WJyXL¶nÝZ?K€F”Z·nÝ”‡cÙÙÙF¯_•ê€úcvzVV}ÿý÷·mß¾ý¦ Éú;Áã‡ú•'áp-Êǵ åÃ5ÏåãÁóÅ™3gêèåÁVm6ÊIEdYžŸŸŸ_åF+‹ äHõ¿-&vd¼ùÇÝ“Á»`)ባQ#+ÕH–Ôe€ç:¥0Ÿ Fõ³ÅS‚Õn lÎ6¥N:Ô¥K((ÛµÕŸ¯Ñ)ˆPq¼Y³fP0W’úû1Å´:VͨSf~>bIÙ¶}æÌ™k;wî\„z„YÜ{ÿþý˜b½ËçÔ)bĈtÙe—©eK1¨ƒvÇo¼±†¯/O6+ÿСC=ùºz”lò°VÀû"¨˜™Ñ–Lc æS5îºP_Ñ_  †´ž'5»í€j…1‰1ãÙP‘Ú¶³²un)6Ài"'z¨‡®ÍPj›g;tì”[TThêP TòàLhÔ¨¤ì)íð÷äÙåÕOš™-UÒc=¸ã3ÎÉlÄõƒcƒë÷Ø 7ÜÐýè;hРFN­ƒï¶páÂÁL;&k®hDê•9c |˜ôxPNcúÒãÁì5tèÐ5º9ÓX>ÀýñÇ÷âNÓcT3žŠ(ïÊ£R~o‰ÉÝX¤y¦%?Lž­Z¶°²òuîÜæP#+5TbɳâŸ+»¢ƒ¥Ê´{—§·kóRqaQ…@Ã|WÀú wT¹KúÑéP’ ÀŸ~À1üÚÌ\Õó¡Ï‘<ˆÖöéÓgpjjêÆÀ@%t<ž?}úôûòòòTe¶í ÖEÖÀ»±W¯^Cx­Ö†±|ПW^yeBNNŽ:ðØc©¦¸P(?cKà@ÀwæéuK˜&ûõë§ZWL8u\U@Í­èx×Òü®}øU7¡ŠÁâ„g»ŠJL”¨ek+ó¤s#­K¸RÐÑP¤U{#×ÍÅÜgò 9Y–Â'Þ›óÎRnìüò Swâ^þúm`?+·,A°\)===‡q ü^¹÷©pÚµkÌ[2žÀùL  Š!ªø¹sçNÕß@¬¨4EýwîÜ™Ça¿CŽYù¬_DÍž=û1 ç+Z>ª•÷­¯‡R:~üøÇ322¢à7Xe0Å"<ÞÆáU5Ñ ±Nßb½^»µ×N-v‚ÐìØÑF±qbµKk}˜4U˜IžBÁØ•hüÐÉy9¹»O?ó\VYåh1Çp&À¡q:àô§,™äòî§äi¹‘Þ@2*çÈ…ò±‚|ÇŽ›¯Ò)†Ìš5kxffæØ´´4Õ$W¯-ÂCY)ÜÎò€ÊÌ1gΜëׯ¿ôæÚk¯-¥`š%Ö[¨C‡¸v?×}y0¥uéÒ¥]8°žŸƒ‰ÐÚ‡Émø5y¶rª j¤·­-ïÙ*ØÈëâù›uJ²…_Â^íÒÚÍ# »ö7W´Ý™`hT—èÑÛÞF]%nîX…¥ÔÒ²¨:¦<á—&çãü]YRV¾?›¿~s¾œM¨¯nT/qÝw ^¸_|ñqI„¡b¤Ë-4å³B:ƒ•X>ÎÁ!õ / À¿!¨*R>fEppMá~†ËþÝ,4ÏçkZqÿ=Á‡àLz†<ž\{ .QPŸZ>%5KBI[âsÈx•Fv‡övJN–¨¤¤:A­P::k;±5TB4Ñó÷eR”É:¯Õ`s™5A1ÌO±þmC…feY‹N›4i‚ˆ¸/ŒJ%¼nÕíQ5ÖJíõ×_¯¾#wú)¦X˜-F×ÿꫯÒ/^|ß7ÞÇ=¥ÁÊg®N¸ æÔ\þÃåƒ^­\¹²Îüùó'=!JE¬wÞy§*é™ó¿‡Ë—ë¥ÛÇÍìæú"†ê5Òr©å„/„øÖŸ±gtÈ0·.Q(11ÿϪ¢ÃÅÁôª‹’â¡CØ©ô™{PÇ‹áíËÁ5<òˆ:qlæF[ …¼Ï˜¡iwïÞô âïA÷K“˜Z÷â;+¨) ±:¨õN0»_WÂp^z×÷1^k¶êAEºÙ%æ2ž1fêæ9ý>寿þ¢™3gÞš’’ÒŠT¨ïÉa,3N`ù 6ôÆ¿pù|ÿ‹¨_`ùØì†©Êx.»3KaÓòqQ¡ÅL€Ð!¦ ³’~Ô"\‹ûßW_ý¢?ôè¾ûîS-6xÆgéƒ ——,8§J—<Üݵò¦e§‰R›´ËúO{öµƒ mÛÚFûö–ЦÍÅjhjUÄÎJMªãŠP¿Ñä»NÓ€XC·Ý¨jÿ]ÌùnaEp s´Ku;3Σa'MšånF°gqcf±T¼;þMæ˜5t‰m<ò†n€2ùi '5ܟÀxëpŸ½“Ä ÎÁj™ÜÜÜ/ºè¢o¸ì¾:àuö-·À3틇€´Cp‚ó‘š5kvß5×\cÙºuë-ÇŽ“tó@Âïö.óÅF}Æõb,íqûíX†è5gÒÍ7߬þ:<°8Ï÷=4räH++ˆŽ9bÑËG{öîݦ‰A\æ–ò‹™^Œ4–|×]wák®N]ð>ß}÷Z?ž-_{ë­·~^¸pá‹kÖ¬épðàAUàè3ÚÔ¥[·nøóÓ<f±"Q¿~ý èK] Õ‰Pý¸™Rý/(i×<íΚǘñýŒ.Šõ-¢µ_ P÷‘êß/‚ŽuÓ‘#næ°B¥”£OQônVî*¡qWC‘øÎÌ}¬I¯ƒW^yå€aÆá÷hö?IS by¨,m¶tâĉ¸Ç€X`¡W~ e'è\ŠÀÀèÕW_Å€Cn#Ãóÿâ¼Ó=þä“Oæ=üðãGŒ%TäÛ®šZ–Ñ䘜œì Áà*f‰wÇÔ©S’'ÂÎJ¾Ý1Aü–@Êñ ¾Œ……Ñåï7Î8°:HÀ0ƒOîׯ߽S¦LŽ‚Å62üjf4”ë­·Þüàƒ®&Ïò7]<[´™ðc½|î 5L¡˜)Xb¯¹êª«zMž}z&ùv“ÕŸµ¢2Á ¯Z.™±±£’·‹qPé]û}àR%7k\¬H#GDÑ‚¦œ\Y]¤†£ƒË\˜D±Ž¢Çï?MW‚[TåÎ-YPYç'¸ÀgVzrñìZ‘¥sz(…ëm¬Õsµ–ƒ¥Ü²f¦²ÚP+­–ƒŽc ¾¥D™‰¥ûY~—… öÅ X„]ÆhNÖ<µ(ÛŽxyUåÔz:C‘µ´t|®$§Ç¢¬[T|žGA;ú[·–…F]Mñ ¬Ý†ð„ö”7 Sb©uƒ¶¬?—Í€¾¦<@‡ÓÿË$kTëwm@ù“BØ?»² kĺ½ž´´œÀh-ð[D E£§à.f §¥ZhüØ(jÜÈÊÓšR¦ ú¨¨Õ颡M¢¨Û•w°¼}s+uíˆý62ÂýN¦¶ü*Ü;MºxÂ¥òŸÛ®’|AdôP¬R›´+¾ÍHAè‹Õ~=nL­[_H›3‹(÷/Y [ÕÝû¸€F¤_ÃF ]> Žšv›YB­GÍà"Ÿ2*5áNÕ j™$ÇDK×—Zº N^$ßÈêƒ,.çÙ7ºBæÔýûFPûKì´+«„•HÊó¬.„ KÃÒ[¨QZ>E¤ßÈ*þ¨Iš!>œÂéœé°‘2ÞÒãµO]cd(ŽV§¾þ›´é AOˆâ‹‰©[¹ø3x¶ª²s­ìNDü’ÛÙ†„´àDy3Ü]át.9µ1­bßléùz¡YÏãq4®6WJo~£ŸS÷âc=³°@QõM«E ›Å³écñÙ"*qG‘íâæ ¶¤ÿï„S8SP#-’Ûßoéÿ ±ÍH(Îø}ÁKJ̼ê/Åi;=ÉpÞ¸<ûùÙZ=Cbbµl wU8oP#½.$¶ºÃÒon±ÜI¶à–ý÷âÓÂS-¢ÏxNÐM0j³–hmõYŽÃ¯F½jE*1NaPW4½)Ä]4Î:àƒ<©åêJt¡¸€|KË…Ò‹ ¼R[ñHxÑNÖ¶/¥éÝpAO¬L%ªü§~„&œþë@´ˆìqƒ¤.Ïl³ô›GB" Šµè>ÅgÏöòlÎ%…j“:½Ç"²\tÓûäÙqóLe*€%@aP‡­ÕÖqî.Öïÿ„X«ËÝòÞ¥¢|à3rŸØèÙrAðl$ É]IJNRêð|²FMÑ(‡îžpº@Wç$éb1}ÜbÓëzJùGã•ܤœð¬ÐuÔ 1>„¨ú'I²}©9¬†Ó j=aAêÕ$Z› Ñ :sÆ‚Pì€H5¬Sû…ùŒÛv«¥7¥FùµºPӤĸ ½ewèQª5^èü†1Råïà”8ÙZ·ÙxO¯x›@\Xø |©} Ȉ/, ¬"%o÷ è)²>Ü ÓÑc&¾''øÊåUµðj]3‚\~o棈¼` G6Š'>A'—" ¿Ù/"|ܶ=ja+‘ÁQ¤Sïùi ŽÁõD)LYG2±¡¹Šé‹«¼2]§ˆåjcb3¡ý7ºýº6‹çì£{)¶/½ä$ÿ»• X:þâ#7Ú!™®qCØvDÄ28Ýv¾+Ҩر0Š ãŒØû¿ ÿòg1˜p9IEND®B`‚python-wikkid_0.4.orig/wikkid/tests/__init__.py0000644000000000000000000000266012265621203016665 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The wikkid tests and test only code.""" import unittest import testtools from zope.interface.verify import verifyObject class ProvidesMixin(object): def assertProvides(self, obj, interface): """Assert 'obj' correctly provides 'interface'.""" self.assertTrue( interface.providedBy(obj), "%r does not provide %r." % (obj, interface)) self.assertTrue( verifyObject(interface, obj), "%r claims to provide %r but does not do so correctly." % (obj, interface)) class TestCase(testtools.TestCase, ProvidesMixin): """Add some zope interface helpers.""" def test_suite(): packages = [ 'formatters', 'views', ] names = [ 'app', 'context', 'model_factory', 'volatile_filestore', 'bzr_filestore', 'bzr_user', 'git_filestore', 'view_dispatcher', 'model', ] module_names = ['wikkid.tests.test_' + name for name in names] loader = unittest.TestLoader() suite = loader.loadTestsFromNames(module_names) for pkgname in packages: pkg = __import__( 'wikkid.tests.' + pkgname, globals(), locals(), ['test_suite']) suite.addTests(pkg.test_suite()) return suite python-wikkid_0.4.orig/wikkid/tests/factory.py0000644000000000000000000000170511755162524016605 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """A base test case for factory tests.""" from webob import Request from wikkid.dispatcher import get_view from wikkid.model.factory import ResourceFactory from wikkid.filestore.volatile import FileStore from wikkid.tests import TestCase class FactoryTestCase(TestCase): """A test case that can make a factory.""" def make_factory(self, content=None): """Make a factory with a volatile filestore.""" filestore = FileStore(content) return ResourceFactory(filestore) class ViewTestCase(FactoryTestCase): """A factory test case that can create views.""" def get_view(self, factory, path, name=None, base_url=None): info = factory.get_resource_at_path(path) request = Request.blank(path, base_url=base_url) return get_view(info, name, request) python-wikkid_0.4.orig/wikkid/tests/fakes.py0000644000000000000000000000130414160200415016203 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The wikkid tests and test only code.""" from zope.interface import implementer from wikkid.interface.user import IUser class TestUserFactory(object): """Right now, user factories don't do anything.""" @implementer(IUser) class TestUser(object): """A test user that implements the interface.""" def __init__(self, email, display_name): self.email = email self.display_name = display_name self.committer_id = "{0} <{1}>".format(email, display_name) class TestRequest(object): """A fake request object.""" python-wikkid_0.4.orig/wikkid/tests/filestore.py0000644000000000000000000001523614160200415017117 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The base test class for filestores.""" from datetime import datetime, timedelta from wikkid.filestore import FileExists from wikkid.interface.filestore import FileType, IFile, IFileStore class TestFileStore(object): """Tests for the filestore and files.""" def test_filestore_provides_IFileStore(self): filestore = self.make_filestore() self.assertProvides(filestore, IFileStore) def test_file_provides_IFile(self): filestore = self.make_filestore([('README', b'not much')]) readme = filestore.get_file('README') self.assertProvides(readme, IFile) def test_file_gives_content(self): filestore = self.make_filestore([('README', b'Content')]) readme = filestore.get_file('README') self.assertEqual(b'Content', readme.get_content()) def assertDirectoryFileType(self, f): self.assertEqual(FileType.DIRECTORY, f.file_type) def assertTextFileType(self, f): self.assertEqual(FileType.TEXT_FILE, f.file_type) def assertBinaryFileType(self, f): self.assertEqual(FileType.BINARY_FILE, f.file_type) def test_file_type(self): filestore = self.make_filestore( [('README', b'Content'), ('lib/', None), ('lib/foo', b'dummy data'), ('image.jpg', b'pretend image'), ('binary-file', b'a\0binary\0file'), ('simple.txt', b'A text file'), ('source.cpp', b'A cpp file')]) self.assertDirectoryFileType(filestore.get_file('lib')) self.assertTextFileType(filestore.get_file('README')) self.assertTextFileType(filestore.get_file('simple.txt')) self.assertTextFileType(filestore.get_file('source.cpp')) self.assertBinaryFileType(filestore.get_file('image.jpg')) self.assertBinaryFileType(filestore.get_file('binary-file')) def test_mimetype(self): filestore = self.make_filestore( [('README', b'Content'), ('lib/', None), ('lib/data', b'dummy data'), ('image.jpg', b'pretend image'), ('binary-file', b'a\0binary\0file'), ('simple.txt', b'A text file'), ('source.cpp', b'A cpp file')]) self.assertIs(None, filestore.get_file('lib').mimetype) self.assertIs(None, filestore.get_file('README').mimetype) self.assertEqual( 'text/plain', filestore.get_file('simple.txt').mimetype) self.assertEqual( 'text/x-c++src', filestore.get_file('source.cpp').mimetype) self.assertEqual( 'image/jpeg', filestore.get_file('image.jpg').mimetype) self.assertIs(None, filestore.get_file('binary-file').mimetype) def test_nonexistant_file(self): filestore = self.make_filestore() readme = filestore.get_file('README') self.assertIs(None, readme) def assertDirectory(self, filestore, path): """The filestore should have a directory at path.""" location = filestore.get_file(path) self.assertDirectoryFileType(location) def test_updating_file_adds_directories(self): filestore = self.make_filestore() user = 'Eric the viking ' filestore.update_file('first/second/third', b'content\n', user, None) self.assertDirectory(filestore, 'first') self.assertDirectory(filestore, 'first/second') third = filestore.get_file('first/second/third') self.assertEqual(b'content\n', third.get_content()) def test_updating_file_with_directory_clash(self): filestore = self.make_filestore( [('first', b'content')]) user = None self.assertRaises( FileExists, filestore.update_file, 'first/second', b'content', user, None) def test_updating_existing_file(self): filestore = self.make_filestore( [('README', b'Content'), ]) user = 'Eric the viking ' parent_rev = filestore.get_file('README').last_modified_in_revision filestore.update_file('README', b'new content\n', user, parent_rev) readme = filestore.get_file('README') self.assertEqual(b'new content\n', readme.get_content()) def test_list_directory_non_existant(self): filestore = self.make_filestore() listing = filestore.list_directory('missing') self.assertIs(None, listing) def test_listing_directory_empty(self): filestore = self.make_filestore( [('empty/', None), ]) listing = filestore.list_directory('empty') self.assertEqual([], listing) def test_listing_directory_of_a_file(self): # If a listing is attempted of a file, then None is returned. filestore = self.make_filestore( [('some-file', b'content'), ]) listing = filestore.list_directory('some-file') self.assertIs(None, listing) def test_listing_directory_root(self): filestore = self.make_filestore( [('some-file', b'content'), ('another-file', b'a'), ('directory/', None), ('directory/subfile', b'b'), ]) listing = filestore.list_directory(None) self.assertEqual( ['another-file', 'directory', 'some-file'], sorted(f.base_name for f in listing)) def test_listing_directory_subdir(self): filestore = self.make_filestore( [('some-file', b'content'), ('another-file', b'a'), ('directory/', None), ('directory/subfile', b'b'), ('directory/another', b'a'), ]) listing = filestore.list_directory('directory') self.assertEqual( ['another', 'subfile'], sorted(f.base_name for f in listing)) def test_last_modified(self): # Make sure that the timestamp and author are recorded. start = datetime.utcnow() - timedelta(5) filestore = self.make_filestore() filestore.update_file( 'new-file.txt', b'some content', 'Test Author ', None) curr = filestore.get_file('new-file.txt') end = datetime.utcnow() + timedelta(5) # A new line is added to the end too. self.assertEqual( 'Test Author ', curr.last_modified_by) self.assertTrue( start <= curr.last_modified_date <= end, '%r <= %r <= %r' % (start, curr.last_modified_date, end)) python-wikkid_0.4.orig/wikkid/tests/formatters/0000755000000000000000000000000011372475637016756 5ustar00python-wikkid_0.4.orig/wikkid/tests/test_app.py0000644000000000000000000000731614160200415016742 0ustar00# # Copyright (C) 2010-2012 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for method and classes in wikkid.app.""" import os.path from testtools.matchers import IsInstance from webob.request import environ_from_url from wikkid.app import WikkidApp from wikkid.context import ExecutionContext from wikkid.filestore.volatile import FileStore from wikkid.view.missing import MissingPage from wikkid.view.root import RootPage from wikkid.view.wiki import WikiPage from wikkid.tests import TestCase class TestApp(TestCase): def assert_not_found(self, status, headers): self.assertEqual("404 Not Found", status) def assert_ok(self, status, headers): self.assertEqual("200 OK", status) def test_traverse_above_static_not_possible_with_relative_path(self): """ Traversal above the static folder, by forging a malicious request with a relative path for example, is not possible. """ environ = environ_from_url("/static/../page.html") filestore = FileStore() app = WikkidApp(filestore) app(environ, self.assert_not_found) def test_traverse_above_static_not_possible_with_absolute_path(self): """ Traversal above the static folder, by forging a malicious request including an absolute path for example, is not possible. """ this_file = os.path.abspath(__file__) environ = environ_from_url("/static/" + this_file) filestore = FileStore() app = WikkidApp(filestore) app(environ, self.assert_not_found) def test_getting_static_style_css_works(self): environ = environ_from_url("/static/default.css") filestore = FileStore() app = WikkidApp(filestore) app(environ, self.assert_ok) def test_getting_static_style_css_works_with_script_name(self): environ = environ_from_url("/test/static/default.css") filestore = FileStore() context = ExecutionContext(script_name="/test") app = WikkidApp(filestore, execution_context=context) app(environ, self.assert_ok) def test_getting_static_css_works_script_name_multiple_segments(self): environ = environ_from_url("/p/project-name/wiki/static/default.css") filestore = FileStore() context = ExecutionContext(script_name="/p/project-name/wiki") app = WikkidApp(filestore, execution_context=context) app(environ, self.assert_ok) def test_getting_anything_outside_script_name_fails(self): environ = environ_from_url("/foo/bar") filestore = FileStore() context = ExecutionContext(script_name="/test") app = WikkidApp(filestore, execution_context=context) app(environ, self.assert_not_found) def assertUrlIsView(self, url, view_type, script_name=None, store_content=None): environ = environ_from_url(url) filestore = FileStore(store_content) context = ExecutionContext(script_name=script_name) app = WikkidApp(filestore, execution_context=context) view = app.get_view(environ) self.assertThat(view, IsInstance(view_type)) def test_home_redirect_url_matches_script_name(self): self.assertUrlIsView("/test", RootPage, "/test") self.assertUrlIsView("/test", RootPage, "/test/") self.assertUrlIsView("/test/", RootPage, "/test") self.assertUrlIsView("/test/", RootPage, "/test/") def test_get_view(self): self.assertUrlIsView("/Home", MissingPage) def test_get_home_view(self): content = [('Home.txt', 'Welcome Home.')] self.assertUrlIsView("/Home", WikiPage, store_content=content) python-wikkid_0.4.orig/wikkid/tests/test_bzr_filestore.py0000644000000000000000000001234014160200415021024 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the wikkid.filestore.bzr.FileStore.""" from textwrap import dedent from breezy.tests import TestCaseWithTransport from wikkid.filestore import UpdateConflicts from wikkid.filestore.bzr import ( BranchFileStore, FileStore, ) from wikkid.tests import ProvidesMixin from wikkid.tests.filestore import TestFileStore class TestBzrFileStore(TestCaseWithTransport, ProvidesMixin, TestFileStore): """Tests for the bzr filestore and files.""" def make_filestore(self, contents=None): tree = self.make_branch_and_tree('.') if contents: self.build_tree_contents(contents) tree.smart_add(['.']) tree.commit(message='Initial commit', authors=['test@example.com']) return FileStore(tree) def test_conflicts(self): filestore = self.make_filestore( [('test.txt', b'one line of content\n')]) f = filestore.get_file('test.txt') base_rev = f.last_modified_in_revision # First update succeeds. filestore.update_file( 'test.txt', b'different line\n', 'Test Author ', base_rev) # Second update with same base revision should raise an exception. conflicts = self.assertRaises( UpdateConflicts, filestore.update_file, 'test.txt', b'also change the first line\n', 'Test Author ', base_rev) curr = filestore.get_file('test.txt') self.assertEqual( curr.last_modified_in_revision, conflicts.basis_rev) self.assertEqual(dedent("""\ <<<<<<< also change the first line ======= different line >>>>>>> """).encode(), conflicts.content) def test_conflicts_dos_line_endings(self): filestore = self.make_filestore( [('test.txt', b'one line of content\r\n')]) f = filestore.get_file('test.txt') base_rev = f.last_modified_in_revision # First update succeeds. filestore.update_file( 'test.txt', b'different line\r\n', 'Test Author ', base_rev) # Second update with same base revision should raise an exception. conflicts = self.assertRaises( UpdateConflicts, filestore.update_file, 'test.txt', b'also change the first line\r\n', 'Test Author ', base_rev) curr = filestore.get_file('test.txt') self.assertEqual( curr.last_modified_in_revision, conflicts.basis_rev) self.assertEqual( b"<<<<<<<\r\n" b"also change the first line\r\n" b"=======\r\n" b"different line\r\n" b">>>>>>>\r\n", conflicts.content) def test_line_endings_unix(self): # If the underlying file has unix file endings, and the post param has # dos endings, the post param is converted before the merge is # attempted. filestore = self.make_filestore( [('test.txt', b'several\nlines\nof\ncontent')]) f = filestore.get_file('test.txt') base_rev = f.last_modified_in_revision # Updating the text with dos endings doesn't convert the file. filestore.update_file( 'test.txt', b'several\r\nslightly different lines\r\nof\r\ncontent', 'Test Author ', base_rev) curr = filestore.get_file('test.txt') # New line added too. self.assertEqual( b'several\nslightly different lines\nof\ncontent\n', curr.get_content()) def test_line_endings_new_file(self): # Line endings for new files default to '\n'. filestore = self.make_filestore() filestore.update_file( 'new-file.txt', b'some\r\ndos\r\nline\r\nendings', 'Test Author ', None) curr = filestore.get_file('new-file.txt') # A new line is added to the end too. self.assertEqual( b'some\ndos\nline\nendings\n', curr.get_content()) def test_empty(self): # Empty files do not have line endings, but they can be saved # nonetheless. filestore = self.make_filestore( [('test.txt', b'several\nlines\nof\ncontent')]) f = filestore.get_file('test.txt') base_rev = f.last_modified_in_revision filestore.update_file( 'test.txt', b'', 'Test Author ', base_rev) curr = filestore.get_file('test.txt') self.assertEqual(b'', curr.get_content()) class TestBranchFileStore(TestBzrFileStore): def make_filestore(self, contents=None): tree = self.make_branch_and_tree('.') if contents: self.build_tree_contents(contents) tree.smart_add(['.']) tree.commit(message='Initial commit', authors=['test@example.com']) return BranchFileStore(tree.branch) python-wikkid_0.4.orig/wikkid/tests/test_bzr_user.py0000644000000000000000000000305114160200415020005 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the wikkid bzr user.""" from breezy.tests import TestCaseWithTransport from webob import Request, Response from wikkid.interface.user import IUser from wikkid.tests import ProvidesMixin from wikkid.user.bzr import LocalBazaarUserMiddleware class TestLocalUserMiddleware(TestCaseWithTransport, ProvidesMixin): def setUp(self): super(TestLocalUserMiddleware, self).setUp() self.user = None def app_func(self, environment, start_response): self.user = environment['wikkid.user'] return Response('done')(environment, start_response) def test_user_is_set(self): branch = self.make_branch_and_tree('.').branch req = Request.blank('/') app = LocalBazaarUserMiddleware(self.app_func, branch) req.get_response(app) self.assertIsNot(None, self.user) self.assertProvides(self.user, IUser) def test_user_attributes(self): branch = self.make_branch_and_tree('.').branch branch.get_config().set_user_option( 'email', 'Test User ') req = Request.blank('/') app = LocalBazaarUserMiddleware(self.app_func, branch) req.get_response(app) self.assertEqual( 'Test User ', self.user.committer_id) self.assertEqual('Test User', self.user.display_name) self.assertEqual('test@example.com', self.user.email) python-wikkid_0.4.orig/wikkid/tests/test_context.py0000644000000000000000000000145711766321571017666 0ustar00# # Copyright (C) 2010-2012 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for method and classes in wikkid.context.""" from testtools.matchers import Equals from wikkid.context import ExecutionContext from wikkid.tests import TestCase class TestContext(TestCase): def test_empty_script_name(self): context = ExecutionContext() self.assertThat(context.script_name, Equals('')) def test_script_name(self): context = ExecutionContext(script_name='/foo') self.assertThat(context.script_name, Equals('/foo')) def test_script_name_strips_trailing_slash(self): context = ExecutionContext(script_name='/foo/') self.assertThat(context.script_name, Equals('/foo')) python-wikkid_0.4.orig/wikkid/tests/test_git_filestore.py0000644000000000000000000000332214160200415021012 0ustar00# # Copyright (C) 2012 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the wikkid.filestore.git.FileStore.""" from dulwich.repo import MemoryRepo from wikkid.filestore.git import ( FileStore, ) from wikkid.tests import ProvidesMixin, TestCase from wikkid.tests.filestore import TestFileStore class TestGitFileStore(TestCase, ProvidesMixin, TestFileStore): """Tests for the git filestore and files.""" def make_filestore(self, contents=None): repo = MemoryRepo() fs = FileStore(repo) if contents: for (path, contents) in contents: if contents is None: # Directory continue fs.update_file( path, contents, author="Somebody ", parent_revision=None, commit_message="Added by make_filestore") return fs def test_empty(self): # Empty files do not have line endings, but they can be saved # nonetheless. filestore = self.make_filestore( [('test.txt', b'several\nlines\nof\ncontent')]) f = filestore.get_file('test.txt') base_rev = f.last_modified_in_revision filestore.update_file( 'test.txt', b'', 'Test Author ', base_rev) curr = filestore.get_file('test.txt') self.assertEqual(b'', curr.get_content()) def test_listing_directory_empty(self): filestore = self.make_filestore( [('empty/', None)]) listing = filestore.list_directory('empty') self.assertIs(None, listing) python-wikkid_0.4.orig/wikkid/tests/test_model.py0000644000000000000000000001250514160200415017256 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the model objects.""" from operator import attrgetter from wikkid.interface.resource import ( IDefaultPage, IDirectoryResource, IMissingResource, IRootResource, IWikiTextFile) from wikkid.tests.factory import FactoryTestCase class TestBaseRootResource(FactoryTestCase): """Tests for BaseResource.root_resource.""" def test_root_can_get_root(self): """Even the root resource can get the root resource.""" factory = self.make_factory() resource = factory.get_resource_at_path('/') root = resource.root_resource self.assertProvides(root, IRootResource) def test_missing_can_get_root(self): """Even the root resource can get the root resource.""" factory = self.make_factory() resource = factory.get_resource_at_path('/MissingPage') root = resource.root_resource self.assertProvides(root, IRootResource) class TestBaseDefaultResource(FactoryTestCase): """Tests for BaseResource.default_resource.""" def test_default_when_missing(self): """If the default is missing, then a missing resource is returned for the default path. """ factory = self.make_factory() resource = factory.get_resource_at_path('/') home = resource.default_resource self.assertProvides(home, IMissingResource) self.assertProvides(home, IDefaultPage) self.assertEqual('/Home', home.preferred_path) def test_default_when_exists(self): """If the default exists, the default wiki page is returned.""" factory = self.make_factory([ ('Home.txt', 'Some content'), ]) resource = factory.get_resource_at_path('/') home = resource.default_resource self.assertProvides(home, IWikiTextFile) self.assertProvides(home, IDefaultPage) self.assertEqual('/Home', home.preferred_path) def test_default_different_name(self): """If the default name has been overridden, then the default_resource attribute returns the correct name. """ factory = self.make_factory([ ('FrontPage.txt', 'Some content'), ]) factory.DEFAULT_PATH = 'FrontPage' resource = factory.get_resource_at_path('/') home = resource.default_resource self.assertProvides(home, IWikiTextFile) self.assertProvides(home, IDefaultPage) self.assertEqual('/FrontPage', home.preferred_path) class TestBaseParent(FactoryTestCase): """Tests for the BaseResource.parent attribute.""" def test_parent_for_root(self): """Root shouldn't have a parent.""" factory = self.make_factory() root = factory.get_root_resource() self.assertIs(None, root.parent) def test_parent_for_default(self): """The default's parent should be root.""" factory = self.make_factory() home = factory.get_default_resource() parent = home.parent self.assertProvides(parent, IRootResource) def test_parent_for_page(self): """Any page in the root directory has root as its parent.""" factory = self.make_factory([ ('SomeDir.txt', 'Some content'), ]) home = factory.get_resource_at_path('/SomeDir') parent = home.parent self.assertProvides(parent, IRootResource) def test_parent_for_subpage(self): """The path of the parent should be the parent directory.""" factory = self.make_factory([ ('SomeDir/', None), ('SomeDir.txt', 'Some content'), ]) missing = factory.get_resource_at_path('/SomeDir/NoPage') parent = missing.parent expected = factory.get_resource_at_path('/SomeDir') self.assertEquals(expected.preferred_path, parent.preferred_path) class TestDirectoryResource(FactoryTestCase): def test_implements_interface(self): """DirectoryResource implements IDirectoryResource.""" factory = self.make_factory([ ('SomeDir/', None), ]) dir_resource = factory.get_resource_at_path('/SomeDir') self.assertProvides(dir_resource, IDirectoryResource) def test_directory_and_pages(self): """Both the directory and wiki page are returned.""" factory = self.make_factory([ ('SomeDir/', None), ('SomeDir.txt', 'Some content'), ]) dir_resource = factory.get_resource_at_path('/') listing = dir_resource.get_listing() some_dir, some_wiki = sorted( listing, key=attrgetter('path')) self.assertEqual('/SomeDir', some_dir.path) self.assertEqual('/SomeDir.txt', some_wiki.path) def test_user_for_file(self): """Test that the user is a user object.""" factory = self.make_factory() page = factory.get_resource_at_path('/testing') page.put_bytes( 'hello world', 'Test User ', None, None) new_page = factory.get_resource_at_path('/testing') user = new_page.last_modified_by self.assertEqual('Test User', user.display_name) self.assertEqual('test@example.com', user.email) python-wikkid_0.4.orig/wikkid/tests/test_model_factory.py0000644000000000000000000002235514160200415021011 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the wikkid.model.factory module.""" from wikkid.interface.resource import ( IBinaryFile, IDirectoryResource, IMissingResource, IRootResource, ISourceTextFile, IWikiTextFile, ) from wikkid.tests.factory import FactoryTestCase # TODO: make a testing filestore that can produce either a volatile filestore # or a bzr filestore. class TestFactoryGetResourceAtPath(FactoryTestCase): """Test the get_resource_at_path method of the Factory class.""" def test_get_resource_at_path_root_no_content(self): # If the root file is selected, and there is no content, there is no # read_filename or file_resource, but there is a write_filename for # the default home page. factory = self.make_factory() info = factory.get_resource_at_path('/') self.assertProvides(info, IRootResource) self.assertEqual('/', info.path) self.assertIs(None, info.get_dir_name()) self.assertIs(None, info.dir_resource) self.assertFalse(info.has_home_page) def test_get_resource_at_path_root_has_page(self): # If the root file is selected, and there is a home page, this is # returned. factory = self.make_factory([ ('Home.txt', b'the home page'), ]) info = factory.get_resource_at_path('/') self.assertProvides(info, IRootResource) self.assertEqual('/', info.path) self.assertIs(None, info.get_dir_name()) self.assertIs(None, info.dir_resource) self.assertEqual('Home.txt', info.file_resource.path) self.assertTrue(info.has_home_page) def test_get_resource_at_path_missing_file(self): # A missing file as no read filename, nor resource, but does have # a write file name. factory = self.make_factory() info = factory.get_resource_at_path('/missing-file') self.assertProvides(info, IMissingResource) self.assertEqual('/missing-file', info.path) self.assertEqual('missing-file.txt', info.write_filename) self.assertIs(None, info.file_resource) self.assertIs(None, info.dir_resource) def test_get_resource_at_path_existing_file_no_suffix(self): # If a file is requested that exists but has no suffix, it is returned # unaltered, and is interpreted as a wiki page. factory = self.make_factory([ ('README', b'A readme file'), ]) info = factory.get_resource_at_path('/README') self.assertProvides(info, IWikiTextFile) self.assertEqual('/README', info.path) self.assertEqual('README', info.write_filename) self.assertEqual('README', info.file_resource.path) self.assertIs(None, info.dir_resource) def test_get_resource_at_path_missing_file_not_text(self): # A missing file is requested, but has a suffix, don't attempt to add # a .txt to it. factory = self.make_factory() info = factory.get_resource_at_path('/missing-file.cpp') self.assertProvides(info, IMissingResource) self.assertEqual('/missing-file.cpp', info.path) self.assertEqual('missing-file.cpp', info.write_filename) self.assertIs(None, info.file_resource) self.assertIs(None, info.dir_resource) def test_get_resource_at_path_not_text(self): # If a file is reqeusted that has a suffix that isn't text, # a source text file object is returned, factory = self.make_factory([ ('sample.cpp', b'A c++ file'), ]) info = factory.get_resource_at_path('/sample.cpp') self.assertProvides(info, ISourceTextFile) self.assertEqual('/sample.cpp', info.path) self.assertEqual('sample.cpp', info.write_filename) self.assertEqual('sample.cpp', info.file_resource.path) self.assertIs(None, info.dir_resource) def test_get_resource_at_path_binary_file(self): # If a file exists and is binary, a binary file is returned. factory = self.make_factory([ ('sample.png', b'A picture'), ]) info = factory.get_resource_at_path('/sample.png') self.assertProvides(info, IBinaryFile) self.assertEqual('/sample.png', info.path) self.assertEqual('sample.png', info.write_filename) self.assertEqual('sample.png', info.file_resource.path) self.assertIs(None, info.dir_resource) def test_get_resource_at_path_directory(self): # A directory without a matching text file doesn't have a # read_filename either, but does have a dir_resource. factory = self.make_factory([ ('SomeDir/', None), ]) info = factory.get_resource_at_path('/SomeDir') self.assertProvides(info, IDirectoryResource) self.assertEqual('/SomeDir', info.path) self.assertEqual('SomeDir.txt', info.write_filename) self.assertIs(None, info.file_resource) self.assertEqual('SomeDir', info.get_dir_name()) def test_get_resource_at_path_directory_with_page(self): # A directory with a matching text file has a text resource and a dir # resource. factory = self.make_factory([ ('SomeDir/', None), ('SomeDir.txt', b'Some content'), ]) info = factory.get_resource_at_path('/SomeDir') self.assertProvides(info, IWikiTextFile) self.assertEqual('/SomeDir', info.path) self.assertEqual('SomeDir.txt', info.write_filename) self.assertEqual('SomeDir.txt', info.file_resource.path) self.assertIsNot('SomeDir', info.dir_resource) def test_get_resource_at_path_page_no_directory(self): # A directory with a matching text file has a text resource and a dir # resource. factory = self.make_factory([ ('SomeDir.txt', b'Some content'), ]) info = factory.get_resource_at_path('/SomeDir') self.assertProvides(info, IWikiTextFile) self.assertEqual('/SomeDir', info.path) self.assertEqual('SomeDir.txt', info.write_filename) self.assertEqual('SomeDir.txt', info.file_resource.path) self.assertIs(None, info.dir_resource) def test_get_resource_at_path_subdirs_missing_file(self): # The path info reflects the request path. factory = self.make_factory() info = factory.get_resource_at_path('/a/b/c/d') self.assertProvides(info, IMissingResource) self.assertEqual('/a/b/c/d', info.path) self.assertEqual('a/b/c/d.txt', info.write_filename) self.assertIs(None, info.file_resource) self.assertIs(None, info.dir_resource) def test_get_resource_at_path_subdirs_existing_file(self): # The path info reflects the request path. factory = self.make_factory([ ('a/b/c/d.txt', b'a text file'), ]) info = factory.get_resource_at_path('/a/b/c/d') self.assertProvides(info, IWikiTextFile) self.assertEqual('/a/b/c/d', info.path) self.assertEqual('a/b/c/d.txt', info.write_filename) self.assertEqual('a/b/c/d.txt', info.file_resource.path) self.assertIs(None, info.dir_resource) def test_get_resource_at_path_subdirs_existing_file_and_dir(self): # The path info reflects the request path. factory = self.make_factory([ ('a/b/c/d.txt', b'a text file'), ('a/b/c/d/e.txt', b'another text file'), ]) info = factory.get_resource_at_path('/a/b/c/d') self.assertProvides(info, IWikiTextFile) self.assertEqual('/a/b/c/d', info.path) self.assertEqual('a/b/c/d.txt', info.write_filename) self.assertEqual('a/b/c/d.txt', info.file_resource.path) self.assertEqual('a/b/c/d', info.dir_resource.path) class TestFactoryGetPreferredPath(FactoryTestCase): """Tests for the get_preferred_path method of the Factory class.""" def test_home_preferred(self): factory = self.make_factory() self.assertEqual('/Home', factory.get_preferred_path('/')) self.assertEqual('/Home', factory.get_preferred_path('/Home')) self.assertEqual('/Home', factory.get_preferred_path('/Home.txt')) def test_default_preferred(self): factory = self.make_factory() factory.DEFAULT_PATH = 'FrontPage' self.assertEqual( '/FrontPage', factory.get_preferred_path('/')) self.assertEqual( '/FrontPage', factory.get_preferred_path('/FrontPage')) self.assertEqual( '/FrontPage', factory.get_preferred_path('/FrontPage.txt')) def test_image_preferred(self): factory = self.make_factory() self.assertEqual( '/foo/bar.jpg', factory.get_preferred_path('/foo/bar.jpg')) def test_text_preferred(self): factory = self.make_factory() self.assertEqual('/README', factory.get_preferred_path('/README')) self.assertEqual('/a.b.txt', factory.get_preferred_path('/a.b.txt')) self.assertEqual('/a', factory.get_preferred_path('/a.txt')) self.assertEqual('/a/b', factory.get_preferred_path('/a/b.txt')) python-wikkid_0.4.orig/wikkid/tests/test_view_dispatcher.py0000644000000000000000000000650314160200415021337 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the view dispatcher.""" from zope.interface import Interface, implementer from wikkid.dispatcher import get_view, register_view, unregister_view from wikkid.tests import TestCase from wikkid.view.base import BaseView class TestGetView(TestCase): """Tests for get_view.""" def test_no_interfaces(self): """If the object supports no interfaces, there is no view.""" class NoInterfaces(object): pass obj = NoInterfaces() self.assertIs(None, get_view(obj, None, None)) def test_interface_not_registered(self): """If the object supports an interface, but that interface is not registered, we get no view.""" class IHasInterface(Interface): pass @implementer(IHasInterface) class HasInterface(object): pass obj = HasInterface() self.assertIs(None, get_view(obj, None, None)) def test_interface_view_registered(self): """If the object supports an interface, and the view is registered, make sure that the view is returned when asked for.""" class IHasInterface(Interface): pass @implementer(IHasInterface) class HasInterface(object): pass class AView(object): for_interface = IHasInterface name = 'name' def __init__(self, *args): pass def initialize(self): self.initialized = True register_view(AView) self.addCleanup(unregister_view, AView) obj = HasInterface() view = get_view(obj, 'name', None) self.assertIsInstance(view, AView) self.assertTrue(view.initialized) def test_interface_view_registered_default(self): """If the object supports an interface, and the view is registered as the default view, when a view is requested where no name is specified, the default view is returned.""" class IHasInterface(Interface): pass @implementer(IHasInterface) class HasInterface(object): pass class AView(object): for_interface = IHasInterface name = 'name' is_default = True def __init__(self, *args): pass def initialize(self): self.initialized = True register_view(AView) self.addCleanup(unregister_view, AView) obj = HasInterface() view = get_view(obj, None, None) self.assertIsInstance(view, AView) self.assertTrue(view.initialized) class TestViewRegistration(TestCase): """Test that views that inherit from BaseView are registered.""" def test_registration(self): """Create a view class, and make sure it is registered.""" class IHasInterface(Interface): pass @implementer(IHasInterface) class HasInterface(object): pass class AView(BaseView): for_interface = IHasInterface name = 'name' obj = HasInterface() view = get_view(obj, 'name', None) self.assertIsInstance(view, AView) self.addCleanup(unregister_view, AView) python-wikkid_0.4.orig/wikkid/tests/test_volatile_filestore.py0000644000000000000000000000102511372503456022061 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the wikkid.filestore.volatile.FileStore.""" from wikkid.tests import TestCase from wikkid.tests.filestore import TestFileStore from wikkid.filestore.volatile import FileStore class TestVolatileFileStore(TestCase, TestFileStore): """Tests for the volatile filestore and files.""" def make_filestore(self, contents=None): return FileStore(contents) python-wikkid_0.4.orig/wikkid/tests/views/0000755000000000000000000000000011370235422015705 5ustar00python-wikkid_0.4.orig/wikkid/tests/formatters/__init__.py0000644000000000000000000000102114160200415021033 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The wikkid tests for the wikkid.formatter package.""" import unittest def test_suite(): names = [ 'registry', 'rest', 'markdown', 'textile', ] module_names = ['wikkid.tests.formatters.test_' + name for name in names] loader = unittest.TestLoader() suite = loader.loadTestsFromNames(module_names) return suite python-wikkid_0.4.orig/wikkid/tests/formatters/test_markdown.py0000644000000000000000000001307414336241273022204 0ustar00# -*- coding: utf-8 -*- # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the wikkid.formatter.markdownformatter module.""" from textwrap import dedent from bs4 import BeautifulSoup # The markdown formatter is optional. If the MarkdownFormatter is not # available, the tests are skipped. try: from wikkid.formatter.markdownformatter import MarkdownFormatter has_markdown = True except ImportError: has_markdown = False from wikkid.tests import TestCase class TestMarkdownFormatter(TestCase): """Tests for the markdown formatter.""" def setUp(self): TestCase.setUp(self) if has_markdown: self.formatter = MarkdownFormatter() else: self.skip('markdown formatter not available') def test_simple_headings(self): # A simple heading and a paragraph. text = dedent("""\ Heading 1 ========= Heading 2 --------- Simple sentence. """) result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertEqual('Heading 1', soup.h1.string) self.assertEqual('Heading 2', soup.h2.string) # We don't care about whitespace on

tags, and markdown inserts # newlines. self.assertEqual('Simple sentence.', soup.p.string.strip()) def test_detailed_headings(self): text = dedent("""\ # Heading 1 ## Heading 2 ### Heading 3 #### Heading 4 ##### Heading 5 ###### Heading 6""") result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertEqual('Heading 1', soup.h1.string) self.assertEqual('Heading 2', soup.h2.string) self.assertEqual('Heading 3', soup.h3.string) self.assertEqual('Heading 4', soup.h4.string) self.assertEqual('Heading 5', soup.h5.string) self.assertEqual('Heading 6', soup.h6.string) def test_inline_link(self): # A paragraph containing a wiki word. text = "A link to the [FrontPage](http://127.0.0.1) helps." result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertEqual('http://127.0.0.1', soup.a['href']) def test_reference_link(self): # A paragraph containing a wiki word. text = dedent("""\ This is [an example][id] reference-style link. [id]: http://127.0.0.1 """) result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertEqual('http://127.0.0.1', soup.a['href']) def test_emphasis(self): texts = ('We can have *emphasis* and **strong** as well!', 'We can have _emphasis_ and __strong__ as well!') for text in texts: result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertEqual('emphasis', soup.em.string) self.assertEqual('strong', soup.strong.string) def test_blockquote(self): text = dedent("""\ > This is a block quoted paragraph > that spans multiple lines. """) result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertTrue(soup.blockquote is not None) def test_lists(self): text = dedent("""\ Some Text. * UL 1 * UL 2 * UL 3 Some More Text! 1. OL 1 2. OL 2 7. OL 3 """) result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertIsNot(None, soup.ul) ulNodes = soup.ul.findAll('li') self.assertEqual( ['UL 1', 'UL 2', 'UL 3'], [node.string.strip() for node in ulNodes]) self.assertIsNot(None, soup.ol) olNodes = soup.ol.findAll('li') self.assertEqual( ['OL 1', 'OL 2', 'OL 3'], [node.string.strip() for node in olNodes]) def test_code_blocks(self): text = dedent("""\ Some Normal Text. Some Code inside pre tags More Normal text. """) result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertEqual( soup.pre.code.string.strip(), 'Some Code inside pre tags') def test_hr(self): # test different HR types: texts = ('* * *', '***', '*********', '- - - -', '------------------') for text in texts: result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertIsNot(None, soup.hr) def test_code(self): text = 'use the `printf()` function' result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertEqual('printf()', soup.code.string) def test_unicode(self): # Test the unicode support of the markdown formatter. text = u'\N{SNOWMAN}' result = self.formatter.format('format', text) soup = BeautifulSoup(result, features="lxml") self.assertEqual(soup.p.string.strip(), text) python-wikkid_0.4.orig/wikkid/tests/formatters/test_registry.py0000644000000000000000000000424014160200415022211 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for wikkid.formatter.registry.""" from textwrap import dedent from wikkid.formatter.registry import get_wiki_formatter from wikkid.tests import TestCase class TestGetWikiFormtter(TestCase): """Tests for get_wiki_formatter.""" def test_default_missing(self): self.assertRaises( KeyError, get_wiki_formatter, 'some content', 'missing') def test_default_exists(self): content, formatter = get_wiki_formatter( 'some content', 'rest') self.assertEqual('some content', content) self.assertEqual( 'RestructuredTextFormatter', formatter.__class__.__name__) def test_specify_missing(self): content, formatter = get_wiki_formatter( dedent("""\ # missing some content """), 'rest') self.assertEqual('# missing\nsome content\n', content) self.assertEqual( 'RestructuredTextFormatter', formatter.__class__.__name__) def test_specify_extra_whitespace(self): content, formatter = get_wiki_formatter( dedent("""\ #\t\tpygments some content """), 'rest') self.assertEqual('some content\n', content) self.assertEqual('PygmentsFormatter', formatter.__class__.__name__) def test_specify_extra_params(self): content, formatter = get_wiki_formatter( dedent("""\ # pygments extra params some content """), 'rest') self.assertEqual('some content\n', content) self.assertEqual('PygmentsFormatter', formatter.__class__.__name__) def test_specify_pygments_case_insensitive(self): content, formatter = get_wiki_formatter( dedent("""\ # pygments some content """), 'rest') self.assertEqual('some content\n', content) self.assertEqual('PygmentsFormatter', formatter.__class__.__name__) python-wikkid_0.4.orig/wikkid/tests/formatters/test_rest.py0000644000000000000000000000173414336241273021337 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the wikkid.formatter.rest module.""" from textwrap import dedent from bs4 import BeautifulSoup from wikkid.formatter.restformatter import RestructuredTextFormatter from wikkid.tests import TestCase class TestRestructuredTextFormatter(TestCase): """Tests for the ReST formatter.""" def setUp(self): TestCase.setUp(self) self.formatter = RestructuredTextFormatter() def test_simple_text(self): # A simple heading and a paragraph. text = dedent("""\ Nice Heading ============ Simple sentence. """) result = self.formatter.format('filename', text) soup = BeautifulSoup(result, features="lxml") self.assertEqual('Nice Heading', soup.h1.string) self.assertEqual('Simple sentence.', soup.p.string) python-wikkid_0.4.orig/wikkid/tests/formatters/test_textile.py0000644000000000000000000000727614160200415022033 0ustar00# -*- coding: utf-8 -*- # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the wikkid.formatter.textileformatter module.""" from textwrap import dedent from bs4 import BeautifulSoup # The textile formatter is optional. If the MarkdownFormatter is not # available, the tests are skipped. try: from wikkid.formatter.textileformatter import TextileFormatter has_textile = True except ImportError: has_textile = False from wikkid.tests import TestCase class TestTextileFormatter(TestCase): """Tests for the textile formatter.""" def setUp(self): TestCase.setUp(self) if has_textile: self.formatter = TextileFormatter() else: self.skip('textile formatter not available') def test_detailed_headings(self): text = dedent("""\ h1. Heading 1 h2. Heading 2 h3. Heading 3 h4. Heading 4 h5. Heading 5 h6. Heading 6 """) result = self.formatter.format('filename', text) soup = BeautifulSoup(result) self.assertEqual('Heading 1', soup.h1.string) self.assertEqual('Heading 2', soup.h2.string) self.assertEqual('Heading 3', soup.h3.string) self.assertEqual('Heading 4', soup.h4.string) self.assertEqual('Heading 5', soup.h5.string) self.assertEqual('Heading 6', soup.h6.string) def test_inline_link(self): # A paragraph containing a wiki word. text = 'A link to the "FrontPage":http://127.0.0.1 helps.' result = self.formatter.format('filename', text) soup = BeautifulSoup(result) self.assertEqual('http://127.0.0.1', soup.a['href']) def test_emphasis(self): text = 'We can have _emphasis_ and *strong* as well!' result = self.formatter.format('filename', text) soup = BeautifulSoup(result) self.assertEqual('emphasis', soup.em.string) self.assertEqual('strong', soup.strong.string) def test_blockquote(self): text = dedent('''\ Some Text bq. This is a block quoted paragraph that spans multiple lines. Some more text. ''') result = self.formatter.format('filename', text) soup = BeautifulSoup(result) self.assertTrue(soup.blockquote is not None) def test_lists(self): text = dedent('''\ Some Text. * UL 1 * UL 2 * UL 3 Some More Text! # OL 1 # OL 2 # OL 3 ''') result = self.formatter.format('filename', text) soup = BeautifulSoup(result) self.assertTrue(soup.ul) ulNodes = soup.ul.findAll('li') for i in range(3): self.assertEqual('UL %d' % (i+1), ulNodes[i].string.strip()) self.assertTrue(soup.ol) olNodes = soup.ol.findAll('li') for i in range(3): self.assertEqual('OL %d' % (i+1), olNodes[i].string.strip()) def test_code_blocks(self): text = dedent('''\ Some Normal Text. @Some Code inside pre tags@ More Normal text. ''') result = self.formatter.format('filename', text) soup = BeautifulSoup(result) self.assertEqual(soup.code.string.strip(), 'Some Code inside pre tags') def test_unicode(self): # Test the unicode support of the textile formatter. text = u'\N{SNOWMAN}' result = self.formatter.format('format', text) soup = BeautifulSoup(result) self.assertEqual(soup.p.string.strip(), text) python-wikkid_0.4.orig/wikkid/tests/views/__init__.py0000644000000000000000000000102111410375636020017 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The wikkid tests for the wikkid.view package.""" import unittest def test_suite(): names = [ 'breadcrumbs', 'utils', 'edit', 'root', 'urls', 'view', ] module_names = ['wikkid.tests.views.test_' + name for name in names] loader = unittest.TestLoader() return loader.loadTestsFromNames(module_names) python-wikkid_0.4.orig/wikkid/tests/views/test_breadcrumbs.py0000644000000000000000000001121614160200415021602 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests the breadcrumbs for the views.""" from wikkid.tests.factory import ViewTestCase from wikkid.tests.fakes import TestRequest, TestUser class TestBreadcrumbs(ViewTestCase): """The breadcrumbs lead the user back home.""" # Don't test the breadcrumbs for the root object directly here as it will # redirect to Home. TODO: add a test browser test for this. def setUp(self): super(TestBreadcrumbs, self).setUp() self.user = TestUser('test@example.com', 'Test User') self.request = TestRequest() def assertBreadcrumbs(self, view, expected): """Make sure the breadcrumbs from view are the expected ones.""" crumbs = [(crumb.title, crumb.path) for crumb in view.breadcrumbs] self.assertEqual(expected, crumbs) def test_home_missing(self): # If the Home page is selected, but there is no content, the # breadcrumb is still Home. factory = self.make_factory() view = self.get_view(factory, '/Home') self.assertBreadcrumbs( view, [('Home', '/Home')]) def test_page_missing(self): # If the page is at the root of the tree, but isn't home, then the # first breadcrumb is home, and the second is the page. factory = self.make_factory() view = self.get_view(factory, '/SamplePage') self.assertBreadcrumbs( view, [('Home', '/Home'), ('Sample Page', '/SamplePage')]) def test_nested_page_missing(self): # If the Home page is selected, but there is no content, the # breadcrumb is still Home. factory = self.make_factory() view = self.get_view(factory, '/SamplePage/SubPage/Next') self.assertBreadcrumbs( view, [('Home', '/Home'), ('Sample Page', '/SamplePage'), ('Sub Page', '/SamplePage/SubPage'), ('Next', '/SamplePage/SubPage/Next')]) def test_source_file_missing(self): # If a non-wiki style name is selected, the breadcrumbs are as a wiki # page. factory = self.make_factory() view = self.get_view(factory, '/wikkid/views/base.py') self.assertBreadcrumbs( view, [('Home', '/Home'), ('wikkid', '/wikkid'), ('views', '/wikkid/views'), ('base.py', '/wikkid/views/base.py')]) def test_source_file_existing(self): # If the Home page is selected, but there is no content, the # breadcrumb is still Home. factory = self.make_factory([ ('wikkid/views/base.py', 'A python file'), ]) view = self.get_view(factory, '/wikkid/views/base.py') self.assertBreadcrumbs( view, [('Home', '/Home'), ('wiki root', '/+listing'), ('wikkid', '/wikkid/+listing'), ('views', '/wikkid/views/+listing'), ('base.py', '/wikkid/views/base.py')]) def test_directory_breadcrumbs_root(self): # Directory breadcrumbs start with Home, and then list the # directories, where the urls for the directories are the listing # urls. factory = self.make_factory() view = self.get_view(factory, '/', 'listing') self.assertBreadcrumbs( view, [('Home', '/Home'), ('wiki root', '/+listing')]) def test_directory_breadcrumbs_nested(self): # For each directory after the root, a listing crumb is added. # Names are not wiki expanded. factory = self.make_factory([ ('SomePage/SubPage/Nested.txt', 'some text')]) view = self.get_view(factory, '/SomePage/SubPage', 'listing') self.assertBreadcrumbs( view, [('Home', '/Home'), ('wiki root', '/+listing'), ('SomePage', '/SomePage/+listing'), ('SubPage', '/SomePage/SubPage')]) def test_directory_breadcrumbs_nested_with_script_name(self): # For each directory after the root, a listing crumb is added. # Names are not wiki expanded. factory = self.make_factory([ ('SomePage/SubPage/Nested.txt', 'some text')]) view = self.get_view( factory, '/SomePage/SubPage', 'listing', '/p/wiki') self.assertBreadcrumbs( view, [('Home', '/p/wiki/Home'), ('wiki root', '/p/wiki/+listing'), ('SomePage', '/p/wiki/SomePage/+listing'), ('SubPage', '/p/wiki/SomePage/SubPage')]) python-wikkid_0.4.orig/wikkid/tests/views/test_edit.py0000644000000000000000000000205011410375636020247 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests the edit views.""" from wikkid.tests.factory import ViewTestCase from wikkid.tests.fakes import TestUser class TestEdit(ViewTestCase): """Test the edit view.""" def setUp(self): super(TestEdit, self).setUp() self.user = TestUser('test@example.com', 'Test User') def test_title_nested(self): """Test that a nested page returns the expected title""" factory = self.make_factory([ ('SomePage/SubPage/Nested.txt', 'some text')]) view = self.get_view(factory, '/SomePage/SubPage', 'edit') self.assertEqual('Editing "Sub Page"', view.title) def test_urls(self): """Check the urls for saving and cancel.""" factory = self.make_factory() view = self.get_view(factory, '/NewPage', 'edit') self.assertEqual('/NewPage', view.cancel_url) self.assertEqual('/NewPage/+save', view.save_url) python-wikkid_0.4.orig/wikkid/tests/views/test_root.py0000644000000000000000000000402014336241273020303 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Test views for the root object.""" from bs4 import BeautifulSoup from testtools.matchers import Equals from webob.exc import HTTPSeeOther from wikkid.skin.loader import Skin from wikkid.tests.factory import ViewTestCase class TestRootViews(ViewTestCase): """Test the views on the root object.""" def test_root_redirects(self): """Going to / redirects to the Home page.""" factory = self.make_factory() view = self.get_view(factory, '/') error = self.assertRaises( HTTPSeeOther, view.render, None) self.assertEqual('/Home', error.headers['Location']) def test_root_redirects_with_script_name(self): """Redirection works and respects the script name""" factory = self.make_factory() view = self.get_view(factory, '/', base_url='/p/test') error = self.assertRaises( HTTPSeeOther, view.render, None) self.assertEqual('/p/test/Home', error.headers['Location']) def test_home_rendering(self): """Render the home page and test the elements.""" factory = self.make_factory() view = self.get_view(factory, '/Home') content = view.render(Skin('default')) soup = BeautifulSoup(content.text, features="lxml") [style] = soup.find_all('link', {'rel': 'stylesheet'}) self.assertThat(style['href'], Equals('/static/default.css')) def test_home_rendering_with_script_name(self): """Render the home page and test the elements.""" factory = self.make_factory() view = self.get_view(factory, '/Home', base_url='/p/test') content = view.render(Skin('default')) soup = BeautifulSoup(content.text, features="lxml") [style] = soup.find_all('link', {'rel': 'stylesheet'}) self.assertThat(style['href'], Equals('/p/test/static/default.css')) python-wikkid_0.4.orig/wikkid/tests/views/test_urls.py0000644000000000000000000001005214160200415020273 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests the edit views.""" from testtools.matchers import Equals from webob import Request from wikkid.tests import TestCase from wikkid.tests.factory import FactoryTestCase from wikkid.view.urls import canonical_url, parse_url class TestCanonicalUrl(FactoryTestCase): """Test the wikkid.view.base.canonical_url.""" def assertUrl(self, resource, url, view_name=None, base_url=None): request = Request.blank('/', base_url=base_url) self.assertThat(canonical_url(resource, request, view_name), Equals(url)) def test_root(self): factory = self.make_factory() root = factory.get_resource_at_path('/') self.assertUrl(root, '/') def test_root_listing(self): factory = self.make_factory() root = factory.get_resource_at_path('/') self.assertUrl(root, '/+listing', view_name='listing') def test_default(self): factory = self.make_factory([ ('Home.txt', 'Some content'), ]) root = factory.get_resource_at_path('/') self.assertUrl(root.default_resource, '/Home') def test_default_view(self): factory = self.make_factory([ ('Home.txt', 'Some content'), ]) root = factory.get_resource_at_path('/') self.assertUrl(root.default_resource, '/Home/+edit', view_name='edit') def test_wiki_page(self): factory = self.make_factory([ ('SomeDir/SomePage.txt', 'Some content'), ]) page = factory.get_resource_at_path('/SomeDir/SomePage') self.assertUrl(page, '/SomeDir/SomePage') def test_wiki_page_view(self): factory = self.make_factory([ ('SomeDir/SomePage.txt', 'Some content'), ]) page = factory.get_resource_at_path('/SomeDir/SomePage') self.assertUrl(page, '/SomeDir/SomePage/+edit', view_name='edit') def test_wiki_page_full_url(self): factory = self.make_factory([ ('SomeDir.txt', 'Some content'), ]) page = factory.get_resource_at_path('/SomeDir.txt') self.assertUrl(page, '/SomeDir') def test_wiki_page_full_url_with_view(self): factory = self.make_factory([ ('SomeDir.txt', 'Some content'), ]) page = factory.get_resource_at_path('/SomeDir.txt') self.assertUrl(page, '/SomeDir/+edit', view_name='edit') def test_other_file(self): factory = self.make_factory([ ('simple.py', '#!/usr/bin/python'), ]) page = factory.get_resource_at_path('/simple.py') self.assertUrl(page, '/simple.py') def test_other_file_view(self): factory = self.make_factory([ ('simple.py', '#!/usr/bin/python'), ]) page = factory.get_resource_at_path('/simple.py') self.assertUrl(page, '/simple.py/+edit', view_name='edit') def test_missing(self): factory = self.make_factory() missing = factory.get_resource_at_path('/MissingPage') self.assertUrl(missing, '/MissingPage') def test_missing_view(self): factory = self.make_factory() missing = factory.get_resource_at_path('/MissingPage') self.assertUrl(missing, '/MissingPage/+edit', view_name='edit') class TestParseUrl(TestCase): """Tests for wikkid.view.base.parse_url.""" def test_root(self): path, view = parse_url('/') self.assertEqual('/', path) self.assertIs(None, view) def test_root_view(self): path, view = parse_url('/+listing') self.assertEqual('/', path) self.assertEqual('listing', view) def test_path(self): path, view = parse_url('/some/path') self.assertEqual('/some/path', path) self.assertIs(None, view) def test_path_view(self): path, view = parse_url('/some/path/+view') self.assertEqual('/some/path', path) self.assertEqual('view', view) python-wikkid_0.4.orig/wikkid/tests/views/test_utils.py0000644000000000000000000000304111372503456020463 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests for the wikkid.view module.""" from testtools import TestCase from wikkid.view.utils import expand_wiki_name, title_for_filename class TestExpandWikiName(TestCase): """Tests for expand_wiki_name.""" def test_expand_wiki_name(self): self.assertEqual('simple.txt', expand_wiki_name('simple.txt')) self.assertEqual('nonMatching', expand_wiki_name('nonMatching')) self.assertEqual('README', expand_wiki_name('README')) self.assertEqual('Home', expand_wiki_name('Home')) self.assertEqual('Front Page', expand_wiki_name('FrontPage')) self.assertEqual('FrontPage.txt', expand_wiki_name('FrontPage.txt')) self.assertEqual('A Simple Page', expand_wiki_name('ASimplePage')) self.assertEqual('FTP Example', expand_wiki_name('FTPExample')) self.assertEqual( 'A Simple FTP Example', expand_wiki_name('ASimpleFTPExample')) class TesttitleForFilename(TestCase): """Tests for title_for_filename.""" def test_title_for_filename(self): self.assertEqual('simple', title_for_filename('simple.txt')) self.assertEqual('simple.cpp', title_for_filename('simple.cpp')) self.assertEqual('Front Page', title_for_filename('FrontPage')) self.assertEqual('Front Page', title_for_filename('FrontPage.txt')) self.assertEqual('FrontPage.cpp', title_for_filename('FrontPage.cpp')) python-wikkid_0.4.orig/wikkid/tests/views/test_view.py0000644000000000000000000000176111410375636020304 0ustar00# # Copyright (C) 2010 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Tests the display views.""" from wikkid.tests.factory import ViewTestCase class TestView(ViewTestCase): """Test the display view.""" def test_wiki_last_modified_by(self): """Test that the last committer is displayed properly""" factory = self.make_factory([ ('SomePage/SubPage/Nested.txt', 'some text')]) view = self.get_view(factory, '/SomePage/SubPage/Nested', 'view') user = view.last_modified_by self.assertEqual('First User', user.display_name) def test_other_last_modified_by(self): """Test that the last committer is displayed properly""" factory = self.make_factory([ ('test.py', 'some text')]) view = self.get_view(factory, '/test.py') user = view.last_modified_by self.assertEqual('First User', user.display_name) python-wikkid_0.4.orig/wikkid/user/__init__.py0000644000000000000000000000026711372503456016511 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """User classes for wikkid.""" python-wikkid_0.4.orig/wikkid/user/baseuser.py0000644000000000000000000000077614160200415016553 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The base user implementation. Provides the gravatar support. """ import hashlib class BaseUser(object): @property def gravatar(self): url = "http://www.gravatar.com/avatar/" data = self.email.lower().encode('utf-8', 'replace') url += hashlib.md5(data).hexdigest() url += "?s=50&d=identicon" return url python-wikkid_0.4.orig/wikkid/user/bzr.py0000644000000000000000000000366114160200415015533 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """A user factory and user class which uses the bzr identity from the local bazaar config.""" import email import logging from webob import Request from zope.interface import implementer from wikkid.interface.user import IUser, IUserFactory from wikkid.user.baseuser import BaseUser def create_bzr_user_from_author_string(author): name, address = email.utils.parseaddr(author) if name: display_name = name else: display_name = address return User(address, display_name, author) class LocalBazaarUserMiddleware(object): """A middleware to inject a user into the environment.""" def __init__(self, app, branch): self.app = app config = branch.get_config() self.user = create_bzr_user_from_author_string(config.username()) def __call__(self, environ, start_response): environ['wikkid.user'] = self.user req = Request(environ) resp = req.get_response(self.app) return resp(environ, start_response) @implementer(IUserFactory) class UserFactory(object): """Generate a user from local bazaar config.""" def __init__(self, branch): """Use the user config from the branch.""" config = branch.get_config() self.user = create_bzr_user_from_author_string(config.username()) logger = logging.getLogger('wikkid') logger.info( 'Using bzr identity: "%s", "%s"', self.user.display_name, self.user.email) def create(self, request): """Create a User.""" return self.user @implementer(IUser) class User(BaseUser): """A user from the local bazaar config.""" def __init__(self, email, display_name, committer_id): self.email = email self.display_name = display_name self.committer_id = committer_id python-wikkid_0.4.orig/wikkid/user/git.py0000644000000000000000000000400214160200415015507 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """A user factory and user class which uses the git identity from the local Git config.""" import email import logging from webob import Request from zope.interface import implementer from wikkid.interface.user import IUser, IUserFactory from wikkid.user.baseuser import BaseUser def create_git_user_from_author_string(author): name, address = email.Utils.parseaddr(author) if name: display_name = name else: display_name = address return User(address, display_name, author) class LocalGitUserMiddleware(object): """A middleware to inject a user into the environment.""" def __init__(self, app, repo): self.app = app config = repo.get_config_stack() email = config.get(("user", ), "email") name = config.get(("user", ), "name") self.user = User(email, name, "%s <%s>" % (name, email)) def __call__(self, environ, start_response): environ['wikkid.user'] = self.user req = Request(environ) resp = req.get_response(self.app) return resp(environ, start_response) @implementer(IUserFactory) class UserFactory(object): """Generate a user from local bazaar config.""" def __init__(self, branch): """Use the user config from the branch.""" config = branch.get_config() self.user = create_git_user_from_author_string(config.username()) logger = logging.getLogger('wikkid') logger.info( 'Using git identity: "%s", "%s"', self.user.display_name, self.user.email) def create(self, request): """Create a User.""" return self.user @implementer(IUser) class User(BaseUser): """A user from the local bazaar config.""" def __init__(self, email, display_name, committer_id): self.email = email self.display_name = display_name self.committer_id = committer_id python-wikkid_0.4.orig/wikkid/view/__init__.py0000644000000000000000000000026511372503456016503 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Views for Wikkid Wiki.""" python-wikkid_0.4.orig/wikkid/view/base.py0000644000000000000000000001132314160200415015636 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """The base view class.""" import logging from webob import Response from wikkid.dispatcher import register_view from wikkid.view.urls import canonical_url from wikkid.view.utils import title_for_filename from wikkid.interface.resource import IDefaultPage, IRootResource class BaseViewMetaClass(type): """This metaclass registers the view with the view registry.""" def __new__(cls, classname, bases, classdict): """Called when defining a new class.""" instance = type.__new__(cls, classname, bases, classdict) register_view(instance) return instance class Breadcrumb(object): """Breadcrumbs exist to give the user quick links up the path chain.""" def __init__(self, context, request, view=None, title=None): self.path = canonical_url(context, request, view) if title is None: self.title = title_for_filename(context.base_name) else: self.title = title class BaseView(object, metaclass=BaseViewMetaClass): """The base view class. This is an abstract base class. """ def __init__(self, context, request, execution_context): self.execution_context = execution_context self.context = context self.request = request if request is not None: self.user = request.environ.get('wikkid.user', None) self.logger = logging.getLogger('wikkid') def _create_breadcrumbs(self): crumbs = [Breadcrumb(self.context, self.request)] current = self.context.parent while not IRootResource.providedBy(current): crumbs.append(Breadcrumb(current, self.request)) current = current.parent # And add in the default page if the context isn't the default. if not IDefaultPage.providedBy(self.context): crumbs.append(Breadcrumb(current.default_resource, self.request)) return reversed(crumbs) def initialize(self): """Provide post-construction initialization.""" @property def breadcrumbs(self): return self._create_breadcrumbs() @property def title(self): return title_for_filename(self.context.base_name) @property def last_modified_by(self): return self.context.last_modified_by @property def last_modified_date(self): last_modified = self.context.last_modified_date if last_modified is None: return None return last_modified.strftime('%Y-%m-%d %H:%M:%S') def before_render(self): """A hook to do things before rendering.""" def canonical_url(self, context, view=None): return canonical_url(context, self.request, view) def template_args(self): """Needs to be implemented in the derived classes. :returns: A dict of values. """ return { 'view': self, 'user': self.user, 'context': self.context, 'request': self.request, 'canonical_url': self.canonical_url, } def _render(self, skin): """Get the template and render with the args. If a template isn't going to be used or provide the conent, this is the method to override. """ template = skin.get_template(self.template) content = template.render(**self.template_args()) # Return the encoded content. return self.make_response(content.encode('utf-8')) def make_response(self, body): """Construct the response object for this request. :param body: The body of the response, as a unicode string. :return: A `Response` object. """ return Response(body) def render(self, skin): """Render the page. Return a tuple of content type and content. """ self.before_render() return self._render(skin) class DirectoryBreadcrumbView(BaseView): """A view that uses the directories as breadcrumbs.""" def _create_breadcrumbs(self): crumbs = [] current = self.context view = None while not IRootResource.providedBy(current): crumbs.append(Breadcrumb( current, self.request, view, title=current.base_name)) current = current.parent # Add listings to subsequent urls. view = 'listing' # Add in the root dir. crumbs.append(Breadcrumb(current, self.request, 'listing', title='wiki root')) # And add in the default page. crumbs.append(Breadcrumb(current.default_resource, self.request)) return reversed(crumbs) python-wikkid_0.4.orig/wikkid/view/binary.py0000644000000000000000000000114311410375636016224 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Views associated with binary files.""" from webob import Response from wikkid.interface.resource import IBinaryFile from wikkid.view.base import BaseView class BinaryFile(BaseView): """Renders a binary file with its mimetype.""" for_interface = IBinaryFile name = 'view' is_default = True def _render(self, skin): return Response( body=self.context.get_bytes(), content_type=self.context.mimetype) python-wikkid_0.4.orig/wikkid/view/directory.py0000644000000000000000000000454614160200415016741 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """View classes to control the rendering of the content.""" from wikkid.interface.resource import IDirectoryResource from wikkid.view.base import DirectoryBreadcrumbView from wikkid.view.urls import canonical_url class ListingItem(object): """An item to be shown in the directory listing.""" def __init__(self, context, request, view, css_class, name=None): self.context = context self.url = canonical_url(self.context, request, view) if name is None: name = context.base_name self.name = name self.css_class = css_class class DirectoryListingPage(DirectoryBreadcrumbView): """The directory listing shows the content in the directory. This view is shown if there is no matching wiki apge the same name as the directory (i.e. with '.txt' on the end). """ for_interface = IDirectoryResource name = 'listing' is_default = False template = 'view_directory' def before_render(self): """Get the listing and split it into directories and files.""" directories = [] files = [] for item in self.context.get_listing(): if IDirectoryResource.providedBy(item): directories.append(item) else: files.append(item) def sort_key(item): return item.base_name.lower() items = [] # If we are looking at / don't add a parent dir. if self.context.path != '/': parent = self.context.parent items.append( ListingItem(parent, self.request, 'listing', 'up', name='..')) for item in sorted(directories, key=sort_key): items.append( ListingItem(item, self.request, 'listing', 'directory')) for item in sorted(files, key=sort_key): items.append(ListingItem(item, self.request, None, 'file')) self.items = items @property def content(self): return 'Directory listing for %s' % self.path @property def title(self): """The title is just the directory path.""" dir_name = self.context.get_dir_name() if dir_name is None: return 'Wiki Root' else: return dir_name python-wikkid_0.4.orig/wikkid/view/edit.py0000644000000000000000000000142211755162524015667 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """View base class for editing text pages.""" from wikkid.view.base import BaseView from wikkid.view.utils import expand_wiki_name class BaseEditView(BaseView): """Base class for editing text.""" name = 'edit' template = 'edit_page' @property def title(self): return 'Editing "%s"' % expand_wiki_name(self.context.base_name) @property def save_url(self): """The link for the cancel button.""" return self.canonical_url(self.context, 'save') @property def cancel_url(self): """The link for the cancel button.""" return self.canonical_url(self.context) python-wikkid_0.4.orig/wikkid/view/missing.py0000644000000000000000000000303411514702505016404 0ustar00# -*- coding: utf-8 -*- # # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """View classes to control the rendering of the content.""" from wikkid.interface.resource import IMissingResource from wikkid.view.base import BaseView from wikkid.view.edit import BaseEditView from wikkid.view.textfile import SaveNewTextContent class BaseMissingView(BaseView): """A base view for +view and +listing. This view just makes the results actual 404s. """ def make_response(self, body): response = super(BaseMissingView, self).make_response(body) response.status = "404 Not Found" return response class MissingPage(BaseMissingView): """A wiki page that does not exist.""" for_interface = IMissingResource name = 'view' is_default = True template = 'missing' @property def content(self): '%s Not found' % self.path class MissingDirectory(BaseMissingView): """A wiki directory that does not exist.""" for_interface = IMissingResource name = 'listing' template = 'missing-dir' @property def content(self): '%s Not found' % self.path class NewWikiPage(BaseEditView): """Show the edit page with no existing content.""" for_interface = IMissingResource @property def rev_id(self): return None @property def content(self): return '' class SaveNewTextFile(SaveNewTextContent): for_interface = IMissingResource python-wikkid_0.4.orig/wikkid/view/root.py0000644000000000000000000000134011755162524015724 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """View classes for the wiki root.""" from webob.exc import HTTPSeeOther from wikkid.interface.resource import IRootResource from wikkid.view.base import BaseView class RootPage(BaseView): """The default view for the root page redirects to the home page.""" for_interface = IRootResource name = 'view' is_default = True def _render(self, skin): """Redirect to Home (or the default page).""" default_resource = self.context.default_resource location = self.canonical_url(default_resource) raise HTTPSeeOther(location=location) python-wikkid_0.4.orig/wikkid/view/sourcetext.py0000644000000000000000000000146011374734156017154 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """View classes to control the rendering of the content.""" from wikkid.formatter.pygmentsformatter import PygmentsFormatter from wikkid.interface.resource import ISourceTextFile from wikkid.view.base import DirectoryBreadcrumbView class SourceTextPage(DirectoryBreadcrumbView): """Any other non-binary file is considered other text. Will be rendered using pygments. """ for_interface = ISourceTextFile name = 'view' is_default = True template = 'view_page' @property def content(self): formatter = PygmentsFormatter() return formatter.format( self.context.base_name, self.context.get_bytes()) python-wikkid_0.4.orig/wikkid/view/textfile.py0000644000000000000000000000543014160200415016552 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """View classes to control the rendering of text pages.""" import logging from webob.exc import HTTPSeeOther from wikkid.filestore import UpdateConflicts from wikkid.interface.resource import ITextFile from wikkid.view.edit import BaseEditView from wikkid.view.wiki import format_content class EditTextFile(BaseEditView): """The page shows the text content in a large edit field.""" for_interface = ITextFile @property def rev_id(self): return self.context.last_modified_in_revision @property def content(self): # We want to pass unicode to the view. byte_string = self.context.get_bytes() try: return byte_string.decode('utf-8') except UnicodeDecodeError: try: return byte_string.decode('latin-1') except UnicodeDecodeError: return byte_string.decode('ascii', 'replace') class SaveNewTextContent(BaseEditView): """Update the text of a file.""" name = 'save' def _render(self, skin): """Save the text file. If it conflicts, render the edit, otherwise render the page (ideally redirect back to the plain page. """ # TODO: barf on a GET # TODO: barf if there is no user. params = self.request.params content = params['content'] description = params['description'] rev_id = ( params['rev-id'].encode('utf-8') if 'rev-id' in params else None) preview = params.get('preview', None) if preview is not None: self.rev_id = rev_id self.description = description self.content = content default_format = self.execution_context.default_format self.preview_content = format_content( content, self.context.base_name, default_format) else: try: self.context.put_bytes( content.encode('utf-8'), self.user.committer_id, rev_id, description) location = self.canonical_url(self.context) raise HTTPSeeOther(location=location) except UpdateConflicts as e: # Show the edit page again. logger = logging.getLogger('wikkid') logger.info('Conflicts detected: \n%r\n', e.content) self.rev_id = e.basis_rev self.content = e.content self.message = "Conflicts detected during merge." self.description = description return super(SaveNewTextContent, self)._render(skin) class UpdateTextFile(SaveNewTextContent): for_interface = ITextFile python-wikkid_0.4.orig/wikkid/view/urls.py0000644000000000000000000000160714160200415015715 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """View URL functions.""" import re VIEW_MATCHER = re.compile(r'^(.*)/\+(\w+)$') def parse_url(path): """Convert a path into a resource path and a view.""" match = VIEW_MATCHER.match(path) if match is not None: resource_path, view = match.groups() if resource_path == '': resource_path = '/' return (resource_path, view) else: return (path, None) def canonical_url(context, request, view=None): """The one true URL for the context object.""" path = context.preferred_path if view is None: return '{0}{1}'.format(request.script_name, path) else: if path == '/': path = '' return '{0}{1}/+{2}'.format(request.script_name, path, view) python-wikkid_0.4.orig/wikkid/view/utils.py0000644000000000000000000000156111372503456016104 0ustar00# # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """Utility methods for wikkid views.""" import re WIKI_PAGE = re.compile('^([A-Z]+[a-z]*)+$') WIKI_PAGE_ELEMENTS = re.compile('([A-Z][a-z]+)') def expand_wiki_name(name): """A wiki name like 'FrontPage' is expanded to 'Front Page'. Names that don't match wiki names are unaltered. """ if WIKI_PAGE.match(name): name_parts = [ part for part in WIKI_PAGE_ELEMENTS.split(name) if part] return ' '.join(name_parts) else: return name def title_for_filename(filename): """Generate a title based on the basename of the file object.""" if filename.endswith('.txt'): return expand_wiki_name(filename[:-4]) else: return expand_wiki_name(filename) python-wikkid_0.4.orig/wikkid/view/wiki.py0000644000000000000000000000363714160200415015700 0ustar00# -*- coding: utf-8 -*- # # Copyright (C) 2010 Wikkid Developers # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). """View classes to control the rendering of wiki pages.""" from webob.exc import HTTPTemporaryRedirect from wikkid.formatter.registry import get_wiki_formatter from wikkid.interface.resource import IWikiTextFile from wikkid.view.base import BaseView def format_content(bytes, base_name, default_format): """ Format the content with the right formatter. Check the first line of the content to see if it specifies a formatter. The default is currently ReST, but we should have it configurable shortly. """ content, formatter = get_wiki_formatter(bytes, default_format) return formatter.format(base_name, content) class WikiPage(BaseView): """A wiki page is a page that is going to be rendered for viewing.""" for_interface = IWikiTextFile name = 'view' is_default = True template = 'view_page' @property def content(self): bytes = self.context.get_bytes() try: text = bytes.decode('utf-8') except UnicodeDecodeError: try: text = bytes.decode('latin-1') except UnicodeDecodeError: text = bytes.decode('ascii', 'replace') default_format = self.execution_context.default_format return format_content(text, self.context.base_name, default_format) def _render(self, skin): """If the page is not being viewed with the preferred path, redirect. For example, /FrontPage.txt will redirect to /FrontPage """ preferred = self.context.preferred_path if self.context.path != preferred: location = self.canonical_url(self.context) raise HTTPTemporaryRedirect(location=location) else: return super(WikiPage, self)._render(skin)