commit d251c5579616743281b3b1abdfc8740efa2329a8 Author: Cyrille L Date: Wed Nov 16 10:47:33 2022 +0100 initial commit diff --git a/src/usr/local/bin/tyto b/src/usr/local/bin/tyto new file mode 100755 index 0000000..3f29b90 --- /dev/null +++ b/src/usr/local/bin/tyto @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# Version: 0.9.0 +# Name: Tyto - Littérateur +# Type: Executable +# Description: Multiple Static Websites generator and manager +# file: tyto +# Folder: /usr/local/bin/ +# By echolib (XMPP: im@echolib.re) +# Repo: https://git.a-lec.org/echolib/tyto.git +# License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 + +#------------ +# funny stats +#------------ +# scripts files: +# app lines: +# app comments: +# app functions: +# lines: +# functions: +# comments: +#---------------------------------------------------------------------- + +''' +All scripts are in /var/lib/tyto/program/ +Directories that must be writeable by Tyto + - /var/lib/tyto/db/ + - /var/log/tyto/ + - /[LOCAL Server]/ (registred per domain) +''' + +#********************************************************************** + +# Import needed libs and tyto's libs +import sys +sys.path.insert(0, '/var/lib/tyto/program') +import check, wip, domain, log + +#=======# +# Tools # +#=======#-------------------------------------------------------------- +#=======================================# +# Check argument from main command line # +# # total arguments # +#---------------------------------------# +def tyto_args(ta): + global file_post, Opt + Domain = False + Opt = '' + Target = '' + + # Start process accoring to first argument action + Actions = { 'check' : check.manage_check, + 'wip' : wip.manage_wip, + 'domain' : domain.manage_domain, + 'log' : log.manage_log + } + + # Dict for Options + Options = { + '-R' : "Remove", 'remove' : "Remove", + '-n' : "New", 'new' : "New", + '-e' : "Edit", 'edit' : "Edit", + '-F' : "Force", 'force' : "Force" + } + + # Set Opt from other arguments options + # Set Target for article or new domain name + if sys.argv[1] == 'domain': Domain = True + + for i in range(2, ta): + if sys.argv[i].endswith('.tyto'): + Target = sys.argv[i] + if Target[0] == '/': + Target = Target[1:len(Target)] + else: + try: + Opt = Options[sys.argv[i]] + except: + if Domain: + Target = sys.argv[i] + else: + print(':( Invalid option "%s"'%sys.argv[i]) + sys.exit(0) + + try: + Actions[sys.argv[1]](Target, Opt) + except KeyError: + print(':( Invalid action "%s"'%sys.argv[1]) + + + + + +#====================# +# MAIN # +# Treat Arguments # +#--------------------#------------------------------------------------- +tyto_args(len(sys.argv)) diff --git a/src/var/lib/tyto/help/CHANGELOG.md b/src/var/lib/tyto/help/CHANGELOG.md new file mode 100644 index 0000000..75c6ac6 --- /dev/null +++ b/src/var/lib/tyto/help/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +Tyto - Littérateur +- Repository: https://git.a-lec.org/echolib/tyto +- Issues: https://git.a-lec.org/echolib/tyto/-/issues +- Changelog: https://git.a-lec.org/echolib/tyto/-/blob/main/CHANGELOG.md +- License: https://git.a-lec.org/echolib/tyto/-/blob/main/LICENSE + +Tyto - Litterateur is a Libre project to create and manage multiple +websites from articles' files. Tyto uses its own syntax to convert +your articles in HTML5 static pages. Tyto works on a GNU/Linux system +and needs minimal dependancies. +- python3 rsync nano gawk curl + +## [0.9.0] First Commit version (pre-final) + diff --git a/src/var/lib/tyto/help/FR_Installation.md b/src/var/lib/tyto/help/FR_Installation.md new file mode 100644 index 0000000..f508aec --- /dev/null +++ b/src/var/lib/tyto/help/FR_Installation.md @@ -0,0 +1,21 @@ +# Installation manuelle + +Si vous utilisez Debian, il est plus que recommandé de procéder à +l'installation par le paquet .deb + +## Préparer les dossiers +```` +sudo mkdir -p /var/lib/tyto /etc/tyto /var/log/tyto +sudo touch /usr/local/bin/tyto +sudo chown -R USER:GROUP /var/lib/tyto /etc/tyto /var/log/tyto +sudo chown USER:GROUP /usr/local/bin/tyto +sudo chmod +x /usr/local/bin/tyto + +git clone https://git.a-lec.org/echolib/tyto +rsync -a folders'repo/ to /folders/ + +# Créer votre dossier pour le serveur web +# On utilise en général /var/www/ +# Celui-ci sera à renseigner lors de la création d'un domaine dans Tyto +sudo mkdir -p /var/www/ +```` diff --git a/src/var/lib/tyto/help/LICENSE b/src/var/lib/tyto/help/LICENSE new file mode 100644 index 0000000..2a1caa8 --- /dev/null +++ b/src/var/lib/tyto/help/LICENSE @@ -0,0 +1,661 @@ + 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. + + GSL Statique Littérateur + Copyright (C) 2022 Libre en Communs / Commissions / Infrastructure + + 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 +. diff --git a/src/var/lib/tyto/help/README.md b/src/var/lib/tyto/help/README.md new file mode 100644 index 0000000..9f49fa4 --- /dev/null +++ b/src/var/lib/tyto/help/README.md @@ -0,0 +1,180 @@ +# STL: Statique Littérateur +STL est une évolution du projet GSL. STL permet de créer un ou plusieurs +sites web/blogs statiques, en fonction de leur nom de domaine. + +Tout comme GSL, STL reprend une grande partie de l'écriture nouvelle de +vos articles, en apportant quelques évolutions. Les articles sont donc +au format .stl. + + +# Fonctionnement de STL ; le dossier de base +Contrairement à GSL, vos articles et les pages de votre site web sont +situés dans le même dossier de base. Pour obtenir ce dossier de base, +et configurer votre domaine, vous devez d'abord vous placer dans le +dossier de votre choix, puis lancer la commande de configuration. + +``` +cd MON-DOSSIER-PREFERE +stl domain new + +# Vous pouvez également pré-remplir votre domaine en fonction de l'URL +stl domain new https://mon-site.xyz +``` + +Dans ce dossier de base (MON-DOSSIER-PREFERE), après la configuration de +votre domaine, vous trouverez de nouveaux dossiers : +- articles/ +- - images/ +- - files/ + +Les sous-dossiers images/ et files/ sont proposés afin de faciliter la +réutilisation d'images et de fichiers (PDF, ou autres) déjà présents +dans un autre article. Lors de la synchronisation, il seront copiés à la +racine wip de votre serveur. Le but : faire en sorte que le +navigateur n'ait pas à recharger un même fichier depuis une URL +différente - ce qui plomberait la rapidité d'affichage de la page et +l'intérêt d'un site statique. L'utilisation de cette fonction dans vos +articles est simple ; voir la documentation expliquant comment écrire un +article dans le dossier help. + +Dans le dossier du serveur (/var/www est un exemple), vous trouverez +les dossiers suivants : +- /var/www/DOMAIN/wip/ +- - template (logos, styles.css, sidebar.html, footer.html, metas.html...) +- - images +- - files +- /var/www/DOMAIN/www/ (non créé pour une installation locale) +- - template/ +- - images +- - files + + +## Installation (server, ou local) +Il est possible d'utiliser STL de 2 manières différentes. Lors de la +configuation d'un nouveau domaine, vous devrez choisir entre une +installation faite sur un serveur distant, et une installation faite +en local (sur votre PC). Vous pouvez bien sûr choisir une installation +de type "server" sur votre PC, si celui-ci sert de serveur web. + +Concernant la décoration du site (styles.css, logos), les fichiers +devront être placés dans le dossier serveur /wip/template/ (créé par STL +lors de l'ajout d'un domaine). + + +### Installation "server" +À choisir si votre PC sert de serveur web, ou si vous avez installé STL +sur votre serveur distant. + +Lorsque vous installez STL, sur votre serveur distant, vous pouvez gérer +pour un même nom de domaine, votre site sur 2 adresses URL différentes. +La version WIP, permet de prévisualiser les modifications apportées à +vos pages (sans modifier celles sur WWW) avant de les valider sur votre +site officiel. + +Lorsque votre site prévisualisé sur WIP vous plaît, vous pouvez alors +synchroniser le dossier WIP sur WWW grâce à la commande : + +``` +# Pour publier un article précis +stl publish (ARTICLE) + +# Pour publier tous les articles dans WIP +stl publish all +``` + +Note : cette commande crée automatiquement le flux RSS dans WIP avant +de tous synchroniser sur WWW. + + +### Installation "local" +À choisir si vous voulez utiliser STL sur votre PC, et synchroniser +vous-même le dossier WIP sur votre serveur distant. Inutile donc, +d'installer STL sur le serveur distant. + +Lors d'une utilisation locale, STL ne crée pas le dossier WWW, et ne +synchronise donc pas le dossier WIP vers WWW. C'est à vous de le faire +(via ssh, par exemple). + +Vous devrez lorsque votre site vous plaît localement lancer la création +du flux RSS - avant de synchroniser votre site local sur votre serveur +distant - avec la commande : + +``` +stl rss +``` + + +# Utilisation de STL +Afin de gérer vos articles, vous devez vous placer dans MON-DOSSIER-PREFERE. +L'autocomplétion est activée et personnalisée pour vous aider à trouver +(ARTICLE.stl). + + +## Créer votre arborescence +Dans MON-DOSSIER-PREFERE, vous trouverez le dossier "articles". Celui-ci +sert de base à votre domain.xyz sur votre serveur. C'est dans ce dossier +"articles", que vous pouvez créer vos articles et vos sous-dossiers. +Il est à noter que le nom de l'article est important, puisque STL +l'utilisera en le transformant en .html. Il est donc recommandé - mais +pas obligatoire - de nommer vos articles index.stl, pour obtenir une page +index.html. Si vous voulez créer une page 404, nommez votre article 404.stl +à la racine du dossier "articles". + + +## Convertir votre article en HTML +Placez vous dans MON-DOSSIER-PREFERE. + +Avant de pouvoir convertir votre article, STL dispose d'un système de +vérification de la syntaxe STL. Si des erreurs sont trouvées, un système +de logs vous aidera à corriger ces erreurs. + +``` +# N'oubliez pas d'utiliser l'autocomplétion +stl check (ARTICLE.stl) +``` + +Vous pouvez maintenant le convertir en HTML dans le dossier wip + +``` +stl wip (ARTICLE.stl) +``` + + +## Utiliser la sidebar +C'est la seule partie du site qui est semi-statique. STL fait appel à +nginx, qui va se charger de traduire la balise HTML ``, +et afficher le rendu du fichier sidebar.html + +Chaque article, une fois convertit avec l'argument wip peut être placé +dans la sidebar à la position désirée, entre 1 et la valeur maximum +décidée, lors de la configuration du domaine. + +``` +stl sidebar add 1 (ARTICLE.stl) +``` + +Vous pouvez également décider de placer un article ausitôt convertit +avec l'argument wip sidebar=POSITON + +``` +stl wip sidebar=2 (ARTICLE.stl) +``` + +## Documentation de STL +Les fichiers de documentations sont dans le dossier help. +Le README est accessible depuis la ligne de commande, comme les arguments +utilisables. + +``` +stl help +stl readme +``` + + +## Dépendances +STL est écrit en bash, et a besoin des utilitaires +- gawk +- rsync +- nano +- curl + diff --git a/src/var/lib/tyto/program/check.py b/src/var/lib/tyto/program/check.py new file mode 100644 index 0000000..2bf064f --- /dev/null +++ b/src/var/lib/tyto/program/check.py @@ -0,0 +1,1024 @@ +#!/usr/bin/env python3 +# Name: Tyto - Littérateur +# Type: Global functions for check +# Description: Check article contents. Create Stats and Database +# file: wip.py +# Folder: /var/lib/tyto/scripts/ +# By echolib (XMPP: im@echolib.re) +# License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 + +#------------ +# funny stats +#------------ +# lines: +# functions: +# comments: +#---------------------------------------------------------------------- + +#********************************************************************** + +# Import needed libs +import sys, os, re, datetime +import domain, log, wip + +Post_Err = False +post_uri = '' +msg_log = '' + +# markers (regex) +m_anchor = r'^>>' # No close marker + +# 'Open' , 'Close' , 'Name' +markers_reg = [ + [ r'^\($|^\(\s' , r'^\)$' , 'paragraphs' ], + [ r'^\(\($|^\(\(\s', r'^\)\)$', 'quotes' ], + [ r'^\[\[$|^\[\[\s', r'^\]\]$', 'brut codes' ], + [ r'^-\($|^-\(\s' , r'^-\)$' , 'lists' ] + ] + +#=======# +# Tools # +#=======#-------------------------------------------------------------- +#===============================# +# Define article IDs, if exists # +# Used for check, wip... # +#-------------------------------# +def post_IDs(file_post): + # Check if argument's file (.tyto) or exit + if not file_post: + print(':( Unused argument file') + sys.exit(1) + + # Set HTML file from file_post + file_html = file_post.replace('.tyto','.html') + + # Check if file exists or exit + global post_uri + post_uri = '%s%s'%(domain.domain_articles, file_post) + if not os.path.exists(post_uri): + print(':( Unused file: %s'%post_uri) + sys.exit(1) + + # From argument file_post + # Set WEB link prefix. Count / in uri + global weburi + slash = 0 + weburi = '' + + for s in file_post: + if s == '/': slash += 1 + + if slash == 0: + weburi = './' + else: + for i in range(0,slash): + weburi = '%s../'%weburi + + # Get Hash from article's content + global hash_chk + hash_chk = get_filesum(post_uri,True) + + # Get Hash ID from URI + global curr_post_ID, curr_post_db, post_logs + curr_post_ID = get_filesum(post_uri,False) + + # Set database file for this article + curr_post_db = '%s%s.conf'%(domain.domain_db, curr_post_ID) + + # Set and check/create logs file for this article + post_logs = '%s%s.log'%(domain.domain_logs, curr_post_ID) + + #------------------ + # Article Database + #------------------ + # Check if Article has DB + # Source its conf and compare + global db_exist + try: + exec(open(curr_post_db).read(),globals()) + db_exist = True + except: + db_exist = False + + if db_exist: + # Set HTML file + global srv_post_wip, srv_post_www + srv_post_wip = [False, domain.srv_wip + file_html] + srv_post_www = [False, domain.srv_www + file_html] + + # Set to True, if file exists on servers + if os.path.exists(srv_post_wip[1]): srv_post_wip[0] = True + if os.path.exists(srv_post_www[1]): srv_post_www[0] = True + +#============================================# +# Check and if not exist, create domain dirs # +# Also Used to create when new domain set # +#--------------------------------------------# +def create_domain_dirs(path_type): + # Needed direcories when creating a new domain + if 'all' in path_type: + all_dirs = [ + domain.srv_wip_template, + domain.srv_wip_images, + domain.srv_wip_files, + domain.srv_www_template, + domain.srv_www_images, + domain.srv_www_files, + domain.domain_articles, + domain.domain_images, + domain.domain_files, + domain.domain_sdb_dir, + domain.domain_db, + domain.domain_logs + ] + for folder in all_dirs: + os.makedirs(folder, exist_ok=True) + + # Check/Create needed directories (only for 'check' process) + if 'db' in path_type: + os.makedirs(domain.domain_db, exist_ok=True) + os.makedirs(domain.domain_logs, exist_ok=True) + +#=======================# +# Return sum of srcfile # +# src: True = file # +# False = string # +#-----------------------# +def get_filesum(path,src): + from hashlib import blake2b + file_sum = blake2b(digest_size=4) + if src: + file_sum.update(open(path, 'rb').read()) + else: + file_sum.update(path.encode()) + return file_sum.hexdigest() + + +#=========================# +# Manage Argument 'check' # +# Start checking article # +#-------------------------#-------------------------------------------- +def manage_check(file_post, Force): + # Set needed IDs + post_IDs(file_post) + + # Start checking Post, then exit if errors found in + check_article(post_uri, Force) + + +#==========================# +# Pre-processing post file # +#--------------------------# +def check_article(post_uri,Force): + global article + # Check needed directories. Create if not exist + create_domain_dirs('db') + + # Create logs + #------------- + if not os.path.exists(post_logs): + file = open(post_logs, "w") + file.write('') + file.close() + msg_log = 'Log > Create logs file for %s in %s\n'%( + post_uri, post_logs) + log.append_f(post_logs,msg_log,0) + + # Article Database + #------------------ + # In check process: no values (kept from db, if exists) + global hash_wip, hash_www, time_wip, time_www + hash_wip = time_wip = hash_www = time_www = '' + if db_exist: + # backup hash_wip/www values + hash_wip = post_wip[0];time_wip = post_wip[1] + hash_www = post_www[0];time_www = post_www[1] + # Compare chk Hashes. + # Pass if Force, and not same + if hash_chk == post_chk[0] and not Force: + print(':) Check was already done, on',post_chk[1]) + sys.exit(0) + + # Processing + #----------- + # Prepare: put post's contents file in headers and article strings + post_to_strings(post_uri) + + # Send to log + msg_log = 'Check > Article: %s. logs: %s'%(post_uri,post_logs) + log.append_f(domain.tyto_logs,msg_log,0) + + # Check markers in headers + check_post_header(headers.rsplit('\n')) + + # Protect bCodes, keep markers for stats, before checking other markers + wip.convert_bcodes(article.rsplit('\n'), + '[[', ']]', + domain.domain_css) + article = wip.article_temp + + # Protect quotes, keep markers for stats, before checking other markers + wip.convert_bcodes(article.rsplit('\n'), + '((', '))', + domain.domain_css) + article = wip.article_temp + + # Protect iCodes, keep markers for stats + wip.convert_icodes(article.rsplit('\n'), domain.domain_css) + article = wip.article_temp + + # Check titles/comments in article + check_article_titles(article.rsplit('\n')) + + # Check links (anchors) + check_links_anchors(article) + + # Check other markers + check_article_markers(article) + + # Error in article + #--------------------- + if Post_Err: + print(':( Invalid article. Needs corrections') + sys.exit(1) + + # No Domain registred yet + #------------------------- + # If in a domain Process to DB + if not domain.domain_active: + print(':/ Article is Ok, but will not be converted') + sys.exit(0) + + # No Error... + #------------ + # Temp post file for this article to use with wip action + # Set + global post_tmp + post_tmp = '%s%s.wip'%(domain.domain_db, curr_post_ID) + + # Create NEW file + file = open(post_tmp, "w") + file.write('') + for line in article.rsplit('\n'): + if len(line) == 0 : continue + elif line.startswith('# ') : continue + elif line.startswith('##') : continue + file.write(line + '\n') + file.close() + + # Create DB + create_DB(curr_post_db) + print(':) Article is Ok and ready to "tyto wip"') + +#=========================================# +# Put file in strings => headers, article # +# But, check if separator is defined... # +#-----------------------------------------# +def post_to_strings(post_uri): + # Check if separator from header and article ; else: exit + file_string = open(post_uri,'r').read() + if not '-----' in file_string: + msg_log = 'Unused separator "-----" (header,post)' + log.append_f(post_logs,msg_log,1) + sys.exit(0) + + # From Separator... + # Split post in 2 strings for Headers and Article + global headers, article, post_lines, headers_ln + + post_lines = file_string.rsplit('\n') + headers = article = '' + post = False + + for line in post_lines: + if line.startswith('-----'): + post = True + continue + + # In post content + if post: + article = '%s\n%s'%(article,line) + + # In post header + else: + headers = '%s\n%s'%(headers,line) + + # Get number lines in headers, min line separator + headers_ln = len(headers.split("\n")) - 1 + +#=================# +# HEADERS CONTENT # +#=================# +#====================================# +# Init stats, arrays... # +# Loop into headers to check markers # +#------------------------------------# +def check_post_header(headers): + global Post_Err + global stats_links_uniq, stats_files_uniq, stats_images_uniq + global stats_links_p, stats_files_p, stats_images_p + global stats_links + global title, about, author, tags, date + + # Set Stats + stats_links_uniq = stats_files_uniq = stats_images_uniq = 0 + stats_links = stats_links_p = stats_files_p = stats_images_p = 0 + + # Set Mandatory marker. 0:True/False 1:ln 2: Content + title = about = author = tags = date = ('','','') + + # Set Optional markers. 0:ln 1:Name 2:URL 3:Alt + link = file = image = ('','','','') + + #----------------------- + # Loop in headers string + #----------------------- + for ln, line in enumerate(headers, 1): + + #----------------------- + # Set mandatory markers + #----------------------- + if line.startswith('title:'): + title = (True,ln,line.split('title:')[1].lstrip()) + if line.startswith('about:'): + about = (True,ln,line.split('about:')[1].lstrip()) + if line.startswith('author:'): + author = (True,ln,line.split('author:')[1].lstrip()) + if line.startswith('tags:'): + tags = (True,ln,line.split('tags:')[1].lstrip()) + if line.startswith('date:'): + date = (True,ln,line.split('date:')[1].lstrip()) + + #---------------------- + # Set optional markers + #---------------------- + # links: + if line.startswith('link:'): + # Create variable array + stats_links_uniq += 1 + check_links(line, ln, stats_links_uniq) + # files: + if line.startswith('file:'): + # Create variable array + stats_files_uniq += 1 + check_files(line, ln, stats_files_uniq) + # images: + if line.startswith('image:'): + # Create variable array + stats_images_uniq += 1 + check_images(line, ln, stats_images_uniq) + + #------------------------------- + # Check valid Mandatory markers + #------------------------------- + if_mandat_marker('title:', title) + if_mandat_marker('about:', about) + if_mandat_marker('author:', author) + if_mandat_marker('tags:', tags) + if_mandat_marker('date:', date) + if date: + check_date(date) + + #------------------------------ + # Check valid Opitonal markers + #------------------------------ + + +#===================# +# MANDATORY Markers # +#===================#-------------------------------------------------- +#=====================================# +# Check if marker is used and defined # +#-------------------------------------# +def if_mandat_marker(marker, m_in): + global Post_Err + + if not m_in[0]: + msg_log = 'Unused needed marker "%s"'%marker + log.append_f(post_logs,msg_log,1) + Post_Err = True + elif not m_in[2]: + msg_log = 'Line %s. Undefined marker "%s"'%( + m_in[1], marker + ) + log.append_f(post_logs,msg_log,1) + +#================================# +# Check Date format and validity # +# Create False date_check # +#--------------------------------# +def check_date(date): + from datetime import datetime + from time import gmtime, strftime + import time + global Post_Err, date_check + + # Check if article date is valid (True) + fmt_article = "%Y-%m-%d" + + try: + bool(datetime.strptime(date[2], fmt_article)) + except ValueError: + Post_Err = True + msg_log = 'Line %s. Invalid date: %s'%(date[1],date[2]) + log.append_f(post_logs,msg_log,1) + + # Create date_check (epoch) from article's Date + now TIME + if not Post_Err: + fmt_check = '%Y-%m-%d %H:%M:%S' + time_check = strftime("%H:%M:%S", gmtime()) + date_check = date[2] + ' ' + time_check + date_check = time.mktime(time.strptime(date_check,fmt_check)) + + +#==================# +# OPTIONAL Markers # +#==================#--------------------------------------------------- +#===============================# +# Statistics # +# Return term number in article # +#-------------------------------# +def stats_counter(term): + term = r"%s"%term + return(sum(1 for match in re.finditer(r'\b%s\b'%term, article))) + +#============================ +# Check optional marker set # +# Called from # +# - check_links # +# - check_files # +# - check_images # +#---------------------------# +def if_option_marker(marker, m_in): + global Post_Err + + if not m_in[1]: + msg_log = 'Line %s. Unused NAME for marker "%s"'%( + m_in[0], marker + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + + if not m_in[2]: + msg_log = 'Line %s. Unused URL for marker "%s"'%( + m_in[0]+1, marker + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + + if not m_in[3]: + msg_log = 'Line %s. Unused Alt-Text for marker "%s"'%( + m_in[0]+2, marker + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + +#=================================# +# Check every marker "links:" # +# For line, from loop header file # +# Also, create Stats # +#---------------------------------# +def check_links(line, ln, stats_links_uniq): + global Post_Err, stats_links_p + + # Create variable array + link_nbr = 'link_%s'%stats_links_uniq + link_name = line.split('link:')[1].lstrip() + link_url = headers.rsplit('\n')[ln].lstrip() + link_alt = headers.rsplit('\n')[ln+1].lstrip() + + link = ( + ln, + link_name, + link_url, + link_alt + ) + + # Set/Check values to check in header + globals()[link_nbr] = link + if_option_marker('link:', globals()[link_nbr]) + + # Check NAME in article + link_page = '_%s'%link_name + if not link_page in article: + msg_log = 'Unused "%s" for marker "link:" in article"'%link_page + log.append_f(post_logs,msg_log,1) + Post_Err = True + + if Post_Err: return + # Set final marker_N + link = ( + '%s'%link_name, + link_url, + link_alt + ) + globals()[link_nbr] = link + + # Stats: count occurence + stats_links_p = stats_counter(link_page) + +#=================================# +# Check every marker "files:" # +# For line, from loop header file # +# Also, create Stats # +#---------------------------------# +def check_files(line, ln, stats_files_uniq): + global Post_Err + global stats_files_p + + # Create variable array + file_nbr = 'file_%s'%stats_files_uniq + file_name = line.split('file:')[1].lstrip() + file_uri = headers.rsplit('\n')[ln].lstrip() + file_alt = headers.rsplit('\n')[ln+1].lstrip() + + file = ( + ln, + file_name, + file_uri, + file_alt + ) + + # Set/Check values to check in header + globals()[file_nbr] = file + if_option_marker('file:', globals()[file_nbr]) + + # Check NAME in article + file_page = '__%s'%file_name + if not file_page in article.rsplit('\n'): + msg_log = 'Unused "%s" for marker "file:" in article"'%file_page + log.append_f(post_logs,msg_log,1) + Post_Err = True + + # Check URI value (exists and where) + # In Generic folder /files/ + if file_uri.startswith('@'): + file_uri = file_uri.replace('@','') + gen_file = '%s%s'%(domain.domain_files,file_uri) + if not os.path.exists(gen_file): + msg_log = 'Unused file for marker "file:" in %s'%gen_file + log.append_f(post_logs,msg_log,1) + Post_Err = True + else: + file_uri = '/files/%s'%file_uri + + # From Root articles (www/ in web) + elif file_uri.startswith('/'): + file_uri = file_uri[1:len(file_uri)] # No need first / to check + usr_file = '%s%s'%(domain.domain_articles,file_uri) + if not os.path.exists(usr_file): + msg_log = 'Unused file for marker "file:" in %s'%usr_file + log.append_f(post_logs,msg_log,1) + Post_Err = True + else: + file_uri = '/%s'%(file_uri) + + # Current or curstom URI + else: + usr_file = '%s%s'%(domain.domain_articles,file_uri) + if not os.path.exists(usr_file): + msg_log = 'Unused file for marker "file:" in %s'%usr_file + log.append_f(post_logs,msg_log,1) + Post_Err = True + + if Post_Err: return + #-------------------- + # Set final marker_N + #-------------------- + file = ( + '%s'%file_name, + file_uri, + file_alt + ) + globals()[file_nbr] = file + + # Stats: count occurence + stats_files_p = stats_counter(file_page) + +#=================================# +# Check every marker "images:" # +# For line, from loop header file # +# Also, create Stats # +#---------------------------------# +def check_images(line, ln, stats_images_uniq): + global Post_Err + global stats_images_p + + # Create variable array + image_nbr = 'image_%s'%stats_images_uniq + image_name = line.split('image:')[1].lstrip() + image_uri = headers.rsplit('\n')[ln].lstrip() + image_alt = headers.rsplit('\n')[ln+1].lstrip() + + image = ( + ln, + image_name, + image_uri, + image_alt + ) + + # Set/Check values to check in header + globals()[image_nbr] = image + if_option_marker('image:', globals()[image_nbr]) + + # Check value in article + image_page = '_image:%s'%image_name + if not image_page in article.rsplit('\n'): + msg_log = 'Unused "%s" for marker "image:" in article"'%image_page + log.append_f(post_logs,msg_log,1) + Post_Err = True + + # Check URI value (exists and where) + # Set HTML value in DB + if image_uri.startswith('@'): + image_uri = image_uri.replace('@','') + gen_image = '%s%s'%(domain.domain_images,image_uri) + if not os.path.exists(gen_image): + msg_log = 'Unused file for marker "imagee:" in %s'%gen_image + log.append_f(post_logs,msg_log,1) + Post_Err = True + else: + image_uri = '/images/%s'%image_uri + + # From Root articles (www/ in web) + elif image_uri.startswith('/'): + image_uri = image_uri[1:len(image_uri)] # No need first / to check + usr_file = '%s%s'%(domain.domain_articles,image_uri) + if not os.path.exists(usr_file): + msg_log = 'Unused file for marker "image:" in %s'%usr_file + log.append_f(post_logs,msg_log,1) + Post_Err = True + else: + image_uri = '/%s'%(image_uri) + + # Current or curstom URI + else: + usr_file = '%s%s'%(domain.domain_articles,image_uri) + if not os.path.exists(usr_file): + msg_log = 'Unused file for marker "file:" in %s'%usr_file + log.append_f(post_logs,msg_log,1) + Post_Err = True + + if Post_Err: return + #-------------------- + # Set final marker_N + #-------------------- + image = ( + '%s'%image_name, + image_uri, + image_alt + ) + globals()[image_nbr] = image + + # Stats: count occurence + stats_images_p = stats_counter(image_page) + + +#=================# +# ARTICLE CONTENT # +#=================# +#=====================================# +# Main for all markers to check # +# Loop into header post, till '-----' # +#-------------------------------------# +def check_article_markers(article): + global Post_Err + global stats_p, stats_bcodes, stats_anchors, stats_quotes + global stats_lists, stats_lists_u, stats_lists_o + global stats_titles, stats_comments + + stats_p = stats_pe = stats_qe = stats_le = 0 + stats_lists = stats_lists_u = stats_lists_o = 0 + stats_anchors = stats_quotes = 0 + precode = False # Do not treat line if in precode: (( + + #------------------------ + # Loop lines from article + #------------------------ + for ln, line in enumerate(article.rsplit('\n'), 1): + + # Do not check if in precode: [[ and ]] + if re.match(markers_reg[2][0], line): precode = True + elif re.match(markers_reg[2][1], line): precode = False + if precode: continue + + #------------------------- + # Markers at begining line + #------------------------- + # Paragraphs: ( and ) + if re.match(markers_reg[0][0], line): stats_p += 1 + elif re.match(markers_reg[0][1], line): stats_pe += 1 + + # Lists: (- and -) ; count items with = and + at begining + elif re.match(markers_reg[3][0], line): stats_lists += 1 + elif re.match(markers_reg[3][1], line): stats_le += 1 + elif line.startswith('='): stats_lists_u +=1 + elif line.startswith('+'): stats_lists_o +=1 + + # Anchors: <: + elif re.match(m_anchor, line): + try : css = line.split(' ')[1] + except: css = '' + if not css: + msg_log = 'Line %s. Unused anchor ID: ">> ID"'%(ln+headers_ln) + log.append_f(post_logs,msg_log,1) + Post_Err = True + else: + stats_anchors += 1 + + # Quotes + elif re.match(markers_reg[1][0], line): stats_quotes += 1 + elif re.match(markers_reg[1][1], line): stats_qe += 1 + + # Check if referenced in header for markers + for marker_p in '_image:', '_code:', '_brut:': + if re.match(r'^%s'%marker_p, line): + m_name = line.split(':')[1][0] + marker_h = marker_p[1:len(marker_p)] + if not re.findall(r'%s\s+%s'%(marker_h, m_name), headers): + msg_log = 'Line %s. Unused marker "%s %s" in header'%( + ln+headers_ln, marker_h, m_name + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + + #------------------------------ + # Check valid contents markers + #------------------------------ + # Paragraphs + if stats_p != stats_pe: + msg_log = 'Unpaired paragraph markers: %s "(" and %s ")"'%( + stats_p, stats_pe + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + + # Precodes + if stats_bcodes != stats_ce: + msg_log = 'Unpaired precode markers: %s "[[" and %s "]]"'%( + stats_bcodes, stats_ce + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + + # lists + if stats_lists != stats_le: + msg_log = 'Unpaired list markers: %s "-(" and %s "-)"'%( + stats_lists, stats_le + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + + # Quotes + if stats_quotes != stats_qe: + msg_log = 'Unpaired quote markers: %s "((" and %s "))"'%( + stats_quotes, stats_qe + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + + #------------------------------------ + # Markers in text (strongs, bolds...) + #------------------------------------ + global m_stats + # Markers around words + m_words = ('*_', '_*', + '+_', '_+', + '/_', '_/', + '~_', '_~', + '×_', '_×', + '-_', '_-', + '(_', '_)', + '<_', '_>', + '\\_', '_\\', + '=_', '_=', + '>_', '_<' + ) + # Init words markers statistics + m_stats = ['0', '0', + '0', '0', + '0', '0', + '0', '0', + '0', '0', + '0', '0', + '0', '0', + '0', '0', + '0', '0', + '0', '0', + '0', '0' + ] + # markers Names (Only used for logs) + m_names = ('strong', + 'bold', + 'emphasis', + 'deletion', + 'custom', + 'underline', + 'iCode', + 'iCode', + 'italics', + 'cite', + 'link anchor' + ) + + # Count markers, get, check and set stats. + pos = pos_name = 0 + for marker in m_words: + m_stats[pos] = article.count(marker) + #print(marker, m_stats) + if pos % 2 != 0: + if m_stats[pos-1] != m_stats[pos]: + msg_log = 'Unpaired %s markers %s "%s" and %s "%s"'%( + m_names[pos_name], + m_stats[pos-1], m_words[pos-1], + m_stats[pos], m_words[pos] + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + pos_name += 1 + pos += 1 + # Add specific alternative iCode '<_','_>' to legacy iCode stats + m_stats[12] = m_stats[12] + m_stats[14] + +#======================================================# +# Thses markers needs ref line, in case invalid # +# As bCodes needs convertion, they must be check first # +# Done after post_to_strings # +# Also count stats: bCodes, titles, comments # +#------------------------------------------------------# +def check_article_titles(article): + global Post_Err + global stats_bcodes, stats_ce + global stats_comments, stats_titles + + stats_bcodes = stats_ce = 0 + stats_comments = stats_titles = 0 + precode = False + + #------------------------ + # Loop lines from article + #------------------------ + for ln, line in enumerate(article, 1): + + # Do not check if in precode: [[ and ]] + if re.match(markers_reg[2][0], line): + precode = True + stats_bcodes += 1 + elif re.match(markers_reg[2][1], line): + precode = False + stats_ce += 1 + if precode: continue + + # Titles + if line.startswith(r'#'): + Title_Err = False + title = line.split(' ', 1) + ht = line[1] + + if ht == ' ' or ht == '#': + stats_comments += 1 + continue + + if ht.isnumeric() and not int(ht) in range(1,7): + msg_log = 'Line %s. Mismatch title number "%s" (1-6)'%( + ln + headers_ln, ht) + log.append_f(post_logs,msg_log,1) + Post_Err = True + continue + try: + title[1] + if title[1] == ' ' or title[1] == '': + Title_Err = True + else: + stats_titles += 1 + except: + Title_Err = True + + if Title_Err: + msg_log = 'Line %s. Unused title description "%s ??"'%( + ln + headers_ln, title[0] + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + + +#===========================# +# Check links anchors # +# (if '>> ID' is registred) # +#---------------------------# +def check_links_anchors(article): + global Post_Err + + #for ln, line in enumerate(article.rsplit('\n'), 1): + anchors_link = re.findall(r'\>_(.*?)_\<', article) + for anchor in anchors_link: + print('> Anchor found: %s'%(anchor)) + anchor_id = anchor.rsplit(':',1)[0] + if not re.search(r'\>\> %s'%anchor_id, article): + msg_log = 'Unused anchor ID ">> %s" from link "%s"'%( + anchor_id, anchor + ) + log.append_f(post_logs,msg_log,1) + Post_Err = True + Post_Err = True + + +#====================================# +# Create Database file for this Post # +#------------------------------------# +def create_DB(post_db): + # Create NEW database for article + file = open(post_db, "w") + file.write('') + file.close() + + # time of check at creating DB + global time_chk + time_chk = log.nowdate() + + # Specific statistics for links (add all) + stats_links = stats_links_p + stats_files_p + stats_images_p + + # log + msg_log = 'Create Database: %s'%post_db + log.append_f(post_logs,msg_log,0) + + # Main Post Conf + lines_conf = ( + '# Domain', + 'post_domain = "%s"'%domain.domain_name, + '\n# Metas & URIs', + 'post_file = "%s"'%post_uri, + 'post_ID = "%s"'%curr_post_ID, + 'post_db = "%s"'%post_db, + 'post_tmp = "%s"'%post_tmp, + '\n# Article Status', + 'post_chk = ("%s","%s")'%(hash_chk,time_chk), + 'post_wip = ("%s","%s")'%(hash_wip,time_wip), + 'post_www = ("%s","%s")'%(hash_www,time_www), + '\n# Mandatory Headers', + 'post_title = "%s"'%title[2], + 'post_about = "%s"'%about[2], + 'post_tags = "%s"'%tags[2], + 'post_date = "%s"'%date[2], + 'post_check = "%s" # └+CheckTime'%date_check, + 'post_author = "%s"'%author[2], + '\n# Optional Headers' + ) + for line_conf in lines_conf: + domain.append_f(post_db, line_conf) + + # Optional headers to Post conf + # Add every links: array found to DB, one per line + if stats_links_uniq > 0: + for n in range(1,stats_links_uniq+1): + m = 'link_%s'%n + domain.append_f(post_db,'%s = %s'%(m,globals()[m])) + + # Add every files: array found to DB, one per line + if stats_files_uniq > 0: + for n in range(1,stats_files_uniq+1): + m = 'file_%s'%n + domain.append_f(post_db,'%s = %s'%(m,globals()[m])) + + # Add every images: array found to DB, one per line + if stats_images_uniq > 0: + for n in range(1,stats_images_uniq+1): + m = 'image_%s'%n + domain.append_f(post_db,'%s = %s'%(m,globals()[m])) + + # Statistics Post conf + lines_conf = '' + lines_conf = ( + '\n# Statistics (Uniq)', + 'links_u = %d'%stats_links_uniq, + 'files_u = %d'%stats_files_uniq, + 'images_u = %d'%stats_images_uniq, + '\n# Statistics (Wordings)', + 'strongs = %d'%m_stats[0], + 'bolds = %d'%m_stats[2], + 'emphasis = %d'%m_stats[4], + 'italics = %d'%m_stats[16], + 'cites = %d'%m_stats[18], + 'deletions = %d'%m_stats[6], + 'customs = %d'%m_stats[8], + 'underlines = %d'%m_stats[10], + 'icodes = %d'%m_stats[12], + '\n# Statistics (Links)', + 'links = %d'%stats_links, + 'links_p = %d'%stats_links_p, + 'files_p = %d'%stats_files_p, + 'images_p = %d'%stats_images_p, + '\n# Statistics (Templates)', + 'titles = %d'%stats_titles, + 'anchors = %d'%stats_anchors, + 'paragraphs = %d'%stats_p, + 'quotes = %d'%stats_quotes, + 'lists = %d'%stats_lists, + 'lists_u = %d'%stats_lists_u, + 'lists_o = %d'%stats_lists_o, + 'precodes = %d'%stats_bcodes, + 'comments = %d'%stats_comments + ) + for line_conf in lines_conf: + domain.append_f(post_db, line_conf) diff --git a/src/var/lib/tyto/program/domain.py b/src/var/lib/tyto/program/domain.py new file mode 100644 index 0000000..b5a0aed --- /dev/null +++ b/src/var/lib/tyto/program/domain.py @@ -0,0 +1,540 @@ +#!/usr/bin/env python3 +# Name: Tyto - Littérateur +# Type: Global functions for domain +# Description: Add new domain, check domain dir... +# file: domain.py +# Folder: /var/lib/tyto/scripts/ +# By echolib (XMPP: im@echolib.re) +# License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 + +#------------ +# funny stats +#------------ +# lines: +# functions: +# comments: +#---------------------------------------------------------------------- + +#********************************************************************** + +#======# +# MAIN # +#======# +import sys, os, subprocess, datetime +import check, log + +# In Tyto DB dir +tyto_db = '/var/lib/tyto/db/' +tyto_domains = '%sdomains.conf'%tyto_db + +# In Tyto log dir +tyto_logs_dir = '/var/log/tyto/' +tyto_logs = '%styto.log'%tyto_logs_dir + +# Current dir +curr_dir = domain_articles = domain_logs = os.getcwd() + +# set domain configuration file from current directory +conf_domain = '%s/tyto_domain.conf'%curr_dir + +# Activation domain +domain_active = domain_conf = False + +# Set needed directories + +# Create Tyto logs +if not os.path.exists(tyto_logs): + file = open(tyto_logs, "w") + file.write('') + file.close() + msg_log = 'Log > Create logs file for Tyto in %s\n'%tyto_logs + log.append_f(tyto_logs,msg_log,0) + +# Get database domains +# If not exists, create the file conf +# Will receive all domain registred +try: + exec(open(tyto_domains).read()) +except: + domains_file = open(tyto_domains, 'w') + domains_file.write("# Tyto's file with all activated domains\n") + domains_file.close() + +domains_db = open(tyto_domains,'r').read() + +# Get user domain configuration file # +# If exists, set to True, and exec +try: # os.path.exists(conf_domain): + exec(open(conf_domain).read()) + datas_domain = open(conf_domain, "r").read() + if domain_active: + print(':) Activated domain: "%s"'%domain_name) + else: + print(':/ Not activated domain in',conf_domain) +except: + print(':( Unused domain file:', conf_domain) + +#=======# +# Tools # +#=======#-------------------------------------------------------------- +#============================# +# Append new value to a file # +#----------------------------# +def append_f(f,line_val): + file = open(f, "a") + file.write('%s\n'%line_val) + +#======================================# +# Just a generic exit # +# out defines message, not exit status # +# for process form: " +# - rename temp domain conf to legacy # +#--------------------------------------# +def exiting(process,out,msg): + msgs = [ + '\n:) All done !', + '\n:/ Maybe next time...', + '%s'%msg + ] + + if process == 'form': + os.rename(temp_domain, conf_domain) + + print(msgs[out]) + sys.exit(0) + + +#==========================# +# Manage Argument 'domain' # +#==========================#------------------------------------------- +def manage_domain(Domain, Opt): + if not Opt: + try: + # No option: get domain and print it + print('\n',datas_domain) + except: + sys.exit(0) + + elif Opt == 'New': + try: + # Domain NAME is defined in CLI + set_domain = Domain + except: + set_domain = '' + + add_domain(set_domain) + + elif Opt == 'Edit' or '-E': + if domain_conf: + print(':> Editing',domain_conf) + old_conf_ID = check.get_filesum(domain_conf, True) + edit_domain = subprocess.run(['/usr/bin/nano', + '--linenumbers', + domain_conf]) + + # Compare before and after domain conf file + new_conf_ID = check.get_filesum(domain_conf,True) + if not old_conf_ID == new_conf_ID: + exiting('root',2,':) Updated domain configuration file.') + else: + exiting('root',2,':) Unchanged domain configuration file.') + else: + sys.exit(0) + +#===================================# +# Main fonction to add a new domain # +# Check first, if it already exists # +# Domain not activated: # +# - Show registred values in form # +#-----------------------------------#---------------------------------- +def add_domain(set_domain): + # Exit if a domain already exists + if domain_active: + sys.exit(0) + + global temp_domain + temp_domain = '%s/tyto_domain.tmp'%curr_dir + + print('\n' + ' ┌──────────────────────────────────────────────┐\n' + ' │ Configure a new domain for current directory │\n' + ' │ Answer Y = yes. Default value = (default) │\n' + ' │ Empty Answer cancel process, except for │\n' + ' │ - "Optional" │\n' + ' │ - "(default) value │\n' + ' ├──────────────────────────────────────────────┘' + ) + + # Create new temp conf file + # Used to prepare values, leaving legacy conf file + file = open(temp_domain, "w") + file.write('# Tyto Configuration Domain\n') + file.close() + + # Domain is or not given in CLI. + # Start form + if set_domain: + domain_input_confirn(set_domain) + domain_form() + else: + domain_input() + domain_form() + + # End of form. + # Show resume's domain configuration from temp conf file + print('\n' + ' ┌─────────────────────────────┐\n' + ' │ Please check domain datas...│\n' + ' ├─────────────────────────────┘' + ) + with open(temp_domain, "r") as file: + post_temp = file.read() + for line in post_temp.split('\n'): + print(' │ %s'%line) + + # Ask to confirm to write activation domain + print(' ├─────────────────────────────') + confirm = input(' └ Activate domain configuration ? ') + if confirm in ['y', 'Y']: + create_domain() + else: + exiting('form',1,'') + +#==============================# +# If domain name is set in CLI # +# Confirm process # +#------------------------------# +def domain_input_confirn(set_domain): + global domain_name + confirm = input(' ├ Add Domain (%s) here ? '%set_domain) + if confirm in ['y', 'Y']: + # Check if domain already registred + isdomain = set_domain.replace('.','_') + if isdomain in domains_db: + dom_folder = globals().get(isdomain,False) + if dom_folder and not dom_folder == curr_dir: + exiting('root',2,'\n:/ Domain exists in %s'%dom_folder) + domain_name = set_domain + else: + exiting('root',1,'') + +#=====================# +# Add new domain_name # +#---------------------# +def domain_input(): + global domain_name + set_domain = input(' ├ Enter Domain Name: ') + if not set_domain: + exiting('root',1,'') + else: + domain_name = set_domain + +#====================# +# Domain FORM # +# domain_name is set # +# Configure domain # +#--------------------# +def domain_form(): + + # First settings to put in temp_domain config file + domain_db = '%s/%s/articles/'%(tyto_db,domain_name) + domain_logs = '%s%s/'%(tyto_logs_dir,domain_name) + domain_articles = '%s/articles/'%curr_dir + domain_images = '%simages/'%domain_articles + domain_files = '%sfiles/'%domain_articles + + # Add settings from domain name before starting form + append_f(temp_domain,'domain_name = "%s"'%domain_name) + append_f(temp_domain,'domain_conf = "%s"'%conf_domain) + append_f(temp_domain,'domain_db = "%s"'%domain_db) + append_f(temp_domain,'domain_logs = "%s"'%domain_logs) + append_f(temp_domain,'\n# Article directories') + append_f(temp_domain,'domain_dir = "%s"'%curr_dir) + append_f(temp_domain,'domain_articles = "%s"'%domain_articles) + append_f(temp_domain,'domain_files = "%s"'%domain_files) + append_f(temp_domain,'domain_images = "%s"'%domain_images) + + # ----------------------- # + # Starting Form # + # Some values are defaut # + # Values can be registred # + # from legacy conf file # + # ----------------------- # + + # Local server Directory + # ---------------------- + global srv + try: + srv + except: + srv = '/var/www' + + set_srv = input(' ├ Local server directory (%s) ? '%srv) + if not set_srv and not srv: + exiting('form',1,'') + if set_srv and set_srv[-1] == '/': + srv = set_srv[:-1] + + if not os.path.exists(srv): + exiting('form',2,'\n:( Unsed directory "%s"'%srv) + + # Settings for server + srv_domain = '%s/%s/'%(srv,domain_name) + srv_wip = '%s/%s/wip/'%(srv,domain_name) + srv_wip_files = '%s/%s/wip/files/'%(srv,domain_name) + srv_wip_images = '%s/%s/wip/images/'%(srv,domain_name) + srv_wip_template = '%s/%s/wip/template/'%(srv,domain_name) + srv_www = '%s/%s/www/'%(srv,domain_name) + srv_www_files = '%s/%s/www/files/'%(srv,domain_name) + srv_www_images = '%s/%s/www/images/'%(srv,domain_name) + srv_www_template = '%s/%s/www/template/'%(srv,domain_name) + # Write settings to temp_omain + append_f(temp_domain,'\n# Server directories') + append_f(temp_domain,'srv = "%s"'%srv) + append_f(temp_domain,'srv_domain = "%s"'%srv_domain) + append_f(temp_domain,'srv_wip = "%s"'%srv_wip) + append_f(temp_domain,'srv_wip_files = "%s"'%srv_wip_files) + append_f(temp_domain,'srv_wip_images = "%s"'%srv_wip_images) + append_f(temp_domain,'srv_wip_template = "%s"'%srv_wip_template) + append_f(temp_domain,'srv_www = "%s"'%srv_www) + append_f(temp_domain,'srv_www_files = "%s"'%srv_www_files) + append_f(temp_domain,'srv_www_images = "%s"'%srv_www_images) + append_f(temp_domain,'srv_www_template = "%s"'%srv_www_template) + + # Domain Title for website + # ------------------------ + global domain_title + try: + domain_title + show_title = domain_title[:14] + '...' + except: + domain_title = show_title = '' + + set_title = input(' ├ Domain Title (%s) ? '%show_title) + if not set_title and not domain_title: + exiting('form',1,'') + if set_title: + domain_title = set_title + if '"' in domain_title: + domain_title = domain_title.replace('"','\\"') + + append_f(temp_domain,'\n# Domain datas for web pages') + append_f(temp_domain,'domain_title = "%s"'%domain_title) + + # Separator Pages Titles (default '-') + # ------------------------------------ + global sep_titles + try: + sep_titles + except: + sep_titles = '-' + + set_sep = input(' ├ Website pages separator (%s) ? '%sep_titles) + if set_sep: + if len(set_sep) > 2: + exiting('form',2,'\n:( Seperator is 2 characters max') + sep_titles = set_sep + + append_f(temp_domain,'sep_titles = "%s"'%sep_titles) + + # Domain description + # ------------------ + global domain_about + try: + domain_about + show_about = domain_about[:14] + '...' + except: + domain_about = show_about = '' + + set_about = input(' ├ Domain description (%s) ? '%show_about) + if not set_about and not domain_about: + exiting('form',1,'') + if set_about: + domain_about = set_about + if '"' in domain_about: + domain_about = domain_about.replace('"','\\"') + + append_f(temp_domain,'domain_about = "%s"'%domain_about) + + # Lang for HTML Pages + # ------------------- + global domain_lang + try: + domain_lang + except: + # Get default system language (2/3 chars for HTML) + import locale + domain_lang = locale.getdefaultlocale()[0].split('_')[0] + + set_lang = input(' ├ Website HTML lang (%s) ? '%domain_lang) + if set_lang: + if len(set_lang) > 3: + exiting('form',2,'\n:( HTML Lang is 3 characters max') + domain_lang = set_lang + + append_f(temp_domain,'domain_lang = "%s"'%domain_lang) + + # Domain CSS (prefix class). alphanum only + # ---------------------------------------- + global domain_css + try: + domain_css + except: + domain_css = 'tyto' + + set_css = input(' ├ Generic CSS class (%s) ? '%domain_css) + if set_css: + domain_css = set_css + if not domain_css.isalnum(): + css_alnum = ''.join(c for c in domain_css if c.isalnum()) + domain_css = css_alnum + + append_f(temp_domain,'domain_css = "%s"'%domain_css) + + # Domain mail + # ----------- + global domain_mail + try: + domain_mail + show_mail = domain_mail[:14] + '...' + except: + domain_mail = show_mail = '' + + set_mail = input(' ├ Contact admin mail (%s) ? '%show_mail) + if not set_mail and not domain_mail: + exiting('form',1,'') + if set_mail: + if not '@' and not '.' in set_mail: + exiting('form',2,'\n:( Invalid mail format (x@x.x)') + domain_mail = set_mail + elif not domain_mail: + exiting('form',2,'\n:( Mail is required.') + + append_f(temp_domain,'domain_mail = "%s"'%domain_mail) + + # Domain Tags + # ----------- + global domain_tags + try: + domain_tags + show_tags = domain_tags[:14] + '...' + except: + domain_tags = show_tags = '' + + set_tags = input(' ├ Domain Tags [x,y] (%s) ? '%show_tags) + if not set_tags and not domain_tags: + exiting('form',1,'') + if set_tags: + domain_tags = set_tags + + append_f(temp_domain,'domain_tags = "%s"'%domain_tags) + + # Webpages Copyright + # ------------------ + global domain_license + try: + domain_license + show_license = domain_license[:14] + '...' + except: + domain_license = show_license = 'CC BY-NC-SA' + + set_license = input(' ├ Website copyright (%s) ? '%show_license) + if set_license: + domain_license = set_license + if '"' in domain_license: + domain_license = domain_license.replace('"','\\"') + + append_f(temp_domain,'domain_license = "%s"'%domain_license) + + # Sidebar Title + # ------------- + global sidebar_title + try: + sidebar_title + show_st = sidebar_title[:14] + '...' + except: + if 'fr' in domain_lang: + sidebar_title = show_st = "À l'affiche !" + else: + sidebar_title = show_st = "Featured !" + + set_st = input(' ├ Sidebar title (%s) ? '%show_st) + if set_st: + sidebar_title = set_st + if '"' in sidebar_title: + sidebar_title = sidebar_title.replace('"','\\"') + + # Sidbar items number. Default: 12 + # ------------------- + global sidebar_items + try: + sidebar_items + except: + sidebar_items = 8 + + set_si = input(' ├ Sidebar max items [1-16] (%s) ? '%sidebar_items) + if set_si and set_si.isdigit(): + if set_si in range(1,16): + exiting('form',2,'\n:( Items number: 1-16') + sidebar_items = set_si + + # Domain LOGO (optional) + # ---------------------- + global domain_logo + try: + domain_logo + show_logo = domain_logo[:14] + '...' + except: + domain_logo = show_logo = '' + + set_logo = input(' ├ Optional. Logo filename (%s) ? '%show_logo) + if set_logo: + domain_logo = set_logo + append_f(temp_domain,'domain_logo = "%s"'%domain_logo) + + # External URL profile (optional) + # ------------------------------- + global domain_exturl + try: + domain_exturl + show_e= domain_exturl[:14] + '...' + except: + domain_exturl = show_e = '' + + set_e = input(' └ Optionnal. URL to a social network (%s) ? '%show_e) + if set_e: + domain_exturl = set_e + + append_f(temp_domain,'domain_exturl = "%s"'%domain_exturl) + + # Adding sidebar conf + sidebar_dir = '%s/articles/sidebar/'%curr_dir + sidebar_load = '%styto.sidebar'%sidebar_dir + append_f(temp_domain,'\n# Sidebar') + append_f(temp_domain,'domain_sdb_dir = "%s"'%sidebar_dir) + append_f(temp_domain,'domain_sdb_load = "%s"'%sidebar_load) + append_f(temp_domain,'domain_sdb_title = "%s"'%sidebar_title) + append_f(temp_domain,'domain_sdb_items = "%s"'%sidebar_items) + +#==========================# +# If confirm activation # +# Add value to domain conf # +# Create all directories # +#--------------------------# +def create_domain(): + # Add activation var to temp domain conf + append_f(temp_domain,'\n# Domain activation') + append_f(temp_domain,'domain_active = True') + + # Add var domain_name to tyto_domains file, if not exists + domain_var = domain_name.replace('.','_') + if not domain_var in domains_db: + append_f(tyto_domains,'%s = "%s"'%(domain_var,curr_dir)) + + # Create all directories for this domain + check.create_domain_dirs('all') + + # Will rename temp_domain conf to legacy domain conf file + exiting('form',2,'\n:) Activated domain: "%s"'%domain_name) + + diff --git a/src/var/lib/tyto/program/log.py b/src/var/lib/tyto/program/log.py new file mode 100644 index 0000000..2712169 --- /dev/null +++ b/src/var/lib/tyto/program/log.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# Name: Tyto - Littérateur +# Type: Global functions for logs +# Description: Print data to specific log file +# file: log.py +# Folder: /var/lib/tyto/scripts/ +# By echolib (XMPP: im@echolib.re) +# License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 + +#------------ +# funny stats +#------------ +# lines: +# functions: +# comments: +#---------------------------------------------------------------------- + +#********************************************************************** +import check, domain + +#=======================# +# Manage Argument 'log' # +# Read, remove log file # +#-----------------------# +def manage_log(file_post, Opt): + import os, sys + + if file_post: + # No domain set, but ask for article : exit + if not domain.domain_conf: + sys.exit(1) + + # Get complete URI for post + post_uri = '%s%s'%(domain.domain_articles, file_post) + + # Get ID from URI + post_ID = check.get_filesum(post_uri,False) + + # Set log file for article + post_logs = '%s%s.log'%(domain.domain_logs,post_ID) + else: + post_logs = domain.tyto_logs + + if os.path.exists(post_logs): + if Opt == 'Remove' : + os.remove(post_logs) + msg_log = 'Log > Removed file: %s'%post_logs + if file_post: + append_f(domain.tyto_logs,msg_log,0) + else: + print(':) %s'%msg_log) + else: + file = open(post_logs,'r').read() + print(file) + else: + print(':| Unsed file yet: %s'%post_logs) + +#==============================# +# Set and return date and time # +# (especially for logs) # +#------------------------------# +def nowdate(): + import datetime + + now = datetime.datetime.now() + return(now.strftime('%Y-%m-%d %H:%M:%S')) + +#==================================# +# Append line to specific log file # +#----------------------------------# +def append_f(f,line,n): + smiley = [':)',':(\033[1;31m','\033[1;33m:|'] + now = nowdate() + + # Open file to append line + file = open(f, "a") + file.write('%s %s\n'%(now,line)) + file.close() + + if not line.endswith('\n'): + print('%s %s\033[0;0m'%(smiley[n],line)) diff --git a/src/var/lib/tyto/program/wip.old.py b/src/var/lib/tyto/program/wip.old.py new file mode 100644 index 0000000..17cd287 --- /dev/null +++ b/src/var/lib/tyto/program/wip.old.py @@ -0,0 +1,412 @@ +#!/usr/bin/python +# Name: Tyto - Littérateur +# Type: Global functions for wip +# Description: Converters from Source to HTML +# file: wip.py +# Folder: /var/lib/tyto/scripts/ +# By echolib (XMPP: im@echolib.re) +# License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 + +#------------ +# funny stats +#------------ +# lines: +# functions: +# comments: +#---------------------------------------------------------------------- + +#********************************************************************** + +import os, sys +import check,log,domain + +Tyto_Err = False +post_uri = '' +# +# Manage WIP argument +# +def manage_wip(file_post, Force): + # Check if argument's file (.tyto) + if not file_post: + print(':( Unused argument file') + sys.exit(1) + + # Check if file exists + post_uri = '%s%s'%(domain.domain_articles,file_post) + if not os.path.exists(post_uri): + print(':( Unused file: %s'%post_uri) + sys.exit(1) + + # Get ID from URI (to get DB file) + post_ID = check.get_filesum(post_uri,False) + + # Set database file for this article + # Check if DB exists + post_db = '%s%s.db'%(domain.domain_db, post_ID) + if not os.path.exists(post_db): + print(':( Use "check" before "wip".') + sys.exit(1) + + # Set and check/create logs file for this article + post_logs = '%s%s.log'%(domain.domain_logs,post_ID) + + +#========# +# # +# Basics # +# # +#========#------------------------------------------------------------- +#===========================# +# Convert file to post_temp # +#---------------------------# +def post_to_string(file_path): + global post_temp + post_temp = '' + post_html = '' + with open(file_path, "r") as file: + post_temp = file.read() + + remove_empty_lines(post_temp.split('\n')) + post_html = post_temp + +#===========================# +# Convert content TO base64 # +#---------------------------# +def convert_to_b64(post_content): + import base64 + global b64_content + post_content_base64 = '' + content_bytes = post_content.encode("ascii") + base64_bytes = base64.b64encode(content_bytes) + b64_content = 'B64_' + base64_bytes.decode("ascii") + '_B64' + +#================================# +# Convert FROM base64 to content # +#--------------------------------# +def convert_from_b64(post_content): + import base64 + global src_content + post_content_src = '' + content_bytes = post_content.encode("ascii") + base64_bytes = base64.b64decode(content_bytes) + src_content = base64_bytes.decode("ascii") + +#==========================# +# Get Writer's CSS config # +# For compatible's markers # +#--------------------------# +def get_css(css,css_gen): + global css_set + + try: + css_set = line.split(" ")[1] + except: + css_set = '%s%s'%(css,css_gen) + +#=============================# +# Get post, witout empty line # +#-----------------------------# +def remove_empty_lines(post): + global post_html, post_temp, line + post_temp = '' + post_html = '' + + for line in post: + c = len(line) + if c > 0: + #print(c,line) + if not post_temp: + post_temp = line + else: + post_temp = '%s\n%s'%(post_temp,line) + + post_html = post_temp + + +#============# +# # +# Converters # +# # +#============#--------------------------------------------------------- +#==========================# +# Convert bCodes to base64 # +#--------------------------# +def convert_bcodes(post,fm,lm,css): + import re + global post_html, line, css_set + post_temp = '' + span_nbr = ''%css + span_code = ''%css + span_end = '' + pre_end = '' + ln_nbr = 0 + bCode = False + blines = '' + + for line in post: + if line.startswith(fm): # Starting bcode marker + ln_nbr = 0 + bCode = True + get_css(css,'_bcode') + pre_start = '
'%css_set
+      blines    = '%s'%pre_start
+      css_set   = ''
+      continue
+    elif line.startswith(lm): # Ending bcode marker
+      bCode     = False
+      line      = ''
+      blines    = '%s\n%s'%(blines,pre_end)
+      convert_to_b64(blines)
+      post_temp = '%s\n%s'%(post_temp,b64_content)
+
+    if not bCode:
+      post_temp = '%s\n%s'%(post_temp,line)
+    else:
+      ln_nbr += 1
+      blines = '%s\n  %s%d%s%s%s%s'%\
+                (
+                blines,span_nbr,ln_nbr,span_end,span_code,line,span_end
+                )
+
+  remove_empty_lines(post_temp.split('\n'))
+  post_html = post_temp
+
+#============================================#
+# Convert Base64 strings to original content #
+#--------------------------------------------#
+def convert_b64_lines(post):
+  import re
+  global post_html,line
+  post_temp    = ''
+  post_html    = ''
+  b64_contents = ''
+  
+  for line in post:
+    if not 'B64_' in line:
+      post_temp = '%s\n%s'%(post_temp,line)
+    else:
+      b64_contents = re.findall(r"B64_(.*?)_B64", line)
+      for b64_content in b64_contents:
+        convert_from_b64(b64_content)
+        line = line.replace('B64_%s_B64'%b64_content, src_content)
+      post_temp = '%s\n%s'%(post_temp,line)
+
+  remove_empty_lines(post_temp)
+  post_html = post_temp
+
+#==========================#
+# Convert iCodes to base64 #
+# fm = FIRST Marker        #
+# lm = LAST Marker         #
+#--------------------------#
+def convert_icodes(post, fm, lm, css):
+  import re
+  global post_html
+  post_temp       = '' # Temp string to become post_html string page
+  icodes_contents = '' # strings containing icode
+  fm_html         = ''%css
+  lm_html         = ''
+  fm_spe          = '!%s'%(fm)  # Special for !<_
+  lm_spe          = '%s!'%lm    # Special for _>!
+  
+  for line in post:
+    # Special marker containting legacy one
+    if lm_spe and fm_spe in line:
+      icodes_contents = re.findall(r"%s(.*?)%s"%(fm_spe,lm_spe), line)
+      for icode in icodes_contents:
+        content_rep = '%s%s%s'%(fm_spe,icode,lm_spe)
+        icode = '%s'%(css,icode)
+        convert_to_b64(icode)
+        line = line.replace(content_rep,b64_content)
+    
+    # Legacy marker
+    if lm and fm in line:
+      icodes_contents = re.findall(r"%s(.*?)%s"%(fm,lm), line)
+      for icode in icodes_contents:
+        content_rep = '%s%s%s'%(fm,icode,lm)
+        icode = '%s'%(css,icode)
+        convert_to_b64(icode)
+        line = line.replace(content_rep,b64_content)
+    
+    post_temp = '%s\n%s'%(post_temp,line)
+  post_html = post_temp
+
+#====================================#
+# Convert Quotes                     #
+# 2 types:                           #
+#   - Advanced, with author          #
+#   - Simple, with no author         #
+# mt = marker type (Should be 3 '-') #
+#------------------------------------#---------------------------------
+#=========================#
+# Return datas after term #
+#-------------------------#
+def get_quote_datas(term,line):
+  import re
+
+  term_data = re.compile('%s(.*)$'%term)
+  if term_data:
+    return term_data.search(line).group(1)
+
+#=====================#
+# Loop in Post        #
+# Convert quotes only #
+#---------------------#
+def convert_quotes(post,mt,css):
+  import re
+  global post_html,line,htmlquote, cite
+  post_temp = '' # Temp string to become post_html string page
+  Quote     = False
+  bquote    = htmlquote = htmllines = ''
+  cite      = ''
+  link      = cite_HTML = ''
+  lang      = lang_HTML = ''
+  book = year = title_HTML = ''
+  quote_nbr = 0
+
+  for line in post:
+    # Starting quote
+    if line.startswith(mt):
+      if not Quote:
+        Quote = True
+        get_css(css,'_quote')
+        quote_nbr += 1
+        line = '_QUOTE_%d'%quote_nbr
+        if post_temp:
+          post_temp = '%s\n%s'%(post_temp,line)
+        else:
+          post_temp = line
+        continue
+      # End of quote
+      # Create HTML and convert marker to Base64
+      else:
+        Quote = False
+        
+        # Create HTML datas
+        if cite and book and year:
+          title_HTML = ' title="%s - %s (%s)"'%(cite,book,year)
+        elif book and year:
+          title_HTML = ' title="%s (%s)"'%(book,year)
+        elif book:
+          title_HTML = ' title="%s"'%book
+        elif year:
+          title_HTML = ' title="%s"'%year
+        
+        htmlquote = '
>' # No close marker + +# 'Open' , 'Close' , 'Name' +markers_reg = [ + [ r'^\($|^\(\s' , r'^\)$' , 'paragraphs' ], + [ r'^\(\($|^\(\(\s', r'^\)\)$', 'quotes' ], + [ r'^\[\[$|^\[\[\s', r'^\]\]$', 'brut codes' ], + [ r'^-\($|^-\(\s' , r'^-\)$' , 'lists' ] + ] + +#=====================# +# Manage WIP argument # +#---------------------# +def manage_wip(file_post, Force): + # Get IDs from Article + check.post_IDs(file_post) + + # Check DB + if not check.db_exist: + print(':( Article must be checked first.') + sys.exit(1) + + # Check ID.wip + if not os.path.exists(check.post_tmp): + print(':( Article must be check again (unsed %s)'%( + check.post_tmp + ) + ) + sys.exit(1) + + # Current Article has changed (not same hash in DB for chk) + if check.hash_chk != check.post_chk[0]: + print(':| Article has changed. Check it first.') + sys.exit(0) + + # Article has same hash in DB for wip + if check.hash_chk == check.post_wip[0]: + # Check if file exists on the server + if check.srv_post_wip[0] and not Force: + print(':| Article was already converted on %s'%( + check.post_wip[1] + ) + ) + sys.exit(0) + + + # All is good, converting... + #--------------------------- + # Send to log + msg_log = 'Wip > Article: %s. logs: %s'%( + check.post_uri, check.post_logs + ) + log.append_f(domain.tyto_logs,msg_log,0) + + # Set file_string from post_tmp + file_string = open(check.post_tmp,'r').read() + post_lines = file_string.rsplit('\n') + + # Set wip_html string. Will be the HTML part
+ global wip_html + wip_html = '' + + for line in post_lines: + if len(line) == 0: continue + wip_html = '%s%s\n'%(wip_html, line) + + wip_begin_markers(wip_html.rsplit('\n')) + wip_titles( wip_html.rsplit('\n')) + wip_words_markers(wip_html) + wip_quotes( wip_html.rsplit('\n')) + + print('> Article HTML:') + print(wip_html) + + +#============================# +# HTML CONVERTERS # +# wip_tmp: new replacedlines # +#============================#----------------------------------------- +#============================# +# New line: | # +# Anchors: >> # +# Paragraphs: ( =

# +# bCodes: (( = # +#----------------------------# +def wip_begin_markers(wip_lines): + global wip_html + + wip_tmp = '' + # Set marker (regex to find), HTML, Need CSS + marks_html = [ + ['^\|$|^\|\s' , '
' , True ], + ['^>>\s' , '' , True ], + [r'^\($|\(\s' , '

' , True ], + ['^\)$|^\)\s' , '

' , False], + ['^\[\[$|^\[\[\s' , '' , False] + ] + + for line in wip_lines: + if len(line) == 0: continue + + # Find if line hase marker and remplace + for marker in marks_html: + if re.match(marker[0], line): + if marker[2]: # Hass CSS + get_css(line) + line = line.replace(line, marker[1]%css_set) + else: + line = line.replace(line, marker[1]) + + wip_tmp = '%s%s\n'%(wip_tmp, line) + + wip_html = wip_tmp + +#===========================# +# Titles: #[1-6] = # +# Add a
# +# - if contents after title # +# (and not a new title) # +#---------------------------# +def wip_titles(wip_lines): + global wip_html + + wip_tmp = '' + has_div = False + + for ln, line in enumerate(wip_lines, 1): + if len(line) == 0: continue + + # Title found + if re.match('#\d\s', line): + tn = line[1] + ts = tn * 2 # Tab spaces + if has_div: # Before Title, if div was open, close it + wip_tmp = '%s
\n'%wip_tmp + has_div = False + + # Replace #[1-6] TITLE + wip_tmp = '%s%s\n'%( + wip_tmp, + line.replace('#%s '%tn, + ''%( + tn,domain.domain_css + ) + ), + tn + ) + + # Check next line + # Add div if contents after, and not a new title + if not re.match('#\d\s', wip_lines[ln]): # NOT a new title + wip_tmp = '%s
\n'%( + wip_tmp, domain.domain_css,tn + ) + has_div = True + else: + wip_tmp = '%s%s\n'%(wip_tmp, line) + + if has_div: # Close last div + wip_tmp = '%s
\n'%wip_tmp + has_div = False + + wip_html = wip_tmp + +#======================================# +# Words Markers (strongs, emphasis...) # +#--------------------------------------# +def wip_words_markers(wip_tmp): + global wip_html + + # Set marker, and its HTML value + marks_html = [ + ['*_' , ''%domain.domain_css], + ['_*' , '' ], + ['+_' , ''%domain.domain_css ], + ['_+' , '' ], + ['-_' , ''%domain.domain_css ], + ['_-' , '' ], + ['~_' , ''%domain.domain_css ], + ['_~' , '' ], + ['/_' , ''%domain.domain_css ], + ['_/' , '' ], + ['\\_' , ''%domain.domain_css ], + ['_\\' , '' ], + ['=_' , ''%domain.domain_css ], + ['_=' , '' ], + ['×_' , '' ], + ['_×' , '' ], + ['(_' , ''%domain.domain_css ], + ['_)' , '' ], + ['<_' , ''%domain.domain_css ], + ['_>' , '' ] + ] + + # Replace marker in string's article (wip_html) + for marker in marks_html: + wip_tmp = wip_tmp.replace(marker[0], marker[1]) + + wip_html = wip_tmp + + +#========# +# Quotes # +#========# +#======================== # +# Loop lines in article # +# For each quote, convert # +# (Decode base64, HTML) # +#-------------------------# +def wip_quotes(wip_lines): + global wip_html, quote_css + + wip_html = '' + quote = False + + for line in wip_lines: + # Starting quote marker + if re.match(markers_reg[1][0], line): + get_css(line) + quote_css = css_set + quote = True + continue + + # Ending quote marker + elif re.match(markers_reg[1][1], line): + quote = False + continue + + # In quote, Should be base64 line containing quote + # Call convert_quote() to create quote_html + # Replace line, with html_quote + if quote: + if re.match('B64\|', line): + line = line.replace('B64|', '') + line = line.replace('|B64', '') + convert_from_b64(line) + bQuote = src_content + convert_quote(bQuote.rsplit('\n')) + line = quote_html + + # Create new HTML article + if wip_html: wip_html = '%s\n%s'%(wip_html, line) + else : wip_html = line + +#============================# +# Convert brut Quote to HTML # +#----------------------------# +def convert_quote(bQuote): + global quote_html + + quote_html = '' + cite = link = lang = year = book = '' + author_title = author_show = author_html = '' + cite_html = lang_html = figca_html = '' + year_show = book_show = '' + + # Set Datas (if exists) + for line in bQuote: + # Get Datas from markers in quote + if line.startswith('_cite:'): + cite = quote_data(line) + elif line.startswith('_link:'): + link = quote_data(line) + cite_html = ' cite="%s"'%link + elif line.startswith('_lang:'): + lang = quote_data(line) + lang_html = ' lang="%s"'%lang + elif line.startswith('_year:'): + year = quote_data(line) + year_show = ' (%s)'%year + elif line.startswith('_book:'): + book = quote_data(line) + book_show = ' - %s'%book + + # Only accepted marker: paragraphs + elif re.match(markers_reg[0][0], line): + get_css(line) + line = '

'%css_set + if quote_html: quote_html = '%s\n%s'%(quote_html, line) + else : quote_html = line + elif re.match(markers_reg[0][1], line): + line = '

' + quote_html = '%s\n%s'%(quote_html, line) + + # main quote content + else: + if quote_html: quote_html = '%s\n%s'%(quote_html, line) + else : quote_html = line + + if not cite : author_show = 'NC' + if year and book: author_show = '%s%s%s'%(author_show, book_show, year_show) + + if cite or link: + author_show = '%s%s'%(cite, author_show) + author_title = ' title="%s"'%author_show + + # Create main blockquote + blockquote = '
\n%s
'%( + quote_css, author_title, cite_html, lang_html, + quote_html + ) + + if cite: + if link: author_html = '%s'%( + quote_css, link, author_show + ) + else : author_html = author_show + + if year: author_html = ''%( + year, author_html + ) + + figca_html = '
\n'%quote_css + \ + '\n%s\n\n'%( + quote_css, author_html + ) + \ + '
' + + quote_html = '
\n%s\n%s\n
'%( + quote_css, blockquote,figca_html + ) + else: + if year: quote_html = ''%( + year, blockquote + ) + else: quote_html = blockquote + + #print('> Quote_HTML:\n', quote_html) + +#=================================# +# Return datas in quote from line # +#---------------------------------# +def quote_data(line): + return line.split(' ',1)[1].lstrip() + + +#=========================# +# Done when command check # +#=========================# +#====================================# +# Protect bCodes contents to base64 # +# fm: First marker ; lm: last marker # +#-----------------------------------=# +def convert_bcodes(article, fm, lm, css): + global article_temp + + article_temp = '' + bCode = False + bCode_lines = '' + + for line in article: + if line.startswith(fm): + bCode = True + article_temp = '%s%s\n'%(article_temp, line) + continue + + if line.startswith(lm): + bCode = False + + #print(bCode_lines) + convert_to_b64(bCode_lines) + bCode_lines = '' + article_temp = '%s%s\n%s\n'%( + article_temp, b64_content, line + ) + continue + + if bCode: + bCode_lines = '%s%s\n'%(bCode_lines, line) + else: + article_temp = '%s%s\n'%(article_temp, line) + +#====================================# +# Protect bCodes contents to base64 # +# fm: First marker ; lm: last marker # +#-----------------------------------=# +def convert_icodes(article, css): + import re + global article_temp + + article_temp = '' + fm1 = '<_' + lm1 = '_>' + fm2 = '(_' ; fm2_r = '\(_' + lm2 = '_)' ; lm2_r = '_\)' + + for line in article: + # Specific marker format + if fm1 and lm1 in line: + iCodes = re.findall(r"%s(.*?)%s"%(fm1,lm1), line) + for iCode in iCodes: + convert_to_b64(iCode) + line = line.replace(fm1 + iCode + lm1, + fm1 + b64_content + lm1 + ) + + # Legacy marker format + if fm2 and lm2 in line: + iCodes = re.findall(r"%s(.*?)%s"%(fm2_r,lm2_r), line) + for iCode in iCodes: + convert_to_b64(iCode) + line = line.replace(fm2 + iCode + lm2, + fm2 + b64_content + lm2 + ) + + article_temp = '%s%s\n'%( + article_temp, line + ) + + +#=======# +# TOOLS # +#=======#-------------------------------------------------------------- +#==========================# +# Get Writer's CSS config # +# For compatible's markers # +# css_gen: generic value # +#--------------------------# +def get_css(line): + global css_set + try : css_set = line.split(" ")[1] + except: css_set = domain.domain_css + + +#=====================================# +# Base64 # +# Mainly used when article is checked # +#-------------------------------------#-------------------------------- +#===========================# +# Convert content TO base64 # +#---------------------------# +def convert_to_b64(post_content): + import base64 + global b64_content + + post_content_base64 = '' + content_bytes = post_content.encode("utf8") + base64_bytes = base64.b64encode(content_bytes) + b64_content = 'B64|' + base64_bytes.decode("utf8") + '|B64' + +#================================# +# Convert FROM base64 to content # +#--------------------------------# +def convert_from_b64(post_content): + import base64 + global src_content + + post_content_src = '' + content_bytes = post_content.encode("utf8") + base64_bytes = base64.b64decode(content_bytes) + src_content = base64_bytes.decode("utf8") +