Almost-minimal filesystem based blog.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

503 lines
15 KiB

7 years ago
  1. =pod
  2. =head1 NAME
  3. App::WRT - WRiting Tool, a static site/blog generator and related utilities
  4. =for HTML <a href="https://travis-ci.org/brennen/wrt"><img src="https://travis-ci.org/brennen/wrt.svg?branch=master"></a>
  5. =head1 SYNOPSIS
  6. Using the commandline tools:
  7. $ mkdir project
  8. $ cd project
  9. $ wrt init # set up some defaults
  10. $ wrt display new # print html for new posts to stdout
  11. $ wrt render-all # publish html to project/public/
  12. Using App::WRT in library form:
  13. #!/usr/bin/env perl
  14. use App::WRT;
  15. my $w = App::WRT->new(
  16. entry_dir => 'archives',
  17. url_root => '/',
  18. # etc.
  19. );
  20. print $w->display(@ARGV);
  21. =head1 INSTALLING
  22. It's possible but not likely this would run on a Perl as old as 5.10.0. In
  23. practice, I know that it works under 5.20.2. It should be fine on any
  24. reasonably modern Linux distribution, and may also be fine on MacOS or a BSD of
  25. your choosing.
  26. To install the latest development version from the main repo:
  27. $ git clone https://github.com/brennen/wrt.git
  28. $ cd wrt
  29. $ perl Build.PL
  30. $ ./Build installdeps
  31. $ ./Build test
  32. $ ./Build install
  33. To install the latest version released on CPAN:
  34. $ cpanm App::WRT
  35. Or:
  36. $ cpan -i App::WRT
  37. You will likely need to use C<sudo> or C<su> to get a systemwide install.
  38. =head1 DESCRIPTION
  39. This started life as C<display.pl>, a simple script to concatenate fragments of
  40. handwritten HTML by date. It has since accumulated several of the usual weblog
  41. features (lightweight markup, feed generation, embedded Perl, poetry tools,
  42. image galleries, and ill-advised dependencies), but the basic idea hasn't
  43. changed that much.
  44. The C<wrt> utility now generates static HTML files, instead of expecting to
  45. run as a CGI script. This is a better idea, for the most part.
  46. The C<App::WRT> module will work with FastCGI, if called from the appropriate
  47. wrapper script, such as C<wrt-fcgi>.
  48. By default, entries are stored in a simple directory tree under C<entry_dir>.
  49. Like:
  50. archives/2001/1/1
  51. archives/2001/1/2/index
  52. archives/2001/1/2/sub_entry
  53. Which will publish files like so:
  54. public/index.html
  55. public/all/index.html
  56. public/2001/index.html
  57. public/2001/1/index.html
  58. public/2001/1/1/index.html
  59. public/2001/1/2/index.html
  60. public/2001/1/2/sub_entry/index.html
  61. Contents will be generated for each year and for the entire collection of dated
  62. entries. Month indices will consist of all entries for that month. A
  63. top-level index file will consist of the most recent month's entries.
  64. It's possible (although not as flexible as it ought to be) to redefine the
  65. directory layout. (See C<%default{entry_map}> below.)
  66. An entry may be either a plain text file, or a directory containing several
  67. files. If it's a directory, a file named "index" will be treated as the text
  68. of the entry, and all other lower-case filenames without extensions will be
  69. treated as sub-entries or documents within that entry, and displayed
  70. accordingly. Links to certain other filetypes will be displayed as well.
  71. Directories may be nested to an arbitrary depth, although it's probably not a
  72. good idea to go very deep with the current display logic.
  73. A PNG or JPEG file with a name like
  74. 2001/1/1.icon.png
  75. 2001/1/1/index.icon.png
  76. 2001/1/1/whatever.icon.png
  77. 2001/1/1/whatever/index.icon.png
  78. will be treated as an icon for the corresponding entry file.
  79. =head2 MARKUP
  80. Entries may consist of hand-written HTML (to be passed along without further
  81. interpretation), a supported form of lightweight markup, or some combination
  82. thereof. Actually, an entry may consist of any darn thing you please, as long
  83. as Perl will agree that it is text, but presumably you're going to be feeding
  84. this to a browser.
  85. Header tags (<h1>, <h2>, etc.) will be used to display titles in feeds and
  86. other places.
  87. Other special markup is indicated by a variety of HTML-like container tags.
  88. B<Embedded Perl> - evaluated and replaced by whatever value you return
  89. (evaluated in a scalar context):
  90. <perl>my $dog = "Ralph."; return $dog;</perl>
  91. This code is evaluated before any other processing is done, so you can return
  92. any other markup understood by the script and have it handled appropriately.
  93. B<Interpolated variables> - actually keys to the hash underlying the App::WRT
  94. object, for the moment:
  95. <perl>$self->title("About Ralph, My Dog"); return '';</perl>
  96. <p>The title is <em>${title}</em>.</p>
  97. This is likely to change at some point, so don't build anything too elaborate
  98. on it.
  99. Embedded code and variables are intended only for use in the F<template> file,
  100. where it's handy to drop in titles or conditionalize aspects of a layout. You
  101. want to be careful with this sort of thing - it's useful in small doses, but
  102. it's also a maintainability nightmare waiting to happen. (WordPress, I am
  103. looking at you.)
  104. B<Includes> - replaced by the contents of the enclosed file path, from the
  105. root of the current wrt project:
  106. <include>path/to/file</include>
  107. This is a bit constraining, since it doesn't allow for files outside of the
  108. current project, but is useful for including HTML generated by an external
  109. script in a page.
  110. B<Several forms of lightweight markup>:
  111. <markdown>John Gruber's Markdown, by way of
  112. Text::Markdown</markdown>
  113. <textile>Dean Allen's Textile, via Brad Choate's
  114. Text::Textile.</textile>
  115. <freeverse>An easy way to
  116. get properly broken lines
  117. plus -- en and em dashes ---
  118. for poetry and such.</freeverse>
  119. B<And a couple of shortcuts>:
  120. <image>filename.ext
  121. alt text, if any</image>
  122. <list>
  123. one list item
  124. another list item
  125. </list>
  126. As it stands, freeverse, image, and list are not particularly robust.
  127. =head2 TEMPLATES
  128. A single template, specified by the C<template_dir> and C<template> config
  129. values, is used to render all pages. See F<example/templates/basic> for an
  130. example, or run C<wrt init> in an empty directory and look at
  131. F<templates/default>.
  132. Here's a short example:
  133. <!DOCTYPE html>
  134. <html>
  135. <head>
  136. <meta charset="UTF-8">
  137. <title>${title_prefix} - ${title}</title>
  138. </head>
  139. <body>
  140. ${content}
  141. </body>
  142. </html>
  143. Within templates, C<${foo}> will be replaced with the corresponding
  144. configuration value. C<${content}> will always be set to the content of the
  145. current entry.
  146. =head2 CONFIGURATION
  147. Configuration is read from a F<wrt.json> in the directory where the C<wrt>
  148. utility is invoked, or can (usually) be specified with the C<--config> option.
  149. See F<example/wrt.json> for a sample configuration.
  150. Under the hood, configuration is done by combining a hash called C<%default>
  151. with values pulled out of the JSON file. Most defaults can be overwritten
  152. from the config file, but changing some would require writing Perl, since
  153. they contain things like subroutine references.
  154. =over
  155. =item %default
  156. Here's a verbatim copy of C<%default>, with some commentary about values.
  157. my %default = (
  158. root_dir => '.', # dir for wrt repository
  159. entry_dir => 'archives', # dir for entry files
  160. publish_dir => 'public', # dir to publish site to
  161. url_root => "/", # root URL for building links
  162. image_url_root => '', # same for images
  163. template_dir => 'templates', # dir for template files
  164. template => 'default', # template to use
  165. title => '', # current title (used in template)
  166. title_prefix => '', # a string to slap in front of titles
  167. stylesheet_url => undef, # path to a CSS file (used in template)
  168. favicon_url => undef, # path to a favicon (used in template)
  169. feed_alias => 'feed', # what entry path should correspond to feed?
  170. feed_length => 30, # how many entries should there be in the feed?
  171. author => undef, # author name (used in template, feed)
  172. description => undef, # site description (used in template)
  173. content => undef, # place to stash content for templates
  174. embedded_perl => 1, # evaluate embedded <perl> tags?
  175. default_entry => 'new', # what to display if no entry specified
  176. # A license string for site content:
  177. license => 'public domain',
  178. # A string value to replace all pages with (useful for occasional
  179. # situations where every page of a site should serve some other
  180. # content in-place, like Net Neutrality protest blackouts):
  181. overlay => undef,
  182. # What gets considered an entry _path_:
  183. entrypath_expr => qr/^ ([a-z0-9_\/-]+) $/x,
  184. # What gets considered a subentry file (slightly misleading
  185. # terminology here):
  186. subentry_expr => qr/^[0-9a-z_-]+(\.(tgz|zip|tar[.]gz|gz|txt))?$/,
  187. # We'll show links for these, but not display them inline:
  188. binfile_expr => qr/[.](tgz|zip|tar[.]gz|gz|txt|pdf)$/,
  189. );
  190. =item $default{entry_map}
  191. A hash which will dispatch entries matching various regexen to the appropriate
  192. output methods. The default looks something like this:
  193. nnnn/[nn/nn/]doc_name - a document within a day.
  194. nnnn/nn/nn - a specific day.
  195. nnnn/nn - a month.
  196. nnnn - a year.
  197. doc_name - a document in the root directory.
  198. You can re-map things to an arbitrary archive layout.
  199. Since the entry map is a hash, and handle() simply loops over its keys, there
  200. is no guaranteed precedence of patterns. Be extremely careful that no entry
  201. will match more than one pattern, or you will wind up with unexpected behavior.
  202. A good way to ensure that this does not happen is to use patterns like:
  203. qr(
  204. ^ # start of string
  205. [0-9/]{4}/ # year
  206. [0-9]{1,2}/ # month
  207. [0-9]{1,2] # day
  208. $ # end of string
  209. )x
  210. ...always marking the start and end of the string explicitly.
  211. This may eventually be rewritten to use an array so that the order can be
  212. explicitly specified.
  213. =item $default{entry_descriptions}
  214. A hashref which contains a map of entry titles to entry descriptions.
  215. =back
  216. =head2 METHODS AND INTERNALS
  217. For no bigger than this thing is, the internals are convoluted. (This is
  218. because it's spaghetti code originally written in a now-archaic language by a
  219. teenager who didn't know how to program.)
  220. =over
  221. =item new_from_file($config_file)
  222. Takes a filename to pull JSON config data out of, and returns a new App::WRT
  223. instance with the parameters set in that file.
  224. =item new(%params)
  225. Get a new WRT object with the specified parameters set.
  226. =item display($entry1, $entry2, ...)
  227. Return a string containing the given entries, which are in the form of
  228. date/entry strings. If no parameters are given, default to default_entry().
  229. display() expands aliases ("new" and "all", for example) as necessary, collects
  230. output from handle($entry), and wraps the whole thing in a template file.
  231. =item handle($entry)
  232. Return the text of an individual entry.
  233. =begin digression
  234. =item A digression about each()
  235. I once spent a lot of time chasing down a bug caused by a while loop in this
  236. method. Specifically, I was using while to iterate over the entry_map hash.
  237. Since C<$self->entry_map> returns a reference to the same hash each time, every
  238. other request was finding C<each()> mid-way through iterating over this hash.
  239. I initially solved this by copying the hash into a local one called C<%map>
  240. every time C<handle()> was called. I could also have called C<keys> or
  241. C<values> on the anonymous hash, as these reset C<each()>.
  242. Presently I'm not using each() or an explicit loop, so this probably doesn't
  243. make a whole lot of sense in the context of the existing code.
  244. =end digression
  245. =item expand_option($option)
  246. Expands/converts 'all', 'new', and 'fulltext' to appropriate values.
  247. =item recent_month()
  248. Tries to find the most recent month in the archive.
  249. If a year file is text, returns that instead.
  250. =item fulltext()
  251. Returns the full text of all entries, in order.
  252. =item get_all_source_files()
  253. Returns a list of all source files for the current entry archive.
  254. This was originally in App::WRT::Renderer, so there may be some pitfalls here.
  255. =item get_all_day_entries()
  256. Returns a list of all entries which are a specific day.
  257. =item get_all_day_entries()
  258. Returns an easily sortable date of the form nnnn-nn-nn for a given entry.
  259. =item link_bar(@extra_links)
  260. Returns a little context-sensitive navigation bar.
  261. =item month_before($this_month)
  262. Return the month before the given month in the archive.
  263. Very naive; there has got to be a smarter way.
  264. =item year($year)
  265. List out the updates for a year.
  266. =item month($month)
  267. Prints the entries in a given month (nnnn/nn).
  268. =item entry_wrapped($entry, $level)
  269. Wraps entry() in entry_markup.
  270. =item entry_stamped($entry, $level)
  271. Wraps entry() + a datestamp in entry_markup()
  272. =item entry_topic_list($entry)
  273. Get a list of topics (by tag-* files) for the entry. This hardcodes a
  274. p1k3-specific thing, and is dumb.
  275. =item entry($entry)
  276. Returns the contents of a given entry. Calls dir_list
  277. and icon_markup. Recursively calls itself.
  278. =item get_sub_entries($entry_loc)
  279. Returns "sub entries" based on the C<subentry_expr> regexp.
  280. =item list_contents($entry, @entries)
  281. Returns links (potentially with icons) for a set of sub-entries within an
  282. entry.
  283. =item icon_markup($entry, $alt)
  284. Check if an icon exists for a given entry if so, return markup to include it.
  285. Icons are PNG or JPEG image files following a specific naming convention:
  286. index.icon.[png|jp(e)g] for directories
  287. [filename].icon.[png|jp(e)g] for flat text files
  288. Calls image_size, uses filename to determine type.
  289. =item datestamp($entry)
  290. Returns a nice html datestamp / breadcrumbs for a given entry.
  291. =item fragment_slurp($file)
  292. Read a text fragment, call line_parse() and eval_perl() to take care of funky
  293. markup and interpreting embedded code, and then return it as a string. Takes
  294. one parameter, the name of the file, and returns '' if it's not an extant text
  295. file.
  296. This might be the place to implement an in-memory cache for FastCGI or mod_perl
  297. environments. The trick is that the results for certain files shouldn't be
  298. cached because they contain embedded code.
  299. =item month_name($number)
  300. Turn numeric dates into English.
  301. =item root_locations($file)
  302. Given a file/entry, return the appropriate concatenations with
  303. entry_dir and url_root.
  304. =item local_path($file)
  305. Return an absolute path for a given file. Called by root_locations.
  306. Arguably this is stupid and inefficient.
  307. =item feed_print_latest()
  308. Return an Atom feed for the most recent entries.
  309. Called from display().
  310. =item feed_print(@entries)
  311. Return an Atom feed for the given list of entries.
  312. Requires XML::Atom::SimpleFeed.
  313. =back
  314. =head1 SEE ALSO
  315. walawiki.org, Blosxom, rassmalog, Text::Textile, XML::Atom::SimpleFeed,
  316. Image::Size, CGI::Fast, and about a gazillion static site generators.
  317. =head1 AUTHOR
  318. Copyright 2001-2017 Brennen Bearnes
  319. =head1 LICENSE
  320. wrt is free software; you can redistribute it and/or modify
  321. it under the terms of the GNU General Public License as published by
  322. the Free Software Foundation; either version 2 of the License, or
  323. (at your option) any later version.
  324. This program is distributed in the hope that it will be useful,
  325. but WITHOUT ANY WARRANTY; without even the implied warranty of
  326. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  327. GNU General Public License for more details.
  328. You should have received a copy of the GNU General Public License
  329. along with this program. If not, see <http://www.gnu.org/licenses/>.