.mkblog.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #!/bin/sh
  2. # This program is free software: you can redistribute it and/or modify
  3. # it under the terms of the GNU Affero General Public License as
  4. # published by the Free Software Foundation, either version 3 of the
  5. # License, or (at your option) any later version.
  6. # This program is distributed in the hope that it will be useful,
  7. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. # GNU Affero General Public License for more details.
  10. # You should have received a copy of the GNU Affero General Public License
  11. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  12. # Explain usage
  13. usage() {
  14. printf "Usage:\n"
  15. helper_print_instruction "init" "Create mkblog.sh skeleton"
  16. helper_print_instruction "new page" "Create a new page"
  17. helper_print_instruction "new post" "Create a new blog post"
  18. helper_print_instruction "build" "Build blog using files"
  19. }
  20. # Init blog
  21. init() {
  22. # Create directory if nonexistent
  23. if [ ! -d "$1/" ]; then
  24. mkdir "$1/"
  25. else
  26. helper_prompt_overwrite "directory"
  27. fi
  28. # set up blog variables
  29. printf "" > "$1/variables"
  30. helper_read_and_export "Blog title" "title" "$1/variables"
  31. helper_read_and_export "Blog subtitle" "subtitle" "$1/variables"
  32. helper_read_and_export "Blog URL" "url" "$1/variables"
  33. printf "var_mdproc=markdown\n" >> "$1/variables"
  34. # create relevant directories if nonexistent
  35. helper_check_and_make_dir "$1/templates/"
  36. helper_check_and_make_dir "$1/static/"
  37. helper_check_and_make_dir "$1/pages/"
  38. helper_check_and_make_dir "$1/posts/"
  39. # Write example CSS to static
  40. cat <<EOF >"$1/static/style.css"
  41. body {
  42. width: 640px;
  43. max-width: 90%;
  44. margin: auto;
  45. }
  46. #skip a
  47. {
  48. position: absolute;
  49. left: -10000px;
  50. top: auto;
  51. width: 1px;
  52. height: 1px;
  53. overflow: hidden;
  54. }
  55. #skip a:focus
  56. {
  57. position: static;
  58. width: auto;
  59. height: auto;
  60. }
  61. img {
  62. max-width: 100%;
  63. }
  64. p {
  65. line-height: 1.6;
  66. }
  67. a {
  68. text-decoration: none;
  69. color: teal;
  70. }
  71. #pages li {
  72. display: inline-block;
  73. margin: 0 1em;
  74. }
  75. #pages a {
  76. font-size: 1.25em;
  77. }
  78. .title {
  79. margin-bottom: 0px;
  80. }
  81. #prevnext {
  82. width: 100%;
  83. text-align: center;
  84. }
  85. .prev, .cur, .next {
  86. display: inline-block;
  87. font-size: 3em;
  88. width: 30%;
  89. }
  90. EOF
  91. # Write example header to templates
  92. cat <<'EOF' >"$1/templates/header.html"
  93. <!DOCTYPE HTML>
  94. <html>
  95. <head>
  96. <meta charset="utf-8" />
  97. <meta name="generator" content="mkblog.sh" />
  98. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  99. <title>${var_page} - ${var_title}</title>
  100. <link rel="stylesheet" type="text/css" href="${var_url}/static/style.css">
  101. </head>
  102. <body>
  103. <div id='skip'><a href='#content'>Skip to Main Content</a></div>
  104. <h1>${var_title}</h1>
  105. <h2>${var_subtitle}</h2>
  106. EOF
  107. # Write example footer to templates
  108. cat <<EOF >"$1/templates/footer.html"
  109. <p><small>This blog was generated by <a href="https://notabug.org/SylvieLorxu/mkblog.sh" target="_blank">mkblog.sh</a>.</small></p>
  110. </body>
  111. </html>
  112. EOF
  113. }
  114. # Make a new page
  115. new_page() {
  116. helper_directory_exists "$1"
  117. helper_read_into "Page title" "page_title"
  118. # this is assigned in helper_read_into, so we're safe
  119. # shellcheck disable=SC2154
  120. if [ -f "$1/pages/${page_title}.md" ]; then
  121. helper_prompt_overwrite "page"
  122. fi
  123. ${EDITOR} "$1/pages/${page_title}.md"
  124. }
  125. # Make a new blog post
  126. new_post() {
  127. helper_directory_exists "$1"
  128. helper_read_into "Blog post title" "blog_post_title"
  129. blog_post_title=$(date "+%Y-%m-%d-%H:%M")-${blog_post_title}.md
  130. if [ -f "$1/posts/${blog_post_title}" ]; then
  131. helper_prompt_overwrite "post"
  132. fi
  133. ${EDITOR} "$1/posts/${blog_post_title}"
  134. }
  135. # Build blog
  136. build() {
  137. helper_directory_exists "$1"
  138. # Clean build directory
  139. if [ -d "$1/build/" ]; then
  140. rm -rf "$1/build/"
  141. fi
  142. mkdir "$1/build/"
  143. mkdir "$1/build/posts/"
  144. mkdir "$1/build/pages/"
  145. mkdir "$1/build/static/"
  146. # Copy static files
  147. cp -r "$1/static/" "$1/build/"
  148. # Get blog info
  149. # shellcheck disable=SC1090
  150. . "$1/variables"
  151. # Setup navbar
  152. # shellcheck disable=SC2154
  153. navdata="<nav id='pages'><ul><li><a href='$var_url/index.html'>Home</a></li>"
  154. find "$1/pages/" -name "$(printf "*\n")" -name '*.md' > tmp
  155. while IFS= read -r page
  156. do
  157. helper_build_setfileinfovars "$1" "$page" "pages"
  158. navdata="$navdata<li><a href='$var_url/pages/${docnoext}.html'>$docnoext</a></li>"
  159. done < tmp
  160. rm tmp
  161. navdata="$navdata</ul></nav><div id='content'>"
  162. # Create pages
  163. find "$1/pages/" -name "$(printf "*\n")" -name '*.md' > tmp
  164. while IFS= read -r page
  165. do
  166. helper_build_setfileinfovars "$1" "$page" "pages"
  167. export var_page="$doctitle"
  168. helper_build_initpage "$1" "" "$dochtmlfilename"
  169. # shellcheck disable=SC2154
  170. helper_build_endpage "$1" "$navdata$beforedochtml$(< "$page" "$var_mdproc")$afterdochtml" "$dochtmlfilename"
  171. done < tmp
  172. rm tmp
  173. export var_page="Home"
  174. helper_build_initpage "$1" "$navdata" "$1/build/index.html"
  175. # Create posts
  176. count=-1
  177. find "$1/posts/" -name "$(printf "*\n")" -name '*.md' | sort -r > tmp
  178. while IFS= read -r post
  179. do
  180. count=$((count + 1))
  181. if [ $count -gt 0 ] && [ $((count%10)) -eq 0 ]; then
  182. nextpage=$((page + 1))
  183. helper_build_endpage "$1" "$(helper_build_generateprevnext "$page" "True")" "$1/build/index$page.html"
  184. page=$nextpage
  185. helper_build_initpage "$1" "$navdata" "$1/build/index$page.html"
  186. fi
  187. helper_build_setfileinfovars "$1" "$post" "posts"
  188. postmarkdown=$(< "$post" "$var_mdproc")
  189. export var_page="$doctitle"
  190. helper_build_initpage "$1" "$navdata" "$dochtmlfilename"
  191. helper_build_endpage "$1" "$beforedochtml$postmarkdown$afterdochtml" "$dochtmlfilename"
  192. # Shorten long posts in the preview
  193. if [ "$(echo "$postmarkdown" | wc -w)" -gt 50 ]; then
  194. # http://stackoverflow.com/a/15612523
  195. entrypreview=$(echo "$postmarkdown" | awk -v n=50 'n==c{exit}n-c>=NF{print;c+=NF;next}{for(i=1;i<=n-c;i++)printf "%s ",$i;print x;exit}' | sed -e 's/[[:space:]|,|.|?|!|-]]*$//')"..."
  196. else
  197. entrypreview=$postmarkdown
  198. fi
  199. # Add preview to page
  200. { echo "$beforedochtmlwithlink";
  201. echo "$entrypreview";
  202. echo "$afterdochtml"; } >> "$1/build/index$page.html"
  203. done < tmp
  204. rm tmp
  205. # Finish last page
  206. helper_build_endpage "$1" "$(helper_build_generateprevnext "$page")" "$1/build/index$page.html"
  207. }
  208. # $1 = blog directory
  209. # $2 = nav html
  210. # $3 = pagename
  211. helper_build_initpage() {
  212. { envsubst < "$1/templates/header.html"; echo "$2"; } >> "$3"
  213. }
  214. # $1 = blog directory
  215. # $2 = extra html (for example pagination or blog article)
  216. # $3 = pagename
  217. helper_build_endpage() {
  218. { echo "$2";
  219. echo "</div>";
  220. envsubst < "$1/templates/footer.html"; } >> "$3"
  221. }
  222. # $1 pagenumber
  223. # $2 hasnext
  224. helper_build_generateprevnext() {
  225. page=$1
  226. extrahtml="<div id='prevnext'>"
  227. pagesfound=0
  228. if [ ! -z "$1" ]; then
  229. pagesfound=$((pagesfound + 1))
  230. previouspage=$(($1 - 1))
  231. if [ $previouspage -eq 0 ]; then
  232. previouspage=""
  233. fi
  234. extrahtml="$extrahtml<a class='prev' href='$var_url/index$previouspage.html'>&laquo;</a>"
  235. page=$(($1 + 1))
  236. else
  237. extrahtml="$extrahtml<a class='prev'></a>"
  238. page="1"
  239. fi
  240. extrahtml="$extrahtml<span class='cur'>$page</span>"
  241. if [ ! -z "$2" ]; then
  242. pagesfound=$((pagesfound + 1))
  243. extrahtml="$extrahtml<a class='next' href='$var_url/index$page.html'>&raquo;</a>"
  244. else
  245. extrahtml="$extrahtml<a class='next'></a>"
  246. fi
  247. # Don't print pagination if there are no other pages
  248. if [ $pagesfound -gt 0 ]; then
  249. echo "$extrahtml</div>"
  250. fi
  251. }
  252. # $1 = blog directory
  253. # $2 = file name
  254. # $3 = file directory (pages/posts)
  255. helper_build_setfileinfovars() {
  256. # Get desired filename
  257. docbasename=$(basename "$2")
  258. docnoext=${docbasename%.md}
  259. dochtmlfilename="$1/build/$3/${docnoext}.html"
  260. if [ "$3" = "pages" ]; then
  261. beforedochtml="<h1 class='title'>${docnoext}</h1><article id='content' class='page'>"
  262. afterdochtml="</article>"
  263. doctitle=${docnoext}
  264. elif [ "$3" = "posts" ]; then
  265. docdate=$(echo "$docbasename" | awk -F '-' '{ printf "%s-%s-%s %s", $1, $2, $3, $4 }')
  266. doctitle=${docbasename#*-*-*-*-}
  267. doctitle=${doctitle%.md}
  268. beforedochtml="<h1 class='title'>$doctitle</h1><br><small class='postdate'>$docdate</small><article id='content' class='post'>"
  269. beforedochtmlwithlink="<h1 class='title'><a href='$var_url/$3/${docnoext}.html'>$doctitle</a></h1><br><small class='postdate'>$docdate</small><article class='post'>"
  270. afterdochtml="</article>"
  271. else
  272. echo "Software error"
  273. exit 5
  274. fi
  275. }
  276. # $1 = directory to check for
  277. helper_directory_exists() {
  278. if [ ! -d "$1" ]; then
  279. echo "$1 does not exist. Try mkblog.sh init $1."
  280. exit 3
  281. fi
  282. }
  283. helper_remind_usage() {
  284. usage
  285. exit 2
  286. }
  287. # $1 = name of the instruction
  288. # $2 = description of instruction
  289. helper_print_instruction() {
  290. printf " mkblog.sh %s directory\n %s in directory.\n" "$1" "$2"
  291. }
  292. # $1 = what kind of thing
  293. helper_prompt_overwrite() {
  294. printf "This %s already exists. Overwrite? (y/N)\n" "$1"
  295. read -r yn
  296. case ${yn} in
  297. [Yy]* ) ;;
  298. * ) printf "No confirmation, quitting.\n"; exit;;
  299. esac
  300. }
  301. # $1 = desired directory loc
  302. helper_check_and_make_dir() {
  303. if [ ! -d "$1" ]; then
  304. mkdir "$1"
  305. fi
  306. }
  307. # $1 = desired variable as a string
  308. # $2 = desired location
  309. # use of eval is safe - only done on constructed strings which aren't
  310. # user-defined
  311. helper_export_into() {
  312. eval printf 'export\ var_%s=\"%s\"\\n' "$1" \"\$"$1"\" >> "$2"
  313. }
  314. # $1 = prompt message
  315. # $2 = target variable
  316. helper_read_into() {
  317. printf '%s: ' "$1"
  318. read -r "$2"
  319. }
  320. # $1 = prompt message
  321. # $2 = target variable
  322. # $3 = destination
  323. helper_read_and_export() {
  324. helper_read_into "$1" "$2"
  325. helper_export_into "$2" "$3"
  326. }
  327. # The actual 'main function'
  328. # Check input
  329. if [ $# -lt 1 ]; then
  330. helper_remind_usage
  331. fi
  332. # Parse command
  333. if [ "$1" = "init" ]; then
  334. if [ $# -ne 2 ]; then
  335. helper_remind_usage
  336. fi
  337. init "$2"
  338. exit 0
  339. elif [ "$1" = "new" ]; then
  340. if [ "$#" -ne 3 ]; then
  341. helper_remind_usage
  342. elif [ "$2" = "page" ]; then
  343. new_page "$3"
  344. exit 0
  345. elif [ "$2" = "post" ]; then
  346. new_post "$3"
  347. exit 0
  348. else
  349. helper_remind_usage
  350. fi
  351. elif [ "$1" = "build" ]; then
  352. if [ $# -ne 2 ]; then
  353. helper_remind_usage
  354. fi
  355. build "$2"
  356. exit 0
  357. else
  358. helper_remind_usage
  359. fi