Merge branch 'master' into develop

This commit is contained in:
Simon Brooke 2020-02-19 21:08:43 +00:00
commit c3f00a18fe
No known key found for this signature in database
GPG key ID: A7A4F18D1D4DF987
29 changed files with 265 additions and 309 deletions

184
README.md
View file

@ -1,187 +1,39 @@
![One wiki to rule them all](https://www.weft.scot/images/smeagol.png) ![One wiki to rule them all](https://www.weft.scot/images/smeagol.png)
# Welcome to Smeagol! # Welcome to Smeagol!
Smeagol is a simple Wiki engine inspired by [Gollum](https://github.com/gollum/gollum/wiki). Gollum is a Wiki engine written in Ruby, which uses a number of simple text formats including [Markdown](http://daringfireball.net/projects/markdown/), and which uses [Git](http://git-scm.com/) to provide versioning and backup. I needed a new Wiki for a project and thought Gollum would be ideal - but unfortunately it doesn't provide user authentication, which I needed, and it was simpler for me to reimplement the bits I did need in Clojure than to modify Gollum. Smeagol is a hackable, extensible Wiki engine which is reasonably user-friendly. It uses [Markdown](http://daringfireball.net/projects/markdown/) as its text format, and [Git](http://git-scm.com/) to provide versioning and backup.
So at this stage Smeagol is a Wiki engine written in Clojure which uses Markdown as its text format, which does have user authentication, and which uses Git as its versioning and backup system. ## Using Smeagol
Read the [[User Documentation]] for an introduction to all Smeagol's features.
<a href="https://zenhub.com"><img src="https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png"></a>
## Status
Smeagol is now a fully working small Wiki engine, and meets my own immediate needs.
## Markup syntax ## Markup syntax
Smeagol uses the Markdown format as provided by [markdown-clj](https://github.com/yogthos/markdown-clj), with the addition that anything enclosed in double square brackets, \[\[like this\]\], will be treated as a link into the wiki itself. Smeagol uses the Markdown format as provided by [markdown-clj](https://github.com/yogthos/markdown-clj), with the addition that anything enclosed in double square brackets, \[\[like this\]\], will be treated as a link into the wiki itself. The markdown format is extensible, and has extensions already for inclusions, for data visualisations and for picture galleries. Read more about [[Extensible Markup]].
### Pluggable extensible markup
A system of pluggable, extensible formatters is supported. In normal markdown, code blocks may be delimited by three backticks at start and end, and often the syntax of the code can be indicated by a token immediately following the opening three backticks. This has been extended to allow custom formatters to be provided for such code blocks. Two example formatters are provided:
#### The Vega formatter
Inspired by [visdown](http://visdown.amitkaps.com/) and [vega-lite](https://vega.github.io/vega-lite/docs/), the Vega formatter allows you to embed vega data visualisations into Smeagol pages. The graph description should start with a line comprising three back-ticks and then the word '`vega`', and end with a line comprising just three backticks.
Here's an example cribbed in its entirety from [here](http://visdown.amitkaps.com/london):
##### Flight punctuality at London airports
```vega
data:
url: "data/london.csv"
transform:
-
filter: datum.year == 2016
mark: rect
encoding:
x:
type: nominal
field: source
y:
type: nominal
field: dest
color:
type: quantitative
field: flights
aggregate: sum
```
Data files can be uploaded in the same way as images, by using the **upload a file** link.
Note that this visualisation will not be rendered in the GitHub wiki, as it doesn't have Smeagol's data visualisation magic. This is what it should look like:
![Example visualisation](https://github.com/simon-brooke/smeagol/blob/develop/resources/public/data/london.png?raw=true)
#### The Mermaid formatter
Graphs can now be embedded in a page using the [Mermaid](http://knsv.github.io/mermaid/index.html) graph description language. The graph description should start with a line comprising three back-ticks and then the word `mermaid`, and end with a line comprising just three backticks.
Here's an example culled from the Mermaid documentation.
##### GANTT Chart
```mermaid
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :1d
```
To add your own formatter, compile it into a jar file which is on the classpath - it does *not* have to be part of the Smeagol project directly, and then edit the value of the key `:formatters` in the file `config.edn`; whose standard definition is:
:formatters {"vega" smeagol.formatting/process-vega
"vis" smeagol.formatting/process-vega
"mermaid" smeagol.formatting/process-mermaid}
The added key should be the word which will follow the opening three backticks of your code block, and the value of that key should be a symbol which evaluates to a function which can format the code block as required.
## Security and authentication ## Security and authentication
Security is now greatly improved. There is a file called *passwd* in the *resources* directory, which contains a clojure map which maps usernames to maps with plain-text passwords and emails thus: Smeagol now has good security and authentication. While the initial password supplied with the system is not encrypted, when it is changed it will be; and passwords for new users added through the user administration pages are encrypted. Read more about [[Security and authentication]].
{:admin {:password "admin" :email "admin@localhost" :admin true} ## Internationalisation
:adam {:password "secret" :email "adam@localhost"}} Smeagol has built in internationalisation. Currently it has translation files for English, German, Lithuanian and Russian. We'd welcome volunteers to translate it into other languages.
that is to say, the username is a keyword and the corresponding password is a string. However, since version 0.5.0, users can now change their own passwords, and when the user changes their password their new password is encrypted using the [scrypt](http://www.tarsnap.com/scrypt.html) one-way encryption scheme. The password file is now no longer either in the *resources/public* directory so cannot be downloaded through the browser, nor in the git archive to which the Wiki content is stored, so that even if that git archive is remotely clonable an attacker cannot get the password file that way.
## Images ## Images
You can (if you're logged in) upload files, including images, using the **Upload a file** link on the top menu bar. You can link to an uploaded image, or other images already available on the web, like this: You can (if you're logged in) upload files, including images, using the **Upload a file** link on the top menu bar. You can link to an uploaded image, or to other images already available on the web, like this:
![Smeagol](http://vignette3.wikia.nocookie.net/lotr/images/e/e1/Gollum_Render.png/revision/latest?cb=20141218075509) ![Smeagol](http://vignette3.wikia.nocookie.net/lotr/images/e/e1/Gollum_Render.png/revision/latest?cb=20141218075509)
## Includes ## Running Smeagol
You can include pages into the current page you're working on. To do so, you can add a include link: You can run Smeagol from the [[Docker Image]]; alternatively you can run it from an executable jar file or as a war file in a servlet container. Read how about [[Configuring Smeagol]] and [[Deploying Smeagol]].
`&[:indent-heading s/Num :indent-list s/Num](relative or absolute uri s/Str)` ## Developing Smeagol
Smeagol is an open source project; you're entitled to make changes yourself. Read more about [[Developing Smeagol]].
Parameters semantics:
* uri: The page to include. At the moment only pages from the current wiki are allowed. A page called "PageToBeIncluded" will result in a uri "PageToBeIncluded.md".
* indent-heading: You can indent headings of included page to adjust the included content to your surrounding structure. Indents 0-9 are supported.
* indent-list: In Same manner you can indent lists of included page to adjust the included content to your surrounding structure. Indents 0-9 are supported.
Security warning: At the moment there is no check against directory traversal attack. So include feature may expose files outside of your wiki content-dir.
## Prerequisites
You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed.
You will need [node](https://nodejs.org/en/) and [bower](https://bower.io/) installed.
## Development
To start a development web server for the application, run:
lein bower install
lein repl
And then, when the repl starts up,
(start-server)
## Running
To build a standalone Smeagol jar file, run:
lein bower install
lein ring uberjar
**HOWEVER**, this will not run without [configuration](https://github.com/journeyman-cc/smeagol/blob/develop/resources/public/content/Environment%20Variables.md).
Alternatively, if you want to deploy to a servlet container (which I would strongly recommend), the simplest thing is to run:
lein bower install
lein ring uberwar
(a command which I'm sure Smeagol would entirely appreciate) and deploy the resulting war file.
## Experimental Docker image
You can now run Smeagol as a [Docker](http://www.docker.com) image. To run my Docker image, use
docker run simonbrooke/smeagol
Smeagol will run, obviously, on the IP address of your Docker image, on port 8080. To find the IP address, start the image using the command above and then use
docker inspect --format '{{ .NetworkSettings.IPAddress }}' $(docker ps -q)
Suppose this prints '10.10.10.10', then the URL to browse to will be http://10.10.10.10:8080/smeagol/
This image is _experimental_, but it does seem to work fairly well. What it does **not** yet do, however, is push the git repository to a remote location, so when you tear the Docker image down your edits will be lost. My next objective for this image is for it to have a cammand line parameter being the git address of a repository from which it can initialise the Wiki content, and to which it will periodically push local changes to the Wiki content.
To build your own Docker image, run:
lein clean
lein bower install
lein ring uberwar
lein docker build
This will build a new Docker image locally; you can, obviously, push it to your own Docker repository if you wish.
## Advertisement
If you like what you see here, I am available for work on open source Clojure projects.
### Phoning home
Smeagol currently requests the WEFT logo in the page footer from my home site. This is mainly so I can get a feel for how many people are using the product. If you object to this, edit the file
resources/templates/base.html
and replace the line
<img height="16" width="16" alt="The Web Engineering Factory &amp; Toolworks" src="http://www.weft.scot/images/weft.logo.64.png"> Developed by <a href="http://www.weft.scot/">WEFT</a>
with the line
<img height="16" width="16" alt="The Web Engineering Factory &amp; Toolworks" src="img/weft.logo.64.png"> Developed by <a href="http://www.weft.scot/">WEFT</a>
## License ## License
Copyright © 2014-2020 Simon Brooke. Licensed under the GNU General Public License, Copyright © 2014-2020 Simon Brooke. Licensed under the GNU General Public License,
version 2.0 or (at your option) any later version. If you wish to incorporate version 2.0 or (at your option) any later version. If you wish to incorporate
parts of Smeagol into another open source project which uses a less restrictive parts of Smeagol into another open source project which uses a less restrictive
license, please contact me; I'm open to dual licensing it. license, please contact me; I'm open to dual licensing it.
## Phoning home
Smeagol does currently fetch one image from my home site. Read more about [[Phoning Home]], and how to prevent it (if you want to).
## Advertisement
If you like what you see here, I am available for work on open source Clojure projects.

View file

@ -1,3 +1,10 @@
# 1.0.4
* New gallery extension `pswp` using [Photoswipe](https://photoswipe.com/);
* Automatic thumbnailing of uploaded images;
* Javascript supporting extensions is only loaded for those pages on which the extensions are actually used;
* Many minor bug fixes.
# 1.0.2 # 1.0.2
Two fixes contributed by a user: Two fixes contributed by a user:

View file

@ -1,102 +1,112 @@
Smeagol's core configuration comes from a configuration file, `config.edn`, which may be overridden by [[Environment Variables]]. The default file is at `resources/config.edn`; this default can be overridden by providing an environment variable, `SMEAGOL_CONFIG`, whose value is the full or relative pathname of a suitable file. Smeagol's core configuration comes from a configuration file, `config.edn`, which may be overridden by [[Environment Variables]]. The default file is at `resources/config.edn`; this default can be overridden by providing an environment variable, `SMEAGOL_CONFIG`, whose value is the full or relative pathname of a suitable file.
The default configuration file is as follows: The default configuration file is as follows:
``` ```
{ {
:content-dir "resources/public/content" :content-dir "resources/public/content"
;; where content is served from. ;; where content is served from.
:default-locale "en-GB" ;; default language used for messages :default-locale "en-GB" ;; default language used for messages
:extensions-from :local ;; where to load JavaScript libraries
;; from: options are :local, :remote.
:formatters ;; formatters for processing markdown :formatters ;; formatters for processing markdown
;; extensions. ;; extensions.
{:backticks {:formatter "smeagol.formatting/process-backticks"
{"vega" smeagol.formatting/process-vega :scripts {}
:styles {}}
"vis" smeagol.formatting/process-vega :mermaid {:formatter "smeagol.extensions.mermaid/process-mermaid"
:scripts {:core {:local "vendor/node_modules/mermaid/dist/mermaid.min.js"
"mermaid" smeagol.extensions.mermaid/process-mermaid :remote "https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.4.6/mermaid.min.js"}}}
:pswp {:formatter "smeagol.extensions.photoswipe/process-photoswipe"
"backticks" smeagol.formatting/process-backticks :scripts {:core {:local "vendor/node_modules/photoswipe/dist/photoswipe.min.js"
:remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"}
"pswp" smeagol.formatting/process-photoswipe} :ui {:local "vendor/node_modules/photoswipe/dist/photoswipe-ui-default.min.js"
:remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"}}
:styles {:core {:local "vendor/node_modules/photoswipe/dist/photoswipe.css"}
:skin {:local "vendor/node_modules/photoswipe/dist/default-skin/default-skin.css"}}}
:test {:formatter "smeagol.extensions.test/process-test" }
:vega {:formatter "smeagol.extensions.vega/process-vega"
:scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"}
:lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
:embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}}}
:vis {:formatter "smeagol.extensions.vega/process-vega"
:scripts {:core {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega/5.9.1/vega.min.js"}
:lite {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-lite/4.1.1/vega-lite.min.js"}
:embed {:remote "https://cdnjs.cloudflare.com/ajax/libs/vega-embed/6.2.2/vega-embed.min.js"}}}}
:log-level :info ;; the minimum logging level; one of :log-level :info ;; the minimum logging level; one of
;; :trace :debug :info :warn :error :fatal ;; :trace :debug :info :warn :error :fatal
:js-from :cdnjs ;; where to load JavaScript libraries
;; from: options are :local, :cdnjs
:passwd "resources/passwd" :passwd "resources/passwd"
;; where the password file is stored ;; where the password file is stored
:site-title "Smeagol" ;; overall title of the site, used in :site-title "Smeagol" ;; overall title of the site, used in
;; page headings ;; page headings
:start-page "Introduction" ;; the page shown to a visitor to the :start-page "Introduction" ;; the page shown to a visitor to the
;; root URL. ;; root URL.
:thumbnails {:small 64 ;; maximum dimension of thumbnails :thumbnails {:small 64 ;; maximum dimension of thumbnails
;; stored in the /small directory ;; stored in the /small directory
:med 400 ;; maximum dimension of thumbnails :med 400 ;; maximum dimension of thumbnails
;; stored in the /med directory ;; stored in the /med directory
}} }}
``` ```
## :content-dir
## content-dir
The value of `content-dir` should be the full or relative path to the content to be served: the Markdown files, and the upload directories. Full paths are advised, where possible. The directory must be readable and writable by the process running Smeagol. The default is `resources/public/conten` The value of `content-dir` should be the full or relative path to the content to be served: the Markdown files, and the upload directories. Full paths are advised, where possible. The directory must be readable and writable by the process running Smeagol. The default is `resources/public/conten`
The value from the configuration file may be overridden with the value of the environment variable `SMEAGOL_CONTENT_DIR`. The value from the configuration file may be overridden with the value of the environment variable `SMEAGOL_CONTENT_DIR`.
## :default-locale
## default-locale
The locale which you expect the majority of your visitors will use. Content negotiation will be done of course, and the best internationalisation file available will be used, but this sets a default for users who do not have any acceptable locale known to us. The default value is `en-GB`. The locale which you expect the majority of your visitors will use. Content negotiation will be done of course, and the best internationalisation file available will be used, but this sets a default for users who do not have any acceptable locale known to us. The default value is `en-GB`.
This parameter may be overridden with the environment variable `SMEAGOL_DEFAULT_LOCALE`.
This parameter may be overridden with the environment variable `SMEAGOL-DEFAULT-LOCALE`. ## :extensions-from
Where javascript support for extensions should be loaded from. Valid values are `:local` and `:remote`; if `:remote` is chosen, they will generally be loaded from [CDNJS](https://cdnjs.com/).
## formatters For an intranet site with limited outside bandwidth, or if you are particularly concerned about security, choose `:local`; otherwise, `:remote` will result in faster loading of your pages.
Specifications for formatters for markup extensions. The exact data stored will change before Smeagol 1.1.0. TODO: update this. This parameter may be overridden with the environment variable `SMEAGOL_JS_FROM`.
## :formatters
## log-level Specifications for formatters for markup extensions.
For each extension, a map is stored with a key `:formatter`, whose value is the fully qualified name of the Clojure function providing the extension, `:scripts` and `:styles`, each of which hava one additional key for each JavaScript (in the case of `:scripts`) or CSS (in the case of `:styles`) file required by the plugin. Each of these per-file keys points to a final further map, with keys `:local` and `:remote`, whose values are URLs - relative, in the case of the `:local` key, absolute in the case of the `:remote`, pointing to where the required resource can be fetched from.
This parameter may be overridden with the environment variable `SMEAGOL_FORMATTERS`, but you'd be pretty unwise to do so unless to disable formatters altogether. Otherwise, editing the `config.edn` file would be far more convenient.
## :log-level
The level at which logging should operate. Each setting implies all of the settings more severe than itself so The level at which logging should operate. Each setting implies all of the settings more severe than itself so
1. setting `:debug` will log all of `debug, info, warn, error` and| `fatal` messages; 1. setting `:debug` will log all of `debug, info, warn, error` and| `fatal` messages;
2. setting `:info` will log all of `info, warn, error` and| `fatal` messages; 2. setting `:info` will log all of `info, warn, error` and| `fatal` messages;
and so on, so that setting `:fatal` will show only messages which report reasons for Smeagol to fail. and so on, so that setting `:fatal` will show only messages which report reasons for Smeagol to fail.
The default setting is `:info`. The default setting is `:info`.
This parameter may be overridden with the environment variable `SMEAGOL_LOG_LEVEL`.
This parameter may be overridden with the environment variable `SMEAGOL-LOG-LEVEL`. ## :passwd
## TODO: Complete this doumentation! The value of this key should be the path to the password file used to authenticate users. It should **NOT** be in the content directory! For most deployments it should be a file elsewhere in the file system, but it must be readable and writable by the account which runs the process serving Smeagol.
This parameter may be overridden with the environment variable `SMEAGOL_PASSWD`.
## :site-title
The value of this key should be the overall title of the site, which is used in page headings.
This parameter may be overridden with the environment variable `SMEAGOL_SITE_TITLE`.
## :start-page
The value of this key should be the name (without the `.md` extension) of the page to show when a user visits the base URL of your Smeagol installation.
## :thumbnails
The value of this key should be a map. Keys in this map should have values which are integers. By default, the key `:small` is bound to `64` and the key `:med` to 400. When an image file is uploaded, it is stored at the resolution you uploaded; but for every key in the `:thumbnails` map whose value is larger than the larger dimension of the uploaded file, scaled copies will also be stored in those sizes.

View file

@ -2,23 +2,45 @@
You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed. You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed.
You will need [node](https://nodejs.org/en/) and [bower](https://bower.io/) installed. You will need [node](https://nodejs.org/en/) installed.
## Running in development ## Running in development
To start a web server for the application during development, run: To start a web server for the application during development, run:
lein bower install ```
lein npm install
lein ring server lein ring server
```
This should start a development server, and open a new window or tab in your default browser with the default page of the wiki loaded into it. This should start a development server, and open a new window or tab in your default browser with the default page of the wiki loaded into it.
## Editing ## Editing
I generally use [LightTable](http://lighttable.com/) as my `Clojure` editor, but it doesn't really matter what you use; if you run Smeagol as described above, then all changes you make in the code (and save) will instantly be applied to the running system. This makes for a productive development environment. I generally use [LightTable](http://lighttable.com/) as my `Clojure` editor, but it doesn't really matter what you use; if you run Smeagol as described above, then all changes you make in the code (and save) will instantly be applied to the running system. This makes for a productive development environment.
## Building for deployment
*Important:* Always run `lein clean` before building a deployment build. Once you have deployed your deployment artifact, run `lein clean` again before continuing development.
### To build a standalone executable jar file
run:
```
lein ring uberjar
```
The resulting file may be run by invoking
```
java -jar \[path to uberjar file\]
```
## Documentation ## Documentation
It is my intention that the code should be sufficiently well documented to be easy to understand. Documentation may be generated from the code by running It is my intention that the code should be sufficiently well documented to be easy to understand. Documentation may be generated from the code by running
```
lein codox lein codox
```
## Contributing ## Contributing
If you make changes to Smeagol which you think are useful, please contribute them in the form of a [pull request on github](https://help.github.com/articles/creating-a-pull-request/). If you make changes to Smeagol which you think are useful, please contribute them in the form of a [pull request on github](https://help.github.com/articles/creating-a-pull-request/).

View file

@ -1,18 +1,13 @@
![One wiki to rule them all](https://www.weft.scot/images/smeagol.png) ![One wiki to rule them all](https://www.weft.scot/images/smeagol.png)
# Welcome to Smeagol! # Welcome to Smeagol!
Smeagol is a simple Wiki engine inspired by [Gollum](https://github.com/gollum/gollum/wiki). Gollum is a Wiki engine written in Ruby, which uses a number of simple text formats including [Markdown](http://daringfireball.net/projects/markdown/), and which uses [Git](http://git-scm.com/) to provide versioning and backup. I needed a new Wiki for a project and thought Gollum would be ideal - but unfortunately it doesn't provide user authentication, which I needed, and it was simpler for me to reimplement the bits I did need in Clojure than to modify Gollum. Smeagol is a hackable, extensible Wiki engine which is reasonably user-friendly. It uses [Markdown](http://daringfireball.net/projects/markdown/) as its text format, and [Git](http://git-scm.com/) to provide versioning and backup.
So at this stage Smeagol is a Wiki engine written in Clojure which uses Markdown as its text format, which does have user authentication, and which uses Git as its versioning and backup system.
## Status
Smeagol is now a fully working small Wiki engine, and meets my own immediate needs.
## Using Smeagol ## Using Smeagol
Read the [[User Documentation]] for an introduction to all Smeagol's features. Read the [[User Documentation]] for an introduction to all Smeagol's features.
## Markup syntax ## Markup syntax
Smeagol uses the Markdown format as provided by [markdown-clj](https://github.com/yogthos/markdown-clj), with the addition that anything enclosed in double square brackets, \[\[like this\]\], will be treated as a link into the wiki itself. Read more about [[Extensible Markup]]. Smeagol uses the Markdown format as provided by [markdown-clj](https://github.com/yogthos/markdown-clj), with the addition that anything enclosed in double square brackets, \[\[like this\]\], will be treated as a link into the wiki itself. The markdown format is extensible, and has extensions already for inclusions, for data visualisations and for picture galleries. Read more about [[Extensible Markup]].
## Security and authentication ## Security and authentication
Smeagol now has good security and authentication. While the initial password supplied with the system is not encrypted, when it is changed it will be; and passwords for new users added through the user administration pages are encrypted. Read more about [[Security and authentication]]. Smeagol now has good security and authentication. While the initial password supplied with the system is not encrypted, when it is changed it will be; and passwords for new users added through the user administration pages are encrypted. Read more about [[Security and authentication]].
@ -26,13 +21,13 @@ You can (if you're logged in) upload files, including images, using the **Upload
![Smeagol](http://vignette3.wikia.nocookie.net/lotr/images/e/e1/Gollum_Render.png/revision/latest?cb=20141218075509) ![Smeagol](http://vignette3.wikia.nocookie.net/lotr/images/e/e1/Gollum_Render.png/revision/latest?cb=20141218075509)
## Running Smeagol ## Running Smeagol
You can run Smeagol from the [[Docker Image]]; alternatively you can run it from an executable jar file or as a war file in a servlet container. Read how in [[Deploying Smeagol]]. You can run Smeagol from the [[Docker Image]]; alternatively you can run it from an executable jar file or as a war file in a servlet container. Read how about [[Configuring Smeagol]] and [[Deploying Smeagol]].
## Developing Smeagol ## Developing Smeagol
Smeagol is an open source project; you're entitled to make changes yourself. Read more about [[Developing Smeagol]]. Smeagol is an open source project; you're entitled to make changes yourself. Read more about [[Developing Smeagol]].
## License ## License
Copyright © 2014-2017 Simon Brooke. Licensed under the GNU General Public License, Copyright © 2014-2020 Simon Brooke. Licensed under the GNU General Public License,
version 2.0 or (at your option) any later version. If you wish to incorporate version 2.0 or (at your option) any later version. If you wish to incorporate
parts of Smeagol into another open source project which uses a less restrictive parts of Smeagol into another open source project which uses a less restrictive
license, please contact me; I'm open to dual licensing it. license, please contact me; I'm open to dual licensing it.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -37,6 +37,8 @@
;; the relative path to the password file. ;; the relative path to the password file.
(def password-file-path (def password-file-path
"Path to the password file."
;; TODO: portability; elegance. Not very happy with this.
(or (or
(:passwd config) (:passwd config)
(str (io/resource-path) "../passwd"))) (str (io/resource-path) "../passwd")))

View file

@ -100,17 +100,17 @@
'( {:from :smeagol-content-dir :to :content-dir} '( {:from :smeagol-content-dir :to :content-dir}
{:from :smeagol-default-locale :to :default-locale} {:from :smeagol-default-locale :to :default-locale}
{:from :smeagol-formatters :to :formatters :transform read-string} {:from :smeagol-formatters :to :formatters :transform read-string}
{:from :smeagol-js-from :to :js-from :transform to-keyword} {:from :smeagol-js-from :to :extensions-from :transform to-keyword}
{:from :smeagol-log-level :to :log-level :transform to-keyword} {:from :smeagol-log-level :to :log-level :transform to-keyword}
{:from :smeagol-passwd :to :passwd} {:from :smeagol-passwd :to :passwd}
{:from :smeagol-site-title :to :site-title})) {:from :smeagol-site-title :to :site-title}))
(defn build-config (defn- build-config
[]
"The actual configuration, as a map. The idea here is that the config "The actual configuration, as a map. The idea here is that the config
file is read (if it is specified and present), but that individual file is read (if it is specified and present), but that individual
values can be overridden by environment variables." values can be overridden by environment variables."
[]
(try (try
(log/info (str "Reading configuration from " config-file-path)) (log/info (str "Reading configuration from " config-file-path))
(let [file-contents (try (let [file-contents (try
@ -146,4 +146,6 @@
(log/error any "Could not load configuration") (log/error any "Could not load configuration")
{}))) {})))
(def config (build-config)) (def config
"The actual configuration, as a map."
(memoize build-config))

View file

@ -11,6 +11,7 @@
[noir.io :as io] [noir.io :as io]
[smeagol.configuration :refer [config]] [smeagol.configuration :refer [config]]
[smeagol.extensions.utils :refer :all] [smeagol.extensions.utils :refer :all]
[smeagol.util :refer [content-dir upload-dir]]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -75,7 +76,7 @@
src := #'[^)]*' ; src := #'[^)]*' ;
SPACE := #'[\\r\\n\\W]*'")) SPACE := #'[\\r\\n\\W]*'"))
(defn simplify (defn- simplify
[tree] [tree]
(if (if
(coll? tree) (coll? tree)
@ -88,16 +89,6 @@
:END-SRC nil :END-SRC nil
(remove empty? (map simplify tree))))) (remove empty? (map simplify tree)))))
(defn uploaded?
"Does this `url` string appear to be one that has been uploaded to our
`uploads` directory?"
[url]
(and
(cs/starts-with? (str url) "content/uploads")
(fs/exists? (cio/file upload-dir (fs/base-name url)))))
;; (uploaded? "content/uploads/g1.jpg")
(defn slide-merge-dimensions (defn slide-merge-dimensions
"If this `slide` appears to be local, return it decorated with the "If this `slide` appears to be local, return it decorated with the
dimensions of the image it references." dimensions of the image it references."
@ -118,7 +109,7 @@
;; {:title "Frost on a gate, Laurieston", ;; {:title "Frost on a gate, Laurieston",
;; :src "content/uploads/g1.jpg"}) ;; :src "content/uploads/g1.jpg"})
(defn process-simple-slide (defn- process-simple-slide
[slide-spec] [slide-spec]
(let [s (simplify (simple-grammar slide-spec)) (let [s (simplify (simple-grammar slide-spec))
s'(zipmap (map first s) (map #(nth % 1) s)) s'(zipmap (map first s) (map #(nth % 1) s))
@ -174,6 +165,8 @@
;; 1) ;; 1)
(defn process-photoswipe (defn process-photoswipe
"Process a Photoswipe specification which may conform either to the
`full` or the `simple` syntax."
[^String url-or-pswp-spec ^Integer index] [^String url-or-pswp-spec ^Integer index]
(let [data (resource-url-or-data->data url-or-pswp-spec) (let [data (resource-url-or-data->data url-or-pswp-spec)
spec (cs/trim (:data data))] spec (cs/trim (:data data))]

View file

@ -2,12 +2,15 @@
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.extensions.utils smeagol.extensions.utils
(:require [cemerick.url :refer (url url-encode url-decode)] (:require [cemerick.url :refer (url url-encode url-decode)]
[clj-yaml.core :as yaml]
[clojure.data.json :as json]
[clojure.java.io :as cjio] [clojure.java.io :as cjio]
[clojure.string :as cs] [clojure.string :as cs]
[me.raynes.fs :as fs] [me.raynes.fs :as fs]
[noir.io :as io] [noir.io :as io]
[smeagol.configuration :refer [config]] [smeagol.configuration :refer [config]]
[taoensso.timbre :as log])) [taoensso.timbre :as log]
[smeagol.util :refer [content-dir upload-dir]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; ;;;;
@ -32,16 +35,6 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def content-dir
(str
(fs/absolute
(or
(:content-dir config)
(cjio/file (io/resource-path) "content")))))
(def upload-dir
(str (cjio/file content-dir "uploads")))
(def resource-url-or-data->data (def resource-url-or-data->data
"Interpret this `resource-url-or-data` string as data to be digested by a "Interpret this `resource-url-or-data` string as data to be digested by a
`process-extension` function. It may be a URL or the pathname of a local `process-extension` function. It may be a URL or the pathname of a local
@ -83,3 +76,19 @@
(.getName (.getClass x)) (.getName (.getClass x))
(.getMessage x) ) (.getMessage x) )
default)))))) default))))))
(defn uploaded?
"Does this `url` string appear to be one that has been uploaded to our
`uploads` directory?"
[url]
(and
(cs/starts-with? (str url) "content/uploads")
(fs/exists? (cjio/file upload-dir (fs/base-name url)))))
;; (uploaded? "content/uploads/g1.jpg")
(defn yaml->json
"Rewrite this string, assumed to be in YAML format, as JSON."
[^String yaml-src]
(json/write-str (yaml/parse-string yaml-src)))

View file

@ -1,9 +1,7 @@
(ns ^{:doc "Format vega/vis extensions to Semagol's extended markdown format." (ns ^{:doc "Format vega/vis extensions to Semagol's extended markdown format."
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.extensions.vega smeagol.extensions.vega
(:require [clojure.data.json :as json] (:require [smeagol.extensions.utils :refer :all]
[clj-yaml.core :as yaml]
[smeagol.extensions.utils :refer :all]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -62,11 +60,6 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn yaml->json
"Rewrite this string, assumed to be in YAML format, as JSON."
[^String yaml-src]
(json/write-str (yaml/parse-string yaml-src)))
(defn process-vega (defn process-vega
"If this `src-resource-or-url` is a valid URL, it is assumed to point to a "If this `src-resource-or-url` is a valid URL, it is assumed to point to a
plain text file pointing to valid `vega-src`; otherwise, it is expected to plain text file pointing to valid `vega-src`; otherwise, it is expected to

View file

@ -48,15 +48,19 @@
(declare process-text) (declare process-text)
(defn process-backticks (defn- process-backticks
"Effectively, escape the backticks surrounding this `text`, by protecting them "Effectively, escape the backticks surrounding this `text`, by protecting them
from the `process-text` filter." from the `process-text` filter.
**NOTE** that it is not expected that this function forms part of a stable
API."
[^String text ^Integer index] [^String text ^Integer index]
(str "<pre class=\"backticks\">```" (.trim text) "\n```</pre>")) (str "<pre class=\"backticks\">```" (.trim text) "\n```</pre>"))
(defn get-first-token (defn get-first-token
"Return the first space-separated token of this `string`." "Return the first space-separated token of the first line of this `string`,
or `nil` if there is none."
[^String string] [^String string]
(try (try
(if string (first (cs/split (first (cs/split-lines string)) #"[^a-zA-Z0-9]+"))) (if string (first (cs/split (first (cs/split-lines string)) #"[^a-zA-Z0-9]+")))
@ -69,7 +73,10 @@
As with `process-text`, this function returns a map with two top-level keys: As with `process-text`, this function returns a map with two top-level keys:
`:inclusions`, a map of constructed keywords to inclusion specifications, `:inclusions`, a map of constructed keywords to inclusion specifications,
and `:text`, an HTML text string with the keywords present where the and `:text`, an HTML text string with the keywords present where the
corresponding inclusion should be inserted." corresponding inclusion should be inserted.
**NOTE** that it is not expected that this function forms part of a stable
API."
[^Integer index ^clojure.lang.Associative result ^String fragment fragments processed] [^Integer index ^clojure.lang.Associative result ^String fragment fragments processed]
(process-text (process-text
(inc index) (inc index)
@ -78,8 +85,9 @@
(cons fragment processed))) (cons fragment processed)))
(defn deep-merge [v & vs] (defn deep-merge
"Cripped in its entirety from https://clojuredocs.org/clojure.core/merge." "Cripped in its entirety from [here](https://clojuredocs.org/clojure.core/merge)."
[v & vs]
(letfn [(rec-merge [v1 v2] (letfn [(rec-merge [v1 v2]
(if (and (map? v1) (map? v2)) (if (and (map? v1) (map? v2))
(merge-with deep-merge v1 v2) (merge-with deep-merge v1 v2)
@ -89,14 +97,23 @@
(last vs)))) (last vs))))
(defn apply-formatter (defn- apply-formatter
"Within the context of `process-text`, process a fragment for which an explicit "Within the context of `process-text`, process a fragment for which an explicit
`formatter` has been identified. `formatter` has been identified, and then recurse back into `process-text` to
process the remainder of the fragments. Arguments are as for `process-text`, q.v.,
the addition of
* `fragment` the current fragment to be processed;
* `token` the identifier of the extension processor to be applied;
* `formatter` the actual extension processor to be applied.
As with `process-text`, this function returns a map with two top-level keys: As with `process-text`, this function returns a map with two top-level keys:
`:inclusions`, a map of constructed keywords to inclusion specifications, `:inclusions`, a map of constructed keywords to inclusion specifications,
and `:text`, an HTML text string with the keywords present where the and `:text`, an HTML text string with the keywords present where the
corresponding inclusion should be inserted." corresponding inclusion should be inserted.
**NOTE** that it is not expected that this function forms part of a stable
API."
[^Integer index [^Integer index
^clojure.lang.Associative result ^clojure.lang.Associative result
fragments fragments
@ -117,9 +134,12 @@
(cons inky processed)))) (cons inky processed))))
(defn reassemble-text (defn- reassemble-text
"Reassemble these processed strings into a complete text, and process it as "Reassemble these processed strings into a complete text, and process it as
Markdown." Markdown.
**NOTE** that it is not expected that this function forms part of a stable
API."
[result processed] [result processed]
(assoc result :text (assoc result :text
(local-links (local-links
@ -128,9 +148,13 @@
:heading-anchors true)))) :heading-anchors true))))
(defn reintegrate-inclusions (defn- reintegrate-inclusions
"Given a map of the form produced by `process-text`, return a string of HTML text "Given a map of the form produced by `process-text`, return a map based on
with the inclusions (if any) reintegrated." that map with the key `:content` bound to a string of HTML text
with the inclusions (if any) generated by extension processors reintegrated.
**NOTE** that it is not expected that this function forms part of a stable
API."
([processed-text] ([processed-text]
(assoc (assoc
processed-text processed-text
@ -155,7 +179,19 @@
(cs/replace (kw inclusions) "\\/" "/")))))))) (cs/replace (kw inclusions) "\\/" "/"))))))))
(defn process-text (defn- process-text
"Process extension fragments in this text. Arguments are:
* `index`, the index number of the current fragment;
* `result`, a context within which the final result is being accumulated;
* `fragments`, a sequence of the fragments of the original text which have
not yet been processed;
* `processed`, a reverse sequence of the fragments of the original text
which have already been processed.
Returns a map derived from `result` enhanced with the accumulated result.
**NOTE** that it is not expected that this function forms part of a stable
API."
[^Integer index ^clojure.lang.Associative result fragments processed] [^Integer index ^clojure.lang.Associative result fragments processed]
(let [fragment (first fragments) (let [fragment (first fragments)
;; if I didn't find a formatter for a back-tick marked fragment, ;; if I didn't find a formatter for a back-tick marked fragment,
@ -184,13 +220,16 @@
(process-markdown-fragment index result remarked (rest fragments) processed)))) (process-markdown-fragment index result remarked (rest fragments) processed))))
(defn md->html (defn md->html
"Process this `text`, assumed to be markdown potentially containing both local links "Process the source text (assumed to be the value of the key `:source` in this
and YAML visualisation specifications, and return a map comprising JSON visualisation `context`, expected to be a full HTTP request map, so that extensions may in
specification, and HTML text with markers for where those should be reinserted. future potentially have access to things like `accepts-*` headers.
The map has two top-level keys: `:inclusions`, a map of constructed keywords to The source is assumed to be markdown potentially containing both local links
inclusion specifications, and `:text`, an HTML text string with the keywords and extension specifications, and return a map with top-level keys:
present where the corresponding inclusion should be inserted." * `:content`, the HTML content of the page to serve; and
* `:extensions`, being a subset of the `:formatters` map from
`smeagol.configuration/config` covering the extensions actually used in the
generated content."
[^clojure.lang.Associative context] [^clojure.lang.Associative context]
(reintegrate-inclusions (reintegrate-inclusions
(process-text (process-text

View file

@ -37,7 +37,11 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def template-path "templates/") (def template-path
"Path to the resource directory in which Selmer templates are stored. These
should be in a place which is not editable from the Wiki, otherwise
users may break things which they cannot subsequently fix!"
"templates/")
(parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field))) (parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
@ -48,10 +52,14 @@
(fn [args context-map] (fn [args context-map]
(let [messages (:i18n context-map) (let [messages (:i18n context-map)
default (or (second args) (first args))] default (or (second args) (first args))]
(if (map? messages) (or (messages (keyword (first args))) default) default)))) (if (map? messages) (or (messages (keyword (first args))) default)
default))))
(deftype RenderableTemplate [template params] (deftype RenderableTemplate
;; Boilerplate from Luminus. Load a template file into an object which may
;; be rendered.
[template params]
Renderable Renderable
(render [this request] (render [this request]
(try (try
@ -75,6 +83,8 @@
(defn render (defn render
"Boilerplate from Luminus. Render an HTML page based on this `template` and
these `params`. Returns HTML source as a string."
[template & [params]] [template & [params]]
(try (try
(RenderableTemplate. template params) (RenderableTemplate. template params)

View file

@ -33,7 +33,7 @@
(defn edit-users (defn edit-users
"Put a list of users on-screen for editing." "Render a page showing a list of users for editing."
[request] [request]
(let [params (keywordize-keys (:params request)) (let [params (keywordize-keys (:params request))
user (session/get :user)] user (session/get :user)]
@ -43,7 +43,8 @@
:users (auth/list-users)})))) :users (auth/list-users)}))))
(defn delete-user (defn delete-user
"Delete a user." "Render a form allowing a user to be deleted; and
process that form.."
[request] [request]
(let [params (keywordize-keys (:params request)) (let [params (keywordize-keys (:params request))
target (:target params) target (:target params)
@ -59,7 +60,8 @@
(defn edit-user (defn edit-user
"Put an individual user's details on screen for editing." "Render a form showing an individual user's details for editing; and
process that form."
[request] [request]
(let [params (keywordize-keys (:params request))] (let [params (keywordize-keys (:params request))]
(try (try

View file

@ -9,6 +9,7 @@
[clojure.walk :refer :all] [clojure.walk :refer :all]
[compojure.core :refer :all] [compojure.core :refer :all]
[java-time :as jt] [java-time :as jt]
[markdown.core :as md]
[me.raynes.fs :as fs] [me.raynes.fs :as fs]
[noir.io :as io] [noir.io :as io]
[noir.response :as response] [noir.response :as response]
@ -20,6 +21,7 @@
[smeagol.formatting :refer [md->html]] [smeagol.formatting :refer [md->html]]
[smeagol.history :as hist] [smeagol.history :as hist]
[smeagol.layout :as layout] [smeagol.layout :as layout]
[smeagol.local-links :refer :all]
[smeagol.routes.admin :as admin] [smeagol.routes.admin :as admin]
[smeagol.sanity :refer [show-sanity-check-error]] [smeagol.sanity :refer [show-sanity-check-error]]
[smeagol.util :as util] [smeagol.util :as util]
@ -107,7 +109,10 @@
(merge (util/standard-params request) (merge (util/standard-params request)
{:title (str (util/get-message :edit-title-prefix request) " " page) {:title (str (util/get-message :edit-title-prefix request) " " page)
:page page :page page
:side-bar (md->html (assoc request :source (slurp (cjio/file util/content-dir side-bar)))) :side-bar (md/md-to-html-string
(local-links
(slurp (cjio/file content-dir side-bar)))
:heading-anchors true)
:content (if exists? (slurp file-path) "") :content (if exists? (slurp file-path) "")
:exists exists?}))))))) :exists exists?})))))))
@ -155,18 +160,22 @@
;; :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"} :core) ;; :remote "https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"} :core)
(defn collect-preferred (defn collect-preferred
"Collect preferred variants of resources required by extensions used in the
page described in this `processed-text`."
([processed-text] ([processed-text]
(concat (concat
(collect-preferred processed-text :scripts) (collect-preferred processed-text :scripts)
(collect-preferred processed-text :styles))) (collect-preferred processed-text :styles)))
([processed-text resource-type] ([processed-text resource-type]
(reduce concat (reduce
concat
(map (map
(fn [extension-key] (fn [extension-key]
(map (map
(fn [requirement] (fn [requirement]
(let [r (preferred-source (let [r (preferred-source
(-> processed-text :extensions extension-key resource-type requirement) (-> processed-text :extensions extension-key
resource-type requirement)
requirement)] requirement)]
(if (empty? r) (if (empty? r)
(log/warn "Found no valid URL for requirement" (log/warn "Found no valid URL for requirement"
@ -235,7 +244,9 @@
;;;; this next section is all stuff supporting the list-uploads page, and maybe ;;;; this next section is all stuff supporting the list-uploads page, and maybe
;;;; should be moved to its own file. ;;;; should be moved to its own file.
(def image-extns #{".gif" ".jpg" ".jpeg" ".png"}) (def image-extns
"File name extensions suggesting files which can be considered to be images."
#{".gif" ".jpg" ".jpeg" ".png"})
(defn format-instant (defn format-instant
"Format this `unix-time`, expected to be a Long, into something human readable. "Format this `unix-time`, expected to be a Long, into something human readable.
@ -322,6 +333,7 @@
{:title (util/get-message :file-upload-title request) {:title (util/get-message :file-upload-title request)
:uploaded uploaded})))) :uploaded uploaded}))))
(defn version-page (defn version-page
"Render a specific historical version of a page" "Render a specific historical version of a page"
[request] [request]
@ -363,7 +375,7 @@
(defn auth-page (defn auth-page
"Render the auth page" "Render the authentication (login) page"
[request] [request]
(or (or
(show-sanity-check-error) (show-sanity-check-error)
@ -395,6 +407,7 @@
(defn wrap-restricted-redirect (defn wrap-restricted-redirect
;; TODO: this is not idiomatic, and it's too late to write something idiomatic just now ;; TODO: this is not idiomatic, and it's too late to write something idiomatic just now
;; TODO TODO: it's also not working.
[f request] [f request]
(route/restricted (route/restricted
(apply (apply

View file

@ -1,5 +1,7 @@
(ns ^{:doc "Functions related to sanity checks and error reporting in conditions (ns ^{:doc "Functions related to sanity checks and error reporting in conditions
where the environment may not be sane." where the environment may not be sane. Generally, the functions in this
file are called (via `sanity-check-installation`, which is the only
supported entry point) at first start-up."
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.sanity smeagol.sanity
(:import (java.util Locale)) (:import (java.util Locale))

View file

@ -72,6 +72,7 @@
(.write iw nil iio-img iw-param))) (.write iw nil iio-img iw-param)))
(def image? (def image?
"True if the file at this `filename` appears as though it may be an image"
(memoize (memoize
(fn [filename] (fn [filename]
(image-file-extns (fs/extension (cs/lower-case (str filename))))))) (image-file-extns (fs/extension (cs/lower-case (str filename)))))))

View file

@ -39,9 +39,12 @@
(def start-page (def start-page
"The page to load on startup, taken from configuration."
(:start-page config)) (:start-page config))
(def content-dir (def content-dir
"The absolute path to the directory in which Wiki content (i.e., Markdown
files) are stored."
(str (str
(fs/absolute (fs/absolute
(or (or
@ -49,6 +52,7 @@
(cjio/file (io/resource-path) "content"))))) (cjio/file (io/resource-path) "content")))))
(def upload-dir (def upload-dir
"The absolute path to the directory in which uploaded files are stored."
(str (cjio/file content-dir "uploads"))) (str (cjio/file content-dir "uploads")))
(def local-url-base (def local-url-base