bb.sh 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. #!/usr/bin/env bash
  2. # BashBlog, a simple blog system written in a single bash script
  3. # Author: Carles Fenollosa <carles.fenollosa@bsc.es>, 2011-2012
  4. #########################################################################################
  5. #
  6. # README
  7. #
  8. #########################################################################################
  9. #
  10. # This is a very basic blog system
  11. #
  12. # Basically it asks the user to create a text file, then converts it into a .html file
  13. # and then rebuilds the index.html and feed.rss.
  14. #
  15. # Comments are not supported.
  16. #
  17. # This script is standalone, it doesn't require any other file to run
  18. #
  19. # Files that this script generates:
  20. # - main.css (inherited from my web page) and blog.css (blog-specific stylesheet)
  21. # - one .html for each post
  22. # - index.html (regenerated each run)
  23. # - feed.rss (regenerated each run)
  24. # - all_posts.html (regenerated each run)
  25. # - it also generates temporal files, which are removed afterwards
  26. #
  27. # It generates valid html and rss files, so keep care to use valid xhtml when editing a post
  28. #
  29. # There are many loops which iterate on '*.html' so make sure that the only html files
  30. # on this folder are the blog entries and index.html and all_posts.html. Drafts must go
  31. # into drafts/ and any other *.html file should be moved out of the way
  32. #########################################################################################
  33. #
  34. # LICENSE
  35. #
  36. #########################################################################################
  37. #
  38. # This program is free software: you can redistribute it and/or modify
  39. # it under the terms of the GNU General Public License as published by
  40. # the Free Software Foundation, either version 3 of the License, or
  41. # (at your option) any later version.
  42. #
  43. # This program is distributed in the hope that it will be useful,
  44. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  45. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  46. # GNU General Public License for more details.
  47. #
  48. # You should have received a copy of the GNU General Public License
  49. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  50. #########################################################################################
  51. #
  52. # CHANGELOG
  53. #
  54. #########################################################################################
  55. #
  56. # 1.5.1 Misc bugfixes and parameter checks
  57. # 1.5 Durad Radojicic refactored some code and added flexibility and i18n
  58. # 1.4.2 Now issues are handled at Github
  59. # 1.4.1 Some code refactoring
  60. # 1.4 Using twitter for comments, improved 'rebuild' command
  61. # 1.3 'edit' command
  62. # 1.2.2 Feedburner support
  63. # 1.2.1 Fixed the timestamps bug
  64. # 1.2 'list' command
  65. # 1.1 Draft and preview support
  66. # 1.0 Read http://is.gd/Bkdoru
  67. #########################################################################################
  68. #
  69. # CODE
  70. #
  71. #########################################################################################
  72. #
  73. # As usual with bash scripts, scroll all the way to the bottom for the main routine
  74. # All other functions are declared above main.
  75. # Global variables
  76. # It is recommended to perform a 'rebuild' after changing any of this in the code
  77. # Config file. Use instead of this function if you want to avoid merges in VCS
  78. global_config=".config"
  79. global_variables() {
  80. global_software_name="BashBlog"
  81. global_software_version="1.5.1"
  82. # Blog title
  83. global_title="My fancy blog"
  84. # The typical subtitle for each blog
  85. global_description="A blog about turtles and carrots"
  86. # The public base URL for this blog
  87. global_url="http://example.com/blog"
  88. # Your name
  89. global_author="John Smith"
  90. # You can use twitter or facebook or anything for global_author_url
  91. global_author_url="http://twitter.com/example"
  92. # Your email
  93. global_email="john@smith.com"
  94. # CC by-nc-nd is a good starting point, you can change this to "&copy;" for Copyright
  95. global_license="CC by-nc-nd"
  96. # If you have a Google Analytics ID (UA-XXXXX), put it here.
  97. # If left empty (i.e. "") Analytics will be disabled
  98. global_analytics=""
  99. # Leave this empty (i.e. "") if you don't want to use feedburner,
  100. # or change it to your own URL
  101. global_feedburner=""
  102. # Leave these empty if you don't want to use twitter for comments
  103. global_twitter="true"
  104. global_twitter_username="example"
  105. # Blog generated files
  106. # index page of blog (it is usually good to use "index.html" here)
  107. index_file="index.html"
  108. number_of_index_articles="8"
  109. # global archive
  110. archive_index="all_posts.html"
  111. # feed file (rss in this case)
  112. blog_feed="feed.rss"
  113. number_of_feed_articles="10"
  114. # Localization and i18n
  115. # "Comments?" (used in twitter link after every post)
  116. template_comments="Comments?"
  117. # "View more posts" (used on bottom of index page as link to archive)
  118. template_archive="View more posts"
  119. # "Back to the index page" (used on archive page, it is link to blog index)
  120. template_archive_index_page="Back to the index page"
  121. # "Subscribe" (used on bottom of index page, it is link to RSS feed)
  122. template_subscribe="Subscribe"
  123. # "Subscribe to this page..." (used as text for browser feed button that is embedded to html)
  124. template_subscribe_browser_button="Subscribe to this page..."
  125. # "Tweet" (used as twitter text button for posting to twitter)
  126. template_twitter_button="Tweet"
  127. # The locale to use for the dates displayed on screen (not for the timestamps)
  128. date_format="%B %d, %Y"
  129. date_locale="C"
  130. # Date options which work on both BSD and GNU date variants
  131. date_R='+%a, %d %h %Y %H:%M:%S %z'
  132. }
  133. # Prints the required google analytics code
  134. google_analytics() {
  135. if [ "$global_analytics" == "" ]; then return; fi
  136. echo "<script type=\"text/javascript\">
  137. var _gaq = _gaq || [];
  138. _gaq.push(['_setAccount', '"$global_analytics"']);
  139. _gaq.push(['_trackPageview']);
  140. (function() {
  141. var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  142. ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  143. var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  144. })();
  145. </script>"
  146. }
  147. # Edit an existing, published .html file while keeping its original timestamp
  148. # Please note that this function does not automatically republish anything, as
  149. # it is usually called from 'main'.
  150. #
  151. # 'edit' is kind of an advanced function, as it leaves to the user the responsibility
  152. # of editing an html file
  153. #
  154. # $1 the file to edit
  155. edit() {
  156. # timestamp="$(date -r $1 +'%Y%m%d%k%M')"
  157. timestamp="$(stat -f "%Sm" -t '%Y%m%d%k%M' $1)"
  158. $EDITOR "$1"
  159. touch -t $timestamp "$1"
  160. }
  161. # Adds the code needed by the twitter button
  162. #
  163. # $1 the post URL
  164. twitter() {
  165. echo "<p id='twitter'>$template_comments &nbsp;"
  166. echo "<a href=\"https://twitter.com/share\" class=\"twitter-share-button\" data-text=\"&lt;Type your comment here but please leave the URL so that other people can follow the comments&gt;\" data-url=\"$1\""
  167. if [ "$global_twitter_username" != "" ]; then
  168. echo " data-via=\"$global_twitter_username\""
  169. fi
  170. 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>"
  171. echo "</p>"
  172. }
  173. # Adds all the bells and whistles to format the html page
  174. # Every blog post is marked with a <!-- entry begin --> and <!-- entry end -->
  175. # which is parsed afterwards in the other functions. There is also a marker
  176. # <!-- text begin --> to determine just the beginning of the text body of the post
  177. #
  178. # $1 a file with the body of the content
  179. # $2 the output file
  180. # $3 "yes" if we want to generate the index.html,
  181. # "no" to insert new blog posts
  182. # $4 title for the html header
  183. # $5 original blog timestamp
  184. create_html_page() {
  185. content="$1"
  186. filename="$2"
  187. index="$3"
  188. title="$4"
  189. timestamp="$5"
  190. # Create the actual blog post
  191. # html, head
  192. cat ".header.html" > "$filename"
  193. echo "<title>$title</title>" >> "$filename"
  194. google_analytics >> "$filename"
  195. echo "</head><body>" >> "$filename"
  196. # body divs
  197. echo '<div id="divbodyholder">' >> "$filename"
  198. echo '<div class="headerholder"><div class="header">' >> "$filename"
  199. # blog title
  200. echo '<div id="title">' >> "$filename"
  201. cat .title.html >> "$filename"
  202. echo '</div></div></div>' >> "$filename" # title, header, headerholder
  203. echo '<div id="divbody"><div class="content">' >> "$filename"
  204. file_url="$(sed 's/.rebuilt//g' <<< $filename)" # Get the correct URL when rebuilding
  205. # one blog entry
  206. if [ "$index" == "no" ]; then
  207. echo '<!-- entry begin -->' >> "$filename" # marks the beginning of the whole post
  208. echo '<h3><a class="ablack" href="'$global_url/$file_url'">' >> "$filename"
  209. echo "$title" >> "$filename"
  210. echo '</a></h3>' >> "$filename"
  211. if [ "$timestamp" == "" ]; then
  212. # echo '<div class="subtitle">'$(LC_ALL=date_locale date +"$date_format")' &mdash; ' >> "$filename"
  213. echo '<div class="subtitle">'$(date "$date_R")' &mdash; ' >> "$filename"
  214. else
  215. # echo '<div class="subtitle">'$(LC_ALL=date_locale date +"$date_format" --date="$timestamp") ' &mdash; ' >> "$filename"
  216. echo '<div class="subtitle">'"$timestamp"' &mdash; ' >> "$filename"
  217. fi
  218. echo "$global_author</div>" >> "$filename"
  219. echo '<!-- text begin -->' >> "$filename" # This marks the text body, after the title, date...
  220. fi
  221. cat "$content" >> "$filename" # Actual content
  222. if [ "$index" == "no" ]; then
  223. echo '<!-- text end -->' >> "$filename"
  224. if [ "$global_twitter" == "true" ]; then
  225. twitter "$global_url/$file_url" >> "$filename"
  226. fi
  227. echo '<!-- entry end -->' >> "$filename" # absolute end of the post
  228. fi
  229. echo '</div>' >> "$filename" # content
  230. # page footer
  231. cat .footer.html >> "$filename"
  232. # close divs
  233. echo '</div></div>' >> "$filename" # divbody and divbodyholder
  234. echo '</body></html>' >> "$filename"
  235. }
  236. # Parse the plain text file into an html file
  237. parse_file() {
  238. # Read for the title and check that the filename is ok
  239. title=""
  240. while read line; do
  241. if [ "$title" == "" ]; then
  242. title="$line"
  243. filename="$(echo $title | tr [:upper:] [:lower:])"
  244. filename="$(echo $filename | sed 's/\ /-/g')"
  245. filename="$(echo $filename | tr -dc '[:alnum:]-')" # html likes alphanumeric
  246. filename="$filename.html"
  247. content="$filename.tmp"
  248. # Check for duplicate file names
  249. while [ -f "$filename" ]; do
  250. suffix="$RANDOM"
  251. filename="$(echo $filename | sed 's/\.html/'$suffix'\.html/g')"
  252. done
  253. else
  254. echo "$line" >> "$content"
  255. fi
  256. done < "$TMPFILE"
  257. # Create the actual html page
  258. create_html_page "$content" "$filename" no "$title"
  259. rm "$content"
  260. }
  261. # Manages the creation of the text file and the parsing to html file
  262. # also the drafts
  263. write_entry() {
  264. if [ "$1" != "" ]; then
  265. TMPFILE="$1"
  266. if [ ! -f "$TMPFILE" ]; then
  267. echo "The file doesn't exist"
  268. delete_includes
  269. exit
  270. fi
  271. else
  272. TMPFILE=".entry-$RANDOM.html"
  273. echo "Title on this line" >> "$TMPFILE"
  274. echo "" >> "$TMPFILE"
  275. echo "<p>The rest of the text file is an <b>html</b> blog post. The process" >> "$TMPFILE"
  276. echo "will continue as soon as you exit your editor</p>" >> "$TMPFILE"
  277. fi
  278. chmod 600 "$TMPFILE"
  279. post_status="E"
  280. while [ "$post_status" != "p" ] && [ "$post_status" != "P" ]; do
  281. $EDITOR "$TMPFILE"
  282. parse_file "$TMPFILE" # this command sets $filename as the html processed file
  283. chmod 600 "$filename"
  284. echo -n "Preview? (Y/n) "
  285. read p
  286. if [ "$p" != "n" ] && [ "$p" != "N" ]; then
  287. chmod 644 "$filename"
  288. echo "Open $global_url/$filename in your browser"
  289. fi
  290. echo -n "[P]ost this entry, [E]dit again, [D]raft for later? (p/E/d) "
  291. read post_status
  292. if [ "$post_status" == "d" ] || [ "$post_status" == "D" ]; then
  293. mkdir -p "drafts/"
  294. chmod 700 "drafts/"
  295. title="$(head -n 1 $TMPFILE)"
  296. title="$(echo $title | tr [:upper:] [:lower:])"
  297. title="$(echo $title | sed 's/\ /-/g')"
  298. title="$(echo $title | tr -dc '[:alnum:]-')"
  299. draft="drafts/$title.html"
  300. while [ -f "$draft" ]; do draft="drafts/$title-$RANDOM.html"; done
  301. mv "$TMPFILE" "$draft"
  302. chmod 600 "$draft"
  303. rm "$filename"
  304. delete_includes
  305. echo "Saved your draft as '$draft'"
  306. exit
  307. fi
  308. if [ "$post_status" == "e" ] || [ "$post_status" == "E" ]; then
  309. rm "$filename" # Delete the html file as it will be generated again
  310. fi
  311. done
  312. rm "$TMPFILE"
  313. chmod 644 "$filename"
  314. echo "Posted $filename"
  315. }
  316. # Create an index page with all the posts
  317. all_posts() {
  318. echo -n "Creating an index page with all the posts "
  319. contentfile="$archive_index.$RANDOM"
  320. while [ -f "$contentfile" ]; do
  321. contentfile="$archive_index.$RANDOM"
  322. done
  323. echo "<h3>All posts</h3>" >> "$contentfile"
  324. echo "<ul>" >> "$contentfile"
  325. for i in $(ls -t *.html); do
  326. if [ "$i" == "$index_file" ] || [ "$i" == "$archive_index" ]; then continue; fi
  327. echo -n "."
  328. # Title
  329. title="$(awk '/<h3><a class="ablack" href=".+">/, /<\/a><\/h3>/{if (!/<h3><a class="ablack" href=".+">/ && !/<\/a><\/h3>/) print}' $i)"
  330. echo -n '<li><a href="'$global_url/$i'">'$title'</a> &mdash;' >> "$contentfile"
  331. # Date
  332. #date="$(LC_ALL=date_locale date -r "$i" +"$date_format")"
  333. date="$(stat -f "%Sm" -t "%a, %d %h %Y %H:%M:%S %z")"
  334. echo " $date</li>" >> "$contentfile"
  335. done
  336. echo ""
  337. echo "</ul>" >> "$contentfile"
  338. echo '<div id="all_posts"><a href="'$global_url'">'$template_archive_index_page'</a></div>' >> "$contentfile"
  339. create_html_page "$contentfile" "$archive_index.tmp" yes "$global_title &mdash; All posts"
  340. mv "$archive_index.tmp" "$archive_index"
  341. chmod 644 "$archive_index"
  342. rm "$contentfile"
  343. }
  344. # Generate the index.html with the content of the latest posts
  345. rebuild_index() {
  346. echo -n "Rebuilding the index "
  347. newindexfile="$index_file.$RANDOM"
  348. contentfile="$newindexfile.content"
  349. while [ -f "$newindexfile" ]; do
  350. newindexfile="$index_file.$RANDOM"
  351. contentfile="$newindexfile.content"
  352. done
  353. # Create the content file
  354. n=0
  355. for i in $(ls -t *.html); do # sort by date, newest first
  356. if [ "$i" == "$index_file" ] || [ "$i" == "$archive_index" ]; then continue; fi
  357. if [ "$n" -ge "$number_of_index_articles" ]; then break; fi
  358. awk '/<!-- entry begin -->/, /<!-- entry end -->/' "$i" >> "$contentfile"
  359. echo -n "."
  360. n=$(( $n + 1 ))
  361. done
  362. if [ "$global_feedburner" == "" ]; then
  363. echo '<div id="all_posts"><a href="'$archive_index'">View more posts</a> &mdash; <a href="'$blog_feed'">'$template_subscribe'</a></div>' >> "$contentfile"
  364. else
  365. echo '<div id="all_posts"><a href="'$archive_index'">'$template_archive'</a> &mdash; <a href="'$global_feedburner'">Subscribe</a></div>' >> "$contentfile"
  366. fi
  367. echo ""
  368. create_html_page "$contentfile" "$newindexfile" yes "$global_title"
  369. rm "$contentfile"
  370. mv "$newindexfile" "$index_file"
  371. chmod 644 "$index_file"
  372. }
  373. # Displays a list of the posts
  374. list_posts() {
  375. ls *.html &> /dev/null
  376. if [[ $? -ne 0 ]]; then
  377. echo "No posts yet. Use 'bb.sh post' to create one"
  378. return
  379. fi
  380. lines=""
  381. n=1
  382. for i in $(ls -t *.html); do
  383. if [ "$i" == "$index_file" ] || [ "$i" == "$archive_index" ]; then continue; fi
  384. line="$n # $(awk '/<h3><a class="ablack" href=".+">/, /<\/a><\/h3>/{if (!/<h3><a class="ablack" href=".+">/ && !/<\/a><\/h3>/) print}' $i) # $(stat -f "%Sm" -t "%a, %d %h %Y %H:%M:%S %z" $i)"
  385. lines="${lines}""$line""\n" # Weird stuff needed for the newlines
  386. n=$(( $n + 1 ))
  387. done
  388. echo -e "$lines" | column -t -s "#"
  389. }
  390. # Generate the feed file
  391. make_rss() {
  392. echo -n "Making RSS "
  393. rssfile="$blog_feed.$RANDOM"
  394. while [ -f "$rssfile" ]; do rssfile="$blog_feed.$RANDOM"; done
  395. echo '<?xml version="1.0" encoding="UTF-8" ?>' >> "$rssfile"
  396. echo '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">' >> "$rssfile"
  397. echo '<channel><title>'$global_title'</title><link>'$global_url'</link>' >> "$rssfile"
  398. echo '<description>'$global_description'</description><language>en</language>' >> "$rssfile"
  399. echo '<lastBuildDate>'$(date "$date_R")'</lastBuildDate>' >> "$rssfile"
  400. echo '<pubDate>'$(date "$date_R")'</pubDate>' >> "$rssfile"
  401. echo '<atom:link href="'$global_url/$blog_feed'" rel="self" type="application/rss+xml" />' >> "$rssfile"
  402. n=0
  403. for i in $(ls -t *.html); do
  404. if [ "$i" == "$index_file" ] || [ "$i" == "$archive_index" ]; then continue; fi
  405. if [ "$n" -ge "$number_of_feed_articles" ]; then break; fi # max 10 items
  406. echo -n "."
  407. echo '<item><title>' >> "$rssfile"
  408. echo "$(awk '/<h3><a class="ablack" href=".+">/, /<\/a><\/h3>/{if (!/<h3><a class="ablack" href=".+">/ && !/<\/a><\/h3>/) print}' $i)" >> "$rssfile"
  409. echo '</title><description><![CDATA[' >> "$rssfile"
  410. echo "$(awk '/<!-- text begin -->/, /<!-- entry end -->/{if (!/<!-- text begin -->/ && !/<!-- entry end -->/) print}' $i)" >> "$rssfile"
  411. echo "]]></description><link>$global_url/$i</link>" >> "$rssfile"
  412. echo "<guid>$global_url/$i</guid>" >> "$rssfile"
  413. echo "<dc:creator>$global_author</dc:creator>" >> "$rssfile"
  414. #echo '<pubDate>'$(date -r "$i" -R)'</pubDate></item>' >> "$rssfile"
  415. echo '<pubDate>'$(stat -f "%Sm" -t "%a, %d %h %Y %H:%M:%S %z" "$i" )'</pubDate></item>' >> "$rssfile"
  416. n=$(( $n + 1 ))
  417. done
  418. echo '</channel></rss>' >> "$rssfile"
  419. echo ""
  420. mv "$rssfile" "$blog_feed"
  421. chmod 644 "$blog_feed"
  422. }
  423. # generate headers, footers, etc
  424. create_includes() {
  425. echo '<h1 class="nomargin"><a class="ablack" href="'$global_url'">'$global_title'</a></h1>' > ".title.html"
  426. echo '<div id="description">'$global_description'</div>' >> ".title.html"
  427. echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' > ".header.html"
  428. echo '<html xmlns="http://www.w3.org/1999/xhtml"><head>' >> ".header.html"
  429. echo '<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />' >> ".header.html"
  430. echo '<link rel="stylesheet" href="main.css" type="text/css" />' >> ".header.html"
  431. echo '<link rel="stylesheet" href="blog.css" type="text/css" />' >> ".header.html"
  432. if [ "$global_feedburner" == "" ]; then
  433. echo '<link rel="alternate" type="application/rss+xml" title="'$template_subscribe_browser_button'" href="'$blog_feed'" />' >> ".header.html"
  434. else
  435. echo '<link rel="alternate" type="application/rss+xml" title="'$template_subscribe_browser_button'" href="'$global_feedburner'" />' >> ".header.html"
  436. fi
  437. protected_mail="$(echo "$global_email" | sed 's/@/\&#64;/g' | sed 's/\./\&#46;/g')"
  438. echo '<div id="footer">'$global_license '<a href="'$global_author_url'">'$global_author'</a> &mdash; <a href="mailto:'$protected_mail'">'$protected_mail'</a></div>' >> ".footer.html"
  439. }
  440. # Delete the temporarily generated include files
  441. delete_includes() {
  442. rm ".title.html" ".footer.html" ".header.html"
  443. }
  444. # Create the css file from scratch
  445. create_css() {
  446. # To avoid overwriting manual changes. However it is recommended that
  447. # this function is modified if the user changes the blog.css file
  448. if [ ! -f "blog.css" ]; then
  449. # blog.css directives will be loaded after main.css and thus will prevail
  450. echo '#title{font-size: x-large;}
  451. a.ablack{color:black !important;}
  452. li{margin-bottom:8px;}
  453. ul,ol{margin-left:24px;margin-right:24px;}
  454. #all_posts{margin-top:24px;text-align:center;}
  455. .subtitle{font-size:small;margin:12px 0px;}
  456. .content p{margin-left:24px;margin-right:24px;}
  457. h1{margin-bottom:12px !important;}
  458. #description{font-size:large;margin-bottom:12px;}
  459. h3{margin-top:42px;margin-bottom:8px;}
  460. h4{margin-left:24px;margin-right:24px;}
  461. #twitter{line-height:20px;vertical-align:top;text-align:right;font-style:italic;color:#333;margin-top:24px;font-size:14px;}' > blog.css
  462. fi
  463. # This is the CSS file from my main page. Any other person would need it to run the blog
  464. # so it's attached here for convenience.
  465. if [ "$(whoami)" == "carlesfe" ] && [ ! -f "main.css" ]; then
  466. ln -s "../style.css" "main.css" # XXX This is clearly machine-dependent, beware!
  467. elif [ ! -f "main.css" ]; then
  468. echo 'body{font-family:Georgia,"Times New Roman",Times,serif;margin:0;padding:0;background-color:#F3F3F3;}
  469. #divbodyholder{padding:5px;background-color:#DDD;width:874px;margin:24px auto;}
  470. #divbody{width:776px;border:solid 1px #ccc;background-color:#fff;padding:0px 48px 24px 48px;top:0;}
  471. .headerholder{background-color:#f9f9f9;border-top:solid 1px #ccc;border-left:solid 1px #ccc;border-right:solid 1px #ccc;}
  472. .header{width:800px;margin:0px auto;padding-top:24px;padding-bottom:8px;}
  473. .content{margin-bottom:45px;}
  474. .nomargin{margin:0;}
  475. .description{margin-top:10px;border-top:solid 1px #666;padding:10px 0;}
  476. h3{font-size:20pt;width:100%;font-weight:bold;margin-top:32px;margin-bottom:0;}
  477. .clear{clear:both;}
  478. #footer{padding-top:10px;border-top:solid 1px #666;color:#333333;text-align:center;font-size:small;font-family:"Courier New","Courier",monospace;}
  479. a{text-decoration:none;color:#003366 !important;}
  480. a:visited{text-decoration:none;color:#336699 !important;}
  481. blockquote{background-color:#f9f9f9;border-left:solid 4px #e9e9e9;margin-left:12px;padding:12px 12px 12px 24px;}
  482. blockquote img{margin:12px 0px;}
  483. blockquote iframe{margin:12px 0px;}' > main.css
  484. fi
  485. }
  486. # Regenerates all the single post entries, keeping the post content but modifying
  487. # the title, html structure, etc
  488. rebuild_all_entries() {
  489. echo -n "Rebuilding all entries "
  490. for i in *.html; do # no need to sort
  491. if [ "$i" == "$index_file" ] || [ "$i" == "$archive_index" ]; then continue; fi
  492. contentfile=".tmp.$RANDOM"
  493. while [ -f "$contentfile" ]; do contentfile=".tmp.$RANDOM"; done
  494. echo -n "."
  495. # Get the title and entry, and rebuild the html structure from scratch (divs, title, description...)
  496. title="$(awk '/<h3><a class="ablack" href=".+">/, /<\/a><\/h3>/{if (!/<h3><a class="ablack" href=".+">/ && !/<\/a><\/h3>/) print}' $i)"
  497. awk '/<!-- text begin -->/, /<!-- text end -->/{if (!/<!-- text begin -->/ && !/<!-- text end -->/) print}' "$i" >> "$contentfile"
  498. # Original post timestamp
  499. #timestamp="$(date -R -r $i)"
  500. timestamp="$(stat -f "%Sm" -t "%a, %d %h %Y %H:%M:%S %z" $i)"
  501. create_html_page "$contentfile" "$i.rebuilt" no "$title" "$timestamp"
  502. # keep the original timestamp!
  503. #timestamp="$(date -r $i +'%Y%m%d%k%M')"
  504. timestamp="$(stat -f "%Sm" -t '%Y%m%d%k%M' $i)"
  505. mv "$i.rebuilt" "$i"
  506. chmod 644 "$i"
  507. touch -t $timestamp "$i"
  508. rm "$contentfile"
  509. done
  510. echo ""
  511. }
  512. # Displays the help
  513. function usage() {
  514. echo "$global_software_name v$global_software_version"
  515. echo "Usage: $0 command [filename]"
  516. echo ""
  517. echo "Commands:"
  518. echo " post [filename] insert a new blog post, or the FILENAME of a draft to continue editing it"
  519. echo " edit [filename] edit an already published .html file. Never edit manually a published .html file,"
  520. echo " always use this function as it keeps the original timestamp "
  521. echo " and rebuilds whatever indices are needed"
  522. echo " rebuild regenerates all the pages and posts, preserving the content of the entries"
  523. echo " reset deletes blog-generated files. Use with a lot of caution and back up first!"
  524. echo " list list all entries. Useful for debug"
  525. echo ""
  526. echo "For more information please open $0 in a code editor and read the header and comments"
  527. }
  528. # Delete all generated content, leaving only this script
  529. reset() {
  530. echo "Are you sure you want to delete all blog entries? Please write \"Yes, I am!\" "
  531. read line
  532. if [ "$line" == "Yes, I am!" ]; then
  533. rm .*.html *.html *.css *.rss &> /dev/null
  534. echo
  535. echo "Deleted all posts, stylesheets and feeds."
  536. echo "Kept your old '.backup.tar.gz' just in case, please delete it manually if needed."
  537. else
  538. echo "Phew! You dodged a bullet there. Nothing was modified."
  539. fi
  540. }
  541. # Main function
  542. # Encapsulated on its own function for readability purposes
  543. #
  544. # $1 command to run
  545. # $2 file name of a draft to continue editing (optional)
  546. do_main() {
  547. # Use config file or fallback to inline configuration
  548. source "$global_config" || global_variables
  549. # Check for $EDITOR
  550. if [[ -z "$EDITOR" ]]; then
  551. echo "Please set your \$EDITOR environment variable"
  552. exit
  553. fi
  554. # Check for validity of argument
  555. if [ "$1" != "reset" ] && [ "$1" != "post" ] && [ "$1" != "rebuild" ] && [ "$1" != "list" ] && [ "$1" != "edit" ]; then
  556. usage; exit;
  557. fi
  558. if [ "$1" == "list" ]; then
  559. list_posts
  560. exit
  561. fi
  562. if [[ "$1" == "edit" ]]; then
  563. if [[ $# -lt 2 ]] || [[ ! -f "$2" ]]; then
  564. echo "Please enter a valid html file to edit"
  565. exit
  566. fi
  567. fi
  568. # Test for existing html files
  569. ls *.html &> /dev/null
  570. if [ $? -ne 0 ] && [ "$1" == "rebuild" ]; then
  571. echo "Can't find any html files, nothing to rebuild"
  572. exit
  573. fi
  574. # We're going to back up just in case
  575. ls *.html &> /dev/null
  576. if [[ $? -eq 0 ]]; then
  577. tar cfz ".backup.tar.gz" *.html
  578. chmod 600 ".backup.tar.gz"
  579. fi
  580. if [ "$1" == "reset" ]; then
  581. reset
  582. exit
  583. fi
  584. create_includes
  585. create_css
  586. if [ "$1" == "post" ]; then write_entry "$2"; fi
  587. if [ "$1" == "rebuild" ]; then rebuild_all_entries; fi
  588. if [ "$1" == "edit" ]; then edit "$2"; fi
  589. rebuild_index
  590. all_posts
  591. make_rss
  592. delete_includes
  593. }
  594. #
  595. # MAIN
  596. # Do not change anything here. If you want to modify the code, edit do_main()
  597. #
  598. do_main $*