updated autolad project.el mechanism

This commit is contained in:
Yann Esposito (Yogsototh) 2019-08-20 15:35:54 +02:00
parent 591fe583bb
commit b841541d3a
Signed by untrusted user who does not match committer: yogsototh
GPG Key ID: 7B19A4C650D59646
8 changed files with 360 additions and 183 deletions

Binary file not shown.

270
project.el Normal file
View File

@ -0,0 +1,270 @@
(setq domainname "https://her.esy.fun")
(setq base-dir (concat (projectile-project-root) "src"))
(setq publish-dir (concat (projectile-project-root) "_site"))
(setq assets-dir (concat base-dir "/"))
(setq publish-assets-dir (concat publish-dir "/"))
(setq rss-dir base-dir)
(setq rss-title "Subscribe to articles")
(setq publish-rss-dir publish-dir)
(setq css-path "/css/minimalist.css")
(setq author-name "Yann Esposito")
(setq author-email "yann@esposito.host")
(require 'org)
(require 'ox-publish)
(require 'ox-html)
(require 'org-element)
(require 'ox-rss)
(setq org-link-file-path-type 'relative)
(setq org-publish-timestamp-directory
(concat (projectile-project-root) "_cache/"))
(defvar org-blog-head
(concat
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" css-path "\"/>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<link rel=\"alternative\" type=\"application/rss+xml\" title=\"" rss-title "\" href=\"/archives.xml\" />"
"<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/favicon.ico\">"))
(defun menu (lst)
"Blog menu"
(concat
"<navigation>"
(mapconcat 'identity
(append
'("<a href=\"/index.html\">Home</a>"
"<a href=\"/archive.html\">Posts</a>"
"<a href=\"/about-me.html\">About</a>")
lst)
" | ")
"</navigation>"))
(defun str-time-to-year-float (date-str)
(/ (float-time
(apply 'encode-time
(mapcar (lambda (x) (if (null x) 0 x))
(parse-time-string date-str))))
(* 365.25 24 60 60)))
(defvar blog-creation-date "2019-07-01")
(defun y-date (date-str)
"Number of year since the begining of this blog"
(let ((y (- (str-time-to-year-float date-str)
(str-time-to-year-float blog-creation-date))))
(format "∆t=%.2f" y)))
(defun get-from-info (info k)
(let ((i (car (plist-get info k))))
(when (and i (stringp i))
i)))
(defun org-blog-preamble (info)
"Pre-amble for whole blog."
(concat
"<div class=\"content\">"
(menu '("<a href=\"#postamble\">↓ bottom ↓</a>"))
"<h1>"
(format "%s" (car (plist-get info :title)))
"</h1>"
(when-let ((date (plist-get info :date)))
(format "<span class=\"article-date\">%s</span>"
(format-time-string "%Y-%m-%d"
(org-timestamp-to-time
(car date)))))
(when-let ((subtitle (car (plist-get info :subtitle))))
(format "<h2>%s</h2>" subtitle))
"</div>"))
(defun rand-obfs (c)
(let ((r (% (random) 20)))
(cond ;; ((eq 0 r) (format "%c" c))
((<= 0 r 10) (format "&#%d;" c))
(t (format "&#x%X;" c)))))
(defun obfuscate-html (txt)
(apply 'concat
(mapcar 'rand-obfs txt)))
(defun org-blog-postamble (info)
"Post-amble for whole blog."
(concat
"<div class=\"content\">"
;; TODO install a comment system
;; (let ((url (format "%s%s" domainname (replace-regexp-in-string base-dir "" (plist-get info :input-file)))))
;; (format "<a href=\"https://comments.esy.fun/slug/%s\">comment</a>"
;; (url-hexify-string url)))
"<footer>"
(when-let ((author (get-from-info info :author)))
(if-let ((email (plist-get info :email)))
(let* ((obfs-email (obfuscate-html email))
(obfs-author (obfuscate-html author))
(obfs-title (obfuscate-html (get-from-info info :title)))
(full-email (format "%s &lt;%s&gt;" obfs-author obfs-email)))
(format "<div class=\"author\">Author: <a href=\"%s%s%s%s\">%s</a></div>"
(obfuscate-html "mailto:")
full-email
(obfuscate-html "?subject=yblog: ")
obfs-title
full-email))
(format "<div class=\"author\">Author: %s</div>" author)))
(when-let ((date (get-from-info info :date)))
(format "<div class=\"date\">Created: %s (%s)</div>" date (y-date date)))
(when-let ((keywords (plist-get info :keywords)))
(format "<div class=\"keywords\">Keywords: <code>%s</code></div>" keywords))
(format "<div class=\"date\">Generated: %s</div>"
(format-time-string "%Y-%m-%d %H:%M:%S"))
(format (concat "<div class=\"creator\"> Generated with "
"<a href=\"https://www.gnu.org/software/emacs/\" target=\"_blank\" rel=\"noopener noreferrer\">Emacs %s</a>, "
"<a href=\"http://spacemacs.org\" target=\"_blank\" rel=\"noopener noreferrer\">Spacemacs %s</a>, "
"<a href=\"http://orgmode.org\" target=\"_blank\" rel=\"noopener noreferrer\">Org Mode %s</a>"
"</div>")
emacs-version spacemacs-version org-version)
"</footer>"
(menu '("<a href=\"#preamble\">↑ Top ↑</a>"))
"</div>"))
(defun org-blog-sitemap-format-entry (entry _style project)
"Return string for each ENTRY in PROJECT."
(when (s-starts-with-p "posts/" entry)
(format (concat "@@html:<span class=\"archive-item\">"
"<span class=\"archive-date\">@@ %s: @@html:</span>@@"
" [[file:%s][%s]]"
" @@html:</span>@@")
(format-time-string "%Y-%m-%d" (org-publish-find-date entry project))
entry
(org-publish-find-title entry project))))
(defun org-blog-sitemap-function (title list)
"Return sitemap using TITLE and LIST returned by `org-blog-sitemap-format-entry'."
(concat "#+TITLE: " title "\n"
"#+AUTHOR: " author-name "\n"
"#+EMAIL: " author-email "\n"
"\n#+begin_archive\n"
(mapconcat (lambda (li)
(format "@@html:<li>@@ %s @@html:</li>@@" (car li)))
(seq-filter #'car (cdr list))
"\n")
"\n#+end_archive\n"))
(defun org-blog-publish-to-html (plist filename pub-dir)
"Same as `org-html-publish-to-html' but modifies html before finishing."
(let ((file-path (org-html-publish-to-html plist filename pub-dir)))
(with-current-buffer (find-file-noselect file-path)
(goto-char (point-min))
(search-forward "<body>")
(insert (mapconcat 'identity
'("<input type=\"radio\" id=\"light\" name=\"theme\"/>"
"<input type=\"radio\" id=\"dark\" name=\"theme\"/>"
"<input type=\"radio\" id=\"raw\" name=\"theme\"/>"
"<input type=\"radio\" id=\"modern\" name=\"theme\"/>"
"<input type=\"radio\" id=\"darkraw\" name=\"theme\"/>"
"<div id=\"labels\">"
"<div class=\"content\">"
"Change theme: "
"<label for=\"light\">Light</label>"
"(<label for=\"raw\">raw</label>,"
" <label for=\"modern\">modern</label>)"
" / "
"<label for=\"dark\">Dark</label>"
"(<label for=\"darkraw\">raw</label>)"
"</div>"
"</div>"
"<div class=\"main\">")
"\n"))
(goto-char (point-max))
(search-backward "</body>")
(insert "\n</div>\n")
(save-buffer)
(kill-buffer))
file-path))
(defun org-blog-publish-attachment (plist filename pub-dir)
"Publish a file with no transformation of any kind.
FILENAME is the filename of the Org file to be published. PLIST
is the property list for the given project. PUB-DIR is the
publishing directory.
Take care of minimizing the pictures using imagemagick.
Return output file name."
(unless (file-directory-p pub-dir)
(make-directory pub-dir t))
(or (equal (expand-file-name (file-name-directory filename))
(file-name-as-directory (expand-file-name pub-dir)))
(let ((dst-file (expand-file-name (file-name-nondirectory filename) pub-dir)))
(if (string-match-p ".*\\.\\(png\\|jpg\\|gif\\)$" filename)
(shell-command (format "~/.nix-profile/bin/convert %s -resize 800x800\\> +dither -colors 16 -depth 4 %s"
filename
dst-file))
(copy-file filename dst-file t)))))
(setq org-publish-project-alist
`(("orgfiles"
:base-directory ,base-dir
:exclude ".*drafts/.*"
:base-extension "org"
:publishing-directory ,publish-dir
:recursive t
:publishing-function org-blog-publish-to-html
:with-toc nil
:with-title nil
:with-date t
:section-numbers nil
:html-doctype "html5"
:html-html5-fancy t
:html-head-include-default-style nil
:html-head-include-scripts nil
:htmlized-source t
:html-head-extra ,org-blog-head
:html-preamble org-blog-preamble
:html-postamble org-blog-postamble
:auto-sitemap t
:sitemap-filename "archive.org"
:sitemap-title "Blog Posts"
:sitemap-style list
:sitemap-sort-files anti-chronologically
:sitemap-format-entry org-blog-sitemap-format-entry
:sitemap-function org-blog-sitemap-function)
("assets"
:base-directory ,assets-dir
:base-extension ".*"
:exclude ".*\.org$"
:publishing-directory ,publish-assets-dir
:publishing-function org-blog-publish-attachment
:recursive t)
("rss"
:base-directory ,rss-dir
:base-extension "org"
:html-link-home ,domainname
:html-link-use-abs-url t
:rss-extension "xml"
:publishing-directory ,publish-rss-dir
:publishing-function (org-rss-publish-to-rss)
:exclude ".*"
:include ("archive.org")
:section-numbers nil
:table-of-contents nil)
("blog" :components ("orgfiles" "assets" "rss"))))
;; add target=_blank and rel="noopener noreferrer" to all links by default
(defun my-org-export-add-target-blank-to-http-links (text backend info)
"Add target=\"_blank\" to external links."
(when (and
(org-export-derived-backend-p backend 'html)
(string-match "href=\"http[^\"]+" text)
(not (string-match "target=\"" text))
(not (string-match (concat "href=\"" domainname "[^\"]*") text)))
(string-match "<a " text)
(replace-match "<a target=\"_blank\" rel=\"noopener noreferrer\" " nil nil text)))
(add-to-list 'org-export-filter-link-functions
'my-org-export-add-target-blank-to-http-links)
(provide 'her-esy-fun-publish)

BIN
project.el.sig Normal file

Binary file not shown.

View File

@ -41,6 +41,9 @@ figure {
figure {
margin: 20px 0px;
}
figure > img {
margin: 0px;
}
li {
position: relative;
display: block;

View File

@ -24,12 +24,12 @@ priority:
3. *disability friendly*; should be easy to read on a text browser so people
with disabilities could easily consume it
4. *nerdy*; should feel mostly like markdown text in a terminal and source code
should be syntax higlighted.
should be syntax highlighted.
5. *user friendly*; support your preferred light/dark theme by default but you
can change it if you want.
6. *rss*; you should be able to get informed when I add a new blog post.
7. *frugal*; try to minize the resources needed to visit my website; no
javascript, no webfont, not too much CSS magic, not much images or really
7. *frugal*; try to minimize the resources needed to visit my website; no
javascript, no web-font, not too much CSS magic, not much images or really
compressed one.
I talk about more details [[file:./posts/new-blog.org][here]].

View File

@ -1,71 +0,0 @@
;;; auto-load-project.el --- Auto load elisp file on project open
;; Copyright © 2019 Yann Esposito <yann@esposito.host>
;;; Commentary:
;;
;;; Code:
(provide :auto-load-project)
(require 'projectile)
(defvar y/trusted-gpg-key-fingerprints
'("448E9FEF4F5B86DE79C1669B0000000000000000")
"The list of GPG fingerprint you trust when decrypting a gpg file.
You can retrieve the fingerprints of your own private keys
with: `gpg --list-secret-keys' (take care of removing the
spaces when copy/pasting here)")
(defun y/get-encryption-key (file)
"Return the fingerprint of they key that encrypted FILE."
(string-trim-right
(shell-command-to-string
(concat
"gpg --status-fd 1 --decrypt -o /dev/null " file " 2>/dev/null"
"|grep DECRYPTION_KEY"
"|awk '{print $4}'"))))
(defun y/trusted-gpg-origin-p (file)
"Return non-nil if the FILE is encrypted with a trusted key."
(member (y/get-encryption-key file) y/trusted-gpg-key-fingerprints))
(defconst y/project-file ".project.el.gpg"
"Project configuration file name.")
(defun y/init-project-el-auto-load ()
"Initialize the autoload of .project.el.gpg for projects."
(with-eval-after-load 'projectile
(defvar y/loaded-projects (list)
"Projects that have been loaded by `y/load-project-file'.")
(defun y/load-project-file ()
"Loads the `y/project-file' for a project. This is run once
after the project is loaded signifying project setup."
(interactive)
(when (projectile-project-p)
(lexical-let* ((current-project-root (projectile-project-root))
(project-init-file (expand-file-name y/project-file current-project-root)))
(when (and (not (member current-project-root y/loaded-projects))
(file-exists-p project-init-file)
(y/trusted-gpg-origin-p project-init-file))
(message "Loading project init file for %s" (projectile-project-name))
(condition-case ex
(progn (load project-init-file)
(add-to-list 'y/loaded-projects current-project-root)
(message "%s loaded successfully" project-init-file))
('error
(message
"There was an error loading %s: %s"
project-init-file
(error-message-string ex))))))))
(add-hook 'find-file-hook #'y/load-project-file t)
(add-hook 'dired-mode-hook #'y/load-project-file t)))
(provide 'auto-load-project)
;;; auto-load-project.el ends here

View File

@ -26,38 +26,38 @@ centralised place in your =~/.emacs.d/.init.el= file.
The main principle is quite simple.
1. When finding a new file in a project, check for the presence of a
=.project.el.gpg= file.
2. Check the file was encrypted with by a trusted fingerprint
3. load the file
1. When opening a new file in a project, check for the presence of a
=project.el= and =project.el.sig= file at the root directory of the
project.
2. Check the file was signed with by a trusted fingerprint.
3. Load the file.
I found a solution that asked you each time you enter in a project if you trust
it.
Other solutions I found on the internet asked you each time you enter in a
project if you trust the file or not.
This was both quite annoying and insecure as it is kind of easy to type 'y'
instead of 'n' and to load an untrusted 3rd party script.
instead of 'n' and to load 3rd party script.
With emacs epa, opening encrypted gpg files is seamless.
But here we not only want to open the gpg file but also check we trust the
person that did it.
Note that checking who's encrypted a gpg file is not straightforward:
Note that checking who signed a file with an external signature is not as
straightforward as it should be:
#+begin_src elisp
(defun y/get-encryption-key (file)
"given a gpg encrypted file, returns the fingerprint of they
key that encrypted it"
(defun auto-load-project/get-sign-key (file)
"Return the fingerprint of they key that signed FILE.
To sign a file you should used
`gpg --local-user my@email --output project.el.sig --detach-sign project.el`"
(string-trim-right
(shell-command-to-string
(concat
"gpg --status-fd 1 --decrypt -o /dev/null " file " 2>/dev/null"
"|grep DECRYPTION_KEY"
"|awk '{print $4}'"))))
(format "gpg --status-fd 1 --verify %s.sig %s 2>/dev/null " file file)
"|grep VALIDSIG"
"|awk '{print $3}'"))))
#+end_src
- The `--status-fd` should provide more script friendly output.
GPG provide localized output by default which are therefore hard to use in
script (for grep for example).
- we do not want to decrypt to a file so we redirect the output to =/dev/null=
We use =projectile= to detect the project-root and when we are in a new project.
Unfortunately the =projectile-after-switch-project-hooks= doesn't work as I
@ -67,69 +67,38 @@ file.
In order not to load the code each time, I need to keep a local state of project
already loaded.
So now, each time I modify the =project.el= I sign it with the following
command line:
#+begin_src bash
gpg --local-user my@email --output project.el.sig --detach-sign project.el
#+end_src
* Solution
:PROPERTIES:
:CUSTOM_ID: solution
:END:
Dowload the code [[file:auto-load-project.el][auto-load-project.el]]
The project is hosted here: https://gitlab.esy.fun/yogsototh/auto-load-project-el
You can setup the emacs package in spacemacs with:
#+begin_src elisp
(defun y/init-project-el-auto-load ()
"Initialize the autoload of .project.el.gpg for projects"
(with-eval-after-load 'projectile
(defvar y/trusted-gpg-key-fingerprints
'("448E9FEF4F5B86DE79C1669B0000000000000000")
"The list of GPG fingerprint you trust when decrypting a gpg file.
You can retrieve the fingerprints of your own private keys
with: `gpg --list-secret-keys' (take care of removing the
spaces when copy/pasting here)")
(defun y/get-encryption-key (file)
"given a gpg encrypted file, returns the fingerprint of
they key that encrypted it"
(string-trim-right
(shell-command-to-string
(concat
"gpg --status-fd 1 --decrypt -o /dev/null " file " 2>/dev/null"
"|grep DECRYPTION_KEY"
"|awk '{print $4}'"))))
(defun y/trusted-gpg-origin-p (file)
"Returns true if the file is encrypted with a trusted key"
(member (y/get-encryption-key file) y/trusted-gpg-key-fingerprints))
(defconst y/project-file ".project.el.gpg"
"Project configuration file name.")
(defvar y/loaded-projects (list)
"Projects that have been loaded by `y/load-project-file'.")
(defun y/load-project-file ()
"Loads the `y/project-file' for a project. This is run once
after the project is loaded signifying project setup."
(interactive)
(when (projectile-project-p)
(lexical-let* ((current-project-root (projectile-project-root))
(project-init-file (expand-file-name y/project-file current-project-root)))
(when (and (not (member current-project-root y/loaded-projects))
(file-exists-p project-init-file)
(y/trusted-gpg-origin-p project-init-file))
(message "Loading project init file for %s" (projectile-project-name))
(condition-case ex
(progn (load project-init-file)
(add-to-list 'y/loaded-projects current-project-root)
(message "%s loaded successfully" project-init-file))
('error
(message
"There was an error loading %s: %s"
project-init-file
(error-message-string ex))))))))
(add-hook 'find-file-hook #'y/load-project-file t)
(add-hook 'dired-mode-hook #'y/load-project-file t)))
(y/init-project-el-auto-load)
;; ...
dotspacemacs-additional-packages
'((auto-load-project :location
(recipe
:fetcher git
:url "https://gitlab.esy.fun/yogsototh/auto-load-project-el"
:files ("auto-load-project.el"))))
;; ...
(defun dotspacemacs/user-config ()
;; ...
(require 'auto-load-project)
(setq auto-load-project/trusted-gpg-key-fingerprints
'("0000000000000000000000000000000000000000" ;; figerprint of trusted key 1
"1111111111111111111111111111111111111111"
"2222222222222222222222222222222222222222"
)))
;; ...
#+end_src

View File

@ -11,30 +11,31 @@
#+STARTUP: showeverything
#+begin_notes
I watched what might be the most failed movie of all time and I still
enjoyed greatly the show.
I watched what may be the worse movie of all time and I still enjoyed
greatly the show.
#+end_notes
I recently wanted to watch again an horror teen movie I watched when I was
a young boy: Troll.
But I discovered there were a movie called Troll 2.
I guessed that it must be the following one.
I wanted to watch an horror teen movie I saw when I was a kid; Troll.
During my searches, I discovered there is another movie named "Troll 2".
I thought that it is certainly the sequel.
Oh god, I saw the imdb note was, really, really bad.
But who cares, I watched it.
And here is a resume of my experience:
I took a look to the imdb note, and it was very bad (2.9 / 10).
But, hey, I decided to watch it.
Here is an overview of my watching experience.
* The watching
:PROPERTIES:
:CUSTOM_ID: the-watching
:END:
Troll 2 synopsis is a family that goes camping to Nilbog.
The son of the family see his dead grandfather that warn him against
Goblins that make you eat things that transform you and then they eat you.
Of course nobody listen to the warn of the boy, the family goes to Nilbog
which is of course infested by Goblins.
The synopsis of Troll 2 is a family living in a city that exchange its
house to go to the village of Nilbog "to live like farmers"...
The son of the family see his dead grandfather.
The grandpa warn his grandson against Goblins.
Those creatures make you eat green things that transform you and then they
eat you.
Of course, nobody listen to the boy that does not want to go to Nilbog,
which is infested by goblins that can take Human appearance.
Troll 2 is bad on most criterion you use to measure the quality of a movie.
@ -51,7 +52,7 @@ So much you can only find them not terrorizing but funny and ridiculous.
Soon after that, you realize the acting of all actors is extremely bad.
In fact, it is so bad, you might not believe me how bad it is.
To give you an idea, the only equal bad acting I ever witnessed was while
looking at amateurs first Youtube movie trying to follow a scenario.
looking at amateurs first Youtube movies trying to follow a scenario.
Apparently all actor were amateurs, it was their first and last movie.
#+CAPTION: One particularly terrible acting scene
@ -66,7 +67,7 @@ dialog.
The scenario is terrible.
For example, most of the thing occurring suffer from terrible plot holes or
terrible mistakes that make everything hard to believe even if you accept
the premises of a world were Goblin would exists.
the premises of a world were Goblins would exist.
For example, the grandfather ghost can stop time for 30 seconds for no
reason at all.
@ -78,7 +79,8 @@ I forgot to give a word about the music.
It is like the director choose the worst music to go along each scene.
The first ending is really, quite surprising.
They beat the monsters with, what I believe was a failed attempt at humor.
They win against the monsters with, what I believe was a failed attempt at
humor.
It misses the point so bad, that the irony still make it funny.
#+CAPTION: Our hero save the day by urinating on the table. His family is frozen for 30s said grandpa, they were for 70s.
@ -91,8 +93,7 @@ To let you close the experience in awe.
But there is a bonus, the cherry on the cake.
During all the movie, it is *never* question of a Troll at all.
The monsters are all Goblin, not Troll.
I think the word "troll" is not pronounced once in the movie.
*There is not Troll in Troll 2*.
Still, it was quite entertaining.
@ -104,10 +105,12 @@ Still, it was quite entertaining.
:END:
What is really interesting though is that I really enjoyed the watch.
It was so bad, that in the end, you do not really enter in the movie, but
really just analyze the movie, and you start to see how much the movie is bad.
And this is so fun.
For example, there are some attempts at humor, but almost all of them fail terribly.
It was so bad, that can't enter in the movie.
You analyze the movie, and see all mistakes and notice everything wrong
about it.
And this is really fun.
For example, there are some attempts at humor, but almost all of them fail
terribly.
And so, you can laugh at how much it failed.
Once going to imdb, I discovered I wasn't alone in loving that terrible
@ -126,13 +129,16 @@ None of them spoke English either.
#+begin_quote
“The cast had few experienced actors, and was primarily assembled from
residents of nearby towns who responded to an open casting call. George
Hardy was a dentist with no acting experience who showed up for fun, hoping
to be cast as an extra, only to be given one of the films largest speaking
roles. Don Packard, who played the store owner, was actually a resident at
a nearby mental hospital, and was cast for—and filmed—his role while on a
day trip; after recovering and being released from the hospital, he
recalled that he had no idea what was happening around him, and that his
disturbed “performance” in the film was not acting”
residents of nearby towns who responded to an open casting call.
George Hardy was a dentist with no acting experience who showed up for fun,
hoping to be cast as an extra, only to be given one of the films largest
speaking roles.
Don Packard, who played the store owner, was actually a resident at a
nearby mental hospital, and was cast for—and filmed—his role while on a day
trip; after recovering and being released from the hospital, he recalled
that he had no idea what was happening around him, and that his disturbed
“performance” in the film was not acting”.
#+end_quote