├── .gitignore ├── LICENSE.txt ├── Markdown.pl ├── README.md ├── baker ├── layout ├── author.md ├── bootstrap.md ├── disqus.md ├── fontawesome.md ├── head.md ├── index.md └── post.md ├── post └── 2015-06-19-hello-world.md └── public ├── css └── base.css ├── favicon.png └── image └── baker.png /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Markdown.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # 4 | # Markdown -- A text-to-HTML conversion tool for web writers 5 | # 6 | # Copyright (c) 2004 John Gruber 7 | # 8 | # 9 | 10 | 11 | package Markdown; 12 | require 5.006_000; 13 | use strict; 14 | use warnings; 15 | 16 | use Digest::MD5 qw(md5_hex); 17 | use vars qw($VERSION); 18 | $VERSION = '1.0.1'; 19 | # Tue 14 Dec 2004 20 | 21 | ## Disabled; causes problems under Perl 5.6.1: 22 | # use utf8; 23 | # binmode( STDOUT, ":utf8" ); # c.f.: http://acis.openlib.org/dev/perl-unicode-struggle.html 24 | 25 | 26 | # 27 | # Global default settings: 28 | # 29 | my $g_empty_element_suffix = " />"; # Change to ">" for HTML output 30 | my $g_tab_width = 4; 31 | 32 | 33 | # 34 | # Globals: 35 | # 36 | 37 | # Regex to match balanced [brackets]. See Friedl's 38 | # "Mastering Regular Expressions", 2nd Ed., pp. 328-331. 39 | my $g_nested_brackets; 40 | $g_nested_brackets = qr{ 41 | (?> # Atomic matching 42 | [^\[\]]+ # Anything other than brackets 43 | | 44 | \[ 45 | (??{ $g_nested_brackets }) # Recursive set of nested brackets 46 | \] 47 | )* 48 | }x; 49 | 50 | 51 | # Table of hash values for escaped characters: 52 | my %g_escape_table; 53 | foreach my $char (split //, '\\`*_{}[]()>#+-.!') { 54 | $g_escape_table{$char} = md5_hex($char); 55 | } 56 | 57 | 58 | # Global hashes, used by various utility routines 59 | my %g_urls; 60 | my %g_titles; 61 | my %g_html_blocks; 62 | 63 | # Used to track when we're inside an ordered or unordered list 64 | # (see _ProcessListItems() for details): 65 | my $g_list_level = 0; 66 | 67 | 68 | #### Blosxom plug-in interface ########################################## 69 | 70 | # Set $g_blosxom_use_meta to 1 to use Blosxom's meta plug-in to determine 71 | # which posts Markdown should process, using a "meta-markup: markdown" 72 | # header. If it's set to 0 (the default), Markdown will process all 73 | # entries. 74 | my $g_blosxom_use_meta = 0; 75 | 76 | sub start { 1; } 77 | sub story { 78 | my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_; 79 | 80 | if ( (! $g_blosxom_use_meta) or 81 | (defined($meta::markup) and ($meta::markup =~ /^\s*markdown\s*$/i)) 82 | ){ 83 | $$body_ref = Markdown($$body_ref); 84 | } 85 | 1; 86 | } 87 | 88 | 89 | #### Movable Type plug-in interface ##################################### 90 | eval {require MT}; # Test to see if we're running in MT. 91 | unless ($@) { 92 | require MT; 93 | import MT; 94 | require MT::Template::Context; 95 | import MT::Template::Context; 96 | 97 | eval {require MT::Plugin}; # Test to see if we're running >= MT 3.0. 98 | unless ($@) { 99 | require MT::Plugin; 100 | import MT::Plugin; 101 | my $plugin = new MT::Plugin({ 102 | name => "Markdown", 103 | description => "A plain-text-to-HTML formatting plugin. (Version: $VERSION)", 104 | doc_link => 'http://daringfireball.net/projects/markdown/' 105 | }); 106 | MT->add_plugin( $plugin ); 107 | } 108 | 109 | MT::Template::Context->add_container_tag(MarkdownOptions => sub { 110 | my $ctx = shift; 111 | my $args = shift; 112 | my $builder = $ctx->stash('builder'); 113 | my $tokens = $ctx->stash('tokens'); 114 | 115 | if (defined ($args->{'output'}) ) { 116 | $ctx->stash('markdown_output', lc $args->{'output'}); 117 | } 118 | 119 | defined (my $str = $builder->build($ctx, $tokens) ) 120 | or return $ctx->error($builder->errstr); 121 | $str; # return value 122 | }); 123 | 124 | MT->add_text_filter('markdown' => { 125 | label => 'Markdown', 126 | docs => 'http://daringfireball.net/projects/markdown/', 127 | on_format => sub { 128 | my $text = shift; 129 | my $ctx = shift; 130 | my $raw = 0; 131 | if (defined $ctx) { 132 | my $output = $ctx->stash('markdown_output'); 133 | if (defined $output && $output =~ m/^html/i) { 134 | $g_empty_element_suffix = ">"; 135 | $ctx->stash('markdown_output', ''); 136 | } 137 | elsif (defined $output && $output eq 'raw') { 138 | $raw = 1; 139 | $ctx->stash('markdown_output', ''); 140 | } 141 | else { 142 | $raw = 0; 143 | $g_empty_element_suffix = " />"; 144 | } 145 | } 146 | $text = $raw ? $text : Markdown($text); 147 | $text; 148 | }, 149 | }); 150 | 151 | # If SmartyPants is loaded, add a combo Markdown/SmartyPants text filter: 152 | my $smartypants; 153 | 154 | { 155 | no warnings "once"; 156 | $smartypants = $MT::Template::Context::Global_filters{'smarty_pants'}; 157 | } 158 | 159 | if ($smartypants) { 160 | MT->add_text_filter('markdown_with_smartypants' => { 161 | label => 'Markdown With SmartyPants', 162 | docs => 'http://daringfireball.net/projects/markdown/', 163 | on_format => sub { 164 | my $text = shift; 165 | my $ctx = shift; 166 | if (defined $ctx) { 167 | my $output = $ctx->stash('markdown_output'); 168 | if (defined $output && $output eq 'html') { 169 | $g_empty_element_suffix = ">"; 170 | } 171 | else { 172 | $g_empty_element_suffix = " />"; 173 | } 174 | } 175 | $text = Markdown($text); 176 | $text = $smartypants->($text, '1'); 177 | }, 178 | }); 179 | } 180 | } 181 | else { 182 | #### BBEdit/command-line text filter interface ########################## 183 | # Needs to be hidden from MT (and Blosxom when running in static mode). 184 | 185 | # We're only using $blosxom::version once; tell Perl not to warn us: 186 | no warnings 'once'; 187 | unless ( defined($blosxom::version) ) { 188 | use warnings; 189 | 190 | #### Check for command-line switches: ################# 191 | my %cli_opts; 192 | use Getopt::Long; 193 | Getopt::Long::Configure('pass_through'); 194 | GetOptions(\%cli_opts, 195 | 'version', 196 | 'shortversion', 197 | 'html4tags', 198 | ); 199 | if ($cli_opts{'version'}) { # Version info 200 | print "\nThis is Markdown, version $VERSION.\n"; 201 | print "Copyright 2004 John Gruber\n"; 202 | print "http://daringfireball.net/projects/markdown/\n\n"; 203 | exit 0; 204 | } 205 | if ($cli_opts{'shortversion'}) { # Just the version number string. 206 | print $VERSION; 207 | exit 0; 208 | } 209 | if ($cli_opts{'html4tags'}) { # Use HTML tag style instead of XHTML 210 | $g_empty_element_suffix = ">"; 211 | } 212 | 213 | 214 | #### Process incoming text: ########################### 215 | my $text; 216 | { 217 | local $/; # Slurp the whole file 218 | $text = <>; 219 | } 220 | print Markdown($text); 221 | } 222 | } 223 | 224 | 225 | 226 | sub Markdown { 227 | # 228 | # Main function. The order in which other subs are called here is 229 | # essential. Link and image substitutions need to happen before 230 | # _EscapeSpecialChars(), so that any *'s or _'s in the 231 | # and tags get encoded. 232 | # 233 | my $text = shift; 234 | 235 | # Clear the global hashes. If we don't clear these, you get conflicts 236 | # from other articles when generating a page which contains more than 237 | # one article (e.g. an index page that shows the N most recent 238 | # articles): 239 | %g_urls = (); 240 | %g_titles = (); 241 | %g_html_blocks = (); 242 | 243 | 244 | # Standardize line endings: 245 | $text =~ s{\r\n}{\n}g; # DOS to Unix 246 | $text =~ s{\r}{\n}g; # Mac to Unix 247 | 248 | # Make sure $text ends with a couple of newlines: 249 | $text .= "\n\n"; 250 | 251 | # Convert all tabs to spaces. 252 | $text = _Detab($text); 253 | 254 | # Strip any lines consisting only of spaces and tabs. 255 | # This makes subsequent regexen easier to write, because we can 256 | # match consecutive blank lines with /\n+/ instead of something 257 | # contorted like /[ \t]*\n+/ . 258 | $text =~ s/^[ \t]+$//mg; 259 | 260 | # Turn block-level HTML blocks into hash entries 261 | $text = _HashHTMLBlocks($text); 262 | 263 | # Strip link definitions, store in hashes. 264 | $text = _StripLinkDefinitions($text); 265 | 266 | $text = _RunBlockGamut($text); 267 | 268 | $text = _UnescapeSpecialChars($text); 269 | 270 | return $text . "\n"; 271 | } 272 | 273 | 274 | sub _StripLinkDefinitions { 275 | # 276 | # Strips link definitions from text, stores the URLs and titles in 277 | # hash references. 278 | # 279 | my $text = shift; 280 | my $less_than_tab = $g_tab_width - 1; 281 | 282 | # Link defs are in the form: ^[id]: url "optional title" 283 | while ($text =~ s{ 284 | ^[ ]{0,$less_than_tab}\[(.+)\]: # id = $1 285 | [ \t]* 286 | \n? # maybe *one* newline 287 | [ \t]* 288 | ? # url = $2 289 | [ \t]* 290 | \n? # maybe one newline 291 | [ \t]* 292 | (?: 293 | (?<=\s) # lookbehind for whitespace 294 | ["(] 295 | (.+?) # title = $3 296 | [")] 297 | [ \t]* 298 | )? # title is optional 299 | (?:\n+|\Z) 300 | } 301 | {}mx) { 302 | $g_urls{lc $1} = _EncodeAmpsAndAngles( $2 ); # Link IDs are case-insensitive 303 | if ($3) { 304 | $g_titles{lc $1} = $3; 305 | $g_titles{lc $1} =~ s/"/"/g; 306 | } 307 | } 308 | 309 | return $text; 310 | } 311 | 312 | 313 | sub _HashHTMLBlocks { 314 | my $text = shift; 315 | my $less_than_tab = $g_tab_width - 1; 316 | 317 | # Hashify HTML blocks: 318 | # We only want to do this for block-level HTML tags, such as headers, 319 | # lists, and tables. That's because we still want to wrap

s around 320 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 321 | # phrase emphasis, and spans. The list of tags we're looking for is 322 | # hard-coded: 323 | my $block_tags_a = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del/; 324 | my $block_tags_b = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math/; 325 | 326 | # First, look for nested blocks, e.g.: 327 | #

328 | #
329 | # tags for inner block must be indented. 330 | #
331 | #
332 | # 333 | # The outermost tags must start at the left margin for this to match, and 334 | # the inner nested divs must be indented. 335 | # We need to do this before the next, more liberal match, because the next 336 | # match will start at the first `
` and stop at the first `
`. 337 | $text =~ s{ 338 | ( # save in $1 339 | ^ # start of line (with /m) 340 | <($block_tags_a) # start tag = $2 341 | \b # word break 342 | (.*\n)*? # any number of lines, minimally matching 343 | # the matching end tag 344 | [ \t]* # trailing spaces/tabs 345 | (?=\n+|\Z) # followed by a newline or end of document 346 | ) 347 | }{ 348 | my $key = md5_hex($1); 349 | $g_html_blocks{$key} = $1; 350 | "\n\n" . $key . "\n\n"; 351 | }egmx; 352 | 353 | 354 | # 355 | # Now match more liberally, simply from `\n` to `\n` 356 | # 357 | $text =~ s{ 358 | ( # save in $1 359 | ^ # start of line (with /m) 360 | <($block_tags_b) # start tag = $2 361 | \b # word break 362 | (.*\n)*? # any number of lines, minimally matching 363 | .* # the matching end tag 364 | [ \t]* # trailing spaces/tabs 365 | (?=\n+|\Z) # followed by a newline or end of document 366 | ) 367 | }{ 368 | my $key = md5_hex($1); 369 | $g_html_blocks{$key} = $1; 370 | "\n\n" . $key . "\n\n"; 371 | }egmx; 372 | # Special case just for
. It was easier to make a special case than 373 | # to make the other regex more complicated. 374 | $text =~ s{ 375 | (?: 376 | (?<=\n\n) # Starting after a blank line 377 | | # or 378 | \A\n? # the beginning of the doc 379 | ) 380 | ( # save in $1 381 | [ ]{0,$less_than_tab} 382 | <(hr) # start tag = $2 383 | \b # word break 384 | ([^<>])*? # 385 | /?> # the matching end tag 386 | [ \t]* 387 | (?=\n{2,}|\Z) # followed by a blank line or end of document 388 | ) 389 | }{ 390 | my $key = md5_hex($1); 391 | $g_html_blocks{$key} = $1; 392 | "\n\n" . $key . "\n\n"; 393 | }egx; 394 | 395 | # Special case for standalone HTML comments: 396 | $text =~ s{ 397 | (?: 398 | (?<=\n\n) # Starting after a blank line 399 | | # or 400 | \A\n? # the beginning of the doc 401 | ) 402 | ( # save in $1 403 | [ ]{0,$less_than_tab} 404 | (?s: 405 | 408 | ) 409 | [ \t]* 410 | (?=\n{2,}|\Z) # followed by a blank line or end of document 411 | ) 412 | }{ 413 | my $key = md5_hex($1); 414 | $g_html_blocks{$key} = $1; 415 | "\n\n" . $key . "\n\n"; 416 | }egx; 417 | 418 | 419 | return $text; 420 | } 421 | 422 | 423 | sub _RunBlockGamut { 424 | # 425 | # These are all the transformations that form block-level 426 | # tags like paragraphs, headers, and list items. 427 | # 428 | my $text = shift; 429 | 430 | $text = _DoHeaders($text); 431 | 432 | # Do Horizontal Rules: 433 | $text =~ s{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}{\n tags around block-level tags. 447 | $text = _HashHTMLBlocks($text); 448 | 449 | $text = _FormParagraphs($text); 450 | 451 | return $text; 452 | } 453 | 454 | 455 | sub _RunSpanGamut { 456 | # 457 | # These are all the transformations that occur *within* block-level 458 | # tags like paragraphs, headers, and list items. 459 | # 460 | my $text = shift; 461 | 462 | $text = _DoCodeSpans($text); 463 | 464 | $text = _EscapeSpecialChars($text); 465 | 466 | # Process anchor and image tags. Images must come first, 467 | # because ![foo][f] looks like an anchor. 468 | $text = _DoImages($text); 469 | $text = _DoAnchors($text); 470 | 471 | # Make links out of things like `` 472 | # Must come after _DoAnchors(), because you can use < and > 473 | # delimiters in inline links like [this](). 474 | $text = _DoAutoLinks($text); 475 | 476 | $text = _EncodeAmpsAndAngles($text); 477 | 478 | $text = _DoItalicsAndBold($text); 479 | 480 | # Do hard breaks: 481 | $text =~ s/ {2,}\n/ or tags. 493 | # my $tags_to_skip = qr!<(/?)(?:pre|code|kbd|script|math)[\s>]!; 494 | 495 | foreach my $cur_token (@$tokens) { 496 | if ($cur_token->[0] eq "tag") { 497 | # Within tags, encode * and _ so they don't conflict 498 | # with their use in Markdown for italics and strong. 499 | # We're replacing each such character with its 500 | # corresponding MD5 checksum value; this is likely 501 | # overkill, but it should prevent us from colliding 502 | # with the escape values by accident. 503 | $cur_token->[1] =~ s! \* !$g_escape_table{'*'}!gx; 504 | $cur_token->[1] =~ s! _ !$g_escape_table{'_'}!gx; 505 | $text .= $cur_token->[1]; 506 | } else { 507 | my $t = $cur_token->[1]; 508 | $t = _EncodeBackslashEscapes($t); 509 | $text .= $t; 510 | } 511 | } 512 | return $text; 513 | } 514 | 515 | 516 | sub _DoAnchors { 517 | # 518 | # Turn Markdown link shortcuts into XHTML
tags. 519 | # 520 | my $text = shift; 521 | 522 | # 523 | # First, handle reference-style links: [link text] [id] 524 | # 525 | $text =~ s{ 526 | ( # wrap whole match in $1 527 | \[ 528 | ($g_nested_brackets) # link text = $2 529 | \] 530 | 531 | [ ]? # one optional space 532 | (?:\n[ ]*)? # one optional newline followed by spaces 533 | 534 | \[ 535 | (.*?) # id = $3 536 | \] 537 | ) 538 | }{ 539 | my $result; 540 | my $whole_match = $1; 541 | my $link_text = $2; 542 | my $link_id = lc $3; 543 | 544 | if ($link_id eq "") { 545 | $link_id = lc $link_text; # for shortcut links like [this][]. 546 | } 547 | 548 | if (defined $g_urls{$link_id}) { 549 | my $url = $g_urls{$link_id}; 550 | $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid 551 | $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. 552 | $result = "? # href = $3 578 | [ \t]* 579 | ( # $4 580 | (['"]) # quote char = $5 581 | (.*?) # Title = $6 582 | \5 # matching quote 583 | )? # title is optional 584 | \) 585 | ) 586 | }{ 587 | my $result; 588 | my $whole_match = $1; 589 | my $link_text = $2; 590 | my $url = $3; 591 | my $title = $6; 592 | 593 | $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid 594 | $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. 595 | $result = " tags. 616 | # 617 | my $text = shift; 618 | 619 | # 620 | # First, handle reference-style labeled images: ![alt text][id] 621 | # 622 | $text =~ s{ 623 | ( # wrap whole match in $1 624 | !\[ 625 | (.*?) # alt text = $2 626 | \] 627 | 628 | [ ]? # one optional space 629 | (?:\n[ ]*)? # one optional newline followed by spaces 630 | 631 | \[ 632 | (.*?) # id = $3 633 | \] 634 | 635 | ) 636 | }{ 637 | my $result; 638 | my $whole_match = $1; 639 | my $alt_text = $2; 640 | my $link_id = lc $3; 641 | 642 | if ($link_id eq "") { 643 | $link_id = lc $alt_text; # for shortcut links like ![this][]. 644 | } 645 | 646 | $alt_text =~ s/"/"/g; 647 | if (defined $g_urls{$link_id}) { 648 | my $url = $g_urls{$link_id}; 649 | $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid 650 | $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. 651 | $result = "\"$alt_text\"";? # src url = $3 680 | [ \t]* 681 | ( # $4 682 | (['"]) # quote char = $5 683 | (.*?) # title = $6 684 | \5 # matching quote 685 | [ \t]* 686 | )? # title is optional 687 | \) 688 | ) 689 | }{ 690 | my $result; 691 | my $whole_match = $1; 692 | my $alt_text = $2; 693 | my $url = $3; 694 | my $title = ''; 695 | if (defined($6)) { 696 | $title = $6; 697 | } 698 | 699 | $alt_text =~ s/"/"/g; 700 | $title =~ s/"/"/g; 701 | $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid 702 | $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. 703 | $result = "\"$alt_text\"";" . _RunSpanGamut($1) . "\n\n"; 730 | }egmx; 731 | 732 | $text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{ 733 | "

" . _RunSpanGamut($1) . "

\n\n"; 734 | }egmx; 735 | 736 | 737 | # atx-style headers: 738 | # # Header 1 739 | # ## Header 2 740 | # ## Header 2 with closing hashes ## 741 | # ... 742 | # ###### Header 6 743 | # 744 | $text =~ s{ 745 | ^(\#{1,6}) # $1 = string of #'s 746 | [ \t]* 747 | (.+?) # $2 = Header text 748 | [ \t]* 749 | \#* # optional closing #'s (not counted) 750 | \n+ 751 | }{ 752 | my $h_level = length($1); 753 | "" . _RunSpanGamut($2) . "\n\n"; 754 | }egmx; 755 | 756 | return $text; 757 | } 758 | 759 | 760 | sub _DoLists { 761 | # 762 | # Form HTML ordered (numbered) and unordered (bulleted) lists. 763 | # 764 | my $text = shift; 765 | my $less_than_tab = $g_tab_width - 1; 766 | 767 | # Re-usable patterns to match list item bullets and number markers: 768 | my $marker_ul = qr/[*+-]/; 769 | my $marker_ol = qr/\d+[.]/; 770 | my $marker_any = qr/(?:$marker_ul|$marker_ol)/; 771 | 772 | # Re-usable pattern to match any entirel ul or ol list: 773 | my $whole_list = qr{ 774 | ( # $1 = whole list 775 | ( # $2 776 | [ ]{0,$less_than_tab} 777 | (${marker_any}) # $3 = first list item marker 778 | [ \t]+ 779 | ) 780 | (?s:.+?) 781 | ( # $4 782 | \z 783 | | 784 | \n{2,} 785 | (?=\S) 786 | (?! # Negative lookahead for another list item marker 787 | [ \t]* 788 | ${marker_any}[ \t]+ 789 | ) 790 | ) 791 | ) 792 | }mx; 793 | 794 | # We use a different prefix before nested lists than top-level lists. 795 | # See extended comment in _ProcessListItems(). 796 | # 797 | # Note: There's a bit of duplication here. My original implementation 798 | # created a scalar regex pattern as the conditional result of the test on 799 | # $g_list_level, and then only ran the $text =~ s{...}{...}egmx 800 | # substitution once, using the scalar as the pattern. This worked, 801 | # everywhere except when running under MT on my hosting account at Pair 802 | # Networks. There, this caused all rebuilds to be killed by the reaper (or 803 | # perhaps they crashed, but that seems incredibly unlikely given that the 804 | # same script on the same server ran fine *except* under MT. I've spent 805 | # more time trying to figure out why this is happening than I'd like to 806 | # admit. My only guess, backed up by the fact that this workaround works, 807 | # is that Perl optimizes the substition when it can figure out that the 808 | # pattern will never change, and when this optimization isn't on, we run 809 | # afoul of the reaper. Thus, the slightly redundant code to that uses two 810 | # static s/// patterns rather than one conditional pattern. 811 | 812 | if ($g_list_level) { 813 | $text =~ s{ 814 | ^ 815 | $whole_list 816 | }{ 817 | my $list = $1; 818 | my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol"; 819 | # Turn double returns into triple returns, so that we can make a 820 | # paragraph for the last item in a list, if necessary: 821 | $list =~ s/\n{2,}/\n\n\n/g; 822 | my $result = _ProcessListItems($list, $marker_any); 823 | $result = "<$list_type>\n" . $result . "\n"; 824 | $result; 825 | }egmx; 826 | } 827 | else { 828 | $text =~ s{ 829 | (?:(?<=\n\n)|\A\n?) 830 | $whole_list 831 | }{ 832 | my $list = $1; 833 | my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol"; 834 | # Turn double returns into triple returns, so that we can make a 835 | # paragraph for the last item in a list, if necessary: 836 | $list =~ s/\n{2,}/\n\n\n/g; 837 | my $result = _ProcessListItems($list, $marker_any); 838 | $result = "<$list_type>\n" . $result . "\n"; 839 | $result; 840 | }egmx; 841 | } 842 | 843 | 844 | return $text; 845 | } 846 | 847 | 848 | sub _ProcessListItems { 849 | # 850 | # Process the contents of a single ordered or unordered list, splitting it 851 | # into individual list items. 852 | # 853 | 854 | my $list_str = shift; 855 | my $marker_any = shift; 856 | 857 | 858 | # The $g_list_level global keeps track of when we're inside a list. 859 | # Each time we enter a list, we increment it; when we leave a list, 860 | # we decrement. If it's zero, we're not in a list anymore. 861 | # 862 | # We do this because when we're not inside a list, we want to treat 863 | # something like this: 864 | # 865 | # I recommend upgrading to version 866 | # 8. Oops, now this line is treated 867 | # as a sub-list. 868 | # 869 | # As a single paragraph, despite the fact that the second line starts 870 | # with a digit-period-space sequence. 871 | # 872 | # Whereas when we're inside a list (or sub-list), that line will be 873 | # treated as the start of a sub-list. What a kludge, huh? This is 874 | # an aspect of Markdown's syntax that's hard to parse perfectly 875 | # without resorting to mind-reading. Perhaps the solution is to 876 | # change the syntax rules such that sub-lists must start with a 877 | # starting cardinal number; e.g. "1." or "a.". 878 | 879 | $g_list_level++; 880 | 881 | # trim trailing blank lines: 882 | $list_str =~ s/\n{2,}\z/\n/; 883 | 884 | 885 | $list_str =~ s{ 886 | (\n)? # leading line = $1 887 | (^[ \t]*) # leading whitespace = $2 888 | ($marker_any) [ \t]+ # list marker = $3 889 | ((?s:.+?) # list item text = $4 890 | (\n{1,2})) 891 | (?= \n* (\z | \2 ($marker_any) [ \t]+)) 892 | }{ 893 | my $item = $4; 894 | my $leading_line = $1; 895 | my $leading_space = $2; 896 | 897 | if ($leading_line or ($item =~ m/\n{2,}/)) { 898 | $item = _RunBlockGamut(_Outdent($item)); 899 | } 900 | else { 901 | # Recursion for sub-lists: 902 | $item = _DoLists(_Outdent($item)); 903 | chomp $item; 904 | $item = _RunSpanGamut($item); 905 | } 906 | 907 | "
  • " . $item . "
  • \n"; 908 | }egmx; 909 | 910 | $g_list_level--; 911 | return $list_str; 912 | } 913 | 914 | 915 | 916 | sub _DoCodeBlocks { 917 | # 918 | # Process Markdown `
    ` blocks.
     919 | #	
     920 | 
     921 | 	my $text = shift;
     922 | 
     923 | 	$text =~ s{
     924 | 			(?:\n\n|\A)
     925 | 			(	            # $1 = the code block -- one or more lines, starting with a space/tab
     926 | 			  (?:
     927 | 			    (?:[ ]{$g_tab_width} | \t)  # Lines must start with a tab or a tab-width of spaces
     928 | 			    .*\n+
     929 | 			  )+
     930 | 			)
     931 | 			((?=^[ ]{0,$g_tab_width}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
     932 | 		}{
     933 | 			my $codeblock = $1;
     934 | 			my $result; # return value
     935 | 
     936 | 			$codeblock = _EncodeCode(_Outdent($codeblock));
     937 | 			$codeblock = _Detab($codeblock);
     938 | 			$codeblock =~ s/\A\n+//; # trim leading newlines
     939 | 			$codeblock =~ s/\s+\z//; # trim trailing whitespace
     940 | 
     941 | 			$result = "\n\n
    " . $codeblock . "\n
    \n\n"; 942 | 943 | $result; 944 | }egmx; 945 | 946 | return $text; 947 | } 948 | 949 | 950 | sub _DoCodeSpans { 951 | # 952 | # * Backtick quotes are used for spans. 953 | # 954 | # * You can use multiple backticks as the delimiters if you want to 955 | # include literal backticks in the code span. So, this input: 956 | # 957 | # Just type ``foo `bar` baz`` at the prompt. 958 | # 959 | # Will translate to: 960 | # 961 | #

    Just type foo `bar` baz at the prompt.

    962 | # 963 | # There's no arbitrary limit to the number of backticks you 964 | # can use as delimters. If you need three consecutive backticks 965 | # in your code, use four for delimiters, etc. 966 | # 967 | # * You can use spaces to get literal backticks at the edges: 968 | # 969 | # ... type `` `bar` `` ... 970 | # 971 | # Turns to: 972 | # 973 | # ... type `bar` ... 974 | # 975 | 976 | my $text = shift; 977 | 978 | $text =~ s@ 979 | (`+) # $1 = Opening run of ` 980 | (.+?) # $2 = The code block 981 | (?$c
    "; 990 | @egsx; 991 | 992 | return $text; 993 | } 994 | 995 | 996 | sub _EncodeCode { 997 | # 998 | # Encode/escape certain characters inside Markdown code runs. 999 | # The point is that in code, these characters are literals, 1000 | # and lose their special Markdown meanings. 1001 | # 1002 | local $_ = shift; 1003 | 1004 | # Encode all ampersands; HTML entities are not 1005 | # entities within a Markdown code span. 1006 | s/&/&/g; 1007 | 1008 | # Encode $'s, but only if we're running under Blosxom. 1009 | # (Blosxom interpolates Perl variables in article bodies.) 1010 | { 1011 | no warnings 'once'; 1012 | if (defined($blosxom::version)) { 1013 | s/\$/$/g; 1014 | } 1015 | } 1016 | 1017 | 1018 | # Do the angle bracket song and dance: 1019 | s! < !<!gx; 1020 | s! > !>!gx; 1021 | 1022 | # Now, escape characters that are magic in Markdown: 1023 | s! \* !$g_escape_table{'*'}!gx; 1024 | s! _ !$g_escape_table{'_'}!gx; 1025 | s! { !$g_escape_table{'{'}!gx; 1026 | s! } !$g_escape_table{'}'}!gx; 1027 | s! \[ !$g_escape_table{'['}!gx; 1028 | s! \] !$g_escape_table{']'}!gx; 1029 | s! \\ !$g_escape_table{'\\'}!gx; 1030 | 1031 | return $_; 1032 | } 1033 | 1034 | 1035 | sub _DoItalicsAndBold { 1036 | my $text = shift; 1037 | 1038 | # must go first: 1039 | $text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 } 1040 | {$2}gsx; 1041 | 1042 | $text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 } 1043 | {$2}gsx; 1044 | 1045 | return $text; 1046 | } 1047 | 1048 | 1049 | sub _DoBlockQuotes { 1050 | my $text = shift; 1051 | 1052 | $text =~ s{ 1053 | ( # Wrap whole match in $1 1054 | ( 1055 | ^[ \t]*>[ \t]? # '>' at the start of a line 1056 | .+\n # rest of the first line 1057 | (.+\n)* # subsequent consecutive lines 1058 | \n* # blanks 1059 | )+ 1060 | ) 1061 | }{ 1062 | my $bq = $1; 1063 | $bq =~ s/^[ \t]*>[ \t]?//gm; # trim one level of quoting 1064 | $bq =~ s/^[ \t]+$//mg; # trim whitespace-only lines 1065 | $bq = _RunBlockGamut($bq); # recurse 1066 | 1067 | $bq =~ s/^/ /g; 1068 | # These leading spaces screw with
     content, so we need to fix that:
    1069 | 			$bq =~ s{
    1070 | 					(\s*
    .+?
    ) 1071 | }{ 1072 | my $pre = $1; 1073 | $pre =~ s/^ //mg; 1074 | $pre; 1075 | }egsx; 1076 | 1077 | "
    \n$bq\n
    \n\n"; 1078 | }egmx; 1079 | 1080 | 1081 | return $text; 1082 | } 1083 | 1084 | 1085 | sub _FormParagraphs { 1086 | # 1087 | # Params: 1088 | # $text - string to process with html

    tags 1089 | # 1090 | my $text = shift; 1091 | 1092 | # Strip leading and trailing lines: 1093 | $text =~ s/\A\n+//; 1094 | $text =~ s/\n+\z//; 1095 | 1096 | my @grafs = split(/\n{2,}/, $text); 1097 | 1098 | # 1099 | # Wrap

    tags. 1100 | # 1101 | foreach (@grafs) { 1102 | unless (defined( $g_html_blocks{$_} )) { 1103 | $_ = _RunSpanGamut($_); 1104 | s/^([ \t]*)/

    /; 1105 | $_ .= "

    "; 1106 | } 1107 | } 1108 | 1109 | # 1110 | # Unhashify HTML blocks 1111 | # 1112 | foreach (@grafs) { 1113 | if (defined( $g_html_blocks{$_} )) { 1114 | $_ = $g_html_blocks{$_}; 1115 | } 1116 | } 1117 | 1118 | return join "\n\n", @grafs; 1119 | } 1120 | 1121 | 1122 | sub _EncodeAmpsAndAngles { 1123 | # Smart processing for ampersands and angle brackets that need to be encoded. 1124 | 1125 | my $text = shift; 1126 | 1127 | # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: 1128 | # http://bumppo.net/projects/amputator/ 1129 | $text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&/g; 1130 | 1131 | # Encode naked <'s 1132 | $text =~ s{<(?![a-z/?\$!])}{<}gi; 1133 | 1134 | return $text; 1135 | } 1136 | 1137 | 1138 | sub _EncodeBackslashEscapes { 1139 | # 1140 | # Parameter: String. 1141 | # Returns: The string, with after processing the following backslash 1142 | # escape sequences. 1143 | # 1144 | local $_ = shift; 1145 | 1146 | s! \\\\ !$g_escape_table{'\\'}!gx; # Must process escaped backslashes first. 1147 | s! \\` !$g_escape_table{'`'}!gx; 1148 | s! \\\* !$g_escape_table{'*'}!gx; 1149 | s! \\_ !$g_escape_table{'_'}!gx; 1150 | s! \\\{ !$g_escape_table{'{'}!gx; 1151 | s! \\\} !$g_escape_table{'}'}!gx; 1152 | s! \\\[ !$g_escape_table{'['}!gx; 1153 | s! \\\] !$g_escape_table{']'}!gx; 1154 | s! \\\( !$g_escape_table{'('}!gx; 1155 | s! \\\) !$g_escape_table{')'}!gx; 1156 | s! \\> !$g_escape_table{'>'}!gx; 1157 | s! \\\# !$g_escape_table{'#'}!gx; 1158 | s! \\\+ !$g_escape_table{'+'}!gx; 1159 | s! \\\- !$g_escape_table{'-'}!gx; 1160 | s! \\\. !$g_escape_table{'.'}!gx; 1161 | s{ \\! }{$g_escape_table{'!'}}gx; 1162 | 1163 | return $_; 1164 | } 1165 | 1166 | 1167 | sub _DoAutoLinks { 1168 | my $text = shift; 1169 | 1170 | $text =~ s{<((https?|ftp):[^'">\s]+)>}{
    $1}gi; 1171 | 1172 | # Email addresses: 1173 | $text =~ s{ 1174 | < 1175 | (?:mailto:)? 1176 | ( 1177 | [-.\w]+ 1178 | \@ 1179 | [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ 1180 | ) 1181 | > 1182 | }{ 1183 | _EncodeEmailAddress( _UnescapeSpecialChars($1) ); 1184 | }egix; 1185 | 1186 | return $text; 1187 | } 1188 | 1189 | 1190 | sub _EncodeEmailAddress { 1191 | # 1192 | # Input: an email address, e.g. "foo@example.com" 1193 | # 1194 | # Output: the email address as a mailto link, with each character 1195 | # of the address encoded as either a decimal or hex entity, in 1196 | # the hopes of foiling most address harvesting spam bots. E.g.: 1197 | # 1198 | # foo 1200 | # @example.com 1201 | # 1202 | # Based on a filter by Matthew Wickline, posted to the BBEdit-Talk 1203 | # mailing list: 1204 | # 1205 | 1206 | my $addr = shift; 1207 | 1208 | srand; 1209 | my @encode = ( 1210 | sub { '&#' . ord(shift) . ';' }, 1211 | sub { '&#x' . sprintf( "%X", ord(shift) ) . ';' }, 1212 | sub { shift }, 1213 | ); 1214 | 1215 | $addr = "mailto:" . $addr; 1216 | 1217 | $addr =~ s{(.)}{ 1218 | my $char = $1; 1219 | if ( $char eq '@' ) { 1220 | # this *must* be encoded. I insist. 1221 | $char = $encode[int rand 1]->($char); 1222 | } elsif ( $char ne ':' ) { 1223 | # leave ':' alone (to spot mailto: later) 1224 | my $r = rand; 1225 | # roughly 10% raw, 45% hex, 45% dec 1226 | $char = ( 1227 | $r > .9 ? $encode[2]->($char) : 1228 | $r < .45 ? $encode[1]->($char) : 1229 | $encode[0]->($char) 1230 | ); 1231 | } 1232 | $char; 1233 | }gex; 1234 | 1235 | $addr = qq{$addr}; 1236 | $addr =~ s{">.+?:}{">}; # strip the mailto: from the visible part 1237 | 1238 | return $addr; 1239 | } 1240 | 1241 | 1242 | sub _UnescapeSpecialChars { 1243 | # 1244 | # Swap back in all the special characters we've hidden. 1245 | # 1246 | my $text = shift; 1247 | 1248 | while( my($char, $hash) = each(%g_escape_table) ) { 1249 | $text =~ s/$hash/$char/g; 1250 | } 1251 | return $text; 1252 | } 1253 | 1254 | 1255 | sub _TokenizeHTML { 1256 | # 1257 | # Parameter: String containing HTML markup. 1258 | # Returns: Reference to an array of the tokens comprising the input 1259 | # string. Each token is either a tag (possibly with nested, 1260 | # tags contained therein, such as , or a 1261 | # run of text between tags. Each element of the array is a 1262 | # two-element array; the first is either 'tag' or 'text'; 1263 | # the second is the actual value. 1264 | # 1265 | # 1266 | # Derived from the _tokenize() subroutine from Brad Choate's MTRegex plugin. 1267 | # 1268 | # 1269 | 1270 | my $str = shift; 1271 | my $pos = 0; 1272 | my $len = length $str; 1273 | my @tokens; 1274 | 1275 | my $depth = 6; 1276 | my $nested_tags = join('|', ('(?:<[a-z/!$](?:[^<>]') x $depth) . (')*>)' x $depth); 1277 | my $match = qr/(?s: ) | # comment 1278 | (?s: <\? .*? \?> ) | # processing instruction 1279 | $nested_tags/ix; # nested tags 1280 | 1281 | while ($str =~ m/($match)/g) { 1282 | my $whole_tag = $1; 1283 | my $sec_start = pos $str; 1284 | my $tag_start = $sec_start - length $whole_tag; 1285 | if ($pos < $tag_start) { 1286 | push @tokens, ['text', substr($str, $pos, $tag_start - $pos)]; 1287 | } 1288 | push @tokens, ['tag', $whole_tag]; 1289 | $pos = pos $str; 1290 | } 1291 | push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len; 1292 | \@tokens; 1293 | } 1294 | 1295 | 1296 | sub _Outdent { 1297 | # 1298 | # Remove one level of line-leading tabs or spaces 1299 | # 1300 | my $text = shift; 1301 | 1302 | $text =~ s/^(\t|[ ]{1,$g_tab_width})//gm; 1303 | return $text; 1304 | } 1305 | 1306 | 1307 | sub _Detab { 1308 | # 1309 | # Cribbed from a post by Bart Lateur: 1310 | # 1311 | # 1312 | my $text = shift; 1313 | 1314 | $text =~ s{(.*?)\t}{$1.(' ' x ($g_tab_width - length($1) % $g_tab_width))}ge; 1315 | return $text; 1316 | } 1317 | 1318 | 1319 | 1; 1320 | 1321 | __END__ 1322 | 1323 | 1324 | =pod 1325 | 1326 | =head1 NAME 1327 | 1328 | B 1329 | 1330 | 1331 | =head1 SYNOPSIS 1332 | 1333 | B [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ] 1334 | [ I ... ] 1335 | 1336 | 1337 | =head1 DESCRIPTION 1338 | 1339 | Markdown is a text-to-HTML filter; it translates an easy-to-read / 1340 | easy-to-write structured text format into HTML. Markdown's text format 1341 | is most similar to that of plain text email, and supports features such 1342 | as headers, *emphasis*, code blocks, blockquotes, and links. 1343 | 1344 | Markdown's syntax is designed not as a generic markup language, but 1345 | specifically to serve as a front-end to (X)HTML. You can use span-level 1346 | HTML tags anywhere in a Markdown document, and you can use block level 1347 | HTML tags (like
    and as well). 1348 | 1349 | For more information about Markdown's syntax, see: 1350 | 1351 | http://daringfireball.net/projects/markdown/ 1352 | 1353 | 1354 | =head1 OPTIONS 1355 | 1356 | Use "--" to end switch parsing. For example, to open a file named "-z", use: 1357 | 1358 | Markdown.pl -- -z 1359 | 1360 | =over 4 1361 | 1362 | 1363 | =item B<--html4tags> 1364 | 1365 | Use HTML 4 style for empty element tags, e.g.: 1366 | 1367 |
    1368 | 1369 | instead of Markdown's default XHTML style tags, e.g.: 1370 | 1371 |
    1372 | 1373 | 1374 | =item B<-v>, B<--version> 1375 | 1376 | Display Markdown's version number and copyright information. 1377 | 1378 | 1379 | =item B<-s>, B<--shortversion> 1380 | 1381 | Display the short-form version number. 1382 | 1383 | 1384 | =back 1385 | 1386 | 1387 | 1388 | =head1 BUGS 1389 | 1390 | To file bug reports or feature requests (other than topics listed in the 1391 | Caveats section above) please send email to: 1392 | 1393 | support@daringfireball.net 1394 | 1395 | Please include with your report: (1) the example input; (2) the output 1396 | you expected; (3) the output Markdown actually produced. 1397 | 1398 | 1399 | =head1 VERSION HISTORY 1400 | 1401 | See the readme file for detailed release notes for this version. 1402 | 1403 | 1.0.1 - 14 Dec 2004 1404 | 1405 | 1.0 - 28 Aug 2004 1406 | 1407 | 1408 | =head1 AUTHOR 1409 | 1410 | John Gruber 1411 | http://daringfireball.net 1412 | 1413 | PHP port and other contributions by Michel Fortin 1414 | http://michelf.com 1415 | 1416 | 1417 | =head1 COPYRIGHT AND LICENSE 1418 | 1419 | Copyright (c) 2003-2004 John Gruber 1420 | 1421 | All rights reserved. 1422 | 1423 | Redistribution and use in source and binary forms, with or without 1424 | modification, are permitted provided that the following conditions are 1425 | met: 1426 | 1427 | * Redistributions of source code must retain the above copyright notice, 1428 | this list of conditions and the following disclaimer. 1429 | 1430 | * Redistributions in binary form must reproduce the above copyright 1431 | notice, this list of conditions and the following disclaimer in the 1432 | documentation and/or other materials provided with the distribution. 1433 | 1434 | * Neither the name "Markdown" nor the names of its contributors may 1435 | be used to endorse or promote products derived from this software 1436 | without specific prior written permission. 1437 | 1438 | This software is provided by the copyright holders and contributors "as 1439 | is" and any express or implied warranties, including, but not limited 1440 | to, the implied warranties of merchantability and fitness for a 1441 | particular purpose are disclaimed. In no event shall the copyright owner 1442 | or contributors be liable for any direct, indirect, incidental, special, 1443 | exemplary, or consequential damages (including, but not limited to, 1444 | procurement of substitute goods or services; loss of use, data, or 1445 | profits; or business interruption) however caused and on any theory of 1446 | liability, whether in contract, strict liability, or tort (including 1447 | negligence or otherwise) arising in any way out of the use of this 1448 | software, even if advised of the possibility of such damage. 1449 | 1450 | =cut 1451 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # baker, the real static blog generator in bash 2 | 3 | ![baker](http://i.imgur.com/Tngl5Vv.png) 4 | 5 | - [x] Template 6 | - [x] Markdown 7 | - [x] Draft 8 | - [ ] Video/Audio (ffmpeg) 9 | - [ ] Tag 10 | - [ ] RSS 11 | 12 | ## Start your first post 13 | 14 | 1. Give it a title: `./baker post I love pizza so much!` This command will create a markdown file that has the slug `i-love-pizza-so-much` in the `post` directory. If the `$EDITOR` environment variable is set, it will open up the post markdown file with the editor. 15 | 16 | 2. Change `draft` from `true` to `false` to publish the post. 17 | 18 | 3. Bake all posts: `./baker bake` 19 | 20 | ## Template redesigned 21 | 22 | The new template engine is much faster (and smaller) than the previous version. It now uses bash's scope as its context. 23 | 24 | ### variable 25 | 26 | Variable identifier should only use `[A-Za-z_]`. Notice that any number is not allowed in a variable name. 27 | 28 | ``` 29 | {{ var }} 30 | 31 | {{ content }} # embed child layout output 32 | ``` 33 | 34 | ### if 35 | 36 | Notice that spaces are not allowed between `!` and `var`. 37 | 38 | ``` 39 | @if !var 40 | ... 41 | @end 42 | ``` 43 | 44 | ### each 45 | 46 | `@each` iterates an array. This is why a number is not allowed in a variable name. 47 | 48 | For example, 49 | 50 | ``` 51 | posts = [ 52 | { 53 | "title": "first", 54 | "content": "example1", 55 | }, 56 | { 57 | "title": "second", 58 | "content": "example2", 59 | }, 60 | ] 61 | ``` 62 | 63 | is encoded as: 64 | 65 | ``` 66 | posts_0_title=first 67 | posts_0_content=example1 68 | 69 | posts_1_title=second 70 | posts_1_content=example2 71 | ``` 72 | 73 | ``` 74 | @each posts 75 | {{ title }} - {{ content }} 76 | @end 77 | ``` 78 | 79 | ### include 80 | 81 | `@include` includes a partial from `$LAYOUT_DIR/$filename.md`. Notice that `.md` is already added. 82 | 83 | ``` 84 | @include filename 85 | ``` 86 | 87 | ### cmd 88 | 89 | `@cmd` gets stdout from embedded bash script. 90 | 91 | ``` 92 | @cmd 93 | 94 | for i in {1..10}; do 95 | echo "$i" 96 | done 97 | 98 | @end 99 | ``` 100 | 101 | ## Markdown 102 | 103 | It currently uses the implementation from [Daring Fireball](http://daringfireball.net/projects/markdown/). 104 | 105 | ## License 106 | 107 | This software is made by taylorchu, and is released under GPL2. 108 | -------------------------------------------------------------------------------- /baker: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # config 5 | # 6 | 7 | # POST_DIR stores all post markdown files 8 | readonly POST_DIR=post 9 | 10 | # OUTPUT_DIR stores all compiled html 11 | readonly OUTPUT_DIR=out 12 | 13 | # LAYOUT_DIR stores all layout markdown files 14 | readonly LAYOUT_DIR=layout 15 | 16 | # PUBLIC_DIR stores css and static images 17 | readonly PUBLIC_DIR=public 18 | 19 | # site 20 | readonly SITE_NAME='a baker blog' 21 | readonly SITE_DESC='written in bash' 22 | readonly DISQUS='bakerbash' 23 | 24 | # author 25 | readonly AUTHOR_NAME='baker' 26 | readonly AUTHOR_DESC='a very-experienced bread baker, who also loves planting trees.' 27 | readonly AUTHOR_EMAIL='email@example.org' 28 | readonly AUTHOR_EMAIL_HASH="$(md5sum <<< "$AUTHOR_NAME" | awk '{ print $1 }')" 29 | readonly AUTHOR_TWITTER='twitter' 30 | readonly AUTHOR_GITHUB='github' 31 | 32 | # 33 | # helper 34 | # 35 | 36 | # absolute dir path of this script 37 | readonly SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" 38 | 39 | # variable identifier definition 40 | readonly VAR_ID='[A-Za-z_]+' 41 | 42 | # headers prints the header of a markdown file 43 | headers() { 44 | awk '/^---$/ { c++; next } c == 1' 45 | } 46 | 47 | # header prints a specific header value 48 | header() { 49 | headers | grep "^$1: " | cut -d ' ' -f 2- 50 | } 51 | 52 | # body prints the body of a markdown file 53 | body() { 54 | awk '/^---$/ { c++; next } c >= 2' 55 | } 56 | 57 | # slug creates a friendly URL like 'hello-world' 58 | slug() { 59 | tr -cs '[:alnum:]\n' - | tr '[:upper:]' '[:lower:]' | sed 's|^-*||;s|-*$||' 60 | } 61 | 62 | # 63 | # template 64 | # 65 | 66 | # first_block gets the first block location 67 | first_block() { 68 | local c 69 | local tag 70 | local start 71 | local lines 72 | local i 73 | readarray -t lines 74 | 75 | # counter is used to find the matching @end 76 | for (( i=0; i < ${#lines[@]}; i++ )); do 77 | case "${lines[i]}" in 78 | @if*) 79 | if (( c == 0 )); then 80 | tag=if 81 | start=$((i+1)) 82 | fi 83 | (( c++ )) 84 | ;; 85 | @each*) 86 | if (( c == 0 )); then 87 | tag=each 88 | start=$((i+1)) 89 | fi 90 | (( c++ )) 91 | ;; 92 | @cmd) 93 | if (( c == 0 )); then 94 | tag=cmd 95 | start=$((i+1)) 96 | fi 97 | (( c++ )) 98 | ;; 99 | @end) 100 | # @end is found before any starting tag 101 | (( c == 0 )) && return 1 102 | (( c-- )) 103 | if (( c == 0)); then 104 | # the matching @end is found, print `tag:start:end` 105 | echo "$tag:$start:$((i+1))" 106 | return 0 107 | fi 108 | ;; 109 | esac 110 | done 111 | } 112 | 113 | # find_submatch prints all occurrences of the first submatch 114 | find_submatch() { 115 | local s="$1" 116 | while [[ "$s" =~ $2 ]]; do 117 | echo "${BASH_REMATCH[1]}" 118 | s="${s#*${BASH_REMATCH[0]}}" 119 | done 120 | } 121 | 122 | # render_inline handles @include, and evaluates variables 123 | render_inline() { 124 | local lines 125 | local line 126 | local matches 127 | local match 128 | readarray -t lines 129 | for line in "${lines[@]}"; do 130 | if [[ "$line" =~ ^@include\ ($VAR_ID)$ ]]; then 131 | # @include renders partial 132 | render_file "$LAYOUT_DIR/${BASH_REMATCH[1]}.md" 133 | else 134 | # evaluate variables 135 | readarray -t matches < <(find_submatch "$line" "\{\{ ($VAR_ID) \}\}") 136 | for match in "${matches[@]}"; do 137 | line="${line//"{{ $match }}"/${!match}}" 138 | done 139 | 140 | echo "$line" 141 | fi 142 | done 143 | } 144 | 145 | # render_if handles @if 146 | render_if() { 147 | local lines 148 | local line 149 | readarray -t lines 150 | [[ "${lines[0]}" =~ ^@if\ ('!')?($VAR_ID)$ ]] || return 1 151 | 152 | # skip render on undefined_var or !defined_var 153 | if [[ "${!BASH_REMATCH[2]}" ]]; then 154 | [[ "${BASH_REMATCH[1]}" == '!' ]] && return 0 155 | else 156 | [[ "${BASH_REMATCH[1]}" == '!' ]] || return 0 157 | fi 158 | 159 | # recursively render the inner block 160 | for line in "${lines[@]:1:${#lines[@]}-2}"; do 161 | echo "$line" 162 | done | render 163 | } 164 | 165 | # render_each handles @each 166 | render_each() { 167 | local lines 168 | local line 169 | local idx 170 | local array 171 | local vars 172 | local var 173 | readarray -t lines 174 | [[ "${lines[0]}" =~ ^@each\ ($VAR_ID)$ ]] || return 1 175 | for (( idx=0; ; idx++ )); do 176 | array="${BASH_REMATCH[1]}_${idx}_" 177 | readarray -t vars < <(compgen -v | grep "^$array") 178 | (( ${#vars[@]} == 0 )) && break 179 | 180 | # define element in the loop context 181 | for var in "${vars[@]}"; do 182 | declare "${var#$array}"="${!var}" 183 | done 184 | 185 | # recursively render the inner block 186 | for line in "${lines[@]:1:${#lines[@]}-2}"; do 187 | echo "$line" 188 | done | render 189 | done 190 | } 191 | 192 | # render_cmd handles @cmd 193 | render_cmd() { 194 | source <(sed '1d; $d') 195 | } 196 | 197 | # render handles general template from stdin 198 | render() { 199 | local input="$(< /dev/stdin)" 200 | local tag 201 | local start 202 | local end 203 | 204 | IFS=: read -r tag start end < <(first_block <<< "$input") 205 | if [[ "$tag" ]]; then 206 | # render inline before the first block 207 | (( start > 1 )) && sed "1,$((start-1))!d" <<< "$input" | render_inline 208 | 209 | # handle first block 210 | case "$tag" in 211 | if) 212 | render_if 213 | ;; 214 | each) 215 | render_each 216 | ;; 217 | cmd) 218 | render_cmd 219 | ;; 220 | esac < <(sed "$start,$end!d" <<< "$input") 221 | 222 | # recursively handle the rest of the template 223 | sed "$((end+1)),\$!d" <<< "$input" | render 224 | else 225 | # if the first block is not found, just render inline 226 | render_inline <<< "$input" 227 | fi 228 | } 229 | 230 | # render_file_case uses markdown for posts, and uses render for templates 231 | render_file_case() { 232 | case "$1" in 233 | "$POST_DIR"/*) 234 | "$SCRIPT_DIR/Markdown.pl" 235 | ;; 236 | "$LAYOUT_DIR"/*) 237 | render 238 | ;; 239 | esac < <(body < "$1") 240 | } 241 | 242 | # export_headers reads markdown file headers 243 | export_headers() { 244 | local vars 245 | local var 246 | readarray -t vars < <(headers < "$1" | cut -d : -f 1) 247 | for var in "${vars[@]}"; do 248 | # by default, declare is local 249 | declare -g "$2$var"="$(header "$var" < "$1")" 250 | done 251 | } 252 | 253 | # render_file renders a specific file (with its parent layout) recursively 254 | render_file() { 255 | local content 256 | local f="$1" 257 | while [[ -f "$f" ]]; do 258 | export_headers "$f" '' 259 | content="$(render_file_case "$f")" 260 | 261 | f="$LAYOUT_DIR/$(header layout < "$f").md" 262 | done 263 | echo "$content" 264 | } 265 | 266 | # 267 | # usage 268 | # 269 | usage() { 270 | cat <<-EOF 271 | baker 272 | post [title] draft a post 273 | bake ship all posts 274 | EOF 275 | exit 1 276 | } 277 | 278 | (( $# == 0 )) && usage 279 | 280 | case "$1" in 281 | bake) 282 | rm -rf "$OUTPUT_DIR" 283 | mkdir -p "$OUTPUT_DIR" 284 | 285 | [[ -d "$POST_DIR" ]] || usage 286 | [[ -d "$PUBLIC_DIR" ]] && cp -r "$PUBLIC_DIR"/. "$OUTPUT_DIR" 287 | 288 | readarray -t posts < <(find "$POST_DIR" -name '*.md' | sort -r) 289 | 290 | idx=0 291 | time for post in "${posts[@]}"; do 292 | id="$(basename "$post" .md)" 293 | # skip drafts 294 | [[ "$(header draft < "$post")" == false ]] || continue 295 | 296 | echo "$id" 297 | render_file "$post" > "$OUTPUT_DIR/$id.html" & 298 | declare "posts_${idx}_id"="$id" 299 | export_headers "$post" "posts_${idx}_" 300 | (( idx++ )) 301 | done 302 | 303 | render_file "$LAYOUT_DIR/index.md" > "$OUTPUT_DIR/index.html" & 304 | wait 305 | ;; 306 | post) 307 | readonly title="${@:2}" 308 | [[ "$title" ]] || usage 309 | 310 | mkdir -p "$POST_DIR" 311 | readonly post_file="$POST_DIR/$(date +%Y-%m-%d)-$(slug <<< "$title").md" 312 | cat > "$post_file" <<-EOF 313 | --- 314 | title: $title 315 | date: $(date -u +%FT%TZ) 316 | layout: post 317 | draft: true 318 | summary: 319 | --- 320 | EOF 321 | 322 | echo "$post_file" 323 | [[ "$EDITOR" ]] && $EDITOR "$post_file" 324 | ;; 325 | *) 326 | usage 327 | ;; 328 | esac 329 | -------------------------------------------------------------------------------- /layout/author.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 |

    {{ AUTHOR_NAME }}

    5 |

    {{ AUTHOR_DESC }}

    6 | 11 | -------------------------------------------------------------------------------- /layout/bootstrap.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /layout/disqus.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 |
    5 | 16 | 17 | -------------------------------------------------------------------------------- /layout/fontawesome.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | -------------------------------------------------------------------------------- /layout/head.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | 7 | 8 | 9 | @include bootstrap 10 | @include fontawesome 11 | -------------------------------------------------------------------------------- /layout/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | @include head 7 | {{ SITE_NAME }} 8 | 9 | 10 |
    11 |
    12 |

    {{ SITE_NAME }}

    13 | {{ SITE_DESC }} 14 |
    15 | 16 |
    17 |
    18 | @each posts 19 |
    20 |

    {{ title }}

    21 | 22 |

    {{ summary }}

    23 |
    24 | @end 25 |
    26 |
    27 | author-avatar 28 |
    29 |
    30 | @include author 31 | 32 | logo-baker 33 |
    34 |
    35 | 36 |
    37 | A baker blog. 38 |
    39 |
    40 | 41 | 42 | -------------------------------------------------------------------------------- /layout/post.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | @include head 7 | {{ title }} - {{ SITE_NAME }} 8 | 9 | 10 |
    11 |
    12 | 18 |

    {{ title }}

    19 |
    20 | 21 |
    22 | {{ content }} 23 |
    24 | 25 |
    26 |
    27 | 28 |
    29 |
    30 | author-avatar 31 |
    32 |
    33 | @include author 34 |
    35 |
    36 | @include disqus 37 |
    38 | 39 | 40 | -------------------------------------------------------------------------------- /post/2015-06-19-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hello world! 3 | date: 2015-06-19T01:01:56Z 4 | layout: post 5 | draft: false 6 | summary: This is your first post. 7 | --- 8 | 9 | Welcome to Baker. This is your first post. Edit or delete it, then start blogging! 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/css/base.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 768px) { 2 | .container { 3 | max-width: 650px; 4 | } 5 | } 6 | body { 7 | padding-top: 40px; 8 | padding-bottom: 40px; 9 | } 10 | h1, 11 | h2, 12 | h3, 13 | h4 { 14 | font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 15 | letter-spacing: -0.02em; 16 | font-weight: bold; 17 | } 18 | article p, 19 | article ul, 20 | article ol { 21 | margin-bottom: 30px; 22 | font-size: 22px; 23 | line-height: 1.5; 24 | font-family: Georgia, Cambria, "Times New Roman", Times, serif; 25 | letter-spacing: .01rem; 26 | } 27 | header, 28 | footer, 29 | article { 30 | margin-bottom: 30px; 31 | } 32 | .post-date, 33 | .author-contact, 34 | .author-desc { 35 | font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 36 | font-size: 14px; 37 | line-height: 1.3; 38 | margin-bottom: 4px; 39 | letter-spacing: -0.02em; 40 | } 41 | .post-date { 42 | color: #7f929c; 43 | } 44 | .post-date time { 45 | font-weight: bold; 46 | } 47 | .author-avatar { 48 | width: 60px; 49 | height: 60px; 50 | } 51 | .author-name { 52 | font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 53 | letter-spacing: -0.02em; 54 | font-weight: bold; 55 | font-size: 18px; 56 | line-height: 1.1; 57 | margin-bottom: 4px; 58 | } 59 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorchu/baker/8dce0cd46c46d8ec29541080058f79c9e0ff579c/public/favicon.png -------------------------------------------------------------------------------- /public/image/baker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorchu/baker/8dce0cd46c46d8ec29541080058f79c9e0ff579c/public/image/baker.png --------------------------------------------------------------------------------