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 +340/−0
- README.markdown +496/−0
- Setup.lhs +3/−0
- Yst/Build.hs +94/−0
- Yst/CSV.hs +45/−0
- Yst/Config.hs +95/−0
- Yst/Data.hs +255/−0
- Yst/Render.hs +121/−0
- Yst/Types.hs +132/−0
- Yst/Util.hs +72/−0
- Yst/Yaml.hs +63/−0
- demo/april_events.st +3/−0
- demo/config.yaml +6/−0
- demo/date.st +1/−0
- demo/event.st +2/−0
- demo/eventgroup.st +3/−0
- demo/events.st +11/−0
- demo/events.yaml +19/−0
- demo/files/css/base-min.css +7/−0
- demo/files/css/hk-pyg.css +20/−0
- demo/files/css/ie.css +20/−0
- demo/files/css/print.css +39/−0
- demo/files/css/reset-fonts-grids.css +7/−0
- demo/files/css/reset-min.css +7/−0
- demo/files/css/screen.css +51/−0
- demo/files/js/nav.js +7/−0
- demo/index.st +9/−0
- demo/index.yaml +32/−0
- demo/layout.html.st +22/−0
- demo/layout.tex.st +20/−0
- demo/room101.txt +9/−0
- demo/room203.txt +9/−0
- yst.cabal +53/−0
- yst.hs +67/−0
+ 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; }
@@ -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+