Repository: limist/literate-programming-examples
Branch: master
Commit: 90a1d2f9ce32
Files: 12
Total size: 190.1 KB
Directory structure:
gitextract_jmrys2px/
├── .gitignore
├── 00-just-code-blocks/
│ └── simple-code-blocks.org
├── 01-clojure-literate-ants/
│ └── literate-ants.org
├── 02-minimal-clojure-app/
│ └── clojure-app-skeleton.org
├── 02-minimal-clojure-project/
│ └── clojure-default-skeleton.org
├── 03-pedestal-app/
│ └── pedestal-app-skeleton.org
├── 03-pedestal-service/
│ └── pedestal-service-skeleton.org
├── 05-luminus-site/
│ ├── luminus-site-skeleton.org
│ └── mysite/
│ └── resources/
│ └── public/
│ ├── css/
│ │ └── screen.css
│ └── md/
│ └── docs.md
├── LICENSE
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
01-clojure-literate-ants/literate-ants.clj
01-clojure-literate-ants/project.clj
02-minimal-clojure-app/src/
02-minimal-clojure-app/test/
================================================
FILE: 00-just-code-blocks/simple-code-blocks.org
================================================
#+TITLE: Simple Code Blocks - a Literate Programming File
#+AUTHOR: Kai Wu
#+EMAIL: k@limist.com
#+LANGUAGE: en
#+STARTUP: align hidestars lognotestate
* Introduction
This file just shows simple usage of code blocks in Org mode; unlike
later examples, there are no Org =:tangle= file locations used here.
Instead, we just show how easy and convenient it is to mix structured
prose/markup with code blocks.
* 4Clojure problem 42 , [[http://www.4clojure.com/problem/42][Factorial Fun]] - first solved [2012-05-23 Wed]
Write a function which calculates factorials.
** My solution, <1 minute: reduce w/ range
#+BEGIN_SRC clojure
#(reduce * (range 1 (inc %)))
#+END_SRC
** Other solutions: =reduce=, or use =apply= with =range=
#+BEGIN_SRC clojure
;; maximental's solution:
#(apply * % (range 2 %))
;; daowen's solution:
#(apply * (range 1 (inc %)))
;; redraiment's solution:
#(apply * % (range 1 %))
;; sheldon's solution:
#(loop [x % r 1] (if (= x 1) r (recur (dec x) (* r x))))
;; vyzamyatin's solution:
#(reduce * (range 1 (inc %)))
;; amcnamara's solution:
#(apply * (range 1 (inc %)))
#+END_SRC
================================================
FILE: 01-clojure-literate-ants/literate-ants.org
================================================
#+TITLE: The Clojure Ants Simulation, in Literate Form
#+AUTHOR: Rich Hickey (this literate/org-babel version, Kai Wu)
#+EMAIL: k@limist.com
#+LANGUAGE: en
#+STARTUP: align indent fold nodlcheck hidestars oddeven lognotestate
#+PROPERTY: tangle literate-ants.clj
* Introduction
** What is this, why is it worthwhile? Quickly please!
+ You're looking at a literate programming (LP) style, single-file
version of Rich Hickey's Clojure ants simulator, in Org mode format.
+ For best results please *use Emacs with Org mode to view this*
=.org= *file*. If you're looking at this on Github.com, STOP - the
rendering there is neither complete nor correct!
** The *benefits* of LP using Emacs + Org
1. Docs matter, a lot. With LP, documentation is integral to
development, never an afterthought.
- For all but small throwaway systems, you're likely keeping a
separate file of development notes already; LP would integrate
that.
2. With one LP file, avoid the incidental/inessential complexity of
the filesystem: avoid context-switch overhead moving between files,
and sidestep your language's imposed filesystem structure.
3. Org rocks for prose:
- Org's plain-text *markup is lightweight*, yet more powerful than
Markdown, and cleaner than rST.
- The *structural editing* provided by Org documents lets you
organize your thoughts/writing/code very quickly. With good
structure even major revisions are easy.
- Org's exporter lets your *write-once, express-many-times*: you
can export an Org file to HTML (e.g. for blogging) or LaTeX
(for serious publishing).
- It's easy to version-control Org files.
4. Org rocks for code:
- Each code block has flexible granularity: can be named and
referred to; evaluated or not; have data sent in or exported;
specify different REPL sessions; specify different target/tangled
files.
- Code blocks are syntax-highlighted.
- Code blocks are ready to edit: jump to major-mode editing easily.
- A single Org file can mix different languages.
5. Meta-development, manage complexity from a coherent perspective: a
unified, single-file approach encourages holistic software
development and exposition, in a natural order, using structure to
enhance understanding. LP is not just documentation and code
together: it's a *process and abstraction unifying the development
lifecycle*: requirements, architecture, design, code, tests,
deployment, and maintenance - can all be bound coherently in one
active format.
** Why Clojure?
[[http://clojure.org][Clojure]] is a modern Lisp dialect that runs (primarily) on the [[http://en.wikipedia.org/wiki/Jvm][Java
Virtual Machine]]. As a language its main paradigm is [[http://en.wikipedia.org/wiki/Functional_programming][functional
programming]] (vs. the usual [[http://en.wikipedia.org/wiki/Object-oriented_programming][OO]] or [[http://en.wikipedia.org/wiki/Imperative_programming][imperative]] languages of Java, Python,
Ruby), and it also provides powerful constructs for concurrency such
as [[http://en.wikipedia.org/wiki/Software_transactional_memory][software transactional memory (STM)]].
So why use or learn Clojure?
1. There are *technical* reasons, better covered elsewhere.
2. There are *aesthetic* reasons: code is poetry, and like the best
poems, expressiveness (the ratio of power:symbols) matters, and
Clojure excels there.
3. Last but certainly not least, there are *human* reasons: I've found
the Clojure community to be one of the smartest and friendliest
around: there's a lot of brilliant-yet-humble innovation going
on. If you've watched Hickey's talks and gotten a sense of his
character, the same spirit generally pervades Clojurians.
** Why literate ants?
When Clojure was invented and began picking up interest from
2007/2008, its creator Rich Hickey would [[http://www.youtube.com/watch?v=dGVqrGmwOAw][highlight Clojure's features
with a demo program]]: a visual simulation of ants seeking food on a
finite 2-D "world" board. I found the =ants.clj= program fascinating,
particularly as simulation is a core interest. Historically,
[[https://en.wikipedia.org/wiki/Object-oriented_programming#History][simulations motivated the object-oriented programming]] paradigm, so
seeing a very different yet concise way to fulfill requirements of
encapsulation, concurrent state, and modeling changes over time drew
me in to Clojure.
To build more skill in Clojure, I wanted to fully understand
=ants.clj=. I also wanted to try Knuth's [[http://vasc.ri.cmu.edu/old_help/Programming/Literate/literate.html][literate programming
approach]] (LP), using my favorite tool: [[http://www.gnu.org/software/emacs/][Emacs]] and its peerless
[[http://orgmode.org][org-mode]]. Thus this =.org= file (and its HTML or PDF version) you're
now looking at is the literate version of Hickey's =ants.clj= code. I
took the original program, made minor changes for my comprehension,
and offer it as a working example of literate Clojure.
** Overview and setup
*** Prerequisites
1. A recent version of Emacs (ideally 24.3+).
2. Both org-mode and =clojure-mode= installed; use Emacs ELPA.
- Consider using an Emacs "starter package" that provides a good
baseline, like [[http://batsov.com/prelude/][Emacs Prelude]] or [[http://overtone.github.io/emacs-live/][Emacs Live]].
Then if you start Emacs and load this file, you'll see it the way it's
meant to be seen: as a multi-level, hierarchically organized and
structured literate code file, w/ syntax-highlighted code blocks.
*** Useful commands
- Use =SHIFT-TAB= and org-mode will cycle through top-level, headings,
and full-expanded displays.
- To generate a source-file from this =.org= file, =CTRL-c-v-t= to do
the /tangling/ step; that means org-mode will process each code
block below, and generate the source file =literate-ants.clj=
*** Skip to the end? Good idea!
Before you dive into the actual code, you may want to run the ants
simulation first - seeing it in action will help with understanding
the details too. So tangle this literate file per above instructions,
so you have the =literate-ants.clj= file, then jump down to [[Running
the Program]].
** Caveats - this may not be the LP you're looking for
1. Don't take this file as anything like an ideal literate programming
example! This is just my version of understanding Rich Hickey's
code, thus it does not reflect a complete or proper literate
programming approach to use.
- And what's *proper LP*? See the last 2009 comment on the
[[http://www.literateprogramming.com/][literateprogramming.com page]]. LP is not just about
documentation, but is a tool/approach for higher-level
abstraction, combining human thought and code.
- So beware: much of my prose below is relatively verbose and
explanatory (the /what/ and /how/ of code), as opposed to what
could/should be in the literate sections: meta, /why/, high-level
discussion of major design choices.
2. This version does not yet reflect more recent (post Clojure 1.2)
changes to the language, e.g. =defstruct= is still used below, but
has been deprecated in favor of [[http://clojure.org/datatypes][Clojure records]].
3. My Java experience is quite limited, so parts which rely heavily on
Java, such as the UI, I don't attempt to explain in-depth.
* The Simulation World
The first part of =ants.clj= sets up the simulation world, where we'll
be introduced to some of Clojure's powers.
** Initial setup of constants/magic-numbers
After the copyright notice, the initial setup code of =ants.clj= is
easy to understand (for coders at least), even if you've never dealt
with Lisp before. We see parameters (aka constants and magic numbers)
being defined for later use using Clojure's =[[http://clojure.org/special_forms#def][def]]= special form: =def=
creates a var (a mutable storage location) which connects a symbol to
a value in the current [[http://clojure.org/namespaces][namespace]].
#+name: sim-world-setup
#+BEGIN_SRC clojure :exports code :results silent :session s1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ant sim ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Copyright (c) Rich Hickey. All rights reserved.
;;
;; The use and distribution terms for this software are covered by the
;; Common Public License 1.0 (http://opensource.org/licenses/cpl.php)
;; which can be found in the file CPL.TXT at the root of this distribution.
;; By using this software in any fashion, you are agreeing to be bound by
;; the terms of this license.
;;
;; You must not remove this notice, or any other, from this software.
;; Set dimensions of the world, as a square 2-D board:
(def dim 80)
;; Number of ants = nants-sqrt^2
(def nants-sqrt 7)
;; Number of places with food:
(def food-places 35)
;; Range of amount of food at a place:
(def food-range 100)
;; Scale factor for pheromone drawing:
(def pher-scale 20.0)
;; Scale factor for food drawing:
(def food-scale 30.0)
;; Evaporation rate:
(def evap-rate 0.99)
(def animation-sleep-ms 100)
(def ant-sleep-ms 40)
(def evap-sleep-ms 1000)
(def running true)
#+END_SRC
** The board: ready to mutate via transactions
Things get more interesting once the actual simulation environment
needs defining:
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defstruct cell :food :pher) ; May also have :ant and :home values
#+END_SRC
First, a call to =[[http://clojuredocs.org/clojure_core/clojure.core/defstruct][defstruct]]= (like a hashmap or dictionary in other
languages) defines a baseline /cell/.
- =defstruct= is like a very lightweight class or
constructor/template function, and conveniently wraps Clojure's
=[[http://clojuredocs.org/clojure_core/clojure.core/create-struct][create-struct]]=.
- Here, a cell has two keys to start, =:food= and =:pher=, to
indicate the presence of food and pheromones. A cell may also have
keys of =:ant= and =:home=, depending on whether an ant and/or the
home-colony is present.
Next, the =world= function creates the 2-dimensional "board" of cells
(here, a square of 80x80 cells), represented as vectors (rows or the
vertical y-dimension) of a vector (the horizontal x-dimension columns
in one row):
#+name sim-world-board-creation
#+BEGIN_SRC clojure :exports code :results silent :session s1
;; World is a 2d vector of refs to cells
(def world
(apply vector
(map (fn [_]
(apply vector
(map (fn [_]
(ref (struct cell 0 0)))
(range dim))))
(range dim))))
#+END_SRC
Reading the above:
- Start with the innermost =[[http://clojuredocs.org/clojure_core/clojure.core/map][map]]= call, which uses an anonymous
function to create one column of 80 cells, per =(range dim)=. The
=[[http://clojuredocs.org/clojure_core/clojure.core/struct][struct]]= returns a new structmap instance using the earlier cell as
the basis, initializing the =:food= and =:pher= values to zero.
- But notice that =struct= is wrapped with a [[http://clojure.org/refs][transactional ref]], and
here's the first glimpse of Clojure's concurrency powers. With
each cell being stateful (possibly time-varying values of =:food=,
=:pher=, =:ant=, and =:home= values) and with multiple threads
updating the board and board elements, we'd typically think of
using locks on each cell when updating its state.
But in Clojure with its [[http://en.wikipedia.org/wiki/Software_transactional_memory][software transactional memory]] (STM), we
just use =ref= for safe references to mutable collections (here, a
=struct=) - all changes to a cell will then be atomic, consistent,
and isolated![fn:Databases-ACID] Like using an RDBMS, you don't
need to manually manage concurrency.
- Once you understand the innermost =(ref (struct cell 0 0 ))= =map=
call, the rest of =(def world...)= is straightforward: =apply=
uses =vector= as a constructor function with the =map= function
producing the vector's arguments, creating a "column" in the 2-D
board.
- Then the pattern is repeated in the outermost
=(apply vector (map...))= call, creating all the columns of the
2-D board.
- Note that as defined, each vector in =world= (again, a 2-D vector of
vectors) corresponds to an x-position, and of course, within that
vector are the y-positions (here, a total of 80 cells).
The =place= function is a selector function (think of "place" as the
noun, not the verb) returning particular cells in the 2-D world. Once
we have a cell, we can then mutate it to represent ants, food, and
pheromones (or their absence):
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn place [[x y]]
(-> world (nth x) (nth y)))
#+END_SRC
- =place= takes a single vector argument (having two elements x and
y), then applies the [[http://www.colourcoding.net/blog/archive/2011/07/09/another-go-at-explaining-the-thrush-operator-in-clojure.aspx][thrush operator]] (the [[http://clojuredocs.org/clojure_core/clojure.core/-%3E][arrow-like ->]]) on the
world object, first selecting the "column" =(nth x)= on world, then
the "row" =(nth y)= on that column.
[fn:Databases-ACID] STM is like a memory-only SQL database, thus the last property of being durable/persistent won't be satisfied.
*** Aside: the thrush operator
The thrush operator helps make code more concise, and arguably
clearer: instead of reading code "inside-out" to mentally evaluate it,
we can read it left-to-right.[fn:Fogus-on-thrush] Consider how the
equivalent =place= function would look without thrushing:
#+BEGIN_SRC clojure :exports code
(defn place-verbose [[x y]]
(nth (nth world x) y))
#+END_SRC
[fn:Fogus-on-thrush] Apparently Clojure's thrush is not quite a true
thrush, see [[http://blog.fogus.me/2010/09/28/thrush-in-clojure-redux/][Michael Fogus' article]].
** Ants as agents - doing asynchronous uncoordinated changes
Next we'll consider the "active things" in =ants.clj=, the ants
themselves. As before, we start with =defstruct=, defining an ant as
having only one required key, its direction. (An ant may temporarily
have another key, =:food=.)
#+name ants-defined
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defstruct ant :dir) ; Always has dir heading; may also have :food
(defn create-ant
"Create an ant at given location, returning an ant agent on the location."
[location direction]
(sync nil
(let [the-place (place location)
the-ant (struct ant direction)]
(alter the-place assoc :ant the-ant)
(agent location))))
#+END_SRC
To explain the above constructor function for ants, =create-ant=:
+ Takes two arguments, =location= and =direction=. =location= will be
a vector =[x y]=, and as we saw, passed on to the place function as
an argument; =direction= is a number from 0-7 inclusive
corresponding to one of the eight cardinal directions.
+ More concurrency support: the [[http://clojuredocs.org/clojure_core/clojure.core/sync][sync function]] takes a flags argument
(as of Clojure 1.3, it's still ignored so just pass nil), and then a
list of expressions that will be executed together atomically (all
or nothing) as a transaction.
+ The [[http://clojuredocs.org/clojure_core/clojure.core/let][let special form]] binds pairs of symbols and expressions in its
arguments vector, providing local, lexical bindings within the scope
of the body following.
+ =sync= will ensure that any mutations of refs using the [[http://clojuredocs.org/clojure_core/clojure.core/alter][alter
function]] will be atomic. Previously we had used =ref= around each
cell, so in the above code where =the-place= is such a ref-wrapped
cell, =alter= takes =the-place= ref as its first argument, then
=[[http://clojuredocs.org/clojure_contrib/clojure.contrib.generic.collection/assoc][assoc]]= as the function to be [[http://clojuredocs.org/clojure_core/clojure.core/apply][apply]]'ed on the-place, tying a new ant
instance to it (remember that as a cell, =the-place= is sure to have
=:food= and =:pher= key-values already, now we add =:ant=). Like the
thrush operator earlier, the syntax of =alter= enables convenient
left-to-right reading.
+ Finally, the [[http://clojuredocs.org/clojure_core/clojure.core/agent][agent function]]. What are Clojure agents? To quote the
docs,
#+BEGIN_QUOTE
Agents provide shared access to mutable state. They allow
non-blocking (asynchronous as opposed to synchronous atoms) and
independent change of individual locations (unlike coordinated
change of multiple locations through refs).
#+END_QUOTE
Clojure's =agent= function takes one required argument of state,
returning an agent object with initial value of that given state.
Here, as the last line of =create-ant=, =agent= effectively returns
the ant object at its starting location. Ants as agents make sense:
we expect them to move around independently (i.e. asynchronously) in
the simulation world.
** Setting up the home, and ants
The home of the ants is not a single cell on the world-board, but a
square of cells, with its top-left corner offset from the origin (0,
0). Its sides are proportional to the number of ants because the home
square will initially contain all the ants - one ant per cell - before
the simulation runs. We can see these two aspects of the home-square
in the two =def= calls for =home-offset= and =home-range= below.
#+name home-setup
#+BEGIN_SRC clojure :exports code :results silent :session s1
(def home-offset (/ dim 4))
(def home-range (range home-offset (+ nants-sqrt home-offset)))
(defn setup
"Places initial food and ants, returns seq of ant agents."
[]
(sync nil
(dotimes [i food-places]
(let [p (place [(rand-int dim) (rand-int dim)])]
(alter p assoc :food (rand-int food-range))))
(doall
(for [x home-range y home-range]
(do
(alter (place [x y]) assoc :home true)
(create-ant [x y] (rand-int 8)))))))
#+END_SRC
The =setup= function's docstring tells us what it's doing, so on to
the details:
+ =setup= takes no arguments.
+ As we saw before in =create-ant=, the =sync= function wraps a
sequence of expressions that together should be executed atomically,
all-or-nothing.
+ Setup initial food: The [[http://clojuredocs.org/clojure_core/clojure.core/dotimes][dotimes function]] takes two arguments, the
first a vector =[name n]= with =n= being the number of times that
the =body= (the second argument) will be repeatedly executed,
usually for its side-effects/mutations.
- Here, the unused name =i= is bound to the integers from 0 to 34,
since we had specified food-places as 35 initially.
- The =body= is clear enough: bind =p= to the randomly chosen place
on the world-board (using the [[http://clojuredocs.org/clojure_core/clojure.core/rand-int][rand-int function]] for x, y). The
already-seen =alter= function modifies that =p= to have a random
amount of food value.
+ Placing the ants in their starting positions: The [[http://clojuredocs.org/clojure_core/clojure.core/doall][doall function]]
forces immediate evaluation of a lazy sequence - in this case the
lazy sequence produced by the [[http://clojuredocs.org/clojure_core/clojure.core/for][for function]].
- Here, the =for= function's first argument is: two
binding-form/collection-expr pairs for every x and y position
within the square of the ants' home.
- The =for= function's second argument is the body-expression, here
wrapped in the [[http://clojuredocs.org/clojure_core/clojure.core/do][do special form]] which ensures order of evaluation
(usually, of expressions having side-effects): designate the place
as a home position, then create an ant on that place with a random
initial direction.
In sum, the =setup= function shows how to deal with state and its
mutation in Clojure: we started with a 2-D world-board of places
(cells) as Clojure refs; then we modify/mutate each place using
=alter=. We can use various looping functions such as =dotimes= and
=doall= to process a batch of state-mutations (of the world-board)
atomically and consistently.
** Orientation and moving around the world
Next, consider facing/orientation and moving to another place in the
2-D world. Three functions below, followed by explanations:
#+name world-wrapping
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn bound
"Returns given n, wrapped into range 0-b"
[b n]
(let [n (rem n b)]
(if (neg? n)
(+ n b)
n)))
;; Directions are 0-7, starting at north and going clockwise. These are
;; the 2-D deltas in order to move one step in a given direction.
(def direction-delta {0 [0 -1]
1 [1 -1]
2 [1 0]
3 [1 1]
4 [0 1]
5 [-1 1]
6 [-1 0]
7 [-1 -1]})
(defn delta-location
"Returns the location one step in the given direction. Note the
world is a torus."
[[x y] direction]
(let [[dx dy] (direction-delta (bound 8 direction))]
[(bound dim (+ x dx)) (bound dim (+ y dy))]))
#+END_SRC
With the 2-D world board, we have the 8 cardinal directions (North,
North-East, East, etc.), and board edges that wrap-around to the
opposite side - like the old arcade games of the 1980's, e.g. [[http://en.wikipedia.org/wiki/Pac-Man][Pac-Man]]
and [[http://en.wikipedia.org/wiki/Asteroids_(video_game)][Asteroids]]. The functions =bound= and =delta-location= help enforce
these world-behaviors, while the definition of =direction-delta= maps
a movement in a cardinal direction to the corresponding change in x
and y. A few comments on each:
- The =bound= function using the built-in [[http://clojuredocs.org/clojure_core/clojure.core/rem][rem (i.e. remainder)
function]] is straightforward. Observe how =bound= is used in
delta-location to ensure wrap-around behavior in: 1) cardinal
directions; 2) the world-board, at its edges given by =dim=.
- =direction-delta= maps the eight cardinal directions (0 is North) to
the corresponding changes in =[x y]=. Note the syntax: it's an
array-map literal, where the order of insertion of key-value pairs
(here, keys 0-7) will be preserved.
- =delta-location= takes the current =[x y]= location and a direction,
returning the new corresponding location on the world-board.
** Ant-agent behavior functions
In Hickey's simulation, ants need to move (rotation and translation),
pick up and drop-off food, and make rudimentary decisions.
*** Ant movements
Our ants need two behaviors to get around their world: turning (or
changing the direction they "face"), and stepping forward. Let's deal
with turning first:
#+name ant-agent-turn
#+BEGIN_SRC clojure :exports code :results silent :session s1
;; An ant agent tracks the location of an ant, and controls the
;; behavior of the ant at that location.
(defn turn
"Turns the ant at the location by the given amount."
[loc amt]
(dosync
(let [p (place loc)
ant (:ant @p)]
(alter p assoc :ant (assoc ant :dir (bound 8 (+ (:dir ant) amt))))))
loc)
#+END_SRC
The =turn= function takes two arguments, location and the amount of
turn. What's interesting is the usage of [[http://clojuredocs.org/clojure_core/clojure.core/dosync][the dosync function]], which
ensures the ant's turn - the changes of state within the =assoc=
function calls - is all-or-nothing. The ant gets a new direction per
the innermost =assoc=, then the outermost =assoc= updates the =place=
with the updated ant.
Now for actual movement to a new place:
#+name ant-agent-move
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn move
"Moves the ant in the direction it is heading. Must be called in a
transaction that has verified the way is clear."
[startloc]
(let [oldp (place startloc)
ant (:ant @oldp)
newloc (delta-location startloc (:dir ant))
newp (place newloc)]
;; move the ant
(alter newp assoc :ant ant)
(alter oldp dissoc :ant)
;; leave pheromone trail
(when-not (:home @oldp)
(alter oldp assoc :pher (inc (:pher @oldp))))
newloc))
#+END_SRC
The =move= function changes state of both the ant and board, thus the
doc-string note that it must be called in a transaction. The code is
self-explanatory, though if "pheromone" is a new term to you, you'll
want to [[http://en.wikipedia.org/wiki/Pheromone][learn about a dominant form of chemical communication]] on
Earth. Whenever our artificial ant is not within its home, it will
"secrete" pheromone (=inc= the =:pher= value by 1) at the place it
just left, making it easier (more likely) for it and other ants to
travel between home and food locations in the future (instead of doing
a completely random walk).
*** Ants and food
When an ant finds food, it "picks up" one unit of it; when it returns
home with a food unit, it will "drop" its food there. These two
interactions (each having two steps) change the board, and as with the
=move= function, they need to occur atomically (all-or-nothing) to
ensure the [[http://www.youtube.com/watch?v=z_KmNZNT5xw][world is in a consistent state]].
#+name ant-agent-food
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn take-food [loc]
"Takes one food from current location. Must be called in a
transaction that has verified there is food available."
(let [p (place loc)
ant (:ant @p)]
(alter p assoc
:food (dec (:food @p))
:ant (assoc ant :food true))
loc))
(defn drop-food [loc]
"Drops food at current location. Must be called in a
transaction that has verified the ant has food."
(let [p (place loc)
ant (:ant @p)]
(alter p assoc
:food (inc (:food @p))
:ant (dissoc ant :food))
loc))
#+END_SRC
Notice how similar the structure is for the two functions above;
possibly they're candidates for macro refactoring.
*** Ant judgment
Our ants need some decision-making for their overall task of finding
food and bringing it home. As we'll see shortly, an ant's behavior
is based on two states, either:
1. The ant does not have food, and is looking for it. In this mode, it
weighs the three map locations ahead of it (ahead, ahead-left,
ahead-right) by the presence of either food or pheromone.
2. The ant has food, and needs to bring it to the home box/location.
Now it weighs which of the three ahead-positions to take by the
presence of pheromone, or home.
So we need functions to express preference of the next location for an
ant. The functions =rank-by= and =wrand= help with that.
#+name ant-agent-judgment-1
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn rank-by
"Returns a map of xs to their 1-based rank when sorted by keyfn."
[keyfn xs]
(let [sorted (sort-by (comp float keyfn) xs)]
(reduce (fn [ret i] (assoc ret (nth sorted i) (inc i)))
{} (range (count sorted)))))
#+END_SRC
The =rank-by= function gives weights to where an ant will move next in
the simulation world. It takes two arguments, =keyfn= and =xs= - but
what do those args look like, and where is =rank-by= used? In the
=behave= function below; you'll see that the =keyfn= checks for the
presence of =:food=, =:pher=, or =:home= - in the three cells (board
locations) of the =xs= vector of =[ahead ahead-left ahead-right]=.[fn:Mutex-cell-values]
- The [[http://clojuredocs.org/clojure_core/clojure.core/sort-by][(sort-by keyfn coll) function]] returns a sorted sequence of items
in coll, ordered by comparing =(keyfn item)=. Here, for the local
value sorted, it will be ascending order of cells/places, by
their :food/:home/:pher values - each of those is valuable to an ant
depending on whether it's looking for food, or bringing it home.
- The [[http://clojuredocs.org/clojure_core/clojure.core/reduce][(reduce f initial-val coll) functionn]] in its 3-arguments form
here has its 1st argument =f= as a function taking two arguments, the
current/initial-val value and the next/first item from coll. In this
case, it will "build-up" a map from the local sorted value, with the
keys being the ranked cells/places, and the values being integers 1,
2 and 3. To get a sense of what's going on, try this on your Clojure
REPL:
#+BEGIN_SRC clojure
(let [sorted [0 0.7 1.0]]
(reduce (fn [ret i] (assoc ret (nth sorted i) (inc i)))
{}
(range (count sorted))))
;; You should see {1.0 3, 0.7 2, 0 1}
;;
;; Within the behave function below, the return value might be
;; like {<cell-ahead-left> 3, <cell-ahead-right> 2, <cell-ahead> 1}
;; or similar.
#+END_SRC
[fn:Mutex-cell-values] Remember that =:food=, =:pher=, and =:home= are mutually exclusive in a cell. When an ant wants to go home with food, and the home cell(s) is ahead of it, it will always go home, there won't be competing =:pher= presence.
Next: The =wrand= function helps with the larger task of randomizing
which location/cell the ant moves to next in a weighted manner; i.e.
the "dice" are loaded with =rank-by=, then "rolled" here:
#+name ant-agent-judgment-2
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn wrand
"Given a vector of slice sizes, returns the index of a slice given a
random spin of a roulette wheel with compartments proportional to
slices."
[slices]
(let [total (reduce + slices)
r (rand total)]
(loop [i 0 sum 0]
(if (< r (+ (slices i) sum))
i
(recur (inc i) (+ (slices i) sum))))))
#+END_SRC
How is =wrand= used? Like =rank-by=, look in the =behave= function:
its single argument of slices is a vector of 3 integers (from
=rank-by= above), corresponding to the relative desirability of the 3
cells ahead of the ant. So if the slices argument looked like =[0 3
1]=, that would correspond to zero probability of moving ahead, and
3/4 chance moving to the ahead-left cell over the ahead-right cell.
- The =let= value =total= uses =reduce= to set the upper bound on the
random number; loosely like setting the maximum number of faces on
the die to be rolled (albeit that some die numbers are geometrically
impossible).
- The [[http://clojuredocs.org/clojure_core/clojure.core/rand][rand function]] returns a random floating point number from 0
(inclusive) to n (exclusive).
- Here's the only looping construct in the entire ants program: it's
analogous to checking which compartment of the roulette wheel the
ball fell in. The =if= checks if =r= "fell into" the current
pocket - the size of which is given by =(slices i)=. If yes, return
the index corresponding to that pocket; if not, check the next
pocket/slice.
*** Tying it all together: the =behave= function for ants
The =behave= function below is the largest one, so it helps to keep in
mind its main parts while diving into details:
1. =let= values - help with readability.
2. =Thread/sleep= - helps slow down ants in the UI display.
3. =dosync= - ensures ants behavior is transactional, all-or-nothing.
4. =if= branch: main logic for an ant, if ant has =:food= take it
home, otherwise look for food.
Also, consider the context of how =behave= is first used: within the
main invocation at the end, there's the expression:
src_clojure{(dorun (map #(send-off % behave) ants))}
So the =behave= function is called on every ant agent via the [[http://clojuredocs.org/clojure_core/clojure.core/send-off][send-off
function]], which is how Clojure dispatches potentially blocking actions
to agents. And there certainly are potentially blocking actions when
using =behave=, since ants may try to move into the same cell, try to
acquire the same food, etc.
#+name ant-agent-behave
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn behave
"The main function for the ant agent."
[loc]
(let [p (place loc)
ant (:ant @p)
ahead (place (delta-location loc (:dir ant)))
ahead-left (place (delta-location loc (dec (:dir ant))))
ahead-right (place (delta-location loc (inc (:dir ant))))
places [ahead ahead-left ahead-right]]
;; Old way of Java interop: (. Thread (sleep ant-sleep-ms))
;; New idiomatic way is,
(Thread/sleep ant-sleep-ms)
(dosync
(when running
(send-off *agent* #'behave))
(if (:food ant)
;; Then take food home:
(cond
(:home @p)
(-> loc drop-food (turn 4))
(and (:home @ahead) (not (:ant @ahead)))
(move loc)
:else
(let [ranks (merge-with +
(rank-by (comp #(if (:home %) 1 0) deref) places)
(rank-by (comp :pher deref) places))]
(([move #(turn % -1) #(turn % 1)]
(wrand [(if (:ant @ahead) 0 (ranks ahead))
(ranks ahead-left) (ranks ahead-right)]))
loc)))
;; No food, go foraging:
(cond
(and (pos? (:food @p)) (not (:home @p)))
(-> loc take-food (turn 4))
(and (pos? (:food @ahead)) (not (:home @ahead)) (not (:ant @ahead)))
(move loc)
:else
(let [ranks (merge-with +
(rank-by (comp :food deref) places)
(rank-by (comp :pher deref) places))]
(([move #(turn % -1) #(turn % 1)]
(wrand [(if (:ant @ahead) 0 (ranks ahead))
(ranks ahead-left) (ranks ahead-right)]))
loc)))))))
#+END_SRC
**** The =let= values
The =let= values: quite straightforward, just note the twist in how
=behave= receives a cell/location as its argument, not an ant (which
an OO-centric design might expect).
**** The only JVM/concurrency leakage: =Thread/sleep=
The src_clojure{(. Thread (sleep ant-sleep-ms))}, or
src_clojure{(Thread/sleep ant-sleep-ms)} call is our first encounter
with [[http://clojure.org/java_interop][Clojure's Java Interop]].
- The first version uses [[http://clojure.org/java_interop#Java Interop-The Dot special form][the dot special form]] and in particular, the
src_clojure{(. Classname-symbol (method-symbol args*))} format, with
=Thread= as the Classname-symbol, and =sleep= as the method-symbol.
- However, outside of macros, the idiomatic form for accessing method
members is the second form, src_clojure{(Classname/staticMethod args*)}
- Beyond syntax, the point of this expression is to slow down an ant
(one ant-agent per thread) between their movements, so you can see
in the UI what they're doing, and they'll appear more realistic.
But more interesting still: in this highly concurrent program, the
=sleep= expression is about the *only explicit reference to threads*
in the entire code, i.e. one of the very few "leaky abstractions"
hinting at Clojure's use of underlying JVM concurrency constructs.
Besides this call, there are no locks, and no explicit thread
allocations.
**** The main =dosync= call
Next, let's look at what's going on within the =dosync= transaction.
***** Repeating asynchronously, without looping
The first expression is:
src_clojure{(when running (send-off *agent* #'behave))}
Initially this may seem strange; aren't we in the =behave= function
because =send-off= already called it before entering it? Won't this
just loop uselessly, not hitting the core =if= code below? Not quite:
- Instead, =send-off= adds another execution of =behave= to the
current agent's *queue* of work/functions, and immediately returns.
- The current agent is referenced by the asterisk-surrounded
~*agent*~ which Clojure dynamically binds to the current active
agent on a thread-local basis.
- Thus after finishing this call of =behave= the ant will do another
action (execute =behave= again), and another, and so on. No explicit
looping, just *queue and repeat*.
Also, note the ~#'~ sharp-quote, before =behave=; this is a Clojure
Var, one of Clojure's mutable reference types. It's just syntactic
sugar for =(var behave)=. Invoking a Var referring to a function is
the same as invoking the function itself...so why bother with it? I
don't know; here's what I could find:
- Besides Clojure docs, this SO thread also suggests there's no
difference, "Apply a =var= is the same as applying the value store
in the =var=."
http://stackoverflow.com/questions/9760480/in-clojure-difference-between-function-quoted-function-and-sharp-quote-functio
- Maybe the #' prefix on =behave= causes the current thread's value
of the function (with the current ant/location) to be sent to the
queue? NO/unlikely. If it was mean to be a dynamic var, it would
have asterisks around it like =*agent*=.
Why use =send-off= instead of =send= ?
- [[http://stackoverflow.com/questions/1646351/what-is-the-difference-between-clojures-send-and-send-off-functions-with-re][send vs. send-off]] - =send= uses threadpool of fixed size which has
low switching overhead but blocking can dry up the threadpool. By
contrast, =send-off= uses a dynamic threadpool and blocking is
tolerated - and that's the right approach here as ant contention for
the same location/food can certainly cause (temporary) blocking.
- http://stackoverflow.com/questions/5964997/clojure-agent-question-using-send-off
***** Determining what the ant does next
Finally, the ant's logic for what to do next is in the large =if=
expression. The code looks dense but at the top level it's just a
binary choice:
+ If the ant has food, take it home; the =cond= specifies 3
sub-cases:
1. At a home cell, drop the food and turn around 180 degrees, to
exit home for more food.
2. If a home cell is ahead, move to it.
3. Otherwise, do a ranking of cells ahead (=places= has the cells
=ahead=, =ahead-left=, =ahead-right=) per presence of pheromones,
or home, and then randomly select from those 3 cells per their
ranking/weighting.
** World behavior: pheromone evaporation
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn evaporate
"Causes all the pheromones to evaporate a bit."
[]
(dorun
(for [x (range dim) y (range dim)]
(dosync
(let [p (place [x y])]
(alter p assoc :pher (* evap-rate (:pher @p))))))))
#+END_SRC
For a bit of realism and a cleaner UI/visual, it's useful to have the
ants' pheromones diminish and evaporate from the world over time.
The =evaporate= function fulfills that requirement:
+ It takes no arguments, it will work over the entire world/board of
cells, accessed via the tuples of =x= and =y=.
+ The =[[http://clojuredocs.org/clojure_core/clojure.core/dorun][dorun]]= function takes a lazy collection/sequence (here, that of
the =for= expression) and forces the realization of that collection
for its side effects, discarding any returned values.
- It's unlike the similarly-named =doall= where we do care about the
values.
- And it's unlike =doseq=, which is like Clojure's =for= but runs
immediately and does not collect the results.
+ =dosync= is used as before, for lock-free updating of a =place=
cell. Here, the desired side-effect/"mutation" is to update the
=:pher= value at the =place= cell with a lower number.
We'll see shortly that =evaporate= will run every second, a process
that (like the ants) will be handled asynchronously using a Clojure
agent.
* The UI
The user interface for the ants relies heavily on Clojure's Java
inter-operation capabilities. But as we'll see, it's more than just
wrapping calls to Java.
** Using the Java AWT
#+BEGIN_SRC clojure :exports code :results silent :session s1
(import
'(java.awt Color Graphics Dimension)
'(java.awt.image BufferedImage)
'(javax.swing JPanel JFrame))
#+END_SRC
The =import= pulls in classes from [[http://docs.oracle.com/javase/6/docs/api/java/awt/package-summary.html][Java's Abstract Window Toolkit]]
(AWT) package, and from the Java Swing package. (Aside: curious [[http://stackoverflow.com/questions/727844/javax-vs-java-package][why
Swing is in the =javax= namespace]]?) Assuming unfamiliarity with Java
Swing, let's describe the classes used:
+ The =[[http://docs.oracle.com/javase/6/docs/api/java/awt/Color.html][Color]]= class encapsulates a color in the standard RGB color
space. In the code below, its usage as a constructor for a color
instance follows several arities:
- 4 integer arguments: r, g, b, and a for the alpha/transparency (0
transparent, 255 opaque)
- 3 integer arguments: r g b
- 1 argument: not a constructor call, but an access of a predefined
static =Color= field by name, returning the color in the RGB color
space.
+ The =[[http://docs.oracle.com/javase/6/docs/api/java/awt/Graphics.html][Graphics]]= class is an abstract base class for all graphics
contexts, i.e. a =Graphics= instance holds the current state data
needed for rendering it: the =[[http://docs.oracle.com/javase/6/docs/api/java/awt/Component.html][Component]]= object on which to draw,
the current clip, color, and font, etc. Below, we'll see that the
Clojure functions that take a =Graphics= instance as an argument:
- =fill-cell=
- =render-ant=
- =render-place=
- =render=
...all do some kind of rendering/drawing.
+ The =[[http://docs.oracle.com/javase/6/docs/api/java/awt/Dimension.html][Dimension]]= class encapsulates the integer width and height of a
component. This class is used just once below, in setting the size
of the panel of the UI.
+ =[[http://docs.oracle.com/javase/6/docs/api/java/awt/image/BufferedImage.html][BufferedImage]]= class is needed for raster image data; below, the
=render= function uses it to paint the background panel.
+ The =[[http://docs.oracle.com/javase/1.4.2/docs/api/javax/swing/JPanel.html][JPanel]]= class is the generic "lightweight" UI container in Java
Swing (seems like the =div= element in HTML). Below, it's used just
once for the main display.
+ The =[[http://docs.oracle.com/javase/1.4.2/docs/api/javax/swing/JFrame.html][JFrame]]= class creates a top-level window (w/ title and border)
in Swing; it's used just once below for the main ants UI window.
** Functions to render the board and the ants
Each discrete cell on the world board is a square matrix of pixels;
with an odd number of pixels chosen, we can have a central position:
#+BEGIN_SRC clojure :exports code :results silent :session s1
(def scale 5) ; A world cell is 5x5 pixels.
#+END_SRC
By default, cells are empty; drawing cells having food or
ant-deposited pheromones is done by filling with symbolic colors -
here by running the Java methods =setColor= and =fillRect=:
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn fill-cell [#^Graphics g x y c]
(doto g
(.setColor c)
(.fillRect (* x scale) (* y scale) scale scale)))
#+END_SRC
Note the use of the =[[http://clojuredocs.org/clojure_core/clojure.core/doto][doto]]= function here and in many places below: in
Java, procedural mutation of a newly constructed instance is common
for initialization. Clojure's =doto= function is meant to be more
concise in specifying the target object just once, and then
methods/setters acting on it and then returning it, implicitly.
Drawing an ant: the graphical appearance of an ant is just a (5-pixel
long) line pointing in one of the 8 cardinal directions, of two
different colors (having food or not):
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn render-ant [ant #^Graphics g x y]
(let [black (. (new Color 0 0 0 255) (getRGB))
gray (. (new Color 100 100 100 255) (getRGB))
red (. (new Color 255 0 0 255) (getRGB))
[hx hy tx ty] ({0 [2 0 2 4] ; Up/North pointing
1 [4 0 0 4]
2 [4 2 0 2]
3 [4 4 0 0]
4 [2 4 2 0] ; Down/South
5 [0 4 4 0]
6 [0 2 4 2]
7 [0 0 4 4]}
(:dir ant))]
(doto g
(.setColor (if (:food ant)
(new Color 255 0 0 255)
(new Color 0 0 0 255)))
(.drawLine (+ hx (* x scale)) (+ hy (* y scale))
(+ tx (* x scale)) (+ ty (* y scale))))))
#+END_SRC
Note the cleverly concise destructuring for the start and end drawing
coordinates, needed in AWT's =[[http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/Graphics.html#drawLine%28int,%20int,%20int,%20int%29][drawLine]]= method.
If a cell in the ants' world is not empty, it has one or more of three
things present: pheromone, food, or an ant. The =render-place=
function updates the cell's appearance accordingly:
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn render-place [g p x y]
(when (pos? (:pher p))
(fill-cell g x y (new Color 0 255 0
(int (min 255 (* 255 (/ (:pher p) pher-scale)))))))
(when (pos? (:food p))
(fill-cell g x y (new Color 255 0 0
(int (min 255 (* 255 (/ (:food p) food-scale)))))))
(when (:ant p)
(render-ant (:ant p) g x y)))
#+END_SRC
Finally, the =render= function ties everything together: initializing
the UI/window appearance by applying =render=place= to every cell, and
also drawing the home space of the ants. Note the heavy usage of the
dot special form: the UI code relies heavily on Java, though Clojure's
=for= and =doto= help us avoid Java boilerplate and stay concise:
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn render [g]
(let [v (dosync (apply vector (for [x (range dim) y (range dim)]
@(place [x y]))))
img (new BufferedImage (* scale dim) (* scale dim)
(. BufferedImage TYPE_INT_ARGB))
bg (. img (getGraphics))]
;; First paint everything white, on the bg instance:
(doto bg
(.setColor (. Color white))
(.fillRect 0 0 (. img (getWidth)) (. img (getHeight))))
(dorun
(for [x (range dim) y (range dim)]
(render-place bg (v (+ (* x dim) y)) x y)))
;; Draw the home space of the ants:
(doto bg
(.setColor (. Color blue))
(.drawRect (* scale home-offset) (* scale home-offset)
(* scale nants-sqrt) (* scale nants-sqrt)))
(. g (drawImage img 0 0 nil))
(. bg (dispose)))) ; Finished using Graphics object, release it.
#+END_SRC
** Setting the scene, then updating it continually
Almost ready to begin our simulation; we need to setup some additional
elements per AWT conventions: the main UI =panel= where visual changes
take place, the top-level window =frame=, and an =animator= agent that
continually updates the visual elements:
#+BEGIN_SRC clojure :exports code :results silent :session s1
(def panel (doto
(proxy [JPanel] [] (paint [g] (render g)))
(.setPreferredSize (new Dimension
(* scale dim)
(* scale dim)))))
(def frame (doto (new JFrame) (.add panel) .pack .show))
(def animator (agent nil))
#+END_SRC
*** Animation, panel-by-panel
Now for bringing the static starting "picture" to life - like the
cartoons of old, the =animation= function will "draw" the next state
of the main panel displaying the ants. Below, Hickey uses the
queue-itself-then-run, again-and-again code pattern we've seen before
(above, in updating an ant's state):
#+BEGIN_SRC clojure :exports code :results silent :session s1
(defn animation [x]
(when running
(send-off *agent* #'animation))
(. panel (repaint))
(. Thread (sleep animation-sleep-ms))
nil)
#+END_SRC
Finally, we need another agent to handle one more time-track of
changes: evaporation, using the =evaporate= function defined above.
#+BEGIN_SRC clojure :exports code :results silent :session s1
(def evaporator (agent nil))
(defn evaporation [x]
(when running
(send-off *agent* #'evaporation))
(evaporate)
(. Thread (sleep evap-sleep-ms))
nil)
#+END_SRC
* Running the Program
** The =project.clj= file
When you tangle this file, the local =project.clj= file will be
created alongside =ants.clj=. Assuming you've installed the excellent
[[http://leiningen.org/][Leiningen]], you'd then:
1. Enter =lein deps= at the shell prompt to get dependencies.
2. Then you can start a REPL with =lein repl=, from which you can
start the simulator (see next section).
#+BEGIN_SRC clojure :tangle project.clj
(defproject literate-ants "1.0.0-SNAPSHOT"
:description "This is a literate version of: Rich Hickey's Ants simulator, demonstrating Clojure's concurrency support."
:dev-dependencies []
:dependencies [[org.clojure/clojure "1.5.1"]]
)
#+END_SRC
** Running the simulator
At the REPL, you can enter the entire =do= expression below, or try
each line within it separately:
#+BEGIN_SRC clojure :tangle no
(do
(load-file "./literate-ants.clj")
(def ants (setup))
(send-off animator animation)
(dorun (map #(send-off % behave) ants))
(send-off evaporator evaporation))
#+END_SRC
Either way you'll see a new window appear with a white background,
blue square representing the ants' home, red squares of food, black or
red (w/ food) moving lines representing each ant, and green squares
for pheromones in various concentrations. A lot happening
concurrently, with no locks, and beautifully concise code - welcome to
Clojure!
** Unused :ARCHIVE:NOEXPORT:
#+BEGIN_SRC clojure :exports code :results silent :session s1 :tangle no
(comment
;demo
(load-file "/Users/rich/dev/clojure/ants.clj")
(def ants (setup))
(send-off animator animation)
(dorun (map #(send-off % behave) ants))
(send-off evaporator evaporation)
)
#+END_SRC
#+name: ants
#+BEGIN_SRC clojure :tangle no :exports none :noweb yes
<<sim-world-setup>>
<<sim-world-board-creation>>
<<ants-defined>>
#+end_src
================================================
FILE: 02-minimal-clojure-app/clojure-app-skeleton.org
================================================
#+TITLE: Clojure App Skeleton, using Org literate programming
#+AUTHOR: Kai Wu
#+EMAIL: k@limist.com
#+LANGUAGE: en
#+STARTUP: align overview indent fold nodlcheck hidestars oddeven lognotestate
#+PROPERTY: mkdirp yes
* Meta: this file, Clojure + Org → LP, etc.
You're looking at a literate programming (LP) file, specifically an
[[http://orgmode.org][Org mode]] formatted file combining both documentation (Org's structured
markup) and code blocks (Clojure code).
For best results please *use Emacs 24.3 or later to view this* =.org=
*file*. If you're looking at this on Github.com, STOP - the rendering
there is neither complete nor correct!
** The *benefits* of LP using Emacs + Org
1. Docs matter, a lot. With LP, documentation is integral to
development, never an afterthought.
- For all but small throwaway systems, you're likely keeping a
separate file of development notes already; LP would integrate
that.
2. With one LP file, avoid the incidental/inessential complexity of
the filesystem: avoid context-switch overhead moving between files,
and sidestep your language's imposed filesystem structure.
3. Org rocks for prose:
- Org's plain-text *markup is lightweight*, yet more powerful than
Markdown, and cleaner than rST.
- The *structural editing* provided by Org documents lets you
organize your thoughts/writing/code very quickly. With good
structure even major revisions are easy.
- Org's exporter lets your *write-once, express-many-times*: you
can export an Org file to HTML (e.g. for blogging) or LaTeX
(for serious publishing).
- It's easy to version-control Org files.
4. Org rocks for code:
- Each code block has flexible granularity: can be named and
referred to; evaluated or not; have data sent in or exported;
specify different REPL sessions; specify different target/tangled
files.
- Code blocks are syntax-highlighted.
- Code blocks are ready to edit: jump to major-mode editing easily.
- A single Org file can mix different languages.
5. Meta-development, manage complexity from a coherent perspective: a
unified, single-file approach encourages holistic software
development and exposition, in a natural order, using structure to
enhance understanding. LP is not just documentation and code
together: it's a *process and abstraction unifying the development
lifecycle*: requirements, architecture, design, code, tests,
deployment, and maintenance - can all be bound coherently in one
active format.
* Using this file
** Prerequisites
1. A recent version of Emacs, 24.3+.
2. Both org-mode (included w/ Emacs 24) and =clojure-mode= installed;
use Emacs ELPA as needed.
- Consider using an Emacs "starter package" that provides a good
baseline, like [[http://batsov.com/prelude/][Emacs Prelude]] or [[http://overtone.github.io/emacs-live/][Emacs Live]].
Then if you start Emacs and load this file, you'll see it the way it's
meant to be seen: as a multi-level, hierarchically organized and
structured literate code file, w/ syntax-highlighted code blocks.
** Weaving and tangling
To use the original Knuth terminology, this single file can be /woven/
into documentation, or /tangled/ to code.
*** Weave/export, to documentation
+ To /weave/: the Org equivalent of /weaving/ is to export this file,
typically to HTML or LaTeX/PDF. The keystroke is =C-c-e= i.e. hold
down the Control key while pressing "c" then "e" to view the export
options.
- e.g. export this file to HTML with =CTRL-c-e h= or, to see it
immediately in a browser window, =CTRL-c-e b=.
+ You don't have to export this file though; if/when you're comfy in
Emacs, the Org format itself is great.
*** Tangle, to code
+ To /tangle/: in Org, it's the same word/term. =C-c-v-t= will cause
all designated code blocks in this file to appear in the filesystem.
Here, the code blocks go to files and directories matching a new
Clojure app, as would be produced by =lein new app
the-project-name=.
** Other coolness
- =SHIFT-TAB= will *cycle* the display: top-level headings only, all
headings, or fully-expanded.
- Within a code block, =CTRL-c= ='= will open a buffer to edit the
code. For full power, be sure =clojure-mode=, =paredit=, and
=nrepl= are installed.
- Org docs: see [[http://orgmode.org/org.html][main documentation]], especially sections on [[http://orgmode.org/org.html#Document-Structure][structure]],
[[http://orgmode.org/org.html#Hyperlinks][links]], [[http://orgmode.org/org.html#Markup][markup]], and [[http://orgmode.org/org.html#Working-With-Source-Code][literate programming]] features.
* Project meta
** Project definition
#+BEGIN_SRC clojure :tangle project.clj
(defproject skeleton-app "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]]
:main skeleton-app.core
:profiles {:uberjar {:aot :all}})
#+END_SRC
** The README
It would be nice to auto-generate the README.md from selected parts of
this =org= file; TBD how.
#+BEGIN_SRC markdown :tangle README.md
# skeleton-app
FIXME: description
## Installation
Download from http://example.com/FIXME.
## Usage
FIXME: explanation
$ java -jar skeleton-app-0.1.0-standalone.jar [args]
## Options
FIXME: listing of options this app accepts.
## Examples
...
### Bugs
...
### Any Other Sections
### That You Think
### Might be Useful
## License
Copyright © 2013 FIXME
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.
#+END_SRC
** License notice
#+BEGIN_SRC text :tangle LICENSE
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and
documentation distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates' from
a Contributor if it was added to the Program by such Contributor itself or
anyone acting on such Contributor's behalf. Contributions do not include
additions to the Program which: (i) are separate modules of software
distributed in conjunction with the Program under their own license
agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly perform,
distribute and sublicense the Contribution of such Contributor, if any, and
such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of the
Contribution and the Program if, at the time the Contribution is added by the
Contributor, such addition of the Contribution causes such combination to be
covered by the Licensed Patents. The patent license shall not apply to any
other combinations which include the Contribution. No hardware per se is
licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses
to its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor disclaims
any liability to Recipient for claims brought by any other entity based on
infringement of intellectual property rights or otherwise. As a condition to
exercising the rights and licenses granted hereunder, each Recipient hereby
assumes sole responsibility to secure any other intellectual property rights
needed, if any. For example, if a third party patent license is required to
allow Recipient to distribute the Program, it is Recipient's responsibility
to acquire that license before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright license
set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under
its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties and
conditions, express and implied, including warranties or conditions of title
and non-infringement, and implied warranties or conditions of merchantability
and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and consequential
damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are offered
by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable manner on
or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained within
the Program.
Each Contributor must identify itself as the originator of its Contribution,
if any, in a manner that reasonably allows subsequent Recipients to identify
the originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a
manner which does not create potential liability for other Contributors.
Therefore, if a Contributor includes the Program in a commercial product
offering, such Contributor ("Commercial Contributor") hereby agrees to defend
and indemnify every other Contributor ("Indemnified Contributor") against any
losses, damages and costs (collectively "Losses") arising from claims,
lawsuits and other legal actions brought by a third party against the
Indemnified Contributor to the extent caused by the acts or omissions of such
Commercial Contributor in connection with its distribution of the Program in
a commercial product offering. The obligations in this section do not apply
to any claims or Losses relating to any actual or alleged intellectual
property infringement. In order to qualify, an Indemnified Contributor must:
a) promptly notify the Commercial Contributor in writing of such claim, and
b) allow the Commercial Contributor tocontrol, and cooperate with the
Commercial Contributor in, the defense and any related settlement
negotiations. The Indemnified Contributor may participate in any such claim
at its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If
that Commercial Contributor then makes performance claims, or offers
warranties related to Product X, those performance claims and warranties are
such Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a
court requires any other Contributor to pay any damages as a result, the
Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
appropriateness of using and distributing the Program and assumes all risks
associated with its exercise of rights under this Agreement , including but
not limited to the risks and costs of program errors, compliance with
applicable laws, damage to or loss of data, programs or equipment, and
unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of the
remainder of the terms of this Agreement, and without further action by the
parties hereto, such provision shall be reformed to the minimum extent
necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted
under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and
does not cure such failure in a reasonable period of time after becoming
aware of such noncompliance. If all Recipient's rights under this Agreement
terminate, Recipient agrees to cease use and distribution of the Program as
soon as reasonably practicable. However, Recipient's obligations under this
Agreement and any licenses granted by Recipient relating to the Program shall
continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to
time. No one other than the Agreement Steward has the right to modify this
Agreement. The Eclipse Foundation is the initial Agreement Steward. The
Eclipse Foundation may assign the responsibility to serve as the Agreement
Steward to a suitable separate entity. Each new version of the Agreement will
be given a distinguishing version number. The Program (including
Contributions) may always be distributed subject to the version of the
Agreement under which it was received. In addition, after a new version of
the Agreement is published, Contributor may elect to distribute the Program
(including its Contributions) under the new version. Except as expressly
stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
licenses to the intellectual property of any Contributor under this
Agreement, whether expressly, by implication, estoppel or otherwise. All
rights in the Program not expressly granted under this Agreement are
reserved.
This Agreement is governed by the laws of the State of Washington and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial
in any resulting litigation.
#+END_SRC
* Requirements
** The user-story
Max touches the GO button using this app, and justly rebuilds the
world with Clojure.
** Non-user-visible requirements
1. Logging
2. Security
* Architecture
1. Foo back-end
2. Bar front-end
- Probably use a Pedestal App client
| Part | Description | Alternatives |
|------+-----------------------------+---------------|
| Bar | Bar presents via D3 charts | Flash? Nein!! |
| Foo | Foo has the data we worship | |
| | | |
* Design
** Foo design
** Bar design
* Source-code
The default =lein new app *= command just produces two files with
actual Clojure code.
** core
#+NAME: core
#+BEGIN_SRC clojure :tangle src/skeleton_app/core.clj
(ns skeleton-app.core
(:gen-class))
(defn -main
"I don't do a whole lot ... yet."
[& args]
(println "Hello, World!"))
#+END_SRC
*** Tests
#+BEGIN_SRC clojure :tangle test/skeleton_app/core_test.clj
(ns skeleton-app.core-test
(:require [clojure.test :refer :all]
[skeleton-app.core :refer :all]))
(deftest a-test
(testing "FIXME, I fail."
(is (= 0 1))))
#+END_SRC
================================================
FILE: 02-minimal-clojure-project/clojure-default-skeleton.org
================================================
#+TITLE: Clojure Default/Project Skeleton, Using Org Literate Programming
#+AUTHOR: Kai Wu
#+EMAIL: k@limist.com
#+LANGUAGE: en
#+STARTUP: align overview indent fold nodlcheck hidestars oddeven lognotestate
#+PROPERTY: mkdirp yes
* Meta: this file, Clojure + Org → LP, etc.
You're looking at a literate programming (LP) file, specifically an
[[http://orgmode.org][Org mode]] formatted file combining both documentation (Org's structured
markup) and code blocks (Clojure code).
For best results please *use Emacs 24.3 or later to view this* =.org=
*file*. If you're looking at this on Github.com, STOP - the rendering
there is neither complete nor correct!
** The *benefits* of LP using Emacs + Org
1. Docs matter, a lot. With LP, documentation is integral to
development, never an afterthought.
- For all but small throwaway systems, you're likely keeping a
separate file of development notes already; LP would integrate
that.
2. With one LP file, avoid the incidental/inessential complexity of
the filesystem: avoid context-switch overhead moving between files,
and sidestep your language's imposed filesystem structure.
3. Org rocks for prose:
- Org's plain-text *markup is lightweight*, yet more powerful than
Markdown, and cleaner than rST.
- The *structural editing* provided by Org documents lets you
organize your thoughts/writing/code very quickly. With good
structure even major revisions are easy.
- Org's exporter lets your *write-once, express-many-times*: you
can export an Org file to HTML (e.g. for blogging) or LaTeX
(for serious publishing).
- It's easy to version-control Org files.
4. Org rocks for code:
- Each code block has flexible granularity: can be named and
referred to; evaluated or not; have data sent in or exported;
specify different REPL sessions; specify different target/tangled
files.
- Code blocks are syntax-highlighted.
- Code blocks are ready to edit: jump to major-mode editing easily.
- A single Org file can mix different languages.
5. Meta-development, manage complexity from a coherent perspective: a
unified, single-file approach encourages holistic software
development and exposition, in a natural order, using structure to
enhance understanding. LP is not just documentation and code
together: it's a *process and abstraction unifying the development
lifecycle*: requirements, architecture, design, code, tests,
deployment, and maintenance - can all be bound coherently in one
active format.
* Using this file
** Prerequisites
1. A recent version of Emacs, 24.3+.
2. Both org-mode (included w/ Emacs 24) and =clojure-mode= installed;
use Emacs ELPA as needed.
- Consider using an Emacs "starter package" that provides a good
baseline, like [[http://batsov.com/prelude/][Emacs Prelude]] or [[http://overtone.github.io/emacs-live/][Emacs Live]].
Then if you start Emacs and load this file, you'll see it the way it's
meant to be seen: as a multi-level, hierarchically organized and
structured literate code file, w/ syntax-highlighted code blocks.
** Weaving and tangling
To use the original Knuth terminology, this single file can be /woven/
into documentation, or /tangled/ to code.
*** Weave/export, to documentation
+ To /weave/: the Org equivalent of /weaving/ is to export this file,
typically to HTML or LaTeX/PDF. The keystroke is =C-c-e= i.e. hold
down the Control key while pressing "c" then "e" to view the export
options.
- e.g. export this file to HTML with =CTRL-c-e h= or, to see it
immediately in a browser window, =CTRL-c-e b=.
+ You don't have to export this file though; if/when you're comfy in
Emacs, the Org format itself is great.
*** Tangle, to code
+ To /tangle/: in Org, it's the same word/term. =C-c-v-t= will cause
all designated code blocks in this file to appear in the filesystem.
Here, the code blocks go to files and directories matching a new
Clojure app, as would be produced by =lein new app
the-project-name=.
** Other coolness
- =SHIFT-TAB= will *cycle* the display: top-level headings only, all
headings, or fully-expanded.
- Within a code block, =CTRL-c= ='= will open a buffer to edit the
code. For full power, be sure =clojure-mode=, =paredit=, and
=nrepl= are installed.
- Org docs: see [[http://orgmode.org/org.html][main documentation]], especially sections on [[http://orgmode.org/org.html#Document-Structure][structure]],
[[http://orgmode.org/org.html#Hyperlinks][links]], [[http://orgmode.org/org.html#Markup][markup]], and [[http://orgmode.org/org.html#Working-With-Source-Code][literate programming]] features.
* Project meta
** Project definition
#+BEGIN_SRC clojure :tangle project.clj
(defproject default-skeleton "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]])
#+END_SRC
** The README
It would be nice to auto-generate the README.md from selected parts of
this =org= file; TBD how.
#+BEGIN_SRC markdown :tangle README.md
# default-skeleton
FIXME: description
## Installation
Download from http://example.com/FIXME.
## Usage
FIXME: explanation
$ java -jar default-skeleton-0.1.0-standalone.jar [args]
## Options
FIXME: listing of options this app accepts.
## Examples
...
### Bugs
...
### Any Other Sections
### That You Think
### Might be Useful
## License
Copyright © 2013 FIXME
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.
#+END_SRC
** License notice
#+BEGIN_SRC text :tangle LICENSE
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and
documentation distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates' from
a Contributor if it was added to the Program by such Contributor itself or
anyone acting on such Contributor's behalf. Contributions do not include
additions to the Program which: (i) are separate modules of software
distributed in conjunction with the Program under their own license
agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly perform,
distribute and sublicense the Contribution of such Contributor, if any, and
such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of the
Contribution and the Program if, at the time the Contribution is added by the
Contributor, such addition of the Contribution causes such combination to be
covered by the Licensed Patents. The patent license shall not apply to any
other combinations which include the Contribution. No hardware per se is
licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses
to its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor disclaims
any liability to Recipient for claims brought by any other entity based on
infringement of intellectual property rights or otherwise. As a condition to
exercising the rights and licenses granted hereunder, each Recipient hereby
assumes sole responsibility to secure any other intellectual property rights
needed, if any. For example, if a third party patent license is required to
allow Recipient to distribute the Program, it is Recipient's responsibility
to acquire that license before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright license
set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under
its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties and
conditions, express and implied, including warranties or conditions of title
and non-infringement, and implied warranties or conditions of merchantability
and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and consequential
damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are offered
by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable manner on
or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained within
the Program.
Each Contributor must identify itself as the originator of its Contribution,
if any, in a manner that reasonably allows subsequent Recipients to identify
the originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a
manner which does not create potential liability for other Contributors.
Therefore, if a Contributor includes the Program in a commercial product
offering, such Contributor ("Commercial Contributor") hereby agrees to defend
and indemnify every other Contributor ("Indemnified Contributor") against any
losses, damages and costs (collectively "Losses") arising from claims,
lawsuits and other legal actions brought by a third party against the
Indemnified Contributor to the extent caused by the acts or omissions of such
Commercial Contributor in connection with its distribution of the Program in
a commercial product offering. The obligations in this section do not apply
to any claims or Losses relating to any actual or alleged intellectual
property infringement. In order to qualify, an Indemnified Contributor must:
a) promptly notify the Commercial Contributor in writing of such claim, and
b) allow the Commercial Contributor tocontrol, and cooperate with the
Commercial Contributor in, the defense and any related settlement
negotiations. The Indemnified Contributor may participate in any such claim
at its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If
that Commercial Contributor then makes performance claims, or offers
warranties related to Product X, those performance claims and warranties are
such Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a
court requires any other Contributor to pay any damages as a result, the
Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
appropriateness of using and distributing the Program and assumes all risks
associated with its exercise of rights under this Agreement , including but
not limited to the risks and costs of program errors, compliance with
applicable laws, damage to or loss of data, programs or equipment, and
unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of the
remainder of the terms of this Agreement, and without further action by the
parties hereto, such provision shall be reformed to the minimum extent
necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted
under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and
does not cure such failure in a reasonable period of time after becoming
aware of such noncompliance. If all Recipient's rights under this Agreement
terminate, Recipient agrees to cease use and distribution of the Program as
soon as reasonably practicable. However, Recipient's obligations under this
Agreement and any licenses granted by Recipient relating to the Program shall
continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to
time. No one other than the Agreement Steward has the right to modify this
Agreement. The Eclipse Foundation is the initial Agreement Steward. The
Eclipse Foundation may assign the responsibility to serve as the Agreement
Steward to a suitable separate entity. Each new version of the Agreement will
be given a distinguishing version number. The Program (including
Contributions) may always be distributed subject to the version of the
Agreement under which it was received. In addition, after a new version of
the Agreement is published, Contributor may elect to distribute the Program
(including its Contributions) under the new version. Except as expressly
stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
licenses to the intellectual property of any Contributor under this
Agreement, whether expressly, by implication, estoppel or otherwise. All
rights in the Program not expressly granted under this Agreement are
reserved.
This Agreement is governed by the laws of the State of Washington and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial
in any resulting litigation.
#+END_SRC
* Requirements
** The user-story
Import this library, and enjoy magical powers.
** Non-user-visible requirements
1. Logging
2. Security
* Architecture
1. Data processing,
2. Data storage
3. Long-running service
4. API
| Part | Description | Alternatives |
|---------+-----------------------------------------------+------------------------|
| API | How other Clojure code accesses this library. | |
| Service | Should execute data processing periodically. | Manual, cmd-line start |
| | | |
* Design
** API design
- Always keep it minimal at first! You can add later, but removing
stuff is a pain.
- Don't require the client/user of the API to do anything the library
could do.
- See Joshua Bloch's talk, [[http://limist.com/coding/talk-notes-how-to-design-a-good-api-and-why-it-matters-bloch.html][How to Design an Good API and Why It Matters]]
* Source-code
The =lein new default my-new-library= command just produces two files with
actual Clojure code.
** core
#+NAME: core
#+BEGIN_SRC clojure :tangle src/default_skeleton/core.clj
(ns default-skeleton.core)
(defn foo
"I don't do a whole lot."
[x]
(println x "Hello, World!"))
#+END_SRC
*** Tests
#+BEGIN_SRC clojure :tangle test/default_skeleton/core_test.clj
(ns default-skeleton.core-test
(:require [clojure.test :refer :all]
[default-skeleton.core :refer :all]))
(deftest a-test
(testing "FIXME, I fail."
(is (= 0 1))))
#+END_SRC
================================================
FILE: 03-pedestal-app/pedestal-app-skeleton.org
================================================
#+TITLE: Pedestal App, in Org literate programming form
#+AUTHOR: Kai Wu
#+EMAIL: k@limist.com
#+STARTUP: align overview indent fold nodlcheck hidestars oddeven lognotestate
#+PROPERTY: mkdirp yes
* Project meta
** Project definition
#+BEGIN_SRC clojure :tangle project.clj
(defproject skeleton-app "0.0.1-SNAPSHOT"
:description "FIXME: write description"
:dependencies [[org.clojure/clojure "1.5.1"]
[org.clojure/clojurescript "0.0-1586"]
[domina "1.0.1"]
[ch.qos.logback/logback-classic "1.0.7" :exclusions [org.slf4j/slf4j-api]]
[io.pedestal/pedestal.app "0.1.8"]
[io.pedestal/pedestal.app-tools "0.1.8"]]
:profiles {:dev {:source-paths ["dev"]}}
:min-lein-version "2.0.0"
:source-paths ["app/src" "app/templates"]
:resource-paths ["config"]
:target-path "out/"
:aliases {"dumbrepl" ["trampoline" "run" "-m" "clojure.main/main"]})
#+END_SRC
* Main application
** Behavior
#+BEGIN_SRC clojure :tangle app/src/skeleton_app/behavior.clj
(ns ^:shared skeleton-app.behavior
(:require [clojure.string :as string]
[io.pedestal.app.messages :as msg]))
;; While creating new behavior, write tests to confirm that it is
;; correct. For examples of various kinds of tests, see
;; test/skeleton_app/test/behavior.clj.
(defn set-value-transform [old-value message]
(:value message))
(def example-app
{;; There are currently 2 versions (formats) for dataflow
;; descritpion: the original version (version 1) and the current
;; version (version 2). If the version is not specified, the
;; descritpion will be assumed to be version 1 and an attempt
;; will be made to convert it to version 2.
:version 2
:transform [[:set-value [:greeting] set-value-transform]]})
;; Once this behavior works, run the Data UI and record
;; rendering data which can be used while working on a custom
;; renderer. Rendering involves making a template:
;;
;; app/templates/skeleton-app.html
;;
;; slicing the template into pieces you can use:
;;
;; app/src/skeleton_app/html_templates.cljs
;;
;; and then writing the rendering code:
;;
;; app/src/skeleton_app/rendering.cljs
(comment
;; The examples below show the signature of each type of function
;; that is used to build a behavior dataflow.
;; transform
(defn example-transform [old-state message]
;; returns new state
)
;; derive
(defn example-derive [old-state inputs]
;; returns new state
)
;; emit
(defn example-emit [inputs]
;; returns rendering deltas
)
;; effect
(defn example-effect [inputs]
;; returns a vector of messages which effect the outside world
)
;; continue
(defn example-continue [inputs]
;; returns a vector of messages which will be processed as part of
;; the same dataflow transaction
)
;; dataflow description reference
{:transform [[:op [:path] example-transform]]
:derive #{[#{[:in]} [:path] example-derive]}
:effect #{[#{[:in]} example-effect]}
:continue #{[#{[:in]} example-continue]}
:emit [[#{[:in]} example-emit]]}
)
#+END_SRC
** HTML templates
#+BEGIN_SRC clojure :tangle app/src/skeleton_app/html_templates.clj
(ns skeleton-app.html-templates
(:use [io.pedestal.app.templates :only [tfn dtfn tnodes]]))
(defmacro skeleton-app-templates
[]
;; Extract the 'hello' template from the template file skeleton-app.html.
;; The 'dtfn' function will create a dynamic template which can be
;; updated after it has been attached to the DOM.
;;
;; To see how this template is used, refer to
;;
;; app/src/skeleton_app/rendering.cljs
;;
;; The last argument to 'dtfn' is a set of fields that should be
;; treated as static fields (may only be set once). Dynamic templates
;; use ids to set values so you cannot dynamically set an id.
{:skeleton-app-page (dtfn (tnodes "skeleton-app.html" "hello") #{:id})})
;; Note: this file will not be reloaded automatically when it is changed.
#+END_SRC
** Rendering
#+BEGIN_SRC clojurescript :tangle app/src/skeleton_app/rendering.cljs
(ns skeleton-app.rendering
(:require [domina :as dom]
[io.pedestal.app.render.push :as render]
[io.pedestal.app.render.push.templates :as templates]
[io.pedestal.app.render.push.handlers.automatic :as d])
(:require-macros [skeleton-app.html-templates :as html-templates]))
;; Load templates.
(def templates (html-templates/skeleton-app-templates))
;; The way rendering is handled below is the result of using the
;; renderer provided in `io.pedestal.app.render`. The only requirement
;; for a renderer is that it must implement the Renderer protocol.
;;
;; This renderer dispatches to rendering functions based on the
;; requested change. See the render-config table below. Each render
;; function takes three arguments: renderer, render operation and a
;; a transmitter which is used to send data back to the application's
;; behavior. This example does not use the transmitter.
(defn render-page [renderer [_ path] transmitter]
(let [;; The renderer that we are using here helps us map changes to
;; the UI tree to the DOM. It keeps a mapping of paths to DOM
;; ids. The `get-parent-id` function will return the DOM id of
;; the parent of the node at path. If the path is [:a :b :c]
;; then this will find the id associated with [:a :b]. The
;; root node [] is configured when we created the renderer.
parent (render/get-parent-id renderer path)
;; Use the `new-id!` function to associate a new id to the
;; given path. With two arguments, this function will generate
;; a random unique id. With three arguments, the given id will
;; be associated with the given path.
id (render/new-id! renderer path)
;; Get the dynamic template named :skeleton-app-page
;; from the templates map. The `add-template` function will
;; associate this template with the node at
;; path. `add-template` returns a function that can be called
;; to generate the initial HTML.
html (templates/add-template renderer path (:skeleton-app-page templates))]
;; Call the `html` function, passing the initial values for the
;; template. This returns an HTML string which is then added to
;; the DOM using Domina.
(dom/append! (dom/by-id parent) (html {:id id :message ""}))))
(defn render-message [renderer [_ path _ new-value] transmitter]
;; This function responds to a :value event. It uses the
;; `update-t` function to update the template at `path` with the new
;; values in the passed map.
(templates/update-t renderer path {:message new-value}))
;; The data structure below is used to map rendering data to functions
;; which handle rendering for that specific change. This function is
;; referenced in config/config.clj and must be a function in order to
;; be used from the tool's "render" view.
(defn render-config []
[;; All :node-create deltas for the node at :greeting will
;; be rendered by the `render-page` function. The node name
;; :greeting is a default name that is used when we don't
;; provide our own combines and emits. To name your own nodes,
;; create a custom combine or emit in the application's behavior.
[:node-create [:greeting] render-page]
;; All :node-destroy deltas for this path will be handled by the
;; library function `d/default-exit`.
[:node-destroy [:greeting] d/default-exit]
;; All :value deltas for this path will be handled by the
;; function `render-message`.
[:value [:greeting] render-message]])
;; In render-config, paths can use wildcard keywords :* and :**. :*
;; means exactly one segment with any value. :** means 0 or more
;; elements.
#+END_SRC
** Back-end/external communications
#+BEGIN_SRC clojurescript :tangle app/src/skeleton_app/services.cljs
(ns skeleton-app.services)
;; The services namespace responsible for communicating with back-end
;; services. It receives messages from the application's behavior,
;; makes requests to services and sends responses back to the
;; behavior.
;;
;; This namespace will usually contain a function which can be
;; configured to receive effect events from the behavior in the file
;;
;; app/src/skeleton_app/start.cljs
;;
;; After creating a new application, set the effect handler function
;; to receive effect
;;
;; (app/consume-effect app services-fn)
;;
;; A very simple example of a services function which echos all events
;; back to the behavior is shown below
(comment
;; The services implementation will need some way to send messages
;; back to the application. The queue passed to the services function
;; will convey messages to the application.
(defn echo-services-fn [message queue]
(put-message queue message))
)
;; During development, it is helpful to implement services which
;; simulate communication with the real services. This implementaiton
;; can be placed in the file
;;
;; app/src/skeleton_app/simulated/services.cljs
;;
#+END_SRC
** Start
#+BEGIN_SRC clojurescript :tangle app/src/skeleton_app/start.cljs
(ns skeleton-app.start
(:require [io.pedestal.app.protocols :as p]
[io.pedestal.app :as app]
[io.pedestal.app.render.push :as push-render]
[io.pedestal.app.render :as render]
[io.pedestal.app.messages :as msg]
[skeleton-app.behavior :as behavior]
[skeleton-app.rendering :as rendering]))
;; In this namespace, the application is built and started.
(defn create-app [render-config]
(let [;; Build the application described in the map
;; 'behavior/example-app'. The application is a record which
;; implements the Receiver protocol.
app (app/build behavior/example-app)
;; Create the render function that will be used by this
;; application. A renderer function takes two arguments: the
;; application model deltas and the input queue.
;;
;; On the line below, we create a renderer that will help in
;; mapping UI data to the DOM.
;;
;; The file, app/src/skeleton_app/rendering.cljs contains
;; the code which does all of the rendering as well as the
;; render-config which is used to map renderering data to
;; specific functions.
render-fn (push-render/renderer "content" render-config render/log-fn)
;; This application does not yet have services, but if it did,
;; this would be a good place to create it.
;; services-fn (fn [message input-queue] ...)
;; Configure the application to send all rendering data to this
;; renderer.
app-model (render/consume-app-model app render-fn)]
;; If services existed, configure the application to send all
;; effects there.
;; (app/consume-effect app services-fn)
;;
;; Start the application
(app/begin app)
;; Send a message to the application so that it does something.
(p/put-message (:input app) {msg/type :set-value msg/topic [:greeting] :value "Hello World!"})
;; Returning the app and app-model from the main function allows
;; the tooling to add support for useful features like logging
;; and recording.
{:app app :app-model app-model}))
(defn ^:export main []
;; config/config.clj refers to this namespace as a main namespace
;; for several aspects. A main namespace must have a no argument
;; main function. To tie into tooling, this function should return
;; the newly created app.
(create-app (rendering/render-config)))
#+END_SRC
** Simulated back-end
#+BEGIN_SRC clojurescript :tangle app/src/skeleton_app/simulated/services.cljs
(ns skeleton-app.simulated.services)
;; Implement services to simulate talking to back-end services
#+END_SRC
#+BEGIN_SRC clojurescript :tangle app/src/skeleton_app/simulated/start.cljs
(ns skeleton-app.simulated.start
(:require [io.pedestal.app.render.push.handlers.automatic :as d]
[skeleton-app.start :as start]
;; This needs to be included somewhere in order for the
;; tools to work.
[io.pedestal.app-tools.tooling :as tooling]))
(defn ^:export main []
;; Create an application which uses the data renderer. The :data-ui
;; aspect is configured to run this main function. See
;;
;; config/config.clj
;;
(start/create-app d/data-renderer-config))
#+END_SRC
* Assets
#+BEGIN_SRC javascript :tangle app/assets/javascripts/xpath.js
(function(){var ca=void(0);var da={targetFrame:ca,exportInstaller:false,useNative:true,useInnerText:true};var ea;if(window.jsxpath){ea=window.jsxpath;}
else{var fa=document.getElementsByTagName('script');var ga=fa[fa.length-1];var ha=ga.src;ea={};var ia=ha.match(/\?(.*)$/);if(ia){var ja=ia[1].split('&');for(var i=0,l=ja.length;i<l;i++){var ka=ja[i];var la=ka.split('=');var ma=la[0];var na=la[1];if(na==ca){na==true;}
else if(na=='false'||/^-?\d+$/.test(na)){na=eval(na);}
ea[ma]=na;}}}
for(var n in da){if(!(n in ea))ea[n]=da[n];}
ea.hasNative=!!(document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("XPath",null));if(ea.hasNative&&ea.useNative&&!ea.exportInstaller){return;}
var oa;var pa;var qa;var ra;var sa;var ta;var va;var wa;var xa;var ya;var za;var Aa;var Ba;var Ca;var Da=new function(){var ua=navigator.userAgent;if(RegExp==ca){if(ua.indexOf("Opera")>=0){this.opera=true;}
else if(ua.indexOf("Netscape")>=0){this.netscape=true;}
else if(ua.indexOf("Mozilla/")==0){this.mozilla=true;}
else{this.unknown=true;}
if(ua.indexOf("Gecko/")>=0){this.gecko=true;}
if(ua.indexOf("Win")>=0){this.windows=true;}
else if(ua.indexOf("Mac")>=0){this.mac=true;}
else if(ua.indexOf("Linux")>=0){this.linux=true;}
else if(ua.indexOf("BSD")>=0){this.bsd=true;}
else if(ua.indexOf("SunOS")>=0){this.sunos=true;}}
else{ /*@cc_on @if(@_jscript)function jscriptVersion(){switch(@_jscript_version){case 3.0:return "4.0";case 5.0:return "5.0";case 5.1:return "5.01";case 5.5:return "5.5";case 5.6:if("XMLHttpRequest" in window)return "7.0";return "6.0";case 5.7:return "7.0";default:return true;}}
if(@_win16||@_win32||@_win64){this.windows=true;this.trident=jscriptVersion();}
else if(@_mac||navigator.platform.indexOf("Mac")>=0){this.mac=true;this.tasman=jscriptVersion();}
if(/MSIE (\d+\.\d+)b?;/.test(ua)){this.ie=RegExp.$1;this['ie'+RegExp.$1.charAt(0)]=true;}@else @*/
if(/AppleWebKit\/(\d+(?:\.\d+)*)/.test(ua)){this.applewebkit=RegExp.$1;if(RegExp.$1.charAt(0)==4){this.applewebkit2=true;}
else{this.applewebkit3=true;}}
else if(typeof Components=="object"&&(/Gecko\/(\d{8})/.test(ua)||navigator.product=="Gecko"&&/^(\d{8})$/.test(navigator.productSub))){this.gecko=RegExp.$1;}/*@end @*/
if(typeof(opera)=="object"&&typeof(opera.version)=="function"){this.opera=opera.version();this['opera'+this.opera[0]+this.opera[2]]=true;}
else if(typeof opera=="object"&&(/Opera[\/ ](\d+\.\d+)/.test(ua))){this.opera=RegExp.$1;}
else if(this.ie){}
else if(/Safari\/(\d+(?:\.\d+)*)/.test(ua)){this.safari=RegExp.$1;}
else if(/NetFront\/(\d+(?:\.\d+)*)/.test(ua)){this.netfront=RegExp.$1;}
else if(/Konqueror\/(\d+(?:\.\d+)*)/.test(ua)){this.konqueror=RegExp.$1;}
else if(ua.indexOf("(compatible;")<0&&(/^Mozilla\/(\d+\.\d+)/.test(ua))){this.mozilla=RegExp.$1;if(/\([^(]*rv:(\d+(?:\.\d+)*).*?\)/.test(ua))this.mozillarv=RegExp.$1;if(/Firefox\/(\d+(?:\.\d+)*)/.test(ua)){this.firefox=RegExp.$1;}
else if(/Netscape\d?\/(\d+(?:\.\d+)*)/.test(ua)){this.netscape=RegExp.$1;}}
else{this.unknown=true;}
if(ua.indexOf("Win 9x 4.90")>=0){this.windows="ME";}
else if(/Win(?:dows)? ?(NT ?(\d+\.\d+)?|\d+|ME|Vista|XP)/.test(ua)){this.windows=RegExp.$1;if(RegExp.$2){this.winnt=RegExp.$2;}
else switch(RegExp.$1){case "2000":this.winnt="5.0";break;case "XP":this.winnt="5.1";break;case "Vista":this.winnt="6.0";break;}}
else if(ua.indexOf("Mac")>=0){this.mac=true;}
else if(ua.indexOf("Linux")>=0){this.linux=true;}
else if(/(\w*BSD)/.test(ua)){this.bsd=RegExp.$1;}
else if(ua.indexOf("SunOS")>=0){this.sunos=true;}}};var Ea=function(Fa){var Ga=Ea.prototype;var Ha=Fa.match(Ga.regs.token);for(var i=0,l=Ha.length;i<l;i++){if(Ga.regs.strip.test(Ha[i])){Ha.splice(i,1);}}
for(var n in Ga)Ha[n]=Ga[n];Ha.index=0;return Ha;};Ea.prototype.regs={token:/\$?(?:(?![0-9-])[\w-]+:)?(?![0-9-])[\w-]+|\/\/|\.\.|::|\d+(?:\.\d*)?|\.\d+|"[^"]*"|'[^']*'|[!<>]=|(?![0-9-])[\w-]+:\*|\s+|./g,strip:/^\s/};Ea.prototype.peek=function(i){return this[this.index+(i||0)];};Ea.prototype.next=function(){return this[this.index++];};Ea.prototype.back=function(){this.index--;};Ea.prototype.empty=function(){return this.length<=this.index;};var Ia=function(Ja,Ka,La){this.node=Ja;this.position=Ka||1;this.last=La||1;};var Ma=function(){};Ma.prototype.number=function(Na){var Oa=this.evaluate(Na);if(Oa.isNodeSet)return Oa.number();return+Oa;};Ma.prototype.string=function(Pa){var Qa=this.evaluate(Pa);if(Qa.isNodeSet)return Qa.string();return ''+Qa;};Ma.prototype.bool=function(Ra){var Sa=this.evaluate(Ra);if(Sa.isNodeSet)return Sa.bool();return!!Sa;};var Ta=function(){};Ta.parsePredicates=function(Ua,Va){while(Ua.peek()=='['){Ua.next();if(Ua.empty()){throw Error('missing predicate expr');}
var Wa=oa.parse(Ua);Va.predicate(Wa);if(Ua.empty()){throw Error('unclosed predicate expr');}
if(Ua.next()!=']'){Ua.back();throw Error('bad token: '+Ua.next());}}};Ta.prototype=new Ma();Ta.prototype.evaluatePredicates=function(Xa,Ya){var Za,predicate,nodes,node,Xa,position,reverse;reverse=this.reverse;Za=this.predicates;Xa.sort();for(var i=Ya||0,l0=Za.length;i<l0;i++){predicate=Za[i];var $a=[];var ab=Xa.list();for(var j=0,l1=ab.length;j<l1;j++){position=reverse?(l1-j):(j+1);exrs=predicate.evaluate(new Ia(ab[j],position,l1));switch(typeof exrs){case 'number':exrs=(position==exrs);break;case 'string':exrs=!!exrs;break;case 'object':exrs=exrs.bool();break;}
if(!exrs){$a.push(j);}}
for(var j=$a.length-1,l1=0;j>=l1;j--){Xa.del($a[j]);}}
return Xa;};if(!window.BinaryExpr&&window.defaultConfig)window.BinaryExpr=null;oa=function(op,bb,cb,db){this.op=op;this.left=bb;this.right=cb;this.datatype=oa.ops[op][2];this.needContextPosition=bb.needContextPosition||cb.needContextPosition;this.needContextNode=bb.needContextNode||cb.needContextNode;if(this.op=='='){if(!cb.needContextNode&&!cb.needContextPosition&&cb.datatype!='nodeset'&&cb.datatype!='void'&&bb.quickAttr){this.quickAttr=true;this.attrName=bb.attrName;this.attrValueExpr=cb;}
else if(!bb.needContextNode&&!bb.needContextPosition&&bb.datatype!='nodeset'&&bb.datatype!='void'&&cb.quickAttr){this.quickAttr=true;this.attrName=cb.attrName;this.attrValueExpr=bb;}}};oa.compare=function(op,eb,fb,gb,hb){var ib,lnodes,rnodes,nodes,nodeset,primitive;fb=fb.evaluate(hb);gb=gb.evaluate(hb);if(fb.isNodeSet&&gb.isNodeSet){lnodes=fb.list();rnodes=gb.list();for(var i=0,l0=lnodes.length;i<l0;i++)for(var j=0,l1=rnodes.length;j<l1;j++)if(eb(wa.to('string',lnodes[i]),wa.to('string',rnodes[j])))return true;return false;}
if(fb.isNodeSet||gb.isNodeSet){if(fb.isNodeSet)nodeset=fb,primitive=gb;else nodeset=gb,primitive=fb;nodes=nodeset.list();ib=typeof primitive;for(var i=0,l=nodes.length;i<l;i++){if(eb(wa.to(ib,nodes[i]),primitive))return true;}
return false;}
if(op=='='||op=='!='){if(typeof fb=='boolean'||typeof gb=='boolean'){return eb(!!fb,!!gb);}
if(typeof fb=='number'||typeof gb=='number'){return eb(+fb,+gb);}
return eb(fb,gb);}
return eb(+fb,+gb);};oa.ops={'div':[6,function(jb,kb,lb){return jb.number(lb)/kb.number(lb);},'number'],'mod':[6,function(mb,nb,ob){return mb.number(ob)%nb.number(ob);},'number'],'*':[6,function(pb,qb,rb){return pb.number(rb)*qb.number(rb);},'number'],'+':[5,function(sb,tb,ub){return sb.number(ub)+tb.number(ub);},'number'],'-':[5,function(vb,wb,xb){return vb.number(xb)-wb.number(xb);},'number'],'<':[4,function(yb,zb,Ab){return oa.compare('<',function(a,b){return a<b},yb,zb,Ab);},'boolean'],'>':[4,function(Bb,Cb,Db){return oa.compare('>',function(a,b){return a>b},Bb,Cb,Db);},'boolean'],'<=':[4,function(Eb,Fb,Gb){return oa.compare('<=',function(a,b){return a<=b},Eb,Fb,Gb);},'boolean'],'>=':[4,function(Hb,Ib,Jb){return oa.compare('>=',function(a,b){return a>=b},Hb,Ib,Jb);},'boolean'],'=':[3,function(Kb,Lb,Mb){return oa.compare('=',function(a,b){return a==b},Kb,Lb,Mb);},'boolean'],'!=':[3,function(Nb,Ob,Pb){return oa.compare('!=',function(a,b){return a!=b},Nb,Ob,Pb);},'boolean'],'and':[2,function(Qb,Rb,Sb){return Qb.bool(Sb)&&Rb.bool(Sb);},'boolean'],'or':[1,function(Tb,Ub,Vb){return Tb.bool(Vb)||Ub.bool(Vb);},'boolean']};oa.parse=function(Wb){var op,precedence,info,expr,stack=[],index=Wb.index;while(true){if(Wb.empty()){throw Error('missing right expression');}
expr=Aa.parse(Wb);op=Wb.next();if(!op){break;}
info=this.ops[op];precedence=info&&info[0];if(!precedence){Wb.back();break;}
while(stack.length&&precedence<=this.ops[stack[stack.length-1]][0]){expr=new oa(stack.pop(),stack.pop(),expr);}
stack.push(expr,op);}
while(stack.length){expr=new oa(stack.pop(),stack.pop(),expr);}
return expr;};oa.prototype=new Ma();oa.prototype.evaluate=function(Xb){return oa.ops[this.op][1](this.left,this.right,Xb);};oa.prototype.show=function(Yb){Yb=Yb||'';var t='';t+=Yb+'binary: '+this.op+'\n';Yb+=' ';t+=this.left.show(Yb);t+=this.right.show(Yb);return t;};if(!window.UnaryExpr&&window.defaultConfig)window.UnaryExpr=null;Aa=function(op,Zb){this.op=op;this.expr=Zb;this.needContextPosition=Zb.needContextPosition;this.needContextNode=Zb.needContextNode;};Aa.ops={'-':1};Aa.parse=function($b){var ac;if(this.ops[$b.peek()])return new Aa($b.next(),Aa.parse($b));else return Ba.parse($b);};Aa.prototype=new Ma();Aa.prototype.datatype='number';Aa.prototype.evaluate=function(bc){return-this.expr.number(bc);};Aa.prototype.show=function(cc){cc=cc||'';var t='';t+=cc+'unary: '+this.op+'\n';cc+=' ';t+=this.expr.show(cc);return t;};if(!window.UnionExpr&&window.defaultConfig)window.UnionExpr=null;Ba=function(){this.paths=[];};Ba.ops={'|':1};Ba.parse=function(dc){var ec,expr;expr=ya.parse(dc);if(!this.ops[dc.peek()])return expr;ec=new Ba();ec.path(expr);while(true){if(!this.ops[dc.next()])break;if(dc.empty()){throw Error('missing next union location path');}
ec.path(ya.parse(dc));}
dc.back();return ec;};Ba.prototype=new Ma();Ba.prototype.datatype='nodeset';Ba.prototype.evaluate=function(fc){var gc=this.paths;var hc=new ta();for(var i=0,l=gc.length;i<l;i++){var ic=gc[i].evaluate(fc);if(!ic.isNodeSet)throw Error('PathExpr must be nodeset');hc.merge(ic);}
return hc;};Ba.prototype.path=function(jc){this.paths.push(jc);if(jc.needContextPosition){this.needContextPosition=true;}
if(jc.needContextNode){this.needContextNode=true;}}
Ba.prototype.show=function(kc){kc=kc||'';var t='';t+=kc+'union:'+'\n';kc+=' ';for(var i=0;i<this.paths.length;i++){t+=this.paths[i].show(kc);}
return t;};if(!window.PathExpr&&window.defaultConfig)window.PathExpr=null;ya=function(lc){this.filter=lc;this.steps=[];this.datatype=lc.datatype;this.needContextPosition=lc.needContextPosition;this.needContextNode=lc.needContextNode;};ya.ops={'//':1,'/':1};ya.parse=function(mc){var op,expr,path,token;if(this.ops[mc.peek()]){op=mc.next();token=mc.peek();if(op=='/'&&(mc.empty()||(token!='.'&&token!='..'&&token!='@'&&token!='*'&&!/(?![0-9])[\w]/.test(token)))){return pa.root();}
path=new ya(pa.root());if(mc.empty()){throw Error('missing next location step');}
expr=za.parse(mc);path.step(op,expr);}
else{expr=pa.parse(mc);if(!expr){expr=za.parse(mc);path=new ya(pa.context());path.step('/',expr);}
else if(!this.ops[mc.peek()])return expr;else path=new ya(expr);}
while(true){if(!this.ops[mc.peek()])break;op=mc.next();if(mc.empty()){throw Error('missing next location step');}
path.step(op,za.parse(mc));}
return path;};ya.prototype=new Ma();ya.prototype.evaluate=function(nc){var oc=this.filter.evaluate(nc);if(!oc.isNodeSet)throw Exception('Filter nodeset must be nodeset type');var pc=this.steps;for(var i=0,l0=pc.length;i<l0&&oc.length;i++){var qc=pc[i][1];var rc=qc.reverse;var sc=oc.iterator(rc);var tc=oc;oc=null;var uc,next;if(!qc.needContextPosition&&qc.axis=='following'){for(uc=sc();next=sc();uc=next){if(Da.applewebkit2){var vc=false;var wc=next;do{if(wc==uc){vc=true;break;}}
while(wc=wc.parentNode);if(!vc)break;}
else{try{if(!uc.contains(next))break}
catch(e){if(!(next.compareDocumentPosition(uc)&8))break}}}
oc=qc.evaluate(new Ia(uc));}
else if(!qc.needContextPosition&&qc.axis=='preceding'){uc=sc();oc=qc.evaluate(new Ia(uc));}
else{uc=sc();var j=0;oc=qc.evaluate(new Ia(uc),false,tc,j);while(uc=sc()){j++;oc.merge(qc.evaluate(new Ia(uc),false,tc,j));}}}
return oc;};ya.prototype.step=function(op,xc){xc.op=op;this.steps.push([op,xc]);this.quickAttr=false;if(this.steps.length==1){if(op=='/'&&xc.axis=='attribute'){var yc=xc.test;if(!yc.notOnlyElement&&yc.name!='*'){this.quickAttr=true;this.attrName=yc.name;}}}};ya.prototype.show=function(zc){zc=zc||'';var t='';t+=zc+'path:'+'\n';zc+=' ';t+=zc+'filter:'+'\n';t+=this.filter.show(zc+' ');if(this.steps.length){t+=zc+'steps:'+'\n';zc+=' ';for(var i=0;i<this.steps.length;i++){var Ac=this.steps[i];t+=zc+'operator: '+Ac[0]+'\n';t+=Ac[1].show(zc);}}
return t;};if(!window.FilterExpr&&window.defaultConfig)window.FilterExpr=null;pa=function(Bc){this.primary=Bc;this.predicates=[];this.datatype=Bc.datatype;this.needContextPosition=Bc.needContextPosition;this.needContextNode=Bc.needContextNode;};pa.parse=function(Cc){var Dc,filter,token,ch;token=Cc.peek();ch=token.charAt(0);switch(ch){case '$':Dc=Ca.parse(Cc);break;case '(':Cc.next();Dc=oa.parse(Cc);if(Cc.empty()){throw Error('unclosed "("');}
if(Cc.next()!=')'){Cc.back();throw Error('bad token: '+Cc.next());}
break;case '"':case "'":Dc=ra.parse(Cc);break;default:if(!isNaN(+token)){Dc=xa.parse(Cc);}
else if(va.types[token]){return null;}
else if(/(?![0-9])[\w]/.test(ch)&&Cc.peek(1)=='('){Dc=qa.parse(Cc);}
else{return null;}
break;}
if(Cc.peek()!='[')return Dc;filter=new pa(Dc);Ta.parsePredicates(Cc,filter);return filter;};pa.root=function(){return new qa('root-node');};pa.context=function(){return new qa('context-node');};pa.prototype=new Ta();pa.prototype.evaluate=function(Ec){var Fc=this.primary.evaluate(Ec);if(!Fc.isNodeSet){if(this.predicates.length)throw Error('Primary result must be nodeset type '+'if filter have predicate expression');return Fc;}
return this.evaluatePredicates(Fc);};pa.prototype.predicate=function(Gc){this.predicates.push(Gc);};pa.prototype.show=function(Hc){Hc=Hc||'';var t='';t+=Hc+'filter: '+'\n';Hc+=' ';t+=this.primary.show(Hc);if(this.predicates.length){t+=Hc+'predicates: '+'\n';Hc+=' ';for(var i=0;i<this.predicates.length;i++){t+=this.predicates[i].show(Hc);}}
return t;};if(!window.NodeUtil&&window.defaultConfig)window.NodeUtil=null;wa={to:function(Ic,Jc){var t,type=Jc.nodeType;if(type==1&&ea.useInnerText&&!Da.applewebkit2){t=Jc.textContent;t=(t==ca||t==null)?Jc.innerText:t;t=(t==ca||t==null)?'':t;}
if(typeof t!='string'){ /*@cc_on
if(type==1&&Jc.nodeName.toLowerCase()=='title'){t=Jc.text;}
else@*/if(type==9||type==1){if(type==9){Jc=Jc.documentElement;}
else{Jc=Jc.firstChild;}
for(t='',stack=[],i=0;Jc;){do{if(Jc.nodeType!=1){t+=Jc.nodeValue;} /*@cc_on
else if(Jc.nodeName.toLowerCase()=='title'){t+=Jc.text;}@*/stack[i++]=Jc;}
while(Jc=Jc.firstChild);while(i&&!(Jc=stack[--i].nextSibling)){}}}
else{t=Jc.nodeValue;}}
switch(Ic){case 'number':return+t;case 'boolean':return!!t;default:return t;}},attrPropMap:{name:'name','class':'className',dir:'dir',id:'id',name:'name',title:'title'},attrMatch:function(Kc,Lc,Mc){ /*@cc_on @if(@_jscript)var Nc=wa.attrPropMap[Lc];if(!Lc||Mc==null&&(Nc&&Kc[Nc]||!Nc&&Kc.getAttribute&&Kc.getAttribute(Lc,2))||Mc!=null&&(Nc&&Kc[Nc]==Mc||!Nc&&Kc.getAttribute&&Kc.getAttribute(Lc,2)==Mc)){@else @*/
if(!Lc||Mc==null&&Kc.hasAttribute&&Kc.hasAttribute(Lc)||Mc!=null&&Kc.getAttribute&&Kc.getAttribute(Lc)==Mc){/*@end @*/
return true;}
else{return false;}},getDescendantNodes:function(Oc,Kc,Pc,Lc,Mc,Qc,Rc){if(Qc){Qc.delDescendant(Kc,Rc);} /*@cc_on
try{if(!Oc.notOnlyElement||Oc.type==8||(Lc&&Oc.type==0)){var Sc=Kc.all;if(!Sc){return Pc;}
var Tc=Oc.name;if(Oc.type==8)Tc='!';else if(Oc.type==0)Tc='*';if(Tc!='*'){Sc=Sc.tags(Tc);if(!Sc){return Pc;}}
if(Lc){var Uc=[]
var i=0;if(Mc!=null&&(Lc=='id'||Lc=='name')){Sc=Sc[Mc];if(!Sc){return Pc;}
if(!Sc.length||Sc.nodeType){Sc=[Sc];}}
while(Kc=Sc[i++]){if(wa.attrMatch(Kc,Lc,Mc))Uc.push(Kc);}
Sc=Uc;}
var i=0;while(Kc=Sc[i++]){if(Tc!='*'||Kc.tagName!='!'){Pc.push(Kc);}}
return Pc;}(function(Vc){var g=arguments.callee;var Kc=Vc.firstChild;if(Kc){for(;Kc;Kc=Kc.nextSibling){if(wa.attrMatch(Kc,Lc,Mc)){if(Oc.match(Kc))Pc.push(Kc);}
g(Kc);}}})(Kc);return Pc;}
catch(e){@*/if(Mc&&Lc=='id'&&Kc.getElementById){Kc=Kc.getElementById(Mc);if(Kc&&Oc.match(Kc)){Pc.push(Kc);}}
else if(Mc&&Lc=='name'&&Kc.getElementsByName){var Wc=Kc.getElementsByName(Mc);for(var i=0,l=Wc.length;i<l;i++){Kc=Wc[i];if(Da.opera?(Kc.name==Mc&&Oc.match(Kc)):Oc.match(Kc)){Pc.push(Kc);}}}
else if(Mc&&Lc=='class'&&Kc.getElementsByClassName){var Wc=Kc.getElementsByClassName(Mc);for(var i=0,l=Wc.length;i<l;i++){Kc=Wc[i];if(Kc.className==Mc&&Oc.match(Kc)){Pc.push(Kc);}}}
else if(Oc.notOnlyElement){(function(Xc){var f=arguments.callee;for(var Kc=Xc.firstChild;Kc;Kc=Kc.nextSibling){if(wa.attrMatch(Kc,Lc,Mc)){if(Oc.match(Kc.nodeType))Pc.push(Kc);}
f(Kc);}})(Kc);}
else{var Tc=Oc.name;if(Kc.getElementsByTagName){var Wc=Kc.getElementsByTagName(Tc);if(Wc){var i=0;while(Kc=Wc[i++]){if(wa.attrMatch(Kc,Lc,Mc))Pc.push(Kc);}}}}
return Pc; /*@cc_on }@*/},getChildNodes:function(Yc,Kc,Zc,Lc,Mc){ /*@cc_on
try{var $c;if((!Yc.notOnlyElement||Yc.type==8||(Lc&&Yc.type==0))&&($c=Kc.children)){var ad,elm;ad=Yc.name;if(Yc.type==8)ad='!';else if(Yc.type==0)ad='*';if(ad!='*'){$c=$c.tags(ad);if(!$c){return Zc;}}
if(Lc){var bd=[]
var i=0;if(Lc=='id'||Lc=='name'){$c=$c[Mc];if(!$c){return Zc;}
if(!$c.length||$c.nodeType){$c=[$c];}}
while(Kc=$c[i++]){if(wa.attrMatch(Kc,Lc,Mc))bd.push(Kc);}
$c=bd;}
var i=0;while(Kc=$c[i++]){if(ad!='*'||Kc.tagName!='!'){Zc.push(Kc);}}
return Zc;}
for(var i=0,Kc=Kc.firstChild;Kc;i++,Kc=Kc.nextSibling){if(wa.attrMatch(Kc,Lc,Mc)){if(Yc.match(Kc))Zc.push(Kc);}}
return Zc;}
catch(e){@*/for(var Kc=Kc.firstChild;Kc;Kc=Kc.nextSibling){if(wa.attrMatch(Kc,Lc,Mc)){if(Yc.match(Kc))Zc.push(Kc);}}
return Zc; /*@cc_on }@*/}}; /*@cc_on
var cd=function(dd,ed,fd){this.node=dd;this.nodeType=2;this.nodeValue=dd.nodeValue;this.nodeName=dd.nodeName;this.parentNode=ed;this.ownerElement=ed;this.parentSourceIndex=fd;};@*/if(!window.Step&&window.defaultConfig)window.Step=null;za=function(gd,hd){this.axis=gd;this.reverse=za.axises[gd][0];this.func=za.axises[gd][1];this.test=hd;this.predicates=[];this._quickAttr=za.axises[gd][2]};za.axises={ancestor:[true,function(jd,kd,ld,_,md,od,pd){while(kd=kd.parentNode){if(od&&kd.nodeType==1){od.reserveDelByNode(kd,pd,true);}
if(jd.match(kd))ld.unshift(kd);}
return ld;}],'ancestor-or-self':[true,function(qd,rd,sd,_,td,ud,vd){do{if(ud&&rd.nodeType==1){ud.reserveDelByNode(rd,vd,true);}
if(qd.match(rd))sd.unshift(rd);}
while(rd=rd.parentNode)return sd;}],attribute:[false,function(wd,xd,yd){var zd=xd.attributes;if(zd){ /*@cc_on
var Ad=xd.sourceIndex;@*/if((wd.notOnlyElement&&wd.type==0)||wd.name=='*'){for(var i=0,attr;attr=zd[i];i++){ /*@cc_on @if(@_jscript)if(attr.nodeValue){yd.push(new cd(attr,xd,Ad));}@else @*/
yd.push(attr);/*@end @*/ }}
else{var Bd=zd.getNamedItem(wd.name); /*@cc_on @if(@_jscript)if(Bd&&Bd.nodeValue){Bd=new cd(Bd,xd,Ad);;@else @*/
if(Bd){/*@end @*/
yd.push(Bd);}}}
return yd;}],child:[false,wa.getChildNodes,true],descendant:[false,wa.getDescendantNodes,true],'descendant-or-self':[false,function(wd,xd,yd,Cd,Dd,Ed,Fd){if(wa.attrMatch(xd,Cd,Dd)){if(wd.match(xd))yd.push(xd);}
return wa.getDescendantNodes(wd,xd,yd,Cd,Dd,Ed,Fd);},true],following:[false,function(wd,xd,yd,Gd,Hd){do{var Id=xd;while(Id=Id.nextSibling){if(wa.attrMatch(Id,Gd,Hd)){if(wd.match(Id))yd.push(Id);}
yd=wa.getDescendantNodes(wd,Id,yd,Gd,Hd);}}
while(xd=xd.parentNode);return yd;},true],'following-sibling':[false,function(wd,xd,yd,_,Jd,Kd,Ld){while(xd=xd.nextSibling){if(Kd&&xd.nodeType==1){Kd.reserveDelByNode(xd,Ld);}
if(wd.match(xd)){yd.push(xd);}}
return yd;}],namespace:[false,function(wd,xd,yd){return yd;}],parent:[false,function(wd,xd,yd){if(xd.nodeType==9){return yd;}
if(xd.nodeType==2){yd.push(xd.ownerElement);return yd;}
var xd=xd.parentNode;if(wd.match(xd))yd.push(xd);return yd;}],preceding:[true,function(wd,xd,yd,Md,Nd){var Od=[];do{Od.unshift(xd);}
while(xd=xd.parentNode);for(var i=1,l0=Od.length;i<l0;i++){var Pd=[];xd=Od[i];while(xd=xd.previousSibling){Pd.unshift(xd);}
for(var j=0,l1=Pd.length;j<l1;j++){xd=Pd[j];if(wa.attrMatch(xd,Md,Nd)){if(wd.match(xd))yd.push(xd);}
yd=wa.getDescendantNodes(wd,xd,yd,Md,Nd);}}
return yd;},true],'preceding-sibling':[true,function(wd,xd,yd,_,Qd,Rd,Sd){while(xd=xd.previousSibling){if(Rd&&xd.nodeType==1){Rd.reserveDelByNode(xd,Sd,true);}
if(wd.match(xd)){yd.unshift(xd)}}
return yd;}],self:[false,function(wd,xd,yd){if(wd.match(xd))yd.push(xd);return yd;}]};za.parse=function(Td){var Ud,test,step,token;if(Td.peek()=='.'){step=this.self();Td.next();}
else if(Td.peek()=='..'){step=this.parent();Td.next();}
else{if(Td.peek()=='@'){Ud='attribute';Td.next();if(Td.empty()){throw Error('missing attribute name');}}
else{if(Td.peek(1)=='::'){if(!/(?![0-9])[\w]/.test(Td.peek().charAt(0))){throw Error('bad token: '+Td.next());}
Ud=Td.next();Td.next();if(!this.axises[Ud]){throw Error('invalid axis: '+Ud);}
if(Td.empty()){throw Error('missing node name');}}
else{Ud='child';}}
token=Td.peek();if(!/(?![0-9])[\w]/.test(token.charAt(0))){if(token=='*'){test=sa.parse(Td)}
else{throw Error('bad token: '+Td.next());}}
else{if(Td.peek(1)=='('){if(!va.types[token]){throw Error('invalid node type: '+token);}
test=va.parse(Td)}
else{test=sa.parse(Td);}}
step=new za(Ud,test);}
Ta.parsePredicates(Td,step);return step;};za.self=function(){return new za('self',new va('node'));};za.parent=function(){return new za('parent',new va('node'));};za.prototype=new Ta();za.prototype.evaluate=function(Vd,Wd,Xd,Yd){var Zd=Vd.node;var $d=false;if(!Wd&&this.op=='//'){if(!this.needContextPosition&&this.axis=='child'){if(this.quickAttr){var ae=this.attrValueExpr?this.attrValueExpr.string(Vd):null;var be=wa.getDescendantNodes(this.test,Zd,new ta(),this.attrName,ae,Xd,Yd);be=this.evaluatePredicates(be,1);}
else{var be=wa.getDescendantNodes(this.test,Zd,new ta(),null,null,Xd,Yd);be=this.evaluatePredicates(be);}}
else{var ce=new za('descendant-or-self',new va('node'));var de=ce.evaluate(Vd,false,Xd,Yd).list();var be=null;ce.op='/';for(var i=0,l=de.length;i<l;i++){if(!be){be=this.evaluate(new Ia(de[i]),true);}
else{be.merge(this.evaluate(new Ia(de[i]),true));}}
be=be||new ta();}}
else{if(this.needContextPosition){Xd=null;Yd=null;}
if(this.quickAttr){var ae=this.attrValueExpr?this.attrValueExpr.string(Vd):null;var be=this.func(this.test,Zd,new ta(),this.attrName,ae,Xd,Yd);be=this.evaluatePredicates(be,1);}
else{var be=this.func(this.test,Zd,new ta(),null,null,Xd,Yd);be=this.evaluatePredicates(be);}
if(Xd){Xd.doDel();}}
return be;};za.prototype.predicate=function(ee){this.predicates.push(ee);if(ee.needContextPosition||ee.datatype=='number'||ee.datatype=='void'){this.needContextPosition=true;}
if(this._quickAttr&&this.predicates.length==1&&ee.quickAttr){var fe=ee.attrName; /*@cc_on @if(@_jscript)this.attrName=fe.toLowerCase();@else @*/
this.attrName=fe;/*@end @*/
this.attrValueExpr=ee.attrValueExpr;this.quickAttr=true;}};za.prototype.show=function(ge){ge=ge||'';var t='';t+=ge+'step: '+'\n';ge+=' ';if(this.axis)t+=ge+'axis: '+this.axis+'\n';t+=this.test.show(ge);if(this.predicates.length){t+=ge+'predicates: '+'\n';ge+=' ';for(var i=0;i<this.predicates.length;i++){t+=this.predicates[i].show(ge);}}
return t;};if(!window.NodeType&&window.defaultConfig)window.NodeType=null;va=function(he,je){this.name=he;this.literal=je;switch(he){case 'comment':this.type=8;break;case 'text':this.type=3;break;case 'processing-instruction':this.type=7;break;case 'node':this.type=0;break;}};va.types={'comment':1,'text':1,'processing-instruction':1,'node':1};va.parse=function(ke){var le,literal,ch;le=ke.next();ke.next();if(ke.empty()){throw Error('bad nodetype');}
ch=ke.peek().charAt(0);if(ch=='"'||ch=="'"){literal=ra.parse(ke);}
if(ke.empty()){throw Error('bad nodetype');}
if(ke.next()!=')'){ke.back();throw Error('bad token '+ke.next());}
return new va(le,literal);};va.prototype=new Ma();va.prototype.notOnlyElement=true;va.prototype.match=function(me){return!this.type||this.type==me.nodeType;};va.prototype.show=function(ne){ne=ne||'';var t='';t+=ne+'nodetype: '+this.type+'\n';if(this.literal){ne+=' ';t+=this.literal.show(ne);}
return t;};if(!window.NameTest&&window.defaultConfig)window.NameTest=null;sa=function(oe){this.name=oe.toLowerCase();};sa.parse=function(pe){if(pe.peek()!='*'&&pe.peek(1)==':'&&pe.peek(2)=='*'){return new sa(pe.next()+pe.next()+pe.next());}
return new sa(pe.next());};sa.prototype=new Ma();sa.prototype.match=function(qe){var re=qe.nodeType;if(re==1||re==2){if(this.name=='*'||this.name==qe.nodeName.toLowerCase()){return true;}}
return false;};sa.prototype.show=function(se){se=se||'';var t='';t+=se+'nametest: '+this.name+'\n';return t;};if(!window.VariableReference&&window.defaultConfig)window.VariableReference=null;Ca=function(te){this.name=te.substring(1);};Ca.parse=function(ue){var ve=ue.next();if(ve.length<2){throw Error('unnamed variable reference');}
return new Ca(ve)};Ca.prototype=new Ma();Ca.prototype.datatype='void';Ca.prototype.show=function(we){we=we||'';var t='';t+=we+'variable: '+this.name+'\n';return t;};if(!window.Literal&&window.defaultConfig)window.Literal=null;ra=function(xe){this.text=xe.substring(1,xe.length-1);};ra.parse=function(ye){var ze=ye.next();if(ze.length<2){throw Error('unclosed literal string');}
return new ra(ze)};ra.prototype=new Ma();ra.prototype.datatype='string';ra.prototype.evaluate=function(Ae){return this.text;};ra.prototype.show=function(Be){Be=Be||'';var t='';t+=Be+'literal: '+this.text+'\n';return t;};if(!window.Number&&window.defaultConfig)window.Number=null;xa=function(Ce){this.digit=+Ce;};xa.parse=function(De){return new xa(De.next());};xa.prototype=new Ma();xa.prototype.datatype='number';xa.prototype.evaluate=function(Ee){return this.digit;};xa.prototype.show=function(Fe){Fe=Fe||'';var t='';t+=Fe+'number: '+this.digit+'\n';return t;};if(!window.FunctionCall&&window.defaultConfig)window.FunctionCall=null;qa=function(Ge){var He=qa.funcs[Ge];if(!He)throw Error(Ge+' is not a function');this.name=Ge;this.func=He[0];this.args=[];this.datatype=He[1];if(He[2]){this.needContextPosition=true;}
this.needContextNodeInfo=He[3];this.needContextNode=this.needContextNodeInfo[0]};qa.funcs={'context-node':[function(){if(arguments.length!=0){throw Error('Function context-node expects ()');}
var ns;ns=new ta();ns.push(this.node);return ns;},'nodeset',false,[true]],'root-node':[function(){if(arguments.length!=0){throw Error('Function root-node expects ()');}
var ns,ctxn;ns=new ta();ctxn=this.node;if(ctxn.nodeType==9)ns.push(ctxn);else ns.push(ctxn.ownerDocument);return ns;},'nodeset',false,[]],last:[function(){if(arguments.length!=0){throw Error('Function last expects ()');}
return this.last;},'number',true,[]],position:[function(){if(arguments.length!=0){throw Error('Function position expects ()');}
return this.position;},'number',true,[]],count:[function(ns){if(arguments.length!=1||!(ns=ns.evaluate(this)).isNodeSet){throw Error('Function count expects (nodeset)');}
return ns.length;},'number',false,[]],id:[function(s){var Ie,ns,i,id,elm,ctxn,doc;if(arguments.length!=1){throw Error('Function id expects (object)');}
ctxn=this.node;if(ctxn.nodeType==9)doc=ctxn;else doc=ctxn.ownerDocument; /*@cc_on
all=doc.all;@*/s=s.string(this);Ie=s.split(/\s+/);ns=new ta();for(i=0,l=Ie.length;i<l;i++){id=Ie[i]; /*@cc_on @if(@_jscript)elm=all[id];if(elm){if((!elm.length||elm.nodeType)&&id==elm.id){ns.push(elm)}
else if(elm.length){var Je=elm;for(var j=0,l0=Je.length;j<l0;j++){var Ke=Je[j];if(id==Ke.id){ns.push(Ke);break;}}}}@else @*/
elm=doc.getElementById(id);if(Da.opera&&elm&&elm.id!=id){var Je=doc.getElementsByName(id);for(var j=0,l0=Je.length;j<l0;j++){elm=Je[j];if(elm.id==id){ns.push(elm);}}}
else{if(elm)ns.push(elm)}/*@end @*/ }
ns.isSorted=false;return ns;},'nodeset',false,[]],'local-name':[function(ns){var nd;switch(arguments.length){case 0:nd=this.node;break;case 1:if((ns=ns.evaluate(this)).isNodeSet){nd=ns.first();break;}
default:throw Error('Function local-name expects (nodeset?)');break;}
return ''+nd.nodeName.toLowerCase();},'string',false,[true,false]],name:[function(ns){return qa.funcs['local-name'][0].apply(this,arguments);},'string',false,[true,false]],'namespace-uri':[function(ns){return '';},'string',false,[true,false]],string:[function(s){switch(arguments.length){case 0:s=wa.to('string',this.node);break;case 1:s=s.string(this);break;default:throw Error('Function string expects (object?)');break;}
return s;},'string',false,[true,false]],concat:[function(s1,s2){if(arguments.length<2){throw Error('Function concat expects (string, string[, ...])');}
for(var t='',i=0,l=arguments.length;i<l;i++){t+=arguments[i].string(this);}
return t;},'string',false,[]],'starts-with':[function(s1,s2){if(arguments.length!=2){throw Error('Function starts-with expects (string, string)');}
s1=s1.string(this);s2=s2.string(this);return s1.indexOf(s2)==0;},'boolean',false,[]],contains:[function(s1,s2){if(arguments.length!=2){throw Error('Function contains expects (string, string)');}
s1=s1.string(this);s2=s2.string(this);return s1.indexOf(s2)!=-1;},'boolean',false,[]],substring:[function(s,n1,n2){var a1,a2;s=s.string(this);n1=n1.number(this);switch(arguments.length){case 2:n2=s.length-n1+1;break;case 3:n2=n2.number(this);break;default:throw Error('Function substring expects (string, string)');break;}
n1=Math.round(n1);n2=Math.round(n2);a1=n1-1;a2=n1+n2-1;if(a2==Infinity){return s.substring(a1<0?0:a1);}
else{return s.substring(a1<0?0:a1,a2)}},'string',false,[]],'substring-before':[function(s1,s2){var n;if(arguments.length!=2){throw Error('Function substring-before expects (string, string)');}
s1=s1.string(this);s2=s2.string(this);n=s1.indexOf(s2);if(n==-1)return '';return s1.substring(0,n);},'string',false,[]],'substring-after':[function(s1,s2){if(arguments.length!=2){throw Error('Function substring-after expects (string, string)');}
s1=s1.string(this);s2=s2.string(this);var n=s1.indexOf(s2);if(n==-1)return '';return s1.substring(n+s2.length);},'string',false,[]],'string-length':[function(s){switch(arguments.length){case 0:s=wa.to('string',this.node);break;case 1:s=s.string(this);break;default:throw Error('Function string-length expects (string?)');break;}
return s.length;},'number',false,[true,false]],'normalize-space':[function(s){switch(arguments.length){case 0:s=wa.to('string',this.node);break;case 1:s=s.string(this);break;default:throw Error('Function normalize-space expects (string?)');break;}
return s.replace(/\s+/g,' ').replace(/^ /,'').replace(/ $/,'');},'string',false,[true,false]],translate:[function(s1,s2,s3){if(arguments.length!=3){throw Error('Function translate expects (string, string, string)');}
s1=s1.string(this);s2=s2.string(this);s3=s3.string(this);var Le=[];for(var i=0,l=s2.length;i<l;i++){var ch=s2.charAt(i);if(!Le[ch])Le[ch]=s3.charAt(i)||'';}
for(var t='',i=0,l=s1.length;i<l;i++){var ch=s1.charAt(i);var Me=Le[ch]
t+=(Me!=ca)?Me:ch;}
return t;},'string',false,[]],'boolean':[function(b){if(arguments.length!=1){throw Error('Function boolean expects (object)');}
return b.bool(this)},'boolean',false,[]],not:[function(b){if(arguments.length!=1){throw Error('Function not expects (object)');}
return!b.bool(this)},'boolean',false,[]],'true':[function(){if(arguments.length!=0){throw Error('Function true expects ()');}
return true;},'boolean',false,[]],'false':[function(){if(arguments.length!=0){throw Error('Function false expects ()');}
return false;},'boolean',false,[]],lang:[function(s){return false;},'boolean',false,[]],number:[function(n){switch(arguments.length){case 0:n=wa.to('number',this.node);break;case 1:n=n.number(this);break;default:throw Error('Function number expects (object?)');break;}
return n;},'number',false,[true,false]],sum:[function(ns){var Ne,n,i,l;if(arguments.length!=1||!(ns=ns.evaluate(this)).isNodeSet){throw Error('Function sum expects (nodeset)');}
Ne=ns.list();n=0;for(i=0,l=Ne.length;i<l;i++){n+=wa.to('number',Ne[i]);}
return n;},'number',false,[]],floor:[function(n){if(arguments.length!=1){throw Error('Function floor expects (number)');}
n=n.number(this);return Math.floor(n);},'number',false,[]],ceiling:[function(n){if(arguments.length!=1){throw Error('Function ceiling expects (number)');}
n=n.number(this);return Math.ceil(n);},'number',false,[]],round:[function(n){if(arguments.length!=1){throw Error('Function round expects (number)');}
n=n.number(this);return Math.round(n);},'number',false,[]]};qa.parse=function(Oe){var Pe,func=new qa(Oe.next());Oe.next();while(Oe.peek()!=')'){if(Oe.empty()){throw Error('missing function argument list');}
Pe=oa.parse(Oe);func.arg(Pe);if(Oe.peek()!=',')break;Oe.next();}
if(Oe.empty()){throw Error('unclosed function argument list');}
if(Oe.next()!=')'){Oe.back();throw Error('bad token: '+Oe.next());}
return func};qa.prototype=new Ma();qa.prototype.evaluate=function(Qe){return this.func.apply(Qe,this.args);};qa.prototype.arg=function(Re){this.args.push(Re);if(Re.needContextPosition){this.needContextPosition=true;}
var Se=this.args;if(Re.needContextNode){Se.needContexNode=true;}
this.needContextNode=Se.needContextNode||this.needContextNodeInfo[Se.length];};qa.prototype.show=function(Te){Te=Te||'';var t='';t+=Te+'function: '+this.name+'\n';Te+=' ';if(this.args.length){t+=Te+'arguments: '+'\n';Te+=' ';for(var i=0;i<this.args.length;i++){t+=this.args[i].show(Te);}}
return t;}; /*@cc_on @if(@_jscript)var Ue=function(Ve,We,Xe,Ye){this.node=Ve;this.nodeType=Ve.nodeType;this.sourceIndex=We;this.subIndex=Xe;this.attributeName=Ye||'';this.order=String.fromCharCode(We)+String.fromCharCode(Xe)+Ye;};Ue.prototype.toString=function(){return this.order;};@else @*/
var Ze={uuid:1,get:function($e){return $e.__ba||($e.__ba=this.uuid++);}};/*@end @*/
if(!window.NodeSet&&window.defaultConfig)window.NodeSet=null;ta=function(){this.length=0;this.nodes=[];this.seen={};this.idIndexMap=null;this.reserveDels=[];};ta.prototype.isNodeSet=true;ta.prototype.isSorted=true; /*@_cc_on
ta.prototype.shortcut=true;@*/ta.prototype.merge=function(af){this.isSorted=false;if(af.only){return this.push(af.only);}
if(this.only){var bf=this.only;delete this.only;this.push(bf);this.length--;}
var cf=af.nodes;for(var i=0,l=cf.length;i<l;i++){this._add(cf[i]);}};ta.prototype.sort=function(){if(this.only)return;if(this.sortOff)return;if(!this.isSorted){this.isSorted=true;this.idIndexMap=null; /*@cc_on
if(this.shortcut){this.nodes.sort();}
else{this.nodes.sort(function(a,b){var df;df=a.sourceIndex-b.sourceIndex;if(df==0)return a.subIndex-a.subIndex;else return df;});}
return;@*/var ef=this.nodes;ef.sort(function(a,b){if(a==b)return 0;if(a.compareDocumentPosition){var ff=a.compareDocumentPosition(b);if(ff&2)return 1;if(ff&4)return -1;return 0;}
else{var gf=a,node2=b,ancestor1=a,ancestor2=b,deep1=0,deep2=0;while(ancestor1=ancestor1.parentNode)deep1++;while(ancestor2=ancestor2.parentNode)deep2++;if(deep1>deep2){while(deep1--!=deep2)gf=gf.parentNode;if(gf==node2)return 1;}
else if(deep2>deep1){while(deep2--!=deep1)node2=node2.parentNode;if(gf==node2)return -1;}
while((ancestor1=gf.parentNode)!=(ancestor2=node2.parentNode)){gf=ancestor1;node2=ancestor2;}
while(gf=gf.nextSibling)if(gf==node2)return -1;return 1;}});}}; /*@cc_on @if(@_jscript)ta.prototype.sourceOffset=1;ta.prototype.subOffset=2;ta.prototype.createWrapper=function(hf){var jf,child,attributes,attributesLength,sourceIndex,subIndex,attributeName;sourceIndex=hf.sourceIndex;if(typeof sourceIndex!='number'){type=hf.nodeType;switch(type){case 2:jf=hf.parentNode;sourceIndex=hf.parentSourceIndex;subIndex=-1;attributeName=hf.nodeName;break;case 9:subIndex=-2;sourceIndex=-1;break;default:child=hf;subIndex=0;do{subIndex++;sourceIndex=child.sourceIndex;if(sourceIndex){jf=child;child=child.lastChild;if(!child){child=jf;break;}
subIndex++;}}
while(child=child.previousSibling);if(!sourceIndex){sourceIndex=hf.parentNode.sourceIndex;}
break;}}
else{subIndex=-2;}
sourceIndex+=this.sourceOffset;subIndex+=this.subOffset;return new Ue(hf,sourceIndex,subIndex,attributeName);};ta.prototype.reserveDelBySourceIndexAndSubIndex=function(kf,lf,mf,nf){var of=this.createIdIndexMap();var pf;if((of=of[kf])&&(pf=of[lf])){if(nf&&(this.length-mf-1)>pf||!nf&&mf<pf){var qf={value:pf,order:String.fromCharCode(pf),toString:function(){return this.order},valueOf:function(){return this.value}};this.reserveDels.push(qf);}}};@else @*/
ta.prototype.reserveDelByNodeID=function(id,rf,sf){var tf=this.createIdIndexMap();var uf;if(uf=tf[id]){if(sf&&(this.length-rf-1)>uf||!sf&&rf<uf){var vf={value:uf,order:String.fromCharCode(uf),toString:function(){return this.order},valueOf:function(){return this.value}};this.reserveDels.push(vf);}}};/*@end @*/
ta.prototype.reserveDelByNode=function(wf,xf,yf){ /*@cc_on @if(@_jscript)wf=this.createWrapper(wf);this.reserveDelBySourceIndexAndSubIndex(wf.sourceIndex,wf.subIndex,xf,yf);@else @*/
this.reserveDelByNodeID(Ze.get(wf),xf,yf);/*@end @*/ };ta.prototype.doDel=function(){if(!this.reserveDels.length)return;if(this.length<0x10000){var zf=this.reserveDels.sort(function(a,b){return b-a});}
else{var zf=this.reserveDels.sort(function(a,b){return b-a});}
for(var i=0,l=zf.length;i<l;i++){this.del(zf[i]);}
this.reserveDels=[];this.idIndexMap=null;};ta.prototype.createIdIndexMap=function(){if(this.idIndexMap){return this.idIndexMap;}
else{var Af=this.idIndexMap={};var Bf=this.nodes;for(var i=0,l=Bf.length;i<l;i++){var Cf=Bf[i]; /*@cc_on @if(@_jscript)var Df=Cf.sourceIndex;var Ef=Cf.subIndex;if(!Af[Df])Af[Df]={};Af[Df][Ef]=i;@else @*/
var id=Ze.get(Cf);Af[id]=i;/*@end @*/ }
return Af;}};ta.prototype.del=function(Ff){this.length--;if(this.only){delete this.only;}
else{var Gf=this.nodes.splice(Ff,1)[0];if(this._first==Gf){delete this._first;delete this._firstSourceIndex;delete this._firstSubIndex;} /*@cc_on @if(@_jscript)delete this.seen[Gf.sourceIndex][Gf.subIndex];@else @*/
delete this.seen[Ze.get(Gf)];/*@end @*/ }};ta.prototype.delDescendant=function(Hf,If){if(this.only)return;var Jf=Hf.nodeType;if(Jf!=1&&Jf!=9)return;if(Da.applewebkit2)return;if(!Hf.contains){if(Jf==1){var Kf=Hf;Hf={contains:function(Lf){return Lf.compareDocumentPosition(Kf)&8;}};}
else{Hf={contains:function(){return true;}};}}
var Mf=this.nodes;for(var i=If+1;i<Mf.length;i++){ /*@cc_on @if(@_jscript)if(Mf[i].node.nodeType==1&&Hf.contains(Mf[i].node)){@else @*/
if(Hf.contains(Mf[i])){/*@end @*/
this.del(i);i--;}}};ta.prototype._add=function(Nf,Of){ /*@cc_on @if(@_jscript)var Pf,firstSourceIndex,firstSubIndex,sourceIndex,subIndex,attributeName;sourceIndex=Nf.sourceIndex;subIndex=Nf.subIndex;attributeName=Nf.attributeName;seen=this.seen;seen=seen[sourceIndex]||(seen[sourceIndex]={});if(Nf.nodeType==2){seen=seen[subIndex]||(seen[subIndex]={});if(seen[attributeName]){return true;}
seen[attributeName]=true;}
else{if(seen[subIndex]){return true;}
seen[subIndex]=true;}
if(sourceIndex>=0x10000||subIndex>=0x10000){this.shortcut=false;}
if(this._first||this.nodes.length==0){Pf=this._first;firstSourceIndex=this._firstSourceIndex;firstSubIndex=this._firstSubIndex;if(!Pf||firstSourceIndex>sourceIndex||(firstSourceIndex==sourceIndex&&firstSubIndex>subIndex)){this._first=Nf;this._firstSourceIndex=sourceIndex;this._firstSubIndex=subIndex}}@else @*/
var Qf=this.seen;var id=Ze.get(Nf);if(Qf[id])return true;Qf[id]=true;/*@end @*/
this.length++;if(Of)this.nodes.unshift(Nf);else this.nodes.push(Nf);};ta.prototype.unshift=function(Rf){if(!this.length){this.length++;this.only=Rf;return}
if(this.only){var Sf=this.only;delete this.only;this.unshift(Sf);this.length--;} /*@cc_on
Rf=this.createWrapper(Rf);@*/return this._add(Rf,true);};ta.prototype.push=function(Tf){if(!this.length){this.length++;this.only=Tf;return;}
if(this.only){var Uf=this.only;delete this.only;this.push(Uf);this.length--;} /*@cc_on
Tf=this.createWrapper(Tf);@*/return this._add(Tf);};ta.prototype.first=function(){if(this.only)return this.only; /*@cc_on
if(this._first)return this._first.node;if(this.nodes.length>1)this.sort();var Vf=this.nodes[0];return Vf?Vf.node:ca;@*/if(this.nodes.length>1)this.sort();return this.nodes[0];};ta.prototype.list=function(){if(this.only)return[this.only];this.sort(); /*@cc_on
var i,l,Mf,results;Mf=this.nodes;results=[];for(i=0,l=Mf.length;i<l;i++){results.push(Mf[i].node);}
return results;@*/return this.nodes;};ta.prototype.string=function(){var Wf=this.only||this.first();return Wf?wa.to('string',Wf):'';};ta.prototype.bool=function(){return!!(this.length||this.only);};ta.prototype.number=function(){return+this.string();};ta.prototype.iterator=function(Xf){this.sort();var Yf=this;if(!Xf){var Zf=0;return function(){if(Yf.only&&Zf++==0)return Yf.only; /*@cc_on @if(@_jscript)var $f=Yf.nodes[Zf++];if($f)return $f.node;return ca;@else @*/
return Yf.nodes[Zf++];/*@end @*/ };}
else{var Zf=0;return function(){var ag=Yf.length-(Zf++)-1;if(Yf.only&&ag==0)return Yf.only; /*@cc_on @if(@_jscript)var bg=Yf.nodes[ag];if(bg)return bg.node;return ca;@else @*/
return Yf.nodes[ag];/*@end @*/ };}};var cg=function(dg){dg=dg||this;var eg=dg.document;var ca=dg.undefined;dg.XPathExpression=function(fg){if(!fg.length){throw dg.Error('no expression');}
var gg=this.lexer=Ea(fg);if(gg.empty()){throw dg.Error('no expression');}
this.expr=oa.parse(gg);if(!gg.empty()){throw dg.Error('bad token: '+gg.next());}};dg.XPathExpression.prototype.evaluate=function(hg,ig){return new dg.XPathResult(this.expr.evaluate(new Ia(hg)),ig);};dg.XPathResult=function(jg,kg){if(kg==0){switch(typeof jg){case 'object':kg++;case 'boolean':kg++;case 'string':kg++;case 'number':kg++;}}
this.resultType=kg;switch(kg){case 1:this.numberValue=jg.isNodeSet?jg.number():+jg;return;case 2:this.stringValue=jg.isNodeSet?jg.string():''+jg;return;case 3:this.booleanValue=jg.isNodeSet?jg.bool():!!jg;return;case 4:case 5:case 6:case 7:this.nodes=jg.list();this.snapshotLength=jg.length;this.index=0;this.invalidIteratorState=false;break;case 8:case 9:this.singleNodeValue=jg.first();return;}};dg.XPathResult.prototype.iterateNext=function(){return this.nodes[this.index++]};dg.XPathResult.prototype.snapshotItem=function(i){return this.nodes[i]};dg.XPathResult.ANY_TYPE=0;dg.XPathResult.NUMBER_TYPE=1;dg.XPathResult.STRING_TYPE=2;dg.XPathResult.BOOLEAN_TYPE=3;dg.XPathResult.UNORDERED_NODE_ITERATOR_TYPE=4;dg.XPathResult.ORDERED_NODE_ITERATOR_TYPE=5;dg.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE=6;dg.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE=7;dg.XPathResult.ANY_UNORDERED_NODE_TYPE=8;dg.XPathResult.FIRST_ORDERED_NODE_TYPE=9;eg.createExpression=function(lg){return new dg.XPathExpression(lg,null);};eg.evaluate=function(mg,ng,_,og){return eg.createExpression(mg,null).evaluate(ng,og);};};var pg;if(ea.targetFrame){var qg=document.getElementById(ea.targetFrame);if(qg)pg=qg.contentWindow;}
if(ea.exportInstaller){window.install=cg;}
if(!ea.hasNative||!ea.useNative){cg(pg||window);}})();
#+END_SRC
================================================
FILE: 03-pedestal-service/pedestal-service-skeleton.org
================================================
#+TITLE: Pedestal Service, in Org literate programming form
#+AUTHOR: Kai Wu
#+EMAIL: k@limist.com
#+STARTUP: overview hidestars
#+PROPERTY: mkdirp yes
This is a literate programming (LP) file for a Pedestal Service
(back-end, multi-thread per HTTP request), in Emacs Org mode format.
To start building your Service, you'd likely begin by doing
search-and-replace to update the default names of "mysvc" below; then
save this LP file to the top-level of your project directory. NOTE
that the :tangle paths specified below assume this LP file sits above
(not in) the directory containing the Pedestal Service; if that's not
what you want, drop the leading "mysvc/" in the :tangle paths.
* Project meta
** Leiningen configuration
#+BEGIN_SRC clojure :tangle mysvc/project.clj
(defproject mysvc "0.0.1-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]
[io.pedestal/pedestal.service "0.2.1"]
[io.pedestal/pedestal.service-tools "0.2.1"]
;; Remove this line and uncomment the next line to
;; use Tomcat instead of Jetty:
[io.pedestal/pedestal.jetty "0.2.1"]
;; [io.pedestal/pedestal.tomcat "0.2.1"]
]
:min-lein-version "2.0.0"
:resource-paths ["config", "resources"]
:aliases {"run-dev" ["trampoline" "run" "-m" "mysvc.server/run-dev"]}
:repl-options {:init-ns user
:init (try
(use 'io.pedestal.service-tools.dev)
(require 'mysvc.service)
;; Nasty trick to get around being unable to reference non-clojure.core symbols in :init
(eval '(init mysvc.service/service #'mysvc.service/routes))
(catch Throwable t
(println "ERROR: There was a problem loading io.pedestal.service-tools.dev")
(clojure.stacktrace/print-stack-trace t)
(println)))
:welcome (println "Welcome to pedestal-service! Run (tools-help) to see a list of useful functions.")}
:main ^{:skip-aot true} mysvc.server)
#+END_SRC
** README
#+BEGIN_SRC markdown :tangle mysvc/README.md
# mysvc
FIXME
## Getting Started
1. Start the application: `lein run`
2. Go to [localhost:8080](http://localhost:8080/) to see: `Hello World!`
3. Read your app's source code at src/mysvc/service.clj. Explore the docs of functions
that define routes and responses.
4. Run your app's tests with `lein test`. Read the tests at test/mysvc/service_test.clj.
5. Learn more! See the [Links section below](#links).
## Configuration
To configure logging see config/logback.xml. By default, the app logs to stdout and logs/.
To learn more about configuring Logback, read its [documentation](http://logback.qos.ch/documentation.html).
## Links
- [Other examples](https://github.com/pedestal/samples)
#+END_SRC
** User/REPL configuration
#+BEGIN_SRC clojure :tangle mysvc/config/user.clj
;; Empty file to permit project.clj :repl-options {:init user} to work
#+END_SRC
** Logback configuration
#+BEGIN_SRC xml :tangle mysvc/config/logback.xml
<!-- Logback configuration. See http://logback.qos.ch/manual/index.html -->
<configuration scan="true" scanPeriod="10 seconds">
<!-- Simple file output -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>logs/mysvc-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 64 MB -->
<maxFileSize>64 MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- Safely log to the same file from multiple JVMs. Degrades performance! -->
<prudent>true</prudent>
</appender>
<!-- Console output -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%-5level %logger{36} - %msg%n</pattern>
</encoder>
<!-- Only log level INFO and above -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<!-- Enable FILE and STDOUT appenders for all log messages.
By default, only log at level INFO and above. -->
<root level="INFO">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
<!-- For loggers in the these namespaces, log at all levels. -->
<logger name="user" level="ALL" />
<!-- To log pedestal internals, enable this and change ThresholdFilter to DEBUG
<logger name="pedestal" level="ALL" />
-->
</configuration>
#+END_SRC
* Code and tests
** The web service
#+BEGIN_SRC clojure :tangle mysvc/src/mysvc/service.clj
(ns mysvc.service
(:require [io.pedestal.service.http :as bootstrap]
[io.pedestal.service.http.route :as route]
[io.pedestal.service.http.body-params :as body-params]
[io.pedestal.service.http.route.definition :refer [defroutes]]
[ring.util.response :as ring-resp]))
(defn about-page
[request]
(ring-resp/response (format "Clojure %s" (clojure-version))))
(defn home-page
[request]
(ring-resp/response "Hello World!"))
(defroutes routes
[[["/" {:get home-page}
;; Set default interceptors for /about and any other paths under /
^:interceptors [(body-params/body-params) bootstrap/html-body]
["/about" {:get about-page}]]]])
;; You can use this fn or a per-request fn via io.pedestal.service.http.route/url-for
(def url-for (route/url-for-routes routes))
;; Consumed by mysvc.server/create-server
(def service {:env :prod
;; You can bring your own non-default interceptors. Make
;; sure you include routing and set it up right for
;; dev-mode. If you do, many other keys for configuring
;; default interceptors will be ignored.
;; :bootstrap/interceptors []
::bootstrap/routes routes
;; Uncomment next line to enable CORS support, add
;; string(s) specifying scheme, host and port for
;; allowed source(s):
;;
;; "http://localhost:8080"
;;
;;::bootstrap/allowed-origins ["scheme://host:port"]
;; Root for resource interceptor that is available by default.
::bootstrap/resource-path "/public"
;; Either :jetty or :tomcat (see comments in project.clj
;; to enable Tomcat)
;;::bootstrap/host "localhost"
::bootstrap/type :jetty
::bootstrap/port 8080})
#+END_SRC
*** Tests
#+BEGIN_SRC clojure :tangle mysvc/test/mysvc/service_test.clj
(ns mysvc.service-test
(:require [clojure.test :refer :all]
[io.pedestal.service.test :refer :all]
[io.pedestal.service.http :as bootstrap]
[mysvc.service :as service]))
(def service
(::bootstrap/service-fn (bootstrap/create-servlet service/service)))
(deftest home-page-test
(is (=
(:body (response-for service :get "/"))
"Hello World!"))
(is (=
(:headers (response-for service :get "/"))
{"Content-Type" "text/html;charset=UTF-8"})))
(deftest about-page-test
(is (.contains
(:body (response-for service :get "/about"))
"Clojure 1.5"))
(is (=
(:headers (response-for service :get "/about"))
{"Content-Type" "text/html;charset=UTF-8"})))
#+END_SRC
** Server/servlet definition
#+BEGIN_SRC clojure :tangle mysvc/src/mysvc/server.clj
(ns mysvc.server
(:gen-class) ; for -main method in uberjar
(:require [io.pedestal.service-tools.server :as server]
[mysvc.service :as service]
[io.pedestal.service-tools.dev :as dev]))
(defn run-dev
"The entry-point for 'lein run-dev'"
[& args]
(dev/init service/service #'service/routes)
(apply dev/-main args))
;; To implement your own server, copy io.pedestal.service-tools.server and
;; customize it.
(defn -main
"The entry-point for 'lein run'"
[& args]
(server/init service/service)
(apply server/-main args))
;; Fns for use with io.pedestal.servlet.ClojureVarServlet
(defn servlet-init [this config]
(server/init service/service)
(server/servlet-init this config))
(defn servlet-destroy [this]
(server/servlet-destroy this))
(defn servlet-service [this servlet-req servlet-resp]
(server/servlet-service this servlet-req servlet-resp))
#+END_SRC
================================================
FILE: 05-luminus-site/luminus-site-skeleton.org
================================================
#+TITLE: Luminus/Clojure Website Template, in Org/LP Format
#+AUTHOR: Kai Wu
#+EMAIL: k@limist.com
#+LANGUAGE: en
#+STARTUP: align hidestars lognotestate
#+PROPERTY: mkdirp yes
* Introduction
This is the Emacs [[http://orgmode.org][Org mode]] literate-programming single file template
for a web application using the Luminus "framework." For the
structure/files below, I used the command,
=lein new luminus mysite +cljs +site +http-kit=
** Useful Luminus information/links
Taken from the Luminus-provided file at
=mysite/resources/public/md/docs.md=
1. [[http://www.luminusweb.net/docs/html_templating.md][HTML templating]]
2. [[http://www.luminusweb.net/docs/database.md][Accessing the database]]
3. [[http://www.luminusweb.net/docs/static_resources.md][Serving static resources]]
4. [[http://www.luminusweb.net/docs/responses.md][Setting response types]]
5. [[http://www.luminusweb.net/docs/routes.md][Defining routes]]
6. [[http://www.luminusweb.net/docs/middleware.md][Adding middleware]]
7. [[http://www.luminusweb.net/docs/sessions_cookies.md][Sessions and cookies]]
8. [[http://www.luminusweb.net/docs/security.md][Security]]
9. [[http://www.luminusweb.net/docs/deployment.md][Deploying the application]]
** What files/directories are NOT included in this =.org= file?
+ Everything under =mysite/resources= such as CSS, fonts, images, JS.
Since those files are not tangled/produced from here, you'll see the
=resources/= subdir already tracked in the git repository.
* Project meta
** Main configuration
As of [2013-10-29 Tue], the starting =project.clj= produced by the
Luminus template used an indentation style that's unorthodox; the
version below differs with the Luminus one only in indentation.
#+BEGIN_SRC clojure :tangle mysite/project.clj
(defproject
mysite "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.5.1"]
[lib-noir "0.7.4"]
[compojure "1.1.5"]
[ring-server "0.3.0"]
[selmer "0.5.1"]
[com.taoensso/timbre "2.6.3"]
[com.postspectacular/rotor "0.1.0"]
[com.taoensso/tower "1.7.1"]
[markdown-clj "0.9.33"]
[com.h2database/h2 "1.3.173"]
[korma "0.3.0-RC6"]
[log4j "1.2.17" :exclusions [javax.mail/mail
javax.jms/jms
com.sun.jdmk/jmxtools
com.sun.jmx/jmxri]]
[http-kit "2.1.11"]
[org.clojure/clojurescript "0.0-1934"]
[domina "1.0.2"]
[prismatic/dommy "0.1.2"]
[cljs-ajax "0.2.0"]]
:cljsbuild {:builds [{:source-paths ["src-cljs"],
:compiler {:pretty-print false,
:output-to "resources/public/js/site.js",
:optimizations :advanced}}]}
:ring {:handler mysite.handler/app,
:init mysite.handler/init,
:destroy mysite.handler/destroy}
:profiles {:production {:ring {:open-browser? false, :stacktraces? false, :auto-reload? false}},
:dev {:dependencies [[ring-mock "0.1.5"] [ring/ring-devel "1.2.0"]]}}
:url "http://example.com/FIXME"
:aot all
:main mysite.core
:plugins [[lein-ring "0.8.7"] [lein-cljsbuild "0.3.3"]]
:description "FIXME: write description"
:min-lein-version "2.0.0")
#+END_SRC
** Procfile, for Heroku deployment
If you want to use this, change the =:tangle no= below to
=:tangle mysite/Procfile=
#+BEGIN_SRC text :tangle no
web: lein with-profile production trampoline ring server
#+END_SRC
** The README
Or you could just do your documentation here in this Org file, which
is superior to Markdown because Org has powerful structural editing.
#+BEGIN_SRC markdown :tangle mysite/README.md
# mysite
FIXME
## Prerequisites
You will need [Leiningen][1] 2.0 or above installed.
[1]: https://github.com/technomancy/leiningen
## Running
To start a web server for the application, run:
lein ring server
## License
Copyright © 2013 FIXME
#+END_SRC
** =.gitignore=
#+BEGIN_SRC shell :tangle mysite/.gitignore
/target
/lib
/classes
/checkouts
pom.xml
*.jar
*.class
/.lein-*
/.env
# Ignore all files tangled from this Org/LP file:
project.clj
README.md
Procfile
src/
src-cljs/
test/
#+END_SRC
* Code and tests
** log4j configuration, for Korma
Note that for application/Clojure-level logging we use [[https://github.com/ptaoussanis/timbre][Timbre]].
Note the header argument of =:padline no= for XML files is needed to
avoid an error in XML parsing.
#+BEGIN_SRC xml :tangle mysite/src/log4j.xml :padline no
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<logger name="com.mchange">
<level value="WARN"/>
</logger>
</log4j:configuration>
#+END_SRC
** Server-side
*** The core: server definition
#+BEGIN_SRC clojure :tangle mysite/src/mysite/core.clj
(ns mysite.core
(:require [mysite.handler :refer [app]]
[ring.middleware.reload :as reload]
[org.httpkit.server :as http-kit]
[taoensso.timbre :as timbre])
(:gen-class))
(defn dev? [args] (some #{"-dev"} args))
(defn port [args]
(if-let [port (first (remove #{"-dev"} args))]
(Integer/parseInt port)
3000))
(defn -main [& args]
(http-kit/run-server
(if (dev? args) (reload/wrap-reload app) app)
{:port (port args)})
(timbre/info "server started on port"))
#+END_SRC
**** Control the server from the REPL
#+BEGIN_SRC clojure :tangle mysite/src/mysite/repl.clj
(ns mysite.repl
(:use mysite.handler
ring.server.standalone
[ring.middleware file-info file]))
(defonce server (atom nil))
(defn get-handler []
;; #'app expands to (var app) so that when we reload our code,
;; the server is forced to re-resolve the symbol in the var
;; rather than having its own copy. When the root binding
;; changes, the server picks it up without having to restart.
(-> #'app
; Makes static assets in $PROJECT_DIR/resources/public/ available.
(wrap-file "resources")
; Content-Type, Content-Length, and Last Modified headers for files in body
(wrap-file-info)))
(defn start-server
"used for starting the server in development mode from REPL"
[& [port]]
(let [port (if port (Integer/parseInt port) 3000)]
(reset! server
(serve (get-handler)
{:port port
:init init
:auto-reload? true
:destroy destroy
:join? false}))
(println (str "You can view the site at http://localhost:" port))))
(defn stop-server []
(.stop @server)
(reset! server nil))
#+END_SRC
*** Handler: base routes, app-level config
#+BEGIN_SRC clojure :tangle mysite/src/mysite/handler.clj
(ns mysite.handler
(:require [compojure.core :refer [defroutes]]
[mysite.routes.home :refer [home-routes]]
[noir.util.middleware :as middleware]
[compojure.route :as route]
[taoensso.timbre :as timbre]
[com.postspectacular.rotor :as rotor]
[mysite.routes.auth :refer [auth-routes]]
[mysite.models.schema :as schema]
[mysite.routes.cljsexample :refer [cljs-routes]]))
(defroutes app-routes
(route/resources "/")
(route/not-found "Not Found"))
(defn init
"init will be called once when
app is deployed as a servlet on
an app server such as Tomcat
put any initialization code here"
[]
(timbre/set-config!
[:appenders :rotor]
{:min-level :info,
:enabled? true,
:async? false,
:max-message-per-msecs nil,
:fn rotor/append})
(timbre/set-config!
[:shared-appender-config :rotor]
{:path "mysite.log", :max-size (* 512 1024), :backlog 10})
(if-not (schema/initialized?) (schema/create-tables))
(timbre/info "mysite started successfully"))
(defn destroy
"destroy will be called when your application
shuts down, put any clean up code here"
[]
(timbre/info "mysite is shutting down..."))
(def app
(middleware/app-handler
[cljs-routes auth-routes home-routes app-routes]
:middleware
[]
:access-rules
[]
:formats
[:json-kw :edn]))
#+END_SRC
**** Tests
#+BEGIN_SRC clojure :tangle mysite/test/mysite/test/handler.clj
(ns mysite.test.handler
(:use clojure.test
ring.mock.request
mysite.handler))
(deftest test-app
(testing "main route"
(let [response (app (request :get "/"))]
(is (= (:status response) 200))
(is (= (:body response)
"<html>\n <head>\n <title>Welcome to mysite</title>\n <link href=\"/css/screen.css\" rel=\"stylesheet\" type=\"text/css\"></link>\n </head>\n <body>\n <div class=\"navbar navbar-fixed-top navbar-inverse\">\n <ul class=\"nav\">\n <li>\n <a href=\"/\">Home</a>\n </li>\n <li>\n <a href=\"/about\">About</a>\n </li>\n </ul>\n </div>\n <div id=\"content\">\n <h1>Welcome to mysite</h1>\n \n<h2>Some links to get started</h2><ol><li><a href='http://www.luminusweb.net/docs/html_templating.md'>HTML templating</a></li><li><a href='http://www.luminusweb.net/docs/database.md'>Accessing the database</a></li><li><a href='http://www.luminusweb.net/docs/static_resources.md'>Serving static resources</a></li><li><a href='http://www.luminusweb.net/docs/responses.md'>Setting response types</a></li><li><a href='http://www.luminusweb.net/docs/routes.md'>Defining routes</a></li><li><a href='http://www.luminusweb.net/docs/middleware.md'>Adding middleware</a></li><li><a href='http://www.luminusweb.net/docs/sessions_cookies.md'>Sessions and cookies</a></li><li><a href='http://www.luminusweb.net/docs/security.md'>Security</a></li><li><a href='http://www.luminusweb.net/docs/deployment.md'>Deploying the application</a></li></ol>\n\n </div> \n <footer>Copyright ...</footer>\n </body>\n</html>\n\n\n"))))
(testing "not-found route"
(let [response (app (request :get "/invalid"))]
(is (= (:status response) 404)))))
#+END_SRC
*** Models and persistence
**** Database queries/functions
#+BEGIN_SRC clojure :tangle mysite/src/mysite/models/db.clj
(ns mysite.models.db
(:use korma.core
[korma.db :only (defdb)])
(:require [mysite.models.schema :as schema]))
(defdb db schema/db-spec)
(defentity users)
(defn create-user [user]
(insert users
(values user)))
(defn update-user [id first-name last-name email]
(update users
(set-fields {:first_name first-name
:last_name last-name
:email email})
(where {:id id})))
(defn get-user [id]
(first (select users
(where {:id id})
(limit 1))))
#+END_SRC
**** Schema
#+BEGIN_SRC clojure :tangle mysite/src/mysite/models/schema.clj
(ns mysite.models.schema
(:require [clojure.java.jdbc :as sql]
[noir.io :as io]))
(def db-store "site.db")
(def db-spec {:classname "org.h2.Driver"
:subprotocol "h2"
:subname (str (io/resource-path) db-store)
:user "sa"
:password ""
:naming {:keys clojure.string/lower-case
:fields clojure.string/upper-case}})
(defn initialized?
"checks to see if the database schema is present"
[]
(.exists (new java.io.File (str (io/resource-path) db-store ".h2.db"))))
(defn create-users-table
[]
(sql/with-connection db-spec
(sql/create-table
:users
[:id "varchar(20) PRIMARY KEY"]
[:first_name "varchar(30)"]
[:last_name "varchar(30)"]
[:email "varchar(30)"]
[:admin :boolean]
[:last_login :time]
[:is_active :boolean]
[:pass "varchar(100)"])))
(defn create-tables
"creates the database tables used by the application"
[]
(create-users-table))
#+END_SRC
*** Routes: URLs/pages and workflows
**** Authentication workflow
#+BEGIN_SRC clojure :tangle mysite/src/mysite/routes/auth.clj
(ns mysite.routes.auth
(:use compojure.core)
(:require [mysite.views.layout :as layout]
[noir.session :as session]
[noir.response :as resp]
[noir.validation :as vali]
[noir.util.crypt :as crypt]
[mysite.models.db :as db]))
(defn valid? [id pass pass1]
(vali/rule (vali/has-value? id)
[:id "user ID is required"])
(vali/rule (vali/min-length? pass 5)
[:pass "password must be at least 5 characters"])
(vali/rule (= pass pass1)
[:pass1 "entered passwords do not match"])
(not (vali/errors? :id :pass :pass1)))
(defn register [& [id]]
(layout/render
"registration.html"
{:id id
:id-error (vali/on-error :id first)
:pass-error (vali/on-error :pass first)
:pass1-error (vali/on-error :pass1 first)}))
(defn handle-registration [id pass pass1]
(if (valid? id pass pass1)
(try
(do
(db/create-user {:id id :pass (crypt/encrypt pass)})
(session/put! :user-id id)
(resp/redirect "/"))
(catch Exception ex
(vali/rule false [:id (.getMessage ex)])
(register)))
(register id)))
(defn profile []
(layout/render
"profile.html"
{:user (db/get-user (session/get :user-id))}))
(defn update-profile [{:keys [first-name last-name email]}]
(db/update-user (session/get :user-id) first-name last-name email)
(profile))
(defn handle-login [id pass]
(let [user (db/get-user id)]
(if (and user (crypt/compare pass (:pass user)))
(session/put! :user-id id))
(resp/redirect "/")))
(defn logout []
(session/clear!)
(resp/redirect "/"))
(defroutes auth-routes
(GET "/register" []
(register))
(POST "/register" [id pass pass1]
(handle-registration id pass pass1))
(GET "/profile" [] (profile))
(POST "/update-profile" {params :params} (update-profile params))
(POST "/login" [id pass]
(handle-login id pass))
(GET "/logout" []
(logout)))
#+END_SRC
**** Routes->pages: homepage, about
#+BEGIN_SRC clojure :tangle mysite/src/mysite/routes/home.clj
(ns mysite.routes.home
(:use compojure.core)
(:require [mysite.views.layout :as layout]
[mysite.util :as util]))
(defn home-page []
(layout/render
"home.html" {:content (util/md->html "/md/docs.md")}))
(defn about-page []
(layout/render "about.html"))
(defroutes home-routes
(GET "/" [] (home-page))
(GET "/about" [] (about-page)))
#+END_SRC
**** CLJS client app hosting
#+BEGIN_SRC clojure :tangle mysite/src/mysite/routes/cljsexample.clj
(ns mysite.routes.cljsexample
(:require [compojure.core :refer :all]
[noir.response :as response]
[mysite.views.layout :as layout]))
(def messages
(atom
[{:message "Hello world"
:user "Foo"}
{:message "Ajax is fun"
:user "Bar"}]))
(defroutes cljs-routes
(GET "/cljsexample" [] (layout/render "cljsexample.html"))
(GET "/messages" [] (response/edn @messages))
(POST "/add-message" [message user]
(response/edn
(swap! messages conj {:message message :user user}))))
#+END_SRC
*** Utility/helper functions
In other words, useful code that doesn't fit elsewhere:
#+BEGIN_SRC clojure :tangle mysite/src/mysite/util.clj
(ns mysite.util
(:require [noir.io :as io]
[markdown.core :as md]))
(defn md->html
"reads a markdown file from public/md and returns an HTML string"
[filename]
(->>
(io/slurp-resource filename)
(md/md-to-html-string)))
#+END_SRC
*** Visuals and templates/layouts
**** Layout
#+BEGIN_SRC clojure :tangle mysite/src/mysite/views/layout.clj
(ns mysite.views.layout
(:require [selmer.parser :as parser]
[clojure.string :as s]
[ring.util.response :refer [content-type response]]
[noir.session :as session])
(:import compojure.response.Renderable))
(def template-path "mysite/views/templates/")
(deftype
RenderableTemplate
[template params]
Renderable
(render
[this request]
(content-type
(->>
(assoc
params
(keyword (s/replace template #".html" "-selected"))
"active"
:servlet-context
(:context request)
:user-id
(session/get :user-id))
(parser/render-file (str template-path template))
response)
"text/html; charset=utf-8")))
(defn render [template & [params]]
(RenderableTemplate. template params))
#+END_SRC
**** About page
#+BEGIN_SRC html :tangle mysite/src/mysite/views/templates/about.html
{% extends "mysite/views/templates/base.html" %}
{% block content %}
<p>this is the story of mysite... work in progress</p>
{% endblock %}
#+END_SRC
**** Base page
#+BEGIN_SRC html :tangle mysite/src/mysite/views/templates/base.html
<!DOCTYPE HTML>
<html>
<head>
<title>Welcome to mysite</title>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{servlet-context}}/">mysite</a>
</div>
{% block menu %}
{% include "mysite/views/templates/menu.html" %}
{% endblock %}
</div>
</div>
<div class="container">
{% block content %}
{% endblock %}
</div>
<!-- scripts and styles -->
{% style "/css/bootstrap-theme.min.css" %}
{% style "/css/bootstrap.min.css" %}
{% style "/css/screen.css" %}
<script src="//code.jquery.com/jquery-2.0.3.min.js" type="text/javascript"></script>
{% script "/js/bootstrap.min.js" %}
<script type="text/javascript">
var context = "{{servlet-context}}";
$(function() {
$("#{{selected-page}}").addClass("active");
});
</script>
</body>
</html>
#+END_SRC
**** CLJS example
#+BEGIN_SRC html :tangle mysite/src/mysite/views/templates/cljsexample.html
{% extends "mysite/views/templates/base.html" %}
{% block content %}
<br/>
<div id="messages"></div>
<textarea id="message"></textarea>
<br/>
<input type="text" id="user"></input>
<br/>
<button id="send">add message</button>
<!-- scripts -->
<script type="text/javascript" src="{{servlet-context}}/js/site.js"></script>
<script type="text/javascript">
mysite.main.init();
</script>
{% endblock %}
#+END_SRC
**** Homepage
#+BEGIN_SRC html :tangle mysite/src/mysite/views/templates/home.html
{% extends "mysite/views/templates/base.html" %}
{% block content %}
<div class="jumbotron">
<h1>Welcome to mysite</h1>
<p>Time to start building your site!</p>
<p><a class="btn btn-primary btn-large" href="http://luminusweb.net">Learn more »</a></p>
</div>
<div class="row-fluid">
<div class="span8">
{{content|safe}}
</div>
</div>
{% endblock %}
#+END_SRC
**** Menu template
#+BEGIN_SRC html :tangle mysite/src/mysite/views/templates/menu.html
<div class="navbar-collapse collapse">
{% if user-id %}
<div class="btn-group pull-right">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-user"></i>{{user-id}} <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="{{servlet-context}}/profile">Profile</a></li>
<li class="divider"></li>
<li><a href="{{servlet-context}}/logout">Sign Out</a></li>
</ul>
</li>
</ul>
</div>
{% else %}
<div class="btn-group pull-right">
<ul class="nav navbar-nav">
<li class="dropdown">
<form action="{{servlet-context}}/login" class="navbar-form" method="POST">
<input class="span2"
id="id"
name="id"
placeholder="user id"
style="margin-right: 5px"
type="text"/>
<input class="span2"
id="pass"
name="pass"
placeholder="password"
style="margin-right: 5px"
type="password"/>
<input class="btn" type="submit" value="Login"/>
</form>
</li>
<li>
<a href="{{servlet-context}}/register">Register</a>
</li>
</ul>
</div>
{% endif %}
</div>
#+END_SRC
**** Profile template
#+BEGIN_SRC html :tangle mysite/src/mysite/views/templates/profile.html
{% extends "mysite/views/templates/base.html" %}
{% block menu %}
{% endblock %}
{% block content %}
<h2>User details for {{user.id}}</h2>
<form action="{{servlet-context}}/update-profile" method="POST">
<label for="id">first name</label>
<p>
<input id="first-name" name="first-name" tabindex="1" type="text" value={{user.first_name}}></input>
</p>
<label for="id">last name</label>
<p>
<input id="last-name" name="last-name" tabindex="1" type="text" value={{user.last_name}}></input>
</p>
<label for="id">email</label>
<p>
<input id="email" name="email" tabindex="1" type="text" value={{user.email}}></input>
</p>
<input class="btn" tabindex="4" type="submit" value="update profile">
</form>
{% endblock %}
#+END_SRC
**** Registration page
#+BEGIN_SRC html :tangle mysite/src/mysite/views/templates/registration.html
{% extends "mysite/views/templates/base.html" %}
{% block menu %}
{% endblock %}
{% block content %}
<form action="{{servlet-context}}/register" method="POST">
<label for="id">user id</label>
{% if id-error %}
<div class="error">{{id-error}}</div>
{% endif %}
<p>
<input id="id" name="id" tabindex="1" type="text" value={{id}}></input>
</p>
<label for="pass">password</label>
{% if pass-error %}
<div class="error">{{pass-error}}</div>
{% endif %}
<p>
<input id="pass" name="pass" tabindex="2" type="password"></input>
</p>
<label for="pass1">retype password</label>
{% if pass1-error %}
<div class="error">{{pass1-error}}</div>
{% endif %}
<p>
<input id="pass1" name="pass1" tabindex="3" type="password"></input>
</p>
<input class="btn" tabindex="4" type="submit" value="create account">
</form>
{% endblock %}
#+END_SRC
** Client-side
*** ClojureScript
#+BEGIN_SRC clojurescript :tangle mysite/src-cljs/main.cljs
(ns mysite.main
(:require [ajax.core :refer [GET POST]]
[domina :refer [value by-id destroy-children! append!]]
[domina.events :refer [listen!]]
[dommy.template :as template]))
(defn render-message [{:keys [message user]}]
[:li [:p {:id user} message " - " user]])
(defn render-messages [messages]
(let [messages-div (by-id "messages")]
(destroy-children! messages-div)
(->> messages
(map render-message)
(into [:ul])
template/node
(append! messages-div))))
(defn add-message [_]
(POST "/add-message"
{:params {:message (value (by-id "message"))
:user (value (by-id "user"))}
:handler render-messages}))
(defn ^:export init []
(GET "/messages" {:handler render-messages})
(listen! (by-id "send") :click add-message))
#+END_SRC
================================================
FILE: 05-luminus-site/mysite/resources/public/css/screen.css
================================================
html,
body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
height: 100%;
padding-top: 70px;
}
.error {
color: red;
}
================================================
FILE: 05-luminus-site/mysite/resources/public/md/docs.md
================================================
### Here are some links to get started
1. [HTML templating](http://www.luminusweb.net/docs/html_templating.md)
2. [Accessing the database](http://www.luminusweb.net/docs/database.md)
3. [Serving static resources](http://www.luminusweb.net/docs/static_resources.md)
4. [Setting response types](http://www.luminusweb.net/docs/responses.md)
5. [Defining routes](http://www.luminusweb.net/docs/routes.md)
6. [Adding middleware](http://www.luminusweb.net/docs/middleware.md)
7. [Sessions and cookies](http://www.luminusweb.net/docs/sessions_cookies.md)
8. [Security](http://www.luminusweb.net/docs/security.md)
9. [Deploying the application](http://www.luminusweb.net/docs/deployment.md)
================================================
FILE: LICENSE
================================================
Eclipse Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and documentation
distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates' from
a Contributor if it was added to the Program by such Contributor itself or
anyone acting on such Contributor's behalf. Contributions do not include
additions to the Program which: (i) are separate modules of software
distributed in conjunction with the Program under their own license
agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly perform,
distribute and sublicense the Contribution of such Contributor, if any, and
such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of the
Contribution and the Program if, at the time the Contribution is added by
the Contributor, such addition of the Contribution causes such combination
to be covered by the Licensed Patents. The patent license shall not apply
to any other combinations which include the Contribution. No hardware per
se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses to
its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor
disclaims any liability to Recipient for claims brought by any other entity
based on infringement of intellectual property rights or otherwise. As a
condition to exercising the rights and licenses granted hereunder, each
Recipient hereby assumes sole responsibility to secure any other
intellectual property rights needed, if any. For example, if a third party
patent license is required to allow Recipient to distribute the Program, it
is Recipient's responsibility to acquire that license before distributing
the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright
license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under its
own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties and
conditions, express and implied, including warranties or conditions of
title and non-infringement, and implied warranties or conditions of
merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and
consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are offered
by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable
manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained within
the Program.
Each Contributor must identify itself as the originator of its Contribution, if
any, in a manner that reasonably allows subsequent Recipients to identify the
originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a manner
which does not create potential liability for other Contributors. Therefore, if
a Contributor includes the Program in a commercial product offering, such
Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
every other Contributor ("Indemnified Contributor") against any losses, damages
and costs (collectively "Losses") arising from claims, lawsuits and other legal
actions brought by a third party against the Indemnified Contributor to the
extent caused by the acts or omissions of such Commercial Contributor in
connection with its distribution of the Program in a commercial product
offering. The obligations in this section do not apply to any claims or Losses
relating to any actual or alleged intellectual property infringement. In order
to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
Contributor in writing of such claim, and b) allow the Commercial Contributor to
control, and cooperate with the Commercial Contributor in, the defense and any
related settlement negotiations. The Indemnified Contributor may participate in
any such claim at its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If that
Commercial Contributor then makes performance claims, or offers warranties
related to Product X, those performance claims and warranties are such
Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a court
requires any other Contributor to pay any damages as a result, the Commercial
Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
Recipient is solely responsible for determining the appropriateness of using and
distributing the Program and assumes all risks associated with its exercise of
rights under this Agreement , including but not limited to the risks and costs
of program errors, compliance with applicable laws, damage to or loss of data,
programs or equipment, and unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under applicable
law, it shall not affect the validity or enforceability of the remainder of the
terms of this Agreement, and without further action by the parties hereto, such
provision shall be reformed to the minimum extent necessary to make such
provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted under
Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and does
not cure such failure in a reasonable period of time after becoming aware of
such noncompliance. If all Recipient's rights under this Agreement terminate,
Recipient agrees to cease use and distribution of the Program as soon as
reasonably practicable. However, Recipient's obligations under this Agreement
and any licenses granted by Recipient relating to the Program shall continue and
survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to time.
No one other than the Agreement Steward has the right to modify this Agreement.
The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation
may assign the responsibility to serve as the Agreement Steward to a suitable
separate entity. Each new version of the Agreement will be given a
distinguishing version number. The Program (including Contributions) may always
be distributed subject to the version of the Agreement under which it was
received. In addition, after a new version of the Agreement is published,
Contributor may elect to distribute the Program (including its Contributions)
under the new version. Except as expressly stated in Sections 2(a) and 2(b)
above, Recipient receives no rights or licenses to the intellectual property of
any Contributor under this Agreement, whether expressly, by implication,
estoppel or otherwise. All rights in the Program not expressly granted under
this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial in
any resulting litigation.
================================================
FILE: README.md
================================================
Literate Programming Examples
=============================
A literate program combines code and prose (documentation) in one file
format. The term was coined by Donald Knuth in his September 1983
paper, *Literate Programming*,
[available online](http://literateprogramming.com/knuthweb.pdf). Decades
later,
[Knuth asserts that literate programming](http://www.informit.com/articles/article.aspx?p=1193856):
1. Was the most important outcome of creating TeX.
2. Enables faster and more reliable creation of software.
3. Manages complexity better than any other methodology he's aware of.
4. Is a source of joy when programming.
5. Enables extraordinary achievements in creating software.
Can these claims be made by non-Knuth programmers who adopt literate
programming (LP) for themselves? I believe the time is ripe to find
out, as we finally have a tool that is simple (plain text), flexible,
and powerful, with mature LP capabilities:
[Emacs Org mode](http://orgmode.org).
Thus this repository is a collection of literate programming (LP)
examples, using Emacs Org mode. These examples are intended to be
directly usable (copy and start hacking), and/or to serve as
educational literate programs. The long-term goal is to collect and
organize a corpus of useful LP examples that point towards Knuth's
vision of programs-as-literature.
Prerequisites
=============
1. Install a recent version of Emacs, 24.3+.
2. Install both `org-mode` (older version should be included w/ Emacs
24+) and `clojure-mode`. Use Emacs ELPA as needed (it can also
upgrade Emacs packages); you can invoke that from Emacs with
`M-x package-list-packages`
- Consider using an Emacs "starter package" that provides a good
baseline configuration, like
[Emacs Prelude](http://batsov.com/prelude/) or
[Emacs Live](http://overtone.github.io/emacs-live/). Both those
packages choose reasonable/good default configurations, and
support Clojure (other languages too).
3. Update your `.emacs` file to support Org's LP features for Clojure
(and possibly other languages).
- The exact location and name varies, depending on whether you
chose one of the starter packages mentioned; e.g. with Emacs
Prelude you'd have a file like
`~/.emacs.d/personal/yourUsername.el`
- You'll need to add the following somewhere within the `.emacs`;
note that the `emacs-lisp` line below is optional, it's just
meant to show that supporting additional languages is easy:
```elisp
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(clojure . t)))
;; Show syntax highlighting per language native mode in *.org
(setq org-src-fontify-natively t)
;; For languages with significant whitespace like Python:
(setq org-src-preserve-indentation t)
```
The benefits of LP using Emacs + Org
====================================
1. Documentation matters, a lot.
- For starters, do you judge a Github project by its README?
- For all but small throwaway systems, you're likely keeping a
separate file of development notes already; LP would integrate
that.
- No matter how clear the function and data names are, code itself
rarely clarifies larger issues of architecture and design, and
the decision histories therein.
- With literate programming, documentation is integral to
development, never an afterthought.
2. With one LP file, you avoid the incidental/inessential complexity
of the filesystem, by avoiding the frequent context-switching
overhead in moving between files. And you sidestep your language's
imposed filesystem structure.
3. Org rocks for prose:
- Org's plain-text markup is lightweight, yet more powerful than
Markdown (which lacks hierarchical structuring), and cleaner than
rST.
- The structural editing provided by Org documents lets you
organize your thoughts/writing/code very quickly. With good
structure even major revisions are easy.
- Org's exporter lets your write-once, express-many-times: you
can export an Org file to HTML (e.g. for blogging) or LaTeX (for
serious publishing) and PDF.
- It's easy to version-control Org files; it's just plain-text.
4. Org rocks for code:
- Each code block has flexible granularity: can be named and
referred to; evaluated or not; have data sent in or exported;
specify different REPL sessions; specify different target/tangled
files (in arbitrary subdirectories).
- Code blocks are syntax-highlighted.
- Code blocks are ready to edit: jump to major-mode editing easily;
edit/REPL as usual; changes will flow back to the containing Org
file.
- A single Org file can mix multiple languages together.
5. Meta-development, manage complexity from a coherent perspective: a
unified, single-file approach encourages holistic software
development and exposition, in a natural order, using structure to
enhance understanding. LP is not just documentation and code
together: it's a **process and abstraction unifying the development
lifecycle**: requirements, architecture, design, code, tests,
deployment, and maintenance - can all be bound coherently in one
active format.
More information
================
- Emacs: no flamebait here; I will simply say that having Org is
sufficient reason to use the Eternal Editor. Just be sure to
[remap your CapsLock to Control](http://www.emacswiki.org/emacs/MovingTheCtrlKey),
for happy hands.
- [Org documentation](http://orgmode.org/org.html), especially the
section on
[Working with source code](http://orgmode.org/org.html#Working-With-Source-Code)
- The excellent paper by Schulte and Davison,
[Active Documents with Org-Mode](http://www.cs.unm.edu/~eschulte/data/CISE-13-3-SciProg.pdf)
- Pro-tip: when you want to "tangle" or export code blocks from an org
file, =CTRL-c-v-t= is the keystroke combo to tangle all blocks. To
tangle only ONE block, the current one your cursor is in, just use
the Emacs prefix code first: =CTRL-u-c-v-t= For big org files, this
saves time, as it's essentially instantaneous to tangle/export one
block.
gitextract_jmrys2px/ ├── .gitignore ├── 00-just-code-blocks/ │ └── simple-code-blocks.org ├── 01-clojure-literate-ants/ │ └── literate-ants.org ├── 02-minimal-clojure-app/ │ └── clojure-app-skeleton.org ├── 02-minimal-clojure-project/ │ └── clojure-default-skeleton.org ├── 03-pedestal-app/ │ └── pedestal-app-skeleton.org ├── 03-pedestal-service/ │ └── pedestal-service-skeleton.org ├── 05-luminus-site/ │ ├── luminus-site-skeleton.org │ └── mysite/ │ └── resources/ │ └── public/ │ ├── css/ │ │ └── screen.css │ └── md/ │ └── docs.md ├── LICENSE └── README.md
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (201K chars).
[
{
"path": ".gitignore",
"chars": 136,
"preview": "01-clojure-literate-ants/literate-ants.clj\n01-clojure-literate-ants/project.clj\n02-minimal-clojure-app/src/\n02-minimal-c"
},
{
"path": "00-just-code-blocks/simple-code-blocks.org",
"chars": 1112,
"preview": "#+TITLE: Simple Code Blocks - a Literate Programming File\n#+AUTHOR: Kai Wu\n#+EMAIL: k@limist.com\n#+LANGUAGE: en\n#+STARTU"
},
{
"path": "01-clojure-literate-ants/literate-ants.org",
"chars": 50042,
"preview": "#+TITLE: The Clojure Ants Simulation, in Literate Form\n#+AUTHOR: Rich Hickey (this literate/org-babel version, Kai Wu)\n#"
},
{
"path": "02-minimal-clojure-app/clojure-app-skeleton.org",
"chars": 18156,
"preview": "#+TITLE: Clojure App Skeleton, using Org literate programming\n#+AUTHOR: Kai Wu\n#+EMAIL: k@limist.com\n#+LANGUAGE: en\n#+ST"
},
{
"path": "02-minimal-clojure-project/clojure-default-skeleton.org",
"chars": 18546,
"preview": "#+TITLE: Clojure Default/Project Skeleton, Using Org Literate Programming\n#+AUTHOR: Kai Wu\n#+EMAIL: k@limist.com\n#+LANGU"
},
{
"path": "03-pedestal-app/pedestal-app-skeleton.org",
"chars": 55448,
"preview": "#+TITLE: Pedestal App, in Org literate programming form\n#+AUTHOR: Kai Wu\n#+EMAIL: k@limist.com\n#+STARTUP: align overview"
},
{
"path": "03-pedestal-service/pedestal-service-skeleton.org",
"chars": 9154,
"preview": "#+TITLE: Pedestal Service, in Org literate programming form\n#+AUTHOR: Kai Wu\n#+EMAIL: k@limist.com\n#+STARTUP: overview h"
},
{
"path": "05-luminus-site/luminus-site-skeleton.org",
"chars": 23470,
"preview": "#+TITLE: Luminus/Clojure Website Template, in Org/LP Format\n#+AUTHOR: Kai Wu\n#+EMAIL: k@limist.com\n#+LANGUAGE: en\n#+STAR"
},
{
"path": "05-luminus-site/mysite/resources/public/css/screen.css",
"chars": 146,
"preview": "html,\nbody {\n\tfont-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n height: 100%;\n padding-top: 70px;\n}\n\n."
},
{
"path": "05-luminus-site/mysite/resources/public/md/docs.md",
"chars": 684,
"preview": "### Here are some links to get started\n\n1. [HTML templating](http://www.luminusweb.net/docs/html_templating.md)\n2. [Acce"
},
{
"path": "LICENSE",
"chars": 11514,
"preview": "Eclipse Public License - v 1.0\n\nTHE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC\nLICENSE (\"AG"
},
{
"path": "README.md",
"chars": 6217,
"preview": "Literate Programming Examples\n=============================\n\nA literate program combines code and prose (documentation) "
}
]
About this extraction
This page contains the full source code of the limist/literate-programming-examples GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (190.1 KB), approximately 51.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.