bb 46 KB

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