bb.sh 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134
  1. #!/usr/bin/env bash
  2. # BashBlog, a simple blog system written in a single bash script
  3. # (C) Carlos Fenollosa <carlos.fenollosa@gmail.com>, 2011-2014 and contributors
  4. # https://github.com/carlesfe/bashblog/contributors
  5. # Check out README.md for more details
  6. # Global variables
  7. # It is recommended to perform a 'rebuild' after changing any of this in the code
  8. # Config file. Any settings "key=value" written there will override the
  9. # global_variables defaults. Useful to avoid editing bb.sh and having to deal
  10. # with merges in VCS
  11. global_config=".config"
  12. # This function will load all the variables defined here. They might be overridden
  13. # by the 'global_config' file contents
  14. global_variables() {
  15. global_software_name="BashBlog"
  16. global_software_version="2.4"
  17. # Blog title
  18. global_title="My fancy blog"
  19. # The typical subtitle for each blog
  20. global_description="A blog about turtles and carrots"
  21. # The public base URL for this blog
  22. global_url="http://example.com/blog"
  23. # Your name
  24. global_author="John Smith"
  25. # You can use twitter or facebook or anything for global_author_url
  26. global_author_url="http://twitter.com/example"
  27. # Your email
  28. global_email="john@smith.com"
  29. # CC by-nc-nd is a good starting point, you can change this to "&copy;" for Copyright
  30. global_license="CC by-nc-nd"
  31. # If you have a Google Analytics ID (UA-XXXXX) and wish to use the standard
  32. # embedding code, put it on global_analytics
  33. # If you have custom analytics code (i.e. non-google) or want to use the Universal
  34. # code, leave global_analytics empty and specify a global_analytics_file
  35. global_analytics=""
  36. global_analytics_file=""
  37. # Leave this empty (i.e. "") if you don't want to use feedburner,
  38. # or change it to your own URL
  39. global_feedburner=""
  40. # Change this to your username if you want to use twitter for comments
  41. global_twitter_username=""
  42. # Set this to false for a Twitter button with share count. The cookieless version
  43. # is just a link.
  44. global_twitter_cookieless="true"
  45. # Set to "topsy" which can search tweets way early in time, or "twitter"
  46. # for the default search page, where tweets more than a week old are hidden
  47. global_twitter_search="twitter"
  48. # Change this to your disqus username to use disqus for comments
  49. global_disqus_username=""
  50. # Blog generated files
  51. # index page of blog (it is usually good to use "index.html" here)
  52. index_file="index.html"
  53. number_of_index_articles="8"
  54. # global archive
  55. archive_index="all_posts.html"
  56. tags_index="all_tags.html"
  57. # feed file (rss in this case)
  58. blog_feed="feed.rss"
  59. number_of_feed_articles="10"
  60. # "cut" blog entry when putting it to index page. Leave blank for full articles in front page
  61. # i.e. include only up to first '<hr>', or '----' in markdown
  62. cut_do="cut"
  63. # When cutting, cut also tags? If "no", tags will appear in index page for cut articles
  64. cut_tags="yes"
  65. # Regexp matching the HTML line where to do the cut
  66. # note that slash is regexp separator so you need to prepend it with backslash
  67. cut_line='<hr ?\/?>'
  68. # save markdown file when posting with "bb post -m". Leave blank to discard it.
  69. save_markdown="yes"
  70. # prefix for tags/categories files
  71. # please make sure that no other html file starts with this prefix
  72. prefix_tags="tag_"
  73. # personalized header and footer (only if you know what you're doing)
  74. # DO NOT name them .header.html, .footer.html or they will be overwritten
  75. # leave blank to generate them, recommended
  76. header_file=""
  77. footer_file=""
  78. # extra content to add just after we open the <body> tag
  79. # and before the actual blog content
  80. body_begin_file=""
  81. # CSS files to include on every page, f.ex. css_include=('main.css' 'blog.css')
  82. # leave empty to use generated
  83. css_include=()
  84. # HTML files to exclude from index, f.ex. post_exclude=('imprint.html 'aboutme.html')
  85. html_exclude=()
  86. # Localization and i18n
  87. # "Comments?" (used in twitter link after every post)
  88. template_comments="Comments?"
  89. # "Read more..." (link under cut article on index page)
  90. template_read_more="Read more..."
  91. # "View more posts" (used on bottom of index page as link to archive)
  92. template_archive="View more posts"
  93. # "All posts" (title of archive page)
  94. template_archive_title="All posts"
  95. # "All tags"
  96. template_tags_title="All tags"
  97. # "posts" (on "All tags" page, text at the end of each tag line, like "2. Music - 15 posts")
  98. template_tags_posts="posts"
  99. # "Posts tagged" (text on a title of a page with index of one tag, like "My Blog - Posts tagged "Music"")
  100. template_tag_title="Posts tagged"
  101. # "Tags:" (beginning of line in HTML file with list of all tags for this article)
  102. template_tags_line_header="Tags:"
  103. # "Back to the index page" (used on archive page, it is link to blog index)
  104. template_archive_index_page="Back to the index page"
  105. # "Subscribe" (used on bottom of index page, it is link to RSS feed)
  106. template_subscribe="Subscribe"
  107. # "Subscribe to this page..." (used as text for browser feed button that is embedded to html)
  108. template_subscribe_browser_button="Subscribe to this page..."
  109. # "Tweet" (used as twitter text button for posting to twitter)
  110. template_twitter_button="Tweet"
  111. template_twitter_comment="&lt;Type your comment here but please leave the URL so that other people can follow the comments&gt;"
  112. # The locale to use for the dates displayed on screen (not for the timestamps)
  113. date_format="%B %d, %Y"
  114. date_allposts_header="%B %Y"
  115. date_locale="C"
  116. # Perform the post title -> filename conversion
  117. # Experts only. You may need to tune the locales too
  118. # Leave empty for no conversion, which is not recommended
  119. # This default filter respects backwards compatibility
  120. convert_filename="iconv -f utf-8 -t ascii//translit | sed 's/^-*//' | tr [:upper:] [:lower:] | tr ' ' '-' | tr -dc '[:alnum:]-'"
  121. # URL where you can view the post while it's being edited
  122. # same as global_url by default
  123. # You can change it to path on your computer, if you write posts locally
  124. # before copying them to the server
  125. preview_url=""
  126. # Markdown location. Trying to autodetect by default.
  127. # The invocation must support the signature 'markdown_bin in.md > out.html'
  128. markdown_bin=$(which Markdown.pl || which markdown)
  129. }
  130. # Check for the validity of some variables
  131. # DO NOT EDIT THIS FUNCTION unless you know what you're doing
  132. global_variables_check() {
  133. [[ $header_file == .header.html ]] &&
  134. echo "Please check your configuration. '.header.html' is not a valid value for the setting 'header_file'" &&
  135. exit
  136. [[ $footer_file == .footer.html ]] &&
  137. echo "Please check your configuration. '.footer.html' is not a valid value for the setting 'footer_file'" &&
  138. exit
  139. }
  140. # Test if the markdown script is working correctly
  141. test_markdown() {
  142. [[ -z $markdown_bin ]] && return 1
  143. [[ -z $(which diff) ]] && return 1
  144. in=/tmp/md-in-${RANDOM}.md
  145. out=/tmp/md-out-${RANDOM}.html
  146. good=/tmp/md-good-${RANDOM}.html
  147. echo -e "line 1\n\nline 2" > "$in"
  148. echo -e "<p>line 1</p>\n\n<p>line 2</p>" > "$good"
  149. "$markdown_bin" "$in" > "$out" 2> /dev/null
  150. diff $good $out &> /dev/null # output is irrelevant, we'll check $?
  151. if (($? != 0)); then
  152. rm -f "$in" "$good" "$out"
  153. return 1
  154. fi
  155. rm -f "$in" "$good" "$out"
  156. return 0
  157. }
  158. # Parse a Markdown file into HTML and return the generated file
  159. markdown() {
  160. out=${1%.md}.html
  161. while [[ -f $out ]]; do out=${out%.html}.$RANDOM.html; done
  162. $markdown_bin "$1" > "$out"
  163. echo "$out"
  164. }
  165. # Prints the required google analytics code
  166. google_analytics() {
  167. [[ -z $global_analytics && -z $global_analytics_file ]] && return
  168. if [[ -z $global_analytics_file ]]; then
  169. echo "<script type=\"text/javascript\">
  170. var _gaq = _gaq || [];
  171. _gaq.push(['_setAccount', '${global_analytics}']);
  172. _gaq.push(['_trackPageview']);
  173. (function() {
  174. var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  175. ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  176. var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  177. })();
  178. </script>"
  179. else
  180. cat "$global_analytics_file"
  181. fi
  182. }
  183. # Prints the required code for disqus comments
  184. disqus_body() {
  185. [[ -z $global_disqus_username ]] && return
  186. echo '<div id="disqus_thread"></div>
  187. <script type="text/javascript">
  188. /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
  189. var disqus_shortname = '\'$global_disqus_username\''; // required: replace example with your forum shortname
  190. /* * * DONT EDIT BELOW THIS LINE * * */
  191. (function() {
  192. var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
  193. dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
  194. (document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
  195. })();
  196. </script>
  197. <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
  198. <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>'
  199. }
  200. # Prints the required code for disqus in the footer
  201. disqus_footer() {
  202. [[ -z $global_disqus_username ]] && return
  203. echo '<script type="text/javascript">
  204. /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
  205. var disqus_shortname = '\'$global_disqus_username\''; // required: replace example with your forum shortname
  206. /* * * DONT EDIT BELOW THIS LINE * * */
  207. (function () {
  208. var s = document.createElement("script"); s.async = true;
  209. s.type = "text/javascript";
  210. s.src = "//" + disqus_shortname + ".disqus.com/count.js";
  211. (document.getElementsByTagName("HEAD")[0] || document.getElementsByTagName("BODY")[0]).appendChild(s);
  212. }());
  213. </script>'
  214. }
  215. # Reads HTML file from stdin, prints its content to stdout
  216. # $1 where to start ("text" or "entry")
  217. # $2 where to stop ("text" or "entry")
  218. # $3 "cut" to remove text from <hr /> to <!-- text end -->
  219. # note that this does not remove <hr /> line itself,
  220. # so you can see if text was cut or not
  221. get_html_file_content() {
  222. awk "/<!-- $1 begin -->/, /<!-- $2 end -->/{
  223. if (!/<!-- $1 begin -->/ && !/<!-- $2 end -->/) print
  224. if (\"$3\" == \"cut\" && /$cut_line/){
  225. if (\"$2\" == \"text\") exit # no need to read further
  226. while (getline > 0 && !/<!-- text end -->/) {
  227. if (\"$cut_tags\" == \"no\" && /^<p>$template_tags_line_header/ ) print
  228. }
  229. }
  230. }"
  231. }
  232. # Edit an existing, published .html file while keeping its original timestamp
  233. # Please note that this function does not automatically republish anything, as
  234. # it is usually called from 'main'.
  235. #
  236. # Note that it edits HTML file, even if you wrote the post as markdown originally
  237. # Note that if you edit title then filename might also change
  238. #
  239. # $1 the file to edit
  240. # $2 (optional) edit mode:
  241. # "keep" to keep old filename
  242. # "full" to edit full HTML, and not only text part (keeps old filename)
  243. # leave empty for default behavior (edit only text part and change name)
  244. edit() {
  245. # Original post timestamp
  246. edit_timestamp=$(LC_ALL=C date -r "${1%%.*}.html" +"%a, %d %b %Y %H:%M:%S %z" )
  247. touch_timestamp=$(LC_ALL=C date -r "${1%%.*}.html" +'%Y%m%d%H%M')
  248. tags_before=$(tags_in_post "${1%%.*}.html")
  249. if [[ $2 == full ]]; then
  250. $EDITOR "$1"
  251. filename=$1
  252. else
  253. if [[ ${1##*.} == md ]]; then
  254. test_markdown
  255. if (($? != 0)); then
  256. echo "Markdown is not working, please edit HTML file directly."
  257. exit
  258. fi
  259. # editing markdown file
  260. $EDITOR "$1"
  261. TMPFILE=$(markdown "$1")
  262. filename=${1%%.*}.html
  263. else
  264. # Create the content file
  265. TMPFILE=$(basename "$1").$RANDOM.html
  266. # Title
  267. get_post_title "$1" > "$TMPFILE"
  268. # Post text with plaintext tags
  269. get_html_file_content 'text' 'text' <"$1" | sed "/^<p>$template_tags_line_header/s|<a href='$prefix_tags\([^']*\).html'>\\1</a>|\\1|g" >> "$TMPFILE"
  270. $EDITOR "$TMPFILE"
  271. filename=$1
  272. fi
  273. rm "$filename"
  274. if [[ $2 == keep ]]; then
  275. parse_file "$TMPFILE" "$edit_timestamp" "$filename"
  276. else
  277. parse_file "$TMPFILE" "$edit_timestamp" # this command sets $filename as the html processed file
  278. [[ ${1##*.} == md ]] && mv "$1" "${filename%%.*}.md" 2>/dev/null
  279. fi
  280. rm "$TMPFILE"
  281. fi
  282. touch -t "$touch_timestamp" "$filename"
  283. chmod 644 "$filename"
  284. echo "Posted $filename"
  285. tags_after=$(tags_in_post "$filename")
  286. relevant_tags=$(echo "$tags_before $tags_after" | tr ',' ' ' | tr ' ' '\n' | sort -u | tr '\n' ' ')
  287. if [[ ! -z $relevant_tags ]]; then
  288. relevant_posts="$(posts_with_tags $relevant_tags) $filename"
  289. rebuild_tags "$relevant_posts" "$relevant_tags"
  290. fi
  291. }
  292. # Create a Twitter summary (twitter "card") for the post
  293. #
  294. # $1 the post file
  295. # $2 the title
  296. twitter_card() {
  297. [[ -z $global_twitter_username ]] && return
  298. echo "<meta name='twitter:card' content='summary' />"
  299. echo "<meta name='twitter:site' content='@$global_twitter_username' />"
  300. echo "<meta name='twitter:title' content='$2' />" # Twitter truncates at 70 char
  301. description=$(grep -v "^<p>$template_tags_line_header" "$1" | sed -e 's/<[^>]*>//g' | head -c 250 | tr '\n' ' ' | sed "s/\"/'/g")
  302. echo "<meta name='twitter:description' content=\"$description\" />"
  303. image=$(sed -n 's/.*<img.*src="\([^"]*\)".*/\1/p' "$1" | head -n 1) # First image is fine
  304. [[ -z $image ]] && return
  305. [[ $image =~ ^https?:// ]] || image=$global_url/$image # Check that URL is absolute
  306. echo "<meta name='twitter:image' content='$image' />"
  307. }
  308. # Adds the code needed by the twitter button
  309. #
  310. # $1 the post URL
  311. twitter() {
  312. [[ -z $global_twitter_username ]] && return
  313. if [[ -z $global_disqus_username ]]; then
  314. if [[ $global_twitter_cookieless == true ]]; then
  315. id=$RANDOM
  316. search_engine="https://twitter.com/search?q="
  317. [[ $global_twitter_search == topsy ]] && search_engine="http://topsy.com/trackback?url="
  318. echo "<p id='twitter'><a href='http://twitter.com/intent/tweet?url=$1&text=$template_twitter_comment&via=$global_twitter_username'>$template_comments $template_twitter_button</a> "
  319. echo "<a href='$search_engine""$1'><span id='count-$id'></span></a>&nbsp;</p>"
  320. # Get current tweet count
  321. echo "<script type=\"text/javascript\">\$.ajax({type: \"GET\", url: \"https://cdn.api.twitter.com/1/urls/count.json?url=$1\",
  322. dataType: \"jsonp\", success: function(data){ \$(\"#count-$id\").html(\"(\" + data.count + \")\"); }}); </script>"
  323. return;
  324. else
  325. echo "<p id='twitter'>$template_comments&nbsp;";
  326. fi
  327. else
  328. echo "<p id='twitter'><a href=\"$1#disqus_thread\">$template_comments</a> &nbsp;"
  329. fi
  330. echo "<a href=\"https://twitter.com/share\" class=\"twitter-share-button\" data-text=\"$template_twitter_comment\" data-url=\"$1\""
  331. echo " data-via=\"$global_twitter_username\""
  332. echo ">$template_twitter_button</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=\"//platform.twitter.com/widgets.js\";fjs.parentNode.insertBefore(js,fjs);}}(document,\"script\",\"twitter-wjs\");</script>"
  333. echo "</p>"
  334. }
  335. # Check if the file is a 'boilerplate' (i.e. not a post)
  336. # The return values are designed to be used like this inside a loop:
  337. # is_boilerplate_file <file> && continue
  338. #
  339. # $1 the file
  340. #
  341. # Return 0 (bash return value 'true') if the input file is an index, feed, etc
  342. # or 1 (bash return value 'false') if it is a blogpost
  343. is_boilerplate_file() {
  344. name=$(clean_filename "$1")
  345. case $name in
  346. ( "$index_file" | "$archive_index" | "$tags_index" | "$footer_file" | "$header_file" | "$global_analytics_file" | "$prefix_tags"* )
  347. return 0 ;;
  348. ( * ) # Check for excluded
  349. for excl in "${html_exclude[@]}"; do
  350. [[ $name == "$excl" ]] && return 0
  351. done
  352. return 1 ;;
  353. esac
  354. }
  355. # Filenames sometimes have leading './' or other oddities which need to be cleaned
  356. #
  357. # $1 the file name
  358. # returns the clean file name
  359. clean_filename() {
  360. echo "${1#./}" # Delete leading './'
  361. }
  362. # Adds all the bells and whistles to format the html page
  363. # Every blog post is marked with a <!-- entry begin --> and <!-- entry end -->
  364. # which is parsed afterwards in the other functions. There is also a marker
  365. # <!-- text begin --> to determine just the beginning of the text body of the post
  366. #
  367. # $1 a file with the body of the content
  368. # $2 the output file
  369. # $3 "yes" if we want to generate the index.html,
  370. # "no" to insert new blog posts
  371. # $4 title for the html header
  372. # $5 original blog timestamp
  373. create_html_page() {
  374. content=$1
  375. filename=$2
  376. index=$3
  377. title=$4
  378. timestamp=$5
  379. # Create the actual blog post
  380. # html, head
  381. {
  382. cat ".header.html"
  383. echo "<title>$title</title>"
  384. google_analytics
  385. twitter_card "$content" "$title"
  386. echo "</head><body>"
  387. # stuff to add before the actual body content
  388. [[ -n $body_begin_file ]] && cat "$body_begin_file"
  389. # body divs
  390. echo '<div id="divbodyholder">'
  391. echo '<div class="headerholder"><div class="header">'
  392. # blog title
  393. echo '<div id="title">'
  394. cat .title.html
  395. echo '</div></div></div>' # title, header, headerholder
  396. echo '<div id="divbody"><div class="content">'
  397. file_url=$(clean_filename "$filename")
  398. file_url=$(sed 's/.rebuilt//g' <<< "$file_url") # Get the correct URL when rebuilding
  399. # one blog entry
  400. if [[ $index == no ]]; then
  401. echo '<!-- entry begin -->' # marks the beginning of the whole post
  402. echo "<h3><a class=\"ablack\" href=\"$file_url\">"
  403. # remove possible <p>'s on the title because of markdown conversion
  404. echo "$title" | sed 's/<\/*p>//g'
  405. echo '</a></h3>'
  406. if [[ -z $timestamp ]]; then
  407. echo "<div class=\"subtitle\">$(LC_ALL=$date_locale date +"$date_format") &mdash; "
  408. else
  409. echo "<div class=\"subtitle\">$(LC_ALL=$date_locale date +"$date_format" --date="$timestamp") &mdash; "
  410. fi
  411. echo "$global_author</div>"
  412. echo '<!-- text begin -->' # This marks the text body, after the title, date...
  413. fi
  414. cat "$content" # Actual content
  415. if [[ $index == no ]]; then
  416. echo -e '\n<!-- text end -->'
  417. twitter "$global_url/$file_url"
  418. echo '<!-- entry end -->' # absolute end of the post
  419. fi
  420. echo '</div>' # content
  421. # Add disqus commments except for index and all_posts pages
  422. [[ $index == no ]] && disqus_body
  423. # page footer
  424. cat .footer.html
  425. # close divs
  426. echo '</div></div>' # divbody and divbodyholder
  427. disqus_footer
  428. echo '</body></html>'
  429. } > "$filename"
  430. }
  431. # Parse the plain text file into an html file
  432. #
  433. # $1 source file name
  434. # $2 (optional) timestamp for the file
  435. # $3 (optional) destination file name
  436. # note that although timestamp is optional, something must be provided at its
  437. # place if destination file name is provided, i.e:
  438. # parse_file source.txt "" destination.html
  439. parse_file() {
  440. # Read for the title and check that the filename is ok
  441. title=""
  442. while IFS='' read -r line; do
  443. if [[ -z $title ]]; then
  444. # remove extra <p> and </p> added by markdown
  445. title=$(echo "$line" | sed 's/<\/*p>//g')
  446. if [[ -n $3 ]]; then
  447. filename=$3
  448. else
  449. filename=$title
  450. [[ -n $convert_filename ]] &&
  451. filename=$(echo "$title" | eval "$convert_filename")
  452. [[ -n $filename ]] ||
  453. filename=$RANDOM # don't allow empty filenames
  454. filename=$filename.html
  455. # Check for duplicate file names
  456. while [[ -f $filename ]]; do
  457. filename=${filename%.html}$RANDOM.html
  458. done
  459. fi
  460. content=$filename.tmp
  461. # Parse possible tags
  462. elif [[ $line == "<p>$template_tags_line_header"* ]]; then
  463. tags=$(echo "$line" | cut -d ":" -f 2- | sed -e 's/<\/p>//g' -e 's/^ *//' -e 's/ *$//' -e 's/, /,/g')
  464. IFS=, read -r -a array <<< "$tags"
  465. echo -n "<p>$template_tags_line_header " >> "$content"
  466. for item in "${array[@]}"; do
  467. echo -n "<a href='$prefix_tags$item.html'>$item</a>, "
  468. done | sed 's/, $/<\/p>/g' >> "$content"
  469. else
  470. echo "$line" >> "$content"
  471. fi
  472. done < "$1"
  473. # Create the actual html page
  474. create_html_page "$content" "$filename" no "$title" "$2"
  475. rm "$content"
  476. }
  477. # Manages the creation of the text file and the parsing to html file
  478. # also the drafts
  479. write_entry() {
  480. test_markdown && fmt=md || fmt=html
  481. f=$2
  482. [[ $2 == -html ]] && fmt=html && f=$3
  483. if [[ -n $f ]]; then
  484. TMPFILE=$f
  485. if [[ ! -f $TMPFILE ]]; then
  486. echo "The file doesn't exist"
  487. delete_includes
  488. exit
  489. fi
  490. # guess format from TMPFILE
  491. extension=${TMPFILE##*.}
  492. [[ $extension == md || $extension == html ]] && fmt=$extension
  493. # but let user override it (`bb.sh post -html file.md`)
  494. [[ $2 == -html ]] && fmt=html
  495. # Test if Markdown is working before re-posting a .md file
  496. if [[ $extension == md ]]; then
  497. test_markdown
  498. if (($? != 0)); then
  499. echo "Markdown is not working, please edit HTML file directly."
  500. exit
  501. fi
  502. fi
  503. else
  504. TMPFILE=.entry-$RANDOM.$fmt
  505. echo -e "Title on this line\n" >> "$TMPFILE"
  506. [[ $fmt == html ]] && cat << EOF >> "$TMPFILE"
  507. <p>The rest of the text file is an <b>html</b> blog post. The process will continue as soon
  508. as you exit your editor.</p>
  509. <p>$template_tags_line_header keep-this-tag-format, tags-are-optional, example</p>
  510. EOF
  511. [[ $fmt == md ]] && cat << EOF >> "$TMPFILE"
  512. The rest of the text file is a **Markdown** blog post. The process will continue
  513. as soon as you exit your editor.
  514. $template_tags_line_header keep-this-tag-format, tags-are-optional, beware-with-underscores-in-markdown, example
  515. EOF
  516. fi
  517. chmod 600 "$TMPFILE"
  518. post_status="E"
  519. filename=""
  520. while [[ $post_status != "p" && $post_status != "P" ]]; do
  521. [[ -n $filename ]] && rm "$filename" # Delete the generated html file, if any
  522. $EDITOR "$TMPFILE"
  523. if [[ $fmt == md ]]; then
  524. html_from_md=$(markdown "$TMPFILE")
  525. parse_file "$html_from_md"
  526. rm "$html_from_md"
  527. else
  528. parse_file "$TMPFILE" # this command sets $filename as the html processed file
  529. fi
  530. chmod 644 "$filename"
  531. [[ -n $preview_url ]] || preview_url=$global_url
  532. echo "To preview the entry, open $preview_url/$filename in your browser"
  533. echo -n "[P]ost this entry, [E]dit again, [D]raft for later? (p/E/d) "
  534. read -r post_status
  535. if [[ $post_status == d || $post_status == D ]]; then
  536. mkdir -p "drafts/"
  537. chmod 700 "drafts/"
  538. title=$(head -n 1 $TMPFILE)
  539. [[ -n $convert_filename ]] && title=$(echo "$title" | eval "$convert_filename")
  540. [[ -n $title ]] || title=$RANDOM
  541. draft=drafts/$title.$fmt
  542. mv "$TMPFILE" "$draft"
  543. chmod 600 "$draft"
  544. rm "$filename"
  545. delete_includes
  546. echo "Saved your draft as '$draft'"
  547. exit
  548. fi
  549. done
  550. if [[ $fmt == md && -n $save_markdown ]]; then
  551. mv "$TMPFILE" "${filename%%.*}.md"
  552. else
  553. rm "$TMPFILE"
  554. fi
  555. chmod 644 "$filename"
  556. echo "Posted $filename"
  557. relevant_tags=$(tags_in_post $filename)
  558. if [[ -n $relevant_tags ]]; then
  559. relevant_posts="$(posts_with_tags $relevant_tags) $filename"
  560. rebuild_tags "$relevant_posts" "$relevant_tags"
  561. fi
  562. }
  563. # Create an index page with all the posts
  564. all_posts() {
  565. echo -n "Creating an index page with all the posts "
  566. contentfile=$archive_index.$RANDOM
  567. while [[ -f $contentfile ]]; do
  568. contentfile=$archive_index.$RANDOM
  569. done
  570. {
  571. echo "<h3>$template_archive_title</h3>"
  572. prev_month=""
  573. while IFS='' read -r i; do
  574. is_boilerplate_file "$i" && continue
  575. echo -n "." 1>&3
  576. # Month headers
  577. month=$(LC_ALL=$date_locale date -r "$i" +"$date_allposts_header")
  578. if [[ $month != "$prev_month" ]]; then
  579. [[ -n $prev_month ]] && echo "</ul>" # Don't close ul before first header
  580. echo "<h4 class='allposts_header'>$month</h4>"
  581. echo "<ul>"
  582. prev_month=$month
  583. fi
  584. # Title
  585. title=$(get_post_title "$i")
  586. echo -n "<li><a href=\"$i\">$title</a> &mdash;"
  587. # Date
  588. date=$(LC_ALL=$date_locale date -r "$i" +"$date_format")
  589. echo " $date</li>"
  590. done < <(ls -t ./*.html)
  591. echo "" 1>&3
  592. echo "</ul>"
  593. echo "<div id=\"all_posts\"><a href=\"./\">$template_archive_index_page</a></div>"
  594. } 3>&1 >"$contentfile"
  595. create_html_page "$contentfile" "$archive_index.tmp" yes "$global_title &mdash; $template_archive_title"
  596. mv "$archive_index.tmp" "$archive_index"
  597. chmod 644 "$archive_index"
  598. rm "$contentfile"
  599. }
  600. # Create an index page with all the tags
  601. all_tags() {
  602. echo -n "Creating an index page with all the tags "
  603. contentfile=$tags_index.$RANDOM
  604. while [[ -f $contentfile ]]; do
  605. contentfile=$tags_index.$RANDOM
  606. done
  607. {
  608. echo "<h3>$template_tags_title</h3>"
  609. echo "<ul>"
  610. for i in ./$prefix_tags*.html; do
  611. [[ -f "$i" ]] || break
  612. echo -n "." 1>&3
  613. nposts=$(grep -c "<\!-- text begin -->" "$i")
  614. tagname=$(echo "$i" | cut -c "$((${#prefix_tags}+3))-" | sed 's/\.html//g')
  615. i=$(clean_filename "$i")
  616. echo "<li><a href=\"$i\">$tagname</a> &mdash; $nposts $template_tags_posts</li>"
  617. done
  618. echo "" 1>&3
  619. echo "</ul>"
  620. echo "<div id=\"all_posts\"><a href=\"./\">$template_archive_index_page</a></div>"
  621. } 3>&1 > "$contentfile"
  622. create_html_page "$contentfile" "$tags_index.tmp" yes "$global_title &mdash; $template_tags_title"
  623. mv "$tags_index.tmp" "$tags_index"
  624. chmod 644 "$tags_index"
  625. rm "$contentfile"
  626. }
  627. # Generate the index.html with the content of the latest posts
  628. rebuild_index() {
  629. echo -n "Rebuilding the index "
  630. newindexfile=$index_file.$RANDOM
  631. contentfile=$newindexfile.content
  632. while [[ -f $newindexfile ]]; do
  633. newindexfile=$index_file.$RANDOM
  634. contentfile=$newindexfile.content
  635. done
  636. # Create the content file
  637. {
  638. n=0
  639. while IFS='' read -r i; do
  640. is_boilerplate_file "$i" && continue;
  641. if ((n >= number_of_index_articles)); then break; fi
  642. if [[ -n $cut_do ]]; then
  643. get_html_file_content 'entry' 'entry' 'cut' <"$i" | awk "/$cut_line/ { print \"<p class=\\\"readmore\\\"><a href=\\\"$i\\\">$template_read_more</a></p>\" ; next } 1"
  644. else
  645. get_html_file_content 'entry' 'entry' <"$i"
  646. fi
  647. echo -n "." 1>&3
  648. n=$(( n + 1 ))
  649. done < <(ls -t ./*.html) # sort by date, newest first
  650. feed=$blog_feed
  651. if [[ -n $global_feedburner ]]; then feed=$global_feedburner; fi
  652. echo "<div id=\"all_posts\"><a href=\"$archive_index\">$template_archive</a> &mdash; <a href=\"$tags_index\">$template_tags_title</a> &mdash; <a href=\"$feed\">$template_subscribe</a></div>"
  653. } 3>&1 >"$contentfile"
  654. echo ""
  655. create_html_page "$contentfile" "$newindexfile" yes "$global_title"
  656. rm "$contentfile"
  657. mv "$newindexfile" "$index_file"
  658. chmod 644 "$index_file"
  659. }
  660. # Finds all tags referenced in one post.
  661. # Accepts either filename as first argument, or post content at stdin
  662. # Prints one line with space-separated tags to stdout
  663. tags_in_post() {
  664. sed -n "/^<p>$template_tags_line_header/{s/^<p>$template_tags_line_header//;s/<[^>]*>//g;s/[ ,]\+/ /g;p;}" "$1" | tr ', ' ' '
  665. }
  666. # Finds all posts referenced in a number of tags.
  667. # Arguments are tags
  668. # Prints one line with space-separated tags to stdout
  669. posts_with_tags() {
  670. (($# < 1)) && return
  671. set -- "${@/#/$prefix_tags}"
  672. set -- "${@/%/.html}"
  673. sed -n '/^<h3><a class="ablack" href="[^"]*">/{s/.*href="\([^"]*\)">.*/\1/;p;}' "$@" 2> /dev/null
  674. }
  675. # Rebuilds tag_*.html files
  676. # if no arguments given, rebuilds all of them
  677. # if arguments given, they should have this format:
  678. # "FILE1 [FILE2 [...]]" "TAG1 [TAG2 [...]]"
  679. # where FILEn are files with posts which should be used for rebuilding tags,
  680. # and TAGn are names of tags which should be rebuilt.
  681. # example:
  682. # rebuild_tags "one_post.html another_article.html" "example-tag another-tag"
  683. # mind the quotes!
  684. rebuild_tags() {
  685. if (($# < 2)); then
  686. # will process all files and tags
  687. files=$(ls -t ./*.html)
  688. all_tags=yes
  689. else
  690. # will process only given files and tags
  691. files=$(printf '%s\n' $1 | sort -u)
  692. files=$(ls -t $files)
  693. tags=$2
  694. fi
  695. echo -n "Rebuilding tag pages "
  696. n=0
  697. if [[ -n $all_tags ]]; then
  698. rm ./"$prefix_tags"*.html &> /dev/null
  699. else
  700. for i in $tags; do
  701. rm "./$prefix_tags$i.html" &> /dev/null
  702. done
  703. fi
  704. # First we will process all files and create temporal tag files
  705. # with just the content of the posts
  706. tmpfile=tmp.$RANDOM
  707. while [[ -f $tmpfile ]]; do tmpfile=tmp.$RANDOM; done
  708. while IFS='' read -r i; do
  709. is_boilerplate_file "$i" && continue;
  710. echo -n "."
  711. if [[ -n $cut_do ]]; then
  712. get_html_file_content 'entry' 'entry' 'cut' <"$i" | awk "/$cut_line/ { print \"<p class=\\\"readmore\\\"><a href=\\\"$i\\\">$template_read_more</a></p>\" ; next } 1"
  713. else
  714. get_html_file_content 'entry' 'entry' <"$i"
  715. fi >"$tmpfile"
  716. for tag in $(tags_in_post "$i"); do
  717. if [[ -n $all_tags || " $tags " == *" $tag "* ]]; then
  718. cat "$tmpfile" >> "$prefix_tags$tag".tmp.html
  719. fi
  720. done
  721. done <<< "$files"
  722. rm "$tmpfile"
  723. # Now generate the tag files with headers, footers, etc
  724. while IFS='' read -r i; do
  725. tagname=$(echo "$i" | cut -c "$((${#prefix_tags}+3))-" | sed 's/\.tmp\.html//g')
  726. create_html_page "$i" "$prefix_tags$tagname.html" yes "$global_title &mdash; $template_tag_title \"$tagname\""
  727. rm "$i"
  728. done < <(ls -t ./"$prefix_tags"*.tmp.html 2>/dev/null)
  729. echo
  730. }
  731. # Return the post title
  732. #
  733. # $1 the html file
  734. get_post_title() {
  735. awk '/<h3><a class="ablack" href=".+">/, /<\/a><\/h3>/{if (!/<h3><a class="ablack" href=".+">/ && !/<\/a><\/h3>/) print}' "$1"
  736. }
  737. # Displays a list of the posts
  738. list_posts() {
  739. ls ./*.html &> /dev/null
  740. (($? != 0)) && echo "No posts yet. Use 'bb.sh post' to create one" && return
  741. lines=""
  742. n=1
  743. while IFS='' read -r i; do
  744. is_boilerplate_file "$i" && continue
  745. line="$n # $(get_post_title "$i") # $(LC_ALL=$date_locale date -r "$i" +"$date_format")"
  746. lines+=$line\\n
  747. n=$(( n + 1 ))
  748. done < <(ls -t ./*.html)
  749. echo -e "$lines" | column -t -s "#"
  750. }
  751. # Generate the feed file
  752. make_rss() {
  753. echo -n "Making RSS "
  754. rssfile=$blog_feed.$RANDOM
  755. while [[ -f $rssfile ]]; do rssfile=$blog_feed.$RANDOM; done
  756. {
  757. pubdate=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z")
  758. echo '<?xml version="1.0" encoding="UTF-8" ?>'
  759. echo '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">'
  760. echo "<channel><title>$global_title</title><link>$global_url</link>"
  761. echo "<description>$global_description</description><language>en</language>"
  762. echo "<lastBuildDate>$pubdate</lastBuildDate>"
  763. echo "<pubDate>$pubdate</pubDate>"
  764. echo "<atom:link href=\"$global_url/$blog_feed\" rel=\"self\" type=\"application/rss+xml\" />"
  765. n=0
  766. while IFS='' read -r i; do
  767. is_boilerplate_file "$i" && continue
  768. ((n >= number_of_feed_articles)) && break # max 10 items
  769. echo -n "." 1>&3
  770. echo '<item><title>'
  771. get_post_title "$i"
  772. echo '</title><description><![CDATA['
  773. get_html_file_content 'text' 'entry' $cut_do <"$i"
  774. echo "]]></description><link>$global_url/$(clean_filename "$i")</link>"
  775. echo "<guid>$global_url/$i</guid>"
  776. echo "<dc:creator>$global_author</dc:creator>"
  777. echo "<pubDate>$(LC_ALL=C date -r "$i" +"%a, %d %b %Y %H:%M:%S %z")</pubDate></item>"
  778. n=$(( n + 1 ))
  779. done < <(ls -t ./*.html)
  780. echo '</channel></rss>'
  781. } 3>&1 >"$rssfile"
  782. echo ""
  783. mv "$rssfile" "$blog_feed"
  784. chmod 644 "$blog_feed"
  785. }
  786. # generate headers, footers, etc
  787. create_includes() {
  788. {
  789. echo "<h1 class=\"nomargin\"><a class=\"ablack\" href=\"$global_url\">$global_title</a></h1>"
  790. echo "<div id=\"description\">$global_description</div>"
  791. } > ".title.html"
  792. if [[ -f $header_file ]]; then cp "$header_file" .header.html
  793. else {
  794. echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
  795. echo '<html xmlns="http://www.w3.org/1999/xhtml"><head>'
  796. echo '<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />'
  797. echo '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
  798. printf '<link rel="stylesheet" href="%s" type="text/css" />\n' "${css_include[@]}"
  799. if [[ -z $global_feedburner ]]; then
  800. echo "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"$template_subscribe_browser_button\" href=\"$blog_feed\" />"
  801. else
  802. echo "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"$template_subscribe_browser_button\" href=\"$global_feedburner\" />"
  803. fi
  804. } > ".header.html"
  805. fi
  806. if [[ -f $footer_file ]]; then cp "$footer_file" .footer.html
  807. else {
  808. protected_mail=${global_email//@/&#64;}
  809. protected_mail=${protected_mail//./&#46;}
  810. echo "<div id=\"footer\">$global_license <a href=\"$global_author_url\">$global_author</a> &mdash; <a href=\"mailto:$protected_mail\">$protected_mail</a><br/>"
  811. echo 'Generated with <a href="https://github.com/cfenollosa/bashblog">bashblog</a>, a single bash script to easily create blogs like this one</div>'
  812. } >> ".footer.html"
  813. fi
  814. }
  815. # Delete the temporarily generated include files
  816. delete_includes() {
  817. rm ".title.html" ".footer.html" ".header.html"
  818. }
  819. # Create the css file from scratch
  820. create_css() {
  821. # To avoid overwriting manual changes. However it is recommended that
  822. # this function is modified if the user changes the blog.css file
  823. [[ -n $css_include ]] && return || css_include=('main.css' 'blog.css')
  824. if [[ ! -f blog.css ]]; then
  825. # blog.css directives will be loaded after main.css and thus will prevail
  826. echo '#title{font-size: x-large;}
  827. a.ablack{color:black !important;}
  828. li{margin-bottom:8px;}
  829. ul,ol{margin-left:24px;margin-right:24px;}
  830. #all_posts{margin-top:24px;text-align:center;}
  831. .subtitle{font-size:small;margin:12px 0px;}
  832. .content p{margin-left:24px;margin-right:24px;}
  833. h1{margin-bottom:12px !important;}
  834. #description{font-size:large;margin-bottom:12px;}
  835. h3{margin-top:42px;margin-bottom:8px;}
  836. h4{margin-left:24px;margin-right:24px;}
  837. #twitter{line-height:20px;vertical-align:top;text-align:right;font-style:italic;color:#333;margin-top:24px;font-size:14px;}' > blog.css
  838. fi
  839. # If there is a style.css from the parent page (i.e. some landing page)
  840. # then use it. This directive is here for compatibility with my own
  841. # home page. Feel free to edit it out, though it doesn't hurt
  842. if [[ -f ../style.css ]] && [[ ! -f main.css ]]; then
  843. ln -s "../style.css" "main.css"
  844. elif [[ ! -f main.css ]]; then
  845. echo 'body{font-family:Georgia,"Times New Roman",Times,serif;margin:0;padding:0;background-color:#F3F3F3;}
  846. #divbodyholder{padding:5px;background-color:#DDD;width:100%;max-width:874px;margin:24px auto;}
  847. #divbody{border:solid 1px #ccc;background-color:#fff;padding:0px 48px 24px 48px;top:0;}
  848. .headerholder{background-color:#f9f9f9;border-top:solid 1px #ccc;border-left:solid 1px #ccc;border-right:solid 1px #ccc;}
  849. .header{width:100%;max-width:800px;margin:0px auto;padding-top:24px;padding-bottom:8px;}
  850. .content{margin-bottom:5%;}
  851. .nomargin{margin:0;}
  852. .description{margin-top:10px;border-top:solid 1px #666;padding:10px 0;}
  853. h3{font-size:20pt;width:100%;font-weight:bold;margin-top:32px;margin-bottom:0;}
  854. .clear{clear:both;}
  855. #footer{padding-top:10px;border-top:solid 1px #666;color:#333333;text-align:center;font-size:small;font-family:"Courier New","Courier",monospace;}
  856. a{text-decoration:none;color:#003366 !important;}
  857. a:visited{text-decoration:none;color:#336699 !important;}
  858. blockquote{background-color:#f9f9f9;border-left:solid 4px #e9e9e9;margin-left:12px;padding:12px 12px 12px 24px;}
  859. blockquote img{margin:12px 0px;}
  860. blockquote iframe{margin:12px 0px;}' > main.css
  861. fi
  862. }
  863. # Regenerates all the single post entries, keeping the post content but modifying
  864. # the title, html structure, etc
  865. rebuild_all_entries() {
  866. echo -n "Rebuilding all entries "
  867. for i in ./*.html; do # no need to sort
  868. is_boilerplate_file "$i" && continue;
  869. contentfile=.tmp.$RANDOM
  870. while [[ -f $contentfile ]]; do contentfile=.tmp.$RANDOM; done
  871. echo -n "."
  872. # Get the title and entry, and rebuild the html structure from scratch (divs, title, description...)
  873. title=$(get_post_title "$i")
  874. get_html_file_content 'text' 'text' <"$i" >> "$contentfile"
  875. # Original post timestamp
  876. timestamp=$(LC_ALL=C date -r "$i" +"%a, %d %b %Y %H:%M:%S %z" )
  877. create_html_page "$contentfile" "$i.rebuilt" no "$title" "$timestamp"
  878. # keep the original timestamp!
  879. timestamp=$(LC_ALL=C date -r "$i" +'%Y%m%d%H%M')
  880. mv "$i.rebuilt" "$i"
  881. chmod 644 "$i"
  882. touch -t "$timestamp" "$i"
  883. rm "$contentfile"
  884. done
  885. echo ""
  886. }
  887. # Displays the help
  888. usage() {
  889. echo "$global_software_name v$global_software_version"
  890. echo "Usage: $0 command [filename]"
  891. echo ""
  892. echo "Commands:"
  893. echo " post [-html] [filename] insert a new blog post, or the filename of a draft to continue editing it"
  894. echo " it tries to use markdown by default, and falls back to HTML if it's not available."
  895. echo " use '-html' to override it and edit the post as HTML even when markdown is available"
  896. echo " edit [-n|-f] [filename] edit an already published .html or .md file. **NEVER** edit manually a published .html file,"
  897. echo " always use this function as it keeps internal data and rebuilds the blog"
  898. echo " use '-n' to give the file a new name, if title was changed"
  899. echo " use '-f' to edit full html file, instead of just text part (also preserves name)"
  900. echo " delete [filename] deletes the post and rebuilds the blog"
  901. echo " rebuild regenerates all the pages and posts, preserving the content of the entries"
  902. echo " reset deletes everything except this script. Use with a lot of caution and back up first!"
  903. echo " list list all posts"
  904. echo ""
  905. echo "For more information please open $0 in a code editor and read the header and comments"
  906. }
  907. # Delete all generated content, leaving only this script
  908. reset() {
  909. echo "Are you sure you want to delete all blog entries? Please write \"Yes, I am!\" "
  910. read -r line
  911. if [[ $line == "Yes, I am!" ]]; then
  912. rm .*.html ./*.html ./*.css ./*.rss &> /dev/null
  913. echo
  914. echo "Deleted all posts, stylesheets and feeds."
  915. echo "Kept your old '.backup.tar.gz' just in case, please delete it manually if needed."
  916. else
  917. echo "Phew! You dodged a bullet there. Nothing was modified."
  918. fi
  919. }
  920. # Detects if GNU date is installed
  921. date_version_detect() {
  922. date --version >/dev/null 2>&1
  923. if (($? != 0)); then
  924. # date utility is BSD. Test if gdate is installed
  925. if gdate --version >/dev/null 2>&1 ; then
  926. date() {
  927. gdate "$@"
  928. }
  929. else
  930. # BSD date
  931. date() {
  932. if [[ $1 == -r ]]; then
  933. # Fall back to using stat for 'date -r'
  934. format=${3//+/}
  935. stat -f "%Sm" -t "$format" "$2"
  936. elif [[ $2 == --date* ]]; then
  937. # convert between dates using BSD date syntax
  938. command date -j -f "%a, %d %b %Y %H:%M:%S %z" "${2#--date=}" "$1"
  939. else
  940. # acceptable format for BSD date
  941. command date -j "$@"
  942. fi
  943. }
  944. fi
  945. fi
  946. }
  947. # Main function
  948. # Encapsulated on its own function for readability purposes
  949. #
  950. # $1 command to run
  951. # $2 file name of a draft to continue editing (optional)
  952. do_main() {
  953. # Detect if using BSD date or GNU date
  954. date_version_detect
  955. # Load default configuration, then override settings with the config file
  956. global_variables
  957. [[ -f $global_config ]] && source "$global_config" &> /dev/null
  958. global_variables_check
  959. # Check for $EDITOR
  960. [[ -z $EDITOR ]] &&
  961. echo "Please set your \$EDITOR environment variable" && exit
  962. # Check for validity of argument
  963. [[ $1 != "reset" && $1 != "post" && $1 != "rebuild" && $1 != "list" && $1 != "edit" && $1 != "delete" ]] &&
  964. usage && exit
  965. [[ $1 == list ]] &&
  966. list_posts && exit
  967. if [[ $1 == edit ]]; then
  968. if (($# < 2)) || [[ ! -f ${!#} ]]; then
  969. echo "Please enter a valid .md or .html file to edit"
  970. exit
  971. fi
  972. fi
  973. # Test for existing html files
  974. if ls ./*.html &> /dev/null; then
  975. # We're going to back up just in case
  976. tar cfz ".backup.tar.gz" *.html &&
  977. chmod 600 ".backup.tar.gz"
  978. elif [[ $1 == rebuild ]]; then
  979. echo "Can't find any html files, nothing to rebuild"
  980. exit
  981. fi
  982. # Keep first backup of this day containing yesterday's version of the blog
  983. [[ ! -f .yesterday.tar.gz || $(date -r .yesterday.tar.gz +'%d') != "$(date +'%d')" ]] &&
  984. cp .backup.tar.gz .yesterday.tar.gz &> /dev/null
  985. [[ $1 == reset ]] &&
  986. reset && exit
  987. create_css
  988. create_includes
  989. [[ $1 == post ]] && write_entry "$@"
  990. [[ $1 == rebuild ]] && rebuild_all_entries && rebuild_tags
  991. [[ $1 == delete ]] && rm "$2" &> /dev/null && rebuild_tags
  992. if [[ $1 == edit ]]; then
  993. if [[ $2 == -n ]]; then
  994. edit "$3"
  995. elif [[ $2 == -f ]]; then
  996. edit "$3" full
  997. else
  998. edit "$2" keep
  999. fi
  1000. fi
  1001. rebuild_index
  1002. all_posts
  1003. all_tags
  1004. make_rss
  1005. delete_includes
  1006. }
  1007. #
  1008. # MAIN
  1009. # Do not change anything here. If you want to modify the code, edit do_main()
  1010. #
  1011. do_main "$@"
  1012. # vim: set shiftwidth=4 tabstop=4 expandtab: