packages feed

yst (empty) → 0.1

raw patch · 34 files changed

+2140/−0 lines, 34 filesdep +HStringTemplatedep +HsSyckdep +basesetup-changed

Dependencies added: HStringTemplate, HsSyck, base, containers, csv, directory, filepath, old-locale, old-time, pandoc, parsec, time, utf8-string, xhtml

Files

+ LICENSE view
@@ -0,0 +1,340 @@+            GNU GENERAL PUBLIC LICENSE+               Version 2, June 1991++ Copyright (C) 1989, 1991 Free Software Foundation, Inc.+    51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++                Preamble++  The licenses for most software are designed to take away your+freedom to share and change it.  By contrast, the GNU General Public+License is intended to guarantee your freedom to share and change free+software--to make sure the software is free for all its users.  This+General Public License applies to most of the Free Software+Foundation's software and to any other program whose authors commit to+using it.  (Some other Free Software Foundation software is covered by+the GNU Library General Public License instead.)  You can apply it to+your programs, too.++  When we speak of free software, we are referring to freedom, not+price.  Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+this service if you wish), that you receive source code or can get it+if you want it, that you can change the software or use pieces of it+in new free programs; and that you know you can do these things.++  To protect your rights, we need to make restrictions that forbid+anyone to deny you these rights or to ask you to surrender the rights.+These restrictions translate to certain responsibilities for you if you+distribute copies of the software, or if you modify it.++  For example, if you distribute copies of such a program, whether+gratis or for a fee, you must give the recipients all the rights that+you have.  You must make sure that they, too, receive or can get the+source code.  And you must show them these terms so they know their+rights.++  We protect your rights with two steps: (1) copyright the software, and+(2) offer you this license which gives you legal permission to copy,+distribute and/or modify the software.++  Also, for each author's protection and ours, we want to make certain+that everyone understands that there is no warranty for this free+software.  If the software is modified by someone else and passed on, we+want its recipients to know that what they have is not the original, so+that any problems introduced by others will not reflect on the original+authors' reputations.++  Finally, any free program is threatened constantly by software+patents.  We wish to avoid the danger that redistributors of a free+program will individually obtain patent licenses, in effect making the+program proprietary.  To prevent this, we have made it clear that any+patent must be licensed for everyone's free use or not licensed at all.++  The precise terms and conditions for copying, distribution and+modification follow.++            GNU GENERAL PUBLIC LICENSE+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION++  0. This License applies to any program or other work which contains+a notice placed by the copyright holder saying it may be distributed+under the terms of this General Public License.  The "Program", below,+refers to any such program or work, and a "work based on the Program"+means either the Program or any derivative work under copyright law:+that is to say, a work containing the Program or a portion of it,+either verbatim or with modifications and/or translated into another+language.  (Hereinafter, translation is included without limitation in+the term "modification".)  Each licensee is addressed as "you".++Activities other than copying, distribution and modification are not+covered by this License; they are outside its scope.  The act of+running the Program is not restricted, and the output from the Program+is covered only if its contents constitute a work based on the+Program (independent of having been made by running the Program).+Whether that is true depends on what the Program does.++  1. You may copy and distribute verbatim copies of the Program's+source code as you receive it, in any medium, provided that you+conspicuously and appropriately publish on each copy an appropriate+copyright notice and disclaimer of warranty; keep intact all the+notices that refer to this License and to the absence of any warranty;+and give any other recipients of the Program a copy of this License+along with the Program.++You may charge a fee for the physical act of transferring a copy, and+you may at your option offer warranty protection in exchange for a fee.++  2. You may modify your copy or copies of the Program or any portion+of it, thus forming a work based on the Program, and copy and+distribute such modifications or work under the terms of Section 1+above, provided that you also meet all of these conditions:++    a) You must cause the modified files to carry prominent notices+    stating that you changed the files and the date of any change.++    b) You must cause any work that you distribute or publish, that in+    whole or in part contains or is derived from the Program or any+    part thereof, to be licensed as a whole at no charge to all third+    parties under the terms of this License.++    c) If the modified program normally reads commands interactively+    when run, you must cause it, when started running for such+    interactive use in the most ordinary way, to print or display an+    announcement including an appropriate copyright notice and a+    notice that there is no warranty (or else, saying that you provide+    a warranty) and that users may redistribute the program under+    these conditions, and telling the user how to view a copy of this+    License.  (Exception: if the Program itself is interactive but+    does not normally print such an announcement, your work based on+    the Program is not required to print an announcement.)++These requirements apply to the modified work as a whole.  If+identifiable sections of that work are not derived from the Program,+and can be reasonably considered independent and separate works in+themselves, then this License, and its terms, do not apply to those+sections when you distribute them as separate works.  But when you+distribute the same sections as part of a whole which is a work based+on the Program, the distribution of the whole must be on the terms of+this License, whose permissions for other licensees extend to the+entire whole, and thus to each and every part regardless of who wrote it.++Thus, it is not the intent of this section to claim rights or contest+your rights to work written entirely by you; rather, the intent is to+exercise the right to control the distribution of derivative or+collective works based on the Program.++In addition, mere aggregation of another work not based on the Program+with the Program (or with a work based on the Program) on a volume of+a storage or distribution medium does not bring the other work under+the scope of this License.++  3. You may copy and distribute the Program (or a work based on it,+under Section 2) in object code or executable form under the terms of+Sections 1 and 2 above provided that you also do one of the following:++    a) Accompany it with the complete corresponding machine-readable+    source code, which must be distributed under the terms of Sections+    1 and 2 above on a medium customarily used for software interchange; or,++    b) Accompany it with a written offer, valid for at least three+    years, to give any third party, for a charge no more than your+    cost of physically performing source distribution, a complete+    machine-readable copy of the corresponding source code, to be+    distributed under the terms of Sections 1 and 2 above on a medium+    customarily used for software interchange; or,++    c) Accompany it with the information you received as to the offer+    to distribute corresponding source code.  (This alternative is+    allowed only for noncommercial distribution and only if you+    received the program in object code or executable form with such+    an offer, in accord with Subsection b above.)++The source code for a work means the preferred form of the work for+making modifications to it.  For an executable work, complete source+code means all the source code for all modules it contains, plus any+associated interface definition files, plus the scripts used to+control compilation and installation of the executable.  However, as a+special exception, the source code distributed need not include+anything that is normally distributed (in either source or binary+form) with the major components (compiler, kernel, and so on) of the+operating system on which the executable runs, unless that component+itself accompanies the executable.++If distribution of executable or object code is made by offering+access to copy from a designated place, then offering equivalent+access to copy the source code from the same place counts as+distribution of the source code, even though third parties are not+compelled to copy the source along with the object code.++  4. You may not copy, modify, sublicense, or distribute the Program+except as expressly provided under this License.  Any attempt+otherwise to copy, modify, sublicense or distribute the Program is+void, and will automatically terminate your rights under this License.+However, parties who have received copies, or rights, from you under+this License will not have their licenses terminated so long as such+parties remain in full compliance.++  5. You are not required to accept this License, since you have not+signed it.  However, nothing else grants you permission to modify or+distribute the Program or its derivative works.  These actions are+prohibited by law if you do not accept this License.  Therefore, by+modifying or distributing the Program (or any work based on the+Program), you indicate your acceptance of this License to do so, and+all its terms and conditions for copying, distributing or modifying+the Program or works based on it.++  6. Each time you redistribute the Program (or any work based on the+Program), the recipient automatically receives a license from the+original licensor to copy, distribute or modify the Program subject to+these terms and conditions.  You may not impose any further+restrictions on the recipients' exercise of the rights granted herein.+You are not responsible for enforcing compliance by third parties to+this License.++  7. If, as a consequence of a court judgment or allegation of patent+infringement or for any other reason (not limited to patent issues),+conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License.  If you cannot+distribute so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you+may not distribute the Program at all.  For example, if a patent+license would not permit royalty-free redistribution of the Program by+all those who receive copies directly or indirectly through you, then+the only way you could satisfy both it and this License would be to+refrain entirely from distribution of the Program.++If any portion of this section is held invalid or unenforceable under+any particular circumstance, the balance of the section is intended to+apply and the section as a whole is intended to apply in other+circumstances.++It is not the purpose of this section to induce you to infringe any+patents or other property right claims or to contest validity of any+such claims; this section has the sole purpose of protecting the+integrity of the free software distribution system, which is+implemented by public license practices.  Many people have made+generous contributions to the wide range of software distributed+through that system in reliance on consistent application of that+system; it is up to the author/donor to decide if he or she is willing+to distribute software through any other system and a licensee cannot+impose that choice.++This section is intended to make thoroughly clear what is believed to+be a consequence of the rest of this License.++  8. If the distribution and/or use of the Program is restricted in+certain countries either by patents or by copyrighted interfaces, the+original copyright holder who places the Program under this License+may add an explicit geographical distribution limitation excluding+those countries, so that distribution is permitted only in or among+countries not thus excluded.  In such case, this License incorporates+the limitation as if written in the body of this License.++  9. The Free Software Foundation may publish revised and/or new versions+of the General Public License from time to time.  Such new versions will+be similar in spirit to the present version, but may differ in detail to+address new problems or concerns.++Each version is given a distinguishing version number.  If the Program+specifies a version number of this License which applies to it and "any+later version", you have the option of following the terms and conditions+either of that version or of any later version published by the Free+Software Foundation.  If the Program does not specify a version number of+this License, you may choose any version ever published by the Free Software+Foundation.++  10. If you wish to incorporate parts of the Program into other free+programs whose distribution conditions are different, write to the author+to ask for permission.  For software which is copyrighted by the Free+Software Foundation, write to the Free Software Foundation; we sometimes+make exceptions for this.  Our decision will be guided by the two goals+of preserving the free status of all derivatives of our free software and+of promoting the sharing and reuse of software generally.++                NO WARRANTY++  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,+REPAIR OR CORRECTION.++  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE+POSSIBILITY OF SUCH DAMAGES.++             END OF TERMS AND CONDITIONS++        How to Apply These Terms to Your New Programs++  If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these terms.++  To do so, attach the following notices to the program.  It is safest+to attach them to the start of each source file to most effectively+convey the exclusion of warranty; and each file should have at least+the "copyright" line and a pointer to where the full notice is found.++    <one line to give the program's name and a brief idea of what it does.>+    Copyright (C) <year>  <name of author>++    This program is free software; you can redistribute it and/or modify+    it under the terms of the GNU General Public License as published by+    the Free Software Foundation; either version 2 of the License, or+    (at your option) any later version.++    This program is distributed in the hope that it will be useful,+    but WITHOUT ANY WARRANTY; without even the implied warranty of+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+    GNU General Public License for more details.++    You should have received a copy of the GNU General Public License+    along with this program; if not, write to the Free Software+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA+++Also add information on how to contact you by electronic and paper mail.++If the program is interactive, make it output a short notice like this+when it starts in an interactive mode:++    Gnomovision version 69, Copyright (C) year  name of author+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.+    This is free software, and you are welcome to redistribute it+    under certain conditions; type `show c' for details.++The hypothetical commands `show w' and `show c' should show the appropriate+parts of the General Public License.  Of course, the commands you use may+be called something other than `show w' and `show c'; they could even be+mouse-clicks or menu items--whatever suits your program.++You should also get your employer (if you work as a programmer) or your+school, if any, to sign a "copyright disclaimer" for the program, if+necessary.  Here is a sample; alter the names:++  Yoyodyne, Inc., hereby disclaims all copyright interest in the program+  `Gnomovision' (which makes passes at compilers) written by James Hacker.++  <signature of Ty Coon>, 1 April 1989+  Ty Coon, President of Vice++This General Public License does not permit incorporating your program into+proprietary programs.  If your program is a subroutine library, you may+consider it more useful to permit linking proprietary applications with the+library.  If this is what you want to do, use the GNU Library General+Public License instead of this License.
+ README.markdown view
@@ -0,0 +1,496 @@+`yst` - static websites from YAML and string templates+======================================================++`yst` is a tool for generating a static website by filling [string+template][]s with data taken from [YAML][] or [CSV][] text files. This approach+combines the speed, security, and ease of deployment of a static+website with the flexibility and maintainability of a dynamic site that+separates presentation and data.++Installing yst+--------------++`yst` is written in Haskell. The easiest way to install `yst` is by using+Haskell's [cabal install][] tool. The best way to get this tool is to+install the [Haskell platform][], which includes a complete installation+of the [GHC][] compiler and the `cabal` executable.++Once you have `cabal`, you can install yst with two commands:++    cabal update+    cabal install yst++(Note that by default, `cabal` installs the `yst` executable into+a special directory:  `~\.cabal\bin` on unix systems.  You will need+to make sure that directory is in your system path.)++Getting started+---------------++To get started with `yst`, use the command:++    yst create mysite++This will create a directory `mysite` and populate it with the files+needed for a sample site.  Change to this directory and run `yst` with+no arguments to create the site:++    cd mysite+    yst++The site will be created in the `site` directory.  Open up `site/index.html`+to take a look.++The configuration file `index.yaml` tells `yst` which pages to build,+and from which templates and data files.  Let's take a look, so we can see+what went into `index.html`. The file is a YAML list of YAML hashes+(name/value pairs). The first item is++    - url      : index.html+      title    : Home+      template : index.st+      requires : event.st+      data     :+        recentevents : events.yaml ORDER BY date DESC LIMIT 2++This says:  build the page `index.html` from the string template `index.st`+(and subtemplate `event.st`) and data from `events.yaml`. Sort this data+(a list of events) by date in descending order, and discard all but the+first two items. Put the resulting data in the string template attribute+`recentevents`. Give the page the title "Home."++Now take a look at `events.yaml`, the data source. Again it is a YAML+list of YAML hashes, each item in the list representing one event.+The first is:++    - date: 2009-06-28+      speaker:  Sam Smith+      title: Building a static website++Pretty self-explanatory!  Try adding a new event, then rebuild the+site by typing `yst` and see how it looks.++If you have problems, make sure `events.yaml` is a valid YAML file.+Structure is shown through indentation, so make sure things line+up right.  And occasionally you may need to use quotation marks+around string values---for example, when a title contains a colon.++There's one more ingredient in our recipe---the string templates.+Take a look at `index.st`, the template from which `index.html`+is built:++    # Welcome+    +    Here's our website. Have a look around.+    +    Our last two events:+    +    $recentevents:event()$+    +    For a complete list of events, see the [events](events.html) page.++The first thing to notice is that this is in [markdown][] format (or,+to be precise, markdown with [pandoc][] extensions).  So, for example,+the first line is a level-one header, and there is a hyperlink+to the events page on the last line.++The second thing to notice is the bit between dollar signs.+This is a placeholder for some formatted data.  The rendered+template will include the list `recentevents` (remember, this+was populated from `events.yaml` after some transformations---see+above).  And each element of this list will be formatted by+a subtemplate called `event.st`:++    - $if(it.speaker)$$it.speaker; separator=" and "$, $endif$ *$it.title$*.++Let's break this down.  The whole line will print a bulleted list+item. `it` here refers to the event that is being processed by the+template. So the first part says: if this event has a `speaker` field,+print the speaker, or, if the `speaker` field is a list, print all the+speakers separated by the word "and", followed by a comma. And the+second part says, print the contents of the `title` field, surrounding+it with asterisks (which is the markdown way of making it *emphasized*).++(See the [string template][] documentation for details of template syntax,+and examples.) ++If you look at `index.html`, you'll see this rough structure, but in+an HTML file, not a markdown file. `yst` converts the rendered markdown+template to HTML (using pandoc), and inserts it into a "layout" file+called `layout.html.st`.  If you look at this file, you'll see that it's+an HTML file with blanks for `$contents$` and `$nav$`.  The `$contents$`+blank gets filled by the rendered template, converted to HTML, and+the `$nav$` blank gets filled by an HTML navigation menu (an unordered+list with links).++To summarize our example, then: `yst` sorts and filters the data in+`events.yaml` according to our instructions; inserts this data into+the `events.st` template, formatting each item using the `event.st`+template; uses pandoc to convert the rendered template to HTML;+constructs a navigation menu; and puts the contents and navigation+menu in the layout template `layout.html.st`. The result is our page,+`index.html`.++Reference+---------++### The `yst` command++Synopsis:++    yst                    # rebuilds site, using default config.yaml+    yst -f myconf.yaml     # rebuilds site, using myconf.yaml as config+    yst create newsite     # creates a starter (demo) site in newsite directory++When run without arguments, `yst` looks at `index.yaml` to determine+the dependencies of each page, and rebuilds only the pages whose+dependencies have changed since the last build.++In order for this to work properly, you must be sure to list all+subtemplates included recursively in the main page template using+the `requires` field.  This field takes a single filename or a+YAML list, which may be in either of two formats:++    requires: [event.st, date.st]++or++    requires:+      - event.st+      - date.st++If you don't list all the subtemplates needed to render a page+under `requires`, `yst` will still work, but it might sometimes+fail to rebuild a page when one of these subtemplates has been changed.++### `config.yaml`++The configuration file specifies the following:++- `indexfile`: the filename of the index file (default: `index.yaml`)+- `title`:  the title of the whole site+- `templatedir`:  the directory containing templates (default: `.`)+- `datadir`:  the directory containing yaml data files (default: `.`)+- `filesdir`: the directory containing static files (default: `files`)+- `layout`: the default layout template for the site (default:+  `layout.html.st`)++### `index.yaml` and submenus++The index file is a YAML list of pages. Each page may have the following+fields:++- `url`:   the relative URL of the page to be built+- `title`:   the title of the page+- `template`:   the main string template from which the page will be built+- `source`:   the markdown source from which the page will be built+- `requires`:   other files changes to which should trigger a page rebuild+  (primarily subtemplates of the main page template)+- `data`:   string template attributes, data sources, and transformations+  (see below)+- `layout`:   a layout template to use, if other than the site default+- `inmenu`:   if 'no', the page will not be included in the site navigation+   menu++Each page must have at least `url`, `title`, and either `template` or+`source`.++The pages may be organized into a tree-like hierarchy, which will be+reflected in the site navigation menu.  It is easiest to see how this+works by example:++    - Rooms:+      - url      : room101.html+        title    : Room 101+        source   : room101.txt+    +      - url      : room203.html+        title    : Room 203+        source   : room203.txt++Here we have a subtree called "Rooms" with two pages under it.+Subtrees can contain other subtrees.  Just be consistent about indentation.++### The `data` field++The `data` field in `index.yaml` can populate any number of stringtemplate+attributes with data from YAML or CSV files.  The syntax is easiest to+explain by example (note that the keywords do not have to be in ALL CAPS,+although they may, and the query doesn't have to end with a semicolon,+though it may):++    data:+        events:  events.yaml order by date desc group by title then location+        people:  people.csv order by birthday then lastname where+                  birthstate = 'CA' limit 5++First we have the name of the stringtemplate attribute to be populated+(say, `events`).  Then, after the colon, we have the data source+(`events.yaml`) followed by one or more *transformations*, which will+be applied in order.  Here are the possible transformations.  In+what follows, brackets denote an optional component, `|` denotes+alternatives, and `*` indicates that the component may be repeated+several times:++`ORDER BY field [ASC|DESC] [THEN field [ASC|DESC]]*`++>   Sorts a list by comparing the value of `field`.  `ASC`+    (the default) means "ascending", and `DESC` means "descending".+    The keyword `THEN` is used to separate fields that will be+    compared in order.  So, if we are ordering by `birthday then lastname`,+    we will compare birthdays, and if these are equal, we will break+    the tie by comparing last names. ++`GROUP BY field [THEN field]*`++>   Converts a list into a list of lists, where each sublist contains+    only items with the same value for `field`.  So, for example,+    `group by date` takes a list of events and produces a list of+    lists of items, where each sublist contains events occuring at+    a single date.  `GROUP BY date THEN venue` would produce a list+    of lists of lists, and so on.++`LIMIT n`++>   Removes all but the *n* top items from a list.  *n* must be a number.++`WHERE condition`++>   Selects only items that meet a condition.++>   A *condition* in a `WHERE` statement is a Boolean combination (using+    `NOT`, `AND`, `OR`, and parentheses for disambiguation) of *basic+    conditions*.  A *basic condition* is of the form `value op value`,+    where `value` may be either a fieldname or a constant.  Note that+    all constants must be enclosed in quotes.  `op` may be one of the+    following:  `=`, `>=`, `<=`, `>`, `<`.++Note that the order of transformations is significant.  You can get+different results if you use `LIMIT` before or after `ORDER BY`,+for example.++### Static files++Any file or subdirectory in the `files` directory (or whatever is+the value of `filesdir` in `config.yaml`) will be copied verbatim to+the site.  So this is the place to put javascripts, css files, images,+PDFs, and the like.++### Date fields++`yst` will recognize date fields in data files automatically, if the+dates are in one of the following formats:++    - the locale's standard date format+    - MM/DD/YYYY (e.g. 04/28/1953)+    - MM/DD/YY (e.g. 04/28/53)+    - YYYY-MM-DD (e.g. 1953-04-28)+    - DD MON YYYY (e.g. 28 Apr 1953)++Dates may be formatted in templates using a stringtemplate "format"+directive. There's an example in the demo file `date.st`:++    $it; format="%B %d, %Y"$++The following codes may be used in format strings (taken from+Haskell's `Date.Time.Format` documentation):++- `%D` :   same as `%m/%d/%y`+- `%F` :   same as `%Y-%m-%d`+- `%x` :   as dateFmt locale (e.g. `%m/%d/%y`)+- `%Y` :   year+- `%y` :   last two digits of year, 00 - 99+- `%C` :   century (being the first two digits of the year), 00 - 99+- `%B` :   month name, long form (fst from months locale), January - December+- `%b, %h` :   month name, short form (snd from months locale), Jan - Dec+- `%m` :   month of year, leading 0 as needed, 01 - 12+- `%d` :   day of month, leading 0 as needed, 01 - 31+- `%e` :   day of month, leading space as needed, 1 - 31+- `%j` :   day of year for Ordinal Date format, 001 - 366+- `%G` :   year for Week Date format+- `%g` :   last two digits of year for Week Date format, 00 - 99+- `%f` :   century (first two digits of year) for Week Date format, 00 - 99+- `%V` :   week for Week Date format, 01 - 53+- `%u` :   day for Week Date format, 1 - 7+- `%a` :   day of week, short form (snd from wDays locale), Sun - Sat+- `%A` :   day of week, long form (fst from wDays locale), Sunday - Saturday+- `%U` :   week number of year, where weeks start on Sunday (as sundayStartWeek), 00 - 53+- `%w` :   day of week number, 0 (= Sunday) - 6 (= Saturday)+- `%W` :   week number of year, where weeks start on Monday (as mondayStartWeek), 00 - 53++### Lists as values++In some cases, a field may have one or several values.  For example, an+event might occur at a date or a date range, and an article may have one+author or a list of authors.++An elegant way to handle these cases is to let the field take either a+scalar or a list value, and use stringtemplate's "separator" directive+to format the result appropriately.  So, for example, in our `events.yaml`+we have:++    - date: 2009-06-28+      speaker:  Sam Smith+      title: Building a static website+    +    - date:  2009-04-15+      speaker:+        - Sam Smith+        - '[Jim Jones](http://foo.bar/baz)'+      title: Advantages of static websites+    +    - date:+        - 2009-04-20+        - 2009-04-22+      title: Seminar on web security+    +    - date: 2009-04-15+      speaker: Jim Jones+      title:  XSS attacks for dummies++Note that the `date` field is sometimes a single date, sometimes a+list (with start and end date of a range), and the `speaker` field is+sometimes a single speaker, and sometimes a range.++Here is how we handle the date in `eventgroup.st`:++    **$first(it).date:date(); separator=" - "$**++Here `first(it).date` is the raw data, which may be a single date+or a list.  `first(it).date:date()` is the result of formatting each+date using the `date.st` template (discussed above).  And+`first(it).date:date; separator=" - "` is the result of taking this+list of formatted dates and concatenating them, separated by a hyphen.+When there is just one date, we just get a date. When there are two,+we get a date range.++We can use the same trick in the case of `speaker`.  If `it` is+an event record, then `it.speaker; separator=" and "` will be+either a single speaker (if the value is not a list) or a list+of speakers separated by "and" (if it is a list).++In sorting lists with `order by`, `yst` compares two lists by+comparing the first members, then (in case of a tie) the second+members, and so on.  If one item is a list and the other a scalar,+the scalar is compared to the first item of the list.  So, in+the example above, `Seminar on web security` will be sorted+an earlier than an event with date `2009-04-21`, and later than+an event with date range `2009-04-20 - 2009-04-21`.++### YAML gotchas++If you have a colon in a YAML value, be sure to enclose it in quotes,+or you'll get an error.  So,++    title:  "Cheever: A Life"++not++    title:  Cheever: A Life++Or (especially if the string is long), use `>` or `|` for a wrapped+or unwrapped multiline string literal:++    title: |+      A very long string that+      goes on and on.++      You can even have blank lines,+      but be sure to maintain indentation.++### Using CSV files instead of YAML++If you like, you can use a CSV file instead of YAML for your data source.+Just give it the extension `.csv`.  In `index.yaml`, you'd have:++    data:+      events:  events.csv order by date desc++This can be handy if you're using existing data, because spreadsheets+and databases can easily be dumped to CSV. In the case of a SQL+database, you can use a query like this to get the CSV:++    SELECT * INTO OUTFILE 'result.csv'+    FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'+    LINES TERMINATED BY '\n'+    FROM my_table;++(Thanks to+<http://www.terminally-incoherent.com/blog/2006/07/20/dump-mysql-table-into-csv-file/>.)++### Using HTML in the templates++Markdown allows raw HTML to be used, so you can embed HTML in templates.+Pandoc's extended markdown is different from standard markdown in that+it parses text within HTML block elements as markdown.  So, for example,+you can include a section in `<div>` tags, or use raw `<ul>` and `<li>`+tags instead of markdown formatting.++### Stringtemplate gotchas++Sometimes you want to create a span-level template---something like+`date.st`, which just formats a date:++    $it; format="%B %d, %Y"$++If you create a file containing this line with a regular text editor,+the file will end with a newline character.  This will be treated+as a newline in the template, so you'll get a new line after every+date in a list of dates: not what you want.++The solution is to set up your text editor so that it doesn't+add the newline at the end of the file.  Using [vim][], you can do this+with the commands `set binary` and `set noeol`.  To make this the+default for `.st` files, add this line to `augroup filetypedetect`+in `~\.vim\filetype.vim`:++    au! BufRead,BufNewFile *.st setfiletype stringtemplate++Then add a file `stringtemplate.st` in `~/.vim/ftplugin` with+the following contents:++    set binary+    set noeol++### Layout templates++Layout files are also string templates, but they are not treated as+markdown by default.  They should use a double extension to indicate+the format.  So, for example, an HTML layout could be `standard.html.st`,+and a LaTeX layout could be `printed.tex.st`.  `yst` will convert the+page contents appropriately for the format of the layout template.+Here are the supported formats and extensions:++- HTML:   `.html.st`, `.xhtml.st`+- LaTeX:   `.tex.st`, `.latex.st`+- ConTeXt:   `.context.st`+- Groff man:   `.`1`.st`+- Rich text format:   `.rtf.st`+- Texinfo:   `.texi.st`+- DocBook:   `.db.st`+- OpenDocument XML:   `.fodt.st`+- Plain text (markdown):   `.txt.st`, `.markdown.st`++The demo site shows how you can use `yst` to produce a LaTeX document+from the same data sources you use to produce HTML pages.++The following stringtemplate attributes are defined when layouts+are rendered:++- `$contents$`:   the result of rendering the page and converting to the layout's format+- `$nav$`:   an HTML navigation menu created from `index.yaml`+- `$gendate$`:   the date the page was generated+- `$sitetitle$`:   the site title from `config.yaml`+- `$pagetitle$`:   the page title as defined in `index.yaml`+++[string template]: http://www.stringtemplate.org/+[YAML]: http://www.yaml.org/+[CSV]: http://en.wikipedia.org/wiki/Comma-separated_values+[cabal install]: http://hackage.haskell.org/trac/hackage/wiki/CabalInstall+[Haskell platform]: http://hackage.haskell.org/platform/+[markdown]: http://daringfireball.com/markdown+[pandoc]: http://johnmacfarlane.net/pandoc/+[vim]: http://www.vim.org/++
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ Yst/Build.hs view
@@ -0,0 +1,94 @@+{-+Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+-}++module Yst.Build (buildSite)+where+import Yst.Types+import Yst.Util+import Yst.Render+import qualified Data.Map as M+import Data.Maybe (fromMaybe)+import Data.List+import System.FilePath+import System.Directory+import System.Exit+import System.Time (ClockTime(..))+import System.IO.UTF8+import System.IO (stderr)+import Prelude hiding (readFile, putStrLn, print, writeFile)+import Control.Monad++dependencies :: Site -> String -> [FilePath]+dependencies site url =+  let page = case M.lookup url (pageIndex site) of+                  Nothing   -> error $ "Tried to get dependencies for nonexistent page: " ++ url+                  Just pg   -> pg+      layout = sourceDir site </> stripStExt (fromMaybe (defaultLayout site) $ layoutFile page) <.> "st"+      requires = map (sourceDir site </>) $ requiresFiles page+      srcdir = sourceDir site </>+                 case sourceFile page of+                       TemplateFile f -> stripStExt f <.> "st"+                       SourceFile f   -> f+      dataFiles = map (\(_,(f,_)) -> dataDir site </> f) $ pageData page+  in  indexFile site : layout : srcdir : (requires ++ dataFiles)++buildSite :: Site -> IO ()+buildSite site = do+  files <- liftM (filter (/=".") . map (makeRelative $ filesDir site)) $ getDirectoryContentsRecursive $ filesDir site+  let pages = M.keys $ pageIndex site+  let overlap = files `intersect` pages+  unless (null overlap) $ forM_ overlap+    (\f -> hPutStrLn stderr $ "Warning: the page '" ++ f ++ "' will overwrite the file by the same name.")+  forM_ files $ \file ->+     updateFile site file+  forM_ pages $ \page ->+     case M.lookup page (pageIndex site) of+          Just  pg -> updatePage site pg+          Nothing  -> error $ "Couldn't find page " ++ page++updateFile :: Site -> FilePath -> IO ()+updateFile site file = do+  let destpath = deployDir site </> file+  let srcpath = filesDir site </> file+  srcmod <- getModificationTime srcpath+  destmod <- catch (getModificationTime destpath) (\_ -> return $ TOD 0 0)+  if srcmod > destmod+     then do+       createDirectoryIfMissing True $ takeDirectory destpath+       hPutStrLn stderr $ "Updating " ++ destpath+       copyFile srcpath destpath+     else return ()++updatePage :: Site -> Page -> IO ()+updatePage site page = do+  let destpath = deployDir site </> pageUrl page+  let deps = dependencies site $ pageUrl page+  forM_ deps $ \dep -> do+    exists <- doesFileExist dep+    unless exists $ do+      hPutStrLn stderr $ "Missing dependency: " ++ dep+      hPutStrLn stderr $ "Aborting!  Cannot build " ++ destpath+      exitWith $ ExitFailure 3+  depsmod <- mapM getModificationTime deps+  destmod <- catch (getModificationTime destpath) (\_ -> return $ TOD 0 0)+  if maximum depsmod > destmod+     then do+       createDirectoryIfMissing True $ takeDirectory destpath+       hPutStrLn stderr $ "Updating " ++ destpath+       renderPage site page >>= writeFile destpath+     else return ()
+ Yst/CSV.hs view
@@ -0,0 +1,45 @@+{-+Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+-}++module Yst.CSV (readCSVFile)+where+import Yst.Types+import Yst.Util+import Text.CSV+import Prelude hiding (readFile)+import System.IO.UTF8++readCSVFile :: FilePath -> IO Node+readCSVFile f = catch (readFile f >>= return . csvToNode . parseCSV' f . stripBlanks)+                   (\e -> errorExit 11 ("Error parsing " ++ f ++ ": " ++ show e) >> return NNil)++parseCSV' :: FilePath -> String -> CSV+parseCSV' f s = case parseCSV f s of+                     Left e    -> error $ "Error parsing " ++ f ++ ": " ++ show e+                     Right c   -> c++csvToNode :: CSV -> Node+csvToNode [] = NNil+csvToNode (fieldNames : records) =+  NList $ map (\record -> NMap $ zip fieldNames $ map fieldToNode record) records ++fieldToNode :: Field -> Node+fieldToNode s =+  case parseAsDate s of+       Nothing -> NString s+       Just d  -> NDate d
+ Yst/Config.hs view
@@ -0,0 +1,95 @@+{-+Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+-}++module Yst.Config (parseConfigFile, parseIndexFile) where+import System.FilePath+import Yst.Types+import Yst.Util+import Yst.Yaml+import Yst.Data+import Data.Char+import qualified Data.Map as M++parseConfigFile :: FilePath -> IO Site+parseConfigFile configfile = do+  node <- readYamlFile configfile+  case node of+       NMap xs -> do+         let indexfile = getStrAttrWithDefault "indexfile" "index.yaml" xs+         (ind, nav) <- parseIndexFile indexfile+         return Site{ siteTitle     = getStrAttrWithDefault "title" "" xs+                    , sourceDir     = getStrAttrWithDefault "sourcedir" "." xs+                    , dataDir       = getStrAttrWithDefault "datadir" "." xs+                    , filesDir      = getStrAttrWithDefault "filesdir" "files" xs+                    , deployDir     = getStrAttrWithDefault "deploydir" "site" xs+                    , defaultLayout = getStrAttrWithDefault "layout" "layout" xs+                    , indexFile     = indexfile+                    , pageIndex     = M.fromList $ map (\pg -> (pageUrl pg, pg)) ind+                    , navigation    = nav+                    }+       _       -> errorExit 7 "Configuration file must be a YAML hash." >> return undefined++parseIndexFile :: FilePath -> IO ([Page], [NavNode])+parseIndexFile indexfile = do+  node <- readYamlFile indexfile+  case node of+        NList xs -> return $ foldr processItem ([], []) xs+        _        -> errorExit 9 "Index file must be a YAML list." >> return undefined++processItem :: Node -> ([Page], [NavNode]) -> ([Page], [NavNode])+processItem (NMap [(s, NList xs)]) (index, nav) = -- a submenu+  (pages ++ index, (NavMenu s navnodes) : nav)+    where (pages, navnodes) = foldr processItem ([], []) xs+processItem (NMap xs) (index, nav) =  -- a page+  (page : index, newnav)+    where page = processPage xs+          newnav = if pageInMenu page+                      then NavPage (pageTitle page) (pageUrl page) : nav+                      else nav+processItem _ _ = error "processItem encountered a non-NMap node."++processPage :: [(String, Node)] -> Page+processPage xs =+  Page { pageData     = case lookup "data" xs of+                             Nothing        -> []+                             Just (NMap ds) -> map (\(k,v) -> (k, parseDataField v)) ds+                             Just _         -> error "data must be a YAML map"+       , layoutFile   = fmap fromNString $ lookup "layout" xs+       , sourceFile   = case (lookup "source" xs, lookup "template" xs) of+                             (Nothing, Nothing)  -> error "No 'source' or 'template' found for page."+                             (Nothing, Just f)   -> TemplateFile $ fromNString f+                             (Just f, Nothing)   -> SourceFile $ fromNString f+                             (Just _, Just _)    -> error "Both 'source' and 'template' specified for the same page."+       , requiresFiles = case lookup "requires" xs of+                              Nothing          -> []+                              Just (NList fs)  -> map getStrAttr fs+                              Just (NString f) -> [f]+                              Just _           -> error "'requires' must be scalar or list"+       , pageUrl      = url+       , pageTitle    = getStrAttrWithDefault "title" (dropExtension url) xs+       , pageInMenu   = (map toLower $ getStrAttrWithDefault "inmenu" "yes" xs) `notElem`+                          ["no","false"]+       }+    where getPageField f = case (fmap fromNString $ lookup f xs) of+                             Just s  -> s+                             Nothing -> error $ "Missing required " ++ f ++ " field in page definition"+          url = getPageField "url"+          getStrAttr (NString s) = s+          getStrAttr x           = error $ "expected string, got " ++ show x++
+ Yst/Data.hs view
@@ -0,0 +1,255 @@+{-# LANGUAGE FlexibleContexts #-}+{-+Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+-}++module Yst.Data (getData, parseDataField)+where+import Yst.Types+import Yst.Util+import Yst.Yaml+import Yst.CSV+import Control.Monad+import Data.Char+import Data.Maybe (fromMaybe)+import Data.List (sortBy, nub)+import Text.ParserCombinators.Parsec+import System.FilePath (takeExtension)++getData :: (FilePath, [DataOption]) -> IO Node+getData (file, opts) = do+  raw <- catch (readDataFile file)+          (\e -> errorExit 15 ("Error reading data from " ++ file ++ ": " ++ show e) >> return undefined)+  return $ foldl applyDataOption raw opts++readDataFile :: FilePath -> IO Node+readDataFile f =+  case (map toLower $ takeExtension f) of+       ".yaml"      -> readYamlFile f+       ".csv"       -> readCSVFile f+       _            -> readYamlFile f++applyDataOption :: Node -> DataOption -> Node+applyDataOption (NList ns) (Limit lim) =+  NList $ take lim ns+applyDataOption (NList ns) (Where cond) =+  NList $ filter (satisfiesCond cond) ns+applyDataOption (NList ns) (GroupBy []) = NList ns+applyDataOption (NList ns) (GroupBy (k:ks)) =+  NList sorted+    where sorted             = map (\x -> applyDataOption (NList $ filter (\n -> getAttr k n == x) ns) (GroupBy ks)) keys+          keys               = nub $ map (getAttr k) ns+          getAttr x (NMap m) = lookup x m+          getAttr _ _        = error "Can't get an attribute from a non-map"+applyDataOption (NList ns) (OrderBy xs) =+  NList $ sortBy (compareNodeAt xs) ns+applyDataOption _ _ = error "order by and group by can be used only on lists"++satisfiesCond :: FilterCond -> Node -> Bool+satisfiesCond (And c1 c2) n = satisfiesCond c1 n && satisfiesCond c2 n+satisfiesCond (Or  c1 c2) n = satisfiesCond c1 n || satisfiesCond c2 n+satisfiesCond (Not c1)    n = not (satisfiesCond c1 n)+satisfiesCond (Filter test arg1 arg2) n =+  (filterTestPred test) (filterArgToNode arg1 n) (filterArgToNode arg2 n)++filterTestPred :: FilterTest -> (Node -> Node -> Bool)+filterTestPred TestEq    = (==)+filterTestPred TestGt    = (>)+filterTestPred TestLt    = (<)+filterTestPred TestGtEq  = (>=)+filterTestPred TestLtEq  = (<=)++filterArgToNode :: FilterArg -> Node -> Node+filterArgToNode (AttrValue attr) (NMap ns) = fromMaybe NNil (lookup attr ns)+filterArgToNode (AttrValue _) x = error $ "Cannot lookup attribute in non-map node: " ++ show x+filterArgToNode (StringConstant s) _ = NString s+filterArgToNode (DateConstant d) _ = NDate d++compareNodeAt :: [(String,SortDirection)] -> Node -> Node -> Ordering+compareNodeAt ((a,dir'):as) (NMap xs) (NMap ys) = reverseIfDescending dir' $+  case ((lookup a xs), (lookup a ys)) of+       (Just x, Just y)   -> case compare x y of+                                   EQ -> compareNodeAt as (NMap xs) (NMap ys)+                                   z  -> z+       (Just _, Nothing)  -> GT+       (Nothing, Just _)  -> LT+       (Nothing, Nothing) -> EQ+compareNodeAt [] (NMap _) (NMap _) = EQ+compareNodeAt _ _ _ = error "sortby and groupby can be used only on lists of maps"++reverseIfDescending :: SortDirection -> Ordering -> Ordering+reverseIfDescending Ascending o = o+reverseIfDescending Descending EQ = EQ+reverseIfDescending Descending LT = GT+reverseIfDescending Descending GT = LT++parseDataField :: Node -> (FilePath, [DataOption])+parseDataField (NString s) = case parse pDataField s s of+  Right res   -> res+  Left err    -> error $ show err+parseDataField x = error $ "Expected string node, got " ++ show x++pDataField :: GenParser Char st ([Char], [DataOption])+pDataField = do+  fname <- pIdentifier <?> "name of YAML or CSV file"+  opts <- many $ (pOptWhere <?> "where [CONDITION]")+              <|> (pOptLimit <?> "limit [NUMBER]")+              <|> (pOptOrderBy <?> "order by [CONDITION]")+              <|> (pOptGroupBy <?> "group by [CONDITION]")+  spaces+  optional $ char ';'+  spaces+  eof+  return (fname, opts)++pIdentifier :: GenParser Char st [Char]+pIdentifier = spaces >> (pQuoted '\'' <|> pQuoted '"' <|> many (noneOf " \t\n;,'\""))++-- | Case-insensitive string parser.+pString :: String -> GenParser Char st String+pString s = do+  s' <- count (length s) anyChar+  if map toLower s == map toLower s'+     then return s+     else mzero++pQuoted :: Char -> GenParser Char st [Char]+pQuoted delim = try $ do+  char delim+  res <- many (noneOf [delim] <|> (try $ char '\\' >> char delim))+  char delim+  return res++pOptLimit :: GenParser Char st DataOption+pOptLimit = try $ do+  optional $ oneOf ",;"+  spaces+  pString "limit"+  pSpace+  lim <- many1 digit+  return $ Limit $ read lim++pOptOrderBy :: GenParser Char st DataOption+pOptOrderBy = try $ do+  optional $ oneOf ",;"+  spaces+  pString "order"+  pSpace+  pString "by"+  pSpace+  keys <- spaces >> sepBy1 pSortKey (try $ pSpace >> spaces >> pString "then" >> pSpace)+  return $ OrderBy keys++pSortKey :: GenParser Char st ([Char], SortDirection)+pSortKey = do+  res <- pIdentifier+  dir' <- option Ascending pAscDesc+  return (res, dir')++pAscDesc :: GenParser Char st SortDirection+pAscDesc = (try $ pSpace >> pString "desc" >> return Descending)+       <|> (try $ pSpace >> pString "asc" >> return Ascending)++pOptGroupBy :: GenParser Char st DataOption+pOptGroupBy = try $ do+  optional $ oneOf ",;"+  spaces+  pString "group"+  pSpace +  pString "by"+  pSpace+  keys <- spaces >> sepBy1 pIdentifier (try $ pSpace >> spaces >> pString "then" >> pSpace)+  return $ GroupBy keys++pOptWhere :: GenParser Char st DataOption+pOptWhere = try $ do+  optional $ oneOf ",;"+  spaces+  pString "where"+  pSpace+  cond <- pBooleanCondition+  return $ Where cond++pBooleanCondition :: GenParser Char st FilterCond+pBooleanCondition = spaces >> (pNot <|> pAnd <|> pOr <|> pInParens pBooleanCondition <|> pBasicCond)++pInParens :: GenParser Char st a -> GenParser Char st a+pInParens innerParser = try $ do+  char '('+  spaces+  res <- innerParser+  spaces+  char ')'+  return res ++pNot :: GenParser Char st FilterCond+pNot = try $ pString "not" >> pSpace >> liftM Not pBooleanCondition++pAnd :: GenParser Char st FilterCond+pAnd = try $ do+  x <- pNot <|> pInParens pBooleanCondition <|> pBasicCond+  pSpace+  pString "and"+  pSpace+  y <- pBooleanCondition+  return $ And x y++pOr :: GenParser Char st FilterCond+pOr = try $ do+  x <- pNot <|> pAnd <|> pInParens pBooleanCondition <|> pBasicCond+  pSpace+  pString "or"+  pSpace+  y <- pBooleanCondition+  return $ Or x y++pBasicCond :: GenParser Char st FilterCond+pBasicCond = try $ do+  x <- pFilterArg+  t <- pFilterTest+  y <- pFilterArg+  return $ Filter t x y++pFilterArg :: GenParser Char st FilterArg+pFilterArg = spaces >> (pStringOrDateConstant <|> pAttrValue)++pStringOrDateConstant :: GenParser Char st FilterArg+pStringOrDateConstant = do+  str <- pQuoted '"' <|> pQuoted '\''+  case parseAsDate str of+       Just d    -> return $ DateConstant d+       Nothing   -> return $ StringConstant str++pAttrValue :: GenParser Char st FilterArg+pAttrValue = liftM AttrValue pIdentifier++pFilterTest :: GenParser Char st FilterTest+pFilterTest = do+  spaces+  choice $ map (\(s,t) -> try $ string s >> return t)+         [ ("==",TestEq)+         , ("=",TestEq)+         , (">=",TestGtEq)+         , ("<=",TestLtEq)+         , (">",TestGt)+         , ("<",TestLt)+         ]++pSpace :: GenParser Char st ()+pSpace = oneOf " \t\n" >> spaces++
+ Yst/Render.hs view
@@ -0,0 +1,121 @@+{-+Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+-}++module Yst.Render (renderPage)+where+import Yst.Types+import Yst.Util+import Yst.Data+import System.Directory+import Text.Pandoc+import Text.XHtml hiding (option, (</>))+import Data.Char+import Data.List (intercalate)+import Text.StringTemplate+import Data.Maybe (fromMaybe)+import System.FilePath+import System.IO.UTF8+import Prelude hiding (readFile, putStrLn, print, writeFile)+import Data.Time+import Control.Monad++renderNav :: String -> [NavNode] -> String+renderNav targeturl nodes = renderHtmlFragment $+  ulist ! [theclass "nav"] << map (renderNavNode targeturl) nodes++renderNavNode :: String -> NavNode -> Html+renderNavNode targeturl (NavPage tit pageurl) =+  li ! attrs << hotlink pageurl << tit+    where attrs = if pageurl == targeturl+                     then [theclass "current"]+                     else []+renderNavNode targeturl (NavMenu tit nodes) =+  li ! attrs << [ toHtml $ hotlink "#" << (tit ++ " »")+                , ulist ! attrs << map (renderNavNode targeturl) nodes ]+    where active = targeturl `isInNavNodes` nodes+          attrs = if active then [theclass "active"] else []+          isInNavNodes u = any (isInNavNode u)+          isInNavNode u (NavPage _ u') = u == u'+          isInNavNode u (NavMenu _ ns) = u `isInNavNodes` ns++formatFromExtension :: FilePath -> Format+formatFromExtension f = case (map toLower $ takeExtension f) of+                             ".html"  -> HtmlFormat+                             ".xhtml" -> HtmlFormat+                             ".latex" -> LaTeXFormat+                             ".tex"   -> LaTeXFormat+                             ".context" -> ConTeXtFormat+                             ".1"     -> ManFormat+                             ".rtf"   -> RTFFormat+                             ".texi"  -> TexinfoFormat+                             ".db"    -> DocBookFormat+                             ".fodt"  -> OpenDocumentFormat+                             ".txt"   -> PlainFormat+                             ".markdown" -> PlainFormat+                             _       -> HtmlFormat+renderPage :: Site -> Page -> IO String+renderPage site page = do+  let menuHtml = renderNav (pageUrl page) (navigation site)+  let layout = fromMaybe (defaultLayout site) $ layoutFile page+  srcDir <- canonicalizePath $ sourceDir site+  g <- directoryGroup srcDir+  attrs <- forM (pageData page) $ \(k, v) -> getData v >>= \n -> return (k,n)+  todaysDate <- liftM utctDay getCurrentTime+  rawContents <-+    case sourceFile page of+          SourceFile sf   -> readFile (srcDir </> sf)+          TemplateFile tf -> do+            templ <- getTemplate tf g+            return $ render (setManyAttrib attrs templ)+  layoutTempl <- getTemplate layout g+  let format = formatFromExtension (stripStExt layout)+  let contents = converterForFormat format rawContents+  return $ render+         . setAttribute "sitetitle" (siteTitle site)+         . setAttribute "pagetitle" (pageTitle page)+         . setAttribute "gendate" todaysDate +         . setAttribute "contents" contents+         . setAttribute "nav" menuHtml+         $ layoutTempl++converterForFormat :: Format -> String -> String+converterForFormat f =+  let reader = readMarkdown defaultParserState{stateSmart = True}+  in  case f of+       HtmlFormat          -> writeHtmlString defaultWriterOptions . reader+       LaTeXFormat         -> writeLaTeX defaultWriterOptions . reader+       PlainFormat         -> id+       ConTeXtFormat       -> writeConTeXt defaultWriterOptions . reader+       ManFormat           -> writeMan defaultWriterOptions . reader+       RTFFormat           -> writeRTF defaultWriterOptions . reader+       DocBookFormat       -> writeDocbook defaultWriterOptions . reader+       TexinfoFormat       -> writeTexinfo defaultWriterOptions . reader+       OpenDocumentFormat  -> writeOpenDocument defaultWriterOptions . reader++getTemplate :: Stringable a => String -> STGroup a -> IO (StringTemplate a)+getTemplate templateName templateGroup = do+  let template = case getStringTemplate (stripStExt templateName) templateGroup of+                       Just pt  -> pt+                       Nothing  -> error $ "Could not load template: " ++ templateName+  case checkTemplate template of+       (Just parseErrors, _, _ )       -> errorExit 17 $ "Error in template '" ++ templateName +++                                             "': " ++ parseErrors+       (_, _, Just templatesNotFound)  -> errorExit 21 $ "Templates referenced in template '" ++ templateName +++                                             "' not found: " ++ (intercalate ", " templatesNotFound)+       (_, _, _)                       -> return ()+  return template
+ Yst/Types.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}+{-+Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+-}++module Yst.Types+where+import System.FilePath+import Data.Char+import Data.Time+import Text.StringTemplate+import qualified Data.Map as M++data Site = Site {+    siteTitle     :: String+  , sourceDir     :: FilePath+  , dataDir       :: FilePath+  , filesDir      :: FilePath+  , deployDir     :: FilePath+  , defaultLayout :: FilePath+  , indexFile     :: FilePath+  , pageIndex     :: M.Map String Page+  , navigation    :: [NavNode]+  } deriving (Show, Read, Eq)++data Source = TemplateFile FilePath+            | SourceFile FilePath+            deriving (Show, Read, Eq)++data Page = Page {+    pageData      :: [(String, (FilePath, [DataOption]))]+  , layoutFile    :: Maybe FilePath+  , sourceFile    :: Source+  , requiresFiles :: [FilePath]+  , pageUrl       :: String+  , pageTitle     :: String+  , pageInMenu    :: Bool+  } deriving (Show, Read, Eq)++data NavNode = NavPage String String+             | NavMenu String [NavNode]+             deriving (Show, Read, Eq)++data Node = NString String+          | NDate Day+          | NList [Node]+          | NMap [(String, Node)]+          | NNil+          deriving (Show, Read, Eq)++instance Ord Node where+  compare (NString x) (NString y)   = compare x y+  compare (NDate x) (NDate y)       = compare x y+  compare (NList x) (NList y)       = compare x y+  compare (NMap x) (NMap y)         = compare x y+  compare NNil NNil                 = EQ+  compare (NList x) y               = compare (NList x) (NList [y])+  compare x (NList y)               = compare (NList [x]) (NList y)+  compare (NString _) _             = GT+  compare (NDate _) _               = GT+  compare _ _                       = GT++data DataOption = OrderBy [(String, SortDirection)]+                | GroupBy [String]+                | Where FilterCond+                | Limit Int +                deriving (Show, Read, Eq)++data FilterCond = Filter FilterTest FilterArg FilterArg+                | And FilterCond FilterCond+                | Or  FilterCond FilterCond+                | Not FilterCond+                deriving (Show, Read, Eq)++data FilterArg = AttrValue String+               | StringConstant String+               | DateConstant Day+               deriving (Show, Read, Eq) ++data FilterTest = TestEq+                | TestGt+                | TestLt+                | TestGtEq+                | TestLtEq+                deriving (Show, Read, Eq)++data SortDirection = Ascending | Descending deriving (Show, Read, Eq)++data Format = HtmlFormat+            | LaTeXFormat+            | ConTeXtFormat+            | PlainFormat+            | ManFormat+            | RTFFormat+            | TexinfoFormat+            | DocBookFormat+            | OpenDocumentFormat+            deriving (Show, Read, Eq)++instance StringTemplateShows String+  where stringTemplateShow s = s+        stringTemplateFormattedShow "uppercase" s = map toUpper s+        stringTemplateFormattedShow "lowercase" s = map toUpper s+        stringTemplateFormattedShow "capitalize" s = if null s+                                                        then ""+                                                        else toUpper (head s) : tail s+        stringTemplateFormattedShow f _ = error $ "Unknown format: " ++ f++instance ToSElem String+ where toSElem = stShowsToSE++instance ToSElem Node+  where toSElem x = case x of+         NString s   -> toSElem s+         NDate d     -> toSElem d+         NList xs    -> toSElem xs+         NMap xs     -> toSElem $ M.fromList xs+         NNil        -> toSElem ""
+ Yst/Util.hs view
@@ -0,0 +1,72 @@+{-+Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+-}++module Yst.Util (stripBlanks, parseAsDate, stripStExt, getStrAttrWithDefault, fromNString, getDirectoryContentsRecursive, errorExit)+where+import Yst.Types+import System.Exit+import System.FilePath+import System.IO (stderr)+import System.IO.UTF8 (hPutStrLn)+import System.Directory+import Control.Monad+import Data.Time+import Data.List (intercalate)+import Data.Char (isSpace)+import System.Locale (defaultTimeLocale)++-- | Strip blank lines from a file.+stripBlanks :: String -> String+stripBlanks = intercalate "\n" . filter (not . all isSpace) . lines++parseAsDate :: (ParseTime t) => String -> Maybe t+parseAsDate s =+  msum $ map (\fs -> parsetimeWith fs s) formats+   where parsetimeWith = parseTime defaultTimeLocale+         formats = ["%x","%m/%d/%Y", "%D","%F", "%d %b %Y"]++stripStExt :: FilePath -> FilePath+stripStExt f =+  if (takeExtension f == ".st")+     then dropExtension f+     else f++getStrAttrWithDefault :: String -> String -> [(String, Node)] -> String+getStrAttrWithDefault attr def xs =+  case lookup attr xs of+        Just (NString s)   -> s+        Just _             -> error $ attr ++ " must have string value."+        Nothing            -> def++fromNString :: Node -> String+fromNString (NString s) = s+fromNString x = error $ "Expected string value, got " ++ show x++getDirectoryContentsRecursive :: FilePath -> IO [FilePath]+getDirectoryContentsRecursive path = do+  isDir <- doesDirectoryExist path+  if isDir+     then do+       contents <- getDirectoryContents path+       let contents' = map (path </>) $ filter (`notElem` ["..","."]) contents+       children <- mapM getDirectoryContentsRecursive contents'+       return (concat children)+     else return [path]++errorExit :: Int -> String -> IO ()+errorExit lvl msg = hPutStrLn stderr msg >> exitWith (ExitFailure lvl)
+ Yst/Yaml.hs view
@@ -0,0 +1,63 @@+{-+Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+-}++module Yst.Yaml (readYamlFile, nodeToYamlNode)+where+import Yst.Types+import Yst.Util+import Data.Yaml.Syck+import Data.Time+import System.Locale (defaultTimeLocale)+import Prelude hiding (readFile)+import System.IO.UTF8++readYamlFile :: FilePath -> IO Node+readYamlFile f = catch (readFile f >>= parseYaml >>= return . yamlNodeToNode)+                   (\e -> errorExit 11 ("Error parsing " ++ f ++ ": " ++ show e) >> return NNil)++yamlNodeToNode :: YamlNode -> Node+yamlNodeToNode n =+  case n_elem n of+        EStr s  -> case parseAsDate (unpackBuf s) of+                        Nothing  -> NString (unpackBuf s)+                        Just d   -> NDate d+        EMap xs | all (\(k,_) -> isStrNode k) xs -> NMap pairs+                     where pairs = map mkPair xs+                           mkPair (k,v) = (strFrom k, yamlNodeToNode v)+        EMap _  -> error "Map keys must all be strings."+        ESeq xs -> NList $ map yamlNodeToNode xs+        ENil    -> NNil++nodeToYamlNode :: Node -> YamlNode+nodeToYamlNode n =+  case n of+       NString s -> mkNode (EStr $ packBuf s)+       NDate s   -> mkNode (EStr $ packBuf $ formatTime defaultTimeLocale "%x" s)+       NMap xs   -> mkNode (EMap $ map (\(k,v) -> (nodeToYamlNode (NString k), nodeToYamlNode v)) xs)+       NList xs  -> mkNode (ESeq $ map nodeToYamlNode xs)+       NNil      -> mkNode ENil++isStrNode :: YamlNode -> Bool+isStrNode x = case n_elem x of+                   EStr _ -> True+                   _      -> False++strFrom :: YamlNode -> String+strFrom x = case n_elem x of+                 EStr z   -> unpackBuf z+                 _        -> error "expected EStr node"
+ demo/april_events.st view
@@ -0,0 +1,3 @@+Here's what happened in April:++$april:event()$
+ demo/config.yaml view
@@ -0,0 +1,6 @@+indexfile: index.yaml+title: My Website+templatedir: .+datadir: .+filesdir: files+layout: layout.html.st
+ demo/date.st view
@@ -0,0 +1,1 @@+$it; format="%B %d, %Y"$
+ demo/event.st view
@@ -0,0 +1,2 @@+- $if(it.speaker)$$it.speaker; separator=" and "$, $endif$ *$it.title$*.+
+ demo/eventgroup.st view
@@ -0,0 +1,3 @@+**$first(it).date:date(); separator=" - "$**++$it:event()$
+ demo/events.st view
@@ -0,0 +1,11 @@+# Events++Here is a list of recent events:++<dl>+$eventsbydate:eventgroup()$+</dl>++Click [here](april_events.tex) for a LaTeX version+of the list of April events, generated form the same+YAML data file.
+ demo/events.yaml view
@@ -0,0 +1,19 @@+- date: 2009-06-28+  speaker:  Sam Smith+  title: Building a static website++- date:  2009-04-15+  speaker:+    - Sam Smith+    - '[Jim Jones](http://foo.bar/baz)'+  title: Advantages of static websites++- date:+    - 2009-04-20+    - 2009-04-22+  title: Seminar on web security++- date: 2009-04-15+  speaker: Jim Jones+  title:  XSS attacks for dummies+
+ demo/files/css/base-min.css view
@@ -0,0 +1,7 @@+/*+Copyright (c) 2009, Yahoo! Inc. All rights reserved.+Code licensed under the BSD License:+http://developer.yahoo.net/yui/license.txt+version: 2.7.0+*/+body{margin:10px;}h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong,dt{font-weight:bold;}optgroup{font-weight:normal;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;}em{font-style:italic;}del{text-decoration:line-through;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{border:1px solid #000;padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}sup{vertical-align:super;}sub{vertical-align:sub;}p,fieldset,table,pre{margin-bottom:1em;}button,input[type="checkbox"],input[type="radio"],input[type="reset"],input[type="submit"]{padding:1px;}
+ demo/files/css/hk-pyg.css view
@@ -0,0 +1,20 @@+/* Loosely based on pygment's default colors */+table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre +   { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }+td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; } +td.sourceCode { padding-left: 5px; }+pre.sourceCode { }+pre.sourceCode span.Normal { }+pre.sourceCode span.Keyword { color: #007020; font-weight: bold; } +pre.sourceCode span.DataType { color: #902000; }+pre.sourceCode span.DecVal { color: #40a070; }+pre.sourceCode span.BaseN { color: #40a070; }+pre.sourceCode span.Float { color: #40a070; }+pre.sourceCode span.Char { color: #4070a0; }+pre.sourceCode span.String { color: #4070a0; }+pre.sourceCode span.Comment { color: #60a0b0; font-style: italic; }+pre.sourceCode span.Others { color: #007020; }+pre.sourceCode span.Alert { color: red; font-weight: bold; }+pre.sourceCode span.Function { color: #06287e; }+pre.sourceCode span.RegionMarker { }+pre.sourceCode span.Error { color: red; font-weight: bold; }
+ demo/files/css/ie.css view
@@ -0,0 +1,20 @@+/* ie.css */+body {text-align:center;}+.container {text-align:left;}+* html .column {overflow-x:hidden;}+* html legend {margin:-18px -8px 16px 0;padding:0;}+ol {margin-left:2em;}+sup {vertical-align:text-top;}+sub {vertical-align:text-bottom;}+html>body p code {*white-space:normal;}+hr {margin:-8px auto 11px;}+.container ul { list-style: disc outside; margin-left: 2em; } /* IE can't handle :before and :after */+.container ul li { text-indent: 0; margin-left: 0; }+.container legend { margin-bottom: 1.6em; } /* IE form margin bug */+sup, sub { font-size: 100%; } /* IE superscript & subscript bug */+.container blockquote p, #content blockquote ul, #content blockquote ol, #content blockquote dl, #content blockquote pre, #content blockquote address, +.container blockquote table, #content blockquote form, #content blockquote h1, #content blockquote h2, #content blockquote h3, #content blockquote h4, #content blockquote h5, #content blockquote h6 { margin-top: .8em; margin-bottom: .8em; } /* IE can't handle :first-child */+* html .container textarea, * html .container input { padding: 0; } /* IE < 7 form fix */+.container input[type='submit'], .container input[type='button'] { padding: 0; } /* IE 7 button fix */+.container legend+* { margin-top: 0; } /* we already added legend margin */+a abbr, a acronym { text-decoration: underline; } /* IE 7 bug */
+ demo/files/css/print.css view
@@ -0,0 +1,39 @@+body { +width:100% !important;+margin:0 !important;+padding:0 !important;+line-height: 1.4;+word-spacing:1.1pt;+letter-spacing:0.2pt; font-family: "Times New Roman", serif; color: #000; background: none; font-size: 12pt; }++/*Headings */+h1,h2,h3,h4,h5,h6 { font-family: Helvetica, Arial, sans-serif; }+h1{font-size:19pt;}+h2{font-size:17pt;}+h3{font-size:15pt;}+h4,h5,h6{font-size:12pt;}++pre, code { font: 10pt Courier, monospace; } +blockquote { margin: 1.3em; padding: 1em;  font-size: 10pt; }+hr { background-color: #ccc; }++/* Images */+img { float: left; margin: 1em 1.5em 1.5em 0; }+a img { border: none; }++/* Links */+a:link, a:visited { background: transparent; font-weight: normal; text-decoration: underline; color:#333; }+a:link[href^="http://"]:after, a[href^="http://"]:visited:after { content: " (" attr(href) ")"; font-size: 90%; }+a[href^="http://"] {color:#000; }++/* Table */+table { margin: 1px; text-align:left; }+th { font-weight: bold; }+th,td { padding: 4px 10px 4px 0; }+tfoot { font-style: italic; }+caption { background: #fff; margin-bottom:2em; text-align:left; }+thead {display: table-header-group;}+tr {page-break-inside: avoid;} ++/*hide various parts from the site*/+
+ demo/files/css/reset-fonts-grids.css view
@@ -0,0 +1,7 @@+/*+Copyright (c) 2009, Yahoo! Inc. All rights reserved.+Code licensed under the BSD License:+http://developer.yahoo.net/yui/license.txt+version: 2.7.0+*/+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea,button{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}body{text-align:center;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main,.yui-g .yui-u .yui-g{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-g .yui-u{width:48.1%;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}.yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#hd:after,#bd:after,#ft:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#hd,#bd,#ft,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;}
+ demo/files/css/reset-min.css view
@@ -0,0 +1,7 @@+/*+Copyright (c) 2009, Yahoo! Inc. All rights reserved.+Code licensed under the BSD License:+http://developer.yahoo.net/yui/license.txt+version: 2.7.0+*/+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}
+ demo/files/css/screen.css view
@@ -0,0 +1,51 @@+@import url("reset-fonts-grids.css");+@import url("base-min.css");+@import url("hk-pyg.css");++html { background: #f9f9f9; color: black; } ++hr { height: 1px; color: #aaa; background-color: #aaa; border: 0; margin: .2em 0 .2em 0; }++h1, h2, h3, h4, h5, h6 { font-weight: normal; border-bottom: 1px solid black; }++h1 { font-size: 153.9%; margin: 1.07em 0 .535em; }+h2 { font-size: 138.5%; margin: 1.14em 0 .57em; }+h3 { font-size: 123.1%; margin: 1.23em 0 .615em; }+h4 { font-size: 116%; margin: 1.33em 0 .67em; }+h5 { font-size: 108%; margin: 1.6em 0 .8em; }+h6 { font-size: 100%; margin: 1.6em 0 .8em; }++ul { list-style-type: square; }+dt { font-weight: bold; margin-bottom: .1em; }++table { border-top: 1px solid #ccc;  border-left: 1px solid #ccc; }+th, td { border-bottom: 1px solid #ddd; border-right: 1px solid #ccc; }++blockquote { padding: 0 1.6em; color: #666; }++/* COLORS */++a:link { text-decoration: underline; color: #36c; }+a:visited { text-decoration: underline; color: #99c; }+a:hover { text-decoration: underline; color: #c33; }+a:active,  a:focus { text-decoration: underline; color: #000; }+/* code,  pre { color: #c33; } /* very optional, but still useful. W3C uses about the same colors for codes */++h1 > a:link, h1 > a:active, h1 > a:hover, h1 > a:focus, h1 > a:visited,+h2 > a:link, h2 > a:active, h2 > a:hover, h2 > a:focus, h2 > a:visited,+h3 > a:link, h3 > a:active, h3 > a:hover, h3 > a:focus, h3 > a:visited,+h4 > a:link, h4 > a:active, h4 > a:hover, h4 > a:focus, h4 > a:visited,+h5 > a:link, h5 > a:active, h5 > a:hover, h5 > a:focus, h5 > a:visited,+h6 > a:link, h6 > a:active, h6 > a:hover, h6 > a:focus, h6 > a:visited {+        color: black;+        text-decoration: none;+}++div#maincol { max-width: 46em; }++#footer { padding: 1em; color: #888; text-align: center; font-size: 93%; }++div#nav { margin-top: 2em; }+div#nav ul { margin-top: 0; margin-left: 1em; margin-bottom: 0; }+div#nav ul li { list-style: none; padding-bottom: 0.1em; }+div#nav ul li.current { list-style: square; }
+ demo/files/js/nav.js view
@@ -0,0 +1,7 @@+$(document).ready(function(){+  $("div#nav li > ul").hide();+  $("div#nav li > ul.active").show();+  $("div#nav ul li a").click(function(event){+    $(event.target).parent().children("ul").slideToggle("fast");+  });+});
+ demo/index.st view
@@ -0,0 +1,9 @@+# Welcome++Here's our website. Have a look around.++Our last two events:++$recentevents:event()$++For a complete list of events, see the [events](events.html) page.
+ demo/index.yaml view
@@ -0,0 +1,32 @@+- url      : index.html+  title    : Home+  template : index.st+  requires : event.st+  data     :+    recentevents : events.yaml ORDER BY date DESC LIMIT 2++- url      : events.html+  title    : Events +  template : events.st+  requires : [event.st, eventgroup.st, date.st]+  data     :+    eventsbydate : events.yaml ORDER BY date DESC GROUP BY date++- url      : april_events.tex+  title    : April Events+  template : april_events.st+  requires : event.st+  data     :+    april  : events.yaml WHERE date >= '2009-04-01' AND date < '2009-05-01' ORDER BY date+  layout   : layout.tex.st+  inmenu   : no ++- Rooms:+  - url      : room101.html+    title    : Room 101+    source   : room101.txt++  - url      : room203.html+    title    : Room 203+    source   : room203.txt+
+ demo/layout.html.st view
@@ -0,0 +1,22 @@+<html>+<head>+   <title>$sitetitle$ - $pagetitle$</title>+   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />+   <link rel="stylesheet" type="text/css" href="css/screen.css" />+   <link rel="stylesheet" type="text/css" media="print" href="css/print.css" />+   <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>+   <script src="js/nav.js" type="text/javascript"></script>+</head>+<body>+  <div id="doc3" class="yui-t1">+    <div id="yui-main">+      <div id="maincol" class="yui-b">+$contents$+      </div>+    </div>+    <div id="nav" class="yui-b first">+$nav$+    </div>+  </div>+</body>+</html>
+ demo/layout.tex.st view
@@ -0,0 +1,20 @@+\documentclass{article}+\usepackage{amsmath}+\usepackage[mathletters]{ucs}+\usepackage[utf8x]{inputenc}+\usepackage{hyperref}+\usepackage{listings}++\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{}++\setlength{\parindent}{0pt}+\setlength{\parskip}{6pt plus 2pt minus 1pt}++\title{$pagetitle$}++\begin{document}+\maketitle++$contents$++\end{document}
+ demo/room101.txt view
@@ -0,0 +1,9 @@+# Room 101++Room 101 holds 60 people.++Equipment:++  - blackboard+  - overhead projector+
+ demo/room203.txt view
@@ -0,0 +1,9 @@+# Room 203++Room 203 is accessible only via rope ladders.++Equipment:++  - Manual typewriters+  - Chalk+
+ yst.cabal view
@@ -0,0 +1,53 @@+name:                yst+version:             0.1+Cabal-version:       >= 1.2+build-type:          Simple+synopsis:            Builds a static website from templates and data in YAML or+                     CSV files.+description:         `yst` is a tool for generating a static website+                     by filling string templates with data taken from+                     YAML text files (or, alternatively, CSV files).+                     This approach combines the speed, security, and+                     ease of deployment of a static website with the+                     flexibility and maintainability of a dynamic site+                     that separates presentation and data.++category:            Text+license:             GPL+license-file:        LICENSE+author:              John MacFarlane+maintainer:          jgm@berkeley.edu+homepage:            http://github.com/jgm/yst+stability:           experimental+data-files:          README.markdown+                     demo/config.yaml+                     demo/date.st+                     demo/eventgroup.st+                     demo/april_events.st+                     demo/events.st+                     demo/event.st+                     demo/events.yaml+                     demo/index.st+                     demo/index.yaml+                     demo/layout.html.st+                     demo/layout.tex.st+                     demo/room101.txt+                     demo/room203.txt+                     demo/files/css/base-min.css+                     demo/files/css/hk-pyg.css+                     demo/files/css/ie.css+                     demo/files/css/print.css+                     demo/files/css/reset-fonts-grids.css+                     demo/files/css/reset-min.css+                     demo/files/css/screen.css+                     demo/files/js/nav.js+Executable           yst+  hs-source-dirs:    .+  main-is:           yst.hs+  other-modules:     Yst.Types, Yst.Yaml, Yst.Util, Yst.Data, Yst.Config,+                     Yst.Render, Yst.Build, Yst.CSV+  build-depends:     base >=3 && < 5, HStringTemplate >= 0.6.1, HsSyck, csv,+                     filepath, containers, directory, utf8-string, time,+                     old-locale, old-time, parsec, xhtml, pandoc+  ghc-options:       -Wall -threaded -fno-warn-orphans+  ghc-prof-options:  -auto-all -caf-all
+ yst.hs view
@@ -0,0 +1,67 @@+{-+Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+-}++module Main+where+import Paths_yst+import Yst.Util+import Yst.Config+import Yst.Build+import System.FilePath+import System.Environment+import System.Directory+import System.Exit+import System.IO.UTF8+import System.IO (stderr)+import Prelude hiding (readFile, putStrLn, print, writeFile)+import Control.Monad++createSite :: FilePath -> IO ()+createSite path = do+  existsd <- doesDirectoryExist path+  existsf <- doesFileExist path+  when (existsd || existsf) $ do+    hPutStrLn stderr $ "Aborting!  " ++ path ++ " already exists."+    exitWith $ ExitFailure 5+  demoDir <- getDataFileName "demo"+  contents <- liftM (filter (/=".") . map (makeRelative demoDir)) $ getDirectoryContentsRecursive demoDir+  forM_ contents $ \file -> do+    let dest = path </> file +    createDirectoryIfMissing True $ takeDirectory dest+    copyFile (demoDir </> file) dest+  readme <- getDataFileName "README.markdown"+  copyFile readme (path </> "README")+  hPutStrLn stderr $ "Created starter site in " ++ path++usageMessage :: IO ()+usageMessage = hPutStrLn stderr $+  "yst - create static website from string templates and YAML data\n" +++  "Usage:\n" +++  "yst [-f CONFIGFILE]        # build website\n" +++  "yst create DIRECTORY       # create starter site in DIRECTORY"++main :: IO ()+main = do+  args <- getArgs+  site <- case args of+               ["-f",x]     -> parseConfigFile x+               []           -> parseConfigFile "config.yaml"+               ["create",d] -> createSite d >> exitWith ExitSuccess+               _            -> usageMessage >> exitWith (ExitFailure 1)+  buildSite site+