about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore9
-rw-r--r--config.toml63
-rw-r--r--content/_index.md7
-rw-r--r--content/post/a-new-site.md14
-rw-r--r--content/post/back-again.md14
-rw-r--r--content/post/cedit-and-paredit.md44
-rw-r--r--content/post/emacs-package-archive-statistics.md169
-rw-r--r--content/post/git-cloning-similar-repositories.md19
-rw-r--r--content/post/opening-projects-with-projectile.md80
-rw-r--r--content/post/repository-management-with-ghq.md75
-rw-r--r--content/post/self-hosted-git.md146
-rw-r--r--layouts/index.html37
-rw-r--r--layouts/partials/hook_head_end.html2
-rw-r--r--netlify.toml77
-rw-r--r--static/.well-known/keybase.txt56
-rw-r--r--static/css/syntax.css81
-rw-r--r--static/img/me-large.jpgbin0 -> 185975 bytes
-rw-r--r--static/img/me-thumb.jpgbin0 -> 13898 bytes
-rw-r--r--static/keybase.txt55
-rw-r--r--static/public_key.asc16
-rw-r--r--static/robots.txt7
-rw-r--r--static/talks/fp-js/index.html431
-rw-r--r--static/talks/fp-js/index.org226
-rw-r--r--static/talks/fp-js/s5-blank.html50
-rw-r--r--static/talks/fp-js/ui/default/blank.gifbin0 -> 49 bytes
-rwxr-xr-xstatic/talks/fp-js/ui/default/bodybg.gifbin0 -> 10119 bytes
-rw-r--r--static/talks/fp-js/ui/default/framing.css23
-rw-r--r--static/talks/fp-js/ui/default/iepngfix.htc42
-rw-r--r--static/talks/fp-js/ui/default/opera.css7
-rw-r--r--static/talks/fp-js/ui/default/outline.css15
-rw-r--r--static/talks/fp-js/ui/default/pretty.css86
-rw-r--r--static/talks/fp-js/ui/default/print.css1
-rw-r--r--static/talks/fp-js/ui/default/s5-core.css9
-rw-r--r--static/talks/fp-js/ui/default/slides.css3
-rw-r--r--static/talks/fp-js/ui/default/slides.js553
-rw-r--r--themes/xmin/.gitignore5
-rw-r--r--themes/xmin/LICENSE.md (renamed from LICENSE.md)0
-rw-r--r--themes/xmin/README.md (renamed from README.md)0
-rw-r--r--themes/xmin/archetypes/default.md (renamed from archetypes/default.md)0
-rw-r--r--themes/xmin/exampleSite/config.toml (renamed from exampleSite/config.toml)0
-rw-r--r--themes/xmin/exampleSite/content/_index.Rmarkdown (renamed from exampleSite/content/_index.Rmarkdown)0
-rw-r--r--themes/xmin/exampleSite/content/_index.markdown (renamed from exampleSite/content/_index.markdown)0
-rw-r--r--themes/xmin/exampleSite/content/about.md (renamed from exampleSite/content/about.md)0
-rw-r--r--themes/xmin/exampleSite/content/note/2017-06-13-a-quick-note.md (renamed from exampleSite/content/note/2017-06-13-a-quick-note.md)0
-rw-r--r--themes/xmin/exampleSite/content/note/2017-06-14-another-note.md (renamed from exampleSite/content/note/2017-06-14-another-note.md)0
-rw-r--r--themes/xmin/exampleSite/content/post/2015-07-23-lorem-ipsum.md (renamed from exampleSite/content/post/2015-07-23-lorem-ipsum.md)0
-rw-r--r--themes/xmin/exampleSite/content/post/2016-02-14-hello-markdown.md (renamed from exampleSite/content/post/2016-02-14-hello-markdown.md)0
-rw-r--r--themes/xmin/exampleSite/layouts/partials/foot_custom.html (renamed from exampleSite/layouts/partials/foot_custom.html)0
-rw-r--r--themes/xmin/hugo-xmin.Rproj (renamed from hugo-xmin.Rproj)0
-rw-r--r--themes/xmin/images/screenshot.png (renamed from images/screenshot.png)bin37151 -> 37151 bytes
-rw-r--r--themes/xmin/images/tn.png (renamed from images/tn.png)bin21823 -> 21823 bytes
-rw-r--r--themes/xmin/layouts/404.html (renamed from layouts/404.html)0
-rw-r--r--themes/xmin/layouts/_default/list.html (renamed from layouts/_default/list.html)0
-rw-r--r--themes/xmin/layouts/_default/single.html (renamed from layouts/_default/single.html)0
-rw-r--r--themes/xmin/layouts/_default/terms.html (renamed from layouts/_default/terms.html)0
-rw-r--r--themes/xmin/layouts/partials/foot_custom.html (renamed from layouts/partials/foot_custom.html)0
-rw-r--r--themes/xmin/layouts/partials/footer.html (renamed from layouts/partials/footer.html)0
-rw-r--r--themes/xmin/layouts/partials/head_custom.html (renamed from layouts/partials/head_custom.html)0
-rw-r--r--themes/xmin/layouts/partials/header.html (renamed from layouts/partials/header.html)0
-rw-r--r--themes/xmin/static/css/fonts.css (renamed from static/css/fonts.css)0
-rw-r--r--themes/xmin/static/css/style.css (renamed from static/css/style.css)0
-rw-r--r--themes/xmin/theme.toml (renamed from theme.toml)0
62 files changed, 2417 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore index ce130a0..a959665 100644 --- a/.gitignore +++ b/.gitignore
@@ -1,5 +1,4 @@
1.Rproj.user 1/public/
2.Rhistory 2
3.RData 3# Local Netlify folder
4.Ruserdata 4.netlify \ No newline at end of file
5exampleSite/public
diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..de9e6a5 --- /dev/null +++ b/config.toml
@@ -0,0 +1,63 @@
1languageCode = "en-GB"
2baseurl = "https://www.alanpearce.eu"
3title = "Alan Pearce"
4theme = "xmin"
5pygmentsUseClasses = true
6pygmentsCodeFences = true
7
8[permalinks]
9 post = "/post/:slug"
10
11[Params]
12Description = "Developer, Emacser"
13footer = "Licensed under a <a rel=\"license\" href=\"http://creativecommons.org/licenses/by/4.0/\">Creative Commons Attribution 4.0 International License</a>."
14
15[Params.GPG]
16fingerprint = "48E6 576C 0707 388C B8BE FD0C CD4B EB92 A8D4 6583"
17url = "/public_key.asc"
18
19[author]
20 name = "Alan Pearce"
21 image = "/img/me-thumb.jpg"
22
23[[menu.main]]
24 name = "Home"
25 url = "/"
26 weight = 1
27[[menu.main]]
28 name = "Posts"
29 URL = "/post/"
30[[menu.main]]
31 name = "Repositories"
32 URL = "https://git.alanpearce.eu"
33
34[[menu.contact]]
35 name = "alan@alanpearce.eu"
36 URL = "mailto:alan@alanpearce.eu"
37 weight = 1
38[[menu.contact]]
39 name = "GitLab"
40 URL = "https://gitlab.com/alanpearce"
41[[menu.contact]]
42 name = "GitHub"
43 URL = "https://github.com/alanpearce"
44[[menu.contact]]
45 name = "StackOverflow Jobs"
46 url = "http://stackoverflow.com/users/story/381895"
47[[menu.contact]]
48 name = "Keybase"
49 url = "https://keybase.io/alanpearce"
50
51[privacy]
52 [privacy.disqus]
53 disable = true
54 [privacy.googleAnalytics]
55 disable = true
56 [privacy.instagram]
57 disable = true
58 [privacy.twitter]
59 disable = true
60 [privacy.vimeo]
61 disable = true
62 [privacy.youtube]
63 disable = true
diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..2090377 --- /dev/null +++ b/content/_index.md
@@ -0,0 +1,7 @@
1---
2title: "Home"
3---
4## Hello
5Hi. My name is Alan, I live in Berlin, where I work as a
6Full-stack Developer. I mostly write about Emacs and
7development-related topics.
diff --git a/content/post/a-new-site.md b/content/post/a-new-site.md new file mode 100644 index 0000000..aa2efbf --- /dev/null +++ b/content/post/a-new-site.md
@@ -0,0 +1,14 @@
1+++
2Categories = ["Geek"]
3Description = "I made a website."
4Title = "A New Site"
5Date = 2014-06-07T20:16:16Z
6+++
7
8I finally got around to making a website. I decided to use [Hugo][] with a slightly-modified [Hyde theme][]
9
10Someday I'll make my own theme, probably using [Stylus][] for CSS processing. But for now, this will do. The more important thing is just to create some content.
11
12[Hugo]: http://hugo.spf13.com/
13[Hyde theme]: https://github.com/spf13/hyde
14[Stylus]: http://learnboost.github.io/stylus/
diff --git a/content/post/back-again.md b/content/post/back-again.md new file mode 100644 index 0000000..7107a56 --- /dev/null +++ b/content/post/back-again.md
@@ -0,0 +1,14 @@
1+++
2Description = "I'm back"
3Tags = ["website"]
4date = "2017-05-06T16:55:57+02:00"
5title = "Back again"
6+++
7
8I've not made any posts for quite some time. My life has changed
9quite a bit, I've emigrated from the UK and it's only now that I'm
10starting to feel more settled.
11
12I hope to start posting a bit more often. Hopefully this post,
13despite being light on content, will help me to get back into the
14sharing mindset.
diff --git a/content/post/cedit-and-paredit.md b/content/post/cedit-and-paredit.md new file mode 100644 index 0000000..23fb1c4 --- /dev/null +++ b/content/post/cedit-and-paredit.md
@@ -0,0 +1,44 @@
1+++
2Categories = ["Emacs"]
3Description = "Cedit and paredit for structural editing"
4Tags = ["development", "emacs"]
5title = "Cedit and Paredit"
6date = 2014-08-04T07:10:14Z
7+++
8
9I recently discovered [cedit][], which provides some structural
10commands for editing c-like languages. (See this
11[Emacs Rocks! episode][e14] if you're not familiar with the concept:
12it introduces [paredit][], a structural editing mode for lisps).
13
14So, it deals with curly braces and semicolons, keeping things balanced
15and correct as show in its [screencast][cedit-readme]. It mentions that it
16integrates with [paredit][] rather than duplicating *all* its
17functionality. After setting up cedit, I decided to try enabling
18paredit alongside cedit and disabling autopair. Once I did,
19however, I noticed an annoying formatting issue: If I were to type
20`foo` and then `(`, paredit would format this as `foo ()`, which makes
21sense, considering that paredit is written for lisps — s-expressions
22are usually separated by spaces — but not so much for c-like languages.
23
24I was thinking about disabling paredit and going back to autopair,
25when I decided to look through the configuration variables for
26paredit. Turns out it provides
27`paredit-space-for-delimiter-predicates`, which is a list of functions
28that control whether a space should be inserted. So, solving the
29formatting issue turned out to be pretty simple:
30
31```elisp
32(defun ap/cedit-space-delimiter-p (endp delimiter)
33"Don't insert a space before delimiters in c-style modes"
34(not cedit-mode))
35(add-to-list 'paredit-space-for-delimiter-predicates #'ap/cedit-space-delimiter-p)
36```
37
38Hopefully that saves someone some time if they try to use the two
39together.
40
41[cedit]: https://github.com/zk-phi/cedit
42[cedit-readme]: https://github.com/zk-phi/cedit#readme
43[e14]: http://emacsrocks.com/e14.html
44[paredit]: http://www.emacswiki.org/emacs/ParEdit
diff --git a/content/post/emacs-package-archive-statistics.md b/content/post/emacs-package-archive-statistics.md new file mode 100644 index 0000000..2275c9a --- /dev/null +++ b/content/post/emacs-package-archive-statistics.md
@@ -0,0 +1,169 @@
1+++
2Categories = ["Emacs"]
3Description = "Working out which package archives I'm using"
4Tags = ["emacs"]
5title = "Emacs Package Archive Statistics"
6date = 2014-07-19T13:19:54Z
7+++
8
9I use [cask][] for managing the dependencies of my Emacs
10configuration. Whenever I opened my `Cask` file, I wondered if I
11really was using all the sources I had defined:
12
13```elisp
14(source gnu)
15(source marmalade)
16(source melpa)
17(source melpa-stable)
18(source org)
19```
20
21It seemed quite strange that we have so many package repositories in
22the Emacs world and I'm not even using all of them. I find this state
23less than ideal, much as
24[Jorgen Schäfer details][state of emacs package archives]. My ideal
25package repository would be once that works with VCS releases, mostly
26because it's a much simpler process to work with than having to sign
27up to yet another website just to upload a package, then ensure it's
28kept up-to-date on every release.
29
30As such, I prefer the concepts behing [MELPA][] and [MELPA Stable][] to
31those of [Marmalade][]. [GNU ELPA][] doesn't appear to allow any
32submissions and [org][org archive] is specific to [org-mode]. I've
33also noticed that many packages I find and use are on github and so
34work with the [MELPA][] system. However, I don't like [MELPA's][MELPA]
35versioning: it just gets the latest code and puts the build date in
36the version, meaning that packages could break at any time.
37
38So, ideally I would use [MELPA Stable][] as much as possible and reduce my
39usage of [Marmalade][] and [MELPA][]. [GNU ELPA][] doesn't appear to have
40many packages, but I wasn't sure if I was using any.
41I couldn't see the information listed in the `*Packages*` buffer, so I
42decided to try to figure out how to generate some usage statistics.
43
44I found [how to get a list of installed packages][], but that just gives
45a list:
46
47```elisp
48(ace-jump-mode ag auto-compile auto-indent-mode autopair ...)
49```
50
51I needed to get more information about those packages. I looked at
52where `list-packages` gets that information from. It seems that
53`package-archive-contents` is a list of cons cells:
54
55```elisp
56(org-plus-contrib .
57 [(20140714)
58 nil "Outline-based notes management and organizer" tar "org"])
59```
60
61Then created a function to loop over the contents of
62`package-activated-list`, retrieving the corresponding contents of
63`package-archive-contents`:
64
65```elisp
66(defun package-list-installed ()
67 (loop for pkg in package-activated-list
68 collect (assq pkg package-archive-contents)))
69```
70
71This generates a list of arrays from `package-archive-contents`.
72There are some helper functions in package.el such as
73`package-desc-kind`. `package-desc-archive` was exactly what I
74needed. I happened to be using a pretest version of Emacs at the time
75and didn't know that it's not in 24.3, so I just made sure it was defined:
76
77```elisp
78(if (not (fboundp #'package-desc-archive))
79 (defsubst package-desc-archive (desc)
80 (aref desc (1- (length desc)))))
81```
82
83Weirdly, some of the arrays (seemingly the ones from the
84[org archive][]) had a different length, but the repository/archive was
85always the last element, which is why I used `(1- (length ))` and not
86a constant, like the other `package-desc-*` functions.
87
88To generate a list of statistics, I just needed to loop over the
89installed packages from `package-list-installed` and update a count
90for each archive:
91
92```elisp
93(defun package-archive-stats ()
94 (let ((archives (makehash))
95 (assoc '()))
96 (dolist (arc package-archives)
97 (puthash (car arc) 0 archives))
98 (maphash (lambda (k v)
99 (setq assoc (cons (cons k v) assoc)))
100 (dolist (pkg (-filter #'identity (package-list-installed)) archives)
101 (let ((pkg-arc (package-desc-archive (cdr pkg))))
102 (incf (gethash pkg-arc archives)))))
103 assoc))
104```
105
106Running this gives a list of cons cells:
107
108```elisp
109(("gnu" . 0)
110 ("org" . 1)
111 ("melpa-stable" . 2)
112 ("melpa" . 106)
113 ("marmalade" . 1))
114```
115
116I wrapped it in an interactive function so that I could check the
117numbers quickly:
118
119```elisp
120(defun package-show-archive-stats ()
121 (interactive)
122 (message "%s" (package-archive-stats)))
123```
124
125With that, I removed `(source gnu)` from my `Cask` file. Now I had
126another question. What package was installed from [marmalade][]? In
127the lisp fashion, I created yet another function:
128
129```elisp
130(defun package-show-installed-from-archive (archive)
131 (interactive (list (helm-comp-read "Archive: " (mapcar #'car package-archives)
132 :must-match t)))
133 (let ((from-arc (mapcar #'car
134 (--filter (equalp (package-desc-archive (cdr it)) archive)
135 (package-list-installed)))))
136 (if (called-interactively-p)
137 (message "%s" from-arc)
138 from-arc)))
139```
140(Non-helm users can replace `helm-comp-read` with
141`ido-completing-read` or similar)
142
143Running this with the argument `"marmalade"` gives:
144
145```elisp
146(php-extras)
147```
148
149I checked on [MELPA Stable][] and [MELPA][], but it's not available
150there. Given that I use [php-extras][] quite a bit at work, I can't remove
151[marmalade][] just yet. However, as it's a git repository, it should be
152easy for me to create a recipe for MELPA. Then I can remove marmalade
153from my [cask][] configuration. Hooray for simplification!
154
155Hopefully, packaging in Emacs will become simpler in the future.
156There are some interesting things in 24.4 like pinning packages to a
157repository, which would allow [MELPA Stable][] to be used even when
158[MELPA][] defines the same package with a higher "version".
159
160[cask]: https://github.com/cask/cask/
161[state of emacs package archives]: http://blog.jorgenschaefer.de/2014/06/the-sorry-state-of-emacs-lisp-package.html
162[marmalade]: http://marmalade-repo.org/
163[GNU ELPA]: http://elpa.gnu.org/packages/
164[MELPA]: http://hiddencameras.milkbox.net/
165[MELPA Stable]: http://melpa-stable.milkbox.net/
166[org archive]: http://orgmode.org/elpa.html
167[how to get a list of installed packages]: http://stackoverflow.com/questions/13866848/how-to-save-a-list-of-all-the-installed-packages-in-emacs-24
168[php-extras]: https://github.com/arnested/php-extras
169[org-mode]: http://orgmode.org/
diff --git a/content/post/git-cloning-similar-repositories.md b/content/post/git-cloning-similar-repositories.md new file mode 100644 index 0000000..96ea7b4 --- /dev/null +++ b/content/post/git-cloning-similar-repositories.md
@@ -0,0 +1,19 @@
1+++
2Categories = ["Development"]
3Description = "Speed up cloning of similar git repositories"
4Tags = ["git"]
5title = "Cloning Similar Git Repositories"
6date = 2014-06-22T08:35:24Z
7+++
8With multiple similar git repositories, for example where a base repository contains a framework or base system installation and other repositories are created from that repository, it's possible to save some time when cloning down another repository by using the `reference` option to [git-clone][]:
9
10 git clone git@github.com/my/repo --reference another-repo
11(Where `another-repo` points to a local version of a repository.)
12
13The reference here doesn't have to be the base repository itself, it could just be another variant of it. The speedup can be quite dramatic if the repositories have megabytes of shared history, from minutes to seconds.
14
15On a related note, I'm surprised that [GitHub][] doesn't allow for multiple renamed forks, which would be very useful in this scenario. [BitBucket][] does support this, however. It even has a 'sync' button for pulling updates from the base into the child repositories, which is very useful, especially for those who prefer GUIs over CLIs.
16
17[git-clone]:https://www.kernel.org/pub/software/scm/git/docs/git-clone.html
18[GitHub]:https://github.com
19[BitBucket]:https://bitbucket.org/ \ No newline at end of file
diff --git a/content/post/opening-projects-with-projectile.md b/content/post/opening-projects-with-projectile.md new file mode 100644 index 0000000..d88d309 --- /dev/null +++ b/content/post/opening-projects-with-projectile.md
@@ -0,0 +1,80 @@
1+++
2Categories = ["Emacs"]
3Description = ""
4Tags = ["emacs", "lisp"]
5title = "Opening Projects with Projectile"
6date = 2014-07-12T09:12:34Z
7+++
8
9I use [Projectile][] for working with projects in Emacs. It's really good at finding files in projects, working with source code indexes (I use [Global][]), and with its [perspective][] support, it's also great at separating projects into workspaces. However, I've always felt it lacking in actually opening projects. I tend to work on different projects all the time and `projectile-switch-project` only tracks projects once they've been opened initially (despite the name, it works across Emacs sessions).
10
11With this in mind, I decided to try to add support for opening projects under a given subdirectory, e.g. `~/projects`, regardless of whether or not I've visited them before.
12
13I saw that projectile uses [Dash.el][] in some places, and after reading about [anaphoric macros], I decided that I'd try to use them to aid me.
14
15```elisp
16(defun ap/subfolder-projects (dir)
17 (--map (file-relative-name it dir)
18 (-filter (lambda (subdir)
19 (--reduce-from (or acc (funcall it subdir)) nil
20 projectile-project-root-files-functions))
21 (-filter #'file-directory-p (directory-files dir t "\\<")))))
22```
23
24First, this filters the non-special files under `dir`, filtering non-directories. Then it runs the list of `projectile-project-root-files-functions` on it to determine if it looks like a projectile project. To make the list more readable, it makes the filenames relative to the passed-in directory. It runs like this:
25
26```elisp
27(ap/subfolder-projects "~/projects") =>
28("dotfiles" "ggtags" …)
29```
30
31So, we've got ourselves a list, but now we need to be able to open the project that's there, even though the folders are relative.
32
33```elisp
34(defun ap/open-subfolder-project (from-dir &optional arg)
35 (let ((project-dir (projectile-completing-read "Open project: "
36 (ap/subfolder-projects from-dir))))
37 (projectile-switch-project-by-name (expand-file-name project-dir from-dir) arg)))
38```
39
40By wrapping the call to `ap/subfolder-projects` in another function that takes the same directory argument, we can re-use the project parent directory and expand the selected project name into an absolute path before passing it to `projectile-switch-project-by-name`.
41
42We get support for multiple completion systems for free, since projectile has a wrapper function that works with the default system, ido, [grizzl][] and recently, [helm][].
43
44Then I defined some helper functions to make it easy to open work and home projects.
45
46```elisp
47(defvar work-project-directory "~/work")
48(defvar home-project-directory "~/projects")
49
50(defun ap/open-work-project (&optional arg)
51 (interactive "P")
52 (ap/open-subfolder-project work-project-directory arg))
53
54(defun ap/open-home-project (&optional arg)
55 (interactive "P")
56 (ap/open-subfolder-project home-project-directory arg))
57```
58
59I could probably simplify this with a macro, but I'm not sure that there's much advantage in it. I only have two project types right now, after all.
60
61With this all set up, whenever I want to start working on a project I just type `M-x home RET` to call up the list.
62
63I also considered trying to add all the projects under a directory to the projectile known project list. I didn't find it quite as easy to use, but it's available below if anyone would prefer that style.
64
65```elisp
66(defun ap/-add-known-subfolder-projects (dir)
67 (-map #'projectile-add-known-project (--map (concat (file-name-as-directory dir) it) (ap/subfolder-projects dir))))
68
69(defun ap/add-known-subfolder-projects ()
70 (interactive)
71 (ap/-add-known-subfolder-projects (ido-read-directory-name "Add projects under: ")))
72```
73
74[Projectile]: https://github.com/bbatsov/projectile
75[Dash.el]: https://github.com/magnars/dash.el
76[Helm]: https://github.com/emacs-helm/helm
77[Global]: https://www.gnu.org/software/global/
78[Anaphoric macros]: https://en.wikipedia.org/wiki/Anaphoric_macro
79[Perspective]: https://github.com/nex3/perspective-el
80[Grizzl]: https://github.com/d11wtq/grizzl
diff --git a/content/post/repository-management-with-ghq.md b/content/post/repository-management-with-ghq.md new file mode 100644 index 0000000..d831c9e --- /dev/null +++ b/content/post/repository-management-with-ghq.md
@@ -0,0 +1,75 @@
1+++
2Tags = ["development","git"]
3date = "2017-05-06T23:31:51+02:00"
4title = "Repository management with ghq"
5+++
6
7I recently encountered [ghq][], a tool for automatically organising VCS-backed
8projects automatically. Give it a repository URL, it will clone a project to
9your projects dir (set by `$GHQ_ROOT`) like so:
10
11```sh
12$ ghq get https://github.com/motemen/ghq
13# Runs `git clone https://github.com/motemen/ghq ~/.ghq/github.com/motemen/ghq`
14```
15
16I don't like the idea of having projects hidden away, so I set
17`$GHQ_ROOT` to `$HOME/projects`.
18
19From there, the `list` and `look` subcommands allow listing
20repositories and visiting them in the shell (actually a subshell).
21
22I wanted a nicer way to visit project directories. Since I'm
23using [fzf][] as a fuzzy-finder, I thought it would be nice to use it
24for this. I created a simple function, `fp` (find project) to do that:
25
26```sh
27fp () {
28 ghq look $(ghq list | fzf +m)
29}
30```
31
32I ran into some issues with the subshell of `ghq look` and wondered
33whether it might be possible to create a zsh command to remove the
34need for a subshell.
35
36I found that `fzf` includes a [cd-widget function][fzf-cd-widget] and created
37something similar that uses `ghq` instead of `find`:
38
39```sh
40cd-project-widget () {
41 local cmd="ghq list"
42 setopt localoptions pipefail 2> /dev/null
43 local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" fzf +m)"
44 if [[ -z "$dir" ]]; then
45 zle redisplay
46 return 0
47 fi
48 cd $(ghq list --full-path | grep "$dir")
49 local ret=$?
50 zle reset-prompt
51 typeset -f zle-line-init >/dev/null && zle zle-line-init
52 return $ret
53}
54zle -N cd-project-widget
55```
56
57It should be quite simple to modify it to work with other
58fuzzy-finders. The basic idea is to show the output of `ghq list` for
59selection, and use `ghq list --full-path` with the selected candidate
60to print the correct directory for `cd`.
61
62What's really nice about this, is that I can bind it to a key
63sequence:
64
65```sh
66bindkey '\es' cd-project-widget
67```
68
69Now I can press `M-s` in a shell, start typing "dotfiles" and press enter to `cd`
70to my [dotfiles][] project. Pretty neat!
71
72[ghq]:https://github.com/motemen/ghq
73[fzf]:https://github.com/junegunn/fzf
74[fzf-cd-widget]:https://github.com/junegunn/fzf/blob/337cdbb37c1efc49b09b4cacc6e9ee1369c7d76d/shell/key-bindings.zsh#L40-L54
75[dotfiles]:https://git.alanpearce.eu/dotfiles
diff --git a/content/post/self-hosted-git.md b/content/post/self-hosted-git.md new file mode 100644 index 0000000..b26f61b --- /dev/null +++ b/content/post/self-hosted-git.md
@@ -0,0 +1,146 @@
1+++
2Description = "I describe my git server setup (using cgit and gitolite), and what it allows"
3Tags = ["development","git"]
4date = "2017-06-04T12:33:02+02:00"
5title = "A simple, powerful self-hosted git setup"
6+++
7
8I had been using [gogs][] for about a year. It worked reasonably
9well, as it focuses on being a lightweight self-hosted GitHub
10replacement. However, that wasn't really what I wanted. I just
11wanted to host my own projects, I didn't need things like issues, pull
12requests or wikis.
13
14I recently switched to [gitolite][] and [cgit][], as they were even
15lighter on resources, don't require another login and work without
16an external database. Gitolite is unusual in its configuration: it
17creates a git repository with its configuration file. I will describe
18how I use them, rather than how to set them up, as they both have
19enough documentation on that.
20
21My gitolite configuration file looks like this:
22
23```
24repo gitolite-admin
25 RW+ = alan
26
27repo dotfiles
28 C = alan
29 RW+ = alan
30 R = READERS
31 option hook.post-update = github-mirror
32
33repo [a-z].*
34 C = alan
35 RW+ = CREATOR
36 RW = WRITERS
37 R = READERS
38```
39
40The first block just allows me to work with the configuration
41repository, as the initial setup only enables one specific public SSH
42key, whereas I have three keys that I configure gitolite with.
43
44The second configures my dotfiles specifically. Naturally, I should
45be the only person with read/write access. The `R = READERS` line
46allows remote configuration of read permissions via `ssh $DOMAIN
47perms` (explained further below). The last line runs a mirror script
48(just `git push --mirror…`) so that
49my [dotfiles repository on GitHub][dotfiles-github] is updated when I
50push to my private version.
51
52## Wild (or magic) repositories
53
54The third block is where things get interesting. gitolite has a
55feature called [wildrepos][], which allows configuring a set of
56repositories at once, using a regular expression to match the
57repository name.
58
59The really nice thing here is that the repository need not exist
60before applying the configuration. Therefore, the line `C = alan`
61means that I can create a remote repository automatically by cloning a
62repository URL that doesn't already exist.
63I can clone and create a new repo simultaneously like so:
64
65```shell
66cd ~/projects
67git clone alanpearce.eu:some-new-repository
68```
69
70But with [ghq][], which I [blogged about before][using-ghq], I don't
71have to concern myself with where to put the repository:
72
73```shell
74$ ghq get alanpearce.eu:some-new-repository
75 clone ssh://alanpearce.eu/some-new-repository -> /Volumes/Code/projects/alanpearce.eu/some-new-repository
76 git clone ssh://alanpearce.eu/some-new-repository /Volumes/Code/projects/alanpearce.eu/some-new-repository
77Cloning into '/Volumes/Code/projects/alanpearce.eu/some-new-repository'...
78Initialized empty Git repository in /var/lib/gitolite/repositories/some-new-repository.git/
79warning: You appear to have cloned an empty repository.
80```
81
82The nice URLs come from this piece of my SSH configuration:
83
84```
85Host alanpearce.eu
86 HostName git.alanpearce.eu
87 User gitolite
88```
89
90## Configuring wild repositories
91
92This repository would be private by default, but I can change that by an
93SSH command. Here's how I would do it:
94
95```shell
96ssh alanpearce.eu perms some-new-repository + READERS gitweb
97ssh alanpearce.eu perms some-new-repository + READERS daemon
98```
99
100The first command makes it visible in cgit, whilst the second makes it
101clonable via `git://` url. I can make a repository
102publically-clonable, but invisible on cgit by only allowing the `daemon`
103user and not `gitweb`, if I wanted.
104
105I can also add or change the description of a repository shown on cgit like
106so:
107
108```shell
109ssh alanpearce.eu desc some-new-repository 'A new repository'
110```
111
112All the remote commands exposed by gitolite are described in the
113`help` command e.g. `ssh alanpearce.eu help`
114
115```
116hello alan, this is gitolite@oak running gitolite3 (unknown) on git 2.12.2
117
118list of remote commands available:
119
120 D
121 desc
122 help
123 info
124 motd
125 perms
126 writable
127
128```
129
130## Conclusion
131
132I much prefer creating repositories in this way. It's much simpler
133and allows me to get on with working on the repositories rather than
134going through a multi-step process in a web browser.
135
136With cgit and gitolite, I have a minimal setup, that does exactly what
137I want, without consuming many system resources with daemons.
138
139[gogs]:https://gogs.io/ "Go Git Service"
140[gitolite]:http://gitolite.com/gitolite/
141[cgit]:https://git.zx2c4.com/cgit/
142[NixOS]:http://nixos.org
143[dotfiles-github]:https://github.com/alanpearce/dotfiles
144[wildrepos]:http://gitolite.com/gitolite/wild/
145[ghq]:https://github.com/motemen/ghq
146[using-ghq]:{{< relref "/post/repository-management-with-ghq.md" >}} "Repository management with ghq"
diff --git a/layouts/index.html b/layouts/index.html new file mode 100644 index 0000000..8aebc2d --- /dev/null +++ b/layouts/index.html
@@ -0,0 +1,37 @@
1{{ define "main" -}}
2<main>
3 <section>
4 {{ .Content }}
5 </section>
6 <section>
7 <h2>Latest Posts</h2>
8 <ul>
9 {{- range first 3 .Site.RegularPages }}
10 <li>
11 <a href="{{ .RelPermalink | strings.TrimRight "/" }}">{{ .Title }}</a>
12 <time datetime="{{ .Date.Format "2006-01-02T15:04:05Z" }}">{{ .Date.Format "Monday, 2 January 2006" }}</time>
13 </li>
14 {{- end }}
15 </ul>
16 </section>
17 <section>
18 <h2>Elsewhere on the Internet</h2>
19 <ul>
20 {{- range .Site.Menus.contact }}
21 <li>
22 {{- if hasPrefix .URL "mailto:" }}
23 <a href="{{ .URL }}" class="u-email email" rel="me">{{ .Name }}</a>
24 {{- else }}
25 <a href="{{ .URL }}" class="u-url url" rel="me">{{ .Name }}</a>
26 {{- end }}
27 </li>
28 {{- end }}
29 </ul>
30 </section>
31 <footer>
32 {{- with .Site.Params.GPG }}
33 GPG Key: <a href="{{ .url }}" rel="pgpkey">{{ .fingerprint }}</a>
34 {{- end }}
35 </footer>
36</main>
37{{- end }}
diff --git a/layouts/partials/hook_head_end.html b/layouts/partials/hook_head_end.html new file mode 100644 index 0000000..13e0ec6 --- /dev/null +++ b/layouts/partials/hook_head_end.html
@@ -0,0 +1,2 @@
1<link href="{{ if .IsPage }}{{ .Permalink | replaceRE "/$" "" }}{{ else }}{{ .Permalink }}{{ end }}" rel="canonical">
2<meta name="googlebot" content="noindex">
diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..4a2efbb --- /dev/null +++ b/netlify.toml
@@ -0,0 +1,77 @@
1[build]
2 publish = "public"
3 command = "hugo"
4 [build.environment]
5 HUGO_VERSION = "0.58.0"
6
7[context.production.environment]
8 HUGO_ENV = "production"
9
10[context.branch-deploy.environment]
11 HUGO_ENABLEGITINFO = "true"
12
13[[redirects]]
14 from = "https://alanpearceeu.netlify.com/*"
15 to = "https://www.alanpearce.eu/:splat"
16 status = 301
17 force = true
18
19[[redirects]]
20 from = "https://www.alanpearce.co.uk/*"
21 to = "https://www.alanpearce.eu/:splat"
22 status = 301
23 force = true
24
25[[redirects]]
26 from = "https://alanpearce.co.uk/*"
27 to = "https://www.alanpearce.eu/:splat"
28 status = 301
29 force = true
30
31[[redirects]]
32 from = "http://www.alanpearce.co.uk/*"
33 to = "https://www.alanpearce.eu/:splat"
34 status = 301
35 force = true
36
37[[redirects]]
38 from = "http://alanpearce.co.uk/*"
39 to = "https://www.alanpearce.eu/:splat"
40 status = 301
41 force = true
42
43[[redirects]]
44 from = "https://www.alanpearce.uk/*"
45 to = "https://www.alanpearce.eu/:splat"
46 status = 301
47 force = true
48
49[[redirects]]
50 from = "https://alanpearce.uk/*"
51 to = "https://www.alanpearce.eu/:splat"
52 status = 301
53 force = true
54
55[[redirects]]
56 from = "http://www.alanpearce.uk/*"
57 to = "https://www.alanpearce.eu/:splat"
58 status = 301
59 force = true
60
61[[redirects]]
62 from = "http://alanpearce.uk/*"
63 to = "https://www.alanpearce.eu/:splat"
64 status = 301
65 force = true
66
67[[headers]]
68 for = "/*"
69 [headers.values]
70 Link = '''
71</css/style.css>; rel=preload; as=stylesheet'''
72
73[[headers]]
74 for = "/post/*"
75 [headers.values]
76 Link = '''
77</css/style.css>; rel=preload; as=stylesheet''' \ No newline at end of file
diff --git a/static/.well-known/keybase.txt b/static/.well-known/keybase.txt new file mode 100644 index 0000000..f027aa9 --- /dev/null +++ b/static/.well-known/keybase.txt
@@ -0,0 +1,56 @@
1==================================================================
2https://keybase.io/alanpearce
3--------------------------------------------------------------------
4
5I hereby claim:
6
7 * I am an admin of https://www.alanpearce.eu
8 * I am alanpearce (https://keybase.io/alanpearce) on keybase.
9 * I have a public key ASAktAZWh67GLebI8PNw4QNlJ4zEiIogKiQQ8WsqVsQa8Qo
10
11To do so, I am signing this object:
12
13{
14 "body": {
15 "key": {
16 "eldest_kid": "01200d892fbb34517abd5120fa546cb65dad1172cd85405cfae4936dcdb8bf5ac1850a",
17 "host": "keybase.io",
18 "kid": "012024b4065687aec62de6c8f0f370e10365278cc4888a202a2410f16b2a56c41af10a",
19 "uid": "91ae6da6b67277c6eded2451d6925919",
20 "username": "alanpearce"
21 },
22 "merkle_root": {
23 "ctime": 1529691082,
24 "hash": "6588b60bdcbf5836c74db6647f69ed9f88e8d45b237f896e75d790534fcb3058a3c2e3e9b7f026469b0ca30fe58f20e47fbe074306e02eba912348f19ab1abd2",
25 "hash_meta": "68aec5954816532401f402af55121c0c7496f3aac93475db68ea50e38e7e45b4",
26 "seqno": 3127786
27 },
28 "service": {
29 "entropy": "pkn3peHHXkyLn2KYC2q0CKkC",
30 "hostname": "www.alanpearce.eu",
31 "protocol": "https:"
32 },
33 "type": "web_service_binding",
34 "version": 2
35 },
36 "client": {
37 "name": "keybase.io go client",
38 "version": "2.1.1"
39 },
40 "ctime": 1529691093,
41 "expire_in": 504576000,
42 "prev": "9d580c5bd9f3b4a01356f507d808de55add562ddf3fded05a7d74299c5766503",
43 "seqno": 25,
44 "tag": "signature"
45}
46
47which yields the signature:
48
49hKRib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgJLQGVoeuxi3myPDzcOEDZSeMxIiKICokEPFrKlbEGvEKp3BheWxvYWTESpcCGcQgnVgMW9nztKATVvUH2AjeVa3VYt3z/e0Fp9dCmcV2ZQPEIIRjxssrSyS8RF3Xr7Br780Q1Y0vy58txz8S6XBBaYpCAgHCo3NpZ8RAeAwN++lz+C+csgxZXXLSv76w2WcYaH41EcagALVLrULinV0j+Ea1TOUhmBfI9KNKFkOSiuEm+kOVktdf4BrWA6hzaWdfdHlwZSCkaGFzaIKkdHlwZQildmFsdWXEIFnViSN5iA8WlkQfuHAD3PEQ0gkZyjv59iuNx7EoBhrRo3RhZ80CAqd2ZXJzaW9uAQ==
50
51And finally, I am proving ownership of this host by posting or
52appending to this document.
53
54View my publicly-auditable identity here: https://keybase.io/alanpearce
55
56================================================================== \ No newline at end of file
diff --git a/static/css/syntax.css b/static/css/syntax.css new file mode 100644 index 0000000..23a21be --- /dev/null +++ b/static/css/syntax.css
@@ -0,0 +1,81 @@
1/* Background */ .chroma { background-color: #f8f8f8 }
2/* Other */ .chroma .x { color: #000000 }
3/* Error */ .chroma .err { color: #a40000 }
4/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
5/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
6/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
7/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em; display: block; }
8/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em; }
9/* Keyword */ .chroma .k { color: #204a87; font-weight: bold }
10/* KeywordConstant */ .chroma .kc { color: #204a87; font-weight: bold }
11/* KeywordDeclaration */ .chroma .kd { color: #204a87; font-weight: bold }
12/* KeywordNamespace */ .chroma .kn { color: #204a87; font-weight: bold }
13/* KeywordPseudo */ .chroma .kp { color: #204a87; font-weight: bold }
14/* KeywordReserved */ .chroma .kr { color: #204a87; font-weight: bold }
15/* KeywordType */ .chroma .kt { color: #204a87; font-weight: bold }
16/* Name */ .chroma .n { color: #000000 }
17/* NameAttribute */ .chroma .na { color: #c4a000 }
18/* NameBuiltin */ .chroma .nb { color: #204a87 }
19/* NameBuiltinPseudo */ .chroma .bp { color: #3465a4 }
20/* NameClass */ .chroma .nc { color: #000000 }
21/* NameConstant */ .chroma .no { color: #000000 }
22/* NameDecorator */ .chroma .nd { color: #5c35cc; font-weight: bold }
23/* NameEntity */ .chroma .ni { color: #ce5c00 }
24/* NameException */ .chroma .ne { color: #cc0000; font-weight: bold }
25/* NameFunction */ .chroma .nf { color: #000000 }
26/* NameFunctionMagic */ .chroma .fm { color: #000000 }
27/* NameLabel */ .chroma .nl { color: #f57900 }
28/* NameNamespace */ .chroma .nn { color: #000000 }
29/* NameOther */ .chroma .nx { color: #000000 }
30/* NameProperty */ .chroma .py { color: #000000 }
31/* NameTag */ .chroma .nt { color: #204a87; font-weight: bold }
32/* NameVariable */ .chroma .nv { color: #000000 }
33/* NameVariableClass */ .chroma .vc { color: #000000 }
34/* NameVariableGlobal */ .chroma .vg { color: #000000 }
35/* NameVariableInstance */ .chroma .vi { color: #000000 }
36/* NameVariableMagic */ .chroma .vm { color: #000000 }
37/* Literal */ .chroma .l { color: #000000 }
38/* LiteralDate */ .chroma .ld { color: #000000 }
39/* LiteralString */ .chroma .s { color: #4e9a06 }
40/* LiteralStringAffix */ .chroma .sa { color: #4e9a06 }
41/* LiteralStringBacktick */ .chroma .sb { color: #4e9a06 }
42/* LiteralStringChar */ .chroma .sc { color: #4e9a06 }
43/* LiteralStringDelimiter */ .chroma .dl { color: #4e9a06 }
44/* LiteralStringDoc */ .chroma .sd { color: #8f5902; font-style: italic }
45/* LiteralStringDouble */ .chroma .s2 { color: #4e9a06 }
46/* LiteralStringEscape */ .chroma .se { color: #4e9a06 }
47/* LiteralStringHeredoc */ .chroma .sh { color: #4e9a06 }
48/* LiteralStringInterpol */ .chroma .si { color: #4e9a06 }
49/* LiteralStringOther */ .chroma .sx { color: #4e9a06 }
50/* LiteralStringRegex */ .chroma .sr { color: #4e9a06 }
51/* LiteralStringSingle */ .chroma .s1 { color: #4e9a06 }
52/* LiteralStringSymbol */ .chroma .ss { color: #4e9a06 }
53/* LiteralNumber */ .chroma .m { color: #0000cf; font-weight: bold }
54/* LiteralNumberBin */ .chroma .mb { color: #0000cf; font-weight: bold }
55/* LiteralNumberFloat */ .chroma .mf { color: #0000cf; font-weight: bold }
56/* LiteralNumberHex */ .chroma .mh { color: #0000cf; font-weight: bold }
57/* LiteralNumberInteger */ .chroma .mi { color: #0000cf; font-weight: bold }
58/* LiteralNumberIntegerLong */ .chroma .il { color: #0000cf; font-weight: bold }
59/* LiteralNumberOct */ .chroma .mo { color: #0000cf; font-weight: bold }
60/* Operator */ .chroma .o { color: #ce5c00; font-weight: bold }
61/* OperatorWord */ .chroma .ow { color: #204a87; font-weight: bold }
62/* Punctuation */ .chroma .p { color: #000000; font-weight: bold }
63/* Comment */ .chroma .c { color: #8f5902; font-style: italic }
64/* CommentHashbang */ .chroma .ch { color: #8f5902; font-style: italic }
65/* CommentMultiline */ .chroma .cm { color: #8f5902; font-style: italic }
66/* CommentSingle */ .chroma .c1 { color: #8f5902; font-style: italic }
67/* CommentSpecial */ .chroma .cs { color: #8f5902; font-style: italic }
68/* CommentPreproc */ .chroma .cp { color: #8f5902; font-style: italic }
69/* CommentPreprocFile */ .chroma .cpf { color: #8f5902; font-style: italic }
70/* Generic */ .chroma .g { color: #000000 }
71/* GenericDeleted */ .chroma .gd { color: #a40000 }
72/* GenericEmph */ .chroma .ge { color: #000000; font-style: italic }
73/* GenericError */ .chroma .gr { color: #ef2929 }
74/* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold }
75/* GenericInserted */ .chroma .gi { color: #00a000 }
76/* GenericOutput */ .chroma .go { color: #000000; font-style: italic }
77/* GenericPrompt */ .chroma .gp { color: #8f5902 }
78/* GenericStrong */ .chroma .gs { color: #000000; font-weight: bold }
79/* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold }
80/* GenericTraceback */ .chroma .gt { color: #a40000; font-weight: bold }
81/* TextWhitespace */ .chroma .w { color: #f8f8f8 }
diff --git a/static/img/me-large.jpg b/static/img/me-large.jpg new file mode 100644 index 0000000..1a1b369 --- /dev/null +++ b/static/img/me-large.jpg
Binary files differ
diff --git a/static/img/me-thumb.jpg b/static/img/me-thumb.jpg new file mode 100644 index 0000000..d3c82f0 --- /dev/null +++ b/static/img/me-thumb.jpg
Binary files differ
diff --git a/static/keybase.txt b/static/keybase.txt new file mode 100644 index 0000000..71b773e --- /dev/null +++ b/static/keybase.txt
@@ -0,0 +1,55 @@
1==================================================================
2https://keybase.io/alanpearce
3--------------------------------------------------------------------
4
5I hereby claim:
6
7 * I am an admin of https://alanpearce.uk
8 * I am alanpearce (https://keybase.io/alanpearce) on keybase.
9 * I have a public key ASANiS-7NFF6vVEg-lRstl2tEXLNhUBc-uSTbc24v1rBhQo
10
11To do so, I am signing this object:
12
13{
14 "body": {
15 "key": {
16 "eldest_kid": "01200d892fbb34517abd5120fa546cb65dad1172cd85405cfae4936dcdb8bf5ac1850a",
17 "host": "keybase.io",
18 "kid": "01200d892fbb34517abd5120fa546cb65dad1172cd85405cfae4936dcdb8bf5ac1850a",
19 "uid": "91ae6da6b67277c6eded2451d6925919",
20 "username": "alanpearce"
21 },
22 "merkle_root": {
23 "ctime": 1528123072,
24 "hash": "e0815741a6d5837c8c4a3af6900f5921484ebc1a0c8931284247134587f5cb2a330840662e835ad3d21397c5617cfa3c6152777b5b643821f5e80cd00d107af3",
25 "hash_meta": "c92918ad06b58621eea19be246eff63e3a86d00c619c5457ae41cc090bbcfd70",
26 "seqno": 2990872
27 },
28 "service": {
29 "hostname": "alanpearce.uk",
30 "protocol": "https:"
31 },
32 "type": "web_service_binding",
33 "version": 1
34 },
35 "client": {
36 "name": "keybase.io go client",
37 "version": "1.0.44"
38 },
39 "ctime": 1528123107,
40 "expire_in": 504576000,
41 "prev": "7c22548954f852d481dec3c2f6ccefb4be4322e2cd51016e2beb4be359b65763",
42 "seqno": 6,
43 "tag": "signature"
44}
45
46which yields the signature:
47
48hKRib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgDYkvuzRRer1RIPpUbLZdrRFyzYVAXPrkk23NuL9awYUKp3BheWxvYWTFA0d7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwMGQ4OTJmYmIzNDUxN2FiZDUxMjBmYTU0NmNiNjVkYWQxMTcyY2Q4NTQwNWNmYWU0OTM2ZGNkYjhiZjVhYzE4NTBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwMGQ4OTJmYmIzNDUxN2FiZDUxMjBmYTU0NmNiNjVkYWQxMTcyY2Q4NTQwNWNmYWU0OTM2ZGNkYjhiZjVhYzE4NTBhIiwidWlkIjoiOTFhZTZkYTZiNjcyNzdjNmVkZWQyNDUxZDY5MjU5MTkiLCJ1c2VybmFtZSI6ImFsYW5wZWFyY2UifSwibWVya2xlX3Jvb3QiOnsiY3RpbWUiOjE1MjgxMjMwNzIsImhhc2giOiJlMDgxNTc0MWE2ZDU4MzdjOGM0YTNhZjY5MDBmNTkyMTQ4NGViYzFhMGM4OTMxMjg0MjQ3MTM0NTg3ZjVjYjJhMzMwODQwNjYyZTgzNWFkM2QyMTM5N2M1NjE3Y2ZhM2M2MTUyNzc3YjViNjQzODIxZjVlODBjZDAwZDEwN2FmMyIsImhhc2hfbWV0YSI6ImM5MjkxOGFkMDZiNTg2MjFlZWExOWJlMjQ2ZWZmNjNlM2E4NmQwMGM2MTljNTQ1N2FlNDFjYzA5MGJiY2ZkNzAiLCJzZXFubyI6Mjk5MDg3Mn0sInNlcnZpY2UiOnsiaG9zdG5hbWUiOiJhbGFucGVhcmNlLnVrIiwicHJvdG9jb2wiOiJodHRwczoifSwidHlwZSI6IndlYl9zZXJ2aWNlX2JpbmRpbmciLCJ2ZXJzaW9uIjoxfSwiY2xpZW50Ijp7Im5hbWUiOiJrZXliYXNlLmlvIGdvIGNsaWVudCIsInZlcnNpb24iOiIxLjAuNDQifSwiY3RpbWUiOjE1MjgxMjMxMDcsImV4cGlyZV9pbiI6NTA0NTc2MDAwLCJwcmV2IjoiN2MyMjU0ODk1NGY4NTJkNDgxZGVjM2MyZjZjY2VmYjRiZTQzMjJlMmNkNTEwMTZlMmJlYjRiZTM1OWI2NTc2MyIsInNlcW5vIjo2LCJ0YWciOiJzaWduYXR1cmUifaNzaWfEQLd80AcgXet5yGW0bL5y5IAf/rQ2R15NNRCk0T6qz/kQlf30JN810HcLoGrX3RcalgHbb8QbcgCWyd0kiep4CgWoc2lnX3R5cGUgpGhhc2iCpHR5cGUIpXZhbHVlxCB8OF/NBSFEq73lc0tGCM3gCNgr29QmOgZd9jvX68GNaKN0YWfNAgKndmVyc2lvbgE=
49
50And finally, I am proving ownership of this host by posting or
51appending to this document.
52
53View my publicly-auditable identity here: https://keybase.io/alanpearce
54
55==================================================================
diff --git a/static/public_key.asc b/static/public_key.asc new file mode 100644 index 0000000..f081429 --- /dev/null +++ b/static/public_key.asc
@@ -0,0 +1,16 @@
1-----BEGIN PGP PUBLIC KEY BLOCK-----
2
3mDMEXOZxBhYJKwYBBAHaRw8BAQdApEDmvmbv0fkrkND5LsR32g9QX8KtXAybgcCv
4euU6N9O0IEFsYW4gUGVhcmNlIDxhbGFuQGFsYW5wZWFyY2UuZXU+iIAEExYIABwF
5AlzmcQYCCwkCGwMEFQgJCgQWAgMBAheAAh4BABYJEM1L65Ko1GWDCxpUUkVaT1It
6R1BHgDABAICw5varaVWeuVlzJ0/XpLDFSHfY1CvQbMHe1LJ/iwGJAP9m3XC0yTyX
7uEG7w3R32Md5urcGH3fTIKK0ea6M+QVtArQgQWxhbiBQZWFyY2UgPGFsYW5Ac2F0
8b3NoaXBheS5pbz6IkAQTFggAOBYhBEjmV2wHBziMuL79DM1L65Ko1GWDBQJc5p5N
9AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEM1L65Ko1GWD2Z4A/jggQexr
10za4DXNK2jolKjIBL9S7pOGbXxldHo69HC+dLAP4lJlaExUbompFaXzV/FETH2pdQ
11azi51lmD8wN5YX4AA7g4BFzmcQYSCisGAQQBl1UBBQEBB0C5WCVOLRJpEHSWMVFH
120xtWavMXh3QUaoNIrX0jcEtpIAMBCAeIbQQYFggACQUCXOZxBgIbDAAWCRDNS+uS
13qNRlgwsaVFJFWk9SLUdQR5PKAP93z83yYaOZMQKZYAD3h2LHdlKD2wl2LaLiFOll
144N4ghgEA5iTNV6d5PHo8NV73T4xm97qY94LpF1cDWwBDYhb0ywI=
15=VSmX
16-----END PGP PUBLIC KEY BLOCK-----
diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..ef30e6f --- /dev/null +++ b/static/robots.txt
@@ -0,0 +1,7 @@
1User-agent: *
2Disallow:
3Host: alanpearce.eu
4Sitemap: https://alanpearce.eu/sitemap.xml
5
6User-agent: googlebot
7Disallow: /
diff --git a/static/talks/fp-js/index.html b/static/talks/fp-js/index.html new file mode 100644 index 0000000..e90650c --- /dev/null +++ b/static/talks/fp-js/index.html
@@ -0,0 +1,431 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
4<head>
5<!-- 2017-10-16 Mon 10:10 -->
6<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7<meta name="viewport" content="width=device-width, initial-scale=1" />
8<title>Functional Programming in JavaScript</title>
9<meta name="generator" content="Org mode" />
10<meta name="author" content="Alan Pearce" />
11<meta name="version" content="S5 1.2a2" />
12<meta name='defaultView' content='slideshow' />
13<meta name='controlVis' content='hidden' />
14<!-- style sheet links -->
15<link rel='stylesheet' href='ui/default/outline.css' type='text/css' media='screen' id='outlineStyle' />
16<link rel='stylesheet' href='ui/default/print.css' type='text/css' media='print' id='slidePrint' />
17<link rel='stylesheet' href='ui/default/opera.css' type='text/css' media='projection' id='operaFix' />
18<link rel='stylesheet' href='ui/default/slides.css' type='text/css' media='screen' id='slideProj' />
19<!-- S5 JS -->
20<script src='ui/default/slides.js' type='text/javascript'></script>
21
22
23</head>
24<body>
25<div class="layout">
26<div id="controls"><!-- no edit --></div>
27<div id="currentSlide"><!-- no edit --></div>
28<div id="header" class="status">
29&#x20;
30</div>
31
32<div id="footer" class="status">
33<h1>Alan Pearce - Functional Programming in JavaScript</h1>
34</div>
35
36</div>
37<div id="content" class="presentation">
38<div id='title-slide' class='slide'>
39<h1>Functional Programming in JavaScript</h1>
40<h2></h2>
41<h2>Alan Pearce</h2>
42<h3><a href="mailto:alan@alanpearce.eu">alan@alanpearce.eu</a></h3>
43<h4></h4>
44</div>
45<div id='table-of-contents' class='slide'>
46<h1>Table of Contents</h1>
47<div id="text-table-of-contents">
48<ul>
49<li>1. Why?</li>
50<li>2. Concepts</li>
51<li>3. Further concepts</li>
52<li>4. First-class functions</li>
53<li>5. Higher-order functions</li>
54<li>6. Higher-order functions (cont.)</li>
55<li>7. Pure functions</li>
56<li>8. Recursion</li>
57<li>9. Partial application</li>
58<li>10. Partial application (cont.)</li>
59<li>11. Currying</li>
60<li>12. Easy Currying</li>
61<li>13. Practical Currying</li>
62<li>14. Functional composition</li>
63<li>15. Functional composition (cont.)</li>
64<li>16. pipe</li>
65<li>17. Point-free programming</li>
66<li>18. Further Resources</li>
67</ul>
68</div>
69</div>
70
71<div id="outline-container-org05e10f9" class="outline-1 slide">
72<h1 id="org05e10f9"><span class="section-number-1">1</span> Why?</h1>
73<div class="outline-text-1" id="text-1">
74<p>
75Imperative programming is concerned with <b>how</b>
76</p>
77
78<p>
79Functional programming is concerned with <b>what</b>
80</p>
81</div>
82</div>
83
84<div id="outline-container-org14fa5d9" class="outline-1 slide">
85<h1 id="org14fa5d9"><span class="section-number-1">2</span> Concepts</h1>
86<div class="outline-text-1" id="text-2">
87<ul class="org-ul">
88<li>First-class Functions</li>
89<li>Higher-order Functions</li>
90<li>Recursion</li>
91<li>Pure functions</li>
92<li>Currying &amp; Partial Application</li>
93</ul>
94</div>
95</div>
96
97<div id="outline-container-orgd930436" class="outline-1 slide">
98<h1 id="orgd930436"><span class="section-number-1">3</span> Further concepts</h1>
99<div class="outline-text-1" id="text-3">
100<ul class="org-ul">
101<li>Lazy Evaluation</li>
102<li>Types &amp; Data Structures</li>
103<li>Category Theory</li>
104</ul>
105</div>
106</div>
107
108<div id="outline-container-orgbd39891" class="outline-1 slide">
109<h1 id="orgbd39891"><span class="section-number-1">4</span> First-class functions</h1>
110<div class="outline-text-1" id="text-4">
111<ul class="org-ul">
112<li>Are values</li>
113<li>Have no restriction on their use</li>
114<li>Enable the use of callback functions in JavaScript</li>
115</ul>
116
117<p>
118
119</p>
120
121<div class="org-src-container">
122<pre><code class="src src-js">var fn = function () {
123 return 2
124}
125</code></pre>
126</div>
127</div>
128</div>
129
130
131<div id="outline-container-org8d1ee8a" class="outline-1 slide">
132<h1 id="org8d1ee8a"><span class="section-number-1">5</span> Higher-order functions</h1>
133<div class="outline-text-1" id="text-5">
134<p>
135Functions that operate on other functions are higher-order functions
136</p>
137
138<p>
139
140</p>
141
142<div class="org-src-container">
143<pre><code class="src src-js">var succ = function (x) {
144 return x + 1
145}
146
147var arr = [1, 2, 3, 4]
148
149arr.map(succ)
150</code></pre>
151</div>
152
153<p>
154Here, <code>Array.prototype.map</code> is the higher-order function
155</p>
156</div>
157</div>
158
159<div id="outline-container-org3bbf4ac" class="outline-1 slide">
160<h1 id="org3bbf4ac"><span class="section-number-1">6</span> Higher-order functions (cont.)</h1>
161<div class="outline-text-1" id="text-6">
162<p>
163Functions that return functions are also higher-order functions
164</p>
165
166<p>
167
168</p>
169
170<div class="org-src-container">
171<pre><code class="src src-js">function <span style="font-weight: bold;">adder</span> (n) {
172 return function (x) {
173 return n + x
174 }
175}
176
177var add1 = adder(1)
178</code></pre>
179</div>
180
181<p>
182<code>adder</code> is a higher-order function
183</p>
184</div>
185</div>
186
187<div id="outline-container-orgfb6eee2" class="outline-1 slide">
188<h1 id="orgfb6eee2"><span class="section-number-1">7</span> Pure functions</h1>
189<div class="outline-text-1" id="text-7">
190<p>
191Functions without side-effects
192</p>
193
194<div class="org-src-container">
195<pre><code class="src src-js">var succ = (x) =&gt; x + 1
196
197console.log(succ(succ(1)))
198
199<span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">could be optimised away by a compiler, e.g.:</span>
200
201console.log(3)
202</code></pre>
203</div>
204</div>
205</div>
206
207<div id="outline-container-org16e9966" class="outline-1 slide">
208<h1 id="org16e9966"><span class="section-number-1">8</span> Recursion</h1>
209<div class="outline-text-1" id="text-8">
210<p>
211Functions that call themselves
212</p>
213
214<div class="org-src-container">
215<pre><code class="src src-js">function <span style="font-weight: bold;">fibonacci</span> (n) {
216 switch (n) {
217 case 0:
218 case 1:
219 return 1
220 default:
221 return fibonacci(n - 1) + fibonacci(n - 2)
222 }
223}
224</code></pre>
225</div>
226</div>
227</div>
228
229<div id="outline-container-org1b7b0af" class="outline-1 slide">
230<h1 id="org1b7b0af"><span class="section-number-1">9</span> Partial application</h1>
231<div class="outline-text-1" id="text-9">
232<p>
233The infamous <code>Function.prototype.bind</code> in JavaScript
234</p>
235
236<div class="org-src-container">
237<pre><code class="src src-js">function <span style="font-weight: bold;">add</span> (x, y) {
238 return x + y
239}
240
241var add1 = add.bind(add, 1)
242
243add1(3) <span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">= 4</span>
244</code></pre>
245</div>
246</div>
247</div>
248
249<div id="outline-container-org796e6d3" class="outline-1 slide">
250<h1 id="org796e6d3"><span class="section-number-1">10</span> Partial application (cont.)</h1>
251<div class="outline-text-1" id="text-10">
252<p>
253After ES6 introduced arrow functions, partial application has become
254more popular
255</p>
256
257<div class="org-src-container">
258<pre><code class="src src-js">var add = x =&gt; y =&gt; x + y
259</code></pre>
260</div>
261</div>
262</div>
263
264<div id="outline-container-orgeb4d67b" class="outline-1 slide">
265<h1 id="orgeb4d67b"><span class="section-number-1">11</span> Currying</h1>
266<div class="outline-text-1" id="text-11">
267<p>
268Related to partial application, but more implicit and general
269</p>
270
271<p>
272Translates <b><i>1</i> function of arity <i>n</i></b> to <b><i>n</i> functions of arity <i>1</i></b>
273</p>
274
275<div class="org-src-container">
276<pre><code class="src src-js">function <span style="font-weight: bold;">volume</span> (w, d, h) {
277 return w * d * h
278}
279
280var vol = curry(volume)
281vol(10)(20)(30)
282<span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">is strictly equivalent to</span>
283volume(10, 20, 30)
284</code></pre>
285</div>
286</div>
287</div>
288
289<div id="outline-container-orgdfd8353" class="outline-1 slide">
290<h1 id="orgdfd8353"><span class="section-number-1">12</span> Easy Currying</h1>
291<div class="outline-text-1" id="text-12">
292<p>
293In order to make currying (and partial application) easier to use,
294move the <b>most important</b> argument to a function to the end:
295</p>
296
297<div class="org-src-container">
298<pre><code class="src src-js">var badMap = (arr, fn) =&gt; arr.map(fn)
299var goodMap = (fn, arr) =&gt; arr.map(fn)
300var curriedBadMap = curry(badmap)
301var curriedGoodMap = curry(goodMap)
302
303var goodDoubleArray = goodMap(x =&gt; x * 2)
304var badDoubleArray = badMap(_, x =&gt; x * 2)
305</code></pre>
306</div>
307
308<p>
309The bad version requires the curry function to support a magic
310placeholder argument and doesn't look as clean.
311</p>
312</div>
313</div>
314
315<div id="outline-container-org30accd5" class="outline-1 slide">
316<h1 id="org30accd5"><span class="section-number-1">13</span> Practical Currying</h1>
317<div class="outline-text-1" id="text-13">
318<p>
319Currying is not automatic in JavaScript, as in other languages
320</p>
321
322<p>
323External tools aren't (so far) able to statically analyse curried
324functions
325</p>
326
327<p>
328Solution: Don't expose curried functions
329Instead, write functions as if currying were automatic
330</p>
331
332<p>
333If consumers want to curry, they can. If they don't, their editor or
334language server will show them the arguments
335</p>
336</div>
337</div>
338
339<div id="outline-container-org5f352dc" class="outline-1 slide">
340<h1 id="org5f352dc"><span class="section-number-1">14</span> Functional composition</h1>
341<div class="outline-text-1" id="text-14">
342<p>
343Creating functions from other functions
344</p>
345
346<p>
347Usually provided by <code>compose</code> (right-to-left) and <code>pipe</code> (left-to-right)
348</p>
349
350<p>
351A very simple definition of <code>compose</code> for only two functions would look like this
352</p>
353
354<div class="org-src-container">
355<pre><code class="src src-js">function <span style="font-weight: bold;">compose</span> (f, g) {
356 return function (...args) {
357 return f(g(...args))
358 }
359}
360</code></pre>
361</div>
362</div>
363</div>
364
365<div id="outline-container-org0c2c4f9" class="outline-1 slide">
366<h1 id="org0c2c4f9"><span class="section-number-1">15</span> Functional composition (cont.)</h1>
367<div class="outline-text-1" id="text-15">
368<div class="org-src-container">
369<pre><code class="src src-js">var plusOne = x =&gt; x + 1
370var timesTwo = x =&gt; x * 2
371
372var plusOneTimesTwo = compose(timesTwo, plusOne)
373var timesTwoPlusOne = compose(plusOne, timesTwo)
374
375nextDoubled(3) <span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">= (3 + 1) * 2 = 8</span>
376doubledPlusOne(3) <span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">= (3 * 2) + 1 = 7</span>
377</code></pre>
378</div>
379</div>
380</div>
381
382<div id="outline-container-orgddc61f6" class="outline-1 slide">
383<h1 id="orgddc61f6"><span class="section-number-1">16</span> pipe</h1>
384<div class="outline-text-1" id="text-16">
385<p>
386What about <code>pipe</code>?
387</p>
388
389<p>
390<code>pipe</code> does the same thing, but runs the functions the other way around
391</p>
392
393<p>
394<code>pipe(f, g)</code> is the same as <code>compose(g, f)</code>
395</p>
396</div>
397</div>
398
399<div id="outline-container-org7426c58" class="outline-1 slide">
400<h1 id="org7426c58"><span class="section-number-1">17</span> Point-free programming</h1>
401<div class="outline-text-1" id="text-17">
402<p>
403With currying and higher-order functions, we (often) don't need to declare function arguments
404</p>
405
406<div class="org-src-container">
407<pre><code class="src src-js">var modulo = a =&gt; b =&gt; b % a
408var eq = a =&gt; b =&gt; a === b
409
410var isEven = x =&gt; eq(0)(modulo(2)(x))
411var isEvenPointFree = compose(eq(0), modulo(2))
412</code></pre>
413</div>
414</div>
415</div>
416
417<div id="outline-container-org8868cfb" class="outline-1 slide">
418<h1 id="org8868cfb"><span class="section-number-1">18</span> Further Resources</h1>
419<div class="outline-text-1" id="text-18">
420<ul class="org-ul">
421<li><a href="https://drboolean.gitbooks.io/mostly-adequate-guide/content/">Mostly adequate guide to FP (in javascript)</a></li>
422<li><a href="http://ramdajs.com/">Ramda</a>, a general-purpose FP library</li>
423<li><a href="https://sanctuary.js.org/">Sanctuary</a>, a JavaScript library for Haskellers</li>
424</ul>
425</div>
426</div>
427
428
429</div>
430</body>
431</html>
diff --git a/static/talks/fp-js/index.org b/static/talks/fp-js/index.org new file mode 100644 index 0000000..8a4bf6c --- /dev/null +++ b/static/talks/fp-js/index.org
@@ -0,0 +1,226 @@
1#+TITLE: Functional Programming in JavaScript
2#+PROPERTY: :html-toplevel-hlevel 1
3#+PROPERTY: :with-toc 0
4* Why?
5
6Imperative programming is concerned with *how*
7
8Functional programming is concerned with *what*
9
10* Concepts
11
12 - First-class Functions
13 - Higher-order Functions
14 - Recursion
15 - Pure functions
16 - Currying & Partial Application
17
18* Further concepts
19
20 - Lazy Evaluation
21 - Types & Data Structures
22 - Category Theory
23
24* First-class functions
25
26 - Have no restriction on their use
27 - Are values
28 - Enable the use of callback functions in JavaScript
29
30
31
32#+BEGIN_SRC js
33const fn = function () {
34 return 2
35}
36#+END_SRC
37
38
39* Higher-order functions
40
41Functions that operate on other functions are higher-order functions
42
43
44
45#+BEGIN_SRC js
46const succ = function (x) {
47 return x + 1
48}
49
50const arr = [1, 2, 3, 4]
51
52arr.map(succ)
53#+END_SRC
54
55Here, =Array.prototype.map= is the higher-order function
56
57* Higher-order functions (cont.)
58
59Functions that return functions are also higher-order functions
60
61
62
63#+BEGIN_SRC js
64function adder (n) {
65 return function (x) {
66 return n + x
67 }
68}
69
70const add1 = adder(1)
71#+END_SRC
72
73=adder= is a higher-order function
74
75* Pure functions
76
77Functions without side-effects
78
79#+BEGIN_SRC js
80const succ = (x) => x + 1
81
82console.log(succ(succ(1)))
83
84// could be optimised away by a compiler, e.g.:
85
86console.log(3)
87#+END_SRC
88
89* Recursion
90
91Functions that call themselves
92
93#+BEGIN_SRC js
94function fibonacci (n) {
95 switch (n) {
96 case 0:
97 case 1:
98 return 1
99 default:
100 return fibonacci(n - 1) + fibonacci(n - 2)
101 }
102}
103#+END_SRC
104
105* Partial application
106
107The infamous =Function.prototype.bind= in JavaScript
108
109#+BEGIN_SRC js
110function add (x, y) {
111 return x + y
112}
113
114const add1 = add.bind(add, 1)
115
116add1(3) // = 4
117#+END_SRC
118
119* Partial application (cont.)
120
121After ES6 introduced arrow functions, partial application has become
122more popular
123
124#+BEGIN_SRC js
125const add = x => y => x + y
126#+END_SRC
127
128* Currying
129
130Related to partial application, but more implicit and general
131
132Translates */1/ function of arity /n/* to */n/ functions of arity /1/*
133
134#+BEGIN_SRC js
135function volume (w, d, h) {
136 return w * d * h
137}
138
139const vol = curry(volume)
140vol(10)(20)(30)
141// is strictly equivalent to
142volume(10, 20, 30)
143#+END_SRC
144
145* Easy Currying
146
147In order to make currying (and partial application) easier to use,
148move the *most important* argument to a function to the end:
149
150#+BEGIN_SRC js
151const badMap = (arr, fn) => arr.map(fn)
152const goodMap = (fn, arr) => arr.map(fn)
153const curriedBadMap = curry(badmap)
154const curriedGoodMap = curry(goodMap)
155
156const goodDoubleArray = goodMap(x => x * 2)
157const badDoubleArray = badMap(_, x => x * 2)
158#+END_SRC
159
160The bad version requires the curry function to support a magic
161placeholder argument and doesn't look as clean.
162
163* Practical Currying
164
165Currying is not automatic in JavaScript, as in other languages
166
167External tools don't (currently) to statically analyse curried
168functions
169
170Solution: Don't expose curried functions
171Instead, write functions as if currying were automatic
172
173* Functional composition
174
175Creating functions from other functions
176
177Usually provided by =compose= (right-to-left) and =pipe= (left-to-right)
178
179A very simple definition of =compose= for only two functions would look like this
180
181#+BEGIN_SRC js
182function compose (f, g) {
183 return function (...args) {
184 return f(g(...args))
185 }
186}
187#+END_SRC
188
189* Functional composition (cont.)
190
191#+BEGIN_SRC js
192const plusOne = x => x + 1
193const timesTwo = x => x * 2
194
195const plusOneTimesTwo = compose(timesTwo, plusOne)
196const timesTwoPlusOne = compose(plusOne, timesTwo)
197
198nextDoubled(3) // = (3 + 1) * 2 = 8
199timesTwoPlusOne(3) // = (3 * 2) + 1 = 7
200#+END_SRC
201
202* pipe
203
204What about =pipe=?
205
206=pipe= does the same thing, but runs the functions the other way around
207
208=pipe(f, g)= is the same as =compose(g, f)=
209
210* Point-free programming
211
212With currying and higher-order functions, we (often) don't need to declare function arguments
213
214#+BEGIN_SRC js
215const modulo = a => b => b % a
216const eq = a => b => a === b
217
218const isEven = x => eq(0)(modulo(2)(x))
219const isEvenPointFree = compose(eq(0), modulo(2))
220#+END_SRC
221
222* Further Resources
223
224- [[https://drboolean.gitbooks.io/mostly-adequate-guide/content/][Mostly adequate guide to FP (in javascript)]]
225- [[http://ramdajs.com/][Ramda]], a general-purpose FP library
226- [[https://sanctuary.js.org/][Sanctuary]], a JavaScript library for Haskellers
diff --git a/static/talks/fp-js/s5-blank.html b/static/talks/fp-js/s5-blank.html new file mode 100644 index 0000000..0d126c7 --- /dev/null +++ b/static/talks/fp-js/s5-blank.html
@@ -0,0 +1,50 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
4<html xmlns="http://www.w3.org/1999/xhtml">
5
6<head>
7<title>Functional Programming</title>
8<!-- metadata -->
9<meta name="generator" content="S5" />
10<meta name="version" content="S5 1.1" />
11<meta name="presdate" content="20050728" />
12<meta name="author" content="Eric A. Meyer" />
13<meta name="company" content="Complex Spiral Consulting" />
14<!-- configuration parameters -->
15<meta name="defaultView" content="slideshow" />
16<meta name="controlVis" content="hidden" />
17<!-- style sheet links -->
18<link rel="stylesheet" href="ui/default/slides.css" type="text/css" media="projection" id="slideProj" />
19<link rel="stylesheet" href="ui/default/outline.css" type="text/css" media="screen" id="outlineStyle" />
20<link rel="stylesheet" href="ui/default/print.css" type="text/css" media="print" id="slidePrint" />
21<link rel="stylesheet" href="ui/default/opera.css" type="text/css" media="projection" id="operaFix" />
22<!-- S5 JS -->
23<script src="ui/default/slides.js" type="text/javascript"></script>
24</head>
25<body>
26
27<div class="layout">
28<div id="controls"><!-- DO NOT EDIT --></div>
29<div id="currentSlide"><!-- DO NOT EDIT --></div>
30<div id="header"></div>
31<div id="footer">
32<h1>Functional Programming</h1>
33</div>
34
35</div>
36
37
38<div class="presentation">
39
40<div class="slide">
41<h1>Functional Programming</h1>
42<h2>in JavaScript</h2>
43<h3>Alan Pearce</h3>
44</div>
45
46
47</div>
48
49</body>
50</html>
diff --git a/static/talks/fp-js/ui/default/blank.gif b/static/talks/fp-js/ui/default/blank.gif new file mode 100644 index 0000000..75b945d --- /dev/null +++ b/static/talks/fp-js/ui/default/blank.gif
Binary files differ
diff --git a/static/talks/fp-js/ui/default/bodybg.gif b/static/talks/fp-js/ui/default/bodybg.gif new file mode 100755 index 0000000..5f448a1 --- /dev/null +++ b/static/talks/fp-js/ui/default/bodybg.gif
Binary files differ
diff --git a/static/talks/fp-js/ui/default/framing.css b/static/talks/fp-js/ui/default/framing.css new file mode 100644 index 0000000..14d8509 --- /dev/null +++ b/static/talks/fp-js/ui/default/framing.css
@@ -0,0 +1,23 @@
1/* The following styles size, place, and layer the slide components.
2 Edit these if you want to change the overall slide layout.
3 The commented lines can be uncommented (and modified, if necessary)
4 to help you with the rearrangement process. */
5
6/* target = 1024x768 */
7
8div#header, div#footer, .slide {width: 100%; top: 0; left: 0;}
9div#header {top: 0; height: 3em; z-index: 1;}
10div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;}
11.slide {top: 0; width: 92%; padding: 3.5em 4% 4%; z-index: 2; list-style: none;}
12div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;}
13div#controls form {position: absolute; bottom: 0; right: 0; width: 100%;
14 margin: 0;}
15#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; z-index: 10;}
16html>body #currentSlide {position: fixed;}
17
18/*
19div#header {background: #FCC;}
20div#footer {background: #CCF;}
21div#controls {background: #BBD;}
22div#currentSlide {background: #FFC;}
23*/
diff --git a/static/talks/fp-js/ui/default/iepngfix.htc b/static/talks/fp-js/ui/default/iepngfix.htc new file mode 100644 index 0000000..bba2db7 --- /dev/null +++ b/static/talks/fp-js/ui/default/iepngfix.htc
@@ -0,0 +1,42 @@
1<public:component>
2<public:attach event="onpropertychange" onevent="doFix()" />
3
4<script>
5
6// IE5.5+ PNG Alpha Fix v1.0 by Angus Turnbull http://www.twinhelix.com
7// Free usage permitted as long as this notice remains intact.
8
9// This must be a path to a blank image. That's all the configuration you need here.
10var blankImg = 'ui/default/blank.gif';
11
12var f = 'DXImageTransform.Microsoft.AlphaImageLoader';
13
14function filt(s, m) {
15 if (filters[f]) {
16 filters[f].enabled = s ? true : false;
17 if (s) with (filters[f]) { src = s; sizingMethod = m }
18 } else if (s) style.filter = 'progid:'+f+'(src="'+s+'",sizingMethod="'+m+'")';
19}
20
21function doFix() {
22 if ((parseFloat(navigator.userAgent.match(/MSIE (\S+)/)[1]) < 5.5) ||
23 (event && !/(background|src)/.test(event.propertyName))) return;
24
25 if (tagName == 'IMG') {
26 if ((/\.png$/i).test(src)) {
27 filt(src, 'image'); // was 'scale'
28 src = blankImg;
29 } else if (src.indexOf(blankImg) < 0) filt();
30 } else if (style.backgroundImage) {
31 if (style.backgroundImage.match(/^url[("']+(.*\.png)[)"']+$/i)) {
32 var s = RegExp.$1;
33 style.backgroundImage = '';
34 filt(s, 'crop');
35 } else filt();
36 }
37}
38
39doFix();
40
41</script>
42</public:component> \ No newline at end of file
diff --git a/static/talks/fp-js/ui/default/opera.css b/static/talks/fp-js/ui/default/opera.css new file mode 100644 index 0000000..9e9d2a3 --- /dev/null +++ b/static/talks/fp-js/ui/default/opera.css
@@ -0,0 +1,7 @@
1/* DO NOT CHANGE THESE unless you really want to break Opera Show */
2.slide {
3 visibility: visible !important;
4 position: static !important;
5 page-break-before: always;
6}
7#slide0 {page-break-before: avoid;}
diff --git a/static/talks/fp-js/ui/default/outline.css b/static/talks/fp-js/ui/default/outline.css new file mode 100644 index 0000000..62db519 --- /dev/null +++ b/static/talks/fp-js/ui/default/outline.css
@@ -0,0 +1,15 @@
1/* don't change this unless you want the layout stuff to show up in the outline view! */
2
3.layout div, #footer *, #controlForm * {display: none;}
4#footer, #controls, #controlForm, #navLinks, #toggle {
5 display: block; visibility: visible; margin: 0; padding: 0;}
6#toggle {float: right; padding: 0.5em;}
7html>body #toggle {position: fixed; top: 0; right: 0;}
8
9/* making the outline look pretty-ish */
10
11#slide0 h1, #slide0 h2, #slide0 h3, #slide0 h4 {border: none; margin: 0;}
12#slide0 h1 {padding-top: 1.5em;}
13.slide h1 {margin: 1.5em 0 0; padding-top: 0.25em;
14 border-top: 1px solid #888; border-bottom: 1px solid #AAA;}
15#toggle {border: 1px solid; border-width: 0 0 1px 1px; background: #FFF;}
diff --git a/static/talks/fp-js/ui/default/pretty.css b/static/talks/fp-js/ui/default/pretty.css new file mode 100644 index 0000000..3d3acef --- /dev/null +++ b/static/talks/fp-js/ui/default/pretty.css
@@ -0,0 +1,86 @@
1/* Following are the presentation styles -- edit away! */
2
3body {background: #FFF url(bodybg.gif) -16px 0 no-repeat; color: #000; font-size: 2em;}
4:link, :visited {text-decoration: none; color: #00C;}
5#controls :active {color: #88A !important;}
6#controls :focus {outline: 1px dotted #227;}
7h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;}
8ul, pre {margin: 0; line-height: 1em;}
9html, body {margin: 0; padding: 0;}
10
11blockquote, q {font-style: italic;}
12blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em; text-align: center; font-size: 1em;}
13blockquote p {margin: 0;}
14blockquote i {font-style: normal;}
15blockquote b {display: block; margin-top: 0.5em; font-weight: normal; font-size: smaller; font-style: normal;}
16blockquote b i {font-style: italic;}
17
18kbd {font-weight: bold; font-size: 1em;}
19sup {font-size: smaller; line-height: 1px;}
20
21.slide code {padding: 2px 0.25em; font-weight: bold; color: #533;}
22.slide code.bad, code del {color: red;}
23.slide code.old {color: silver;}
24.slide pre {padding: 0; margin: 0.25em 0 0.5em 0.5em; color: #533; font-size: 90%;}
25.slide pre code {display: block;}
26.slide ul {margin-left: 5%; margin-right: 7%; list-style: disc;}
27.slide li {margin-top: 0.75em; margin-right: 0;}
28.slide ul ul {line-height: 1;}
29.slide ul ul li {margin: .2em; font-size: 85%; list-style: square;}
30.slide img.leader {display: block; margin: 0 auto;}
31
32div#header, div#footer {background: #005; color: #AAB;
33 font-family: Verdana, Helvetica, sans-serif;}
34div#header {background: #005 url(bodybg.gif) -16px 0 no-repeat;
35 line-height: 1px;}
36div#footer {font-size: 0.5em; font-weight: bold; padding: 1em 0;}
37#footer h1, #footer h2 {display: block; padding: 0 1em;}
38#footer h2 {font-style: italic;}
39
40div.long {font-size: 0.75em;}
41.slide h1 {position: absolute; top: 0.7em; left: 87px; z-index: 1;
42 margin: 0; padding: 0.3em 0 0 50px; white-space: nowrap;
43 font: bold 150%/1em Helvetica, sans-serif; text-transform: capitalize;
44 color: #DDE; background: #005;}
45.slide h3 {font-size: 130%;}
46h1 abbr {font-variant: small-caps;}
47
48div#controls {position: absolute; left: 50%; bottom: 0;
49 width: 50%;
50 text-align: right; font: bold 0.9em Verdana, Helvetica, sans-serif;}
51html>body div#controls {position: fixed; padding: 0 0 1em 0;
52 top: auto;}
53div#controls form {position: absolute; bottom: 0; right: 0; width: 100%;
54 margin: 0; padding: 0;}
55#controls #navLinks a {padding: 0; margin: 0 0.5em;
56 background: #005; border: none; color: #779;
57 cursor: pointer;}
58#controls #navList {height: 1em;}
59#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; background: #DDD; color: #227;}
60
61#currentSlide {text-align: center; font-size: 0.5em; color: #449;}
62
63#slide0 {padding-top: 3.5em; font-size: 90%;}
64#slide0 h1 {position: static; margin: 1em 0 0; padding: 0;
65 font: bold 2em Helvetica, sans-serif; white-space: normal;
66 color: #000; background: transparent;}
67#slide0 h2 {font: bold italic 1em Helvetica, sans-serif; margin: 0.25em;}
68#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;}
69#slide0 h4 {margin-top: 0; font-size: 1em;}
70
71ul.urls {list-style: none; display: inline; margin: 0;}
72.urls li {display: inline; margin: 0;}
73.note {display: none;}
74.external {border-bottom: 1px dotted gray;}
75html>body .external {border-bottom: none;}
76.external:after {content: " \274F"; font-size: smaller; color: #77B;}
77
78.incremental, .incremental *, .incremental *:after {color: #DDE; visibility: visible;}
79img.incremental {visibility: hidden;}
80.slide .current {color: #B02;}
81
82
83/* diagnostics
84
85li:after {content: " [" attr(class) "]"; color: #F88;}
86 */ \ No newline at end of file
diff --git a/static/talks/fp-js/ui/default/print.css b/static/talks/fp-js/ui/default/print.css new file mode 100644 index 0000000..e7a71d1 --- /dev/null +++ b/static/talks/fp-js/ui/default/print.css
@@ -0,0 +1 @@
/* The following rule is necessary to have all slides appear in print! DO NOT REMOVE IT! */ .slide, ul {page-break-inside: avoid; visibility: visible !important;} h1 {page-break-after: avoid;} body {font-size: 12pt; background: white;} * {color: black;} #slide0 h1 {font-size: 200%; border: none; margin: 0.5em 0 0.25em;} #slide0 h3 {margin: 0; padding: 0;} #slide0 h4 {margin: 0 0 0.5em; padding: 0;} #slide0 {margin-bottom: 3em;} h1 {border-top: 2pt solid gray; border-bottom: 1px dotted silver;} .extra {background: transparent !important;} div.extra, pre.extra, .example {font-size: 10pt; color: #333;} ul.extra a {font-weight: bold;} p.example {display: none;} #header {display: none;} #footer h1 {margin: 0; border-bottom: 1px solid; color: gray; font-style: italic;} #footer h2, #controls {display: none;} /* The following rule keeps the layout stuff out of print. Remove at your own risk! */ .layout, .layout * {display: none !important;} \ No newline at end of file
diff --git a/static/talks/fp-js/ui/default/s5-core.css b/static/talks/fp-js/ui/default/s5-core.css new file mode 100644 index 0000000..86444e0 --- /dev/null +++ b/static/talks/fp-js/ui/default/s5-core.css
@@ -0,0 +1,9 @@
1/* Do not edit or override these styles! The system will likely break if you do. */
2
3div#header, div#footer, div#controls, .slide {position: absolute;}
4html>body div#header, html>body div#footer,
5 html>body div#controls, html>body .slide {position: fixed;}
6.handout {display: none;}
7.layout {display: block;}
8.slide, .hideme, .incremental {visibility: hidden;}
9#slide0 {visibility: visible;}
diff --git a/static/talks/fp-js/ui/default/slides.css b/static/talks/fp-js/ui/default/slides.css new file mode 100644 index 0000000..0786d7d --- /dev/null +++ b/static/talks/fp-js/ui/default/slides.css
@@ -0,0 +1,3 @@
1@import url(s5-core.css); /* required to make the slide show run at all */
2@import url(framing.css); /* sets basic placement and size of slide components */
3@import url(pretty.css); /* stuff that makes the slides look better than blah */ \ No newline at end of file
diff --git a/static/talks/fp-js/ui/default/slides.js b/static/talks/fp-js/ui/default/slides.js new file mode 100644 index 0000000..38fe853 --- /dev/null +++ b/static/talks/fp-js/ui/default/slides.js
@@ -0,0 +1,553 @@
1// S5 v1.1 slides.js -- released into the Public Domain
2//
3// Please see http://www.meyerweb.com/eric/tools/s5/credits.html for information
4// about all the wonderful and talented contributors to this code!
5
6var undef;
7var slideCSS = '';
8var snum = 0;
9var smax = 1;
10var incpos = 0;
11var number = undef;
12var s5mode = true;
13var defaultView = 'slideshow';
14var controlVis = 'visible';
15
16var isIE = navigator.appName == 'Microsoft Internet Explorer' && navigator.userAgent.indexOf('Opera') < 1 ? 1 : 0;
17var isOp = navigator.userAgent.indexOf('Opera') > -1 ? 1 : 0;
18var isGe = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('Safari') < 1 ? 1 : 0;
19
20function hasClass(object, className) {
21 if (!object.className) return false;
22 return (object.className.search('(^|\\s)' + className + '(\\s|$)') != -1);
23}
24
25function hasValue(object, value) {
26 if (!object) return false;
27 return (object.search('(^|\\s)' + value + '(\\s|$)') != -1);
28}
29
30function removeClass(object,className) {
31 if (!object) return;
32 object.className = object.className.replace(new RegExp('(^|\\s)'+className+'(\\s|$)'), RegExp.$1+RegExp.$2);
33}
34
35function addClass(object,className) {
36 if (!object || hasClass(object, className)) return;
37 if (object.className) {
38 object.className += ' '+className;
39 } else {
40 object.className = className;
41 }
42}
43
44function GetElementsWithClassName(elementName,className) {
45 var allElements = document.getElementsByTagName(elementName);
46 var elemColl = new Array();
47 for (var i = 0; i< allElements.length; i++) {
48 if (hasClass(allElements[i], className)) {
49 elemColl[elemColl.length] = allElements[i];
50 }
51 }
52 return elemColl;
53}
54
55function isParentOrSelf(element, id) {
56 if (element == null || element.nodeName=='BODY') return false;
57 else if (element.id == id) return true;
58 else return isParentOrSelf(element.parentNode, id);
59}
60
61function nodeValue(node) {
62 var result = "";
63 if (node.nodeType == 1) {
64 var children = node.childNodes;
65 for (var i = 0; i < children.length; ++i) {
66 result += nodeValue(children[i]);
67 }
68 }
69 else if (node.nodeType == 3) {
70 result = node.nodeValue;
71 }
72 return(result);
73}
74
75function slideLabel() {
76 var slideColl = GetElementsWithClassName('*','slide');
77 var list = document.getElementById('jumplist');
78 smax = slideColl.length;
79 for (var n = 0; n < smax; n++) {
80 var obj = slideColl[n];
81
82 var did = 'slide' + n.toString();
83 obj.setAttribute('id',did);
84 if (isOp) continue;
85
86 var otext = '';
87 var menu = obj.firstChild;
88 if (!menu) continue; // to cope with empty slides
89 while (menu && menu.nodeType == 3) {
90 menu = menu.nextSibling;
91 }
92 if (!menu) continue; // to cope with slides with only text nodes
93
94 var menunodes = menu.childNodes;
95 for (var o = 0; o < menunodes.length; o++) {
96 otext += nodeValue(menunodes[o]);
97 }
98 list.options[list.length] = new Option(n + ' : ' + otext, n);
99 }
100}
101
102function currentSlide() {
103 var cs;
104 if (document.getElementById) {
105 cs = document.getElementById('currentSlide');
106 } else {
107 cs = document.currentSlide;
108 }
109 cs.innerHTML = '<span id="csHere">' + snum + '<\/span> ' +
110 '<span id="csSep">\/<\/span> ' +
111 '<span id="csTotal">' + (smax-1) + '<\/span>';
112 if (snum == 0) {
113 cs.style.visibility = 'hidden';
114 } else {
115 cs.style.visibility = 'visible';
116 }
117}
118
119function go(step) {
120 if (document.getElementById('slideProj').disabled || step == 0) return;
121 var jl = document.getElementById('jumplist');
122 var cid = 'slide' + snum;
123 var ce = document.getElementById(cid);
124 if (incrementals[snum].length > 0) {
125 for (var i = 0; i < incrementals[snum].length; i++) {
126 removeClass(incrementals[snum][i], 'current');
127 removeClass(incrementals[snum][i], 'incremental');
128 }
129 }
130 if (step != 'j') {
131 snum += step;
132 lmax = smax - 1;
133 if (snum > lmax) snum = lmax;
134 if (snum < 0) snum = 0;
135 } else
136 snum = parseInt(jl.value);
137 var nid = 'slide' + snum;
138 var ne = document.getElementById(nid);
139 if (!ne) {
140 ne = document.getElementById('slide0');
141 snum = 0;
142 }
143 if (step < 0) {incpos = incrementals[snum].length} else {incpos = 0;}
144 if (incrementals[snum].length > 0 && incpos == 0) {
145 for (var i = 0; i < incrementals[snum].length; i++) {
146 if (hasClass(incrementals[snum][i], 'current'))
147 incpos = i + 1;
148 else
149 addClass(incrementals[snum][i], 'incremental');
150 }
151 }
152 if (incrementals[snum].length > 0 && incpos > 0)
153 addClass(incrementals[snum][incpos - 1], 'current');
154 ce.style.visibility = 'hidden';
155 ne.style.visibility = 'visible';
156 jl.selectedIndex = snum;
157 currentSlide();
158 number = 0;
159}
160
161function goTo(target) {
162 if (target >= smax || target == snum) return;
163 go(target - snum);
164}
165
166function subgo(step) {
167 if (step > 0) {
168 removeClass(incrementals[snum][incpos - 1],'current');
169 removeClass(incrementals[snum][incpos], 'incremental');
170 addClass(incrementals[snum][incpos],'current');
171 incpos++;
172 } else {
173 incpos--;
174 removeClass(incrementals[snum][incpos],'current');
175 addClass(incrementals[snum][incpos], 'incremental');
176 addClass(incrementals[snum][incpos - 1],'current');
177 }
178}
179
180function toggle() {
181 var slideColl = GetElementsWithClassName('*','slide');
182 var slides = document.getElementById('slideProj');
183 var outline = document.getElementById('outlineStyle');
184 if (!slides.disabled) {
185 slides.disabled = true;
186 outline.disabled = false;
187 s5mode = false;
188 fontSize('1em');
189 for (var n = 0; n < smax; n++) {
190 var slide = slideColl[n];
191 slide.style.visibility = 'visible';
192 }
193 } else {
194 slides.disabled = false;
195 outline.disabled = true;
196 s5mode = true;
197 fontScale();
198 for (var n = 0; n < smax; n++) {
199 var slide = slideColl[n];
200 slide.style.visibility = 'hidden';
201 }
202 slideColl[snum].style.visibility = 'visible';
203 }
204}
205
206function showHide(action) {
207 var obj = GetElementsWithClassName('*','hideme')[0];
208 switch (action) {
209 case 's': obj.style.visibility = 'visible'; break;
210 case 'h': obj.style.visibility = 'hidden'; break;
211 case 'k':
212 if (obj.style.visibility != 'visible') {
213 obj.style.visibility = 'visible';
214 } else {
215 obj.style.visibility = 'hidden';
216 }
217 break;
218 }
219}
220
221// 'keys' code adapted from MozPoint (http://mozpoint.mozdev.org/)
222function keys(key) {
223 if (!key) {
224 key = event;
225 key.which = key.keyCode;
226 }
227 if (key.which == 84) {
228 toggle();
229 return;
230 }
231 if (s5mode) {
232 switch (key.which) {
233 case 10: // return
234 case 13: // enter
235 if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return;
236 if (key.target && isParentOrSelf(key.target, 'controls')) return;
237 if(number != undef) {
238 goTo(number);
239 break;
240 }
241 case 32: // spacebar
242 case 34: // page down
243 case 39: // rightkey
244 case 40: // downkey
245 if(number != undef) {
246 go(number);
247 } else if (!incrementals[snum] || incpos >= incrementals[snum].length) {
248 go(1);
249 } else {
250 subgo(1);
251 }
252 break;
253 case 33: // page up
254 case 37: // leftkey
255 case 38: // upkey
256 if(number != undef) {
257 go(-1 * number);
258 } else if (!incrementals[snum] || incpos <= 0) {
259 go(-1);
260 } else {
261 subgo(-1);
262 }
263 break;
264 case 36: // home
265 goTo(0);
266 break;
267 case 35: // end
268 goTo(smax-1);
269 break;
270 case 67: // c
271 showHide('k');
272 break;
273 }
274 if (key.which < 48 || key.which > 57) {
275 number = undef;
276 } else {
277 if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return;
278 if (key.target && isParentOrSelf(key.target, 'controls')) return;
279 number = (((number != undef) ? number : 0) * 10) + (key.which - 48);
280 }
281 }
282 return false;
283}
284
285function clicker(e) {
286 number = undef;
287 var target;
288 if (window.event) {
289 target = window.event.srcElement;
290 e = window.event;
291 } else target = e.target;
292 if (target.getAttribute('href') != null || hasValue(target.rel, 'external') || isParentOrSelf(target, 'controls') || isParentOrSelf(target,'embed') || isParentOrSelf(target,'object')) return true;
293 if (!e.which || e.which == 1) {
294 if (!incrementals[snum] || incpos >= incrementals[snum].length) {
295 go(1);
296 } else {
297 subgo(1);
298 }
299 }
300}
301
302function findSlide(hash) {
303 var target = null;
304 var slides = GetElementsWithClassName('*','slide');
305 for (var i = 0; i < slides.length; i++) {
306 var targetSlide = slides[i];
307 if ( (targetSlide.name && targetSlide.name == hash)
308 || (targetSlide.id && targetSlide.id == hash) ) {
309 target = targetSlide;
310 break;
311 }
312 }
313 while(target != null && target.nodeName != 'BODY') {
314 if (hasClass(target, 'slide')) {
315 return parseInt(target.id.slice(5));
316 }
317 target = target.parentNode;
318 }
319 return null;
320}
321
322function slideJump() {
323 if (window.location.hash == null) return;
324 var sregex = /^#slide(\d+)$/;
325 var matches = sregex.exec(window.location.hash);
326 var dest = null;
327 if (matches != null) {
328 dest = parseInt(matches[1]);
329 } else {
330 dest = findSlide(window.location.hash.slice(1));
331 }
332 if (dest != null)
333 go(dest - snum);
334}
335
336function fixLinks() {
337 var thisUri = window.location.href;
338 thisUri = thisUri.slice(0, thisUri.length - window.location.hash.length);
339 var aelements = document.getElementsByTagName('A');
340 for (var i = 0; i < aelements.length; i++) {
341 var a = aelements[i].href;
342 var slideID = a.match('\#slide[0-9]{1,2}');
343 if ((slideID) && (slideID[0].slice(0,1) == '#')) {
344 var dest = findSlide(slideID[0].slice(1));
345 if (dest != null) {
346 if (aelements[i].addEventListener) {
347 aelements[i].addEventListener("click", new Function("e",
348 "if (document.getElementById('slideProj').disabled) return;" +
349 "go("+dest+" - snum); " +
350 "if (e.preventDefault) e.preventDefault();"), true);
351 } else if (aelements[i].attachEvent) {
352 aelements[i].attachEvent("onclick", new Function("",
353 "if (document.getElementById('slideProj').disabled) return;" +
354 "go("+dest+" - snum); " +
355 "event.returnValue = false;"));
356 }
357 }
358 }
359 }
360}
361
362function externalLinks() {
363 if (!document.getElementsByTagName) return;
364 var anchors = document.getElementsByTagName('a');
365 for (var i=0; i<anchors.length; i++) {
366 var anchor = anchors[i];
367 if (anchor.getAttribute('href') && hasValue(anchor.rel, 'external')) {
368 anchor.target = '_blank';
369 addClass(anchor,'external');
370 }
371 }
372}
373
374function createControls() {
375 var controlsDiv = document.getElementById("controls");
376 if (!controlsDiv) return;
377 var hider = ' onmouseover="showHide(\'s\');" onmouseout="showHide(\'h\');"';
378 var hideDiv, hideList = '';
379 if (controlVis == 'hidden') {
380 hideDiv = hider;
381 } else {
382 hideList = hider;
383 }
384 controlsDiv.innerHTML = '<form action="#" id="controlForm"' + hideDiv + '>' +
385 '<div id="navLinks">' +
386 '<a accesskey="t" id="toggle" href="javascript:toggle();">&#216;<\/a>' +
387 '<a accesskey="z" id="prev" href="javascript:go(-1);">&laquo;<\/a>' +
388 '<a accesskey="x" id="next" href="javascript:go(1);">&raquo;<\/a>' +
389 '<div id="navList"' + hideList + '><select id="jumplist" onchange="go(\'j\');"><\/select><\/div>' +
390 '<\/div><\/form>';
391 if (controlVis == 'hidden') {
392 var hidden = document.getElementById('navLinks');
393 } else {
394 var hidden = document.getElementById('jumplist');
395 }
396 addClass(hidden,'hideme');
397}
398
399function fontScale() { // causes layout problems in FireFox that get fixed if browser's Reload is used; same may be true of other Gecko-based browsers
400 if (!s5mode) return false;
401 var vScale = 22; // both yield 32 (after rounding) at 1024x768
402 var hScale = 32; // perhaps should auto-calculate based on theme's declared value?
403 if (window.innerHeight) {
404 var vSize = window.innerHeight;
405 var hSize = window.innerWidth;
406 } else if (document.documentElement.clientHeight) {
407 var vSize = document.documentElement.clientHeight;
408 var hSize = document.documentElement.clientWidth;
409 } else if (document.body.clientHeight) {
410 var vSize = document.body.clientHeight;
411 var hSize = document.body.clientWidth;
412 } else {
413 var vSize = 700; // assuming 1024x768, minus chrome and such
414 var hSize = 1024; // these do not account for kiosk mode or Opera Show
415 }
416 var newSize = Math.min(Math.round(vSize/vScale),Math.round(hSize/hScale));
417 fontSize(newSize + 'px');
418 if (isGe) { // hack to counter incremental reflow bugs
419 var obj = document.getElementsByTagName('body')[0];
420 obj.style.display = 'none';
421 obj.style.display = 'block';
422 }
423}
424
425function fontSize(value) {
426 if (!(s5ss = document.getElementById('s5ss'))) {
427 if (!isIE) {
428 document.getElementsByTagName('head')[0].appendChild(s5ss = document.createElement('style'));
429 s5ss.setAttribute('media','screen, projection');
430 s5ss.setAttribute('id','s5ss');
431 } else {
432 document.createStyleSheet();
433 document.s5ss = document.styleSheets[document.styleSheets.length - 1];
434 }
435 }
436 if (!isIE) {
437 while (s5ss.lastChild) s5ss.removeChild(s5ss.lastChild);
438 s5ss.appendChild(document.createTextNode('body {font-size: ' + value + ' !important;}'));
439 } else {
440 document.s5ss.addRule('body','font-size: ' + value + ' !important;');
441 }
442}
443
444function notOperaFix() {
445 slideCSS = document.getElementById('slideProj').href;
446 var slides = document.getElementById('slideProj');
447 var outline = document.getElementById('outlineStyle');
448 slides.setAttribute('media','screen');
449 outline.disabled = true;
450 if (isGe) {
451 slides.setAttribute('href','null'); // Gecko fix
452 slides.setAttribute('href',slideCSS); // Gecko fix
453 }
454 if (isIE && document.styleSheets && document.styleSheets[0]) {
455 document.styleSheets[0].addRule('img', 'behavior: url(ui/default/iepngfix.htc)');
456 document.styleSheets[0].addRule('div', 'behavior: url(ui/default/iepngfix.htc)');
457 document.styleSheets[0].addRule('.slide', 'behavior: url(ui/default/iepngfix.htc)');
458 }
459}
460
461function getIncrementals(obj) {
462 var incrementals = new Array();
463 if (!obj)
464 return incrementals;
465 var children = obj.childNodes;
466 for (var i = 0; i < children.length; i++) {
467 var child = children[i];
468 if (hasClass(child, 'incremental')) {
469 if (child.nodeName == 'OL' || child.nodeName == 'UL') {
470 removeClass(child, 'incremental');
471 for (var j = 0; j < child.childNodes.length; j++) {
472 if (child.childNodes[j].nodeType == 1) {
473 addClass(child.childNodes[j], 'incremental');
474 }
475 }
476 } else {
477 incrementals[incrementals.length] = child;
478 removeClass(child,'incremental');
479 }
480 }
481 if (hasClass(child, 'show-first')) {
482 if (child.nodeName == 'OL' || child.nodeName == 'UL') {
483 removeClass(child, 'show-first');
484 if (child.childNodes[isGe].nodeType == 1) {
485 removeClass(child.childNodes[isGe], 'incremental');
486 }
487 } else {
488 incrementals[incrementals.length] = child;
489 }
490 }
491 incrementals = incrementals.concat(getIncrementals(child));
492 }
493 return incrementals;
494}
495
496function createIncrementals() {
497 var incrementals = new Array();
498 for (var i = 0; i < smax; i++) {
499 incrementals[i] = getIncrementals(document.getElementById('slide'+i));
500 }
501 return incrementals;
502}
503
504function defaultCheck() {
505 var allMetas = document.getElementsByTagName('meta');
506 for (var i = 0; i< allMetas.length; i++) {
507 if (allMetas[i].name == 'defaultView') {
508 defaultView = allMetas[i].content;
509 }
510 if (allMetas[i].name == 'controlVis') {
511 controlVis = allMetas[i].content;
512 }
513 }
514}
515
516// Key trap fix, new function body for trap()
517function trap(e) {
518 if (!e) {
519 e = event;
520 e.which = e.keyCode;
521 }
522 try {
523 modifierKey = e.ctrlKey || e.altKey || e.metaKey;
524 }
525 catch(e) {
526 modifierKey = false;
527 }
528 return modifierKey || e.which == 0;
529}
530
531function startup() {
532 defaultCheck();
533 if (!isOp)
534 createControls();
535 slideLabel();
536 fixLinks();
537 externalLinks();
538 fontScale();
539 if (!isOp) {
540 notOperaFix();
541 incrementals = createIncrementals();
542 slideJump();
543 if (defaultView == 'outline') {
544 toggle();
545 }
546 document.onkeyup = keys;
547 document.onkeypress = trap;
548 document.onclick = clicker;
549 }
550}
551
552window.onload = startup;
553window.onresize = function(){setTimeout('fontScale()', 50);} \ No newline at end of file
diff --git a/themes/xmin/.gitignore b/themes/xmin/.gitignore new file mode 100644 index 0000000..ce130a0 --- /dev/null +++ b/themes/xmin/.gitignore
@@ -0,0 +1,5 @@
1.Rproj.user
2.Rhistory
3.RData
4.Ruserdata
5exampleSite/public
diff --git a/LICENSE.md b/themes/xmin/LICENSE.md index fa77e18..fa77e18 100644 --- a/LICENSE.md +++ b/themes/xmin/LICENSE.md
diff --git a/README.md b/themes/xmin/README.md index 3cd9a40..3cd9a40 100644 --- a/README.md +++ b/themes/xmin/README.md
diff --git a/archetypes/default.md b/themes/xmin/archetypes/default.md index fb98e92..fb98e92 100644 --- a/archetypes/default.md +++ b/themes/xmin/archetypes/default.md
diff --git a/exampleSite/config.toml b/themes/xmin/exampleSite/config.toml index 7147426..7147426 100644 --- a/exampleSite/config.toml +++ b/themes/xmin/exampleSite/config.toml
diff --git a/exampleSite/content/_index.Rmarkdown b/themes/xmin/exampleSite/content/_index.Rmarkdown index 9819e5d..9819e5d 100644 --- a/exampleSite/content/_index.Rmarkdown +++ b/themes/xmin/exampleSite/content/_index.Rmarkdown
diff --git a/exampleSite/content/_index.markdown b/themes/xmin/exampleSite/content/_index.markdown index ac2271c..ac2271c 100644 --- a/exampleSite/content/_index.markdown +++ b/themes/xmin/exampleSite/content/_index.markdown
diff --git a/exampleSite/content/about.md b/themes/xmin/exampleSite/content/about.md index dd83942..dd83942 100644 --- a/exampleSite/content/about.md +++ b/themes/xmin/exampleSite/content/about.md
diff --git a/exampleSite/content/note/2017-06-13-a-quick-note.md b/themes/xmin/exampleSite/content/note/2017-06-13-a-quick-note.md index 9d855a4..9d855a4 100644 --- a/exampleSite/content/note/2017-06-13-a-quick-note.md +++ b/themes/xmin/exampleSite/content/note/2017-06-13-a-quick-note.md
diff --git a/exampleSite/content/note/2017-06-14-another-note.md b/themes/xmin/exampleSite/content/note/2017-06-14-another-note.md index 16f41de..16f41de 100644 --- a/exampleSite/content/note/2017-06-14-another-note.md +++ b/themes/xmin/exampleSite/content/note/2017-06-14-another-note.md
diff --git a/exampleSite/content/post/2015-07-23-lorem-ipsum.md b/themes/xmin/exampleSite/content/post/2015-07-23-lorem-ipsum.md index ef58622..ef58622 100644 --- a/exampleSite/content/post/2015-07-23-lorem-ipsum.md +++ b/themes/xmin/exampleSite/content/post/2015-07-23-lorem-ipsum.md
diff --git a/exampleSite/content/post/2016-02-14-hello-markdown.md b/themes/xmin/exampleSite/content/post/2016-02-14-hello-markdown.md index e06e31a..e06e31a 100644 --- a/exampleSite/content/post/2016-02-14-hello-markdown.md +++ b/themes/xmin/exampleSite/content/post/2016-02-14-hello-markdown.md
diff --git a/exampleSite/layouts/partials/foot_custom.html b/themes/xmin/exampleSite/layouts/partials/foot_custom.html index 270a44d..270a44d 100644 --- a/exampleSite/layouts/partials/foot_custom.html +++ b/themes/xmin/exampleSite/layouts/partials/foot_custom.html
diff --git a/hugo-xmin.Rproj b/themes/xmin/hugo-xmin.Rproj index d64e28b..d64e28b 100644 --- a/hugo-xmin.Rproj +++ b/themes/xmin/hugo-xmin.Rproj
diff --git a/images/screenshot.png b/themes/xmin/images/screenshot.png index 3df0a14..3df0a14 100644 --- a/images/screenshot.png +++ b/themes/xmin/images/screenshot.png
Binary files differ
diff --git a/images/tn.png b/themes/xmin/images/tn.png index 389a20c..389a20c 100644 --- a/images/tn.png +++ b/themes/xmin/images/tn.png
Binary files differ
diff --git a/layouts/404.html b/themes/xmin/layouts/404.html index c2e4e40..c2e4e40 100644 --- a/layouts/404.html +++ b/themes/xmin/layouts/404.html
diff --git a/layouts/_default/list.html b/themes/xmin/layouts/_default/list.html index 06b290a..06b290a 100644 --- a/layouts/_default/list.html +++ b/themes/xmin/layouts/_default/list.html
diff --git a/layouts/_default/single.html b/themes/xmin/layouts/_default/single.html index de3f121..de3f121 100644 --- a/layouts/_default/single.html +++ b/themes/xmin/layouts/_default/single.html
diff --git a/layouts/_default/terms.html b/themes/xmin/layouts/_default/terms.html index 71f47e7..71f47e7 100644 --- a/layouts/_default/terms.html +++ b/themes/xmin/layouts/_default/terms.html
diff --git a/layouts/partials/foot_custom.html b/themes/xmin/layouts/partials/foot_custom.html index e69de29..e69de29 100644 --- a/layouts/partials/foot_custom.html +++ b/themes/xmin/layouts/partials/foot_custom.html
diff --git a/layouts/partials/footer.html b/themes/xmin/layouts/partials/footer.html index 3f46ea5..3f46ea5 100644 --- a/layouts/partials/footer.html +++ b/themes/xmin/layouts/partials/footer.html
diff --git a/layouts/partials/head_custom.html b/themes/xmin/layouts/partials/head_custom.html index e69de29..e69de29 100644 --- a/layouts/partials/head_custom.html +++ b/themes/xmin/layouts/partials/head_custom.html
diff --git a/layouts/partials/header.html b/themes/xmin/layouts/partials/header.html index 4f431eb..4f431eb 100644 --- a/layouts/partials/header.html +++ b/themes/xmin/layouts/partials/header.html
diff --git a/static/css/fonts.css b/themes/xmin/static/css/fonts.css index 8ffcecd..8ffcecd 100644 --- a/static/css/fonts.css +++ b/themes/xmin/static/css/fonts.css
diff --git a/static/css/style.css b/themes/xmin/static/css/style.css index 4dc3ae4..4dc3ae4 100644 --- a/static/css/style.css +++ b/themes/xmin/static/css/style.css
diff --git a/theme.toml b/themes/xmin/theme.toml index 8733afd..8733afd 100644 --- a/theme.toml +++ b/themes/xmin/theme.toml