]> Git — Sourcephile - sourcephile-web.git/commitdiff
init main
authorJulien Moutinho <julm+literate-web@sourcephile.fr>
Sun, 12 Dec 2021 04:15:56 +0000 (05:15 +0100)
committerJulien Moutinho <julm+sourcephile-web@sourcephile.fr>
Tue, 23 Aug 2022 02:35:11 +0000 (04:35 +0200)
52 files changed:
.envrc [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.hlint.yaml [new symlink]
.reuse/dep5 [new file with mode: 0644]
ChangeLog.md [new file with mode: 0644]
LICENSES/AGPL-3.0-or-later.txt [new file with mode: 0644]
LICENSES/CC-BY-SA-3.0.txt [new file with mode: 0644]
LICENSES/CC0-1.0.txt [new file with mode: 0644]
content/drafts/future-of-haskellPackages.md [new file with mode: 0644]
content/drafts/the-gap-of-goodwill.md [new file with mode: 0644]
content/drafts/todo.md [new file with mode: 0644]
content/posts.old/DDC5.Demandes.UnBilanApproximatif.md [new file with mode: 0644]
content/posts.old/a.md [new file with mode: 0644]
content/posts.old/a/aa.md [new file with mode: 0644]
content/posts.old/a/ab.md [new file with mode: 0644]
content/posts.old/b.md [new file with mode: 0644]
content/posts.old/ddc7.logiciellerie.une_assoce.md [new file with mode: 0644]
content/posts.old/programing-language-vision.md [new file with mode: 0644]
content/posts.old/sourcephile-statuts.md [new file with mode: 0644]
content/posts.old/studien.md [new file with mode: 0644]
content/posts.old/test-tech.fr.md [new file with mode: 0644]
content/posts.old/test-tech.md [new file with mode: 0644]
content/posts/a.md [new file with mode: 0644]
content/posts/a/aa.md [new file with mode: 0644]
content/posts/b.md [new file with mode: 0644]
content/special/index.md [new file with mode: 0644]
content/special/not-found.md [new file with mode: 0644]
content/special/projects.md [new file with mode: 0644]
content/static/css/extra.css [new file with mode: 0644]
content/static/css/windi-extras.css [new symlink]
content/static/icons/open-iconic [new symlink]
flake.lock [new file with mode: 0644]
flake.nix [new file with mode: 0644]
generator/.hlint.yaml [new file with mode: 0644]
generator/Main.hs [new file with mode: 0644]
generator/Site/Body.hs [new file with mode: 0644]
generator/Site/Feed.hs [new file with mode: 0644]
generator/Site/Filter.hs [new file with mode: 0644]
generator/Site/Lang.hs [new file with mode: 0644]
generator/Site/Model.hs [new file with mode: 0644]
generator/Site/Page.hs [new file with mode: 0644]
generator/Site/Render.hs [new file with mode: 0644]
generator/Site/Tag.hs [new file with mode: 0644]
generator/Site/Update.hs [new file with mode: 0644]
generator/Utils/Html.hs [new file with mode: 0644]
generator/Utils/Pandoc.hs [new file with mode: 0644]
generator/Utils/Pandoc/Html.hs [new file with mode: 0644]
generator/Utils/Pandoc/SignsCount.hs [new file with mode: 0644]
generator/css/windi-extras.html [new file with mode: 0644]
generator/fourmolu.yaml [new file with mode: 0644]
generator/generator.cabal [new file with mode: 0644]
generator/hie.yaml [new file with mode: 0644]

diff --git a/.envrc b/.envrc
new file mode 100644 (file)
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..66348c8
--- /dev/null
@@ -0,0 +1,24 @@
+*.actual.*
+*.eventlog
+*.eventlog.html
+*.eventlog.json
+*.hi
+*.hp
+*.o
+*.orig
+*.prof
+*.root
+*~
+.direnv/
+.ghc.environment.*
+.shake
+.stack-work/
+/.direnv
+/.pre-commit-config.yaml
+/content/static/css/windi.css
+/content/static/font
+/output
+dist-newstyle/
+dump-core/
+hlint.html
+result*
diff --git a/.hlint.yaml b/.hlint.yaml
new file mode 120000 (symlink)
index 0000000..ea372ca
--- /dev/null
@@ -0,0 +1 @@
+generator/.hlint.yaml
\ No newline at end of file
diff --git a/.reuse/dep5 b/.reuse/dep5
new file mode 100644 (file)
index 0000000..5aad435
--- /dev/null
@@ -0,0 +1,20 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: sourcephile-web
+Upstream-Contact: Julien Moutinho <julm+sourcephile-web@sourcephile.fr>
+Source: https://code.git.sourcephile.fr/~julm/sourcephile-web
+
+Files: *.nix *.lock cabal.project* *.cabal *.md .chglog/* .envrc .gitignore .gitmodules *.yaml
+Copyright: Julien Moutinho <julm+sourcephile-web@sourcephile.fr>
+License: CC0-1.0
+
+Files: generator/*
+Copyright: Julien Moutinho <julm+sourcephile-web@sourcephile.fr>
+License: AGPL-3.0-or-later
+
+Files: content/*
+Copyright: Julien Moutinho <julm+sourcephile-web@sourcephile.fr>
+License: CC-BY-SA-3.0
+
+Files: content/static/icons/open-iconic
+Copyright: https://github.com/iconic/open-iconic/graphs/contributors
+License: MIT
diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/LICENSES/AGPL-3.0-or-later.txt b/LICENSES/AGPL-3.0-or-later.txt
new file mode 100644 (file)
index 0000000..0c97efd
--- /dev/null
@@ -0,0 +1,235 @@
+GNU AFFERO GENERAL PUBLIC LICENSE
+Version 3, 19 November 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+
+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.
+
+     <one line to give the program's name and a brief idea of what it does.>
+     Copyright (C) <year>  <name of author>
+
+     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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
diff --git a/LICENSES/CC-BY-SA-3.0.txt b/LICENSES/CC-BY-SA-3.0.txt
new file mode 100644 (file)
index 0000000..39a8591
--- /dev/null
@@ -0,0 +1,99 @@
+Creative Commons Attribution-ShareAlike 3.0 Unported
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
+
+1. Definitions
+
+     a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
+
+     b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License.
+
+     c. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License.
+
+     d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership.
+
+     e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.
+
+     f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
+
+     g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
+
+     h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
+
+     i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
+
+     j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
+
+     k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
+
+2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
+
+     a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections;
+
+     b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";
+
+     c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and,
+
+     d. to Distribute and Publicly Perform Adaptations.
+
+     e. For the avoidance of doubt:
+
+          i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
+
+          ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and,
+
+          iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License.
+
+The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved.
+
+4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
+
+     a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested.
+
+     b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License.
+
+     c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
+
+     d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise.
+
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+     a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
+
+     b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
+
+8. Miscellaneous
+
+     a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
+
+     b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
+
+     c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+
+     d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
+
+     e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
+
+     f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
+
+Creative Commons Notice
+
+Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
+
+Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License.
+
+Creative Commons may be contacted at http://creativecommons.org/.
diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt
new file mode 100644 (file)
index 0000000..0e259d4
--- /dev/null
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
diff --git a/content/drafts/future-of-haskellPackages.md b/content/drafts/future-of-haskellPackages.md
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/content/drafts/the-gap-of-goodwill.md b/content/drafts/the-gap-of-goodwill.md
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/content/drafts/todo.md b/content/drafts/todo.md
new file mode 100644 (file)
index 0000000..a85221c
--- /dev/null
@@ -0,0 +1,19 @@
+Title: Todo-List
+Date: 2017-06-12
+
+Ich liebe Todo-Lists.
+Auf meiner eigenen stehen gerade fast 300 Einträge, Chaos Darmstadt habe ich gerade eine aufgequatscht.
+Ich sammel hier mal, worüber ich gerne nochmal etwas schreiben würde:
+
+ * Radikale Gedanken und Unsicherheit
+ * Was soll denn nun Wahrheit sein?
+ * Warum ist Wahrheit wichtig?
+ * Warum bin ich Atheist?
+ * Moral - mal so ganz allgemein
+ * Warum ich Tagesnachrichten vermeide.
+ * Schuld
+ * Lesetipps
+
+Offensichtlich alles Themen, zu denen ich nur falsche Dinge sagen kann.
+Aber es sind halt solche die mich interessieren.
+Vermutlich schreibe ich im Endeffekt doch nichts oder über etwas ganz anderes …
diff --git a/content/posts.old/DDC5.Demandes.UnBilanApproximatif.md b/content/posts.old/DDC5.Demandes.UnBilanApproximatif.md
new file mode 100644 (file)
index 0000000..f5df822
--- /dev/null
@@ -0,0 +1,58 @@
+---
+title: Un bilan approximatif pour juger une Demande de Critiques
+lang: fr
+summary: 
+tags:
+   - DemandeDeCritiques
+   - Juridique
+   - Brouillon
+   - tech
+   - Français
+discussion:
+   url: https://mails.sourcephile.fr/inbox/test?q=tc:"<test%2Btoto@sourcephile.fr>"&x=t
+   mail: test@sourcephile.fr
+updates:
+  - DDC6: Un nom pour la logiciellerie
+---
+
+
+# Explications
+## Objectif
+L’évaluation approximative d’une Demande, est un indicateur de la maturité d’une Demande. Cet indicateur est avant tout destiné à l’Équipe afin qu’elle ait une idée approximative de si son travail d’élucidation des raisons à réaliser la Demande et de recherche de propositions actionnables est suffisamment mature ou du moins évolue dans le bon sens.
+L’enjeu de décider selon la mention majoritaire voire à l’unanimité, n’est pas uniquement une question de respect du pluralisme voire de l’individualisme. Mais avant tout de rendre probable la construction d’actions qui ne deviennent possibles que si toute la communauté, ou presque, est convaincue du bienfait de la Demande, par exemple parce que sa participation active est requise pour la faire vivre (par exemple, la Fête de la Montagne), rattraper les ratés ou débloquer des situations par une capacité collective à agir.
+Attention toutefois, l’enjeu n’est généralement pas de rassembler toujours plus fort, de sensibiliser toujours plus fort, mais avant tout et surtout de passer à l’action. Ce qui demandera probablement de ne pas rester en mode « on imagine tous et toutes ensemble le bien qu’on pourrait faire en coopérant », mais de considérer également que « pour coopérer on va aussi devoir résoudre les enjeux distributifs qui se posent entre-nous ».
+
+## Une aide à la conception
+Alors qu’on pourrait s’attendre à des mentions plus vagues et formant une échelle plus équilibrée côté rejet comme : « Fortement contre », « Contre », « Plutôt contre », etc. Les mentions possibles ici laissent le rejet exprimable, mais se focalisent surtout sur la construction de la Demande, dans le but principal de savoir si cela « vaut le coup ou non » pour l’Équipe de revoir sa copie. Lui éviter en tout cas de rester dans une croyance magique qu’il manque juste de « volonté politique » et que si elle tire fort la corde politique elle va attirer à elle suffisamment de soutiens, car si la corde n’est pas nouée à la poignée d’une porte mais à un mur, il y a un moment où ça ne va pas fonctionner. Et peut-être vaudrait-il mieux dans ce cas dénouer cette corde pour la renouer sur des problèmes qu’elle ne voyait pas jusque là.
+
+## Ni un appel à volontaires, ni un appel à dons
+Attention toutefois, cette évaluation approximative ne doit pas être confondu avec une recherche de qui donnera de sa personne ou de sa bourse pour actionner la Demande. L’analyse de quel·les acteur·rices individuels ou collectifs peuvent avoir la main (jusqu’à un certain point) sur la traduction en actes de la Demande doit plutôt être traitée dans la section « Actions ».
+
+# Le quorum attracteur de la théorie des sondages
+
+L’essor des sondages modernes — que ce soit pour le marketing, la médecine ou plus généralement la recherche scientifique — est arrivé à établir des attracteurs que ne possédaient aucune des cités médiévales ni même l’Athènes antique qui pratiquaient le tirage-au-sort, que ce soit pour partager des pouvoirs ou des corvées. Ces outils théoriques nous permettent notamment de calculer une taille suffisante à donner à un échantillon pour le rendre représentatif d’une population parmi laquelle il est sélectionné aléatoirement.
+Ainsi la présente Demande propose (mais il reste encore à faire vérifier cela par des expert·tes en calculs de probabilités, voire des simulations informatiques) de calculer — pour une population de taille N et une échelle de m mentions possibles — une taille d’échantillon aléatoire suffisante pour que la mention majoritaire prise sur cette échantillon soit représentative de celle qu’on obtiendrait à partir des jugements de l’ensemble de la population, selon :
+- une certaine marge d’erreur ε : formant un intervalle de confiance autour de la mention majoritaire de l’échantillon.
+- et un certain niveau de confiance 1-δ : si 1-δ = 95%, seulement 95 échantillons sur 100 génèrent des intervalles de confiance qui contiennent la mention majoritaire de la population.
+
+Pour cela il s’agit de représenter les futurs jugements par une série de variables aléatoires 1 ≤ Xi ≤ m suivant une loi binômiale, définie par : Xi = 1 si le rang du ième jugement parmi le profil de mérite est inférieur ou égal à la borne inférieur de l’intervalle de confiance du rang médian (1/2−ε)·N, et Xi = 0 sinon. Alors une taille minimale n que doit avoir cet échantillon pour être représentatif avec un niveau de confiance 1-δ peut être obtenue à partir des inégalités de Serfling [Serfling1974] (Corollaire 1.3), ce qui donne (en langage Maxima) :
+```
+(%i1) n(N,ɛ,δ,m) := assoc(n, solve(exp(-2*ɛ^2*n / ((1-(n-1)/N)*(m-1)^2)) = δ/2, n));
+```
+
+Ainsi par exemple, pour N = 60 millions de personnes s’exprimant sur une échelle de m = 5 mentions possibles, il suffit d’avoir les jugements de n ≈ 2072 personnes sélectionnées aléatoirement parmi ces N, pour que la mention majoritaire de cet échantillon soit dans 1-δ = 85% des échantillons à plus ou moins ɛ·N = 0.1·60·106 = 6 millions de jugements de la mention majoritaire qui serait obtenue si les N personnes s’exprimaient :
+
+```
+(%i2)
+float(n(60*10^6,0.1,1-0.85,5));
+(%o2) 2072.1422015348
+```
+
+Autre exemple, pour N = 500 personnes, s’exprimant sur une échelle de m = 5 mentions possibles, il suffit d’avoir les jugements de n ≈ 186 personnes sélectionnées aléatoirement parmi ces N, pour que la mention majoritaire de cet échantillon soit dans 1-δ = 80% des échantillons à plus ou moins ɛ·N = 0.25·500 = 125 jugements de la mention majoritaire qui serait obtenue si les N personnes s’exprimaient :
+
+```
+(%i4) float(n(500,0.25,1-0.80,5));
+(%o4) 185.7989645902684
+```
+
+Attention toutefois, ce quorum attracteur n’est valable que dans le cas idéalisé où les personnes sont sélectionnées aléatoirement, ce qui n’est qu’une approximation d’un scrutin où le vote est laissé au volontariat.
diff --git a/content/posts.old/a.md b/content/posts.old/a.md
new file mode 100644 (file)
index 0000000..a6ca145
--- /dev/null
@@ -0,0 +1,10 @@
+---
+title: Let's talk about A
+date: 2021-12-10
+lang: fr
+summary:
+  Lorem ipsum dolor sit amet.
+---
+
+# This is A
+Lorem ipsum [This is ../b](../b) and [This is b](b).
diff --git a/content/posts.old/a/aa.md b/content/posts.old/a/aa.md
new file mode 100644 (file)
index 0000000..a6ca145
--- /dev/null
@@ -0,0 +1,10 @@
+---
+title: Let's talk about A
+date: 2021-12-10
+lang: fr
+summary:
+  Lorem ipsum dolor sit amet.
+---
+
+# This is A
+Lorem ipsum [This is ../b](../b) and [This is b](b).
diff --git a/content/posts.old/a/ab.md b/content/posts.old/a/ab.md
new file mode 100644 (file)
index 0000000..a6ca145
--- /dev/null
@@ -0,0 +1,10 @@
+---
+title: Let's talk about A
+date: 2021-12-10
+lang: fr
+summary:
+  Lorem ipsum dolor sit amet.
+---
+
+# This is A
+Lorem ipsum [This is ../b](../b) and [This is b](b).
diff --git a/content/posts.old/b.md b/content/posts.old/b.md
new file mode 100644 (file)
index 0000000..5d8c6a5
--- /dev/null
@@ -0,0 +1,10 @@
+---
+title: Let's talk about B
+date: 2021-12-10
+lang: fr
+summary:
+  Lorem ipsum dolor sit amet.
+---
+
+# This is B
+Lorem ipsum [This is ../a](../a) and [This is a](a).
diff --git a/content/posts.old/ddc7.logiciellerie.une_assoce.md b/content/posts.old/ddc7.logiciellerie.une_assoce.md
new file mode 100644 (file)
index 0000000..a2eb02b
--- /dev/null
@@ -0,0 +1,333 @@
+---
+title: Une association pour Sourcephile
+lang: fr
+summary: |
+  Ceci est un **résumé** court
+  1. voilàa
+  2. enfin
+  3. une liste
+tags:
+   - DemandeDeCritiques
+   - Juridique
+   - Brouillon
+   - tech
+   - unison
+   - haskell
+   - fragnix
+   - grin
+discussion:
+   url: https://mails.sourcephile.fr/inbox/test?q=tc:"<test%2Bddc7@sourcephile.fr>"&x=t
+   mail: test+ddc7@sourcephile.fr
+license: CC-BY-SA-4.0
+updates:
+  - DDC6: Un nom pour la logiciellerie
+---
+
+# Bilan approximatif
+## Mentions possibles
+- R: « À rejeter »
+- C: « À clarifier »
+- A: « À améliorer »
+- T: « À tester »
+- G: « À garder »
+
+## Analyses des préoccupations
+### [T] Pour l’indépendance
+#### [T] Concernant les produits
+- [T] julm: une association autorise la réception de dons (éventuellement partiellement fiscalement déductibles) et des subventions (publiques après un an d'existence).
+### [A] Pour la coopération
+#### [T] Concernant le salariat
+- [T] julm: ça peut permettre de mieux faire reconnaitre mes activités comme étant du travail, produisant de la valeur d'usage et de la valeur économique.
+#### [A] Concernant la collaboration
+- [A] julm: il faudrait d'abord trouver des co-"porteurs du projet associatif". Trouver des gens qui codent "comme moi" paraît improbable.
+
+# Explications
+## Introduction
+### Des buts dans l'informatique, mais des moyens dans la société : quelle reconnaissance du travail et réintégration de sa division ?
+Le logiciel libre n'a pas de prix mais a un coût. Sans salaire socialisé conventionnant ses producteur.rices comme pour la médecine de secteur 1, il ne reste que des voies économiquement précaires et la vocation d'individus pour assurer la mise en commun et en partage des logiciels.
+
+Faire un logiciel métier nécessite des connaissances en informatiques et sur le métier. Or qui est compétent.te en développement de logiciel ne l'est généralement pas dans le métier considéré, et vice-et-versa.
+
+### Stratégie compétitive et justification coopérative : quelle théorie de l'action pour « se jeter à l'eau » ?
+Quels modèles suivre ?
+On est toujours petit dans le système d'actions, face au reste du monde nos marges de manoeuvre nous paraissent limitées, les forces en face de nous sont énormes, et il faut gérer le fait qu'on est un acteur face à beaucoup d'autres acteurs. La stratégie consiste alors à constuire de petites actions abordables qui peuvent malgré tout produire de grands effets.
+
+Une théorie d'action reste une théorie, un manuel permettant de penser et parler des choses et de critiquer d'autres théories, mais de même que lire ou écrire des manuels de judo ne suffit pas à être un judoka, de même une théorie d'action nécessite de pratiquer la mise en oeuvre des concepts dans de petits ou grands bains.
+
+### Agir au delà de penser l'action : quelles règles se donner, mais surtout avec qui ?
+Une association, ça sert à s'associer avec d'autres personnes, qui « mettent en commun leurs connaissances ou leur activité dans un but autre que de partager des bénéfices ». Depuis 2014 je passe par des vagues de réflexions, de lectures et d'écritures de plusieurs mois sur le comment faire cela au mieux. Cependant, je ne trouve ainsi personne avec qui m'associer, ou plus exactement personne qui partage réellement les préoccupations pour lesquelles j'aimerais m'associer. Soit c'est du logiciel libre et alors ce ne sont pas des technologies que j'aime ou alors des communautés qui demandent trop d'efforts pour faire accepter les modifications que je souhaite apporter. Soit c'est des technologies que j'aime et ce n'est pas du logiciel libre ou alors des logiciels guidés par des intérêts capitalistiques ou des recherches qui ne m'intéressent pas, ce qui dans tous les cas me pousserait à délaisser mes propres recherches et logiciels.
+
+De ce travail préliminaire il ressort qu'on peut se donner des règles au niveaux des interactions (sur la manière dont nous devrions en tant qu'individu interagir entre-nous), mais pas au niveau de l'émergence (sur la manière dont l'association (cette mini-société) va fonctionner). Et qu'au plus une règle sera contraignante pour des individus ou d'usage peu commun, au plus elle demandera d'accumuler de nombreuses justifications, comme des jurisprudences suite à d'anciennes dérives contre lesquelles ces règles existent.
+
+Si on raisonne en disant que tout le monde va faire ce qu'il a le droit de faire et pas autre chose, on est à côté de la réalité, et si on raisonne en se disant les choses vont se passer comme le droit prévoit qu'elles vont se passer, on se raconte des histoires. Le droit est un des éléments de pouvoir, une ressource que les acteurs peuvent utiliser dans leur jeu de pouvoir, mais le droit ne décide pas en dernier ressort.
+Ce sont les règles, plus la mise en oeuvre des règles, plus la manière dont les règles sont considérées comme utiles par qui les reçoit, qu'il importe de considérer.
+
+Le meilleur moyen abordable de se donner des règles au niveau d'un "petit bain" est de faire une nouvelle entité juridique, qui assume qu'on puisse s'organiser avec d'autres règles et en portant d'autres préoccupations, mais ailleurs et pour d'autres, afin de poser clairement que dans notre association ce sont ces règles là qu'on explore et auxquelles on tient pour aller nager dans le "grand bain".
+
+## Motivations
+Il ne va pas de soi que faire une association (ou n'importe qu'elle autre structure juridique) soit une bonne idée pour faire de bons logiciels libres.
+A commencer par le fait qu'il existe déjà des associations qui font cela (eg. Framasoft, le projet GNU, et autres assoces plus spécifiques), il y aurait peut-être même moyen de rejoindre un institut comme l'Inria ou le CNRS. Cependant leur fonctionnement « par projet » ANR ou quoi pour avoir des crédits, a contribué au non-renouvellement de mon CDD au LIP6 pour travailler sur `grenouille` (.com), recalant le code à de la vulgaire technique sans aucun intérêt scientifique. Refus de continuer le projet ANR me renvoyant dans de longs mois de bénévolat et d’isolement. Donc je ne suis à tort ou à raison pas motivé du tout pour re-explorer cette piste Inria/CNRS.
+
+### Entraînement solitaire ou performance productive : comment considérer le travail déjà accompli ?
+Absolument aucun des logiciels que j'ai initié depuis 10 ans n'est pris au sérieux, parfois même pas par moi-même. Ce que je fais ce n'est considéré comme important pour personne en dehors éventuellement de moi. Donc, soit je me raconte effectivement des histoires, et il me faut **probablement** réviser ce que je fais. Soit c'est important, et alors il faut que je ne sois pas le seul à me donner les moyens que cela aboutisse à quelque chose. Mais la principale raison de ce manque de considération vient **probablement** d'un manque de communication, car je n'ai pas l'assurance nécessaire pour « délivrer » sur la moindre promesse de faire quelque chose d'utile, de fonctionnel, d'ergonomique, et si je l'étais tout porte à croire que ce ne serait **probablement** pas de la recherche que je ferais, mais du simple développement.
+
+J'ai diagnostiqué des problèmes avancés de tâches métier spécifiques (de la comptabilité puis du jugement majoritaire) pouvant potentiellement être partiellement mieux résolu par des solutions que j'ai en bagage. Je sais ou veux savoir coder, je ne suis pas en mode yakafokon, donc je me suis mis à coder. Mais en rester à cela a des limites : mon moral, mes économies, ma motivation et surtout ma connaissance seulement superficielle des problèmes à résoudre, étant donné qu'il s'agit de créer des logiciels libres professionnels.
+
+Mes limitations ont eu pour conséquence que j'ai peu à peu détourné mes efforts vers la conception et le développement de bibliothèques logicielles répondant aux besoins du développement logiciel, ce sur quoi j'étais devenu expert et dans le besoin, mais cela a fait davantage avancer le champ de l'informatique que le champ des métiers qu'il fallait initialement (ré)outiller. Plus précisément, dès que j'obtenais une conception et une implémentation de la logique métier, ce que je considérais être le plus stimulant intellectuellement, je publiais et passais à une toute autre logique métier, ce qui laissait des bibliothèques sans interfaces humaines, ou alors peu conviviales pour le grand public (ie. en ligne de commande).
+
+Ce qui est fait est fait, et n'est pas en soi une raison valable pour décider de persévérer ou pas. "L'erreur est humaine". Avoir fait "tout ça pour rien", ou du moins pas pour ce qui était envisagé, est une éventualité qu'il faut considérer. J'ai déjà abandonné 2 ans de travaux en C pour TI-89, 5 ans de travaux en OCaml, plusieurs mois de travaux en XSLT, et déjà certains en Haskell. Ces abandons proviennent essentiellement d'une perte d'intérêt de ma part pour les technologies en jeu, mais aussi parce qu'une bonne idée de produit n'est pas nécessairement une bonne idée à vendre (directement par des services ou indirectement via des dons ou subventions), et depuis trop longtemps parce que ni ma santé ni mes finances ne me permettent plus de vivre dignement proche de mes pairs et de leurs lieux de socialisation, ni même de m'y rendre ponctuellement.
+
+Dans mon cas, ces produits restent aussi confidentiels qu'invendables en grande partie parce qu'ils ne s'adressent pas directement aux personnes qui m'entourent physiquement, mais seulement au microcosme en ligne où j'évolue. Pour les plus aboutis, il s'agit de bibliothèques (qui au mieux ne peuvent être utiles qu'à d'autres développeurs) ou d'outils en ligne de commande voire en texte brut (qui ne peuvent intéresser au mieux que d'autres geeks n'ayant pas peur du terminal). Et dans de très nombreux cas il s'agit de logiciels certes à l'architecture élégante, mais remplissant des tâches pour lesquelles il existe déjà des alternatives suffisamment efficaces.
+
+On apprend énormément en (ré)écrivant soi-même les logiciels que l'on utilise, mais cela ne veut pas forcément dire que c'est une bonne idée de les utiliser ou de les promouvoir comme outil de production. L'archétype de cela étant les outils cryptographiques (comme hjugement-protocol), ou les cadriciels (comme symantic-http), mais également des outils moins sensibles comme un processeur de texte (hdoc). Peut-être que ces réécritures seront meilleures que les alternatives déjà établies, mais ne le seront **probablement** que sur des aspects très étroits.
+
+Et cela vaut également pour les travaux que je n'ai pas encore totalement abandonnés, et qui sont les suivants, par ordre alphabétique :
+- hcompta (avec hcompta-lcc, hcompta-cli) (**probablement** à renommer « ledgerphile ») : implémentation d'un logiciel de comptabilité permettant un mode texte (donc une compta pérenne et versionnable). Alternatives concurrentes remarquables : ledger, hledger, beancount.
+- hdoc (**probablement** à renommer « textphile ») : implémentation d'un compilateur de texte depuis un format original (TCT, une sorte de mélange entre Markdown et YAML) vers du XML puis du HTML (et potentiellement du PDF). Alternatives concurrentes remarquables : tous les outils Markdown.
+- hjugement (hjugement-protocol, hjugement-cli, hjugement-web) (**probablement** à renommer « majurity ») : implémentation du mode de scrutin du Jugement Majoritaire adossé au protocole cryptographique de vote Helios-C ainsi que diverses recherches originales (rang majoritaire, intégration dans les sections de hdoc). Alternatives concurrentes remarquables : moje (de MieuxVoter) et belenios (de la Loria).
+- htirage (**probablement** à renommer « reloto ») : implémentation d'un algorithme original de tirage au sort publiquement vérifiable. Alternative concurrente remarquable : NomCom (de l'IETF).
+- symantic (avec symantic-lib) : mini-compilateur permettant à l'utilisateur.rice finale d'écrire et exécuter des requêtes dans un langage customisable, inspiré de Haskell. Développé pour hcompta, utilisé par hcompta-cli. Alternatives concurrentes remarquables : aucune connue en Haskell, mais certains s'en rapprochent sur certains aspects comme hint ou bound.
+- symantic-cli : Cadriciel pour écrire et documenter des logiciels en ligne de commande. Utilisé par hjugement, hcompta, hdoc, htirage. Alternatives concurrentes remarquables : options-applicative.
+- symantic-document : Bibliothèque pour formater du texte à afficher dans un terminal. Alternative concurrente remarquable : ansi-wl-pprint.
+- symantic-grammar : Cadriciel pour parser et documenter des grammaires (non-contextuelles). Alternative concurrente remarquable : certainement beaucoup.
+- symantic-http (avec symantic-http-client, symantic-http-server, symantic-http-pipes) : Cadriciel pour interroger, fournir et documenter des API Web. Utilisé par : hjugement-server (en prévision). Alternative concurrente remarquable : Servant.
+- symantic-xml : Cadriciel pour lire, écrire et documenter du XML. Utilisé par hdoc. Alternatives concurrentes remarquables : haxml, HXT
+
+### Seul ou accompagné face aux critiques et confrontations : quel chemin pour aller vite et loin selon une boussole pas trop désalignée de la mienne ?
+Lorsqu'on travaille dans une niche intellectuelle ou un mouchoir de poche, on ressent rapidement de manière aiguë des besoins : de faire comprendre ce que l'on fait, de marques de reconnaissances qu'on fait quelque chose d'utile et qui vaut la peine, de respect du travail accompli ou poursuivi voire d'être un modèle à suivre, de camaraderie voire de loyauté, d'inspiration voire d'exaltation, de sympathie ou d'avoir au moins quelqu'un·e à qui se plaindre, et pas seulement un canard en plastique.
+
+Mais, il ne va pas de soi que rallier des co-équipier.res permette de produire de logiciels plus corrects et progressistes (une page Wikipédia est généralement moins didactique (présentation n'allant pas du simple au correct), moins claire sur ses biais et moins cohérente/architecturée qu'un ouvrage à un, deux, voire trois auteur.res). Avec du temps il m'a été possible de faire quelques uns de ces logiciels. Et je pourrais **probablement** les faire progresser et les maintenir tous (par vague) encore un moment (et trouver à force de persévérance plus de confort et de reconnaissance).
+
+Il ne va donc pas de soi qu'il soit pertinent d'être pro-actif dans la recherche de co-équipier.res, aux besoins, intérêts et aspirations potentiellement conflictuels avec les miens. Il vaut mieux être seul que mal accompagné. Alors que pourtant une équipe peut avancer plus vite, et qu'un groupe d'équipes peut prendre de meilleures décisions qu'une seule personne ou qu'une seule équipe de personnes aux parcours et sensibilitées similaires.
+Mais en l'état actuel même les personnes les plus alignées avec les préoccupations que j'ai, peuvent difficilement savoir ce que j'ai fait qui peut leur être utile et ce dont j'ai besoin pour faire progresser ces ouvrages. Ce n'est pas simplement parce que mes travaux sont trop peu aboutis pour le grand public ou parce que je suis trop radical ou trop perfectionniste ou trop pénible que depuis 10 ans je n'ai jamais été rejoint par personne (dév ou pas) sur mes travaux, et que seuls mes contributions pour corriger des bugs sont acceptées (et pas les contributions les plus substancielles). C'est aussi parce que je ne suis pas dans un cadre permettant à autrui de prendre au sérieux mes travaux (sans expertise préalable), et de clarifier comment contribuer à tenir ces travaux avec moi.
+
+La bonne nouvelle c'est que si je ressens ce besoin de compagnie dans une niche intellectuelle extrèmement petite, alors globalement les autres personnes se trouvant dans cette niche, doivent ressentir la même chose. Ce que semble confirmer le voyage de Sandy Maguire. L'existence quelque part d'une e-compagnie potentielle semble donc certaine, du moins sur certaines préoccupations, et il suffirait d'augmenter la luminosité de mes travaux vers Internet pour la rallier, sur le même principe que gagner au loto est improbable, sauf si l'on joue des milliards de coups. Mais, sans noyau dur préexistant, s'ouvrir aussi largement c'est s'exposer à se retrouver avec toute sorte de gens pénibles par bien d'autres aspects, ne se révélant pas forcément aux premiers échanges.
+
+La bonne nouvelle c'est que cette peur parfaitement raisonnable, est très **probablement** réciproque chez des personnes réfléchies ou expérimentées sur les relations sociales, ce peut donc être un critère pour apprécier des futurs éventuels co-équipier.res. Cela incite donc à des collaborations très ponctuelles (bug report, écriture d'un papier), voire non-concertées (écriture ou utilisation de bibliothèques logicielles) ou très circoncises (par logiciel ou par domaine de compétences ou par préoccupation) ou très progressives (bénévoler avant de se faire embaucher, faire preuve de ses préoccupations prioritaires avant d'accéder à des postes de pouvoirs-responsabilités).
+
+Il apparaît donc important de circonscrire voire compartimenter clairement l'organisation du travail, selon des interfaces d'intégration plus ou moins renégociables, mais toujours spécifiques, cependant pas nécessairement par mission, mais plutôt par préoccupation, acceptant toutes d'être minoritaires au sens deleuzien du terme, c'est-à-dire n'étant chacune qu'une préoccupation parmi d'autres. De sorte à ne pas renégocier toutes les interdépendances à la fois lorsqu'une personne porte une préoccupation qui est adverse à la notre. Toutefois, une seule association est trop petite pour faire société en interne de manière aussi idéalisée. Ce ne pourra donc être qu'une approximation.
+
+## Objectif
+### Objectif minimal : améliorer les conditions de travail
+Je veux un bureau avec de l'électricité à volonté et tout le confort. J'en ai marre de vivre sur la brèche, dans le froid ou la fumée, sans électricité ni Internet ou sans calme studieux, sans ordinateurs et serveurs adaptés et dédiés à mon travail.
+
+### Objectif modeste : mieux offrir en partage le travail effectué
+### Objectif honorable : trouver des personnes avec qui travailler
+### Objectif ambitieux : trouver des financements
+### Objectif maximal : impulser une logiciellerie libriste de progiciels
+Produire des progiciels libres qui soient utiles à une pluralité d'organisations à lucrativité limitée, aux divers coeur de métier et responsabilités dans la société ou bien en concurrence, mais qui n'ont pas en interne ou au sein de leurs collectifs déjà constitués, les ressources ou les compétences d'opérer ou de développer des logiciels adaptés à leurs préoccupations et respectueux de leurs libertés et de leur autonomie, mais peuvent, pour résoudre certains problèmes communs, se retrouver dans Sourcephile pour co-construire des logiciels libres répondant mieux à leurs besoins que d'autres logiciels existant répondant prioritairement à d'autres intérêts.
+
+## Inspirations
+### Théorie de la justification
+De Luc Boltanski et Laurent Thévenot, dans leur ouvrage « De la justification. Les économies de la grandeur ».
+- https://fr.wikipedia.org/wiki/%C3%89conomies_de_la_grandeur
+
+### Pluralisme des préoccupations
+De Laurent Mermet, dans le séminaire « Recherche Environnementale sur la Société » (RES)
+- https://youtube.com/playlist?list=PL8xv7G5rx2K6YDVrqP2-LtxVmox
+
+## « Let's break into teams » : une préoccupation de référence pour tous.tes
+Parmi une multitude de personnes, plusieurs référentiels de valeurs, niveaux d'attentes, choix de ce qui est pertinent, et types de raisonnement sont possibles, mais il n'est généralement pas possible de construire et tenir un nombre d'analyses aussi grand que le nombre de personnes concernées par une situation donnée, d'autant plus que cela demande un travail et un courage tels que peu de personnes ont la motivation et les compétences de tenir une préoccupation numériquement minoritaire dans un groupe donné. La question dès lors est de commencer à construire et tenir les analyses pertinentes **dans le cas spécifique** de la construction de cette logiciellerie particulière, réunissant ici et maintenant telles personnes et pas telles autres.
+
+Tenir une préoccupation est un vrai corps de métier, depuis lequel se construit une vraie culture propre à sa discipline de pensée, et donc une même personne physique ne peut tenir correctement qu'une seule préoccupation à la fois, et ne peut en changer qu'après des efforts soutenus sur plusieurs années. Dès lors, pour mieux analyser, justifier, négocier voire défendre chaque préoccupation, le principe premier de l'organisation doit être de se répartir en équipes référentes chacune d'une préoccupation. Et dans un second temps, de se répartir éventuellement par produit, par territoire ou autre.
+
+Cultiver ces savoirs et soucis hétérogènes est indispensable pour résoudre au mieux les problèmes, mais pour ne pas se quereller à coups d'agressions inter-disciplinaires risquant de rajouter des problèmes de personne en plus des différends, une telle organisation doit être fondée sur une convention critique claire de respect mutuel.
+Autrement dit, s'il est nécessaire que chaque membre actif s'engage en endossant le portage d'une préoccupation de référence comme guide de son analyse, mettant ses désirs et ses soucis avant le reste dans chaque situation pertinente pour elle afin de chercher à ce qu'elle soit ici décisive, il est tout ausi nécessaire que chaque membre actif respecte que d'autres portent d'autres causes tout aussi légitimes que la sienne, et garde donc une écoute respectueuse face aux autres problèmes des autres préoccupations, qui sont aussi des responsabilités collectives qui doivent être tenues. Le rôle de chaque équipe est donc d'être claire et résolue sur ses propres besoins fondamentaux, et ouverte aux besoins fondamentaux des autres équipes. 
+
+Dans la pratique d'une petite association et surtout au début, certains membres actifs devront assurer plus de fonctions qu'ils ne le souhaitent et peuvent, c'est-à-dire être référents du portage de préoccupations secondaires tant que nul autre ne les tiendra pour sa préoccupation de référence première. Clarifier cela semble un bon moyen de permettre à autrui de venir se faire une place reconnue dans l'association, et de délester ou renouveller ses anciens membres.
+
+Et dans la pratique d'une association quasi-unipersonnelle, cela me réduit à construire seul les possibles pour négocier avec moi-même, pour décider à quoi renoncer, pour imaginer de nouveaux résultats et pour construire des alternatives qui n'emportent pas ma faveur mais sont nécessaires pour mieux apprécier les qualités de ce que je favorise.
+
+### La boussole scientifique : Sourcephile, Lab.
+- Valorise : la science, la recherche, la vérité scientifique, l'accumulation de preuves, les connaissances, la rationalité, la publication technique, l'expertise, le savoir-convaincre, le partage des connaissances, le progrès, l'imagination, la réflexion, l'exactitude, les nouvelles perspectives, l'humilité, la persévérance, ...
+- Dévalorise : les prémisses cachés, l'inexactitude, les croyances infondées, les idées reçues, l'opinion, la flagornerie, le mensonge, l'omission, ...
+- Métiers : développeur, chercheur, ...
+- Outils : les codes sources, les langages de programmation, Haskell, PureScript, Idris, Nix, les billets de blog, les bug-trackers, les mailing lists, l'anglais, les conventions de Hackers, le CCC, les mathématiques, les papiers scientifiques, HAL, arxiv, les colloques scientifiques, les universités, le CNRS, l'Inria, les listes méls, ...
+
+### La boussole productive : Sourcephile, Prod.
+- Valorise : la productivité, l'efficacité, l'efficience, la rationalisation, la performance, l'ergonomie, le savoir-faire, la fiabilité, le mainstream, les chemins battus, la vie-privée, la sécurité, la standardisation, la stabilité, l'infrastructure, ...
+- Dévalorise : l'improductivité, l'inefficacité, l'innovation, la nouveauté, le changement, l'instabilité, le bleeding edge, ...
+- Métiers : technicien réseau, administrateur système, ingénieur informatique, ...
+- Outils : les exécutables, les serveurs, NixOS, GuixSD, Debian, OpenBSD, Grenode, Tetaneutral, les PTT de Tarnac, la Féderation FDN, le FrNOG, ...
+
+### La boussole indépendante : Sourcephile, Corp.
+- Valorise : vaincre, la compétitivité, la mise en concurrence, la divergence, la stratégie, la ruse, la force, la réthorique, l'ambiguïté, le secret, le renseignement, les affaires fructueuses, le marchandage, savoir faire des affaires, l'intérêt des relations, le réseau, la négociation, le pragmatisme, le financement, la rentabilité, le partenariat, l'entreprenariat, l'affrontement, l'exclusion, le licenciement, le distributif, la divergence, la hiérarchie, le mérite, l'autonomie, l'indépendance, la liberté, l'évitement, l'initiative, la défiance, le contrôle, la dureté, le retour sur investissement, ...
+- Dévalorise : perdre, la défaite, le renoncement, l'indésirable, ne pas être compétitif, ...
+- Métiers : administrateur, directeur, manager, ...
+- Outils : le planning, les deadlines, les subventions, les dons, les ventes, l'échelle des salaires, les distinctions, les prix, ...
+
+### La boussole coopérative : Sourcephile, Coop.
+- Valorise : convaincre, la qualité des relations, la coopérativité, la collaboration, la coordination, la convergence, la participation, l'adhésion, la justification, la démocratie, le pluralisme, le vote, le tirage au sort, le procès, le recours à un tiers, la délégation, le faire société, les formalités, la comptabilité, le respect des règles et procédures, la veille législative, l'organisation, l'institution, l'unité, avancer avec le groupe, jouer en équipe, l'interdépendance, la solidarité, l'égalité, l'équité, l'équilibre, l'inclusion, l'embauche, l'écriture inclusive, le logiciel libre, la confiance, l'éthique, le salariat, le salaire socialisé, les promotions, les conditions de travail, ...
+- Dévalorise : l'autorité injustifiée, l'exclusion, la division, l'individualisme, jouer solo, l'arbitraire, l'illégalité, les inégalités politiques, sociales ou économiques, ...
+- Métiers : comptable, secrétaire, avocat, juriste, syndicaliste, ...
+- Outils : les logiciels de bureautique, LibreOffice, les outils de gestion, l'Économie Sociale et Solidaire, les chambres régionale de l'ÉSS, les URSCOP, les syndicats la CGT, Sud, la CFDT, le Régime Général, la Sécurité sociale, les URSSAF, la médecine du travail, le code du travail, le contrat de travail, ...
+
+### La boussole domestique : Sourcephile, Home
+- Valorise : le social, la médiation, la modération, la commune humanité, le respect des personnes, le savoir-vivre, la convivialité, la communauté, la bienveillance, la tolérance, la bienséance, la discrétion, l'informel, la fidélité, la fraternité, la camaraderie, les pratiques d'usage, les ajustements informels, la douceur, ...
+- Dévalorise : l'impolitesse, la vulgarité, la traîtrise, le manque de respect, le logiciel privateur, ...
+- Métiers : ...
+- Outils : les codes de conduites.
+
+### La boussole essaimante : Sourcephile, Éditions
+- Valorise : la communication, l'advocacy, la plaidoirie, le prosélytisme, les relations publiques, l'image, l'image de marque, le style, le design, la démarcation, l'humour, la publicité, le savoir-paraître, la réputation, la renommée, le mainstream, la nouveauté, la publication didactique, l'information du public, l'éducation populaire, la sensibilisation, le rayonnement, la restitution, la vulgarisation, la pédagogie, l'instruction, l'enseignement, l'open source, l'open data, ...
+- Dévalorise : la banalité, l'indifférence, le méconnu, la désuétude, l'ennuyeux, l'incompréhensible, ...
+- Métiers : community manager, enseignant-chercheur, ...
+- Outils : les forums, Les Rencontre Mondiales du Logiciel Libre, le Capitole du Libre, les Journées du Logiciel Libre, le Chaos Computer Camp, les outils de dessin, Gimp, Inkscape, Blender, les billets de blog, les podcasts, les tutoriels, ...
+
+### La boussole environnementale : Sourcephile, World
+- Valorise : la sobriété énergétique, l'abstinence, les sources d'énergies renouvellables, le réemploi, le recyclage, la biodiversité, l'alimentation décarnée, ...
+- Dévalorise : la pollution, l'obsolescence programmée, les externalités négatives, ...
+
+## « Tout le monde autour de la table » : composition des fonctions
+Règles de collaboration et de dialogue interdisciplinaires.
+
+### « Sur la table » : transparence de l'assemblée générale
+La transparence totale n'est pas un gage de coopération, encore moins de relations pacifiées, surtout lorsqu'il faut résoudre des enjeux distributifs importants. Par conséquent, l'assemblée générale n'est pas un lieu où chaque fonction de l'association doit mettre sur la table ses objectifs et autres informations sans discernement, sans précaution de ne pas augmenter la violence des situations, car un partage total de l'information ne favorise pas forcément des relations pacifiées et une coopération complète, pas plus qu'elle n'aide forcément à résoudre des enjeux distributifs. Il est donc tout à fait acceptable que la transparence créée par la publication rende tacite ou repousse à d'autres endroits plus discrets une partie de la communication. L'intérêt principal de cette transparence des débats entre fonctions de l'association est d'accentuer la contrainte des normes morales dans les justifications avancées, et de permettre de rendre des comptes au personnes du public qui supportent l'association par toute sorte de contributions, sans les obliger à devenir membre de l'association, mais en leur montrant honnêtement le fonctionnement auquel elles auront à faire si elles décident d'aventure de devenir membre.
+
+### « Sous la table » : les réunions
+
+## « Help needed » : rallier des associé.es
+Il est fortement souhaitable que je trouve à m'associer avec des personnes desquelles je sâche assez bien à quoi m'attendre, car les pratiquant depuis un moment, dans un milieu professionnel et en mode professionnel ! Mais je ne connais pas de telles personnes compétentes dans les domaines qu'il faudrait, ni même désirant le devenir. Il me faudra donc faire des inférences, des paris et aller progressivement. L'idéal serait d'être rejoint par des personnes de mon niveau ou plus dans l'informatique, et d'un niveau équivalent ou plus dans les autres domaines nécessaires. Autant j'aime partager mes connaissances, autant je n'aime pas prendre les gens par la main et les babysitter.
+
+Cependant il est très improbable que j'ai le budget pour assumer leur niveau de compétence et donc les retenir face aux offres concurrentes de hauts revenus, plans de carrière, direction de forces de travail, déploiements de machines puissantes, etc., et face aux coûts de leurs trains de vie. Dès lors je ne peux qu'essayer de faire vibrer d'autres cordes et attracteurs : camaraderie, entraides, utilités, libertés, expérimentations, ... et espérer que des personnes compétentes et disponibles accordent suffisament de valeur à tout cela pour être prêtes à sacrifier significativement ces avantages personnels auxquels elles pourraient prétendre.
+
+### Demande de ralliement
+#### Spontanée
+Une demande ciblée peut se faire suite à la rencontre d'une contribution intéressante, sur un champ commun.
+
+Exemple :
+> Bonjour Martin,
+> 
+> Julien, développeur Haskell comme toi. Je cherche de l'aide pour porter le logiciel totophile. Serais-tu intéressé pour en parler ?
+
+#### À l'aveugle
+- Billet de blog
+- Annonce sur le Fediverse
+- Annonce sur HaskellNews
+
+## « To do the right thing » : prendre le temps de se justifier
+Même lorsqu'on n'est pas en conflit les un.es avec les autres, on peut avoir des raisonnements différents depuis des référentiels différents. Desfois les gens ne sont pas braqués mais de totale bonne foie, et ne sont pas en mode stratégique mais tentent de coopérer, mais pour autant le fait de chercher l'action juste n'est pas trivial, et il faut avoir une discussion.
+
+## Rédaction de statuts
+Un statut c'est ce qui ne bouge pas, c'est ce qui est stabilisé.
+Les statuts sont là pour sécuriser les personnes qui travaillent, et pour apporter des garanties aux tiers.
+
+### Intérêt général
+- https://www.service-public.fr/associations/vosdroits/F34246
+
+#### Avantages
+Demander l'intérêt général permet de déterminer clairement le statut fiscal de l'association ou d'une partie de ses activités, sectorisées au niveau comptable. Autrement dit de savoir ce qui est ou n'est pas assujetti aux impôts commerciaux (TVA, IS, CET).
+
+Un organisme d’intérêt général peut délivrer des reçus fiscaux à ses donateur.rices et membres afin qu'ils puissent bénéficier d'une réduction d’impôt sur le revenu ou sur les sociétés.
+Par exemple : https://soutenir.framasoft.org/fr/defiscalisation/
+
+#### Démarches
+Il faut que l'association :
+- ait un caractère philanthropique, éducatif, scientifique, social, humanitaire, sportif, familial ou culturel,
+- ou concourt à la mise en valeur du patrimoine artistique, à la défense de l'environnement naturel ou à la diffusion de la culture, de la langue et des connaissances scientifiques françaises.
+
+La demande se fait via le formulaire : https://bofip.impots.gouv.fr/bofip/635-PGP.html
+En courrier recommandé avec accusé de réception, à la direction départementale des finances publiques du siège social de l'association. La demande peut aussi faire l'objet d'un dépôt contre décharge. L'administration doit statuer dans les 6 mois.
+
+#### Conditions
+Si une association décide de mener une activité lucrative, elle peut continuer d'être exonérée des impôts dits commerciaux, si elle remplit toutes les conditions suivantes :
+- sa gestion est désintéressée,
+- ses activités commerciales ne concurrencent pas le secteur privé,
+- l'activité lucrative représente une part marginale du budget de l'association et ses activités non lucratives restent prépondérantes.
+
+L'existence d'un secteur lucratif ne remet pas en cause la qualification d'intérêt général d'une association.
+Toutefois, les versements effectués n'ouvrent droit à réduction d'impôt que si les dons restent affectés directement et exclusivement au secteur non lucratif. De même des valeurs financières transférées du secteur lucratif vers le secteur non lucratif n'ouvrent pas droit à réduction d'impôt en faveur du mécénat. 
+
+Les dons ne doivent pas être requis pour obtenir quoi que ce soit (un accès ou un service), point sur lequel les Cyclofficines ont vu leur demande retoquée.
+
+#### Resources
+- https://www.service-public.fr/associations/vosdroits/F31838 "Une association à but non lucratif peut-elle avoir une activité commerciale ?"
+- https://www.assistant-juridique.fr/sectorisation_activite_lucrative.jsp "Comment sectoriser les activités lucratives d'une association ?"
+
+### Utilité sociale
+### Reconnaissance d'utilité publique ?
+Cet reconnaissance permet d'obtenir une légitimité ainsi que des avantages économiques (recevoir des donations ou des legs), juridiques (se constituer partie civile) ou encore techniques (mise à disposition de fonctionnaires territoriaux). Il faut 3 ans d'existence, environ deux cents de membres, un but d'intérêt général, des statuts démocratiques, une comptabilité rigoureuse, un montant annuel minimum de ressources estimé à 46K€. Cela ne semble donc pas à l'ordre du jour.
+
+## Rédaction d'un règlement intérieur
+## Rédaction d'un code de conduite
+Voir la DDC4.
+
+## Travaux futurs
+
+# Critiques
+## Inconvénients
+### Il faut commencer à deux
+Problème : une association étant un contrat de droit privé, il faut être minimum 2 personnes dès le début. Il n'existe pas d'organisation, il va donc falloir passer par la négociation-initiation. Donc faire attention aux discours ambiguës que je reçois, ceux où l'on entend ou ressent que la personne dit ou se met à dire des choses ambiguës, ou ne fait pas ou plus des choses qui doivent être faites et qu'elle pourrait faire : « hmm.. tu n'es n'est pas totalement engagée.. hmm!.. ».
+
+Réponse : sévy peut s'occuper de la compta, mais pas du cahier des charges ni rien des soucis d'une co-« porteuse du projet associatif ».
+
+### Chaque révision officielle des statuts est^Wétait payante
+Problème : la révision des statuts auprès de la préfecture n'est pas payante, mais leur publication au journal officiel l'est.
+
+Réponse : on a plutôt intérêt à bien penser les statuts dès le début en ce qui concerne l'intérêt général, l'utilité sociale et les principes coopératifs. Cependant ce n’est plus le cas désormais : https://www.service-public.fr/associations/actualites/A13768
+> L'ensemble des publications au Journal officiel des associations et fondations d'entreprise (JOAFE), ainsi que celles liées aux comptes annuels des associations et des organisations syndicales et professionnelles, sont gratuites depuis le 1er janvier 2020.
+
+### S'en tenir au pied de la lettre : quelle limite à la tenue d'une préoccupation ?
+Problème : se répartir les rôles et les missions afin de tenir des analyses à fond, de ne pas mélanger les arguments et de ne pas perdre les objectifs en y allant au couteau si nécessaire favorise la critique, mais toute analyse a ses limites et la déployer à l'extrème peut mener à des absurdités dans certaines situations.
+
+Réponse : ce n'est pas la même chose d'avoir une mission claire, que d'être borné.e et donc ne pas accepter d'arbitrages en balayant d'un revers de main toute objection comme dans une dérive sectaire. Ou d'être cyniques en essayant de pousser ses intérêts particuliers sous le nom d'intérêt général, lequel ne peut émerger que de la composition de toutes les préoccupations, et pas d'une seule.
+
+### Être sur la défensive en permanence
+Problème : tenir certaines préoccupations nécessite d'aller constamment sensibiliser, argumenter voire lutter contre ses camarades, or jouer les mouches du coche peut être épuisant physiquement et pour les nerfs, au point de perdre le recul nécessaire pour ne pas être sur la défensive en permanence.
+
+Réponse : il est important de bien reconnaître la légitimité du rôle de chacun.e.
+
+### Ne pas perdre de vue la matérialité logicielle des effets visés et produits : quelles garanties et quels détournements d'énergies ?
+Problème : je ne veux m'encombrer que le moins possible des contraintes ou tâches qui ne concernent pas directement la recherche, le développement et l'expérimentation des seuls outils dont je ressens qu'ils peuvent être utiles à des personnes ayant les mêmes préoccupations que moi. Et je veux me laisser au maximum le droit à l'échec - à ne pas "délivrer", à changer et rechanger, à prioriser et reprioriser ce que je fais, au grès de mes vagues d'intérêts, et des solutions que je souhaite approfondir et appliquer.
+
+Mes trois intérêts principaux à faire ou rejoindre une structure sont de chercher du soutien moral, éventuellement économique et parfois technique. En rendant mieux visible que, moi aussi, je me préoccupe de faire tel ou tel outil en m'y prenant de telle ou telle manière, et donc en appelant à l'aide d'autres personnes disposées à collaborer de manière concertée avec moi dans ce sens. Plus de collaboration concertée ne signifie pas nécessairement que les outils seront forcément meilleurs, mais si peu de concertation, si peu d'aide comme actuellement est assez épuisant voire peu rassurant quant à l'avenir.
+
+Réponse : il s'agit de ne pas perdre de vue en chemin qu'une telle association doit être là avant tout et surtout pour améliorer notre situation collective par rapport à l'informatique, et pas par exemple pour maintenir des emplois ou des logiciels à l'utilité douteuse. Ça, moi ça ne m'intéresse pas, et si je rejoins cette association c'est pour faire des choses concrètes susceptibles d'améliorer notre prise en charge collective de l'informatique, mais est-ce que cela va de pair avec une nouvelle mise à l'épreuve de mon énième conception de l'organisation collective ? De toute façon il y a une organisation collective qui émerge en analysant des résultats : quelle soit concertée ou aveugle, juridique ou informelle, appropriée ou impensée. Et dans tous les cas si le moindre logiciel attire un jour l'intérêt d'autrui sans organisation pour le porter, il vaudra mieux avoir déjà commencé à réfléchir sérieusement à comment s'organiser, pour être prêt à assumer une bonne dynamique communautaire de logiciel libre.
+
+Dans cette dynamique, je peux porter pour un temps plusieurs préoccupations, mais j'aimerais assez rapidement faire des pas en arrière pour laisser la place à d'autres, pour me concentrer sur ce qui concerne surtout la recherche scientifique.
+
+## Questions non-résolues
+### Avec qui s'associer ?
+Ou plus exactement, avec qui s'associer comme co-porteur du projet associatif.
+Force est de constater que les refus des personnes qui ne sont pas informaticiennes ou qui font ni du Haskell ni du NixOS me limitent aux personnes partageant les mêmes compétences que moi. Ce qui ne garantit en rien qu'elles partagent les mêmes préoccupations que moi (programmation fonctionnelle, logiciel libre, démocratie critique, régime général). Autant de niches intellectuelles, dont l'intersection est quasiment nulle (on en croise quand même sur le Fediverse, mais pas nécessairement dans le même besoin d'association que moi).
+
+## Alternatives
+### Continuer de manière informelle
+Liberapay et Stripe devraient suffire pour recevoir des dons.
+Cependant cela ne procure pas du tout la sécurité du salariat.
+
+### Rejoindre GNU
+- htps://gnu.org
+
+GNU a une renommée dans le monde informatique, ce qui peut rassurer voire rallier des supports. Cependant la forge logicielle (savannah.gnu.org) qui est proposée n'est pas du tout fiable (complètement hors-ligne ce 25 nov. 2019), donc déjà ça ne démontre pas d'un grand sérieux dans la gestion des ressources, une fois la page des statistiques revenue en ligne, on constate qu'il n'y a quasiment plus d'activité sur savannah GNU ou non-GNU, toutefois https://git.savannah.gnu.org/cgit montre presque une centaine de dépôts actifs (les non-actifs sont faussement indiqués avec une dernière activité de 3 semaines, ce qui ne correspond pas au log de leurs commits). De GNU, la structuration a l'air un plus verticale (Chief GNUisance) que par préoccupation. Les contraintes forcées sur les formats (TexInfo, gettext) demanderaient des efforts techniques pour adapter symantic-document. Point positif, le transfert du copyright n'est pas requis, après tout, devoir imposer le respect de la GPL semble un problème très improbable, réglé généralement à l'amiable si je me souviens bien du constat d'Eben Moglen (voir aussi : gpl-violations.org). Un point intéressant est l'utilisation d'Hydra (et donc de NixOS), et bien sûr également l'existence de GNU Guix.
+Dans tous les cas, rejoindre le projet GNU ne permet pas d'avoir un salaire, et donc ne répond pas à la préoccupation économique. 
+
+### Rejoindre Gnome
+Un dépôt de projets morts ?
+
+### Rejoindre Apache
+- https://incubator.apache.org
+- http://labs.apache.org
+
+Apache a une renommée dans le monde informatique, ce qui peut rassurer voire rallier des supports. C'est une personne juridique qui relève du droit du Delaware (USA), pas du droit Français, ce qui n'est pas tellement rassurant sur le plan juridique. D'autant plus qu'Apache n'accepte que la participation d'individus, pas d'autres personnes morales, et par conséquent pas d'une assoce française pour me protéger par ailleurs. C'est de l'open source sans copyleft, pas du logiciel libre : obligation d'utiliser une licence Apache. L'utilisation de Git est expérimentale, SVN semble favorisé (normal, c'est une production d'Apache). La mesure du consensus a l'air davantage prise au sérieux qu'ailleurs, un mélange de scrutin majoritaire et de "consensus paresseux" : https://www.apache.org/foundation/voting.html
+L'organisation interne a l'air de se faire essentiellement par projet, pas par préoccupation. Les individus parlent en leur nom propre, et peuvent donc sauter d'une préoccupation à l'autre, et pas seulement d'une casquette à l'autre.
+Les services proposés aux développement sont décents, sans être hors de portée d'une petite assoce : https://www.apache.org/dev/services.html
+Quasiment aucun des projets Apache n'est en Haskell, ceux qui le sont (Thrift, OpenWhix) offrent juste des interfaces comme ils en offrent pour d'autres langages.
+Dans tous les cas, la clarification publique de leur fonctionnement est une source d'éducation, d'inspiration et d'exemplarité précieuse : https://www.apache.org/foundation/how-it-works.html
+
+### Faire une micro-entreprise ou EURL
+
+### Rejoindre une CAE
+Une CAE (Coopérative d'Activité et d'Emploi) permet de bénéficier du statut de salarié dans une grande entreprise, tout en gardant une maîtrise de son travail concret comparable à un.e artisan.ne. Une CAE facilite aussi l'obtention de marchés (gros chiffre d'affaires, coopération internes, support administratif, ...).
+Cependant il peut y avoir un problème d'assurance quand il s'agit de base de données (raison invoquée par Coopaname pour ne pas me prendre circa 2013).
+Mais surtout, cette structure n'est pas adaptée à recevoir des dons, ni aux petits salaires qui vont avec. 
+
+### Faire une Scic
+Une Scic est une (Société Coopérative d'Intérêt Collectif).
+Une Scic permet du bénévolat et du salariat tout comme une association.
+Une Scic est adaptée à la collaboration avec les entités publiques (mairies, collectivités territoriales, ....), ce qui n'est pas d'actualité pour l'instant.
+Une Scic impose des collèges de vote pondérés à priori. Cette pondération est compatible avec le Jugement Majoritaire (il suffit de donner un nombre de bulletins au prorata de cette pondération), mais oblige à décider à priori quelles seront les préoccupations ou combinaisons de préoccupations qui vont s'affronter. Et donc, oblige de décider à priori et pour toutes les décisions, quelles seront les préoccupations ou combinaisons de préoccupations qui pourront être décisives en dernière instance, ou ne pourront jamais l'être. Ce fonctionnement est d'une rigidité défavorable à la composition (et recomposition) du pluralisme de nos préoccupations, que je souhaite explorer avec Sourcephile.
+
+### Faire une association par but de logiciels
+Cela semble à terme être le meilleur moyen de faire société, de mieux construire des préoccupations différentes et de ne pas forcer "une même taille pour tout" ("one size fits all"). Mais le coût supplémentaire au démarrage est trop important (j'ai déjà du mal à faire une assoce, alors plusieurs..). Cependant, avoir un objet spécifique est important pour faire converger des supports par ailleurs divergents. Il faudra donc veiller à bien s'organiser en équipes aux préoccupations différenciées.
diff --git a/content/posts.old/programing-language-vision.md b/content/posts.old/programing-language-vision.md
new file mode 100644 (file)
index 0000000..10f9fea
--- /dev/null
@@ -0,0 +1,56 @@
+---
+title: My vision for the Future of Programing Languages
+updated: 2021-06-20
+lang: en
+summary: A description of a fictitious future programing language.
+tags:
+   - tech
+   - unison
+   - haskell
+   - fragnix
+   - grin
+---
+
+People who know me closer know that I have an obsession with finding the
+perfect programing language.
+
+# The Criteria
+
+
+I would like to have a programing language, that is
+
+ * strongly, statically and dependently typed
+   * Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer urna lacus, laoreet tempus tincidunt sit amet, dictum sed est. Duis leo ex, tincidunt eu nisi eu, porttitor euismod tortor. Curabitur faucibus quam leo, id bibendum justo eleifend id.
+ * purely functional
+ * uses immutable, semantic hashed, content-addressed code and dependency management
+ * and has automatic, static memory management.
+
+Of course what you also always want from a programing language is a nice
+community, a rich ecosystem, good tooling and obviously a fast execution, but I
+won’t be talking about those here …
+
+## The Type System
+
+I think in the last years static, strong typing has increasingly become
+mainstream. Examples include type annotations for Python, Typescript and Rust.
+Of course it is not enough to have static types, they also need to be
+sufficiently expressive. For me that means that you at the very least need
+Algebraic Data Types.
+
+Still, in this post I am dreaming, and so I would like to have dependent types
+(with type erasure).  I know a lot of people are apprehensive of the "dark type
+level magic" Haskell offers today. But I am quite convinced that type level
+programing will get easier and more intuitive once reasoning on the type level
+works exactly the same as on the term level.
+
+One application for which I personally would like good dependent types support
+is tracking matrix dimensions at the type level, this is currently possible but
+tedious in Haskell. But of course there are lots and lots of other use cases
+for dependent types.
+
+## Purely functional
+
+I wont argue much for this. But in my opinion it is absolutely a must have.
+Tracking side effects and having referential stability change how you think
+about a program and enable declarative programing.
+
diff --git a/content/posts.old/sourcephile-statuts.md b/content/posts.old/sourcephile-statuts.md
new file mode 100644 (file)
index 0000000..b3c0968
--- /dev/null
@@ -0,0 +1,274 @@
+---
+title: Statuts de Sourcephile
+updated: 2020-03-07
+lang: fr
+summary: Brouillon de statuts associatifs pour Sourcephile
+license: CC-BY-SA-4.0
+tags:
+   - Juridique
+   - Brouillon
+---
+
+# Introduction
+
+En adhérant aux présents statuts, nous formons une association régie par la loi du 1er juillet 1901 et ses décrets d’application. Dans ce document nous statuons sur les dispositions fondamentales et stables qui nous lient ou qui s’adressent à des tiers, et nous les complétons par un règlement intérieur contenant les dispositions plus à même de changer ou qui n’intéressent que les membres de notre association, mais s’appliquent néanmoins à l’ensemble des membres, au même titre que les dispositions des présents statuts.
+
+# Dénomination sociale
+Notre association a pour titre : « Sourcephile ».
+
+# Objet social
+Notre association a pour objectif de nous aider à faire progresser l’informatique libre ou ouverte, selon sept préoccupations de référence plus ou moins complémentaires ou contraires selon les situations, que chaque membre **peut** construire et invoquer pour convaincre les autres membres, et **doit** réciproquement accepter de composer avec :
+
+1. Une préoccupation pour la **science**, désirant avant tout la production de connaissances permettant d'élucider la frontière de tension entre possible et interdit non négociable, en se souciant notamment de construire des arguments pour convaincre des pairs, et en envisageant le plus concrètement possible des actions possibles, dans différents scénarios prévisibles, ou pour ouvrir de nouveaux possibles.
+2. Une préoccupation pour la **production**, désirant avant tout la productivité en termes d'efficacité et de routine, en se souciant notamment de susciter la gratitude d'usager.res.
+3. Une préoccupation pour la **coopération**, désirant avant tout la découverte de relations mutuellement bénéfiques, ou justifiées entre nos membres ou avec des tiers, en se souciant notamment de permettre à nos membres de construire des critiques de leurs propres actions et d'en contrôler les résultats.
+4. Une préoccupation pour l’**indépendance**, désirant avant tout porter l'intérêt de notre association là où elle a des enjeux distributifs, en se souciant notamment de se préparer à exploiter des situations favorables à notre objet social et que tous nos membres mènent des actions qui convergent selon une stratégie qui tienne la route.
+5. Une préoccupation pour la **camaraderie**, cherchant avant tout le respect mutuel entre nos membres, en se souciant notamment de sécuriser les conditions de travail matérielles et psychologiques des membres de l'association.
+6. Une préoccupation pour l’**essaimage**, désirant avant tout soutenir des tiers également intéressés par faire progresser l'informatique libre, en se souciant notamment de la réutilisation, de l'explication, de la modification et du repartage de recettes de fabrication par le plus grand nombre.
+7. Et une préoccupation **environnementale**, désirant avant tout à minimiser notre consommation de ressources polluantes ou nuisibles à la biodiversité, en se souciant notamment de sobriété énergétique.
+7. Et une préoccupation **environnementale**, désirant avant tout à minimiser notre consommation de ressources polluantes ou nuisibles à la biodiversité, en se souciant notamment de sobriété énergétique.
+7. Et une préoccupation **environnementale**, désirant avant tout à minimiser notre consommation de ressources polluantes ou nuisibles à la biodiversité, en se souciant notamment de sobriété énergétique.
+7. Et une préoccupation **environnementale**, désirant avant tout à minimiser notre consommation de ressources polluantes ou nuisibles à la biodiversité, en se souciant notamment de sobriété énergétique.
+7. Et une préoccupation **environnementale**, désirant avant tout à minimiser notre consommation de ressources polluantes ou nuisibles à la biodiversité, en se souciant notamment de sobriété énergétique.
+7. Et une préoccupation **environnementale**, désirant avant tout à minimiser notre consommation de ressources polluantes ou nuisibles à la biodiversité, en se souciant notamment de sobriété énergétique.
+7. Et une préoccupation **environnementale**, désirant avant tout à minimiser notre consommation de ressources polluantes ou nuisibles à la biodiversité, en se souciant notamment de sobriété énergétique.
+
+# Siège social
+Le siège social de notre association est fixé à Gentioux-Pigerolles, en Creuse, en France.
+
+# Durée sociale
+La durée de notre association est indéterminée.
+
+# Membres
+Pour être membre il faut :
+
+- Communiquer à notre fonction de modération les informations personnelles ou officielles nécessaires à l’adhésion (nom et prénoms, adresse postale complète et adresse mél), afin qu’après avoir constaté qu’aucun critère de radiation ne s’applique, elle effectue les démarches induites par l’adhésion.
+- Respecter les présents statuts, notre règlement intérieur ainsi que notre code de conduite.
+- Choisir et s’aquitter pour chaque année civile, lors du renouvellement de son adhésion, d’une cotisation à prix libre.
+
+Nous accueillons dans notre association toute personne physique dès lors qu’elle manifeste sa volonté de soutenir notre objet social et s’engage pour cela à prendre ses responsabilités en tant que membre. Nous accueillons également dans notre association toute personne morale, de droit privé ou public, française ou étrangère, dès lors qu’elle est à lucrativité limitée et manifeste sa volonté de soutenir notre objet social, et nomme (avec son accord) une unique personne physique membre pour représenter son unité de volition.
+
+Notre association distingue quatre catégories d’engagement de membres correspondant à quatre niveaux d’engagement possibles : membres soutiens, membres usagers, membres contributeurs et membres responsables. Chacune de ces catégories d’engagement impose des conditions à remplir pour y être admis et les droits et devoirs qui y sont attachés. L’identité civile et les affiliations professionnelles de tout membre contributeur ou membre responsable **doivent** être rendues publiques.
+
+## Membres soutiens
+Personnes physiques ou morales qui souhaitent apporter un soutien moral voire financier à cette association, sans donner davantage de leur personne. Leur droits et devoirs sont similaires aux membres usagers à la différence que les membres soutiens ne sont pas pris en compte pour fixer les quorums.
+
+Pour être membre soutien une personne **doit** :
+
+- Se déclarer membre soutien lors du renouvellement de son adhésion auprès de notre fonction de modération.
+
+En tant que membre soutien :
+
+- Elle **peut** proposer la constitution de toute nouvelle fonction auprès de notre assemblée générale.
+- Elle **peut** participer à notre assemblée générale en tant que membre d’une de nos fonctions primordiales des tiers.
+
+## Membres usagers
+Personnes physiques ou morales qui souhaitent manifester leur usage ou besoin des produits public de cette association, voire accéder à des services ou prestations réservé.es aux membres, et y exercer un rôle plus direct et concerné que le public : d’orientation générale et de contrôle général.
+
+Pour être membre usager une personne **doit** :
+
+- Soumettre une demande à notre fonction de modération de devenir membre usager pour bénéficier d’un bien ou service de notre association, afin qu’elle l’accepte ou lui oppose un refus motivé.
+- S'acquitter d'une cotisation de fonctionnement ou d'un niveau supérieur.
+
+En tant que membre usager :
+
+- Elle **peut** proposer la constitution de toute nouvelle fonction auprès de notre assemblée générale.
+- Elle **peut** participer à notre assemblée générale en tant que membre d’une de nos fonctions primordiales de consommation.
+
+## Membres contributeurs
+Personnes physiques ou morales qui souhaitent apporter des contributions supervisées voire mentorées à cette association et y exercer un rôle d’implémentation et de conseil. Les différentes natures et différents niveaux possibles des contributions des membres contributeurs sont explicité·es dans le règlement intérieur.
+
+Pour être membre contributeur une personne membre **doit** :
+
+- Soumettre sa candidature à la fonction à laquelle elle souhaite contribuer. Elle sera membre (co-)contributeur de cette fonction dès lors et tant que les membres responsables de cette fonction jugeront bon qu’il en soit ainsi.
+- Convenir aux termes de notre Convention des Contributeurs Sourcephile, destinée aux personnes physiques ou pour personnes morales selon ce qu'il est, laquelle lui permet de transférer les droits sur ses contributions aux productions de notre association qui permettent de garantir la gestion commune en logiciel libre ou ouvert, par la personne morale de notre association.
+
+Chaque membre contributeur **peut** :
+
+- Participer à notre assemblée générale au nom des fonctions dont il est membre contributeur.
+- Participer, avant leur éventuelle date de clôture, aux délibérations de notre assemblée générale et des fonctions dont il est membre responsable.
+
+## Membres responsables
+Personnes physiques qui souhaitent apporter un travail soutenu ou une autre contribution majeure à cette association, et exercer un rôle assidu de pilotage et de gestion de l’une de ses fonctions primordiale. Les différentes natures et le niveau précis des responsabilités des membres responsables sont explicité·es dans le règlement intérieur. Les membres responsables peuvent être bénévoles ou salarié.es de l’association (les membres salarié·es peuvent accéder à la fonction primordiale de direction qui est bénévole, mais **ne doivent pas** excéder un quart des membres directeurs, pour ne pas remettre en cause la gestion désintéressée de notre association).
+
+Toute personne membre responsable **doit** :
+
+- Être élue par notre assemblée générale suite à sa candidature pour une fonction primordiale. Son mandat durera pour l'année civile. Ce mandat pourra être révoqué ou renouvellé lors de toute assemblée générale.
+- Rejoindre notre fonction de direction selon un tour de rôle déterminé par notre dispositif de reLOTO.
+- S'acquitter d'une cotisation de fonctionnement ou d'un niveau supérieur.
+- Participer à notre assemblée générale au nom des fonctions dont elle est membre responsable.
+- Participer, avant leur éventuelle date de clôture, aux délibérations de notre assemblée générale et des fonctions dont elle est membre responsable.
+- Transmettre définitivement à la personne morale de notre association ses droits patrimoniaux sur ses contributions aux productions de notre association.
+- Démissionner en écrivant à notre fonction de direction. S'il ne reste aucune personne membre responsable de la fonction concernée, notre fonction de modération **doit** convoquer une assemblée générale extraordinaire pour remplacer ce membre. L'absence sans justification à l'une des réunions, entretiens ou autre rendez-vous d'une fonction dont elle est membre responsable, est un motif de révocation, par notre fonction de modération.
+# L’assemblée générale
+Une personne == une mention par casquette, i.e. pour chacune de ses fonctions primordiales.
+Une fonction primordiale == un profil de mérite divisé par le nombre de ses fonctionnaires.
+
+La souveraineté sur notre association appartient à son assemblée générale, qui est composée collégialement des fonctions primordiales, dont le rôle est de prévoir, organiser, commander, coordonner et contrôler, de manière générale, nos actions pour notre association.
+
+Chacune des fonctions primordiales qui la compose **doit** :
+
+- Être traitée à égalité par les dispositions statutaires ou règlementaires de notre association (isonomie).
+- Être écoutée par les autres fonctions de l’assemblée (iségorie). Pour cela elle **doit** demander à ce que notre fonction de direction inscrive à l'ordre du jour de l'assemblée générale les Demandes de Critiques et rapports qu'elle souhaite voir considéré.es.
+- Être informée de toute information des autres fonctions primordiales ne relevant pas du respect de la vie-privée de personnes physiques (transparence), en particulier aucun « secret des affaires » ne lui est opposable.
+- Peser dans les délibérations par un profil d’opinions de ses membres divisé par le nombre de ses membres (isocratie). Chaque membre ne **peut** apporter et modifier pour chaque délibération qui n'est pas close, qu'une unique mention, accompagnée d'un commentaire, par fonction primordiale dont il est membre (co-)contributeur ou (co-)responsable.
+
+Chaque assemblée générale **peut** débattre de décisions mais **ne doit pas** être le lieu des décisions, qui se prennent ensuite jusqu'à une date fixée. Les décisions ainsi construites obligent toutes les fonctions et tous les membres de l’association, sans exception.
+
+Cette assemblée générale **ne doit pas** être le lieu de discussions stratégiques qu'il n'est pas dans l'intérêt de notre association qu'elles soient tenues en public, ces discussions incombant à notre fonction de direction. Elle **doit** être un lieu d’écoutes attentives et de justifications publiques entre les préoccupations de nos fonctions primordiales, chacune à cette occasion rendant public et expliquant, ou demandant à d’autres fonctions de rendre public et d’expliquer, les informations qu’elle juge pertinentes de rendre public. Afin que chaque fonction primordiale puisse expliquer ses besoins et ses intérêts, proposer des problèmes pertinents à résoudre de son point de vue même si les résoudre nécessite des actions de la part d’autres fonctions primordiales, avancer des solutions en cherchant à les justifier pour les faire accepter par les autres fonctions primordiales ou du moins notre fonction de direction, écouter les critiques des autres fonctions primordiales, puis tenir ou réviser ses propres propositions.
+
+Au moyen de notre dispositif des Demandes de Critiques, chaque fonction primordiale de l'assemblée générale **doit** :
+
+1. Écouter les rapports et explications des autres fonctions primordiales.
+2. Débattre de leurs propositions d'action.
+3. Permettre à chacun de ses membres de délibérer dessus, au moyen de notre dispositif de Jugement Majoritaire.
+
+Les Demandes de Critiques ordinaires **devant** être adressées à notre assemblée générale sont :
+
+- La révision de nos statuts.
+- La révision de notre règlement intérieur.
+- La révision de notre code de conduite.
+- La reconnaissance de toute nouvelle fonction primordiale à des membres contributeurs ou membres responsables, qui lui font cette demande pour mieux porter une préoccupation de référence synergique avec l’objet social de l’association, mais différenciée de celles des fonctions existantes.
+- La reconnaissance de toute nouvelle boussole analytique ayant droit de cité dans l'association, par toute fonction primordiale qui lui fait cette demande pour mieux porter une préoccupation de référence synergique avec l’objet social de l’association, mais différenciée de celles des boussoles existantes.
+- Le mandat de notre fonction de direction pour ester en justice ou défendre notre association dans toute action judiciaire.
+- La dissolution de toute fonction primordiale.
+- L’adhésion à d’autres associations, fédérations d’associations, syndicats ou autres organisations.
+- La dissolution volontaire de notre association. Auquel cas notre assemblée générale **doit** désigner des membres responsables pour former une fonction de liquidation investie des pouvoirs les plus étendus pour :
+  - Distribuer le boni de liquidation de notre association et ses ressources patrimoniales au Centre National de la Recherche Scientifique (CNRS) ou à des Entreprises de l’Économie Sociale et Solidaire (EESS) au sens de l’article 1 de la loi n°2014-856 du 31 juillet 2014, dont l’objet social est similaire au notre, conformément au décret du 16 août 1901.
+  - Permettre la reprise des apports avec droit de reprise par leurs apporteur·ses, si et tel que prévu dans leur convention d’apport, à leur valeur numéraire, sans aucun réajustement sur l’inflation ou autre. Si l’association n’a pas assez de ressources pour honorer entièrement la reprise des apports en numéraire, les ressources sont reprises au prorata de ces apports.
+  - Publier la dissolution de notre association.
+
+Les rapports ordinaires **devant** être adressés à notre assemblée générale sont :
+
+- Rapports d’activité des fonctions primordiales.
+- Rapports moraux des fonctions primordiales.
+- Rapport d’orientation des fonctions primordiales.
+- Rapports comptables de la fonction d'intendance.
+
+Par défaut et après chaque révision du sujet de la délibération, le vote des parties prenantes (re)part à blanc. Le vote par procuration **n’est pas autorisé**, le quorum est atteint lorsque suffisament de bulletins ont été enregistrés auprès de la fonction secrétaire pour que la mention majoritaire ne puisse plus changer sans changement de bulletins déjà enregistrés, ou retrait ou ajout de parties prenantes à la délibération.
+
+# Les fonctions primordiales de gestion
+## La fonction de direction
+Une fonction primordiale de direction est constituée pour se préoccuper de piloter notre association, en analysant et se positionnant stratégiquement chaque situation, afin d'obtenir des résultats conformes aux orientations fixées par son assemblée générale.
+Elle est formée pour chaque année civile d'un membre responsable de chacune des autres fonctions primordiales, nommé par notre dispositif de reLOTO.
+
+Elle **doit** :
+
+- Prévoir et fixer des objectifs et actions intermédiaires que **devront** accomplir nos autres fonctions primordiales, contrôler leur réalisation, et prendre d’éventuelles mesures correctives.
+- Assurer les démarches administratives de l’association, dues aux autorités publiques, à nos membres ou bien à nos fonctions, qui ne sont pas déjà la prérogatives explicite d’autres fonctions.
+- Faire connaître dans les 3 mois à la préfecture les changements de membres survenus en son sein.
+- Assurer la responsabilité d’employeur (notamment le paiement des salaires), en étant appuyée notamment par notre fonction d’intendance pour effectuer toute démarche relative à l’emploi.
+- Aider toute fonction à pourvoir au remplacement d’un de ses membres démissionnaire.
+- Fixer et faire connaître l'ordre du jour des assemblées générales.
+
+Elle **peut** :
+
+- Prendre à bail ou acquérir tout bien nécessaire aux besoins de l’association.
+- Valider la sollicitation de toute subvention envers l’association, sur conseil notamment de notre fonction d’intendance.
+- Déléguer, à un ou plusieurs membres d'une fonction primordiale de gestion, l’habilitation de remplir toutes les formalités de déclaration et de publication prescrites par la législation et tout autre acte nécessaire au fonctionnement de l’association. Ces délégations sont inscrites au règlement intérieur.
+- Autoriser ou interdire tout contrat ou convention passé·es entre l’association, d’une part, et un membre actif, son ou sa conjoint·te, un·e proche ou toute personne avec qui ce membre actif a une communauté d’intérêts, d’autre part, dans les conditions précisées par les articles L 612-4, L 612-5 et D 612-5 du Code de commerce, et fait activement connaître cette information à notre assemblée générale.
+- Déplacer le siège social de notre association n’importe où en France métropolitaine, sachant que les tribunaux reconnus compétents par l’association pour toutes les actions en justice la concernant, sont ceux ayant juridiction où se situe son siège social, alors même qu’il s’agirait de contrats passés dans des établissements sis dans d’autres lieux.
+- Décider de l’allocation de nos ressources envers du salariat, en décidant d’embaucher, de licencier, de renouveller ou pas des contrats de travail. Les formalités administratives **doivent** être déléguées à notre fonction d’intendance, les entretiens d’embauche et les entretiens professionnels annuels **doivent** être délégués à des membres de toute fonction primordiale qui se sent concernée.
+- Fixer ou reporter une éventuelle date de clôture sur les délibérations de notre assemblée générale, en veillant à permettre la décision éclairée et réfléchie du plus grand nombre.
+
+Chacun·e de ses membres **doit** :
+
+- Exercer ses fonctions d’administrateur·rice bénévolement. Les frais et débours occasionnés pour l’accomplissement de son mandat, peuvent être remboursés sur justificatifs, après accord préalable de notre fonction d'intendance, et si les finances de notre association permettent l’ordonnance du remboursement.
+- Avoir atteint la majorité légale en France (18 ans).
+- Être conscient que la responsabilité juridique de l’association en matière de gestion, incombe, sous réserve d’appréciation souveraine des tribunaux, aux membres responsables de sa fonction de direction. Que par conséquent, la responsabilité civile personnelle de dirigeant·tes de notre association **peut** être engagée vis-à-vis de notre association, par des actes fautifs dans le cadre de l’exercice de leur contrat de mandat, et vis-à-vis de tiers, si ces dirigeant·tes commettent une faute, notamment de gestion financière, considérée comme détachable de leurs fonctions, ce qui **peut** leur entraîner une obligation de payer des dommages et intérêts. Par ailleurs, en cas de liquidation judiciaire, ces dirigeant·tes ayant commis certaines fautes de gestion définies par la législation **peuvent** être condamné·es à assumer tout ou partie des dettes de notre association. Ces exceptions mentionnées, aucun membre de l’association n’est personnellement ou solidairement responsable des engagements financiers contractés par l’association. Seules les ressources financières de l’association répondent de ses engagements.
+
+Chacun·e de ses membres **peut** :
+- Démissionner par écrit auprès de notre fonction de modération, auquel cas notre fonction de secrétariat **doit** nommer promptement un membre remplaçant par un nouvel usage de notre dispositif de reLOTO n'excluant des nominations possibles que ce membre et les autres membres en vigueur de notre fonction de direction. 
+- Être révoqué par délibération de notre assemblée générale.
+
+## La fonction de secrétariat
+Une fonction primordiale de secrétariat est constituée pour se préoccuper d’enregistrements, d’archivages, et d’écritures concernant la gestion de notre association.
+
+Elle **doit** :
+
+- Maintenir à disposition des membres un dispositif de gestion des droits et devoir des membres appelé « règlement intérieur ».
+- Maintienir à disposition des membres un dispositif de dissensus formel appelé « Demandes de Critiques » précisé dans le règlement intérieur et visant à confronter des arguments sur leur seul mérite et non sur la base de qui les avance, ainsi que des espaces de discussions où elle assure la modération pour garantir le respect mutuel et le civisme d’éventuels désaccords.
+- Maintenir à disposition des membres en fonction un dispositif de délibération appelé « Jugement Majoritaire » précisé dans le règlement intérieur et visant à mesurer précisément le niveau actuel de consensus ou de dissensus des délibérations.
+- Maintenir à disposition des membres un dispositif de tirage au sort publiquement vérifiable appelé « reLOTO » précisé dans le règlement intérieur .
+- Tenir à jour le registre des délibérations de l’assemblée générale, ainsi qu’enregistrer les documents apportés aux débats, en veillant à leur archivage et en les mettant à disposition de l’ensemble des membres et du public.
+- Tenir à jour le registre des procès-verbaux de l’association qu’elle **doit** établir et faire signer par un·e membre de la fonction de direction.
+
+## La fonction d’intendance
+Une fonction primordiale d’intendance est constituée pour se préoccuper d’obtenir et de distribuer les ressources matérielles ou financières de notre association, et d’administration dans le domaine de la comptabilité, des biens, du contentieux avec des tiers et de l’expertise légale afférente.
+
+Elle **doit** :
+
+- Chercher à obtenir les financements nécessaires au fonctionnement, aux investissements et à l’équilibre de l’association.
+- Tenir à jour le registre des comptes de l’association par une comptabilité en partie double de toutes les recettes et de toutes les dépenses de l’association. En particulier elle **doit** produire un bilan comptable annuel, un compte de résultat annuel et les annexes pertinentes.
+- Assurer la transparence de l’information financière de l’association auprès de ses membres, de ses partenaires et du public. Notamment en publiant chaque trimestre un rapport comptable.
+- Effectuer une veille légale du Code du travail, du Code de la Sécurité sociale, des conventions collective pertinentes, et de toute autre loi, décret ou règlement concernant notre association.
+- Contrôler que les ressources financières de notre association proviennent :
+  - de cotisations,
+  - de dons manuels avec ou sans affectation,
+  - d'apports avec ou sans droits de reprises,
+  - de subventions,
+  - d’appels publics à la générosité, de financements participatifs, et toute autre collecte de fonds privés,
+  - de ventes de produits, de services ou de prestations fournies par notre association, qui **peut** exercer des activités économiques de façon habituelle au sens de l’article L442-7 du Code de commerce.
+  - de toute autre ressource autorisée par la loi.
+- Contrôler que notre association agit sans but lucratif, au moyen d’une gestion désintéressée selon l’article 261 du Code général des impôts (petit d du 1° de l’alinéa 7) et le Bulletin Officiel des Impôts 4 H-5-06, en suivant les dispositions suivantes :
+  - Sa fonction de direction **doit** être effectuée à titre bénévole, par des personnes n’ayant elles-mêmes, ou par personne interposée, aucun intérêt direct ou indirect dans les résultats de son exploitation.
+  - Elle **ne doit pas** procéder à la moindre distribution directe ou indirecte de bénéfice, sous quelque forme que ce soit. En outre, si elle dégage des excédents, elle **ne doit pas** les accumuler pour les placer dans une épargne au taux d’intérêt supérieur à l’inflation, mais les affecter à des besoins ultérieurs ou au financement de projets liés à son objet social.
+  - Ses membres et leurs ayants droit **ne doivent pas** pouvoir être déclarés attributaires d’une part quelconque de son actif, sous réserve du droit de reprise des apports.
+- Contrôler que notre association soit alignée sur la pratique salariale d’une Entreprise Solidaire d’Utilité Sociale (ESUS) en appliquant une échelle de rémunération limitée et une distribution politique de la valeur ajoutée selon un barème satisfaisant les conditions de l’article L.3332-17-1 du Code du travail, en suivant les dispositions suivantes :
+  - le salaire net minimum horaire **doit** être supérieur ou égal à cent vingt pourcent du salaire minimum de croissance (Smic) net français,
+  - le salaire net horaire maximal pratiqué dans l’association **doit** être inférieur ou égal à quatre fois le salaire net horaire minimum pratiqué dans l’association.
+- Tenir à jour le registre unique du personnel de l’association.
+- Établir des contrats de travail par écrit.
+- Opérer le paiement de nos fournisseurs aussi promptement que possible (généralement pas au-delà de 30 jours après la fourniture effective).
+- Contrôler que notre association ne valorise économiquement aucune donnée à caractère personnel.
+- Contrôler que tous les membres sont à jour de leur cotisation, notamment avant chaque assemblée générale ou lorsqu’ils demandent à bénéficier de biens ou de services réservés aux membres de l’association.
+- Permettre la reprise des apports avec droit de reprise, si et tel que prévu dans leur convention d’apport.
+- Réclamer leur cotisation aux membres qui refusent de mettre en place un prélèvement automatique.
+- Signaler à notre foncton de modération tout retard de paiement de cotisation.
+- Fixer les montants de chacun des quatre niveaux de cotisations de membres.
+- Rendre public l’identité civile ou sociale et les affiliations professionnelles des membres versant une cotisation d’investissement ou d’équilibre.
+- Faire ouvrir, garder et sécuriser les accès de tout compte bancaire au nom de l’association.
+
+## La fonction de modération
+Une fonction primordiale de modération est constituée pour se préoccuper des relations humaines de notre association.
+
+Elle **doit** :
+
+- Tenir à jour le registre des adhésions de l’association.
+- Autoriser ou opposer un refus motivé à toute demande concernant l’usage des biens et services proposés par l’association à ses membres.
+- Acter de l’obtention de la qualité de membre de l’association, et **doit** lui adresser par écrit une réponse positive ou un refus motivé dans les trois mois, l’adhésion étant réputée acceptée en l’absence de réponse au-delà de ce délai.
+- Acter la radiation de tout membre suite à :
+  - Sa déadhésion volontaire qui **doit** lui être présentée par écrit.
+  - Son décès (pour une personne physique).
+  - Sa dissolution (pour une personne morale).
+- Informer tout membre du motif de sa radiation prochaine, et l’inviter à réparer ses dommages ou présenter sa défense à notre fonction de modération, dans un délai de 30 jours, pour tout :
+  - Défaut de paiement de cotisation (signalé par notre fonction d'intendance), après relance par courrier ou courriel avec accusé de réception restée infructueuse sur la récupération de la cotisation 30 jours après réception de l'accusé de réception ou 60 jours après l'envoi.
+  - Juste motif (qui n’exempte pas de la cotisation annuelle due, au prorata des jours écoulés depuis le début de l’année civile), en cas :
+    - De manque à ses obligations de membre imposées par les présents statuts, notre règlement intérieur, ou notre code de conduite.
+    - De préjudice moral ou matériel à notre association ou à son objet social.
+    - De lucrativité limitée jugée non effective (pour une personne morale).
+- Entendre la défense et délibérer sur la radiation de tout membre. L'intéressé.e **peut** se faire assister pour sa défense de conseiller·res de son choix, et contester cette radiation devant le Tribunal de Grande Instance du siège social de notre association.
+- Convoquer, organiser et animer chaque trimestre une assemblée générale se tenant à tour de rôle dans les différents lieux d’attachement effectifs de l’association.
+- Assurer que les membres responsables **ne pouvant pas** se rendre physiquement aux assemblées générales **peuvent** y participer via de la messagerie instantanée, de la téléphonie ou de la visioconférence.
+- Convoquer, organiser et animer, de manière extraordinaire cette fois, une assemblée générale, aussi souvent qu'elle le souhaite ou qu’une autre fonction primordiale la lui demande.
+- Faire respecter l'ordre du jour des assemblées générales.
+
+# Les fonctions primordiales de production
+Les prérogatives des fonctions primordiales de production sont détaillées dans notre règlement intérieur.
+## La fonction d’informatique scientifique
+Une fonction primordiale d’informatique scientifique est constituée pour se préoccuper de recherche et de développement.
+## La fonction d’infrastructure informatique
+Une fonction primordiale d’infrastructure informatique est constituée pour se préoccuper d’opérer services informatiques à disposition du public ou des membres.
+## La fonction d’étude informatique
+Une fonction primordiale d’étude informatique est constituée pour se préoccuper de transmissions instructives, de conseils et de formations informatiques à l’attention du public ou des membres.
+
+# Les fonctions primordiales de consommation
+Les prérogatives des fonctions primordiales de consommation sont détaillées dans notre règlement intérieur.
+## La fonction des usagers
+Une fonction primordiale des usagers est constituée pour se préoccuper des besoins et intérêts des membres usagers des biens et services informatiques à disposition du public ou des membres.
+
+# Les fonctions primordiales des tiers
+Les prérogatives des fonctions primordiales des tiers sont détaillées dans notre règlement intérieur.
+## La fonction des soutiens
+Une fonction primordiale des soutiens est constituée pour se préoccuper des besoins et intérêts des membres soutiens de notre association.
diff --git a/content/posts.old/studien.md b/content/posts.old/studien.md
new file mode 100644 (file)
index 0000000..476129f
--- /dev/null
@@ -0,0 +1,88 @@
+---
+title: Studien
+lang: de
+date: 2017-06-13
+tags:
+   - non-tech
+   - statistics
+summary: Traue keiner Studie, die Du nicht selbst gefälscht hast …
+---
+
+Vor ein paar Tagen stellte man mir in einem Gespräch eine spannende Frage, die ungefähr so lautete:
+„Lässt Du Dein Weltbild durch Studien beeinflussen?“
+
+Die anwesenden Mathematiker waren sich alle sofort einig, dass das bei ihnen definitiv nicht der Fall ist.
+Ich war mir da bezogen auf mich nicht so sicher.
+Ich befinde mich da in einem unangenehmen Spagat.
+Einerseits kennt man Beispiel noch und nöcher für Fehler mit Statistik und Studien.
+Schlechte Randomisierung, zu kleine Stichproben, Experimenter-Effect, Excel-Bugs, 3D-Torten-Diagramme die Varianten und Möglichkeiten sind schier endlos.
+Ich habe gerade keine Links parat, aber ich hoffe ihr kennt die guten Beispiele, wie zum Beispiel die Schokoladendiät, die beim abnehmen hilft.
+Exemplarisch verweise ich auf [XKCD](https://www.xkcd.com/882/).
+Manchmal liegt es auch nicht an Unfähigkeit oder dem Problem, dass es einfach so unglaublich schwer ist gute Daten zu erheben, sondern ist bewusste Täuschung mit Zahlen [oder in der Darstellung](http://wy3mg1xgify37n21x223cw7xl1.wpengine.netdna-cdn.com/wp-content/uploads/2014/04/florida-gun-deaths-e1397682743475.jpg).
+In meiner nicht empirischen Wahrnehmung, haben Teile der Medien (jedenfalls die, die ich konsumieren) da in letzter Zeit ein verschärftes Auge drauf, was ich sehr begrüsse.
+Ich vermute, dass dies mit der generellen öffentlich wahrgenommenen Bedrohung der Wahrheit zusammen hängt.
+
+Aber wie ich schon vor ein paar Jahren bei [Eliezer Yudkowsky](http://lesswrong.com) las, „If correlation doesn’t imply causation, then what does?“
+(Unter diesem Namen findet man von dort aus auch diesen [Artikel](http://www.michaelnielsen.org/ddi/if-correlation-doesnt-imply-causation-then-what-does/) den schaue ich mir später nochmal genauer an.).
+Mein Punkt dabei ist: Warum sollte ich überspitzt gesagt überhaupt etwas glauben, was nicht aus einer Studie kommt?
+Natürlich ist unser Meinungsfindungsprozess etwas vielschichtiger als das. Es ist ja schon kompliziert zu sortieren, was tatsächlich Fakt ist (und damit durch eine Studie untersucht werden kann) und was Meinung.
+Viele Fakten ergeben sich auch aus unserer eindeutigen Wahrnehmung oder stützen sich auf Experimente.
+Aber da wird der Übergang schon fließend.
+Viele der in unserem Umfeld anerkannten Meinungen stützen sich natürlich auf Studien und prägen so sicher auch die Weltbilder der oben genannten Mathematiker.
+Das relevanteste Beispiel dazu ist mit Sicherheit die Medizin.
+Ganz besonders furchtbares Unwesen treiben Studien in den Ernährungswissenschaften.
+(Obwohl ich dieser Wissenschaft überhaupt nicht ihre Seriosität oder Berechtigung absprechen müsste, bezweifle ich, dass viel von dem, was im Volkswissen dazu ankommt, Wissenschaft ist.)
+
+In der Grundlagenvorlesungen zur Stochastik habe ich gelernt, dass es nur eine aussagekräftige Form der Studie gibt: Die randomisierte, kontrollierte und prospektive Studie.
+Das heißt mit zufälliger Stichprobe, Kontrollgruppe und nicht auf Basis einer retrospektiven Auswertung.
+Damit kommt so eine Studie, finde ich, schon ziemlich nahe dran an ein Experiment.
+Alle anderen Studien zeigen zwar Korrelationen auf, aber können nur als Hinweis auf möglich Kausalität interpretiert werden.
+Solche Korrelationen zu beobachten und unkommentiert in die Welt zu setzen, ist sicher häufiger als fahrlässig zu bezeichnen.
+
+Natürlich ist es häufig sehr schwierig - schlimmer! ich würde sagen: meistens unmöglich - solche Studien durchzuführen.
+Und selbst, wenn man sich an die obigen Regeln hält, kann noch alles möglich schiefgehen.
+Zum Beispiel muss ein Medikamententest doppeltblind erfolgen, um eine Verfälschung des Placebo-Effekts zu vermeiden.
+Im Endeffekt sind also Wissenschaften, die solche Studien benötigen schwer.
+Ernährungswissenschaften sind ein gutes Beispiel für eine aus meiner Perspektive sehr schwere Wissenschaft.
+Ich hoffe die Wissenschaftler_innen in den betreffenden Disziplinen, sind sich im Klaren darüber, wie schwer es ist, über solche Probleme überhaupt Aussagen zu machen.
+Leider verleitet der Wunsch, etwas Aussagen zu können, oder einfach der Drang zu publizieren, dazu dieses Problem eventuell aus den Augen zu verlieren.
+Ich kenne mich in diesen Teilen der Wissenschaft nicht gut aus, aber manchmal Frage ich mich, ob es nicht eigentlich zu kompliziert ist um dies überhaupt als Wissenschaft zu betreiben.
+Es ist kein Wunder, dass ich mich bevorzugt am anderen Ende des Spektrums befinde, in den Wissenschaften, wo Daten- und Faktenlage deutlich klarer ist;
+in meiner hier vorgestellten Charakterisierung also die „leichten“ Wissenschaften.
+(Das mag von außen freilich nicht immer so aussehen. Dadurch, dass Mathematik und Physik so „leicht“ sind, ist es aber ja nicht verwunderlich, dass man tiefere und komplizierter Strukturen verstehen kann.)
+
+Ich habe das Gefühl damit in der Zwickmühle zu sein.
+Einerseits führt mich jede neue Studie mehr in die Irre, andererseits will ich mich eigentlich nicht trauen irgendetwas ohne eine seriöse Studie zu glauben.
+Und gerade in den Bereichen Politik, Gesellschaft oder Psychologie, also den für das Leben wirklich spannenden Fragestellungen hört man andauernd von neuen Studien und Statistiken.
+(Eines meiner Standardbeispiele ist: „Hilft der Mindestlohn?“ Da gibt es Daten in jede Richtung.)
+Wenn ich ehrlich bin, neige ich vermutlich auch dazu, mir unter dem Datenmüll, der an mir vorbei strömt, mir immer das rauszupicken, was mir gerade gefällt.
+(Ich habe mal gelesen, dass Intelligenz mit Naivität korreliert ist. Ich finde mich ziemlich naiv …)
+Aber es sind eben auch diese Bereiche, in denen ich mich so sehr nach Fakten sehne.
+Ich denke man verliert viel zu häufig aus den Augen, wie sehr man sich an solchen Stellen mit jeder Äußerung auf dünnem Eis bewegt.
+
+Eine andere Perspektive auf das Problem hat mir dieser [Artikel (PDF)](http://www.ucl.ac.uk/Pharmacology/dc-bits/holmes-deconstruction-ebhc-06.pdf) aufgezeigt.
+Bei dem mir nicht wirklich klar ist, in wie fern ich die Position teilen kann.
+Es wird dort behauptet, dass die evidenzbasierte Medizin ein faschistoides System ist, weil sie keine anderen Meinungen akzeptiert.
+Das liest sich für mich sehr verwirrend, weil ich einerseits den Argumenten für dies Position zustimmen muss,
+aber andererseits das Problem daran nicht erkennen kann, weil mir der Ansatz der evidenzbasierten Medizin als der offensichtlich richtige erscheint.
+
+Zusammenfassend finde ich es also sehr schwierig einerseits das Gefühl zu haben, dass ich nichts glauben sollte, was nicht durch Studien gestützt wird, aber andererseits auch keiner Studien glauben sollte.
+Die Tatsache, dass dies Problematik Fragen betrifft zu denen einen fundierte Meinung wirklich nützlich wäre, macht das ganze nicht leichter.
+
+Es gibt natürlich die offensichtlich Antwort auf die Problematik.
+Meta-Studien, reproduzierte Studien, etc.
+Die Unmöglichkeit sichere Fakten zu erhalten hat mich nur mal wieder frustrierend getroffen.
+Vielleicht frustriert mich daran am meisten die Realisierung, dass in meinem Weltbild sicher ein Haufen falscher Annahmen ist, die auf der falschen Abwägung der obigen Problematik beruhen. Mein Fehler ist glaube ich tendenziell zu studiengläubig zu sein. Da heißt es in Zukunft mehr aufzupassen.
+
+**Update:**
+Mich erreichte folgende Anmerkung:
+
+> Zwei Anmerkungen aus den Wirtschaftswissenschaften: 
+>
+> (1) Die Verwendung von randomisierten kontrollierten Studien und ein stärkerer Fokus auf natürliche Experimente haben in den letzten 20 Jahren zumindest die Entwicklungsökonomie revolutioniert. Fragen wie "kaufen und verwenden Personen Anti-Moskito-Netze eher wenn sie sie umsonst bekommen, zu einem gestützten Preis oder zum Marktpreis?" haben hierdurch endlich Antworten bekommen, die zumindest im vorgegebenen lokalen Kontext belastbar sind und (natur-)wissenschaftlichen Ansprüchen gerecht werden. Für eine angenehm enthusiastische Darstellung siehe mein großes Idol Esther Duflo (ja, sie ist Französin): [https://www.youtube.com/watch?v=0zvrGiPkVcs](https://www.youtube.com/watch?v=0zvrGiPkVcs). Natürlich ist auch mit RCTs nicht alles Zucker, aber es ist ein riesiger Schritt nach vorn.
+>
+> (2) Die Mindestlohn-Literatur wurde in den 1990er Jahren durch David Card und Alan B. Krueger eben genau durch die Nutzung natürlicher Experimente revolutioniert. Zuvor hatten Ökonomen meist mithilfe von Regressionsmodellen (stark) negative Effekte gefunden. Card und Krueger nutzten aus, dass einer von zwei benachbarten US-Bundesstaaten, die beide zu Beginn den gleichen Mindestlohn hatten, seinen Mindestlohn erhöhte, um zu vergleichen wie sich die Arbeitslosigkeit und andere Arbeitsmarktvariablen im Fast-Food-Sektor - ein Sektor mit einem sehr hohen Anteil an Mindestlohnverdienern - entlang der Staatengrenze entwickelten; hierbei fanden Card und Krueger keinerlei Erhöhung der Arbeitslosigkeit und argumentierten, dass der Mindestlohn einerseits nicht allzu hoch (also nicht allzu "bindend") gewesen sein mag, aber auch, dass er vermutlich die Nachfrage nach FastFood-Produkten - und somit auch nach Arbeitskräften in diesem Sektor - erhöht und somit die gestiegenen Arbeitskosten kompensiert haben könnte. Lange Rede, kurzer Sinn: die Verwendung von quasi-experimentellen Designs in der Mindestlohnforschung gilt mittlerweile als Goldstandard. Leider konnte ich den Bundestag nicht davon überzeugen die Einführung des bundesweiten Mindestlohns auf Gemeindeebene zu randomisieren. :D 
+>
+> Des Weiteren: [http://tylervigen.com/spurious-correlations](http://tylervigen.com/spurious-correlations)
+
+Vielen Dank, Hector!
diff --git a/content/posts.old/test-tech.fr.md b/content/posts.old/test-tech.fr.md
new file mode 100644 (file)
index 0000000..4ef9969
--- /dev/null
@@ -0,0 +1,20 @@
+---
+title: This is a technical post
+date: 2021-12-10
+lang: fr
+summary:
+  Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer urna lacus, laoreet tempus tincidunt sit amet, dictum sed est. Duis leo ex, tincidunt eu nisi eu, porttitor euismod tortor. Curabitur faucibus quam leo, id bibendum justo eleifend id.
+tags:
+   - tech
+   - haskell
+---
+
+# Some title
+
+Lorem ipsum [Hello](../test-tech) dolor sit amet, consectetur adipiscing elit. Integer urna lacus, laoreet tempus tincidunt sit amet, dictum sed est. Duis leo ex, tincidunt eu nisi eu, porttitor euismod tortor. Curabitur faucibus quam leo, id bibendum justo eleifend id. Etiam fermentum ante sit amet fermentum viverra. Sed sed venenatis sapien. Vivamus consequat feugiat diam vel varius. Phasellus non efficitur diam. Duis sed consectetur elit, eu pretium nibh. Sed vehicula, felis ullamcorper blandit venenatis, velit purus egestas elit, et efficitur nulla enim vel ex.
+
+## Some sub title
+Curabitur nec ornare lacus. Nam vitae lobortis libero. Suspendisse laoreet tellus sed iaculis molestie. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus erat nisl, convallis eget molestie vel, luctus non purus. Suspendisse porttitor mi arcu, sit amet pulvinar nibh dapibus eget. Vestibulum laoreet, metus quis maximus auctor, mi urna elementum enim, vel porttitor lacus sem eget elit. Mauris vitae arcu quis odio vehicula hendrerit.
+
+Phasellus elementum ante sit amet dignissim mollis. Fusce mollis efficitur efficitur. Aenean fringilla lacus ac porttitor blandit. Donec a mauris nunc. Etiam luctus, dolor sit amet sollicitudin feugiat, est sem aliquet quam, ut iaculis tellus nunc eget orci. Ut rutrum justo vel pharetra blandit. Cras sodales ipsum lectus, et sodales purus laoreet non. Mauris elit dolor, molestie sed mauris congue, imperdiet scelerisque mauris. Nunc malesuada varius est sed interdum. Fusce venenatis libero tortor, ac sagittis erat blandit at.
+
diff --git a/content/posts.old/test-tech.md b/content/posts.old/test-tech.md
new file mode 100644 (file)
index 0000000..287df11
--- /dev/null
@@ -0,0 +1,30 @@
+---
+title: This is a technical post
+date: 2021-12-10
+lang: en
+summary: contribute back to low-tech free software communities
+tags:
+   - tech
+   - haskell
+---
+
+# Images
+## Lol
+- pictures: static/pics/lol
+  Some very long description to test the grid layout of captions, I said a very very very long text is needed to reach the last column of the grid and test
+
+## Lol
+- pictures: static/pics/lol
+  Some very long description to test the grid layout of captions, I said a very very very long text is needed to reach the last column of the grid and test
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer urna lacus, laoreet tempus tincidunt sit amet, dictum sed est. Duis leo ex, tincidunt eu nisi eu, porttitor euismod tortor. Curabitur faucibus quam leo, id bibendum justo eleifend id. Etiam fermentum ante sit amet fermentum viverra. Sed sed venenatis sapien. Vivamus consequat feugiat diam vel varius. Phasellus non efficitur diam. Duis sed consectetur elit, eu pretium nibh. Sed vehicula, felis ullamcorper blandit venenatis, velit purus egestas elit, et efficitur nulla enim vel ex.
+- pictures: static/pics/lol/piano-socks.jpg
+- pictures: static/pics/lol/piano-socks.jpg
+- pictures: static/pics/lol/piano-socks.jpg
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer urna lacus, laoreet tempus tincidunt sit amet, dictum sed est. Duis leo ex, tincidunt eu nisi eu, porttitor euismod tortor. Curabitur faucibus quam leo, id bibendum justo eleifend id. Etiam fermentum ante sit amet fermentum viverra. Sed sed venenatis sapien. Vivamus consequat feugiat diam vel varius. Phasellus non efficitur diam. Duis sed consectetur elit, eu pretium nibh. Sed vehicula, felis ullamcorper blandit venenatis, velit purus egestas elit, et efficitur nulla enim vel ex.
+
+Curabitur nec ornare lacus. Nam vitae lobortis libero. Suspendisse laoreet tellus sed iaculis molestie. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus erat nisl, convallis eget molestie vel, luctus non purus. Suspendisse porttitor mi arcu, sit amet pulvinar nibh dapibus eget. Vestibulum laoreet, metus quis maximus auctor, mi urna elementum enim, vel porttitor lacus sem eget elit. Mauris vitae arcu quis odio vehicula hendrerit.
+
+Phasellus elementum ante sit amet dignissim mollis. Fusce mollis efficitur efficitur. Aenean fringilla lacus ac porttitor blandit. Donec a mauris nunc. Etiam luctus, dolor sit amet sollicitudin feugiat, est sem aliquet quam, ut iaculis tellus nunc eget orci. Ut rutrum justo vel pharetra blandit. Cras sodales ipsum lectus, et sodales purus laoreet non. Mauris elit dolor, molestie sed mauris congue, imperdiet scelerisque mauris. Nunc malesuada varius est sed interdum. Fusce venenatis libero tortor, ac sagittis erat blandit at.
+
diff --git a/content/posts/a.md b/content/posts/a.md
new file mode 100644 (file)
index 0000000..9691dcf
--- /dev/null
@@ -0,0 +1,12 @@
+---
+title: Let's talk about A
+date: 2021-12-10
+lang: fr
+summary:
+  Lorem ipsum dolor sit amet.
+---
+
+# This is A
+Lorem ipsum [This is ../b](../b) and [This is b](b).
+
+Lorem ipsum [This is a/aa](a/aa).
diff --git a/content/posts/a/aa.md b/content/posts/a/aa.md
new file mode 100644 (file)
index 0000000..a6ca145
--- /dev/null
@@ -0,0 +1,10 @@
+---
+title: Let's talk about A
+date: 2021-12-10
+lang: fr
+summary:
+  Lorem ipsum dolor sit amet.
+---
+
+# This is A
+Lorem ipsum [This is ../b](../b) and [This is b](b).
diff --git a/content/posts/b.md b/content/posts/b.md
new file mode 100644 (file)
index 0000000..5d8c6a5
--- /dev/null
@@ -0,0 +1,10 @@
+---
+title: Let's talk about B
+date: 2021-12-10
+lang: fr
+summary:
+  Lorem ipsum dolor sit amet.
+---
+
+# This is B
+Lorem ipsum [This is ../a](../a) and [This is a](a).
diff --git a/content/special/index.md b/content/special/index.md
new file mode 100644 (file)
index 0000000..3829b5a
--- /dev/null
@@ -0,0 +1,7 @@
+---
+title: About
+summary: failed sourcerer student composing softwares
+lang: en
+---
+
+Blah balh blah..
diff --git a/content/special/not-found.md b/content/special/not-found.md
new file mode 100644 (file)
index 0000000..9fca88e
--- /dev/null
@@ -0,0 +1,6 @@
+---
+title: "404"
+lang: en
+---
+
+Sorry, I can‘t find this link for you.
diff --git a/content/special/projects.md b/content/special/projects.md
new file mode 100644 (file)
index 0000000..c660fc8
--- /dev/null
@@ -0,0 +1,47 @@
+---
+title: Projects
+lang: en
+---
+
+I maintain the following projects:
+
+### haskell-taskwarrior
+
+You can find the [taskwarrior](https://taskwarrior.org/) Haskell library on [Hackage](https://hackage.haskell.org/package/taskwarrior) and on [GitHub](https://github.com/maralorn/haskell-taskwarrior)
+
+---
+
+### nix-output-monitor
+
+A nifty little tool written in Haskell for making `nix-build` output more
+informative. It’s packaged in
+[nixpkgs](https://search.nixos.org/packages?query=nix-output-monitor) and you
+can contribute on [GitHub](https://github.com/maralorn/nix-output-monitor) or
+see the code [on my git
+server.](https://git.maralorn.de/nix-output-monitor/about)
+
+---
+
+### nixos-config
+
+I have a fairly extensive and intricate [config for nixos and home-manager](https://git.maralorn.de/nixos-config) on all my systems.
+
+---
+
+### blog
+
+You can find the source to this statically generated blog using Haskell and [Ema](https://ema.srid.ca/) on [my git server](https://git.maralorn.de/blog).
+
+---
+
+### kassandra
+
+A taskwarrior frontend written in Haskell with the frp-library [reflex-dom](https://reflex-frp.org/) currently only for my personal use. [My git server](https://git.maralorn.de/kassandra2/about), [GitHub Mirror](https://github.com/maralorn/kassandra)
+
+---
+
+### nixpkgs.haskellPackages
+
+I am one of (thankfully) several co-maintainers of the [Haskell packages](https://nixos.org/manual/nixpkgs/stable/#haskell) for [nixos](https://nixos.org).
+
+---
diff --git a/content/static/css/extra.css b/content/static/css/extra.css
new file mode 100644 (file)
index 0000000..a5db2ca
--- /dev/null
@@ -0,0 +1,244 @@
+html {
+  text-rendering: optimizeLegibility;
+  font-kerning: normal;
+}
+
+/* Links */
+a {
+  color: blue;
+}
+a:visited {
+  color: purple;
+}
+
+/* Table of Content */
+.toc a {
+  color: inherit;
+}
+.toc details > summary::marker {
+  color: white;
+}
+.toc-section-number {
+  color: rgba(194, 65, 12);
+  font-weight: bold;
+}
+.toc ul > li {
+  line-height: 1.3;
+  text-align: left;
+}
+.toc ul > li > ul {
+  margin-left: 1em;
+  padding-bottom: 1ex;
+  padding-top: 1ex;
+}
+.section-nav > a:visited { color:rgba(194, 65, 12, var(--tw-text-opacity)); }
+.section-nav > a:visited:hover { color:purple; }
+
+/* Tag */
+a.tag:visited { color:inherit; }
+a.tag:visited:hover { color:purple; }
+
+/* Summary */
+.summary p:first-child {
+  display: inline;
+}
+
+/* Titles */
+section h2, section h3, section h4, section h5, section h6 {
+  margin-bottom: 1ex;
+}
+
+/* Page */
+.page p,
+.page ul,
+.page ol {
+  margin-bottom: 2ex;
+}
+.page p:last-child,
+.page ul:last-child,
+.page ol:last-child {
+  margin-bottom:0;
+}
+
+/* Article */
+.article ul > li {
+  display: flex;
+  flex-direction: row;
+  margin-bottom: 2ex;
+}
+.article ol {
+  list-style-position: outside;
+  list-style-type: decimal;
+  padding-left: 3em;
+}
+.article ol > li {
+  margin-bottom: 2ex;
+}
+.article a::after {
+  background: #007bff;
+  content: "";
+  display: block;
+  height: 0.15em;
+  margin-top: -0.15em;
+  text-decoration: none;
+  transition: width 0.35s;
+  width: 0;
+}
+
+/* Pictures */
+div.pictures {
+  clear:right;
+}
+.pictures > ul {
+  display: grid;
+  grid-auto-flow: dense;
+  grid-gap: 1em;
+  /*grid-auto-rows: 300px;*/
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+}
+@media (min-width: 640px) {
+  .pictures > ul { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
+}
+.pictures > ul > li {
+  list-style-type: none;
+  position: relative;
+}
+.pictures > ul > li.highlight-box {
+  grid-row: span 2;
+  grid-column: span 2;
+}
+.pictures > ul > li.highlight-tall {
+  grid-row: span 2;
+}
+.pictures > ul > li.highlight-wide {
+  grid-column: span 2;
+}
+.pictures > ul > li img {
+  border: 1px solid black;
+  box-shadow: 1px 3px 8px rgba(0, 0, 0, 0.5);
+  display: block;
+  /*height: 100%;*/
+  width: 100%;
+  margin: auto;
+  object-fit: contain;
+}
+.pictures > ul > li span {
+  font-size: 1rem;
+  font-weight: bold;
+  color: #fff;
+  position: absolute;
+  right: 10px;
+  text-shadow: 2px 2px 6px rgba(0, 0, 0, 0.4);
+  top: 10px;
+}
+
+/*
+
+html {
+  font-family: helvetica, sans-serif;
+  font-size: 18px; }
+
+h1 {
+  font-family: 'BauerBodoniW01-BoldDisp', georgia, serif;
+  font-size: 2rem;
+  color: #fff;
+  margin: 0;
+  padding: 0.75em 0.25em;
+  background: #2c2c2c;
+  line-height: 1.1em; }
+
+ul {
+  padding: 0; }
+
+li {
+}
+
+li:focus {
+  border: 10px solid blue; }
+
+*/
+
+/*
+html {
+  font-family: 'AdobeGaramondW01-Regula', georgia, serif;
+  font-size: 18px;
+  margin-bottom: 200px; }
+
+main {
+  width: 80%;
+  margin: 2em auto; }
+
+article {
+  margin: 100px 0; }
+
+footer {
+  text-align: center;
+  margin: 3em 0 3em 0;
+  clear: both; }
+
+h1 {
+  font-family: 'BauerBodoniW01-BoldDisp', georgia, serif;
+  font-size: 3rem;
+  color: #64664A;
+  margin: 0 0 1em;
+  line-height: 1.1em;
+  -webkit-column-span: all;
+          column-span: all; }
+
+h2 {
+  font-family: 'BauerBodoniW01-BoldDisp', georgia, serif;
+  font-size: 1.5rem;
+  color: #64664A;
+  font-weight: 400; }
+
+p {
+  line-height: 1.5em; }
+
+article {
+  -webkit-column-width: 200px;
+          column-width: 200px;
+  -webkit-column-gap: 2em;
+          column-gap: 2em;
+  -webkit-column-rule: 1px solid #444;
+          column-rule: 1px solid #444; }
+  article img {
+    width: 100%;
+    margin: 1em 0;
+    display: block; }
+  article figure {
+    -webkit-column-span: all;
+            column-span: all;
+    margin: 1em 0;
+    font-style: italic;
+    color: #999; }
+
+ul {
+  -webkit-columns: 250px;
+          columns: 250px;
+  -webkit-column-break-inside: avoid;
+          break-inside: avoid;
+  margin: 0;
+  padding: 0;
+  list-style: none; }
+  ul img {
+    width: 100%;
+    display: block;
+    -o-object-fit: cover;
+       object-fit: cover;
+    margin-top: 0px;
+    margin-bottom: 20px;
+    border: 1px solid black; }
+
+section {
+  -webkit-column-width: 300px;
+          column-width: 250px; }
+  section div {
+    border: 1px solid black;
+    margin: 0 10px 20px;
+    padding-bottom: 1rem;
+    -webkit-column-break-inside: avoid;
+            break-inside: avoid; }
+  section h2, section p {
+    margin: 0 1rem;
+    margin-right: 1rem; }
+*/
diff --git a/content/static/css/windi-extras.css b/content/static/css/windi-extras.css
new file mode 120000 (symlink)
index 0000000..78fca7b
--- /dev/null
@@ -0,0 +1 @@
+/nix/store/mgy5jydnm8p1yh7wy9cks2dm4icrxqng-windi-extras.css
\ No newline at end of file
diff --git a/content/static/icons/open-iconic b/content/static/icons/open-iconic
new file mode 120000 (symlink)
index 0000000..88d25cb
--- /dev/null
@@ -0,0 +1 @@
+/nix/store/yaz54mz8zxb9knfdc6n7104rwywibwma-source
\ No newline at end of file
diff --git a/flake.lock b/flake.lock
new file mode 100644 (file)
index 0000000..6262d6a
--- /dev/null
@@ -0,0 +1,600 @@
+{
+  "nodes": {
+    "PyF": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "hls": "hls",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1641413315,
+        "narHash": "sha256-pXyrbXFHPTu0c9Pl3DilhrefLOEjZnnRfn0CYQTUOZQ=",
+        "owner": "guibou",
+        "repo": "PyF",
+        "rev": "49b0e482db4036811fb3d920d3187239595b1f68",
+        "type": "github"
+      },
+      "original": {
+        "owner": "guibou",
+        "ref": "main",
+        "repo": "PyF",
+        "type": "github"
+      }
+    },
+    "commonmark-simple": {
+      "inputs": {
+        "flake-compat": "flake-compat_2",
+        "flake-utils": "flake-utils_4",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1644029826,
+        "narHash": "sha256-Gin56NWJot7B8A8GxtIIgU46zyqMJ8o4NwupMrU73t4=",
+        "owner": "srid",
+        "repo": "commonmark-simple",
+        "rev": "3f83cbcecd8a72bc3ce04cd39a3035f1f414961c",
+        "type": "github"
+      },
+      "original": {
+        "owner": "srid",
+        "repo": "commonmark-simple",
+        "type": "github"
+      }
+    },
+    "ema": {
+      "inputs": {
+        "flake-compat": "flake-compat_3",
+        "flake-utils": "flake-utils_5",
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "pre-commit-hooks": [
+          "pre-commit-hooks"
+        ],
+        "url-slug": [
+          "url-slug"
+        ]
+      },
+      "locked": {
+        "lastModified": 1644980557,
+        "narHash": "sha256-WxbsBTREGXp1lHfQeFrwzpD9tjt0gDsNB4p98V7hKPY=",
+        "owner": "srid",
+        "repo": "ema",
+        "rev": "89c9f9234c47437513774efa7e24ef0bdd6427bb",
+        "type": "github"
+      },
+      "original": {
+        "owner": "srid",
+        "repo": "ema",
+        "rev": "89c9f9234c47437513774efa7e24ef0bdd6427bb",
+        "type": "github"
+      }
+    },
+    "flake-compat": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1627913399,
+        "narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-compat_2": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1641205782,
+        "narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-compat_3": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1641205782,
+        "narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-compat_4": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1641205782,
+        "narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-compat_5": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1627913399,
+        "narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-compat_6": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1641205782,
+        "narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-utils": {
+      "locked": {
+        "lastModified": 1637014545,
+        "narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_2": {
+      "locked": {
+        "lastModified": 1629481132,
+        "narHash": "sha256-JHgasjPR0/J1J3DRm4KxM4zTyAj4IOJY8vIl75v/kPI=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "997f7efcb746a9c140ce1f13c72263189225f482",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_3": {
+      "locked": {
+        "lastModified": 1638122382,
+        "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "74f7e4319258e287b0f9cb95426c9853b282730b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_4": {
+      "locked": {
+        "lastModified": 1642700792,
+        "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_5": {
+      "locked": {
+        "lastModified": 1642700792,
+        "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_6": {
+      "locked": {
+        "lastModified": 1619345332,
+        "narHash": "sha256-qHnQkEp1uklKTpx3MvKtY6xzgcqXDsz5nLilbbuL+3A=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "2ebf2558e5bf978c7fb8ea927dfaed8fefab2e28",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_7": {
+      "locked": {
+        "lastModified": 1638122382,
+        "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "74f7e4319258e287b0f9cb95426c9853b282730b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_8": {
+      "locked": {
+        "lastModified": 1631561581,
+        "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_9": {
+      "locked": {
+        "lastModified": 1642700792,
+        "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "gitignore": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1611672876,
+        "narHash": "sha256-qHu3uZ/o9jBHiA3MEKHJ06k7w4heOhA+4HCSIvflRxo=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "211907489e9f198594c0eb0ca9256a1949c9d412",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "hls": {
+      "inputs": {
+        "flake-compat": "flake-compat",
+        "flake-utils": "flake-utils_2",
+        "gitignore": "gitignore",
+        "nixpkgs": "nixpkgs",
+        "pre-commit-hooks": "pre-commit-hooks"
+      },
+      "locked": {
+        "lastModified": 1637415948,
+        "narHash": "sha256-GskoEhMpjBsV/FFGK01m13mkCALIJa8SmWD2HafsrMk=",
+        "owner": "haskell",
+        "repo": "haskell-language-server",
+        "rev": "b7e3a64ef9c09774397628a6a6a6ac799f871ffa",
+        "type": "github"
+      },
+      "original": {
+        "owner": "haskell",
+        "repo": "haskell-language-server",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1630887066,
+        "narHash": "sha256-0ecIlrLsNIIa+zrNmzXXmbMBLZlmHU/aWFsa4bq99Hk=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "5e47a07e9f2d7ed999f2c7943b0896f5f7321ca3",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixpkgs-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs_2": {
+      "locked": {
+        "lastModified": 1640328990,
+        "narHash": "sha256-KQbvJx4qO9bo04tfTZuISyY4vRC5k3ZB3lyLS21XWIw=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "ab93217a2b74a1c36bc892c14f44ee5959c33f12",
+        "type": "github"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "type": "indirect"
+      }
+    },
+    "nixpkgs_3": {
+      "locked": {
+        "lastModified": 1660908602,
+        "narHash": "sha256-SwZ85IPWvC4NxxFhWhRMTJpApSHbY1u4YK2UFWEBWvY=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "495b19d5b3e62b4ec7e846bdfb6ef3d9c3b83492",
+        "type": "github"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "type": "indirect"
+      }
+    },
+    "nixpkgs_4": {
+      "locked": {
+        "lastModified": 1641521701,
+        "narHash": "sha256-IuW+4EqqKjNdJ4yO0Qr8k63OkyV1TaF5vsrZzU3GCfI=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "d77bbfcbb650d9c219ca3286e1efb707b922d7c2",
+        "type": "github"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "type": "indirect"
+      }
+    },
+    "open-iconic": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1404337542,
+        "narHash": "sha256-rPQ9d7RM9mmZCIgf+QZHtYZrOg+veIc9yuTRNM8ahX8=",
+        "owner": "iconic",
+        "repo": "open-iconic",
+        "rev": "1d1e8885c5031874b32f4e480e371ce2b1c24144",
+        "type": "github"
+      },
+      "original": {
+        "owner": "iconic",
+        "repo": "open-iconic",
+        "type": "github"
+      }
+    },
+    "pre-commit-hooks": {
+      "inputs": {
+        "flake-utils": "flake-utils_3",
+        "nixpkgs": "nixpkgs_2"
+      },
+      "locked": {
+        "lastModified": 1624971177,
+        "narHash": "sha256-Amf/nBj1E77RmbSSmV+hg6YOpR+rddCbbVgo5C7BS0I=",
+        "owner": "cachix",
+        "repo": "pre-commit-hooks.nix",
+        "rev": "397f0713d007250a2c7a745e555fa16c5dc8cadb",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "pre-commit-hooks.nix",
+        "type": "github"
+      }
+    },
+    "pre-commit-hooks_2": {
+      "inputs": {
+        "flake-utils": "flake-utils_6",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1639823344,
+        "narHash": "sha256-jlsQb2y6A5dB1R0wVPLOfDGM0wLyfYqEJNzMtXuzCXw=",
+        "owner": "cachix",
+        "repo": "pre-commit-hooks.nix",
+        "rev": "ff9c0b459ddc4b79c06e19d44251daa8e9cd1746",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "pre-commit-hooks.nix",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "PyF": "PyF",
+        "commonmark-simple": "commonmark-simple",
+        "ema": "ema",
+        "nixpkgs": "nixpkgs_3",
+        "open-iconic": "open-iconic",
+        "pre-commit-hooks": "pre-commit-hooks_2",
+        "selfGit": "selfGit",
+        "tailwind-haskell": "tailwind-haskell",
+        "unionmount": "unionmount",
+        "url-slug": "url-slug",
+        "windiCSS": "windiCSS"
+      }
+    },
+    "selfGit": {
+      "flake": false,
+      "locked": {
+        "narHash": "sha256-lmEhSiZt5CyvtcqLhBRHHAVDe34mdZ2QEapQtEyqWhM=",
+        "path": "/home/julm/work/sourcephile/haskell/sourcephile-web/.git",
+        "type": "path"
+      },
+      "original": {
+        "path": "/home/julm/work/sourcephile/haskell/sourcephile-web/.git",
+        "type": "path"
+      }
+    },
+    "tailwind-haskell": {
+      "inputs": {
+        "flake-compat": [
+          "ema",
+          "flake-compat"
+        ],
+        "flake-utils": [
+          "ema",
+          "flake-utils"
+        ],
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "tailwind-nix": "tailwind-nix"
+      },
+      "locked": {
+        "lastModified": 1643931884,
+        "narHash": "sha256-qOFzT+Go5SGCGo5kBmLHNLgKAiJgnUcwe3NrS4TaZ0Q=",
+        "owner": "srid",
+        "repo": "tailwind-haskell",
+        "rev": "f5463f639f403751fb4701dd2aeadac32f90f65d",
+        "type": "github"
+      },
+      "original": {
+        "owner": "srid",
+        "ref": "master",
+        "repo": "tailwind-haskell",
+        "type": "github"
+      }
+    },
+    "tailwind-nix": {
+      "inputs": {
+        "flake-compat": "flake-compat_4",
+        "flake-utils": "flake-utils_7",
+        "nixpkgs": "nixpkgs_4"
+      },
+      "locked": {
+        "lastModified": 1643563783,
+        "narHash": "sha256-aFCT2NexGIgxeW32vfQcPuLlG2GhxkhIpSEuk3sXmOU=",
+        "owner": "srid",
+        "repo": "tailwind-nix",
+        "rev": "f199f8cf6855ad5ab0a56bbac90f07860fb400d7",
+        "type": "github"
+      },
+      "original": {
+        "owner": "srid",
+        "repo": "tailwind-nix",
+        "type": "github"
+      }
+    },
+    "unionmount": {
+      "inputs": {
+        "flake-compat": "flake-compat_5",
+        "flake-utils": "flake-utils_8",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1645223446,
+        "narHash": "sha256-NdWn6/M6M1bF40OMQFJroIUd+tqyRrMRfWBrxWzfglk=",
+        "owner": "srid",
+        "repo": "unionmount",
+        "rev": "a419705bce7f1ab67e6cdebd0155148d24473acb",
+        "type": "github"
+      },
+      "original": {
+        "owner": "srid",
+        "repo": "unionmount",
+        "type": "github"
+      }
+    },
+    "url-slug": {
+      "inputs": {
+        "flake-compat": "flake-compat_6",
+        "flake-utils": "flake-utils_9",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1644079925,
+        "narHash": "sha256-4BaKTIflfPXr3Pe/J+XalD9zqkZIOFsO0WBNSL1SsNs=",
+        "owner": "srid",
+        "repo": "url-slug",
+        "rev": "e4199261305ae91045ee9189ac96b113d1e549c8",
+        "type": "github"
+      },
+      "original": {
+        "owner": "srid",
+        "repo": "url-slug",
+        "type": "github"
+      }
+    },
+    "windiCSS": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1625250934,
+        "narHash": "sha256-iIhdu7uez4F57VUW6Rj3dP1vPOeKTl+kp8qo4aIyH8g=",
+        "owner": "srid",
+        "repo": "windicss-nix",
+        "rev": "d96ae0137d56fffa62e53a1815d318cd5f37c987",
+        "type": "github"
+      },
+      "original": {
+        "owner": "srid",
+        "repo": "windicss-nix",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644 (file)
index 0000000..eb49627
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,169 @@
+{
+  description = "sourcephile.fr website";
+  inputs = {
+    nixpkgs.url = "flake:nixpkgs";
+    #ema.url = "path:/home/julm/src/haskell/ema/ema";
+    ema.url = "github:srid/ema/89c9f9234c47437513774efa7e24ef0bdd6427bb";
+    ema.inputs.nixpkgs.follows = "nixpkgs";
+    ema.inputs.pre-commit-hooks.follows = "pre-commit-hooks";
+    ema.inputs.url-slug.follows = "url-slug";
+    url-slug.url = "github:srid/url-slug";
+    url-slug.inputs.nixpkgs.follows = "nixpkgs";
+    commonmark-simple.url = "github:srid/commonmark-simple";
+    commonmark-simple.inputs.nixpkgs.follows = "nixpkgs";
+    unionmount.url = "github:srid/unionmount";
+    unionmount.inputs.nixpkgs.follows = "nixpkgs";
+
+    pre-commit-hooks.inputs.nixpkgs.follows = "nixpkgs";
+    pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
+    PyF.url = "github:guibou/PyF/main";
+    PyF.inputs.nixpkgs.follows = "nixpkgs";
+    open-iconic.url = "github:iconic/open-iconic";
+    open-iconic.flake = false;
+    windiCSS = {
+      url = "github:srid/windicss-nix";
+      flake = false;
+    };
+    # .git/ is needed by the generator to use git log
+    selfGit.url = "path:/home/julm/work/sourcephile/haskell/sourcephile-web/.git";
+    selfGit.flake = false;
+
+    tailwind-haskell.url = "github:srid/tailwind-haskell/master";
+    tailwind-haskell.inputs.nixpkgs.follows = "nixpkgs";
+    tailwind-haskell.inputs.flake-utils.follows = "ema/flake-utils";
+    tailwind-haskell.inputs.flake-compat.follows = "ema/flake-compat";
+  };
+  outputs = inputs:
+    let
+      lib = inputs.nixpkgs.lib;
+      forAllSystems = f: lib.genAttrs lib.systems.flakeExposed (system: f rec {
+        inherit system;
+        pkgs = inputs.nixpkgs.legacyPackages.${system};
+        haskellPackages = pkgs.haskellPackages/*.packages.ghc901*/.extend (with pkgs.haskell.lib; hfinal: hprev: {
+          # ${pkg} = buildFromSdist (hprev.callCabal2nix pkg ./. {});
+          generator = hprev.callCabal2nix "generator" ./generator { };
+          ema = disableCabalFlag inputs.ema.defaultPackage.${system} "with-examples";
+          tailwind = inputs.tailwind-haskell.defaultPackage.${system};
+          url-slug = inputs.url-slug.defaultPackage.${system};
+          commonmark-simple = inputs.commonmark-simple.defaultPackage.${system};
+          unionmount = inputs.unionmount.defaultPackage.${system};
+          PyF = inputs.PyF.defaultPackage.${system};
+          #thumbnail-plus = doJailbreak (unmarkBroken hprev.thumbnail-plus);
+          thumbnail-plus = buildFromSdist (hprev.callCabal2nix "thumbnail-plus"
+            (pkgs.fetchFromGitHub {
+              owner = "abhin4v";
+              repo = "thumbnail-plus";
+              rev = "fc10600d3af19ccce7c69416e2ec33143faa4d37";
+              sha256 = "sha256-lhkhd7pSDq1AhF6jAnWl0at7s4RQvuW+nDjboA3W0D4=";
+            })
+            { });
+        });
+        windiCSS = (import inputs.windiCSS { inherit pkgs; }).shell.nodeDependencies;
+        tailwindCSS = pkgs.fetchurl {
+          url = "https://unpkg.com/tailwindcss@2/dist/tailwind.css";
+          sha256 = "sha256-3jBdkj3eH4VZ53NLsfAZcK86du/IRLlMo8hIj+qNo/c=";
+          #url = "https://unpkg.com/tailwindcss@2/dist/tailwind.min.css";
+          #sha256 = "sha256-tq2XQC7duQPnpdenPuR6Z5IE773aRSGjkcutnfUJuTI=";
+        };
+        windiCSS-extras = pkgs.runCommand "windi-extras.css" { } ''
+          ${windiCSS}/bin/windicss ${generator/css/windi-extras.html} -o $out
+        '';
+        watch-site = pkgs.writeShellScriptBin "watch-site" ''
+          set -eux
+          mkdir -p content/static/icons
+          # FIXME: unfortunately browsers may not invalidate their caching for those .css
+          ln -fns ${tailwindCSS} content/static/css/windi.css
+          ln -fns ${windiCSS-extras} content/static/css/windi-extras.css
+          ln -fns ${inputs.open-iconic} content/static/icons/open-iconic
+          exec ${pkgs.ghcid}/bin/ghcid --no-height-limit --reverse-errors -C generator --warnings \
+            -T "System.Directory.withCurrentDirectory \"../content\" Main.main"
+        '';
+      });
+    in
+    rec {
+      # nix -L build
+      defaultPackage = forAllSystems (args: with args; packages.${system}.website);
+      packages = forAllSystems (args: with args; rec {
+        inherit (haskellPackages) generator;
+        generated = pkgs.runCommand "generated"
+          {
+            LOCALE_ARCHIVE = "${pkgs.glibcLocales}/lib/locale/locale-archive";
+            LC_ALL = "fr_FR.UTF-8";
+          } ''
+          set -eux
+          PATH="${lib.makeBinPath [pkgs.git]}:$PATH"
+          echo >.git "gitdir: ${inputs.selfGit}"
+          cp -r --no-preserve=mode -s ${./content}/* .
+          ln -fns ${windiCSS-extras} static/css/windi-extras.css
+          rm -rf static/icons/open-iconic
+          echo >lastModified ${toString inputs.self.lastModified}
+          mkdir $out
+          ${generator}/bin/generator gen $out
+          mkdir -p $out/static/icons
+          ln -s ${inputs.open-iconic} $out/static/icons/open-iconic
+        '';
+        # Produce a CSS file that contains only the WindiCSS styles
+        # in use by the generated HTML files.
+        windicss = pkgs.runCommand "windi.css" { } ''
+          ${windiCSS}/bin/windicss '${generated}/**/*.html' -o $out --preflight --minify
+        '';
+        website = pkgs.runCommand "website" { } ''
+          set -eux
+          # Copy because HTML files will be modified
+          cp -r --no-preserve=mode ${generated} $out
+          # Name with a hash to invalidate browser's cache if the CSS has changed
+          cssHash=$(md5sum ${windicss} | cut -f 1 -d ' ')
+          mkdir -p $out/static/css
+          ln -s ${windicss} $out/static/css/windi.$cssHash.css
+          # Retroactively replace the CSS with the minified CSS
+          find $out -iname "*.html" -exec \
+            sed -i "s|/static/css/windi.css|/static/css/windi.$cssHash.css|" {} +
+        '';
+      });
+      # direnv allow
+      devShell = forAllSystems (args: with args; haskellPackages.shellFor {
+        packages = ps: [ ps.generator ];
+        nativeBuildInputs = with haskellPackages; [
+          cabal-fmt
+          cabal-install
+          fourmolu
+          ghcid
+          haskell-language-server
+          pkgs.git-chglog
+          pkgs.reuse
+          windiCSS
+          watch-site
+        ];
+        inherit (checks.${system}.pre-commit-check) shellHook;
+        #withHoogle = true;
+      });
+      # nix flake check
+      checks = forAllSystems (args: with args; {
+        pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
+          src = ./.;
+          settings.ormolu.defaultExtensions = [
+            "ImportQualifiedPost"
+            "TypeApplications"
+          ];
+          hooks = {
+            hlint.enable = true;
+            nixpkgs-fmt.enable = true;
+            fourmolu.enable = true;
+            cabal-fmt.enable = true;
+          };
+        };
+      });
+      # nix run .#watch
+      defaultApp = apps.watch;
+      apps = forAllSystems (args: with args; {
+        watch = {
+          type = "app";
+          program = watch-site + "/bin/watch-site";
+        };
+        hoogle = {
+          type = "app";
+          program = (pkgs.writeShellScript "hoogle-server" ''exec hoogle server --local --links -p 8080'').outPath;
+        };
+      });
+    };
+}
diff --git a/generator/.hlint.yaml b/generator/.hlint.yaml
new file mode 100644 (file)
index 0000000..ccfbdf3
--- /dev/null
@@ -0,0 +1,26 @@
+- arguments:
+  - -XQuasiQuotes
+
+- extensions:
+  - name: Haskell2010
+  - name: NoCPP
+  - name: TypeApplications
+
+- ignore: {name: Move brackets to avoid $}
+- ignore: {name: Reduce duplication}
+- ignore: {name: Redundant $}
+- ignore: {name: Redundant bracket}
+- ignore: {name: Redundant do}
+- ignore: {name: Redundant lambda}
+- ignore: {name: Use camelCase}
+- ignore: {name: Use const}
+- ignore: {name: Use fmap}
+- ignore: {name: Use if}
+- ignore: {name: Use import/export shortcut}
+- ignore: {name: Use intercalate}
+- ignore: {name: Use list literal pattern}
+- ignore: {name: Use list literal}
+- ignore: {name: Use newtype instead of data}
+- ignore: {name: Use null}
+- ignore: {name: Use span}
+- ignore: {name: Use ++}
diff --git a/generator/Main.hs b/generator/Main.hs
new file mode 100644 (file)
index 0000000..18e8ad4
--- /dev/null
@@ -0,0 +1,14 @@
+module Main where
+
+import Ema qualified
+import Relude
+import Site.Render
+import Site.Update
+import Prelude ()
+
+-- https://wave.webaim.org/report#/https://sourcephile.fr
+main :: IO (Either () _)
+main =
+  Ema.runEma
+    (\action model -> either Ema.AssetStatic (renderRoute action model))
+    updateModel
diff --git a/generator/Site/Body.hs b/generator/Site/Body.hs
new file mode 100644 (file)
index 0000000..a6dc1e4
--- /dev/null
@@ -0,0 +1,192 @@
+module Site.Body where
+
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Text qualified as Text
+import Data.Time qualified as Time
+import Ema
+import Ema qualified
+import Network.URI.Slug (decodeSlug, encodeSlug)
+import PyF
+import Relude
+import Text.Blaze qualified as B
+import Text.Blaze.Html5 ((!))
+import Text.Blaze.Html5 qualified as H
+import Text.Blaze.Html5.Attributes qualified as A
+import Prelude ()
+
+import Site.Model
+import Utils.Html
+
+renderBody :: Model -> Route -> Content -> H.Html
+renderBody model@Model{..} route Content{..} =
+  -- The "overflow-y-scroll" makes the scrollbar visible always, so as to
+  -- avoid content shifts when switching to routes with suddenly scrollable content.
+  H.body
+    ! classes
+      [ "bg-gray-50"
+      , "flex"
+      , "flex-col"
+      , "overflow-y-scroll"
+      , "font-sans"
+      , --, "max-w-prose"
+        --, "mx-auto"
+        "text-xs"
+      , "mx-4"
+      , "px-4"
+      , "block"
+      ]
+    $ do
+      --renderNav model route
+      renderBodyHead
+      H.hr
+      contentHtml
+  where
+    -- H.hr
+    -- renderBodyFoot
+
+    renderBodyHead =
+      H.nav
+        ! classes
+          [ "text-xs"
+          ]
+        $ case route of
+          RoutePage page
+            | Just{} <- Map.lookup page modelPosts ->
+              html ("post" : page)
+          RouteSpecial page
+            | Just{} <- Map.lookup page modelSpecials ->
+              html page
+          RouteFilter filt@Filter{..}
+            | maybe True (`elem` allTagsModel) filterTag ->
+              html $ "list" : encodeFilter filt
+          RouteFeeds -> html ["feeds"]
+          RouteFilterAtom filt@Filter{..}
+            | maybe True (`elem` allTagsModel) filterTag ->
+              html $ "feed" : encodeFilter filt
+          _notFound ->
+            --error [fmt|Route {notFound:s} does not exist.|]
+            html ["not-found"]
+      where
+        allTagsModel = allTags model
+        html slugs =
+          H.ul
+            ! classes
+              [ "items-start"
+              , "flex"
+              , "flex-wrap"
+              , "justify-start"
+              ]
+            $ mconcat $
+              List.intersperse
+                "/"
+                [ H.li ! classes (if path == [] then ["pr-2"] else ["px-2"]) $
+                  H.a ! A.href (B.toValue $ encodeSlugs path) $
+                    H.text $ encodeSlug slug
+                | (slug, path) <-
+                    List.zip
+                      (decodeSlug (Text.toLower orgName) : slugs)
+                      (List.inits slugs)
+                ]
+    renderBodyFoot =
+      H.footer
+        ! classes
+          [ "border-t-2"
+          , "flex"
+          , "flex-row"
+          , "justify-between"
+          , "mt-4"
+          , "text-sm"
+          , "clear-left"
+          ]
+        $ do
+          {-
+          H.a
+            ! classes ["hover:bg-blue-100", "text-blue-600"]
+            ! A.href [fmt|{Ema.routeUrl model $ Right @FilePath route:s}#top|]
+            $ "Back to top"
+          -}
+          H.span
+            ! classes ["text-gray-600"]
+            $ [fmt|Generated: {maybe "dynamically"
+              (Time.formatTime Time.defaultTimeLocale "%F")
+              modelTime
+            }|]
+          H.span do
+            "The "
+            H.a
+              ! A.href [fmt|https://git.code.{domainName}/~julm/sourcephile-web|]
+              $ "code for this site"
+            " is "
+            H.a
+              ! A.href "https://spdx.org/licenses/AGPL-3.0-or-later.html"
+              $ "AGPL-3.0-or-later"
+            "."
+
+{-
+renderNav :: Model -> Route -> H.Html
+renderNav model route =
+  H.div ! A.id "top" ! classes ["bg-gray-500"] $
+    H.nav
+      ! classes
+        [ "flex"
+        , "flex-col"
+        , "items-stretch"
+        , "max-w-6xl"
+        , "mx-auto"
+        , "sm:flex-row"
+        , "sm:h-16"
+        , "sm:justify-between"
+        ]
+      $ do
+        H.span
+          ! classes
+            [ "flex"
+            , "flex-col"
+            , "items-stretch"
+            , "sm:flex-row"
+            , "sm:justify-start"
+            ]
+          $ headLinks
+            [
+              ( A.href "#"
+              , do
+                  --H.img
+                  --  ! classes ["w-10", "h-10", "mr-4", "rounded-full"]
+                  --  ! A.src "/static/img/my_avatar.jpg"
+                  --  ! A.alt "logo"
+                  [fmt|{orgName}|]
+              )
+            , (hrefRoute model $ RouteFilter noFilter, "All Posts")
+            , (hrefRoute model $ RouteFilter noFilter{filterTag = Just $ Tag "non-tech"}, "Non-Tech")
+            , (hrefRoute model $ RouteFilter noFilter{filterTag = Just $ Tag "tech"}, "Tech")
+            , (hrefRoute model $ RouteSpecial ["projects"], "Projects")
+            ]
+        H.span ! classes ["flex", "flex-row", "justify-items-center"] $
+          headLinks
+            [ (A.title "Atom" <> hrefRoute model RouteFeeds, openIconic "rss")
+            , (A.title "IRC" <> A.href [fmt|irc://irc.geeknode.org/#sourcephile|], openIconic "chat")
+            , (A.title "XMPP" <> A.href [fmt|xmpp:sourcephile@{domainName}?join|], openIconic "chat")
+            , (A.title "Mail" <> A.href [fmt|mailto:contact@{domainName}|], openIconic "envelope-closed")
+            , (A.title "Git" <> A.href [fmt|https://git.code.{domainName}|], openIconic "fork")
+            ]
+  where
+    headLinks = mapM_ $ \(attrs, html) ->
+      H.a
+        ! smallCaps
+          [ "flex"
+          , "flex-row"
+          , "hover:bg-blue-600"
+          , "items-center"
+          , "justify-center"
+          , "justify-items-center"
+          , "p-4"
+          , "sm:w-auto"
+          , "text-lg"
+          , "text-white"
+          , "w-full"
+          ]
+        ! A.style "font-variant: small-caps"
+        ! attrs
+        $ html ! classes ["w-4"]
+-}
diff --git a/generator/Site/Feed.hs b/generator/Site/Feed.hs
new file mode 100644 (file)
index 0000000..300145b
--- /dev/null
@@ -0,0 +1,92 @@
+module Site.Feed where
+
+import Data.Default (def)
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Time qualified as Time
+import PyF
+import Relude
+import Text.Atom.Feed qualified as Atom
+import Text.Blaze.Html5 ((!))
+import Text.Blaze.Html5 qualified as H
+import Text.Pandoc (runPure)
+import Text.Pandoc.Writers (writeHtml5String)
+import Prelude ()
+
+import Site.Filter
+import Site.Lang
+import Site.Model
+import Site.Page
+import Site.Tag
+import Utils.Html
+
+mkFeed :: Model -> Filter -> Atom.Feed
+mkFeed model filt@Filter{..} =
+  ( Atom.nullFeed
+      (absoluteLink model (Left ""))
+      --(absoluteLink model (Right (RouteFilterAtom filt)))
+      (Atom.TextString [fmt|{orgName} - {feedTitle filt}|])
+      (atomTime . List.maximum . mapMaybe (metaUpdated . pageMeta) . Map.elems $ modelPosts model)
+  )
+    { Atom.feedAuthors =
+        one
+          Atom.nullPerson
+            { Atom.personName = orgName
+            , Atom.personEmail = Just [fmt|contact@{domainName}|]
+            }
+    , --, Atom.feedIcon = Just $ absoluteLink model (Left "static/img/feedIcon.png")
+      Atom.feedEntries =
+        [ feedEntry post
+        | post@(_slug, Page{..}) <- filterPosts model filt
+        , isJust $ metaUpdated pageMeta
+        ]
+    , Atom.feedLinks = one $ Atom.nullLink [fmt|https://{domainName}|]
+    , Atom.feedCategories = Atom.newCategory . unTag <$> maybeToList filterTag
+    }
+  where
+    atomTime = toText . Time.formatTime Time.defaultTimeLocale "%Y-%m-%d"
+    feedEntry (pageName, Page{..}) =
+      ( Atom.nullEntry
+          pageLink
+          (Atom.TextString $ metaTitle pageMeta)
+          (atomTime (fromMaybe (error "Pages without date") $ metaUpdated pageMeta))
+      )
+        { Atom.entryCategories = Atom.newCategory . unTag <$> metaTags pageMeta
+        , Atom.entryLinks = one $ Atom.nullLink pageLink
+        , Atom.entrySummary = Atom.TextString . markdownText <$> metaSummary pageMeta
+        , Atom.entryContent =
+            Just $
+              Atom.HTMLContent $
+                either (error . show) id $
+                  runPure $ writeHtml5String def pageDoc
+        }
+      where
+        pageLink = absoluteLink model $ Right $ RoutePage pageName
+
+feedList :: Model -> H.Html
+feedList model = do
+  H.h2 ! classes ["text-blue-800", "text-xl"] $ "Feed of all posts"
+  H.table ! classes ["my-2", "table-fixed", "w-full"] $
+    H.tr $ do
+      icell ""
+      forM_ (Nothing : (Just <$> [minBound ..])) $ \lang ->
+        cell $ mkLink lang Nothing
+  H.h2 ! classes ["text-blue-800", "text-xl"] $ "Feeds by tag"
+  H.table ! classes ["my-2", "table-fixed", "w-full"] $ do
+    forM_ (allTags model) $ \tag -> H.tr $ do
+      icell (renderTag model True tag)
+      forM_ (Nothing : (Just <$> [minBound ..])) $ \lang ->
+        cell $ mkLink lang (Just tag)
+  where
+    mkLink lang tag =
+      H.a
+        ! classes
+          [ "bg-gray-100"
+          , "hover:underline"
+          , "px-1"
+          , "text-blue-800"
+          ]
+        ! hrefRoute model (RouteFilterAtom (Filter lang tag))
+        $ maybe "Any" langText lang
+    cell = H.td ! classes ["text-right", "w-1/4"]
+    icell = H.td ! classes ["text-left", "w-1/4"]
diff --git a/generator/Site/Filter.hs b/generator/Site/Filter.hs
new file mode 100644 (file)
index 0000000..385a2a9
--- /dev/null
@@ -0,0 +1,53 @@
+module Site.Filter where
+
+import Data.List qualified as List
+import Data.Text qualified as Text
+import PyF
+import Relude
+import Text.Blaze.Html5 ((!))
+import Text.Blaze.Html5 qualified as H
+import Text.Blaze.Html5.Attributes qualified as A
+import Prelude ()
+
+import Site.Lang
+import Site.Model
+import Site.Page
+import Utils.Html
+
+feedTitle :: Filter -> Text
+feedTitle (Filter language tag) =
+  Text.unwords $
+    (if isNothing tag then ["All Posts"] else ["Posts"])
+      <> maybeToList ((\(Tag t) -> [fmt|tagged "{t}"|]) <$> tag)
+      <> maybeToList (("in " <>) . langText <$> language)
+
+renderFilter :: Model -> Filter -> Content
+renderFilter model filt = Content (Just (feedTitle filt)) $ do
+  H.a
+    ! classes
+      [ "fg-white"
+      , "hover:bg-orange-100"
+      , "block"
+      , "p-1"
+      ]
+    -- https://codepen.io/sosuke/pen/Pjoqqp for #F76300
+    ! A.style "filter: invert(31%) sepia(72%) saturate(2199%) hue-rotate(17deg) brightness(111%) contrast(108%);"
+    ! hrefRoute model (RouteFilterAtom filt)
+    $ openIconic "rss-alt"
+      ! classes ["h-4", "w-4"]
+  H.div
+    ! classes ["text-left"]
+    $ mconcat $ List.intersperse " • " $ langLink <$> [minBound ..]
+  H.div
+    ! classes ["space-y-8", "pb-8"]
+    $ renderPagesListing model (filterPosts model filt)
+  where
+    langLink l =
+      H.a
+        ! classes (["hover:underline"] <> prop)
+        ! hrefRoute model (RouteFilter filt{filterLang = if Just l == filterLang filt then Nothing else Just l})
+        $ H.text (textOfLang l)
+      where
+        prop
+          | Just x <- filterLang filt, x /= l = ["line-through"]
+          | otherwise = []
diff --git a/generator/Site/Lang.hs b/generator/Site/Lang.hs
new file mode 100644 (file)
index 0000000..fb9328a
--- /dev/null
@@ -0,0 +1,38 @@
+module Site.Lang where
+
+import Data.Aeson (FromJSON (parseJSON), withText)
+import Data.Text qualified as Text
+import Relude
+import Prelude ()
+
+-- * Type 'Lang'
+data Lang
+  = LangDe
+  | LangEn
+  | LangFr
+  | LangJp
+  deriving (Bounded, Show, Enum, Eq)
+
+langText :: IsString a => Lang -> a
+langText = \case
+  LangDe -> "Deutsch"
+  LangEn -> "English"
+  LangFr -> "français"
+  LangJp -> "日本語"
+
+parseLang :: Text -> Maybe Lang
+parseLang = \case
+  "de" -> Just LangDe
+  "en" -> Just LangEn
+  "fr" -> Just LangFr
+  "jp" -> Just LangJp
+  _ -> Nothing
+
+instance FromJSON Lang where
+  parseJSON =
+    withText "Lang" $
+      maybe (fail "expected a Lang") pure
+        . parseLang
+
+textOfLang :: Lang -> Text
+textOfLang = Text.drop 4 . Text.toLower . show
diff --git a/generator/Site/Model.hs b/generator/Site/Model.hs
new file mode 100644 (file)
index 0000000..a955c7f
--- /dev/null
@@ -0,0 +1,309 @@
+module Site.Model where
+
+import Commonmark.Simple qualified as Markdown
+import Data.Aeson (FromJSON (..), Options (..), defaultOptions, genericParseJSON, withText)
+import Data.Char qualified as Char
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+import Data.Time qualified as Time
+import Ema (Ema (..))
+import Ema qualified
+import Graphics.ThumbnailPlus qualified as Thumb
+import Network.URI.Slug (Slug (..), decodeSlug, encodeSlug)
+import PyF
+import Relude
+import Relude.Unsafe qualified as Unsafe
+import Text.Blaze.Html5 ((!))
+import Text.Blaze.Html5 qualified as H
+import Text.Blaze.Html5.Attributes qualified as A
+import Text.Pandoc.Definition (Pandoc (..))
+import Prelude ()
+
+import Site.Lang
+import Utils.Html
+
+domainName :: Text
+domainName = "sourcephile.fr"
+
+orgName :: Text
+orgName = "Sourcephile"
+
+-- * Type 'Model'
+
+{- | The state used to generate the site.
+ Changing the model automatically changes the view.
+-}
+data Model = Model
+  { modelTime :: Maybe Time.UTCTime
+  , modelPosts :: Map [Slug] Page
+  , modelSpecials :: Map [Slug] Page
+  , modelPictures :: Map [Slug] [(Thumb.Size, FilePath)]
+  , modelLocalLinks :: Map [Slug] (Set LocalLink)
+  }
+
+backLinks :: HasCallStack => LocalLink -> Map [Slug] (Set LocalLink) -> Set [Slug]
+backLinks page =
+  Map.foldMapWithKey
+    ( \k lls ->
+        if Set.member page lls
+          then Set.singleton k
+          else mempty
+    )
+-- ** Type 'Page'
+data Page = Page
+  { pageMeta :: Meta
+  , pageDoc :: Pandoc
+  , pageLocalLinks :: Set LocalLink
+  }
+
+-- *** Type 'LocalLink'
+newtype LocalLink = LocalLink [Slug]
+  deriving (Eq, Ord, Show)
+
+localLink :: HasCallStack => [Slug] -> Text -> LocalLink
+localLink base target = LocalLink
+  case Text.span (== '/') target of
+    (Text.isPrefixOf "/" -> True, absTarget) ->
+      go $ decodeSlugs absTarget
+    ("", relTarget) ->
+      go $ base <> decodeSlugs relTarget
+  where
+    go = \case
+      s : ".." : ss -> go ss
+      ".." : ss -> go ss
+      s : ss -> s : go ss
+      [] -> []
+-- *** Type 'Meta'
+data Meta = Meta
+  { metaAuthors :: Maybe (NonEmpty Entity)
+  , metaLang :: Lang
+  , metaPublished :: Maybe Time.Day
+  , metaLisense :: Maybe Text
+  , metaSummary :: Maybe Markdown
+  , metaTitle :: Text
+  , metaUpdated :: Maybe Time.Day
+  , metatags :: Maybe (NonEmpty Tag)
+  , metaDiscussion :: Maybe Discussion
+  }
+  deriving (Show, Generic)
+instance FromJSON Meta where
+  parseJSON =
+    genericParseJSON
+      defaultOptions
+        { fieldLabelModifier = \case
+            (drop 4 -> c : cs) -> Char.toLower c : cs
+            _ -> error ""
+        }
+
+-- | 'metaTags can't be a list to be able to derive FromJSON
+metaTags :: Meta -> [Tag]
+metaTags = maybe [] toList . metatags
+
+-- **** Type 'Markdown'
+data Markdown = Markdown
+  { markdownText :: Text
+  , markdownPandoc :: Pandoc
+  }
+  deriving (Show)
+instance FromJSON Markdown where
+  parseJSON = withText "Markdown" $ \markdownText ->
+    case Markdown.parseMarkdown "" markdownText of
+      Right markdownPandoc -> pure Markdown{..}
+      Left err -> fail (toString err)
+
+-- **** Type 'Discussion'
+data Discussion = Discussion
+  { discussionUrl :: Text
+  , discussionMail :: Text
+  }
+  deriving (Show, Eq, Ord, Generic)
+instance FromJSON Discussion where
+  parseJSON =
+    genericParseJSON
+      defaultOptions
+        { fieldLabelModifier = \case
+            (drop 10 -> c : cs) -> Char.toLower c : cs
+            _ -> error ""
+        }
+
+-- **** Type 'Entity'
+data Entity = Entity
+  { entityName :: Text
+  , entityMail :: Maybe Text
+  }
+  deriving (Show, Eq, Ord, Generic)
+instance FromJSON Entity where
+  parseJSON =
+    genericParseJSON
+      defaultOptions
+        { fieldLabelModifier = \case
+            (drop 6 -> c : cs) -> Char.toLower c : cs
+            _ -> error ""
+        }
+
+-- **** Type 'Tag'
+newtype Tag = Tag {unTag :: Text}
+  deriving stock (Show, Eq, Ord, Generic)
+  deriving (FromJSON) via Text
+
+allTags :: HasCallStack => Model -> [Tag]
+allTags =
+  reverse
+    . (Unsafe.head <$>)
+    . List.sortOn List.length
+    . List.group
+    . List.sort
+    . List.concat
+    . (metaTags . pageMeta <$>)
+    . Map.elems
+    . modelPosts
+
+-- * Type 'Route'
+data Route
+  = RouteFeeds
+  | RouteFilter Filter
+  | RouteFilterAtom Filter
+  | RoutePage [Slug]
+  | RouteSpecial [Slug]
+  deriving (Show, Eq)
+
+hrefRoute :: HasCallStack => Model -> Route -> H.Attribute
+hrefRoute model route = A.href $ H.textValue $ {-"/" <>-} Ema.routeUrl model (Right @FilePath route)
+
+absoluteLink :: HasCallStack => Model -> Either FilePath Route -> Text
+absoluteLink model route = [fmt|https://{domainName}/{Ema.routeUrl model route}|]
+
+encodeSlugs :: HasCallStack => [Slug] -> Text
+encodeSlugs slugs = Text.concat $ "/" : List.intersperse "/" (encodeSlug <$> slugs)
+
+decodeSlugs :: HasCallStack => Text -> [Slug]
+decodeSlugs = (decodeSlug <$>) . Text.splitOn "/"
+
+routeElem :: HasCallStack => Model -> Route -> H.Html -> H.Html
+routeElem model route =
+  H.a
+    ! hrefRoute model route
+    ! classes
+      [ "hover:bg-blue-600"
+      , "inline-block"
+      , "p-4"
+      , "text-white"
+      ]
+
+instance Ema (Either FilePath Route) where
+  type ModelFor (Either FilePath Route) = Model
+
+  -- Where to generate this Route.
+  -- Called by Ema.routeUrl
+  encodeRoute model@Model{..} = either id $ \case
+    RoutePage page
+      | Just{} <- Map.lookup page modelPosts ->
+        html ("post" : page)
+    RouteSpecial page
+      | Just{} <- Map.lookup page modelSpecials ->
+        html page
+    RouteFilter Filter{filterTag = Nothing, filterLang = Nothing} ->
+      html ["list", "tag"]
+    RouteFilter filt@Filter{..}
+      | maybe True (`elem` allTagsModel) filterTag ->
+        html $ "list" : encodeFilter filt
+    RouteFeeds -> html ["feeds"]
+    RouteFilterAtom filt@Filter{..}
+      | maybe True (`elem` allTagsModel) filterTag ->
+        pathOf "xml" $ "feed" : encodeFilter filt
+    _notFound ->
+      --error [fmt|Route {notFound:s} does not exist.|]
+      html ["not-found"]
+    where
+      allTagsModel = allTags model
+      html = pathOf "html"
+      pathOf suffix = toString . (<> "." <> suffix) . encodeSlugs
+
+  -- Which route does this filepath correspond to?
+  decodeRoute Model{..} filePath@(toText -> route)
+    -- Files
+    | Text.isPrefixOf "static/" route = Just $ Left filePath
+    -- XML
+    | Just (decodeSlugs -> slugs) <- Text.stripSuffix ".xml" route =
+      case slugs of
+        "feed" : (decodeFilter -> Just filt) ->
+          Just $ Right $ RouteFilterAtom filt
+        _ -> Nothing
+    -- HTML
+    | Just (decodeSlugs -> slugs) <- Text.stripSuffix ".html" route =
+      case slugs of
+        ["feeds"] -> Just $ Right RouteFeeds
+        "list" : (decodeFilter -> Just filt) ->
+          Just $ Right $ RouteFilter filt
+        ["post"] -> Just $ Right $ RouteFilter noFilter
+        "post" : pageName
+          | Map.member pageName modelPosts ->
+            Just $ Right $ RoutePage pageName
+        pageName
+          | Map.member pageName modelSpecials ->
+            Just $ Right $ RouteSpecial pageName
+        _ -> Nothing
+    -- 404
+    | otherwise = Nothing
+
+  -- The routes to statically generate
+  -- (not used by the live-server).
+  allRoutes model =
+    [Left "static", Right RouteFeeds]
+    <> allPages
+    <> allSpecials
+    <> feeds
+    <> lists
+    where
+      allPages = Right . RoutePage <$> Map.keys (modelPosts model)
+      allSpecials = Right . RouteSpecial <$> Map.keys (modelSpecials model)
+      allIndices =
+        [ Filter{..}
+        | filterLang <- Nothing : (Just <$> [minBound ..])
+        , filterTag <- Nothing : (Just <$> toList (allTags model))
+        ]
+      feeds = Right . RouteFilterAtom <$> allIndices
+      lists = Right . RouteFilter <$> allIndices
+
+-- ** Type 'Filter'
+
+-- Because the static site cannot afford
+-- to generate all intersecting categories,
+-- therefore select only a few whose intersection makes sense.
+data Filter = Filter
+  { filterLang :: Maybe Lang
+  , filterTag :: Maybe Tag
+  }
+  deriving (Show, Eq)
+
+noFilter :: Filter
+noFilter =
+  Filter
+    { filterLang = Nothing
+    , filterTag = Nothing
+    }
+
+encodeFilter :: HasCallStack => Filter -> [Slug]
+encodeFilter Filter{..} =
+  decodeSlug <$> case filterTag of
+    Nothing -> langTag
+    Just tag -> ["tag", unTag tag] <> langTag
+  where
+    langTag = textOfLang <$> maybeToList filterLang
+
+decodeFilter :: HasCallStack => [Slug] -> Maybe Filter
+decodeFilter = \case
+  ["all", parseLang . encodeSlug -> filterLang] -> Just noFilter{filterLang}
+  ["tag"] -> Just noFilter
+  ["tag", Just . Tag . encodeSlug -> filterTag, parseLang . encodeSlug -> filterLang] -> Just Filter{..}
+  ["tag", Just . Tag . encodeSlug -> filterTag] -> Just Filter{filterLang = Nothing, ..}
+  _ -> Nothing
+
+-- * Type 'Content'
+data Content = Content
+  { contentTitle :: Maybe Text
+  , contentHtml :: H.Html
+  -- , contentLang :: Maybe Lang
+  }
diff --git a/generator/Site/Page.hs b/generator/Site/Page.hs
new file mode 100644 (file)
index 0000000..8f32af1
--- /dev/null
@@ -0,0 +1,664 @@
+module Site.Page where
+
+import Data.Default (def)
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Time qualified as Time
+import Ema qualified
+import Graphics.ThumbnailPlus qualified as Thumb
+import Network.URI.Slug (Slug)
+import PyF
+import Relude
+import Text.Blaze.Html5 ((!))
+import Text.Blaze.Html5 qualified as H
+import Text.Blaze.Html5.Attributes qualified as A
+import Text.Blaze.Internal qualified as Blaze
+import Text.Pandoc.Options qualified as Pandoc
+import Text.Pandoc.Writers.Shared qualified as Pandoc
+import Prelude ()
+
+import Data.Text qualified as Text
+import Site.Lang
+import Site.Model
+import Site.Tag
+import Text.Pandoc.Builder qualified as Pandoc
+import Text.Pandoc.Shared qualified as Pandoc
+import Text.Pandoc.Walk qualified as Pandoc
+import Utils.Html
+import Utils.Pandoc as Pandoc
+import Utils.Pandoc.Html (htmlOfPandoc)
+
+renderPage :: Model -> ([Slug], Page) -> Content
+renderPage
+  model@Model{..}
+  ( pagePath
+    , page@Page
+        { pageMeta = meta@Meta{metaLang}
+        , pageDoc = Pandoc.Pandoc pandocMeta pandocBlocks
+        }
+    ) =
+    Content
+      { contentTitle = Nothing -- Just metaTitle
+      , contentHtml = do
+          H.div ! classes ["flex", "flex-row", "flex-wrap"] $ do
+            H.div
+              ! A.style "flex-grow: 2;"
+              ! classes
+                [ "page"
+                , --, "flex-grow"
+                  "w-96"
+                , "max-w-xl"
+                , "mx-auto"
+                , "fg-black"
+                , "bg-white"
+                , "px-4"
+                , "text-justify"
+                --, "float-left"
+                --, "flow-root"
+                --, "w-full"
+                --, "lg:w-3/4"
+                ]
+              $ do
+                renderPageTitle
+                renderPageTags
+                renderPageSummary
+                -- H.hr ! classes ["min-w-full"]
+                H.article
+                  ! classes
+                    [ "article"
+                    --, "clear-left"
+                    ]
+                  $ pageHtml
+            H.div
+              -- ! A.style "width: 20em"
+              -- TODO: use column-gap: https://www.w3.org/TR/css-align-3/
+              -- once implemented in Web engines.
+              ! A.style "min-width:20rem; flex-grow: 1;"
+              ! classes
+                [ "w-80"
+                , "max-w-xl"
+                , "mx-auto"
+                , "flex"
+                , "flex-col"
+                -- , "flex-grow"
+                --, "w-full"
+                --, "lg:w-80"
+                ]
+              $ do
+                forM_
+                  ( catMaybes
+                      [ renderPageHeaders
+                      , renderPageToC
+                      , renderPageBackLinks
+                      , renderTags model
+                      , similarPages model (pagePath, pageMeta page)
+                      ]
+                  )
+                  $ \aside ->
+                    H.aside ! classes ["mb-3"] $ aside
+                H.aside $ recentPages metaLang model (Just (metaTitle meta))
+                {-
+                H.aside
+                  ! classes [
+                            --"float-left"
+                            --, "w-full"
+                            --, "lg:pr-0"
+                            --, "lg:w-1/4"
+                            ]
+                  $ do
+                renderPageDiscussion
+                -}
+      }
+    where
+      doc = Pandoc.makeSections True Nothing pandocBlocks
+      tocHtmlMaybe =
+        case Pandoc.toTableOfContents writerOpts $
+          Pandoc.walk walkToC doc of
+          Pandoc.BulletList [] -> Nothing
+          ul -> Just $ htmlOfPandoc writerOpts $ Pandoc.Pandoc pandocMeta [ul]
+      walkToC = \case
+        -- Hyper-link section numbers.
+        Pandoc.Div
+          (divId, "section" : divCls, divDict)
+          (Pandoc.Header level ("", headerCls, headerDict) headerInlines : divBlocks) ->
+            Pandoc.Div
+              (divId, "section" : divCls, divDict)
+              $
+              -- level + 1 to only have the title in a <h1>
+              Pandoc.Header
+                (level + 1)
+                (divId, headerCls, ("number", number) : headerDict)
+                (Pandoc.Space : Pandoc.Span ("", ["font-bold" | level == 1], []) headerInlines : []) :
+              divBlocks
+            where
+              sectionNum = fromMaybe mempty $ List.lookup "number" headerDict
+              sectionNums = Text.split ('.' ==) sectionNum
+              number
+                | List.length sectionNums == 1 = sectionNum <> "."
+                | otherwise = sectionNum
+        x -> x
+
+      writerOpts =
+        def
+          { Pandoc.writerExtensions = Pandoc.enableExtension Pandoc.Ext_smart Pandoc.pandocExtensions
+          , -- makeSections is called here and customized
+            Pandoc.writerNumberSections = False
+          , -- toTableOfContents is called here and customized
+            Pandoc.writerTableOfContents = False
+          , Pandoc.writerSectionDivs = True
+          , Pandoc.writerTOCDepth = maxBound
+          }
+      pageHtml = htmlOfPandoc writerOpts $
+        Pandoc.walk
+          ( \case
+              Pandoc.Link (linkId, linkCls, linkKVs) label (uri, title)
+                | not (Text.isInfixOf ":" uri) -> do
+                  -- keep only local URIs
+                  Pandoc.Link
+                    (linkId, ["text-red-800"] <> linkCls, linkKVs)
+                    label
+                    (uri <> ".html", title)
+              inl -> inl
+          )
+          $ (`Pandoc.walk` Pandoc.Pandoc pandocMeta doc) $ \case
+            Pandoc.BulletList items
+              | all
+                  ( \case
+                      Pandoc.Plain (Pandoc.Str "pictures:" : Pandoc.Space : Pandoc.Str _ : _) : [] -> True
+                      _ -> False
+                  )
+                  items ->
+                walkPictures items
+            blk -> blk
+      renderPageTitle =
+        H.header $
+          H.h1
+            ! classes
+              [ "bg-black"
+              , "font-bold"
+              , "leading-relaxed"
+              , "mb-1"
+              , "mt-4"
+              , "text-center"
+              , "text-lg"
+              , "text-white"
+              ]
+            $ H.text (metaTitle meta)
+      renderPageTags =
+        unless (null (metaTags meta)) do
+          H.aside
+            ! classes
+              [ "flex"
+              , "flex-row"
+              , "flex-wrap"
+              , "mb-2"
+              --, "max-w-screen-md"
+              ]
+            $ forM_ (metaTags meta) $
+              renderTag model False
+      renderPageSummary =
+        whenJust (metaSummary meta) $ \Markdown{..} -> do
+          H.div
+            ! classes
+              [ "summary"
+              , --, "border-1"
+                --, "border-black"
+                "bg-gray-100"
+              , "mb-2"
+              ]
+            $ do
+              H.span
+                ! classes
+                  [ "bg-black"
+                  , "font-bold"
+                  , "px-4"
+                  , --, "mb-1"
+                    --, "text-left"
+                    "text-xs"
+                  , "text-white"
+                  , "display-inline"
+                  , "float-left"
+                  ]
+                $ H.text "Résumé"
+              H.div ! classes ["px-2"] $
+                H.span
+                  ! classes
+                    [ "inline"
+                    , "italic"
+                    , "ml-2"
+                    ]
+                  $ htmlOfPandoc def markdownPandoc
+      -- H.span
+      --   ! classes [ "bg-gray-500"
+      --             , "float-right"
+      --             , "px-4"
+      --             , "mt-1"
+      --             , "text-white"
+      --             , "font-bold"
+      --             ]
+      --   ! A.style "font-size: 0.55rem"
+      --   $ [fmt|{signsCount pageDoc} signes|]
+      renderPageHeaders = Just $
+        H.table
+          ! classes
+            [ "document-headers"
+            , "border-collapse"
+            , "border-white"
+            ]
+          $ forM_ (zip [0 :: Int ..] headers) $ \(headerRank, (headerName, headerValue)) -> do
+            H.tr
+              ! classes
+                [ if headerRank `mod` 2 == 0
+                    then "bg-gray-300"
+                    else "bg-gray-200"
+                ]
+              $ do
+                H.th
+                  ! classes
+                    [ "font-bold"
+                    , "bg-black"
+                    , "text-white"
+                    , "px-3"
+                    , "whitespace-nowrap"
+                    , if headerRank /= 0 then "border-t" else ""
+                    ]
+                  $ do
+                    H.text (headerName metaLang)
+                H.td
+                  ! classes
+                    [ "px-3"
+                    , "w-full"
+                    , if headerRank /= 0 then "border-t" else ""
+                    ]
+                  $ do
+                    headerValue
+        where
+          dateHtml = Time.formatTime (timeLocale metaLang) "%-e %B %Y"
+          headers :: [(Lang -> Text, H.Html)]
+          headers =
+            catMaybes
+              [ metaAuthors meta >>= \authors ->
+                  Just
+                    ( i18nAuthors
+                    , H.ul $
+                        forM_ authors $ \Entity{..} ->
+                          H.li $
+                            case entityMail of
+                              Nothing -> H.text entityName
+                              Just mail ->
+                                H.a
+                                  ! A.href ("mailto:" <> H.textValue mail)
+                                  $ H.text entityName
+                    )
+              , metaUpdated meta >>= \date ->
+                  Just (i18nUpdated, H.string $ dateHtml date)
+              , metaPublished meta >>= \date ->
+                  Just (i18nPublished, H.string $ dateHtml date)
+              , let lisense = fromMaybe "CC-BY-SA-4.0" (metaLisense meta)
+                 in Just (i18nLisense, H.a ! A.href [fmt|https://spdx.org/licenses/{lisense}.html|] $ H.text lisense)
+              , metaDiscussion meta >>= \Discussion{..} ->
+                  Just
+                    ( i18nDiscussion
+                    , H.a ! A.href (H.textValue [fmt|mailto:{discussionMail}|]) $
+                        H.text discussionMail
+                    )
+              , Just (i18nLanguage, H.text $ langText metaLang)
+              ]
+      renderPageToC =
+        tocHtmlMaybe <&> \tocHtml ->
+          H.nav
+            ! classes
+              [ "toc"
+              ]
+            $ do
+              H.details ! A.open "" $ do
+                H.summary
+                  ! classes
+                    [ "bg-black"
+                    , "border-l-8"
+                    , "border-black"
+                    , "text-white"
+                    ]
+                  $ H.h2
+                    ! classes
+                      [ "font-bold"
+                      , "pl-1"
+                      , "text-left"
+                      , "text-xs"
+                      , "inline-block"
+                      ]
+                    $ i18nTableOfContent metaLang
+                H.div
+                  ! classes
+                    [ "p-2"
+                    , "bg-yellow-50"
+                    , "border-1"
+                    , "border-black"
+                    ]
+                  $ tocHtml
+      renderPageBackLinks =
+        let bls = backLinks (LocalLink pagePath) modelLocalLinks
+         in if null bls
+              then Nothing
+              else Just do
+                H.nav
+                  ! classes
+                    [ "backlinks"
+                    ]
+                  $ do
+                    H.details ! A.open "" $ do
+                      H.summary
+                        ! classes
+                          [ "bg-black"
+                          , "border-l-8"
+                          , "border-black"
+                          , "text-white"
+                          ]
+                        $ H.h2
+                          ! classes
+                            [ "font-bold"
+                            , "pl-1"
+                            , "text-left"
+                            , "text-xs"
+                            , "inline-block"
+                            ]
+                          $ i18nBacklinks metaLang
+                      H.ul $
+                        forM_ (modelLocalLinks Map.! pagePath) $ \(LocalLink lnk) ->
+                          H.li $ H.string $ show lnk
+                      H.ul
+                        ! classes
+                          [ "p-2"
+                          , "bg-yellow-50"
+                          , "border-1"
+                          , "border-black"
+                          ]
+                        $ do
+                          forM_ bls $ \bl ->
+                            H.li do
+                              "— "
+                              H.a
+                                ! A.href (H.stringValue (Ema.encodeRoute model (Right @FilePath (RoutePage bl))))
+                                $ H.text (metaTitle (pageMeta (modelPosts Map.! bl)))
+      renderPageDiscussion =
+        whenJust (metaDiscussion meta) $ \Discussion{..} -> do
+          H.aside
+            ! A.id "discussion"
+            ! classes
+              [ "discussion"
+              , "bg-gray-100"
+              , --, "float-left"
+                --, "clear-both"
+                "w-full"
+              , --, "h-screen"
+                "p-0"
+              , "mt-4"
+              ]
+            $ do
+              H.h2
+                ! classes
+                  [ "bg-gray-500"
+                  , "font-bold"
+                  , "px-4"
+                  , "text-left"
+                  , "text-sm"
+                  , "text-white"
+                  ]
+                $ do
+                  H.a
+                    ! A.href "#discussion"
+                    ! classes ["text-white"]
+                    $ H.text (i18nDiscussion metaLang)
+                  H.span
+                    ! classes ["font-normal", "text-sm"]
+                    $ do
+                      " ("
+                      H.a ! A.href ("mailto:" <> H.textValue discussionMail) $
+                        H.text discussionMail
+                      ")"
+              H.iframe
+                ! classes
+                  [ "block"
+                  , "border-1"
+                  , --, "float-left", "clear-left"
+                    "w-full"
+                  ]
+                ! A.style "height:100vh"
+                ! A.src (H.textValue discussionUrl)
+                ! A.src "http://oignon.wg:8000"
+                ! Blaze.attribute "loading" " loading=\"" "lazy"
+                $ ""
+      walkPictures items =
+        Pandoc.Div
+          ("", ["pictures", "mb-2"], [])
+          [ Pandoc.BulletList $
+              mconcat $
+                items <&> \case
+                  Pandoc.Plain (Pandoc.Str "pictures:" : Pandoc.Space : Pandoc.Str (decodeSlugs -> prefix) : plainInlines) : [] ->
+                    [ let alt = Pandoc.trimWhiteInlines plainInlines
+                       in let (maxThumbSize, maxThumbPath) = List.last thumbs
+                           in let (minThumbSize, _minThumbPath) = List.head $ traceShowId thumbs
+                               in pure $
+                                    Pandoc.Plain
+                                      [ Pandoc.Link
+                                          ("", [], [])
+                                          [ {-
+                                            Pandoc.RawInline "html5" [fmt|
+                                              <picture><source srcset="/{}" media="(min-width: {}px)"></picture>
+                                            -}
+                                            Pandoc.Image
+                                              ( ""
+                                              , []
+                                              ,
+                                                [
+                                                  ( "srcset"
+                                                  , Text.intercalate ", " $
+                                                      thumbs <&> \(size, name) ->
+                                                        [fmt|/{name} {Thumb.width size}w|]
+                                                  )
+                                                ,
+                                                  ( "sizes"
+                                                  , Text.intercalate
+                                                      ", "
+                                                      [ [fmt|(min-width: 640px) {Thumb.width maxThumbSize}px|]
+                                                      , [fmt|{Thumb.width minThumbSize}px|]
+                                                      ]
+                                                  )
+                                                ]
+                                              )
+                                              alt
+                                              ( toText $ '/' : maxThumbPath
+                                              , Pandoc.stringify alt -- title
+                                              )
+                                          ]
+                                          (encodeSlugs slugs, "")
+                                      , Pandoc.Span
+                                          ("", [], [])
+                                          [ Pandoc.Str [fmt|{Thumb.width maxThumbSize}x{Thumb.height maxThumbSize}|]
+                                          ]
+                                      ]
+                    | (slugs, thumbs) <- Map.toList modelPictures
+                    , prefix `List.isPrefixOf` slugs
+                    ]
+                  _ -> []
+          ]
+
+i18nAuthors = \case
+  LangEn -> "Author(s)"
+  LangFr -> "Auteur.rice(s)"
+  _ -> i18nUpdated LangEn
+i18nBacklinks = \case
+  LangEn -> "Backlinks"
+  LangFr -> "Rétroliens"
+  _ -> i18nBacklinks LangEn
+i18nLanguage = \case
+  LangEn -> "Language"
+  LangFr -> "Langage"
+  _ -> i18nLanguage LangEn
+i18nLisense = \case
+  LangEn -> "Lisense"
+  LangFr -> "License"
+  _ -> i18nLisense LangEn
+i18nDiscussion = \case
+  LangEn -> "Discussion"
+  LangFr -> "Discussion"
+  _ -> i18nDiscussion LangEn
+i18nUpdated = \case
+  LangEn -> "Updated"
+  LangFr -> "Mise-à-jour"
+  _ -> i18nUpdated LangEn
+i18nPublished = \case
+  LangEn -> "Published"
+  LangFr -> "Publication"
+  _ -> i18nPublished LangEn
+i18nLatestPosts = \case
+  LangEn -> "Latest Posts"
+  LangFr -> "Derniers Billets"
+  _ -> i18nLatestPosts LangEn
+i18nTableOfContent = \case
+  LangEn -> "Table of Content"
+  LangFr -> "Sommaire"
+  _ -> i18nTableOfContent LangEn
+
+similarPages :: Model -> ([Slug], Meta) -> Maybe H.Html
+similarPages model (pagePath, meta) =
+  if null posts
+    then Nothing
+    else Just do
+      H.nav
+        ! classes
+          [ "similars"
+          , "bg-gray-100"
+          , "border-1"
+          , "border-black"
+          , "p-0"
+          ]
+        $ do
+          H.h2
+            ! classes
+              [ "bg-black"
+              , "font-bold"
+              , "px-4"
+              , "text-left"
+              , "text-xs"
+              , "text-white"
+              ]
+            $ do
+              H.text "Similar Posts"
+              H.span ! classes ["font-normal"] $
+                H.text [fmt| ({List.length simPosts})|]
+          renderPagesListing model posts
+  where
+    simPosts =
+      List.filter (\(path, Page{..}) -> not (null (commonTags meta pageMeta)) && path /= pagePath) $
+        filterPosts model noFilter
+    posts = take 5 $ List.sortOn (negate . List.length . commonTags meta . pageMeta . snd) simPosts
+    commonTags x y = tagSet x `Set.intersection` tagSet y
+      where
+        tagSet = fromList . metaTags
+
+renderPagesListing :: Model -> [([Slug], Page)] -> H.Html
+renderPagesListing model pages =
+  H.ul do
+    forM_ pages $ \(pagePath, Page{..}) ->
+      H.li
+        ! classes
+          [ "border-t-2"
+          , "flex"
+          , "flex-col"
+          , "p-2"
+          ]
+        $ do
+          H.h6 ! classes ["text-sm"] $ do
+            H.a ! classes ["hover:bg-blue-50", "rounded"]
+              ! hrefRoute model (RoutePage pagePath)
+              $ H.text $ metaTitle pageMeta
+          unless (null (metaTags pageMeta)) do
+            H.div
+              ! classes ["flex", "flex-row", "flex-wrap"]
+              $ forM_ (metaTags pageMeta) $
+                renderTag model False
+          whenJust (metaSummary pageMeta) $ \Markdown{..} -> do
+            H.span
+              ! classes ["italic", "text-gray-700"]
+              $ htmlOfPandoc def markdownPandoc
+
+timeLocale :: Lang -> Time.TimeLocale
+timeLocale = \case
+  LangFr ->
+    Time.defaultTimeLocale
+      { Time.months =
+          [ ("janvier", "Jan")
+          , ("février", "Févr")
+          , ("mars", "Mar")
+          , ("avril", "Apr")
+          , ("mai", "May")
+          , ("juin", "Juin")
+          , ("juillet", "Juil")
+          , ("août", "Aoû")
+          , ("septembre", "Sep")
+          , ("octobre", "Oct")
+          , ("novembre", "Nov")
+          , ("décembre", "Déc")
+          ]
+      }
+  _ -> Time.defaultTimeLocale
+
+renderSpecial :: Model -> Page -> Content
+renderSpecial model Page{..} =
+  Content
+    { contentTitle = Just $ metaTitle pageMeta
+    , contentHtml = do
+        H.header $
+          H.h1
+            ! classes
+              [ "bg-black"
+              , "font-bold"
+              , "leading-relaxed"
+              , "mb-1"
+              , "mt-4"
+              , "text-center"
+              , "text-lg"
+              , "text-white"
+              ]
+            $ H.text $ metaTitle pageMeta
+        htmlOfPandoc def pageDoc
+        recentPages (metaLang pageMeta) model Nothing
+    }
+
+recentPages :: Lang -> Model -> Maybe Text -> H.Html
+recentPages lang model here =
+  H.nav
+    ! classes
+      [ "recents"
+      , "bg-gray-100"
+      , "border-1"
+      , "border-black"
+      , "p-0"
+      ]
+    $ do
+      H.h2
+        ! classes
+          [ "bg-black"
+          , "font-bold"
+          , "px-4"
+          , "text-left"
+          , "text-xs"
+          , "text-white"
+          ]
+        $ i18nLatestPosts lang
+      renderPagesListing model posts
+  where
+    posts =
+      List.take 5 $
+        List.filter (\(_, Page{..}) -> isJust (metaUpdated pageMeta) && maybe True (/= metaTitle pageMeta) here) $
+          filterPosts model noFilter
+
+filterPosts :: Model -> Filter -> [([Slug], Page)]
+filterPosts Model{..} (Filter lang tag) =
+  reverse $
+    List.sortOn (metaUpdated . pageMeta . snd) $
+      maybe id (\t -> List.filter (\(_, Page{..}) -> t `elem` metaTags pageMeta)) tag $
+        maybe id (\l -> List.filter (\(_, Page{..}) -> l == metaLang pageMeta)) lang $
+          Map.toList modelPosts
diff --git a/generator/Site/Render.hs b/generator/Site/Render.hs
new file mode 100644 (file)
index 0000000..6e3d926
--- /dev/null
@@ -0,0 +1,99 @@
+module Site.Render where
+
+import Data.Map.Strict qualified as Map
+import Data.Some (Some (..))
+import Data.Text qualified as Text
+import Ema qualified
+import Ema.CLI qualified
+import PyF
+import Relude
+import Text.Atom.Feed.Export qualified as Export (textFeed)
+import Text.Blaze.Html.Renderer.Utf8 qualified as H
+import Text.Blaze.Html5 ((!))
+import Text.Blaze.Html5 qualified as H
+import Text.Blaze.Html5.Attributes qualified as A
+import Prelude ()
+
+import Site.Body
+import Site.Feed
+import Site.Filter
+import Site.Model
+import Site.Page
+
+renderRoute :: Some Ema.CLI.Action -> Model -> Route -> Ema.Asset LByteString
+renderRoute emaAction model route = case route of
+  RoutePage pageName ->
+    Ema.AssetGenerated Ema.Html $
+      renderHtml $
+        case Map.lookup pageName (modelPosts model) of
+          Nothing -> error [fmt|Page not found in Model: {pageName:s}|]
+          Just page -> renderPage model (pageName, page)
+  RouteSpecial pageName ->
+    Ema.AssetGenerated Ema.Html $
+      renderHtml $
+        case Map.lookup pageName (modelSpecials model) of
+          Nothing -> error [fmt|Special Page not found in Model: {pageName:s}|]
+          Just page -> renderSpecial model page
+  RouteFeeds ->
+    Ema.AssetGenerated Ema.Html $
+      renderHtml
+        Content
+          { contentTitle = Just "Feeds"
+          , contentHtml = feedList model
+          }
+  RouteFilter filt ->
+    Ema.AssetGenerated Ema.Html $
+      renderHtml $
+        renderFilter model filt
+  RouteFilterAtom filt ->
+    Ema.AssetGenerated Ema.Other $
+      maybe (error "Feed malformed?") encodeUtf8 $
+        Export.textFeed $ mkFeed model filt
+  where
+    renderHtml content@Content{..} =
+      H.renderHtml do
+        H.docType
+        H.html ! A.lang "fr" $ do
+          H.head do
+            H.meta ! A.charset "UTF-8"
+            -- This makes the site mobile-friendly by default.
+            H.meta ! A.name "viewport" ! A.content "width=device-width, initial-scale=1"
+            -- When Ema.CLI.Generate this is https://unpkg.com/tailwindcss@2/dist/tailwind.css
+            -- When Ema.CLI.Run this is the output of windicss on the generated .html
+            H.link
+              ! A.rel "stylesheet"
+              ! A.type_ "text/css"
+              ! A.href "/static/css/windi.css"
+            when (Ema.CLI.isLiveServer emaAction) $
+              -- Add WindiCSS classes missing from https://unpkg.com/tailwindcss@2/dist/tailwind.css
+              H.link
+                ! A.rel "stylesheet"
+                ! A.type_ "text/css"
+                ! A.href "/static/css/windi-extras.css"
+            let localTitle = Text.intercalate " - " $ maybeToList contentTitle <> [orgName]
+            H.title $ H.text localTitle
+            --H.base ! A.href "/"
+            let description = "Some description."
+            H.meta ! A.name "description" ! A.content description
+            let openGraph (name :: Text) contentTag =
+                  H.meta
+                    ! H.customAttribute "property" [fmt|openGraph:{name}|]
+                    ! A.content contentTag
+            openGraph "title" $ H.preEscapedTextValue localTitle
+            openGraph "description" description
+            openGraph "image" $ H.preEscapedTextValue $ absoluteLink model $ Left "static/img/image.jpg"
+            openGraph "image:alt" "some logo"
+            openGraph "locale" "fr_FR"
+            openGraph "type" "article"
+            openGraph "url" $ H.preEscapedTextValue $ absoluteLink model $ Left ""
+            -- H.link ! A.rel "icon" ! A.type_ "image/png" ! A.href "/static/img/favicon.png"
+            H.link
+              ! A.href "/static/css/extra.css"
+              ! A.rel "stylesheet"
+              ! A.type_ "text/css"
+            H.link
+              ! hrefRoute model (RouteFilterAtom $ Filter Nothing Nothing)
+              ! A.rel "alternate"
+              ! A.title [fmt|{orgName} - All Posts|]
+              ! A.type_ "application/atom+xml"
+        renderBody model route content
diff --git a/generator/Site/Tag.hs b/generator/Site/Tag.hs
new file mode 100644 (file)
index 0000000..7fda73c
--- /dev/null
@@ -0,0 +1,70 @@
+module Site.Tag where
+
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import PyF
+import Relude
+import Text.Blaze.Html5 ((!))
+import Text.Blaze.Html5 qualified as H
+--import Text.Blaze.Html5.Attributes qualified as A
+import Prelude ()
+
+import Site.Model
+import Utils.Html
+
+renderTags :: Model -> Maybe H.Html
+renderTags model = do
+  let tags = allTags model
+  if null tags
+  then Nothing
+  else Just do
+    H.aside ! classes ["mb-3"] $
+      H.nav ! classes [ "tags"
+                      , "bg-gray-100"
+                      , "border-1"
+                      , "border-black"
+                      , "p-0"
+                      ] $ do
+        H.h2
+          ! classes [ "bg-black"
+                    , "font-bold"
+                    , "px-4"
+                    , "text-left"
+                    , "text-xs"
+                    , "text-white"
+                    ]
+          $ "All Tags"
+        H.span
+          ! classes ["flex", "flex-wrap", "justify-start", "p-2"]
+          $ forM_ tags $
+             renderTag model True
+
+renderTag :: Model -> Bool -> Tag -> H.Html
+renderTag model showCount tag =
+  H.a
+    ! smallCaps
+      [ "tag"
+      , "bg-yellow-200"
+      , "float-left"
+      , "hover:bg-yellow-400"
+      , "inline-block"
+      , "m-px"
+      , "pr-1"
+      , "text-black"
+      , "text-xs"
+      , "whitespace-nowrap"
+      ]
+    ! hrefRoute model (RouteFilter (Filter Nothing (Just tag)))
+    $ do
+      openIconic "tag" ! classes ["inline-block", "m-1"]
+      H.span ! classes ["inline-block"] $ H.text $ unTag tag
+      memptyIfFalse showCount $
+        H.span
+          ! classes ["ml-1", "text-gray-500"]
+          $ [fmt|({count})|]
+  where
+    count =
+      List.length $
+        List.filter (tag `elem`) $
+          metaTags . pageMeta <$> Map.elems (modelPosts model)
+
diff --git a/generator/Site/Update.hs b/generator/Site/Update.hs
new file mode 100644 (file)
index 0000000..9dbfcef
--- /dev/null
@@ -0,0 +1,247 @@
+module Site.Update where
+
+import Commonmark.Simple qualified as Markdown
+import Control.Monad.Logger (MonadLoggerIO, logErrorNS, logInfoNS)
+import Control.Monad.Trans.Resource (MonadResource, runResourceT)
+import Data.Char qualified as Char
+import Data.LVar (LVar)
+import Data.List qualified as List
+import Data.List.Split (splitOn)
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Some (Some)
+import Data.Text qualified as Text
+import Data.Time qualified as Time
+import Data.Time.Clock.POSIX qualified as Time
+import Ema.CLI qualified
+import Graphics.ThumbnailPlus qualified as Thumb
+import Network.URI.Slug (Slug, decodeSlug)
+import PyF
+import Relude
+import System.Directory qualified as FS
+import System.FilePath ((</>))
+import System.FilePath qualified as FS
+import System.IO.Error qualified as IO
+import System.Process (readProcess)
+import System.UnionMount qualified as Watch
+import Text.Pandoc.Definition qualified as Pandoc
+import Text.Pandoc.Walk qualified as Pandoc
+import Text.Read (read)
+import UnliftIO (MonadUnliftIO)
+import Prelude ()
+
+import Site.Model
+
+data Watch
+  = -- | A file generated by flake.nix
+    Watch_LastModified
+  | Watch_Static_Pics
+  | Watch_Special_Markdown
+  | Watch_Posts_Markdown
+  deriving (Eq, Show, Ord)
+
+updateModel ::
+  (MonadIO m, MonadUnliftIO m, MonadLoggerIO m) =>
+  Some Ema.CLI.Action ->
+  LVar Model ->
+  m ()
+updateModel _ model =
+  void
+    . Watch.mountOnLVar
+      "."
+      [ (Watch_LastModified, "lastModified")
+      , (Watch_Static_Pics, "static/pics/**/*.jpg")
+      , (Watch_Special_Markdown, "special/**/*.md")
+      , (Watch_Posts_Markdown, "posts/**/*.md")
+      ]
+      {-ignored-}
+      [ "*~"
+      , ".*"
+      , "*.swp"
+      , "*.swx"
+      , "drafts/*"
+      , "static/thumbs/**"
+      ]
+      {-current-} model
+      {-initial-} Model
+        { modelTime = Nothing
+        , modelPosts = mempty
+        , modelSpecials = mempty
+        , modelPictures = mempty
+        , modelLocalLinks = mempty
+        }
+    $ \case
+      Watch_LastModified -> \filePath -> \case
+        Watch.Delete -> pure $ \m -> m{modelTime = Nothing}
+        Watch.Refresh _refreshAction () -> do
+          lastMod <- readFile filePath
+          modelTime <-
+            either error (pure . Just) $
+              Time.parseTimeM True Time.defaultTimeLocale "%s" lastMod
+          pure $ \m -> m{modelTime}
+      Watch_Static_Pics -> \filePath ->
+        let slugs = (decodeSlug <$>) $ Text.splitOn "/" $ toText filePath
+         in let thumbsDir = FS.joinPath $ ["static", "thumbs"] <> List.drop 2 (FS.splitPath filePath)
+             in \case
+                  Watch.Delete -> do
+                    logInfoNS domainName [fmt|Removing thumbsDir {thumbsDir}|]
+                    liftIO $ FS.removeDirectoryRecursive thumbsDir
+                    pure $ \m -> m{modelPictures = Map.delete slugs (modelPictures m)}
+                  Watch.Refresh refreshAction () ->
+                    case refreshAction of
+                      Watch.New -> do
+                        thumbs <- runResourceT $ createThumbnails thumbsDir filePath
+                        pure $ \m -> m{modelPictures = Map.insert slugs thumbs (modelPictures m)}
+                      Watch.Update -> do
+                        thumbs <- runResourceT $ createThumbnails thumbsDir filePath
+                        pure $ \m -> m{modelPictures = Map.insert slugs thumbs (modelPictures m)}
+                      Watch.Existing -> do
+                        dirEither <- liftIO $ IO.tryIOError $ FS.listDirectory thumbsDir
+                        thumbs <- case dirEither of
+                          Left err
+                            | IO.isDoesNotExistError err ->
+                              runResourceT $ createThumbnails thumbsDir filePath
+                            | otherwise -> error [fmt|{err:s}|]
+                          Right dir ->
+                            return $
+                              List.sortOn (Thumb.width . fst) $
+                                dir <&> \fp -> (thumbSizeFromFilePath fp, thumbsDir </> fp)
+                        pure $ \m -> m{modelPictures = Map.insert slugs thumbs (modelPictures m)}
+      Watch_Posts_Markdown -> \filePath ->
+        loadMarkdown filePath $ \f m -> m{modelPosts = f (modelPosts m)}
+      Watch_Special_Markdown -> \filePath ->
+        loadMarkdown filePath $ \f m -> m{modelSpecials = f (modelSpecials m)}
+
+loadMarkdown ::
+  MonadIO m =>
+  MonadLoggerIO m =>
+  FilePath ->
+  ((Map [Slug] Page -> Map [Slug] Page) -> Model -> Model) ->
+  Watch.FileAction () ->
+  m (Model -> Model)
+loadMarkdown filePath modifyModel =
+  let pageSlugs =
+        (decodeSlug <$>) $
+          List.drop 1 $
+            Text.splitOn "/" $
+              toText $
+                FS.dropExtension filePath
+   in \case
+        Watch.Delete -> pure $ modifyModel $ Map.delete pageSlugs
+        Watch.Refresh _refreshAction () -> do
+          fileContent <- Relude.readFileText filePath
+          Markdown.parseMarkdownWithFrontMatter
+            Markdown.fullMarkdownSpec
+            filePath
+            fileContent
+            & \case
+              Right (Just Meta{..}, pageDoc) -> do
+                -- Get default Meta values from Git commits
+                -- in case their not specified within the Mardown FrontMatter.
+                -- Does not work within pure nix build because there is not .git there.
+                (authorDate, commitDate, authorName, authorMail) <-
+                  liftIO (gitLog "%ad\n%cd\n%an\n%ae\n" filePath) <&> \case
+                    (List.lines -> [log_ad, log_cd, log_an, log_ae]) ->
+                      ( gregorianOfEpoch <$> readMaybe @Integer log_ad
+                      , gregorianOfEpoch <$> readMaybe @Integer log_cd
+                      , Just $ toText log_an
+                      , Just $ toText log_ae
+                      )
+                    _ -> (Nothing, Nothing, Nothing, Nothing)
+                let pageLocalLinks =
+                      (`Pandoc.query` pageDoc) $ \case
+                        Pandoc.Link _attrs _label (uri, _title)
+                          | not (Text.isInfixOf ":" uri) -> do
+                            -- keep only local URIs
+                            Set.singleton $ localLink pageSlugs uri
+                        _inl -> Set.empty
+                pure $ \model ->
+                  modifyModel
+                    ( Map.insert
+                        pageSlugs
+                        Page
+                          { pageMeta =
+                              Meta
+                                { metaPublished = metaPublished <|> authorDate
+                                , metaUpdated = metaUpdated <|> commitDate
+                                , metaAuthors =
+                                    metaAuthors
+                                      <|> ( authorName <&> \entityName ->
+                                              pure
+                                                Entity
+                                                  { entityName
+                                                  , entityMail = authorMail
+                                                  }
+                                          )
+                                , ..
+                                }
+                          , pageDoc
+                          , pageLocalLinks
+                          }
+                    )
+                    model{modelLocalLinks = Map.insert pageSlugs pageLocalLinks (modelLocalLinks model)}
+              {-
+              pure $ modifyModel $ Map.insert pageSlugs Page {pageMeta = Meta{..}, ..}
+              -}
+              Left err -> id <$ logErrorNS domainName [fmt|Parse error on {filePath}: {err}|]
+              _ -> id <$ logErrorNS domainName [fmt|Failed to parse Meta on {filePath}|]
+
+createThumbnails ::
+  MonadResource m =>
+  MonadLoggerIO m =>
+  FilePath ->
+  FilePath ->
+  m [(Thumb.Size, FilePath)]
+createThumbnails thumbsDir picPath = do
+  liftIO $ FS.createDirectoryIfMissing True thumbsDir
+  Thumb.createThumbnails thumbsConfig picPath >>= \case
+    Thumb.CreatedThumbnails thumbs _releaseKeys -> do
+      logInfoNS domainName [fmt|Creating {List.length thumbs} thumbs in {thumbsDir}|]
+      liftIO $ do
+        forM thumbs $ \thumb -> do
+          let dest = thumbsDir </> thumbName thumb
+          FS.copyFile (Thumb.thumbFp thumb) dest
+          return (Thumb.thumbSize thumb, dest)
+    err -> do
+      logErrorNS domainName [fmt|Error: {show err :: String}|]
+      return []
+  where
+    thumbsConfig =
+      Thumb.Configuration
+        { Thumb.maxFileSize = 10 * 1024 * 1024
+        , Thumb.maxImageSize = Thumb.Size 4096 4096
+        , Thumb.reencodeOriginal = Thumb.Never
+        , Thumb.thumbnailSizes = List.map (\s -> (Thumb.Size s s, Nothing)) thumbSizes
+        , Thumb.temporaryDirectory = FS.getTemporaryDirectory
+        }
+
+thumbSizes :: [Int]
+thumbSizes = [300, 800]
+
+thumbName :: Thumb.Thumbnail -> String
+thumbName Thumb.Thumbnail{thumbSize = Thumb.Size{..}, ..} =
+  [fmt|{width}x{height}.{(Char.toLower <$> show @String thumbFormat)}|]
+
+thumbSizeFromFilePath :: FilePath -> Thumb.Size
+thumbSizeFromFilePath fp =
+  case splitOn "x" $ List.reverse $ List.takeWhile (/= '-') $ List.reverse $ FS.takeBaseName fp of
+    [w, h] -> Thumb.Size (read w) (read h)
+    _ -> error [fmt|Cannot parse thumb's sizes from filepath: {fp}|]
+
+utcOfEpoch :: Integral a => a -> Time.UTCTime
+utcOfEpoch = Time.posixSecondsToUTCTime . fromIntegral
+gregorianOfEpoch :: Integral a => a -> Time.Day
+gregorianOfEpoch = Time.utctDay . utcOfEpoch
+
+gitLog :: String -> FilePath -> IO String
+gitLog format filePath =
+  readProcess
+    "git"
+    [ "log"
+    , "-1"
+    , "HEAD"
+    , "--pretty=format:" ++ format
+    , "--date=format:%s"
+    , filePath
+    ]
+    ""
diff --git a/generator/Utils/Html.hs b/generator/Utils/Html.hs
new file mode 100644 (file)
index 0000000..28d0217
--- /dev/null
@@ -0,0 +1,25 @@
+module Utils.Html where
+
+import Data.Text qualified as Text
+import PyF
+import Relude
+import Text.Blaze.Html5 ((!))
+import Text.Blaze.Html5 qualified as H
+import Text.Blaze.Html5.Attributes qualified as A
+import Text.Blaze.Internal qualified as A
+import Prelude ()
+
+-- Use filter from https://codepen.io/sosuke/pen/Pjoqqp to change the color.
+openIconic :: Text -> H.Html
+openIconic iconName =
+  H.img
+    ! A.src [fmt|/static/icons/open-iconic/svg/{iconName}.svg|]
+    ! A.alt [fmt|{iconName}|]
+
+classes :: [Text] -> A.Attribute
+classes = A.class_ . H.textValue . Text.intercalate " "
+
+smallCaps :: [Text] -> A.Attribute
+smallCaps cs =
+  classes ("tracking-wider" : cs)
+    <> A.style "font-variant: small-caps"
diff --git a/generator/Utils/Pandoc.hs b/generator/Utils/Pandoc.hs
new file mode 100644 (file)
index 0000000..d615a08
--- /dev/null
@@ -0,0 +1,151 @@
+module Utils.Pandoc where
+
+import Data.List qualified as List
+import Relude
+import Text.Pandoc.Definition
+import Text.Pandoc.Walk (query)
+
+trimWhiteInlines :: [Inline] -> [Inline]
+trimWhiteInlines =
+  List.dropWhile isWhite .
+  List.dropWhileEnd isWhite
+  where
+  isWhite Space = True
+  isWhite SoftBreak = True
+  isWhite _ = False
+
+{-
+renderPandocBlock :: B.Block -> H.Html
+renderPandocBlock = \case
+  B.BlockQuote bs ->
+    H.blockquote $ forM_ bs renderPandocBlock
+  B.BulletList bss ->
+    H.ul ! A.class_ (listStyle <> " list-disc") $
+      forM_ bss $ \bs ->
+        H.li ! A.class_ listItemStyle $ forM_ bs renderPandocBlock
+  B.CodeBlock (id', classes, attrs) s ->
+    -- Prism friendly classes
+    let classes' = flip List.concatMap classes $ \classes -> [classes, "language-" <> classes]
+     in H.div
+          ! A.class_ "py-0.5 text-sm"
+          $ H.pre
+            ! renderPandocAttr (id', classes', attrs)
+            $ H.code
+              ! renderPandocAttr ("", classes', [])
+              $ H.text s
+  B.DefinitionList defs ->
+    H.dl $
+      forM_ defs $ \(term, descList) -> do
+        forM_ term renderPandocInline
+        forM_ descList $ \desc ->
+          H.dd $ forM_ desc renderPandocBlock
+  B.Div attr bs ->
+    H.div ! renderPandocAttr attr $ forM_ bs renderPandocBlock
+  B.Header level attr is ->
+    renderHeader level ! renderPandocAttr attr $ do
+      fromString $ show attr
+      forM_ is renderPandocInline
+  B.HorizontalRule -> H.hr
+  B.LineBlock iss ->
+    forM_ iss $ \is ->
+      forM_ is renderPandocInline >> "\n"
+  B.Null -> pure ()
+  B.OrderedList _ bss ->
+    H.ol ! A.class_ (listStyle <> " list-decimal") $
+      forM_ bss $ \bs ->
+        H.li ! A.class_ listItemStyle $
+          forM_ bs renderPandocBlock
+  B.Para is ->
+    H.p ! A.class_ "my-2" $ forM_ is renderPandocInline
+  B.Plain is ->
+    forM_ is renderPandocInline
+  B.RawBlock (B.Format fmt) html
+    | fmt == "html" -> H.unsafeByteString $ encodeUtf8 html
+    | otherwise -> throw Unsupported
+  B.Table{} ->
+    throw Unsupported
+  where
+    listStyle = "list-inside ml-2 space-y-1 "
+    listItemStyle = ""
+
+renderHeader :: Int -> H.Html -> H.Html
+renderHeader = \case
+  1 -> H.h1 ! classes ("text-xl":cs)
+  2 -> H.h2 ! classes ("text-xl":cs)
+  3 -> H.h3 ! classes ("text-lg":cs)
+  4 -> H.h4 ! classes ("text-lg":cs)
+  5 -> H.h5 ! classes ("text-lg":cs)
+  6 -> H.h6 ! classes ("text-lg":cs)
+  _ -> error "Invalid pandoc header level"
+  where
+    cs = ["mt-4", "mb-2", "font-bold"]
+
+renderPandocInline :: B.Inline -> H.Html
+renderPandocInline = \case
+  B.Code attr s ->
+    H.code ! renderPandocAttr attr $ H.toHtml s
+  B.Emph is ->
+    H.em $ forM_ is renderPandocInline
+  B.Image attr is (url, title) ->
+    H.img ! A.src (H.textValue url) ! A.title (H.textValue title) ! A.alt (H.textValue $ Markdown.plainify is) ! renderPandocAttr attr
+  B.Link attr is (url, title) -> do
+    let (classes, target) =
+          if "://" `T.isInfixOf` url
+            then ("text-blue-600 hover:underline", targetBlank)
+            else ("text-blue-600 hover:bg-blue-50", mempty)
+    H.a
+      ! A.class_ classes
+      ! A.href (H.textValue url)
+      ! A.title (H.textValue title)
+      ! target
+      ! renderPandocAttr attr
+      $ forM_ is renderPandocInline
+  B.LineBreak -> H.br
+  B.Math _ _ ->
+    throw Unsupported
+  B.Note _ ->
+    throw Unsupported
+  B.Quoted qt is ->
+    flip inQuotes qt $ forM_ is renderPandocInline
+  B.RawInline _fmt s ->
+    H.pre $ H.toHtml s
+  B.SoftBreak -> " "
+  B.Space -> " "
+  B.Span attr is ->
+    H.span ! renderPandocAttr attr $ forM_ is renderPandocInline
+  B.Str s -> H.toHtml s
+  B.Strikeout is ->
+    H.del $ forM_ is renderPandocInline
+  B.Strong is ->
+    H.strong $ forM_ is renderPandocInline
+  B.Subscript is ->
+    H.sub $ forM_ is renderPandocInline
+  B.Superscript is ->
+    H.sup $ forM_ is renderPandocInline
+  B.Underline is ->
+    H.u $ forM_ is renderPandocInline
+  x ->
+    H.pre $ H.toHtml $ show @Text x
+  where
+    inQuotes :: H.Html -> B.QuoteType -> H.Html
+    inQuotes w = \case
+      B.SingleQuote -> "‘" >> w <* "’"
+      B.DoubleQuote -> "“" >> w <* "”"
+
+targetBlank :: H.Attribute
+targetBlank = A.target "_blank" <> A.rel "noopener"
+
+renderPandocAttr :: B.Attr -> H.Attribute
+renderPandocAttr (id_, classes, attrs) =
+  unlessNull id_ (A.id (H.textValue id_))
+    <> unlessNull class_ (A.class_ (H.textValue class_))
+    <> foldMap (\(k, v) -> H.dataAttribute (H.textTag k) (H.textValue v)) attrs
+  where
+    class_ = T.intercalate " " classes
+    unlessNull x f
+      | T.null x = mempty
+      | otherwise = f
+
+data Unsupported = Unsupported
+  deriving (Show, Exception)
+-}
diff --git a/generator/Utils/Pandoc/Html.hs b/generator/Utils/Pandoc/Html.hs
new file mode 100644 (file)
index 0000000..12d4b4c
--- /dev/null
@@ -0,0 +1,22 @@
+module Utils.Pandoc.Html where
+
+import Relude
+import Data.List qualified as List
+import Text.Blaze.Html5 qualified as H
+import Text.Pandoc (runPure, writeHtml5, PandocPure, WriterOptions)
+import Text.Pandoc.Writers.Shared qualified as P
+import Text.Pandoc.Walk qualified as P
+import Text.Pandoc.Builder qualified as B
+import Text.Pandoc.Options qualified as P
+import Text.Pandoc.Shared qualified as P
+import Text.Pandoc.Definition (Pandoc (..))
+import Prelude ()
+import Data.Default (def)
+import PyF
+import Data.Text qualified as Text
+
+htmlOfPandoc :: WriterOptions -> Pandoc -> H.Html
+htmlOfPandoc opts = unPandocM . writeHtml5 opts
+
+unPandocM :: PandocPure a -> a
+unPandocM p = either (error . show) id (runPure p)
diff --git a/generator/Utils/Pandoc/SignsCount.hs b/generator/Utils/Pandoc/SignsCount.hs
new file mode 100644 (file)
index 0000000..34647ed
--- /dev/null
@@ -0,0 +1,65 @@
+module Utils.Pandoc.SignsCount where
+
+--import Text.HTML.TagSoup (innerText, parseTags)
+import Data.Text qualified as Text
+import Relude
+import Text.Pandoc.Definition
+
+signsCount :: Pandoc -> Int
+signsCount (Pandoc _ bs) = sum $ map goBlock bs
+  where
+    goBlocks = sum . map goBlock
+    goBlocks2 = sum . map goBlocks
+
+    goBlock :: Block -> Int
+    goBlock (BlockQuote bs') = goBlocks bs'
+    goBlock (BulletList bss) = goBlocks2 bss
+    goBlock (CodeBlock _ s) = Text.length s
+    goBlock (DefinitionList ls) = sum $ map (\(is, bss) -> goInlines is + goBlocks2 bss) ls
+    goBlock (Div (id', classes, _) bs')
+      | id' == "refs" && "references" `elem` classes = 0
+      | otherwise = goBlocks bs'
+    goBlock (Header _ _ is) = goInlines is
+    goBlock HorizontalRule = 0
+    goBlock (LineBlock iss) = goInlines2 iss
+    goBlock (OrderedList _ bss) = goBlocks2 bss
+    goBlock (Para is) = goInlines is
+    goBlock (Plain is) = goInlines is
+    -- TODO: goBlock (RawBlock _ s) = Text.length . innerText . parseTags $ s
+    goBlock (RawBlock _ s) = 0
+    goBlock Null = 0
+    goBlock (Table _ (Caption msc bs') _ (TableHead _ thrs) tbs (TableFoot _ tfrs)) =
+      goInlines (concat (maybeToList msc)) +
+      goBlocks bs' +
+      goRows thrs +
+      goRows tfrs +
+      sum (map (\(TableBody _ _ hr br) -> goRows hr + goRows br) tbs)
+
+    goRows = sum . map goRow
+    goRow (Row _ cells) = sum . map goCell $ cells
+    goCell (Cell _ _ _ _ bs') = goBlocks bs'
+
+    goInlines = sum . map goInline
+    goInlines2 = sum . map goInlines
+
+    goInline :: Inline -> Int
+    goInline (Cite _ is) = goInlines is
+    goInline (Code _ s) = Text.length s
+    goInline (Emph is) = goInlines is
+    goInline (Image _ is (_, s)) = goInlines is + Text.length s
+    goInline (Link _ is (_, s)) = goInlines is + Text.length s
+    goInline (Math _ s) = Text.length s
+    goInline (Note bs') = goBlocks bs'
+    goInline (Quoted _ is) = goInlines is
+    goInline (RawInline _ s) = Text.length s
+    goInline (SmallCaps is) = goInlines is
+    goInline (Span _ s) = goInlines s
+    goInline (Str s) = Text.length s
+    goInline (Strikeout is) = goInlines is
+    goInline (Strong is) = goInlines is
+    goInline (Subscript is) = goInlines is
+    goInline (Superscript is) = goInlines is
+    goInline (Underline is) = goInlines is
+    goInline LineBreak = 1
+    goInline SoftBreak = 1
+    goInline Space = 1
diff --git a/generator/css/windi-extras.html b/generator/css/windi-extras.html
new file mode 100644 (file)
index 0000000..c6c9c6f
--- /dev/null
@@ -0,0 +1,46 @@
+<!-- This file contain WindiCSS classes missing
+     from https://unpkg.com/tailwindcss@2/dist/tailwind.css
+-->
+<a class="
+  border-1
+  text-orange-50
+  text-orange-100
+  text-orange-200
+  text-orange-300
+  text-orange-400
+  text-orange-500
+  text-orange-600
+  text-orange-700
+  text-orange-800
+  text-orange-900
+  bg-orange-50
+  bg-orange-100
+  bg-orange-200
+  bg-orange-300
+  bg-orange-400
+  bg-orange-500
+  bg-orange-600
+  bg-orange-700
+  bg-orange-800
+  bg-orange-900
+  hover:fg-orange-50
+  hover:fg-orange-100
+  hover:fg-orange-200
+  hover:fg-orange-300
+  hover:fg-orange-400
+  hover:fg-orange-500
+  hover:fg-orange-600
+  hover:fg-orange-700
+  hover:fg-orange-800
+  hover:fg-orange-900
+  hover:bg-orange-50
+  hover:bg-orange-100
+  hover:bg-orange-200
+  hover:bg-orange-300
+  hover:bg-orange-400
+  hover:bg-orange-500
+  hover:bg-orange-600
+  hover:bg-orange-700
+  hover:bg-orange-800
+  hover:bg-orange-900
+"/>
diff --git a/generator/fourmolu.yaml b/generator/fourmolu.yaml
new file mode 100644 (file)
index 0000000..e7453cd
--- /dev/null
@@ -0,0 +1,8 @@
+comma-style: leading
+diff-friendly-import-export: true
+haddock-style: multi-line
+indent-wheres: true
+indentation: 2
+newlines-between-decls: 1
+record-brace-space: false
+respectful: true
diff --git a/generator/generator.cabal b/generator/generator.cabal
new file mode 100644 (file)
index 0000000..6049a7d
--- /dev/null
@@ -0,0 +1,114 @@
+cabal-version:      3.0
+name:               generator
+maintainer:         mailto:~julm/sourcephile-web@todo.code.sourcephile.fr
+bug-reports:        https://todo.code.sourcephile.fr/~julm/sourcephile-web
+homepage:           https://sourcephile.fr
+author:             Julien Moutinho <julm+sourcephile-web@sourcephile.fr>
+copyright:          Julien Moutinho <julm+sourcephile-web@sourcephile.fr>
+license:            AGPL-3.0-or-later
+
+-- PVP:  +-+------- breaking API changes
+--       | | +----- non-breaking API additions
+--       | | | +--- code changes with no API change
+version:            0.0.0.20211205
+stability:          experimental
+category:           Web
+synopsis:           sourcephile.fr
+description:        A website for Sourcephile, hosted at https://sourcephile.fr
+build-type:         Simple
+tested-with:        GHC ==8.10.7
+
+-- extra-doc-files:    ../ChangeLog.md
+extra-source-files:
+  .hlint.yaml
+  cabal.project
+  hie.yaml
+
+extra-tmp-files:
+
+source-repository head
+  type:     git
+  location: https://git.code.sourcephile.fr/~julm/sourcephile-web
+
+executable generator
+  build-depends:
+    , aeson
+    , base
+    , blaze-html
+    , blaze-markup
+    , commonmark
+    , commonmark-pandoc
+    , commonmark-simple
+    , containers
+    , data-default
+    , directory
+    , ema                >=0.6
+    , feed
+    , filepath
+    , lvar
+    , megaparsec
+    , monad-logger
+    , pandoc
+    , pandoc-types
+    , parsec
+    , process
+    , PyF                >=0.10
+    , relude
+    , resourcet
+    , some
+    , split
+    , text
+    , thumbnail-plus
+    , time
+    , transformers
+    , unionmount         >=0.1
+    , unliftio
+    , uri-encode
+    , url-slug
+
+  ghc-options:
+    -Wall -Wincomplete-record-updates -Wincomplete-uni-patterns
+
+  default-extensions:
+    NoImplicitPrelude
+    BlockArguments
+    DataKinds
+    DeriveAnyClass
+    DeriveGeneric
+    DerivingVia
+    FlexibleContexts
+    FlexibleInstances
+    ImportQualifiedPost
+    KindSignatures
+    LambdaCase
+    MultiParamTypeClasses
+    MultiWayIf
+    NamedFieldPuns
+    OverloadedStrings
+    PartialTypeSignatures
+    QuasiQuotes
+    RecordWildCards
+    ScopedTypeVariables
+    TupleSections
+    TypeApplications
+    TypeFamilies
+    ViewPatterns
+
+  main-is:            Main.hs
+  hs-source-dirs:     .
+  other-modules:
+    Site.Body
+    Site.Feed
+    Site.Filter
+    Site.Lang
+    Site.Model
+    Site.Page
+    Site.Render
+    Site.Tag
+    Site.Update
+    Utils.Html
+    Utils.Pandoc
+    Utils.Pandoc.Html
+    Utils.Pandoc.SignsCount
+
+  default-language:   Haskell2010
diff --git a/generator/hie.yaml b/generator/hie.yaml
new file mode 100644 (file)
index 0000000..c5a5dd3
--- /dev/null
@@ -0,0 +1,4 @@
+cradle:
+  cabal:
+    - path: "."
+      component: "generator"