From bc2c2fa4554a55d23b39abbed28b714ebb6d4492 Mon Sep 17 00:00:00 2001 From: Daniel Berkompas Date: Sat, 24 Feb 2018 15:02:38 -0800 Subject: [PATCH] Remove old site --- CNAME | 1 - Gemfile | 4 - Gemfile.lock | 130 ------- README.md | 4 - _config.yml | 19 - _data/theme.yml | 34 -- _includes/analytics.html | 9 - _includes/css/base.css | 335 ----------------- _includes/css/layout.css | 86 ----- _includes/css/pygments_themes/README.md | 23 -- _includes/css/pygments_themes/UNLICENSE.txt | 24 -- _includes/css/pygments_themes/autumn.css | 58 --- _includes/css/pygments_themes/borland.css | 46 --- _includes/css/pygments_themes/bw.css | 34 -- _includes/css/pygments_themes/colorful.css | 61 --- _includes/css/pygments_themes/default.css | 61 --- _includes/css/pygments_themes/emacs.css | 61 --- _includes/css/pygments_themes/friendly.css | 61 --- _includes/css/pygments_themes/fruity.css | 70 ---- _includes/css/pygments_themes/github.css | 61 --- _includes/css/pygments_themes/manni.css | 61 --- _includes/css/pygments_themes/monokai.css | 65 ---- _includes/css/pygments_themes/murphy.css | 61 --- _includes/css/pygments_themes/native.css | 70 ---- _includes/css/pygments_themes/pastie.css | 60 --- _includes/css/pygments_themes/perldoc.css | 58 --- _includes/css/pygments_themes/tango.css | 69 ---- _includes/css/pygments_themes/trac.css | 59 --- _includes/css/pygments_themes/vim.css | 70 ---- _includes/css/pygments_themes/vs.css | 33 -- _includes/css/pygments_themes/zenburn.css | 136 ------- _includes/css/screen.css | 348 ------------------ _includes/css/skeleton.css | 236 ------------ _includes/css/syntax.css | 3 - _includes/footer.html | 12 - _includes/sidebar.html | 27 -- _includes/social.html | 72 ---- _includes/subscribe.html | 32 -- _layouts/default.html | 38 -- _layouts/post.html | 48 --- _posts/.#2016-01-28-seo-tags-in-phoenix.md | 1 - _posts/2015-02-07-spark-notebook.md | 31 -- _posts/2015-02-07-yet-another-tech-blog.md | 12 - _posts/2015-02-09-moving-beyond-ruby.md | 70 ---- _posts/2015-02-15-think-about-the-next-guy.md | 60 --- _posts/2015-02-19-undo-git-commit.md | 17 - _posts/2015-02-21-find-nth-prime-in-elixir.md | 57 --- ...-02-27-why-i-write-straight-html-and-js.md | 83 ----- _posts/2015-03-06-immutable-databases.md | 46 --- _posts/2015-03-11-ex-twiml.md | 52 --- .../2015-03-21-manage-env-vars-in-elixir.md | 52 --- ...5-03-28-stream-paginated-apis-in-elixir.md | 236 ------------ _posts/2015-04-01-contracts-gem.md | 165 --------- ...-04-03-run-dialyzer-on-elixir-on-travis.md | 64 ---- ...-04-08-generate-dialyzer-plts-on-travis.md | 48 --- .../2015-04-17-keep-your-ets-tables-alive.md | 52 --- _posts/2015-04-23-telephonist-on-github.md | 81 ---- ...-04-28-why-a-static-blog-is-a-good-idea.md | 70 ---- _posts/2015-05-08-testing-ecto-validations.md | 144 -------- .../2015-05-12-a-personal-development-plan.md | 30 -- _posts/2015-05-20-useful-ecto-validators.md | 60 --- .../2015-06-09-number-helpers-for-elixir.md | 53 --- .../2015-06-10-how-to-write-guard-macros.md | 52 --- .../2015-06-16-rate-limiting-a-phoenix-api.md | 202 ---------- .../2015-07-03-encrypting-data-with-ecto.md | 254 ------------- ...07-09-changing-your-ecto-encryption-key.md | 224 ----------- _posts/2015-07-16-fixtures-for-ecto.md | 156 -------- _posts/2015-07-28-long-running-branches.md | 37 -- ...015-08-03-elixir-as-an-operating-system.md | 55 --- ...-08-21-genservers-as-concurrent-objects.md | 144 -------- ...2015-08-28-how-to-run-elixir-cloud9-ide.md | 30 -- ...015-09-03-better-pipelines-with-monadex.md | 186 ---------- _posts/2015-09-22-cloak-your-ecto-data.md | 145 -------- .../2015-09-25-announcing-learn-elixir-tv.md | 47 --- .../2015-10-15-why-im-checking-out-graphql.md | 70 ---- ...12-08-use-gen-event-with-ecto-callbacks.md | 168 --------- _posts/2016-01-28-seo-tags-in-phoenix.md | 289 --------------- .../2016-04-05-background-jobs-in-phoenix.md | 92 ----- ...04-23-multidimensional-arrays-in-elixir.md | 221 ----------- _posts/2016-09-27-ecto-multi-services.md | 182 --------- ...017-01-17-reusable-templates-in-phoenix.md | 108 ------ ...7-09-30-why-your-software-projects-fail.md | 71 ---- assets/css/all.css | 7 - assets/img/algorithms-manual.jpg | Bin 20268 -> 0 bytes assets/img/mastering-regex.jpg | Bin 18471 -> 0 bytes assets/img/spark-notebook.jpg | Bin 64650 -> 0 bytes assets/img/tic_tac_toe.png | Bin 15269 -> 0 bytes favicon.png | Bin 3141 -> 0 bytes index.html | 13 - keybase.txt | 82 ----- logo.jpg | Bin 7099 -> 0 bytes logo.png | Bin 6586 -> 0 bytes personal-development-plan/index.md | 115 ------ site.json | 11 - 94 files changed, 7185 deletions(-) delete mode 100644 CNAME delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 README.md delete mode 100644 _config.yml delete mode 100644 _data/theme.yml delete mode 100644 _includes/analytics.html delete mode 100644 _includes/css/base.css delete mode 100644 _includes/css/layout.css delete mode 100644 _includes/css/pygments_themes/README.md delete mode 100644 _includes/css/pygments_themes/UNLICENSE.txt delete mode 100644 _includes/css/pygments_themes/autumn.css delete mode 100644 _includes/css/pygments_themes/borland.css delete mode 100644 _includes/css/pygments_themes/bw.css delete mode 100644 _includes/css/pygments_themes/colorful.css delete mode 100644 _includes/css/pygments_themes/default.css delete mode 100644 _includes/css/pygments_themes/emacs.css delete mode 100644 _includes/css/pygments_themes/friendly.css delete mode 100644 _includes/css/pygments_themes/fruity.css delete mode 100644 _includes/css/pygments_themes/github.css delete mode 100644 _includes/css/pygments_themes/manni.css delete mode 100644 _includes/css/pygments_themes/monokai.css delete mode 100644 _includes/css/pygments_themes/murphy.css delete mode 100644 _includes/css/pygments_themes/native.css delete mode 100644 _includes/css/pygments_themes/pastie.css delete mode 100644 _includes/css/pygments_themes/perldoc.css delete mode 100644 _includes/css/pygments_themes/tango.css delete mode 100644 _includes/css/pygments_themes/trac.css delete mode 100644 _includes/css/pygments_themes/vim.css delete mode 100644 _includes/css/pygments_themes/vs.css delete mode 100644 _includes/css/pygments_themes/zenburn.css delete mode 100644 _includes/css/screen.css delete mode 100644 _includes/css/skeleton.css delete mode 100644 _includes/css/syntax.css delete mode 100644 _includes/footer.html delete mode 100644 _includes/sidebar.html delete mode 100644 _includes/social.html delete mode 100644 _includes/subscribe.html delete mode 100644 _layouts/default.html delete mode 100644 _layouts/post.html delete mode 120000 _posts/.#2016-01-28-seo-tags-in-phoenix.md delete mode 100644 _posts/2015-02-07-spark-notebook.md delete mode 100644 _posts/2015-02-07-yet-another-tech-blog.md delete mode 100644 _posts/2015-02-09-moving-beyond-ruby.md delete mode 100644 _posts/2015-02-15-think-about-the-next-guy.md delete mode 100644 _posts/2015-02-19-undo-git-commit.md delete mode 100644 _posts/2015-02-21-find-nth-prime-in-elixir.md delete mode 100644 _posts/2015-02-27-why-i-write-straight-html-and-js.md delete mode 100644 _posts/2015-03-06-immutable-databases.md delete mode 100644 _posts/2015-03-11-ex-twiml.md delete mode 100644 _posts/2015-03-21-manage-env-vars-in-elixir.md delete mode 100644 _posts/2015-03-28-stream-paginated-apis-in-elixir.md delete mode 100644 _posts/2015-04-01-contracts-gem.md delete mode 100644 _posts/2015-04-03-run-dialyzer-on-elixir-on-travis.md delete mode 100644 _posts/2015-04-08-generate-dialyzer-plts-on-travis.md delete mode 100644 _posts/2015-04-17-keep-your-ets-tables-alive.md delete mode 100644 _posts/2015-04-23-telephonist-on-github.md delete mode 100644 _posts/2015-04-28-why-a-static-blog-is-a-good-idea.md delete mode 100644 _posts/2015-05-08-testing-ecto-validations.md delete mode 100644 _posts/2015-05-12-a-personal-development-plan.md delete mode 100644 _posts/2015-05-20-useful-ecto-validators.md delete mode 100644 _posts/2015-06-09-number-helpers-for-elixir.md delete mode 100644 _posts/2015-06-10-how-to-write-guard-macros.md delete mode 100644 _posts/2015-06-16-rate-limiting-a-phoenix-api.md delete mode 100644 _posts/2015-07-03-encrypting-data-with-ecto.md delete mode 100644 _posts/2015-07-09-changing-your-ecto-encryption-key.md delete mode 100644 _posts/2015-07-16-fixtures-for-ecto.md delete mode 100644 _posts/2015-07-28-long-running-branches.md delete mode 100644 _posts/2015-08-03-elixir-as-an-operating-system.md delete mode 100644 _posts/2015-08-21-genservers-as-concurrent-objects.md delete mode 100644 _posts/2015-08-28-how-to-run-elixir-cloud9-ide.md delete mode 100644 _posts/2015-09-03-better-pipelines-with-monadex.md delete mode 100644 _posts/2015-09-22-cloak-your-ecto-data.md delete mode 100644 _posts/2015-09-25-announcing-learn-elixir-tv.md delete mode 100644 _posts/2015-10-15-why-im-checking-out-graphql.md delete mode 100644 _posts/2015-12-08-use-gen-event-with-ecto-callbacks.md delete mode 100644 _posts/2016-01-28-seo-tags-in-phoenix.md delete mode 100644 _posts/2016-04-05-background-jobs-in-phoenix.md delete mode 100644 _posts/2016-04-23-multidimensional-arrays-in-elixir.md delete mode 100644 _posts/2016-09-27-ecto-multi-services.md delete mode 100644 _posts/2017-01-17-reusable-templates-in-phoenix.md delete mode 100644 _posts/2017-09-30-why-your-software-projects-fail.md delete mode 100644 assets/css/all.css delete mode 100644 assets/img/algorithms-manual.jpg delete mode 100644 assets/img/mastering-regex.jpg delete mode 100644 assets/img/spark-notebook.jpg delete mode 100644 assets/img/tic_tac_toe.png delete mode 100644 favicon.png delete mode 100644 index.html delete mode 100644 keybase.txt delete mode 100644 logo.jpg delete mode 100644 logo.png delete mode 100644 personal-development-plan/index.md delete mode 100644 site.json diff --git a/CNAME b/CNAME deleted file mode 100644 index fee88b8aa..000000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -blog.danielberkompas.com diff --git a/Gemfile b/Gemfile deleted file mode 100644 index efaeec038..000000000 --- a/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source "https://rubygems.org" - -gem "github-pages" -gem "jekyll-feed" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 362d27aa3..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,130 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - RedCloth (4.2.9) - activesupport (4.2.5.1) - i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.3.8) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.10.0) - colorator (0.1) - ethon (0.8.1) - ffi (>= 1.3.0) - execjs (2.6.0) - faraday (0.9.2) - multipart-post (>= 1.2, < 3) - ffi (1.9.10) - gemoji (2.1.0) - github-pages (48) - RedCloth (= 4.2.9) - github-pages-health-check (= 0.6.1) - jekyll (= 3.0.3) - jekyll-coffeescript (= 1.0.1) - jekyll-feed (= 0.3.1) - jekyll-gist (= 1.4.0) - jekyll-mentions (= 1.0.0) - jekyll-paginate (= 1.1.0) - jekyll-redirect-from (= 0.9.1) - jekyll-sass-converter (= 1.3.0) - jekyll-seo-tag (= 1.0.0) - jekyll-sitemap (= 0.10.0) - jekyll-textile-converter (= 0.1.0) - jemoji (= 0.5.1) - kramdown (= 1.9.0) - liquid (= 3.0.6) - mercenary (~> 0.3) - rdiscount (= 2.1.8) - redcarpet (= 3.3.3) - rouge (= 1.10.1) - terminal-table (~> 1.4) - github-pages-health-check (0.6.1) - addressable (~> 2.3) - net-dns (~> 0.8) - public_suffix (~> 1.4) - typhoeus (~> 0.7) - html-pipeline (2.3.0) - activesupport (>= 2, < 5) - nokogiri (>= 1.4) - i18n (0.7.0) - jekyll (3.0.3) - colorator (~> 0.1) - jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 1.1) - kramdown (~> 1.3) - liquid (~> 3.0) - mercenary (~> 0.3.3) - rouge (~> 1.7) - safe_yaml (~> 1.0) - jekyll-coffeescript (1.0.1) - coffee-script (~> 2.2) - jekyll-feed (0.3.1) - jekyll-gist (1.4.0) - octokit (~> 4.2) - jekyll-mentions (1.0.0) - html-pipeline (~> 2.2) - jekyll (~> 3.0) - jekyll-paginate (1.1.0) - jekyll-redirect-from (0.9.1) - jekyll (>= 2.0) - jekyll-sass-converter (1.3.0) - sass (~> 3.2) - jekyll-seo-tag (1.0.0) - jekyll (>= 2.0) - jekyll-sitemap (0.10.0) - jekyll-textile-converter (0.1.0) - RedCloth (~> 4.0) - jekyll-watch (1.3.1) - listen (~> 3.0) - jemoji (0.5.1) - gemoji (~> 2.0) - html-pipeline (~> 2.2) - jekyll (>= 2.0) - json (1.8.3) - kramdown (1.9.0) - liquid (3.0.6) - listen (3.0.6) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9.7) - mercenary (0.3.5) - mini_portile2 (2.0.0) - minitest (5.8.4) - multipart-post (2.0.0) - net-dns (0.8.0) - nokogiri (1.6.7.2) - mini_portile2 (~> 2.0.0.rc2) - octokit (4.2.0) - sawyer (~> 0.6.0, >= 0.5.3) - public_suffix (1.5.3) - rb-fsevent (0.9.7) - rb-inotify (0.9.7) - ffi (>= 0.5.0) - rdiscount (2.1.8) - redcarpet (3.3.3) - rouge (1.10.1) - safe_yaml (1.0.4) - sass (3.4.21) - sawyer (0.6.0) - addressable (~> 2.3.5) - faraday (~> 0.8, < 0.10) - terminal-table (1.5.2) - thread_safe (0.3.5) - typhoeus (0.8.0) - ethon (>= 0.8.0) - tzinfo (1.2.2) - thread_safe (~> 0.1) - -PLATFORMS - ruby - -DEPENDENCIES - github-pages - jekyll-feed - -BUNDLED WITH - 1.11.2 diff --git a/README.md b/README.md deleted file mode 100644 index b9d05db74..000000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Miscellaneous Bits - -Repo for my public blog, hosted at . Based on -the Lagom theme. diff --git a/_config.yml b/_config.yml deleted file mode 100644 index ecb60bc3a..000000000 --- a/_config.yml +++ /dev/null @@ -1,19 +0,0 @@ -# General Jekyll Config -#highlighter: pygments - -name: Miscellaneous Bits -description: A technical blog on Elixir, Ruby and other topics -url: http://blog.danielberkompas.com -author: - name: Daniel Berkompas - url: http://danielberkompas.com -highlighter: rouge -markdown: kramdown -kramdown: - input: GFM -lsi: false -exclude: [LICENSE, CNAME, README.md, .gitignore, Gemfile, Gemfile.lock] -gems: - - jekyll-feed -feed: - path: atom.xml diff --git a/_data/theme.yml b/_data/theme.yml deleted file mode 100644 index 62b4f7813..000000000 --- a/_data/theme.yml +++ /dev/null @@ -1,34 +0,0 @@ -# Theme customization - please change from the defaults! - -# Color for top bar, links, etc -highlight_color: '000000' -link_color: '#507d3e' # '#56c8f2' - -# Profile links on the left sidebar, leave blank to ignore -social: - github: danielberkompas - twitter: dberkom - gravatar: 86e48b29ff56ebbba07da529f6fcfc8d - linkedin: danielberkompas - #bitbucket: philipj - #hacker_news: swanson - #stackexchange: 348619 - #stackoverflow: 348619 - #facebook: zuck - #tumblr: animalygifs - #gplus: 110552447039675960964 - -# "Hi, I'm _______" -name: Daniel Berkompas -email: himself@danielberkompas.com -blog_name: Miscellaneous Bits -description: "A technical blog by Daniel Berkompas" - -#email: test@example.com -#tagline: "weak opinions, strongly held" - -# Google Analytics key, leave blank to ignore -google_analytics_key: UA-xxxx-x - -# Toggle "Postings are my own" disclaimer in footer -show_disclaimer: true diff --git a/_includes/analytics.html b/_includes/analytics.html deleted file mode 100644 index 3e0ddc01b..000000000 --- a/_includes/analytics.html +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/_includes/css/base.css b/_includes/css/base.css deleted file mode 100644 index f027915d1..000000000 --- a/_includes/css/base.css +++ /dev/null @@ -1,335 +0,0 @@ -/* -* Skeleton V1.1 -* Copyright 2011, Dave Gamache -* www.getskeleton.com -* Free to use under the MIT license. -* http://www.opensource.org/licenses/mit-license.php -* 8/17/2011 -*/ - - -/* Table of Content -================================================== - #Reset & Basics - #Basic Styles - #Site Styles - #Typography - #Links - #Lists - #Images - #Buttons - #Tabs - #Forms - #Misc */ - - -/* #Reset & Basics (Inspired by E. Meyers) -================================================== */ - html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; } - article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { - display: block; } - body { - line-height: 1; } - ol, ul { - list-style: none; } - blockquote, q { - quotes: none; } - blockquote:before, blockquote:after, - q:before, q:after { - content: ''; - content: none; } - table { - border-collapse: collapse; - border-spacing: 0; } - - -/* #Basic Styles -================================================== */ - body { - background: #fff; - font: 14px/21px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #444; - -webkit-font-smoothing: antialiased; /* Fix for webkit rendering */ - -webkit-text-size-adjust: 100%; - } - - -/* #Typography -================================================== */ - h1, h2, h3, h4, h5, h6 { - color: #181818; - font-family: "Georgia", "Times New Roman", Helvetica, Arial, sans-serif; - font-weight: normal; } - h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; } - h1 { font-size: 46px; line-height: 50px; margin-bottom: 14px;} - h2 { font-size: 35px; line-height: 40px; margin-bottom: 10px; } - h3 { font-size: 28px; line-height: 34px; margin-bottom: 8px; } - h4 { font-size: 21px; line-height: 30px; margin-bottom: 4px; } - h5 { font-size: 17px; line-height: 24px; } - h6 { font-size: 14px; line-height: 21px; } - .subheader { color: #777; } - - p { margin: 0 0 20px 0; } - p img { margin: 0 auto; } - p.lead { font-size: 21px; line-height: 27px; color: #777; } - - em { font-style: italic; } - strong { font-weight: bold; color: #333; } - small { font-size: 80%; } - -/* Blockquotes */ - blockquote, blockquote p { font-size: 17px; line-height: 24px; color: #777; font-style: italic; } - blockquote { margin: 0px; padding: 0px 20px 0 15px; border-left: 2px solid #ddd; } - blockquote cite { display: block; font-size: 12px; color: #555; } - blockquote cite:before { content: "\2014 \0020"; } - blockquote cite a, blockquote cite a:visited, blockquote cite a:visited { color: {{ site.colors.highlight }}; } - - hr { border: solid #ddd; border-width: 1px 0 0; clear: both; margin: 10px 0 30px; height: 0; } - - -/* #Links -================================================== */ - a, a:visited { color: #333; text-decoration: underline; outline: 0; } - a:hover, a:focus { color: #000; } - p a, p a:visited { line-height: inherit; } - - -/* #Lists -================================================== */ - ul, ol { margin-bottom: 20px; } - ul { list-style: none outside; } - ol { list-style: decimal; } - ol, ul.square, ul.circle, ul.disc { margin-left: 30px; } - ul.square { list-style: square outside; } - ul.circle { list-style: circle outside; } - ul.disc { list-style: disc outside; } - ul ul, ul ol, - ol ol, ol ul { margin: 4px 0 5px 30px; font-size: 90%; } - ul ul li, ul ol li, - ol ol li, ol ul li { margin-bottom: 6px; } - li { line-height: 18px; margin-bottom: 12px; } - ul.large li { line-height: 21px; } - li p { line-height: 21px; } - -/* #Images -================================================== */ - - img.scale-with-grid { - max-width: 100%; - height: auto; } - - -/* #Buttons -================================================== */ - - a.button, - button, - input[type="submit"], - input[type="reset"], - input[type="button"] { - background: #eee; /* Old browsers */ - background: #eee -moz-linear-gradient(top, rgba(255,255,255,.2) 0%, rgba(0,0,0,.2) 100%); /* FF3.6+ */ - background: #eee -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.2)), color-stop(100%,rgba(0,0,0,.2))); /* Chrome,Safari4+ */ - background: #eee -webkit-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Chrome10+,Safari5.1+ */ - background: #eee -o-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Opera11.10+ */ - background: #eee -ms-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* IE10+ */ - background: #eee linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* W3C */ - border: 1px solid #aaa; - border-top: 1px solid #ccc; - border-left: 1px solid #ccc; - padding: 4px 12px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - color: #444; - display: inline-block; - font-size: 11px; - font-weight: bold; - text-decoration: none; - text-shadow: 0 1px rgba(255, 255, 255, .75); - cursor: pointer; - margin-bottom: 20px; - line-height: 21px; - font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; } - - a.button:hover, - button:hover, - input[type="submit"]:hover, - input[type="reset"]:hover, - input[type="button"]:hover { - color: #222; - background: #ddd; /* Old browsers */ - background: #ddd -moz-linear-gradient(top, rgba(255,255,255,.3) 0%, rgba(0,0,0,.3) 100%); /* FF3.6+ */ - background: #ddd -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.3)), color-stop(100%,rgba(0,0,0,.3))); /* Chrome,Safari4+ */ - background: #ddd -webkit-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Chrome10+,Safari5.1+ */ - background: #ddd -o-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Opera11.10+ */ - background: #ddd -ms-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* IE10+ */ - background: #ddd linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* W3C */ - border: 1px solid #888; - border-top: 1px solid #aaa; - border-left: 1px solid #aaa; } - - a.button:active, - button:active, - input[type="submit"]:active, - input[type="reset"]:active, - input[type="button"]:active { - border: 1px solid #666; - background: #ccc; /* Old browsers */ - background: #ccc -moz-linear-gradient(top, rgba(255,255,255,.35) 0%, rgba(10,10,10,.4) 100%); /* FF3.6+ */ - background: #ccc -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.35)), color-stop(100%,rgba(10,10,10,.4))); /* Chrome,Safari4+ */ - background: #ccc -webkit-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Chrome10+,Safari5.1+ */ - background: #ccc -o-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Opera11.10+ */ - background: #ccc -ms-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* IE10+ */ - background: #ccc linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* W3C */ } - - .button.full-width, - button.full-width, - input[type="submit"].full-width, - input[type="reset"].full-width, - input[type="button"].full-width { - width: 100%; - padding-left: 0 !important; - padding-right: 0 !important; - text-align: center; } - - -/* #Tabs (activate in tabs.js) -================================================== */ - ul.tabs { - display: block; - margin: 0 0 20px 0; - padding: 0; - border-bottom: solid 1px #ddd; } - ul.tabs li { - display: block; - width: auto; - height: 30px; - padding: 0; - float: left; - margin-bottom: 0; } - ul.tabs li a { - display: block; - text-decoration: none; - width: auto; - height: 29px; - padding: 0px 20px; - line-height: 30px; - border: solid 1px #ddd; - border-width: 1px 1px 0 0; - margin: 0; - background: #f5f5f5; - font-size: 13px; } - ul.tabs li a.active { - background: #fff; - height: 30px; - position: relative; - top: -4px; - padding-top: 4px; - border-left-width: 1px; - margin: 0 0 0 -1px; - color: #111; - -moz-border-radius-topleft: 2px; - -webkit-border-top-left-radius: 2px; - border-top-left-radius: 2px; - -moz-border-radius-topright: 2px; - -webkit-border-top-right-radius: 2px; - border-top-right-radius: 2px; } - ul.tabs li:first-child a.active { - margin-left: 0; } - ul.tabs li:first-child a { - border-width: 1px 1px 0 1px; - -moz-border-radius-topleft: 2px; - -webkit-border-top-left-radius: 2px; - border-top-left-radius: 2px; } - ul.tabs li:last-child a { - -moz-border-radius-topright: 2px; - -webkit-border-top-right-radius: 2px; - border-top-right-radius: 2px; } - - ul.tabs-content { margin: 0; display: block; } - ul.tabs-content > li { display:none; } - ul.tabs-content > li.active { display: block; } - - /* Clearfixing tabs for beautiful stacking */ - ul.tabs:before, - ul.tabs:after { - content: '\0020'; - display: block; - overflow: hidden; - visibility: hidden; - width: 0; - height: 0; } - ul.tabs:after { - clear: both; } - ul.tabs { - zoom: 1; } - - -/* #Forms -================================================== */ - - form { - margin-bottom: 20px; } - fieldset { - margin-bottom: 20px; } - input[type="text"], - input[type="password"], - input[type="email"], - textarea, - select { - border: 1px solid #ccc; - padding: 6px 4px; - outline: none; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - border-radius: 2px; - font: 13px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #777; - margin: 0; - width: 210px; - max-width: 100%; - display: block; - margin-bottom: 20px; - background: #fff; } - select { - padding: 0; } - input[type="text"]:focus, - input[type="password"]:focus, - input[type="email"]:focus, - textarea:focus { - border: 1px solid #aaa; - color: #444; - -moz-box-shadow: 0 0 3px rgba(0,0,0,.2); - -webkit-box-shadow: 0 0 3px rgba(0,0,0,.2); - box-shadow: 0 0 3px rgba(0,0,0,.2); } - textarea { - min-height: 60px; } - label, - legend { - display: block; - font-weight: bold; - font-size: 13px; } - select { - width: 220px; } - input[type="checkbox"] { - display: inline; } - label span, - legend span { - font-weight: normal; - font-size: 13px; - color: #444; } - -/* #Misc -================================================== */ - .remove-bottom { margin-bottom: 0 !important; } - .half-bottom { margin-bottom: 10px !important; } - .add-bottom { margin-bottom: 20px !important; } - - diff --git a/_includes/css/layout.css b/_includes/css/layout.css deleted file mode 100644 index c71a6057f..000000000 --- a/_includes/css/layout.css +++ /dev/null @@ -1,86 +0,0 @@ -/* -* Skeleton V1.1 -* Copyright 2011, Dave Gamache -* www.getskeleton.com -* Free to use under the MIT license. -* http://www.opensource.org/licenses/mit-license.php -* 8/17/2011 -*/ - -/* Smaller than standard 960 (devices and browsers) */ -@media only screen and (max-width: 959px) { - #logo { - font-size: 21px; - margin-bottom: 15px; } - nav .button { - padding: 9px 20px 11px; } -} - -/* Tablet Portrait size to standard 960 (devices and browsers) */ -@media only screen and (min-width: 768px) and (max-width: 959px) { - nav { - width: 160px; } - .sidebar h2 { - font-size: 18px; - } - - #bio { - font-size: 14px; - } -} - -/* All Mobile Sizes (devices and browser) */ -@media only screen and (max-width: 767px) { - header h1 { font-size: 34px; line-height: 37px; } - nav { position: relative; } - nav ul, - #logo { text-align: left; } - .sidebar { - display: none; - } - .content { - border: 0px; - padding-left: 0px; - } -} - -/* Mobile Landscape Size to Tablet Portrait (devices and browsers) */ -@media only screen and (min-width: 480px) and (max-width: 767px) { - .sidebar { - display: none; - } - - h1 { - font-size: 28px; - line-height: 36px; - } -} - -/* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */ -@media only screen and (max-width: 479px) { - .sidebar { - display: none; - } - - h1 { - font-size: 24px; - line-height: 32px; - } - - h2 { - font-size: 22px; - line-height: 24px; - } -} - -.float-left { - float: left; -} - -.float-right { - float: right; -} - -.clear { - clear: both; -} diff --git a/_includes/css/pygments_themes/README.md b/_includes/css/pygments_themes/README.md deleted file mode 100644 index 83c205a2e..000000000 --- a/_includes/css/pygments_themes/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# jekyll-pygments-themes - -A set of CSS theme files for Pygments (Python-based code highlighting tool) -created from the original built-in Pygments styles, ready for use with Jekyll. - -## Theme Previews and Custom Theme Builder -- - -## Using Themes Without Jekyll -If you want to use the themes with something other than Jekyll, you may need to -remove or change the CSS style prefix of `.highlight`. - -## Links - -- [Jekyll](http://jekyllrb.com/) ([direct link to code highlighting documentation](http://jekyllrb.com/docs/templates/#code-snippet-highlighting)) -- [Pygments](http://pygments.org) - -## Hacking - -If you want to hack on the site, check out the [gh-pages](https://github.com/jwarby/jekyll-pygments-themes/tree/gh-pages) branch. - -## Acknowledgements -Forked from [richleland/pygments-css](https://github.com/richleland/pygments-css). diff --git a/_includes/css/pygments_themes/UNLICENSE.txt b/_includes/css/pygments_themes/UNLICENSE.txt deleted file mode 100644 index cf1ab25da..000000000 --- a/_includes/css/pygments_themes/UNLICENSE.txt +++ /dev/null @@ -1,24 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to diff --git a/_includes/css/pygments_themes/autumn.css b/_includes/css/pygments_themes/autumn.css deleted file mode 100644 index a5f3d4c3f..000000000 --- a/_includes/css/pygments_themes/autumn.css +++ /dev/null @@ -1,58 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #aaaaaa; font-style: italic } /* Comment */ -.highlight .err { color: #F00000; background-color: #F0A0A0 } /* Error */ -.highlight .k { color: #0000aa } /* Keyword */ -.highlight .cm { color: #aaaaaa; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #4c8317 } /* Comment.Preproc */ -.highlight .c1 { color: #aaaaaa; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #0000aa; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #aa0000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00aa00 } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { color: #0000aa } /* Keyword.Constant */ -.highlight .kd { color: #0000aa } /* Keyword.Declaration */ -.highlight .kn { color: #0000aa } /* Keyword.Namespace */ -.highlight .kp { color: #0000aa } /* Keyword.Pseudo */ -.highlight .kr { color: #0000aa } /* Keyword.Reserved */ -.highlight .kt { color: #00aaaa } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #aa5500 } /* Literal.String */ -.highlight .na { color: #1e90ff } /* Name.Attribute */ -.highlight .nb { color: #00aaaa } /* Name.Builtin */ -.highlight .nc { color: #00aa00; text-decoration: underline } /* Name.Class */ -.highlight .no { color: #aa0000 } /* Name.Constant */ -.highlight .nd { color: #888888 } /* Name.Decorator */ -.highlight .ni { color: #800000; font-weight: bold } /* Name.Entity */ -.highlight .nf { color: #00aa00 } /* Name.Function */ -.highlight .nn { color: #00aaaa; text-decoration: underline } /* Name.Namespace */ -.highlight .nt { color: #1e90ff; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #aa0000 } /* Name.Variable */ -.highlight .ow { color: #0000aa } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #009999 } /* Literal.Number.Float */ -.highlight .mh { color: #009999 } /* Literal.Number.Hex */ -.highlight .mi { color: #009999 } /* Literal.Number.Integer */ -.highlight .mo { color: #009999 } /* Literal.Number.Oct */ -.highlight .sb { color: #aa5500 } /* Literal.String.Backtick */ -.highlight .sc { color: #aa5500 } /* Literal.String.Char */ -.highlight .sd { color: #aa5500 } /* Literal.String.Doc */ -.highlight .s2 { color: #aa5500 } /* Literal.String.Double */ -.highlight .se { color: #aa5500 } /* Literal.String.Escape */ -.highlight .sh { color: #aa5500 } /* Literal.String.Heredoc */ -.highlight .si { color: #aa5500 } /* Literal.String.Interpol */ -.highlight .sx { color: #aa5500 } /* Literal.String.Other */ -.highlight .sr { color: #009999 } /* Literal.String.Regex */ -.highlight .s1 { color: #aa5500 } /* Literal.String.Single */ -.highlight .ss { color: #0000aa } /* Literal.String.Symbol */ -.highlight .bp { color: #00aaaa } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #aa0000 } /* Name.Variable.Class */ -.highlight .vg { color: #aa0000 } /* Name.Variable.Global */ -.highlight .vi { color: #aa0000 } /* Name.Variable.Instance */ -.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/borland.css b/_includes/css/pygments_themes/borland.css deleted file mode 100644 index 2e98c792f..000000000 --- a/_includes/css/pygments_themes/borland.css +++ /dev/null @@ -1,46 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #008800; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { color: #000080; font-weight: bold } /* Keyword */ -.highlight .cm { color: #008800; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #008080 } /* Comment.Preproc */ -.highlight .c1 { color: #008800; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #008800; font-weight: bold } /* Comment.Special */ -.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #999999 } /* Generic.Heading */ -.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { color: #000080; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #000080; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #000080; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #000080; font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { color: #000080; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #000080; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #0000FF } /* Literal.Number */ -.highlight .s { color: #0000FF } /* Literal.String */ -.highlight .na { color: #FF0000 } /* Name.Attribute */ -.highlight .nt { color: #000080; font-weight: bold } /* Name.Tag */ -.highlight .ow { font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #0000FF } /* Literal.Number.Float */ -.highlight .mh { color: #0000FF } /* Literal.Number.Hex */ -.highlight .mi { color: #0000FF } /* Literal.Number.Integer */ -.highlight .mo { color: #0000FF } /* Literal.Number.Oct */ -.highlight .sb { color: #0000FF } /* Literal.String.Backtick */ -.highlight .sc { color: #800080 } /* Literal.String.Char */ -.highlight .sd { color: #0000FF } /* Literal.String.Doc */ -.highlight .s2 { color: #0000FF } /* Literal.String.Double */ -.highlight .se { color: #0000FF } /* Literal.String.Escape */ -.highlight .sh { color: #0000FF } /* Literal.String.Heredoc */ -.highlight .si { color: #0000FF } /* Literal.String.Interpol */ -.highlight .sx { color: #0000FF } /* Literal.String.Other */ -.highlight .sr { color: #0000FF } /* Literal.String.Regex */ -.highlight .s1 { color: #0000FF } /* Literal.String.Single */ -.highlight .ss { color: #0000FF } /* Literal.String.Symbol */ -.highlight .il { color: #0000FF } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/bw.css b/_includes/css/pygments_themes/bw.css deleted file mode 100644 index 632756bbb..000000000 --- a/_includes/css/pygments_themes/bw.css +++ /dev/null @@ -1,34 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { font-weight: bold } /* Keyword */ -.highlight .cm { font-style: italic } /* Comment.Multiline */ -.highlight .c1 { font-style: italic } /* Comment.Single */ -.highlight .cs { font-style: italic } /* Comment.Special */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gh { font-weight: bold } /* Generic.Heading */ -.highlight .gp { font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { font-weight: bold } /* Generic.Subheading */ -.highlight .kc { font-weight: bold } /* Keyword.Constant */ -.highlight .kd { font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { font-weight: bold } /* Keyword.Namespace */ -.highlight .kr { font-weight: bold } /* Keyword.Reserved */ -.highlight .s { font-style: italic } /* Literal.String */ -.highlight .nc { font-weight: bold } /* Name.Class */ -.highlight .ni { font-weight: bold } /* Name.Entity */ -.highlight .ne { font-weight: bold } /* Name.Exception */ -.highlight .nn { font-weight: bold } /* Name.Namespace */ -.highlight .nt { font-weight: bold } /* Name.Tag */ -.highlight .ow { font-weight: bold } /* Operator.Word */ -.highlight .sb { font-style: italic } /* Literal.String.Backtick */ -.highlight .sc { font-style: italic } /* Literal.String.Char */ -.highlight .sd { font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { font-style: italic } /* Literal.String.Double */ -.highlight .se { font-weight: bold; font-style: italic } /* Literal.String.Escape */ -.highlight .sh { font-style: italic } /* Literal.String.Heredoc */ -.highlight .si { font-weight: bold; font-style: italic } /* Literal.String.Interpol */ -.highlight .sx { font-style: italic } /* Literal.String.Other */ -.highlight .sr { font-style: italic } /* Literal.String.Regex */ -.highlight .s1 { font-style: italic } /* Literal.String.Single */ -.highlight .ss { font-style: italic } /* Literal.String.Symbol */ diff --git a/_includes/css/pygments_themes/colorful.css b/_includes/css/pygments_themes/colorful.css deleted file mode 100644 index e5abd6909..000000000 --- a/_includes/css/pygments_themes/colorful.css +++ /dev/null @@ -1,61 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #808080 } /* Comment */ -.highlight .err { color: #F00000; background-color: #F0A0A0 } /* Error */ -.highlight .k { color: #008000; font-weight: bold } /* Keyword */ -.highlight .o { color: #303030 } /* Operator */ -.highlight .cm { color: #808080 } /* Comment.Multiline */ -.highlight .cp { color: #507090 } /* Comment.Preproc */ -.highlight .c1 { color: #808080 } /* Comment.Single */ -.highlight .cs { color: #cc0000; font-weight: bold } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #808080 } /* Generic.Output */ -.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0040D0 } /* Generic.Traceback */ -.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #303090; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #6000E0; font-weight: bold } /* Literal.Number */ -.highlight .s { background-color: #fff0f0 } /* Literal.String */ -.highlight .na { color: #0000C0 } /* Name.Attribute */ -.highlight .nb { color: #007020 } /* Name.Builtin */ -.highlight .nc { color: #B00060; font-weight: bold } /* Name.Class */ -.highlight .no { color: #003060; font-weight: bold } /* Name.Constant */ -.highlight .nd { color: #505050; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #800000; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #F00000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #0060B0; font-weight: bold } /* Name.Function */ -.highlight .nl { color: #907000; font-weight: bold } /* Name.Label */ -.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #007000 } /* Name.Tag */ -.highlight .nv { color: #906030 } /* Name.Variable */ -.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */ -.highlight .mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */ -.highlight .mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */ -.highlight .mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */ -.highlight .sb { background-color: #fff0f0 } /* Literal.String.Backtick */ -.highlight .sc { color: #0040D0 } /* Literal.String.Char */ -.highlight .sd { color: #D04020 } /* Literal.String.Doc */ -.highlight .s2 { background-color: #fff0f0 } /* Literal.String.Double */ -.highlight .se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */ -.highlight .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */ -.highlight .si { background-color: #e0e0e0 } /* Literal.String.Interpol */ -.highlight .sx { color: #D02000; background-color: #fff0f0 } /* Literal.String.Other */ -.highlight .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ -.highlight .s1 { background-color: #fff0f0 } /* Literal.String.Single */ -.highlight .ss { color: #A06000 } /* Literal.String.Symbol */ -.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #306090 } /* Name.Variable.Class */ -.highlight .vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */ -.highlight .vi { color: #3030B0 } /* Name.Variable.Instance */ -.highlight .il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/default.css b/_includes/css/pygments_themes/default.css deleted file mode 100644 index 8070f2fb6..000000000 --- a/_includes/css/pygments_themes/default.css +++ /dev/null @@ -1,61 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #408080; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #008000; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #BC7A00 } /* Comment.Preproc */ -.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #808080 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0040D0 } /* Generic.Traceback */ -.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #008000 } /* Keyword.Pseudo */ -.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #B00040 } /* Keyword.Type */ -.highlight .m { color: #666666 } /* Literal.Number */ -.highlight .s { color: #BA2121 } /* Literal.String */ -.highlight .na { color: #7D9029 } /* Name.Attribute */ -.highlight .nb { color: #008000 } /* Name.Builtin */ -.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ -.highlight .no { color: #880000 } /* Name.Constant */ -.highlight .nd { color: #AA22FF } /* Name.Decorator */ -.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #0000FF } /* Name.Function */ -.highlight .nl { color: #A0A000 } /* Name.Label */ -.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #19177C } /* Name.Variable */ -.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #666666 } /* Literal.Number.Float */ -.highlight .mh { color: #666666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666666 } /* Literal.Number.Oct */ -.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -.highlight .sc { color: #BA2121 } /* Literal.String.Char */ -.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ -.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -.highlight .ss { color: #19177C } /* Literal.String.Symbol */ -.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #19177C } /* Name.Variable.Class */ -.highlight .vg { color: #19177C } /* Name.Variable.Global */ -.highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/emacs.css b/_includes/css/pygments_themes/emacs.css deleted file mode 100644 index 489c0ad51..000000000 --- a/_includes/css/pygments_themes/emacs.css +++ /dev/null @@ -1,61 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #008800; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #AA22FF; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .cm { color: #008800; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #008800 } /* Comment.Preproc */ -.highlight .c1 { color: #008800; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #008800; font-weight: bold } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #808080 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0040D0 } /* Generic.Traceback */ -.highlight .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #AA22FF; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #AA22FF } /* Keyword.Pseudo */ -.highlight .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #00BB00; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #666666 } /* Literal.Number */ -.highlight .s { color: #BB4444 } /* Literal.String */ -.highlight .na { color: #BB4444 } /* Name.Attribute */ -.highlight .nb { color: #AA22FF } /* Name.Builtin */ -.highlight .nc { color: #0000FF } /* Name.Class */ -.highlight .no { color: #880000 } /* Name.Constant */ -.highlight .nd { color: #AA22FF } /* Name.Decorator */ -.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #00A000 } /* Name.Function */ -.highlight .nl { color: #A0A000 } /* Name.Label */ -.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #B8860B } /* Name.Variable */ -.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #666666 } /* Literal.Number.Float */ -.highlight .mh { color: #666666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666666 } /* Literal.Number.Oct */ -.highlight .sb { color: #BB4444 } /* Literal.String.Backtick */ -.highlight .sc { color: #BB4444 } /* Literal.String.Char */ -.highlight .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #BB4444 } /* Literal.String.Double */ -.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BB4444 } /* Literal.String.Heredoc */ -.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ -.highlight .s1 { color: #BB4444 } /* Literal.String.Single */ -.highlight .ss { color: #B8860B } /* Literal.String.Symbol */ -.highlight .bp { color: #AA22FF } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #B8860B } /* Name.Variable.Class */ -.highlight .vg { color: #B8860B } /* Name.Variable.Global */ -.highlight .vi { color: #B8860B } /* Name.Variable.Instance */ -.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/friendly.css b/_includes/css/pygments_themes/friendly.css deleted file mode 100644 index 846e04814..000000000 --- a/_includes/css/pygments_themes/friendly.css +++ /dev/null @@ -1,61 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #60a0b0; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #007020; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #007020 } /* Comment.Preproc */ -.highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #808080 } /* Generic.Output */ -.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0040D0 } /* Generic.Traceback */ -.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #007020 } /* Keyword.Pseudo */ -.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #902000 } /* Keyword.Type */ -.highlight .m { color: #40a070 } /* Literal.Number */ -.highlight .s { color: #4070a0 } /* Literal.String */ -.highlight .na { color: #4070a0 } /* Name.Attribute */ -.highlight .nb { color: #007020 } /* Name.Builtin */ -.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ -.highlight .no { color: #60add5 } /* Name.Constant */ -.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #007020 } /* Name.Exception */ -.highlight .nf { color: #06287e } /* Name.Function */ -.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ -.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #bb60d5 } /* Name.Variable */ -.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #40a070 } /* Literal.Number.Float */ -.highlight .mh { color: #40a070 } /* Literal.Number.Hex */ -.highlight .mi { color: #40a070 } /* Literal.Number.Integer */ -.highlight .mo { color: #40a070 } /* Literal.Number.Oct */ -.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ -.highlight .sc { color: #4070a0 } /* Literal.String.Char */ -.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ -.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ -.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ -.highlight .sx { color: #c65d09 } /* Literal.String.Other */ -.highlight .sr { color: #235388 } /* Literal.String.Regex */ -.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ -.highlight .ss { color: #517918 } /* Literal.String.Symbol */ -.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ -.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ -.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ -.highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/fruity.css b/_includes/css/pygments_themes/fruity.css deleted file mode 100644 index 915235032..000000000 --- a/_includes/css/pygments_themes/fruity.css +++ /dev/null @@ -1,70 +0,0 @@ -.highlight pre { background-color: #333; } -.highlight .hll { background-color: #333333 } -.highlight .c { color: #008800; font-style: italic; background-color: #0f140f } /* Comment */ -.highlight .err { color: #ffffff } /* Error */ -.highlight .g { color: #ffffff } /* Generic */ -.highlight .k { color: #fb660a; font-weight: bold } /* Keyword */ -.highlight .l { color: #ffffff } /* Literal */ -.highlight .n { color: #ffffff } /* Name */ -.highlight .o { color: #ffffff } /* Operator */ -.highlight .x { color: #ffffff } /* Other */ -.highlight .p { color: #ffffff } /* Punctuation */ -.highlight .cm { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Multiline */ -.highlight .cp { color: #ff0007; font-weight: bold; font-style: italic; background-color: #0f140f } /* Comment.Preproc */ -.highlight .c1 { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Single */ -.highlight .cs { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Special */ -.highlight .gd { color: #ffffff } /* Generic.Deleted */ -.highlight .ge { color: #ffffff } /* Generic.Emph */ -.highlight .gr { color: #ffffff } /* Generic.Error */ -.highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #ffffff } /* Generic.Inserted */ -.highlight .go { color: #444444; background-color: #222222 } /* Generic.Output */ -.highlight .gp { color: #ffffff } /* Generic.Prompt */ -.highlight .gs { color: #ffffff } /* Generic.Strong */ -.highlight .gu { color: #ffffff; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #ffffff } /* Generic.Traceback */ -.highlight .kc { color: #fb660a; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #fb660a; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #fb660a; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #fb660a } /* Keyword.Pseudo */ -.highlight .kr { color: #fb660a; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #cdcaa9; font-weight: bold } /* Keyword.Type */ -.highlight .ld { color: #ffffff } /* Literal.Date */ -.highlight .m { color: #0086f7; font-weight: bold } /* Literal.Number */ -.highlight .s { color: #0086d2 } /* Literal.String */ -.highlight .na { color: #ff0086; font-weight: bold } /* Name.Attribute */ -.highlight .nb { color: #ffffff } /* Name.Builtin */ -.highlight .nc { color: #ffffff } /* Name.Class */ -.highlight .no { color: #0086d2 } /* Name.Constant */ -.highlight .nd { color: #ffffff } /* Name.Decorator */ -.highlight .ni { color: #ffffff } /* Name.Entity */ -.highlight .ne { color: #ffffff } /* Name.Exception */ -.highlight .nf { color: #ff0086; font-weight: bold } /* Name.Function */ -.highlight .nl { color: #ffffff } /* Name.Label */ -.highlight .nn { color: #ffffff } /* Name.Namespace */ -.highlight .nx { color: #ffffff } /* Name.Other */ -.highlight .py { color: #ffffff } /* Name.Property */ -.highlight .nt { color: #fb660a; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #fb660a } /* Name.Variable */ -.highlight .ow { color: #ffffff } /* Operator.Word */ -.highlight .w { color: #888888 } /* Text.Whitespace */ -.highlight .mf { color: #0086f7; font-weight: bold } /* Literal.Number.Float */ -.highlight .mh { color: #0086f7; font-weight: bold } /* Literal.Number.Hex */ -.highlight .mi { color: #0086f7; font-weight: bold } /* Literal.Number.Integer */ -.highlight .mo { color: #0086f7; font-weight: bold } /* Literal.Number.Oct */ -.highlight .sb { color: #0086d2 } /* Literal.String.Backtick */ -.highlight .sc { color: #0086d2 } /* Literal.String.Char */ -.highlight .sd { color: #0086d2 } /* Literal.String.Doc */ -.highlight .s2 { color: #0086d2 } /* Literal.String.Double */ -.highlight .se { color: #0086d2 } /* Literal.String.Escape */ -.highlight .sh { color: #0086d2 } /* Literal.String.Heredoc */ -.highlight .si { color: #0086d2 } /* Literal.String.Interpol */ -.highlight .sx { color: #0086d2 } /* Literal.String.Other */ -.highlight .sr { color: #0086d2 } /* Literal.String.Regex */ -.highlight .s1 { color: #0086d2 } /* Literal.String.Single */ -.highlight .ss { color: #0086d2 } /* Literal.String.Symbol */ -.highlight .bp { color: #ffffff } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #fb660a } /* Name.Variable.Class */ -.highlight .vg { color: #fb660a } /* Name.Variable.Global */ -.highlight .vi { color: #fb660a } /* Name.Variable.Instance */ -.highlight .il { color: #0086f7; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/github.css b/_includes/css/pygments_themes/github.css deleted file mode 100644 index 5c45e6776..000000000 --- a/_includes/css/pygments_themes/github.css +++ /dev/null @@ -1,61 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #999988; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { color: #000000; font-weight: bold } /* Keyword */ -.highlight .o { color: #000000; font-weight: bold } /* Operator */ -.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ -.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #999999 } /* Generic.Heading */ -.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #d01040 } /* Literal.String */ -.highlight .na { color: #008080 } /* Name.Attribute */ -.highlight .nb { color: #0086B3 } /* Name.Builtin */ -.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ -.highlight .no { color: #008080 } /* Name.Constant */ -.highlight .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #800080 } /* Name.Entity */ -.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ -.highlight .nl { color: #990000; font-weight: bold } /* Name.Label */ -.highlight .nn { color: #555555 } /* Name.Namespace */ -.highlight .nt { color: #000080 } /* Name.Tag */ -.highlight .nv { color: #008080 } /* Name.Variable */ -.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #009999 } /* Literal.Number.Float */ -.highlight .mh { color: #009999 } /* Literal.Number.Hex */ -.highlight .mi { color: #009999 } /* Literal.Number.Integer */ -.highlight .mo { color: #009999 } /* Literal.Number.Oct */ -.highlight .sb { color: #d01040 } /* Literal.String.Backtick */ -.highlight .sc { color: #d01040 } /* Literal.String.Char */ -.highlight .sd { color: #d01040 } /* Literal.String.Doc */ -.highlight .s2 { color: #d01040 } /* Literal.String.Double */ -.highlight .se { color: #d01040 } /* Literal.String.Escape */ -.highlight .sh { color: #d01040 } /* Literal.String.Heredoc */ -.highlight .si { color: #d01040 } /* Literal.String.Interpol */ -.highlight .sx { color: #d01040 } /* Literal.String.Other */ -.highlight .sr { color: #009926 } /* Literal.String.Regex */ -.highlight .s1 { color: #d01040 } /* Literal.String.Single */ -.highlight .ss { color: #990073 } /* Literal.String.Symbol */ -.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #008080 } /* Name.Variable.Class */ -.highlight .vg { color: #008080 } /* Name.Variable.Global */ -.highlight .vi { color: #008080 } /* Name.Variable.Instance */ -.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/manni.css b/_includes/css/pygments_themes/manni.css deleted file mode 100644 index d5bc47e14..000000000 --- a/_includes/css/pygments_themes/manni.css +++ /dev/null @@ -1,61 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #0099FF; font-style: italic } /* Comment */ -.highlight .err { color: #AA0000; background-color: #FFAAAA } /* Error */ -.highlight .k { color: #006699; font-weight: bold } /* Keyword */ -.highlight .o { color: #555555 } /* Operator */ -.highlight .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #009999 } /* Comment.Preproc */ -.highlight .c1 { color: #0099FF; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #0099FF; font-weight: bold; font-style: italic } /* Comment.Special */ -.highlight .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #003300; font-weight: bold } /* Generic.Heading */ -.highlight .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ -.highlight .go { color: #AAAAAA } /* Generic.Output */ -.highlight .gp { color: #000099; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #003300; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #99CC66 } /* Generic.Traceback */ -.highlight .kc { color: #006699; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #006699; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #006699; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #006699 } /* Keyword.Pseudo */ -.highlight .kr { color: #006699; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #007788; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #FF6600 } /* Literal.Number */ -.highlight .s { color: #CC3300 } /* Literal.String */ -.highlight .na { color: #330099 } /* Name.Attribute */ -.highlight .nb { color: #336666 } /* Name.Builtin */ -.highlight .nc { color: #00AA88; font-weight: bold } /* Name.Class */ -.highlight .no { color: #336600 } /* Name.Constant */ -.highlight .nd { color: #9999FF } /* Name.Decorator */ -.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #CC0000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #CC00FF } /* Name.Function */ -.highlight .nl { color: #9999FF } /* Name.Label */ -.highlight .nn { color: #00CCFF; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #330099; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #003333 } /* Name.Variable */ -.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #FF6600 } /* Literal.Number.Float */ -.highlight .mh { color: #FF6600 } /* Literal.Number.Hex */ -.highlight .mi { color: #FF6600 } /* Literal.Number.Integer */ -.highlight .mo { color: #FF6600 } /* Literal.Number.Oct */ -.highlight .sb { color: #CC3300 } /* Literal.String.Backtick */ -.highlight .sc { color: #CC3300 } /* Literal.String.Char */ -.highlight .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #CC3300 } /* Literal.String.Double */ -.highlight .se { color: #CC3300; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #CC3300 } /* Literal.String.Heredoc */ -.highlight .si { color: #AA0000 } /* Literal.String.Interpol */ -.highlight .sx { color: #CC3300 } /* Literal.String.Other */ -.highlight .sr { color: #33AAAA } /* Literal.String.Regex */ -.highlight .s1 { color: #CC3300 } /* Literal.String.Single */ -.highlight .ss { color: #FFCC33 } /* Literal.String.Symbol */ -.highlight .bp { color: #336666 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #003333 } /* Name.Variable.Class */ -.highlight .vg { color: #003333 } /* Name.Variable.Global */ -.highlight .vi { color: #003333 } /* Name.Variable.Instance */ -.highlight .il { color: #FF6600 } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/monokai.css b/_includes/css/pygments_themes/monokai.css deleted file mode 100644 index e020d51f6..000000000 --- a/_includes/css/pygments_themes/monokai.css +++ /dev/null @@ -1,65 +0,0 @@ -.highlight pre { background-color: #272822; } -.highlight .hll { background-color: #272822; } -.highlight .c { color: #75715e } /* Comment */ -.highlight .err { color: #960050; background-color: #1e0010 } /* Error */ -.highlight .k { color: #66d9ef } /* Keyword */ -.highlight .l { color: #ae81ff } /* Literal */ -.highlight .n { color: #f8f8f2 } /* Name */ -.highlight .o { color: #f92672 } /* Operator */ -.highlight .p { color: #f8f8f2 } /* Punctuation */ -.highlight .cm { color: #75715e } /* Comment.Multiline */ -.highlight .cp { color: #75715e } /* Comment.Preproc */ -.highlight .c1 { color: #75715e } /* Comment.Single */ -.highlight .cs { color: #75715e } /* Comment.Special */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .kc { color: #66d9ef } /* Keyword.Constant */ -.highlight .kd { color: #66d9ef } /* Keyword.Declaration */ -.highlight .kn { color: #f92672 } /* Keyword.Namespace */ -.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ -.highlight .kr { color: #66d9ef } /* Keyword.Reserved */ -.highlight .kt { color: #66d9ef } /* Keyword.Type */ -.highlight .ld { color: #e6db74 } /* Literal.Date */ -.highlight .m { color: #ae81ff } /* Literal.Number */ -.highlight .s { color: #e6db74 } /* Literal.String */ -.highlight .na { color: #a6e22e } /* Name.Attribute */ -.highlight .nb { color: #f8f8f2 } /* Name.Builtin */ -.highlight .nc { color: #a6e22e } /* Name.Class */ -.highlight .no { color: #66d9ef } /* Name.Constant */ -.highlight .nd { color: #a6e22e } /* Name.Decorator */ -.highlight .ni { color: #f8f8f2 } /* Name.Entity */ -.highlight .ne { color: #a6e22e } /* Name.Exception */ -.highlight .nf { color: #a6e22e } /* Name.Function */ -.highlight .nl { color: #f8f8f2 } /* Name.Label */ -.highlight .nn { color: #f8f8f2 } /* Name.Namespace */ -.highlight .nx { color: #a6e22e } /* Name.Other */ -.highlight .py { color: #f8f8f2 } /* Name.Property */ -.highlight .nt { color: #f92672 } /* Name.Tag */ -.highlight .nv { color: #f8f8f2 } /* Name.Variable */ -.highlight .ow { color: #f92672 } /* Operator.Word */ -.highlight .w { color: #f8f8f2 } /* Text.Whitespace */ -.highlight .mf { color: #ae81ff } /* Literal.Number.Float */ -.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ -.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ -.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ -.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ -.highlight .sc { color: #e6db74 } /* Literal.String.Char */ -.highlight .sd { color: #e6db74 } /* Literal.String.Doc */ -.highlight .s2 { color: #e6db74 } /* Literal.String.Double */ -.highlight .se { color: #ae81ff } /* Literal.String.Escape */ -.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ -.highlight .si { color: #e6db74 } /* Literal.String.Interpol */ -.highlight .sx { color: #e6db74 } /* Literal.String.Other */ -.highlight .sr { color: #e6db74 } /* Literal.String.Regex */ -.highlight .s1 { color: #e6db74 } /* Literal.String.Single */ -.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ -.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ -.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ -.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ -.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ - -.highlight .gh { } /* Generic Heading & Diff Header */ -.highlight .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ -.highlight .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ -.highlight .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ diff --git a/_includes/css/pygments_themes/murphy.css b/_includes/css/pygments_themes/murphy.css deleted file mode 100644 index 482d46b57..000000000 --- a/_includes/css/pygments_themes/murphy.css +++ /dev/null @@ -1,61 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #606060; font-style: italic } /* Comment */ -.highlight .err { color: #F00000; background-color: #F0A0A0 } /* Error */ -.highlight .k { color: #208090; font-weight: bold } /* Keyword */ -.highlight .o { color: #303030 } /* Operator */ -.highlight .cm { color: #606060; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #507090 } /* Comment.Preproc */ -.highlight .c1 { color: #606060; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #c00000; font-weight: bold; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #808080 } /* Generic.Output */ -.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0040D0 } /* Generic.Traceback */ -.highlight .kc { color: #208090; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #208090; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #208090; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #0080f0; font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { color: #208090; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #6060f0; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #6000E0; font-weight: bold } /* Literal.Number */ -.highlight .s { background-color: #e0e0ff } /* Literal.String */ -.highlight .na { color: #000070 } /* Name.Attribute */ -.highlight .nb { color: #007020 } /* Name.Builtin */ -.highlight .nc { color: #e090e0; font-weight: bold } /* Name.Class */ -.highlight .no { color: #50e0d0; font-weight: bold } /* Name.Constant */ -.highlight .nd { color: #505050; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #800000 } /* Name.Entity */ -.highlight .ne { color: #F00000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #50e0d0; font-weight: bold } /* Name.Function */ -.highlight .nl { color: #907000; font-weight: bold } /* Name.Label */ -.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #007000 } /* Name.Tag */ -.highlight .nv { color: #003060 } /* Name.Variable */ -.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */ -.highlight .mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */ -.highlight .mi { color: #6060f0; font-weight: bold } /* Literal.Number.Integer */ -.highlight .mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */ -.highlight .sb { background-color: #e0e0ff } /* Literal.String.Backtick */ -.highlight .sc { color: #8080F0 } /* Literal.String.Char */ -.highlight .sd { color: #D04020 } /* Literal.String.Doc */ -.highlight .s2 { background-color: #e0e0ff } /* Literal.String.Double */ -.highlight .se { color: #606060; font-weight: bold; background-color: #e0e0ff } /* Literal.String.Escape */ -.highlight .sh { background-color: #e0e0ff } /* Literal.String.Heredoc */ -.highlight .si { background-color: #e0e0e0 } /* Literal.String.Interpol */ -.highlight .sx { color: #f08080; background-color: #e0e0ff } /* Literal.String.Other */ -.highlight .sr { color: #000000; background-color: #e0e0ff } /* Literal.String.Regex */ -.highlight .s1 { background-color: #e0e0ff } /* Literal.String.Single */ -.highlight .ss { color: #f0c080 } /* Literal.String.Symbol */ -.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #c0c0f0 } /* Name.Variable.Class */ -.highlight .vg { color: #f08040 } /* Name.Variable.Global */ -.highlight .vi { color: #a0a0f0 } /* Name.Variable.Instance */ -.highlight .il { color: #6060f0; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/native.css b/_includes/css/pygments_themes/native.css deleted file mode 100644 index eac4a783c..000000000 --- a/_includes/css/pygments_themes/native.css +++ /dev/null @@ -1,70 +0,0 @@ -.highlight pre { background-color: #404040 } -.highlight .hll { background-color: #404040 } -.highlight .c { color: #999999; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .g { color: #d0d0d0 } /* Generic */ -.highlight .k { color: #6ab825; font-weight: bold } /* Keyword */ -.highlight .l { color: #d0d0d0 } /* Literal */ -.highlight .n { color: #d0d0d0 } /* Name */ -.highlight .o { color: #d0d0d0 } /* Operator */ -.highlight .x { color: #d0d0d0 } /* Other */ -.highlight .p { color: #d0d0d0 } /* Punctuation */ -.highlight .cm { color: #999999; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #cd2828; font-weight: bold } /* Comment.Preproc */ -.highlight .c1 { color: #999999; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */ -.highlight .gd { color: #d22323 } /* Generic.Deleted */ -.highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #d22323 } /* Generic.Error */ -.highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #589819 } /* Generic.Inserted */ -.highlight .go { color: #cccccc } /* Generic.Output */ -.highlight .gp { color: #aaaaaa } /* Generic.Prompt */ -.highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ -.highlight .gt { color: #d22323 } /* Generic.Traceback */ -.highlight .kc { color: #6ab825; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #6ab825; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #6ab825; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #6ab825 } /* Keyword.Pseudo */ -.highlight .kr { color: #6ab825; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #6ab825; font-weight: bold } /* Keyword.Type */ -.highlight .ld { color: #d0d0d0 } /* Literal.Date */ -.highlight .m { color: #3677a9 } /* Literal.Number */ -.highlight .s { color: #ed9d13 } /* Literal.String */ -.highlight .na { color: #bbbbbb } /* Name.Attribute */ -.highlight .nb { color: #24909d } /* Name.Builtin */ -.highlight .nc { color: #447fcf; text-decoration: underline } /* Name.Class */ -.highlight .no { color: #40ffff } /* Name.Constant */ -.highlight .nd { color: #ffa500 } /* Name.Decorator */ -.highlight .ni { color: #d0d0d0 } /* Name.Entity */ -.highlight .ne { color: #bbbbbb } /* Name.Exception */ -.highlight .nf { color: #447fcf } /* Name.Function */ -.highlight .nl { color: #d0d0d0 } /* Name.Label */ -.highlight .nn { color: #447fcf; text-decoration: underline } /* Name.Namespace */ -.highlight .nx { color: #d0d0d0 } /* Name.Other */ -.highlight .py { color: #d0d0d0 } /* Name.Property */ -.highlight .nt { color: #6ab825; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #40ffff } /* Name.Variable */ -.highlight .ow { color: #6ab825; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #666666 } /* Text.Whitespace */ -.highlight .mf { color: #3677a9 } /* Literal.Number.Float */ -.highlight .mh { color: #3677a9 } /* Literal.Number.Hex */ -.highlight .mi { color: #3677a9 } /* Literal.Number.Integer */ -.highlight .mo { color: #3677a9 } /* Literal.Number.Oct */ -.highlight .sb { color: #ed9d13 } /* Literal.String.Backtick */ -.highlight .sc { color: #ed9d13 } /* Literal.String.Char */ -.highlight .sd { color: #ed9d13 } /* Literal.String.Doc */ -.highlight .s2 { color: #ed9d13 } /* Literal.String.Double */ -.highlight .se { color: #ed9d13 } /* Literal.String.Escape */ -.highlight .sh { color: #ed9d13 } /* Literal.String.Heredoc */ -.highlight .si { color: #ed9d13 } /* Literal.String.Interpol */ -.highlight .sx { color: #ffa500 } /* Literal.String.Other */ -.highlight .sr { color: #ed9d13 } /* Literal.String.Regex */ -.highlight .s1 { color: #ed9d13 } /* Literal.String.Single */ -.highlight .ss { color: #ed9d13 } /* Literal.String.Symbol */ -.highlight .bp { color: #24909d } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #40ffff } /* Name.Variable.Class */ -.highlight .vg { color: #40ffff } /* Name.Variable.Global */ -.highlight .vi { color: #40ffff } /* Name.Variable.Instance */ -.highlight .il { color: #3677a9 } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/pastie.css b/_includes/css/pygments_themes/pastie.css deleted file mode 100644 index 538bdc618..000000000 --- a/_includes/css/pygments_themes/pastie.css +++ /dev/null @@ -1,60 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #888888 } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { color: #008800; font-weight: bold } /* Keyword */ -.highlight .cm { color: #888888 } /* Comment.Multiline */ -.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ -.highlight .c1 { color: #888888 } /* Comment.Single */ -.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ -.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #303030 } /* Generic.Heading */ -.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #606060 } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #008800 } /* Keyword.Pseudo */ -.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ -.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ -.highlight .na { color: #336699 } /* Name.Attribute */ -.highlight .nb { color: #003388 } /* Name.Builtin */ -.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ -.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ -.highlight .nd { color: #555555 } /* Name.Decorator */ -.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ -.highlight .nl { color: #336699; font-style: italic } /* Name.Label */ -.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ -.highlight .py { color: #336699; font-weight: bold } /* Name.Property */ -.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #336699 } /* Name.Variable */ -.highlight .ow { color: #008800 } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ -.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ -.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ -.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ -.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ -.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ -.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ -.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ -.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ -.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ -.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ -.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ -.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ -.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ -.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ -.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #336699 } /* Name.Variable.Class */ -.highlight .vg { color: #dd7700 } /* Name.Variable.Global */ -.highlight .vi { color: #3333bb } /* Name.Variable.Instance */ -.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/perldoc.css b/_includes/css/pygments_themes/perldoc.css deleted file mode 100644 index 50516f227..000000000 --- a/_includes/css/pygments_themes/perldoc.css +++ /dev/null @@ -1,58 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #228B22 } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { color: #8B008B; font-weight: bold } /* Keyword */ -.highlight .cm { color: #228B22 } /* Comment.Multiline */ -.highlight .cp { color: #1e889b } /* Comment.Preproc */ -.highlight .c1 { color: #228B22 } /* Comment.Single */ -.highlight .cs { color: #8B008B; font-weight: bold } /* Comment.Special */ -.highlight .gd { color: #aa0000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00aa00 } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { color: #8B008B; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #8B008B; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #8B008B; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #8B008B; font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { color: #8B008B; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #a7a7a7; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #B452CD } /* Literal.Number */ -.highlight .s { color: #CD5555 } /* Literal.String */ -.highlight .na { color: #658b00 } /* Name.Attribute */ -.highlight .nb { color: #658b00 } /* Name.Builtin */ -.highlight .nc { color: #008b45; font-weight: bold } /* Name.Class */ -.highlight .no { color: #00688B } /* Name.Constant */ -.highlight .nd { color: #707a7c } /* Name.Decorator */ -.highlight .ne { color: #008b45; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #008b45 } /* Name.Function */ -.highlight .nn { color: #008b45; text-decoration: underline } /* Name.Namespace */ -.highlight .nt { color: #8B008B; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #00688B } /* Name.Variable */ -.highlight .ow { color: #8B008B } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #B452CD } /* Literal.Number.Float */ -.highlight .mh { color: #B452CD } /* Literal.Number.Hex */ -.highlight .mi { color: #B452CD } /* Literal.Number.Integer */ -.highlight .mo { color: #B452CD } /* Literal.Number.Oct */ -.highlight .sb { color: #CD5555 } /* Literal.String.Backtick */ -.highlight .sc { color: #CD5555 } /* Literal.String.Char */ -.highlight .sd { color: #CD5555 } /* Literal.String.Doc */ -.highlight .s2 { color: #CD5555 } /* Literal.String.Double */ -.highlight .se { color: #CD5555 } /* Literal.String.Escape */ -.highlight .sh { color: #1c7e71; font-style: italic } /* Literal.String.Heredoc */ -.highlight .si { color: #CD5555 } /* Literal.String.Interpol */ -.highlight .sx { color: #cb6c20 } /* Literal.String.Other */ -.highlight .sr { color: #1c7e71 } /* Literal.String.Regex */ -.highlight .s1 { color: #CD5555 } /* Literal.String.Single */ -.highlight .ss { color: #CD5555 } /* Literal.String.Symbol */ -.highlight .bp { color: #658b00 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #00688B } /* Name.Variable.Class */ -.highlight .vg { color: #00688B } /* Name.Variable.Global */ -.highlight .vi { color: #00688B } /* Name.Variable.Instance */ -.highlight .il { color: #B452CD } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/tango.css b/_includes/css/pygments_themes/tango.css deleted file mode 100644 index bfd380365..000000000 --- a/_includes/css/pygments_themes/tango.css +++ /dev/null @@ -1,69 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #8f5902; font-style: italic } /* Comment */ -.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ -.highlight .g { color: #000000 } /* Generic */ -.highlight .k { color: #204a87; font-weight: bold } /* Keyword */ -.highlight .l { color: #000000 } /* Literal */ -.highlight .n { color: #000000 } /* Name */ -.highlight .o { color: #ce5c00; font-weight: bold } /* Operator */ -.highlight .x { color: #000000 } /* Other */ -.highlight .p { color: #000000; font-weight: bold } /* Punctuation */ -.highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #8f5902; font-style: italic } /* Comment.Preproc */ -.highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #a40000 } /* Generic.Deleted */ -.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #ef2929 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #000000; font-style: italic } /* Generic.Output */ -.highlight .gp { color: #8f5902 } /* Generic.Prompt */ -.highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ -.highlight .kc { color: #204a87; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #204a87; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #204a87; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #204a87; font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { color: #204a87; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #204a87; font-weight: bold } /* Keyword.Type */ -.highlight .ld { color: #000000 } /* Literal.Date */ -.highlight .m { color: #0000cf; font-weight: bold } /* Literal.Number */ -.highlight .s { color: #4e9a06 } /* Literal.String */ -.highlight .na { color: #c4a000 } /* Name.Attribute */ -.highlight .nb { color: #204a87 } /* Name.Builtin */ -.highlight .nc { color: #000000 } /* Name.Class */ -.highlight .no { color: #000000 } /* Name.Constant */ -.highlight .nd { color: #5c35cc; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #ce5c00 } /* Name.Entity */ -.highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #000000 } /* Name.Function */ -.highlight .nl { color: #f57900 } /* Name.Label */ -.highlight .nn { color: #000000 } /* Name.Namespace */ -.highlight .nx { color: #000000 } /* Name.Other */ -.highlight .py { color: #000000 } /* Name.Property */ -.highlight .nt { color: #204a87; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #000000 } /* Name.Variable */ -.highlight .ow { color: #204a87; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ -.highlight .mf { color: #0000cf; font-weight: bold } /* Literal.Number.Float */ -.highlight .mh { color: #0000cf; font-weight: bold } /* Literal.Number.Hex */ -.highlight .mi { color: #0000cf; font-weight: bold } /* Literal.Number.Integer */ -.highlight .mo { color: #0000cf; font-weight: bold } /* Literal.Number.Oct */ -.highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ -.highlight .sc { color: #4e9a06 } /* Literal.String.Char */ -.highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ -.highlight .se { color: #4e9a06 } /* Literal.String.Escape */ -.highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ -.highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ -.highlight .sx { color: #4e9a06 } /* Literal.String.Other */ -.highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ -.highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ -.highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ -.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #000000 } /* Name.Variable.Class */ -.highlight .vg { color: #000000 } /* Name.Variable.Global */ -.highlight .vi { color: #000000 } /* Name.Variable.Instance */ -.highlight .il { color: #0000cf; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/trac.css b/_includes/css/pygments_themes/trac.css deleted file mode 100644 index 851ba3c1a..000000000 --- a/_includes/css/pygments_themes/trac.css +++ /dev/null @@ -1,59 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #999988; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { font-weight: bold } /* Keyword */ -.highlight .o { font-weight: bold } /* Operator */ -.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ -.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #999999 } /* Generic.Heading */ -.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { font-weight: bold } /* Keyword.Constant */ -.highlight .kd { font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #bb8844 } /* Literal.String */ -.highlight .na { color: #008080 } /* Name.Attribute */ -.highlight .nb { color: #999999 } /* Name.Builtin */ -.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ -.highlight .no { color: #008080 } /* Name.Constant */ -.highlight .ni { color: #800080 } /* Name.Entity */ -.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ -.highlight .nn { color: #555555 } /* Name.Namespace */ -.highlight .nt { color: #000080 } /* Name.Tag */ -.highlight .nv { color: #008080 } /* Name.Variable */ -.highlight .ow { font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #009999 } /* Literal.Number.Float */ -.highlight .mh { color: #009999 } /* Literal.Number.Hex */ -.highlight .mi { color: #009999 } /* Literal.Number.Integer */ -.highlight .mo { color: #009999 } /* Literal.Number.Oct */ -.highlight .sb { color: #bb8844 } /* Literal.String.Backtick */ -.highlight .sc { color: #bb8844 } /* Literal.String.Char */ -.highlight .sd { color: #bb8844 } /* Literal.String.Doc */ -.highlight .s2 { color: #bb8844 } /* Literal.String.Double */ -.highlight .se { color: #bb8844 } /* Literal.String.Escape */ -.highlight .sh { color: #bb8844 } /* Literal.String.Heredoc */ -.highlight .si { color: #bb8844 } /* Literal.String.Interpol */ -.highlight .sx { color: #bb8844 } /* Literal.String.Other */ -.highlight .sr { color: #808000 } /* Literal.String.Regex */ -.highlight .s1 { color: #bb8844 } /* Literal.String.Single */ -.highlight .ss { color: #bb8844 } /* Literal.String.Symbol */ -.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #008080 } /* Name.Variable.Class */ -.highlight .vg { color: #008080 } /* Name.Variable.Global */ -.highlight .vi { color: #008080 } /* Name.Variable.Instance */ -.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/vim.css b/_includes/css/pygments_themes/vim.css deleted file mode 100644 index 3af4a140b..000000000 --- a/_includes/css/pygments_themes/vim.css +++ /dev/null @@ -1,70 +0,0 @@ -.highlight pre { background-color: #222222 } -.highlight .hll { background-color: #222222 } -.highlight .c { color: #000080 } /* Comment */ -.highlight .err { color: #cccccc; border: 1px solid #FF0000 } /* Error */ -.highlight .g { color: #cccccc } /* Generic */ -.highlight .k { color: #cdcd00 } /* Keyword */ -.highlight .l { color: #cccccc } /* Literal */ -.highlight .n { color: #cccccc } /* Name */ -.highlight .o { color: #3399cc } /* Operator */ -.highlight .x { color: #cccccc } /* Other */ -.highlight .p { color: #cccccc } /* Punctuation */ -.highlight .cm { color: #000080 } /* Comment.Multiline */ -.highlight .cp { color: #000080 } /* Comment.Preproc */ -.highlight .c1 { color: #000080 } /* Comment.Single */ -.highlight .cs { color: #cd0000; font-weight: bold } /* Comment.Special */ -.highlight .gd { color: #cd0000 } /* Generic.Deleted */ -.highlight .ge { color: #cccccc; font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00cd00 } /* Generic.Inserted */ -.highlight .go { color: #808080 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { color: #cccccc; font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0040D0 } /* Generic.Traceback */ -.highlight .kc { color: #cdcd00 } /* Keyword.Constant */ -.highlight .kd { color: #00cd00 } /* Keyword.Declaration */ -.highlight .kn { color: #cd00cd } /* Keyword.Namespace */ -.highlight .kp { color: #cdcd00 } /* Keyword.Pseudo */ -.highlight .kr { color: #cdcd00 } /* Keyword.Reserved */ -.highlight .kt { color: #00cd00 } /* Keyword.Type */ -.highlight .ld { color: #cccccc } /* Literal.Date */ -.highlight .m { color: #cd00cd } /* Literal.Number */ -.highlight .s { color: #cd0000 } /* Literal.String */ -.highlight .na { color: #cccccc } /* Name.Attribute */ -.highlight .nb { color: #cd00cd } /* Name.Builtin */ -.highlight .nc { color: #00cdcd } /* Name.Class */ -.highlight .no { color: #cccccc } /* Name.Constant */ -.highlight .nd { color: #cccccc } /* Name.Decorator */ -.highlight .ni { color: #cccccc } /* Name.Entity */ -.highlight .ne { color: #666699; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #cccccc } /* Name.Function */ -.highlight .nl { color: #cccccc } /* Name.Label */ -.highlight .nn { color: #cccccc } /* Name.Namespace */ -.highlight .nx { color: #cccccc } /* Name.Other */ -.highlight .py { color: #cccccc } /* Name.Property */ -.highlight .nt { color: #cccccc } /* Name.Tag */ -.highlight .nv { color: #00cdcd } /* Name.Variable */ -.highlight .ow { color: #cdcd00 } /* Operator.Word */ -.highlight .w { color: #cccccc } /* Text.Whitespace */ -.highlight .mf { color: #cd00cd } /* Literal.Number.Float */ -.highlight .mh { color: #cd00cd } /* Literal.Number.Hex */ -.highlight .mi { color: #cd00cd } /* Literal.Number.Integer */ -.highlight .mo { color: #cd00cd } /* Literal.Number.Oct */ -.highlight .sb { color: #cd0000 } /* Literal.String.Backtick */ -.highlight .sc { color: #cd0000 } /* Literal.String.Char */ -.highlight .sd { color: #cd0000 } /* Literal.String.Doc */ -.highlight .s2 { color: #cd0000 } /* Literal.String.Double */ -.highlight .se { color: #cd0000 } /* Literal.String.Escape */ -.highlight .sh { color: #cd0000 } /* Literal.String.Heredoc */ -.highlight .si { color: #cd0000 } /* Literal.String.Interpol */ -.highlight .sx { color: #cd0000 } /* Literal.String.Other */ -.highlight .sr { color: #cd0000 } /* Literal.String.Regex */ -.highlight .s1 { color: #cd0000 } /* Literal.String.Single */ -.highlight .ss { color: #cd0000 } /* Literal.String.Symbol */ -.highlight .bp { color: #cd00cd } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #00cdcd } /* Name.Variable.Class */ -.highlight .vg { color: #00cdcd } /* Name.Variable.Global */ -.highlight .vi { color: #00cdcd } /* Name.Variable.Instance */ -.highlight .il { color: #cd00cd } /* Literal.Number.Integer.Long */ diff --git a/_includes/css/pygments_themes/vs.css b/_includes/css/pygments_themes/vs.css deleted file mode 100644 index e1e55d898..000000000 --- a/_includes/css/pygments_themes/vs.css +++ /dev/null @@ -1,33 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #008000 } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #0000ff } /* Keyword */ -.highlight .cm { color: #008000 } /* Comment.Multiline */ -.highlight .cp { color: #0000ff } /* Comment.Preproc */ -.highlight .c1 { color: #008000 } /* Comment.Single */ -.highlight .cs { color: #008000 } /* Comment.Special */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gh { font-weight: bold } /* Generic.Heading */ -.highlight .gp { font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { font-weight: bold } /* Generic.Subheading */ -.highlight .kc { color: #0000ff } /* Keyword.Constant */ -.highlight .kd { color: #0000ff } /* Keyword.Declaration */ -.highlight .kn { color: #0000ff } /* Keyword.Namespace */ -.highlight .kp { color: #0000ff } /* Keyword.Pseudo */ -.highlight .kr { color: #0000ff } /* Keyword.Reserved */ -.highlight .kt { color: #2b91af } /* Keyword.Type */ -.highlight .s { color: #a31515 } /* Literal.String */ -.highlight .nc { color: #2b91af } /* Name.Class */ -.highlight .ow { color: #0000ff } /* Operator.Word */ -.highlight .sb { color: #a31515 } /* Literal.String.Backtick */ -.highlight .sc { color: #a31515 } /* Literal.String.Char */ -.highlight .sd { color: #a31515 } /* Literal.String.Doc */ -.highlight .s2 { color: #a31515 } /* Literal.String.Double */ -.highlight .se { color: #a31515 } /* Literal.String.Escape */ -.highlight .sh { color: #a31515 } /* Literal.String.Heredoc */ -.highlight .si { color: #a31515 } /* Literal.String.Interpol */ -.highlight .sx { color: #a31515 } /* Literal.String.Other */ -.highlight .sr { color: #a31515 } /* Literal.String.Regex */ -.highlight .s1 { color: #a31515 } /* Literal.String.Single */ -.highlight .ss { color: #a31515 } /* Literal.String.Symbol */ diff --git a/_includes/css/pygments_themes/zenburn.css b/_includes/css/pygments_themes/zenburn.css deleted file mode 100644 index 287591d54..000000000 --- a/_includes/css/pygments_themes/zenburn.css +++ /dev/null @@ -1,136 +0,0 @@ -.highlight code, .highlight pre { -color:#fdce93; -background-color:#3f3f3f; -} - -.highlight .hll { -background-color:#222; -} - -.highlight .err { -color:#e37170; -background-color:#3d3535; -} - -.highlight .k { -color:#f0dfaf; -} - -.highlight .p { -color:#41706f; -} - -.highlight .cs { -color:#cd0000; -font-weight:700; -} - -.highlight .gd { -color:#cd0000; -} - -.highlight .ge { -color:#ccc; -font-style:italic; -} - -.highlight .gr { -color:red; -} - -.highlight .go { -color:gray; -} - -.highlight .gs { -color:#ccc; -font-weight:700; -} - -.highlight .gu { -color:purple; -font-weight:700; -} - -.highlight .gt { -color:#0040D0; -} - -.highlight .kc { -color:#dca3a3; -} - -.highlight .kd { -color:#ffff86; -} - -.highlight .kn { -color:#dfaf8f; -font-weight:700; -} - -.highlight .kp { -color:#cdcf99; -} - -.highlight .kr { -color:#cdcd00; -} - -.highlight .ni { -color:#c28182; -} - -.highlight .ne { -color:#c3bf9f; -font-weight:700; -} - -.highlight .nn { -color:#8fbede; -} - -.highlight .vi { -color:#ffffc7; -} - -.highlight .c,.preview-zenburn .highlight .g,.preview-zenburn .highlight .cm,.preview-zenburn .highlight .cp,.preview-zenburn .highlight .c1 { -color:#7f9f7f; -} - -.highlight .l,.preview-zenburn .highlight .x,.preview-zenburn .highlight .no,.preview-zenburn .highlight .nd,.preview-zenburn .highlight .nl,.preview-zenburn .highlight .nx,.preview-zenburn .highlight .py,.preview-zenburn .highlight .w { -color:#ccc; -} - -.highlight .n,.preview-zenburn .highlight .nv,.preview-zenburn .highlight .vg { -color:#dcdccc; -} - -.highlight .o,.preview-zenburn .highlight .ow { -color:#f0efd0; -} - -.highlight .gh,.preview-zenburn .highlight .gp { -color:#dcdccc; -font-weight:700; -} - -.highlight .gi,.preview-zenburn .highlight .kt { -color:#00cd00; -} - -.highlight .ld,.preview-zenburn .highlight .s,.preview-zenburn .highlight .sb,.preview-zenburn .highlight .sc,.preview-zenburn .highlight .sd,.preview-zenburn .highlight .s2,.preview-zenburn .highlight .se,.preview-zenburn .highlight .sh,.preview-zenburn .highlight .si,.preview-zenburn .highlight .sx,.preview-zenburn .highlight .sr,.preview-zenburn .highlight .s1,.preview-zenburn .highlight .ss { -color:#cc9393; -} - -.highlight .m,.preview-zenburn .highlight .mf,.preview-zenburn .highlight .mh,.preview-zenburn .highlight .mi,.preview-zenburn .highlight .mo,.preview-zenburn .highlight .il { -color:#8cd0d3; -} - -.highlight .na,.preview-zenburn .highlight .nt { -color:#9ac39f; -} - -.highlight .nb,.preview-zenburn .highlight .nc,.preview-zenburn .highlight .nf,.preview-zenburn .highlight .bp,.preview-zenburn .highlight .vc { -color:#efef8f; -} diff --git a/_includes/css/screen.css b/_includes/css/screen.css deleted file mode 100644 index cbff9f1c5..000000000 --- a/_includes/css/screen.css +++ /dev/null @@ -1,348 +0,0 @@ -* { - margin: 0; - padding: 0; -} - -html, body { - height: 100%; -} - -body { - font-family: "open sans", sans-serif; - font-size: 16px; - background-color: white; - color: #222222; - line-height: 24px; - margin: 0; - border-top: 7px solid {{ site.data.theme.highlight_color }}; -} - -#post p, #post li { - font-size: 18px; - line-height: 150%; -} - -h1, h2, h3, h4, h5, h6 { - color: #181818; - font-family: "open sans", sans-serif; - font-weight: 600; -} - -h1.title { - font-weight: 800; -} - -h1 { - font-size: 32px; - line-height: 40px; -} - -h2 { - font-size: 24px; - line-height: 30px; -} - -h3 { - font-size: 21px; - line-height: 24px; - margin: 1em 0; -} - -ul { - margin: 1em 0; - list-style: disc; -} - -a { - color: {{ site.data.theme.link_color }}; - text-decoration: none; -} - -a:hover { - color: {{ site.data.theme.link_color }}; - text-decoration: underline; -} - -a:visited { - color: {{ site.data.theme.link_color }}; -} - -table { - font-size: inherit; - font: 100%; -} - -img { - display: block; - margin-left: auto; - margin-right: auto; -} - -ul.posts { - margin-top: 0; - list-style-type: none; - margin-bottom: 10px; -} - -ul.posts li { - line-height: 22px; - font-size: 16px; - margin-bottom: 0px; -} - -ul.posts span { - font-family: 'Lucida Console', 'Andale Mono', monospace; - color: #aaa; - padding-right: 5px; - font-size:14px; -} - -.meta { - color: #aaa; - margin: 0 0 10px 0; -} - -.site .footer { - font-size: 80%; - color: #666; - border-top: 4px solid #eee; - overflow: hidden; -} - -nav h1, nav h2 { - text-align: center; -} - -#bio { - margin-top: 10px; - margin-bottom: 15px; - border: dotted #ddd; - border-width: 1px 0; - padding-top: 15px; -} - -#social { - margin-bottom: 10px; - font-size: 13px; -} - -#post pre { - border-top: 1px dashed #ddd; - border-bottom: 1px dashed #ddd; - /*border: 1px solid #ddd;*/ - /*background-color: #eef;*/ - padding: 0 .4em; - padding-bottom: 15px; - padding-top: 15px; - margin-bottom: 15px !important; -} - -#post ul, -#post ol { - margin-left: 1.35em; -} - -#post code { - border: 1px solid #ddd; - /*background-color: #eef;*/ - font-size: 85%; - padding: 0 .2em; -} - -#post pre code { - border: none; -} - -.sidebar { - padding-top: 25px; - font-family: "open sans", sans-serif; -} - -.sidebar p { - font-weight: 200; -} - -.sidebar a { - font-weight: 600; -} - -.content { - font: 400 16px/22px "open sans", sans-serif; - border-left: 1px solid #DDD; - padding-left: 40px; - padding-top: 25px; - min-height: 400px; -} - -.footer { - width: 100%; -} - -.footer { - font-size: 80%; - color: #666; - border-top: 4px solid #EEE; - overflow: hidden; -} - -.aside { - font-size: 75%; - color: #666; -} - -#home h2 { - color: {{ site.data.theme.highlight_color }}; -} - -#logo, #gravatar { - margin-bottom: 25px; -} - -#gravatar { - width: 180px; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; - border-radius: 50%; -} - -#stalker { - display: inline-block; - height: 18px; - margin-left: 5px; -} - -#stalker a { - width: 29px; - overflow: hidden; - float: left; -} - -#stalker i { - font-size: 28px; - margin-right: 5px; -} - -#stalker a:hover { - text-decoration: none; - color: {{ site.data.theme.highlight_color }}; -} - -i.fa-hn { - font-family: "open sans", sans-serif; - text-align: center; - font-size: 18px !important; - position: relative; - color: white; -} - -.home { - font-size: 24px; - float: right; - color: #AAA; -} - -.home:hover { - text-decoration: none; -} - -.disclaimer p { - margin-top: 5px; - font-size: 11px; - text-align: center; -} - -.footnotes { - font-size: 14px; - font-style: italic; -} - -.icon-overlay { - font-size: 16px !important; - position: relative; - bottom: 27px; - left: 5px; - color: white; -} - -.caption { - margin: 0 auto; - text-align: center; - font-size: smaller; - padding-bottom: 10px -} - -sup { - vertical-align: super; - font-size: smaller; -} - -table { - width: 100%; - margin-bottom: 20px; - border-color: #ccc; - border: 1px solid #ccc; - table-layout: fixed; -} - -table thead { - font-size: 1.25em; -} - -table tbody tr:nth-child(even) { - background-color: #f4f4f4; -} - -table th { - padding: 10px; - border-bottom: 1px solid #ccc; - border-right: 1px solid #ccc; -} - -table td { - padding-top: 5px; - padding-bottom: 5px; - padding-right: 10px; - padding-left: 10px; - border-right: 1px solid #ccc; -} - -#post code { - padding: 0; - font-family: Monaco, Menlo, Consolas ,"Courier New", monospace; -} - -#post pre code.language-text { - font-family: "Menlo", Monospace; -} - -small { - font-size: 0.5em; -} - -/* Custom elements */ -#subscribe { - margin-top: 40px; - border-top: 1px dashed #efefef; -} - -/* Colors */ -.green { - color: #2B8025; -} - -.red { - color: #802525; -} - -.orange { - color: #c15700; -} - -/* Syntax highlighting overrides */ -.highlight .ss { - color: #00AA88 !important; -} - -.language-elixir .err { - background-color: transparent !important; -} diff --git a/_includes/css/skeleton.css b/_includes/css/skeleton.css deleted file mode 100644 index d0264a40b..000000000 --- a/_includes/css/skeleton.css +++ /dev/null @@ -1,236 +0,0 @@ -/* -* Skeleton V1.1 -* Copyright 2011, Dave Gamache -* www.getskeleton.com -* Free to use under the MIT license. -* http://www.opensource.org/licenses/mit-license.php -* 8/17/2011 -*/ - - -/* Table of Contents -================================================== - #Base 960 Grid - #Tablet (Portrait) - #Mobile (Portrait) - #Mobile (Landscape) - #Clearing */ - - - -/* #Base 960 Grid -================================================== */ - - .container { position: relative; width: 960px; margin: 0 auto; padding: 0; } - .column, .columns { float: left; display: inline; margin-left: 10px; margin-right: 10px; } - .row { margin-bottom: 20px; } - - /* Nested Column Classes */ - .column.alpha, .columns.alpha { margin-left: 0; } - .column.omega, .columns.omega { margin-right: 0; } - - /* Base Grid */ - .container .one.column { width: 40px; } - .container .two.columns { width: 100px; } - .container .three.columns { width: 160px; } - .container .four.columns { width: 220px; } - .container .five.columns { width: 280px; } - .container .six.columns { width: 340px; } - .container .seven.columns { width: 400px; } - .container .eight.columns { width: 460px; } - .container .nine.columns { width: 520px; } - .container .ten.columns { width: 580px; } - .container .eleven.columns { width: 640px; } - .container .twelve.columns { width: 700px; } - .container .thirteen.columns { width: 760px; } - .container .fourteen.columns { width: 820px; } - .container .fifteen.columns { width: 880px; } - .container .sixteen.columns { width: 940px; } - - .container .one-third.column { width: 300px; } - .container .two-thirds.column { width: 620px; } - - /* Offsets */ - .container .offset-by-one { padding-left: 60px; } - .container .offset-by-two { padding-left: 120px; } - .container .offset-by-three { padding-left: 180px; } - .container .offset-by-four { padding-left: 240px; } - .container .offset-by-five { padding-left: 300px; } - .container .offset-by-six { padding-left: 360px; } - .container .offset-by-seven { padding-left: 420px; } - .container .offset-by-eight { padding-left: 480px; } - .container .offset-by-nine { padding-left: 540px; } - .container .offset-by-ten { padding-left: 600px; } - .container .offset-by-eleven { padding-left: 660px; } - .container .offset-by-twelve { padding-left: 720px; } - .container .offset-by-thirteen { padding-left: 780px; } - .container .offset-by-fourteen { padding-left: 840px; } - .container .offset-by-fifteen { padding-left: 900px; } - - - -/* #Tablet (Portrait) -================================================== */ - - /* Note: Design for a width of 768px */ - - @media only screen and (min-width: 768px) and (max-width: 959px) { - .container { width: 768px; } - .container .column, - .container .columns { margin-left: 10px; margin-right: 10px; } - .column.alpha, .columns.alpha { margin-left: 0; margin-right: 10px; } - .column.omega, .columns.omega { margin-right: 0; margin-left: 10px; } - - .container .one.column { width: 28px; } - .container .two.columns { width: 76px; } - .container .three.columns { width: 124px; } - .container .four.columns { width: 172px; } - .container .five.columns { width: 220px; } - .container .six.columns { width: 268px; } - .container .seven.columns { width: 316px; } - .container .eight.columns { width: 364px; } - .container .nine.columns { width: 412px; } - .container .ten.columns { width: 460px; } - .container .eleven.columns { width: 508px; } - .container .twelve.columns { width: 556px; } - .container .thirteen.columns { width: 604px; } - .container .fourteen.columns { width: 652px; } - .container .fifteen.columns { width: 700px; } - .container .sixteen.columns { width: 748px; } - - .container .one-third.column { width: 236px; } - .container .two-thirds.column { width: 492px; } - - /* Offsets */ - .container .offset-by-one { padding-left: 48px; } - .container .offset-by-two { padding-left: 96px; } - .container .offset-by-three { padding-left: 144px; } - .container .offset-by-four { padding-left: 192px; } - .container .offset-by-five { padding-left: 240px; } - .container .offset-by-six { padding-left: 288px; } - .container .offset-by-seven { padding-left: 336px; } - .container .offset-by-eight { padding-left: 348px; } - .container .offset-by-nine { padding-left: 432px; } - .container .offset-by-ten { padding-left: 480px; } - .container .offset-by-eleven { padding-left: 528px; } - .container .offset-by-twelve { padding-left: 576px; } - .container .offset-by-thirteen { padding-left: 624px; } - .container .offset-by-fourteen { padding-left: 672px; } - .container .offset-by-fifteen { padding-left: 720px; } - } - - -/* #Mobile (Portrait) -================================================== */ - - /* Note: Design for a width of 320px */ - - @media only screen and (max-width: 767px) { - .container { width: 300px; } - .columns, .column { margin: 0; } - - .container .one.column, - .container .two.columns, - .container .three.columns, - .container .four.columns, - .container .five.columns, - .container .six.columns, - .container .seven.columns, - .container .eight.columns, - .container .nine.columns, - .container .ten.columns, - .container .eleven.columns, - .container .twelve.columns, - .container .thirteen.columns, - .container .fourteen.columns, - .container .fifteen.columns, - .container .sixteen.columns, - .container .one-third.column, - .container .two-thirds.column { width: 300px; } - - /* Offsets */ - .container .offset-by-one, - .container .offset-by-two, - .container .offset-by-three, - .container .offset-by-four, - .container .offset-by-five, - .container .offset-by-six, - .container .offset-by-seven, - .container .offset-by-eight, - .container .offset-by-nine, - .container .offset-by-ten, - .container .offset-by-eleven, - .container .offset-by-twelve, - .container .offset-by-thirteen, - .container .offset-by-fourteen, - .container .offset-by-fifteen { padding-left: 0; } - - } - - -/* #Mobile (Landscape) -================================================== */ - - /* Note: Design for a width of 480px */ - - @media only screen and (min-width: 480px) and (max-width: 767px) { - .container { width: 420px; } - .columns, .column { margin: 0; } - - .container .one.column, - .container .two.columns, - .container .three.columns, - .container .four.columns, - .container .five.columns, - .container .six.columns, - .container .seven.columns, - .container .eight.columns, - .container .nine.columns, - .container .ten.columns, - .container .eleven.columns, - .container .twelve.columns, - .container .thirteen.columns, - .container .fourteen.columns, - .container .fifteen.columns, - .container .sixteen.columns, - .container .one-third.column, - .container .two-thirds.column { width: 420px; } - } - - -/* #Clearing -================================================== */ - - /* Self Clearing Goodness */ - .container:after { content: "\0020"; display: block; height: 0; clear: both; visibility: hidden; } - - /* Use clearfix class on parent to clear nested columns, - or wrap each row of columns in a
*/ - .clearfix:before, - .clearfix:after, - .row:before, - .row:after { - content: '\0020'; - display: block; - overflow: hidden; - visibility: hidden; - width: 0; - height: 0; } - .row:after, - .clearfix:after { - clear: both; } - .row, - .clearfix { - zoom: 1; } - - /* You can also use a
to clear columns */ - .clear { - clear: both; - display: block; - overflow: hidden; - visibility: hidden; - width: 0; - height: 0; - } - - diff --git a/_includes/css/syntax.css b/_includes/css/syntax.css deleted file mode 100644 index 882985cdd..000000000 --- a/_includes/css/syntax.css +++ /dev/null @@ -1,3 +0,0 @@ -.highlight{background-color:#073642;color:#93a1a1}.highlight .c{color:#586e75 !important;font-style:italic !important}.highlight .cm{color:#586e75 !important;font-style:italic !important}.highlight .cp{color:#586e75 !important;font-style:italic !important}.highlight .c1{color:#586e75 !important;font-style:italic !important}.highlight .cs{color:#586e75 !important;font-weight:bold !important;font-style:italic !important}.highlight .err{color:#dc322f !important;background:none !important}.highlight .k{color:#cb4b16 !important}.highlight .o{color:#93a1a1 !important;font-weight:bold !important}.highlight .p{color:#93a1a1 !important}.highlight .ow{color:#2aa198 !important;font-weight:bold !important}.highlight .gd{color:#93a1a1 !important;background-color:#372c34 !important;display:inline-block}.highlight .gd .x{color:#93a1a1 !important;background-color:#4d2d33 !important;display:inline-block}.highlight .ge{color:#93a1a1 !important;font-style:italic !important}.highlight .gr{color:#aa0000}.highlight .gh{color:#586e75 !important}.highlight .gi{color:#93a1a1 !important;background-color:#1a412b !important;display:inline-block}.highlight .gi .x{color:#93a1a1 !important;background-color:#355720 !important;display:inline-block}.highlight .go{color:#888888}.highlight .gp{color:#555555}.highlight .gs{color:#93a1a1 !important;font-weight:bold !important}.highlight .gu{color:#6c71c4 !important}.highlight .gt{color:#aa0000}.highlight .kc{color:#859900 !important;font-weight:bold !important}.highlight .kd{color:#268bd2 !important}.highlight .kp{color:#cb4b16 !important;font-weight:bold !important}.highlight .kr{color:#d33682 !important;font-weight:bold !important}.highlight .kt{color:#2aa198 !important}.highlight .n{color:#268bd2 !important}.highlight .na{color:#268bd2 !important}.highlight .nb{color:#859900 !important}.highlight .nc{color:#d33682 !important}.highlight .no{color:#b58900 !important}.highlight .ni{color:#800080}.highlight .nl{color:#859900 !important}.highlight .ne{color:#268bd2 !important;font-weight:bold !important}.highlight .nf{color:#268bd2 !important;font-weight:bold !important}.highlight .nn{color:#b58900 !important}.highlight .nt{color:#268bd2 !important;font-weight:bold !important}.highlight .nx{color:#b58900 !important}.highlight .bp{color:#999999}.highlight .vc{color:#008080}.highlight .vg{color:#268bd2 !important}.highlight .vi{color:#268bd2 !important}.highlight .nv{color:#268bd2 !important}.highlight .w{color:#bbbbbb}.highlight .mf{color:#2aa198 !important}.highlight .m{color:#2aa198 !important}.highlight .mh{color:#2aa198 !important}.highlight .mi{color:#2aa198 !important}.highlight .mo{color:#009999}.highlight .s{color:#2aa198 !important}.highlight .sb{color:#d14}.highlight .sc{color:#d14}.highlight .sd{color:#2aa198 !important}.highlight .s2{color:#2aa198 !important}.highlight .se{color:#dc322f !important}.highlight .sh{color:#d14}.highlight .si{color:#268bd2 !important}.highlight .sx{color:#d14}.highlight .sr{color:#2aa198 !important}.highlight .s1{color:#2aa198 !important}.highlight .ss{color:#990073}.highlight .il{color:#009999}.highlight div .gd,.highlight div .gd .x,.highlight div .gi,.highlight div .gi .x{display:inline-block;width:100%} -pre { white-space: pre; overflow: auto; } -code, pre { font-family: Monaco,Menlo,Consolas,"Courier New",monospace; } diff --git a/_includes/footer.html b/_includes/footer.html deleted file mode 100644 index 13b62d600..000000000 --- a/_includes/footer.html +++ /dev/null @@ -1,12 +0,0 @@ -
- {% if site.data.theme.show_disclaimer %} -

- The postings on this site are my own and don't necessarily represent my - employer’s positions, strategies or opinions. -

- {% endif %} - -

- © {{ site.data.theme.name }}, {{ site.time | date: '%Y' }} — built with Jekyll using Lagom theme -

-
diff --git a/_includes/sidebar.html b/_includes/sidebar.html deleted file mode 100644 index 97716c66c..000000000 --- a/_includes/sidebar.html +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/_includes/social.html b/_includes/social.html deleted file mode 100644 index 01d8907d2..000000000 --- a/_includes/social.html +++ /dev/null @@ -1,72 +0,0 @@ -
GPG Key
-

I am dberkom on Keybase.

- -Follow me: -
- {% if site.data.theme.social.github %} - - - - {% endif %} - - {% if site.data.theme.social.bitbucket %} - - - - {% endif %} - - {% if site.data.theme.social.hacker_news %} - - - - Y - - - {% endif %} - - {% if site.data.theme.social.stackexchange %} - - - - {% endif %} - - {% if site.data.theme.social.stackoverflow %} - - - - {% endif %} - - {% if site.data.theme.social.twitter %} - - - - {% endif %} - - {% if site.data.theme.social.facebook %} - - - - {% endif %} - - {% if site.data.theme.social.tumblr %} - - - - {% endif %} - - {% if site.data.theme.social.linkedin %} - - - - {% endif %} - - {% if site.data.theme.social.gplus %} - - - - {% endif %} - - - - -
diff --git a/_includes/subscribe.html b/_includes/subscribe.html deleted file mode 100644 index 13e8f0fce..000000000 --- a/_includes/subscribe.html +++ /dev/null @@ -1,32 +0,0 @@ - - - -
-
-
- - - -
- -
-
-
- - diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index 16763f023..000000000 --- a/_layouts/default.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - {{ site.data.theme.name }} - {{ page.title }} - - - - - - - - - - - - -
- - -
- {{ content }} - - -
-
- -{% include analytics.html %} - - diff --git a/_layouts/post.html b/_layouts/post.html deleted file mode 100644 index 9fc69f6c7..000000000 --- a/_layouts/post.html +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: default ---- - -

- {{ page.date | date: "%B %d, %Y" }} - - - -

- -

{{ page.title }}

- -
- {{ content }} -
- -
-

Did You Enjoy This Post?

- {% include subscribe.html %} -
- -{% if page.comments %} -
-

Comments

-
- - -
-{% endif %} - - diff --git a/_posts/.#2016-01-28-seo-tags-in-phoenix.md b/_posts/.#2016-01-28-seo-tags-in-phoenix.md deleted file mode 120000 index 7cd185e61..000000000 --- a/_posts/.#2016-01-28-seo-tags-in-phoenix.md +++ /dev/null @@ -1 +0,0 @@ -dberkom@Bastiat.local.25543 \ No newline at end of file diff --git a/_posts/2015-02-07-spark-notebook.md b/_posts/2015-02-07-spark-notebook.md deleted file mode 100644 index 4634dd80f..000000000 --- a/_posts/2015-02-07-spark-notebook.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: post -title: The Spark Notebook -category: getting-things-done -date: 2015-02-07 07:00:00 -0800 ---- - -![The Spark Notebook](/assets/img/spark-notebook.jpg) - -My wife recently backed the [Spark Notebook Kickstarter][spark-notebook-kickstarter]. and she kindly ordered me two of them as a late Christmas present. - -So far, _I love it_. You know how we all make New Year's Resolutions and then break them? Thanks to this notebook, this is the first year I feel like I have a fighting chance of actually accomplishing my goals. - - - -The notebook is set up to encourage you to telescope in on your goals, starting from a high level, all the way down to your day-to-day routine. It's structured like this: - -1. A theme for the year, with supporting goals. -2. Monthly goals, complete with a calendar. -3. Weekly goals, with a week planner. - -It also comes with project planners and ordinary note-taking pages. - -By constantly encouraging to review your high-level goals, the notebook helps you stay on track with your daily goals, making sure that everything is in sync. Your big goals get broken up into smaller, achievable goals, and you can see how it all adds up to the big picture. - -So, even if you're a developer (like me), or usually use computers to solve these kinds of problems (like me), I **strongly** encourage you to get a Spark Notebook. - -There might be [some left][spark-notebook]. - -[spark-notebook-kickstarter]: https://www.kickstarter.com/projects/katemats/spark-notebook-a-place-for-your-life-plans-and-gre -[spark-notebook]: http://thesparknotebook.com diff --git a/_posts/2015-02-07-yet-another-tech-blog.md b/_posts/2015-02-07-yet-another-tech-blog.md deleted file mode 100644 index ca5195924..000000000 --- a/_posts/2015-02-07-yet-another-tech-blog.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: post -title: Yet Another Tech Blog -date: 2015-02-07 07:00:00 -0800 ---- - -After a long series of attempts to get my blog up and running, I've finally settled on a stack I can be happy with for now: [Jekyll][jekyll] on [Github pages][github-pages]. It's secure, doesn't require me to mess with hosting, and lets me write, which is after all what a blog is all about. - -I plan to write about my day-to-day experiences with technology, particularly the tech that I'm using or learning. No promises that I'll restrict myself to that, but we'll see what the future holds. - -[jekyll]: http://jekyllrb.com -[github-pages]: https://pages.github.com/ diff --git a/_posts/2015-02-09-moving-beyond-ruby.md b/_posts/2015-02-09-moving-beyond-ruby.md deleted file mode 100644 index b0946b33d..000000000 --- a/_posts/2015-02-09-moving-beyond-ruby.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -layout: post -title: Moving Beyond Ruby -categories: ruby elixir -date: 2015-02-09 07:00:00 -0800 ---- - -I've been a Ruby developer for the last 4 years of my career. It's served me very well, I still like it, and I expect that I'll still be writing it for my career for years to come. However, there are some things that Ruby (and Rails) don't do so well out of the box, and these things are causing me to look elsewhere. - - - -- **Concurrency**. Ruby wasn't built for concurrency, and therefore there is no consensus on how to do it. -- **Scalability**. It is genuinely hard to scale Rails applications out of the box. In fact, the box should be labeled "Scalability not included." Much of this has to do with Ruby's concurrency support and slow execution speed. -- **Portability**. Anyone who has set up a machine for Ruby development can attest to the difficulty of getting up and running. Setting up your production servers to run it can also be a pain. (Unless you get to use [Heroku][heroku]) - -I recognize that there are solutions to all of these problems. However, I would prefer to be able to write in a language and framework that does a better job of these things out of the box. - -## Going Functional -As a programming teacher, I've seen first hand how students struggle with the concept of "Objects" and "Instances." Many of us were taught that Object-Oriented Programming is easier to learn, yet these students have a hard time getting their minds around it. - -This got me thinking, is Object-Oriented Programming really _simpler_ than Functional Programming? My working theory is no, it isn't. Programming really has to do with transforming data from one state into another state, and this fits very well into the functional paradigm of immutable state, first-class functions, and function chaining. - -In contrast, Objects obscure what's really going on. Since computers aren't like people, imposing this anthropomorphic system on top of what the computer does actually makes things _more complex_. - -Theoretically then, we should be able to compose programs that are both _simpler_ and _easier to understand_ with just the simple building blocks that Functional Programming languages give us. - -## Elixir -To test this theory, I've started learning [Elixir][elixir]. There are, of course, other functional languages I could learn, but Elixir seems like the easiest to pick up because it superficially looks like Ruby. - -See for yourself: - -#### Ruby -```ruby -class PagesController < ApplicationController - - def index - @title = params[:title] - @members = [ - {name: "Chris McCord"}, - {name: "Matt Sears"}, - {name: "David Stump"}, - {name: "Ricardo Thompson"} - ] - render "index" - end -end -``` - -#### Elixir -```elixir -defmodule Benchmarker.Controllers.Pages do - use Phoenix.Controller - - def index(conn, %{"title" => title}) do - render conn, "index", title: title, members: [ - %{name: "Chris McCord"}, - %{name: "Matt Sears"}, - %{name: "David Stump"}, - %{name: "Ricardo Thompson"} - ] - end -end -```` - -Elixir runs on the Erlang VM, which is battle tested and has excellent support for concurrency. Also, it has [support for unikernels](http://elixir-lang.org/blog/2013/05/02/elixir-on-xen/), something I'm interested in from a security perspective. - -I intend to post more about my experiences with Elixir, so stay tuned. In the future, I also intend to take a look at Rust and Go. - -[elixir]: http://elixir-lang.org -[heroku]: http://heroku.com diff --git a/_posts/2015-02-15-think-about-the-next-guy.md b/_posts/2015-02-15-think-about-the-next-guy.md deleted file mode 100644 index e44c9d189..000000000 --- a/_posts/2015-02-15-think-about-the-next-guy.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: post -title: Think About the Next Guy -category: best-practices -date: 2015-02-15 07:00:00 -0800 ---- - -> I hated all my toil in which I toil under the sun, seeing that I must leave it to the man who will come after me, and who knows whether he will be wise or a fool? Yet he will be master of all for which I toiled and used my wisdom under the sun. - Ecclesiastes 2:18-19 - -Everyone dies, and the world is constantly changing. Therefore, if anything is to be carried on into the future, there must be a _succession_. Someone new must take up the mission, the art, or the craft, or it will die with the current generation. - - - -Programmers should be very familiar with this. How many formerly popular frameworks have been left in the dust? How many popular applications have disappeared? How often have you rewritten old codebases, not necessarily because they were bad, but because they were undocumented and you didn't understand them? - -How often has a client come to you, wanting just one feature added to their software, and you ended up telling them the whole thing had to be rewritten? How often have you inherited code from previous developers? How often was that a pleasant experience? How often has a key employee left your company, taking all his knowledge with him, leaving new developers to pick up the pieces? How often has an open source project you rely on been abandoned, forcing you to maintain your own fork? - -If your career has been anything like mine, I'm guessing that all these things have happened to you, and they weren't fun. - -## We are really, really bad at succession - -Most programmers give very little thought to succession. We bang out our code, merrily implementing new features and starting new frameworks, rarely thinking about the long-term implications of what we're doing. - -As a result, our projects don't have READMEs, they have no high-level documentation, no low-level unit documentation, no instructions for new developers, and often few tests. Further, we write them in whatever is new and shiny, rarely thinking about whether the new and shiny will still be shiny as little as six months from now. - -If we have any documentation at all, it's usually directed toward our end-users, and talks about how to _use_ the system. The system itself remains an undocumented black box. - -## Why it matters - -**Code that no one understands is crap.** It doesn't matter what language it is written in, what patterns it uses, or how shiny it was at the time. It's crap. It gets thrown out. - -**Code that people understand and can extend is good.** Again, it doesn't matter what it is written in, or whether it has curly braces and semi-colons or not. - -**Rewrites are wasteful.** Resources that could be used building new functionality are instead being used to reproduce old functionality. It is therefore better to extend than rewrite. - -**Rewrites produce worse code as often as not.** I know we always feel like we've made something better when we rewrite it, but this isn't always true. The guy whose code you're rewriting probably thought that he was improving things too. - -**Businesses can't afford endless rewrites.** Increasingly, programmers with a proven track record of writing maintainable software will get the jobs or the contracts, even if they're not using the new hotness. We get by now due to the fact that programmers are in high demand and people are throwing lots of money at technology to see what will stick. This won't last forever. Eventually, our clientele will become more educated and ask harder questions. - -**Eventually, you've got to settle down.** Programmers get old and die like everyone else. Not all of us are going to be able to start our own business or have a comfortable retirement. This means you probably face three options at retirement age: - -1. Get into management. -2. Keep chasing new trends when you're 60. The kids will be better than you at this because they always are. -3. Make a way for yourself writing maintainable software that stays relevant over the long term. Let the kids play with the new toys. - -Taking any of these options other than #2 will require planning and preparation, _before_ you hit retirement age. - -## What you can do about it - -There are some simple disciplines that I think will help keep your code maintainable in the long run. - -1. **Think about the next guy who's going to work on your code.** You might not know him. You might not be there for him. So, what does he need to know about your code so that he doesn't just throw it out and waste time rewriting it? Tell him. Make sure he knows how _to even get it running._ - -2. **Don't get dazzled so quickly by new technology.** New is not always better. Keep a firm bias toward proven tools and let the new ones mature for a few years. Try not to get locked into a technology that might be discontinued any time soon. - -3. **Design your code for extensibility.** Your code will certainly need to change, so try to foresee what types of changes will be required. For example, if it's a reporting app, it's foreseeable that someone will want a new report. Make it easy to add one, and then document how to do it. Isolate the areas of the code that need to change often from the areas that don't. - -I admit I haven't always practiced these disciplines, but the older I get, the more value I see in them. They can only make you a better developer, and ensure that those who come after you can succeed. We need a lot more programmers who think about the next guy. - -Expect to see more posts on this topic. diff --git a/_posts/2015-02-19-undo-git-commit.md b/_posts/2015-02-19-undo-git-commit.md deleted file mode 100644 index f6addfd69..000000000 --- a/_posts/2015-02-19-undo-git-commit.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: post -title: Undo a Commit in Git -category: git -date: 2015-02-19 07:00:00 -0800 ---- - -I keep forgetting how to do this, so I thought I'd post this for my own future benefit: - -```bash -// Completely deletes the most recent commit -$ git reset --hard HEAD~1 - -// Removes the most recent commit, but leaves changes intact. Useful if you -// might want to make a new commit. -$ git reset --soft HEAD~1 -``` diff --git a/_posts/2015-02-21-find-nth-prime-in-elixir.md b/_posts/2015-02-21-find-nth-prime-in-elixir.md deleted file mode 100644 index 85264063e..000000000 --- a/_posts/2015-02-21-find-nth-prime-in-elixir.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: post -title: Find the Nth Prime in Elixir -categories: - - elixir -date: 2015-02-21 07:00:00 -0800 ---- - -I've been working through [Exercism's][exercism] set of code challenges for Elixir, and came across this one. - -> Write a program that can tell you what the nth prime is. -> -> By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. - -This is a perfect use case for Elixir's [Stream][elixir-stream] module, because we want to generate a list of values and return the last one. - - - -Stream contains a function called `iterate`, which takes a starting value, and then lazily generates new value based on the previous value. To create a Stream for primes, therefore, we can write something like this: - -```elixir -# Starting at 2, create new values using the next_prime/1 function -Stream.iterate(2, &next_prime/1) -``` - -We can then chain a couple of other functions to generate only up to the number of primes requested, and return the last one. - -```elixir -def nth(count) do - Stream.iterate(2, &next_prime/1) - |> Enum.take(count) - |> List.last -end -``` - -Finally, we must define the `next_prime/1` function. Given a starting point, it should iterate until it reaches another prime number. - -```elixir -def next_prime(num) do - num = num + 1 - - # If the factors only include this number, it's prime - if factors_for(num) == [num] do - num - else - next_prime(num) - end -end -``` - -`factors_for/1` is also a function I defined, but I'll leave it as an exercise for the reader. - -I enjoyed getting to use [Stream][elixir-stream] for the first time with this exercise. Ruby has something similar ([Enumerable::Lazy][lazy]), but I hadn't used it or anything like it before. Another tool for the toolbox! - -[lazy]: http://railsware.com/blog/2012/03/13/ruby-2-0-enumerablelazy/ -[elixir-stream]: http://elixir-lang.org/docs/stable/elixir/Stream.html -[exercism]: http://exercism.io diff --git a/_posts/2015-02-27-why-i-write-straight-html-and-js.md b/_posts/2015-02-27-why-i-write-straight-html-and-js.md deleted file mode 100644 index 3a9c97412..000000000 --- a/_posts/2015-02-27-why-i-write-straight-html-and-js.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -layout: post -title: Why I Write Straight HTML and JS -categories: - - html - - js -date: 2015-02-27 07:00:00 -0800 ---- - -It's very popular in the Rails community to use templating languages that compile down to HTML. The asset pipeline likewise makes it very easy to use Coffeescript and SCSS, or other languages that compile down to Javascript and CSS. - -When I got started with Rails, I was just as much a fan of these technologies as everyone else. Over time though, they've lost their shine, and I'll explain why. - - - -## 1. Whitespace - -Nearly all these languages achieve their "simplicity" through whitespace. They make code shorter by removing closing tags, whether those be `

` tags or javascript `}`. You end up with code nesting that looks something like this: - -```slim -html - head - title My first HTML project - meta content="..." - link href="..." type="text/css" - - body - p Hello World -``` - -At first glance, this looks wonderful. Unfortunately, most code ends up looking more like this over time: - -```slim -.span2 - h1 class="#{icon_color_class}": i class="#{icon}" -.span10 - h5.pad-bottom-small= text - ul.unstyled - - if collection.size > 0 - - collection.each do |item| - li= item.name - - else - li (None) - - = link_to link, url, class: link_color_class -``` - -Here are the problems I have with this: - -- **Nesting is messy.** In HTML, it's very easy to format code however you want. Templating languages all have different funky ways to accomplish this. - -- **The code is hard to scan.** Without end tags, it's much harder to tell what the structure of the file should be. - -- **Growth makes things worse.** The larger your templates grow, the harder it is to see what is going on. - -- **It's ugly.** These templating languages are advertised based on how pretty they are, but in practice, they can look much worse than straight HTML. - -## 2. They're Distracting - -These abstractions distract developers in the following ways: - -- **Documentation**. Most documentation and tutorials are written in the base languages of the web, and developers using Slim, HAML, Emblem, or Cofeescript then have to spend time figuring out how to do the same thing. If they were just writing HTML and Javascript, they could just copy-paste and move on. - -- **Debugging.** Debugging can become much more complicated, because what you see in your codebase is not actually what gets run in the browser. Bugs crop up due to the fact that your attempt doesn't compile into the same code as the documentation you were following. Getting help is harder, because other people on the internet aren't using the specific combination of technologies that you are, and therefore your specific issue is rare. - -- **Context Switching.** The developer must keep Javascript and HTML in his mind as he writes the compiled language. This means that he has two languages in his head as he codes, rather than one. Arguably, this is harder on the brain than just writing straight Javascript and HTML. - -## 3. Speed - -If you write straight Javascript and HTML, your asset compilation times will be faster. In production, your views will render more quickly, as the server or browser must do much less work to generate HTML. - -## 4. High Maintenance Costs - -If you maintain a lot of projects, having lots of templating languages floating around can be a large burden. New developers have to get up to speed on the templating language (which slows them down) and it's harder to share code between codebases due to the dependencies that code brings with it. - -## What About the Features? - -Some of these languages offer features not available in their underlying -language. SCSS is a good example. This used to be true of CoffeeScript, but now that ES6 has adopted most of its good ideas, and transpilers are readily available, this is no longer a compelling reason to use CoffeeScript. - -I still use SCSS because its features are so useful, and its learning curve is so low due to the fact that CSS is valid SCSS. In my experience, SCSS is the exception rather than the rule, however. If your language provides _actual features_ rather than syntactic sugar, I'd consider keeping it. Otherwise, I'd throw it out. - -You already know Javascript. You already know HTML. So does everyone who will work on your project now or in the future. Why not speak the common language? diff --git a/_posts/2015-03-06-immutable-databases.md b/_posts/2015-03-06-immutable-databases.md deleted file mode 100644 index 204e792a8..000000000 --- a/_posts/2015-03-06-immutable-databases.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: post -title: Thoughts on Immutable Databases -author: Daniel Berkompas -categories: databases -date: 2015-03-06 07:00:00 -0800 ---- - -I came across this talk on Apache Samza this past week: - - - -The author, Martin Kleppmann, also wrote up an [excellent post on the same subject][samza-post], which is well worth your time. - -In both, he argues that an stream of immutable facts (a log, essentially) is a better datastructure for databases, rather than the existing model of mutating state in place. - - - -No fact in this stream of facts is ever altered, the stream is only appended to. Database views are then built on top of this stream of facts, and these views automatically update when a new fact is appended to the stream. This allows for the same kind of querying that we all love, but eliminates some of the following headaches: - -- **Migrations**. Just create a new view for your log, and when it's ready, move your app over to the new view. - -- **Testing Features**. You can create a whole new set of views into your data for a feature, give only certain users access to them, and if the feature doesn't pan out, delete them all with no negative consequences. - -- **Real-time updates.** Since the foundational datastructure is really an event stream, these events can be propogated to subscribers quite easily. Your app can be notified whenever any of the views it depends on is updated. - -- **Scaling**. Since the data is stored as a list of facts, it's easier to distribute across multiple machines, especially since these facts are never changed. - -With that introduction out of the way, (and you really should watch the talk or read the post) I've got a few thoughts. - -First, this sounds very attractive to me. If I had that much flexibility with my views, I could move much faster and be a lot more ambitious with my changes. After all, since nothing in the underlying data ever changes, we can always go back. - -I do have a few concerns though. - -- **The log would get huge.** Imagine storing every fact generated by your web app in one or more logs. Those logs would get very, very large! They could be compressed, but this might slow the database down, depending on how often it compresses the data. It'd be like GC. - -- **You'd trade migrations for long waits creating new views.** In order to generate a new view, the database would have to scan all the relevant logs, which is to say, terabytes of data. While you'd no longer have to worry about breaking your production web app during the migration, you might have to sit and wait for a long time before your new view could be used. - -- **Validations are essential.** Since the facts written to the database are immutable, you _must_ prevent facts from being recorded in an invalid format. Otherwise, you'll be dealing with that malformed data forever. It's also worth pointing out that there seem to be no databases currently -available that use this architecture. However, there is a project, -[PipelineDB][pipeline] which seems to be doing just that. Details are sparse, but I've applied for the beta program, and if I get in, I'll post about it. - -Despite those concerns, this seems to be a promising new direction for a sector of tech that's stayed relatively constant for a long time, databases. It'll be interesting to see how this approach turns out once it starts hitting production! - -[samza-post]: http://blog.confluent.io/2015/03/04/turning-the-database-inside-out-with-apache-samza/ -[pipeline]: http://pipelinedb.com diff --git a/_posts/2015-03-11-ex-twiml.md b/_posts/2015-03-11-ex-twiml.md deleted file mode 100644 index 2c14264f7..000000000 --- a/_posts/2015-03-11-ex-twiml.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: post -title: ExTwiml -author: Daniel Berkompas -categories: - - elixir - - twilio -date: 2015-03-11 07:00:00 -0800 ---- - -At [LeadSimple](http://leadsimple.com), we use [Twilio](http://twilio.com) to handle phone call routing. Twilio interacts with regular HTTP endpoints on your server to control calls and SMS messages, through a form of XML they call `TwiML`. - -I've been toying recently ideas on how to bring next-generation Erlang (Elixir) and next-generation telephony (Twilio) together. Toward that end, I made a little library this week to make generating TwiML from Elixir easy. - - - -It looks like this: - -```elixir -defmodule YourModule do - import ExTwiml - - def render do - twiml do - play "/assets/welcome.mp3" - gather digits: 4, finish_on_key: "#" do - say """ - Please enter the last four digits of your credit card number, followed - by the pound sign. - """, voice: "woman" - end - end - end -end -``` - -Calling `YourModule.render` will return the following XML as a string: - -```xml - - - /assets/welcome.mp3 - - - Please enter the last four digits of your credit card number, followed by - the pound sign. - - - -``` - -The library is called [ExTwiml](https://github.com/danielberkompas/ex_twiml). It's available from Github or [Hex](https://hex.pm/packages/ex_twiml). diff --git a/_posts/2015-03-21-manage-env-vars-in-elixir.md b/_posts/2015-03-21-manage-env-vars-in-elixir.md deleted file mode 100644 index e883dff1d..000000000 --- a/_posts/2015-03-21-manage-env-vars-in-elixir.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: post -title: Manage Environment Variables in Elixir -author: Daniel Berkompas -categories: - - elixir -date: 2015-03-21 07:00:00 -0800 ---- - -I love how the Elixir build tool, `Mix`, has built-in support for configuration settings. It makes configuring packages much simpler by providing a standard interface for config settings. - -I'm currently developing a [Twilio][twilio] API client for Elixir. While I develop and test it, I need to store an "Account SID" and "Auth token" to make requests. Naturally, I turned to Mix config. - - - -Because I needed to have separate configuration files for each environment, so that I could test with different credentials, I set up `config/config.exs` like this: - -```elixir -use Mix.Config - -import_config "#{Mix.env}.exs" -``` - -I then created `config/development.exs` and `config/test.exs` files to store environment-specific settings. - -```elixir -# config/development.exs -use Mix.Config - -config :ex_twilio, account_sid: "SID HERE" -config :ex_twilio, auth_token: "TOKEN HERE" -``` - -But I'm going to be pushing this project to Github when it's ready, so I don't want to store my real credentials here. Instead, I want to use `ENV` variables. So, I update my `config/.exs` files to look like this: - -```elixir -use Mix.Config - -config :ex_twilio, account_sid: System.get_env("TWILIO_ACCOUNT_SID") -config :ex_twilio, auth_token: System.get_env("TWILIO_AUTH_TOKEN") -``` - -After trying a variety of different solutions, it seems like the best way currently to get these variables to be set is to create a `.env` file in your project directory: - -``` -export TWILIO_ACCOUNT_SID="" -export TWILIO_AUTH_TOKEN="" -``` - -Then, simply run `source .env` in your shell before you run any `iex` or `mix` commands. Done! - -[twilio]: http://twilio.com diff --git a/_posts/2015-03-28-stream-paginated-apis-in-elixir.md b/_posts/2015-03-28-stream-paginated-apis-in-elixir.md deleted file mode 100644 index f35ed5b8c..000000000 --- a/_posts/2015-03-28-stream-paginated-apis-in-elixir.md +++ /dev/null @@ -1,236 +0,0 @@ ---- -layout: post -title: Stream Paginated APIs in Elixir -categories: - - elixir - - twilio -date: 2015-03-28 07:00:00 -0800 ---- -_This article was recently featured in [Elixir Radar](http://plataformatec.com.br/elixir-radar). Since it was written, I've found a simpler way to implement this kind of Stream. I'll be writing an updated tutorial in the future, but if you're interested, you should also check out the current state of [ExTwilio.ResultStream](https://github.com/danielberkompas/ex_twilio/blob/371f2b263d763d1459b467d1fbe8783fce825c0e/lib/ex_twilio/result_stream.ex)._ ---- - -This past week, as I worked on my new [ExTwilio][ExTwilio] API library for [Twilio][twilio], I ran into a snag dealing with [Twilio's API pagination][twilio-page-api]. - -Twilio paginates its "list" APIs, requiring multiple requests to fetch all of a given resource. However, users of my API library will expect to be able to fetch _all_ of a resource and perform operations on it, like this: - -```elixir -calls = ExTwilio.Call.all -Enum.each calls, fn(call) -> - # perform some operation -end -``` - -Users won't want to mess with the details of pagination. They want to get a collection containing everything and then operate on it. - -I find that there are two basic ways to achieve this, a _blocking_ way and a _non-blocking_ way. - - - -## The Blocking Approach -In this first approach, we fetch all the pages, merge their results together, and _only then_ return a collection to the caller. The caller is blocked and must wait while this is going on. - -This isn't ideal for two reasons: - -1. It will take an unknown amount of time. -2. Nothing can be done with the data until it has _all_ been loaded. - -The user probably doesn't want to wait to perform any operations until the entire result set has been fetched. - -## The Non-Blocking Approach -The second, better approach, is to use Elixir's [Stream][Elixir.Stream] module to create a lazy collection. As we iterate through the collection, the [Stream][Elixir.Stream] module will automatically fetch new pages of the API as needed. - -You can think of the Stream here like a bucket used to fetch water from a well. The well is the API, and the bucket can hold one page of results at a time. The Stream returns items from the bucket, one at a time. - -The logic to follow looks like this: - -- First, we fill the bucket with the first page of results from the well. -- We then pour out the bucket, one result at a time. -- If the bucket is empty and the well is also empty, stop. -- If the bucket is empty and the well still has water, fill the bucket again and resume pouring. - -Results from the well can be processed as each "bucket" arrives, and the bucket will be refilled as long as there are still results left. - -How do we implement this in Elixir? We start by defining a `stream` function. - -```elixir -def stream - start = fn -> end - next_item = fn -> end - stop = fn -> end - - Stream.resource(start, next_item, stop) -end -``` -The [Stream.resource/3][Stream.resource] method takes three funs. - -- `start` is used to initialize the Stream, and set its initial state. -- `next_item` takes the Stream's previous state and returns a tuple, like so: {[next item for collection], new_state} -- `stop` is a handy hook to clean up once the Stream is finished. - -We'll look at each of these in order. - -## start - -```elixir -start = fn -> - # Assumes there is a list/0 method that will fetch the first page - # of the API, in the format {status, items, paging_metadata} - case list do - {:ok, items, meta} -> {items, meta["next_page_uri"]} - _error -> {[], nil} - end -end -``` - -We will store our state for the Stream in a two-element tuple, in this format: `{items, next_page_url}`. If our `list/0` method returns the first page successfully, we'll fill the state with that first page of items. - -If, on the other hand, the first page doesn't load, we'll start with an empty state which we'll make cause the Stream to finish instantly. - -## next_item - -This is where things get interesting. - -```elixir -next_item = fn state = {[], nil} -> {:halt, state} - state = {[], next} -> fetch_next_page.(state) - state -> pop_item.(state) -end -``` - -The logic is pretty simple: - -- If there are no items left in the current page, and there is no next page, then stop. -- If there are no items left in the current page, but there is a next page, then go get that page. -- If neither of the above are true, pop an item off the state and return it. - -You'll notice there are two new funs here, `fetch_next_page` and `pop_item`. They look like this: - -```elixir -# Use pattern matching to pop the top item off the list of items, passing the -# tail as the new state. -pop_item = fn {[head|tail], next} -> - new_state = {tail, next} - {[head], new_state} -end - -# Get the next page, and use pop_item to both set the new state and return the -# first item of the new page. -fetch_next_page = fn state = {[], next_page} -> - # Assumes you have a method called `fetch_page` which will take a page URL and - # get that page of results. - case fetch_page(next_page) do - {:ok, items, meta} -> pop_item.({items, meta["next_page_uri"]}) - {:error, _msg} -> {:halt, state} - end -end -``` - -## stop -The simplest function of all! We don't really have anything to clean up. - -```elixir -stop = fn(_state) -> end -``` - -## Putting It All Together - -Our complete `stream/0` method looks like this: - -```elixir -def stream - start = fn -> - # Assumes there is a list/0 method that will fetch the first page - # of the API, in the format {status, items, paging_metadata} - case list do - {:ok, items, meta} -> {items, meta["next_page_uri"]} - _error -> {[], nil} - end - end - - # Use pattern matching to pop the top item off the list of items, passing the - # tail as the new state. - pop_item = fn {[head|tail], next} -> - new_state = {tail, next} - {[head], new_state} - end - - # Get the next page, and use pop_item to both set the new state and return the - # first item of the new page. - fetch_next_page = fn state = {[], next_page} -> - # Assumes you have a method called `fetch_page` which will take a page URL and - # get that page of results. - case fetch_page(next_page) do - {:ok, items, meta} -> pop_item.({items, meta["next_page_uri"]}) - {:error, _msg} -> {:halt, state} - end - end - - next_item = fn state = {[], nil} -> {:halt, state} - state = {[], next} -> fetch_next_page.(state) - state -> pop_item.(state) - end - - stop = fn(_state) -> end - - Stream.resource(start, next_item, stop) -end -``` - -You can now stream through resources to your heart's content! For example, you can process all of your Twilio Calls like this: - -```elixir -Enum.each ExTwilio.Call.stream, fn(call) -> - IO.puts call.sid -end -``` - -Since it's just a Stream, you can also _stack operations_ on the stream which will be lazily executed whenever you want! - -```elixir -# Find calls that were longer than 2 minutes (160 seconds) -# "stream" is returned instantly -stream = Stream.filter ExTwilio.Call.stream, fn(call) -> - call.duration > 120 -end - -# Append another operation to get just the call SIDs. Only calls that got -# through the filter above will be mapped. -stream = Stream.map stream, fn(call) -> call.sid end - -# Execute all the operations and return a collection: -Enum.into stream, [] # => ["CN234098123....", "CN123098123..."] - -# Or, just put out all the sids -Enum.each stream, fn(sid) -> - IO.puts sid -end -``` - -Using this technique, you can programmatically build up filters on your remote paged resources, knowing that all those filters will only be executed when you need the final data. (Aside: this is very similar to ActiveRecord::Relation) - -In short, this is very cool. But you knew that. - -## Wrapping Up - -[ExTwilio][ExTwilio] supports streaming for all Twilio endpoints that have a paged API. For those masochistic users who still want a blocking method, I've given them an `all` method as well. Internally, it just delegates to `stream`: - -```elixir -def all do - Enum.into stream, [] -end -``` - -Writing this code was a lot of fun, and I hope this walkthrough was helpful to you! If you want to learn more about Elixir's Stream API, check out the following: - -- [Stream Docs][Elixir.Stream] -- [Programming Elixir, by Dave Thomas][programming-elixir] - -If you want to keep up with my progress building full-featured Elixir libraries for Twilio, stay tuned to this blog, or check out [ExTwilio][ExTwilio] and [ExTwiml][ExTwiml]. - -[ExTwilio]: https://github.com/danielberkompas/ex_twilio -[ExTwiml]: https://github.com/danielberkompas/ex_twiml -[twilio]: https://www.twilio.com -[twilio-page-api]: https://www.twilio.com/docs/api/rest/response#response-formats-list -[Elixir.Stream]: http://elixir-lang.org/docs/stable/elixir/Stream.html -[Stream.resource]: http://elixir-lang.org/docs/stable/elixir/Stream.html#resource/3 -[programming-elixir]: https://pragprog.com/book/elixir/programming-elixir diff --git a/_posts/2015-04-01-contracts-gem.md b/_posts/2015-04-01-contracts-gem.md deleted file mode 100644 index 53f32355f..000000000 --- a/_posts/2015-04-01-contracts-gem.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -layout: post -title: "Contracts: Type Checking for Ruby" -categories: - - elixir - - ruby -date: 2015-04-01 07:00:00 -0800 ---- - -Like many Rubyists who read popular coding news, I recently came across the [Contracts][contracts] gem. It caught my eye because it implements some of the features I like in [Elixir][elixir], but for Ruby. - - - -## Type Annotations in Elixir - -Both Ruby and Elixir are duck-typed languages. However, Elixir (and its parent, Erlang) allow for static type analysis through annotations. In Elixir, they look like this: - -```elixir -@spec add(integer, integer) :: integer -def add(a, b) do - a + b -end -``` - -Custom types can be defined: - -```elixir -@type uuid :: String.t - -@spec find(uuid) :: map -def find(uuid) do - # implementation here -end -``` - -Arguments can be deconstructed via pattern matching in type specs, just like they can in function definitions. Suppose the `add/2` function took a tuple instead of two arguments. You could write it like this: - -```elixir -@spec add({integer, integer}) :: integer -def add({a, b}), do: a + b -``` - -All together, your `Math` module might look like this: - -```elixir -defmodule Math do - @spec add({integer, integer}) :: integer - def add({a, b}), do: add(a, b) - - @spec add(integer, integer) :: integer - def add(a, b), do: a + b -end - -Math.add(2, 3) # => 5 -Math.add({2, 3}) # => 5 -Math.add("hello") # => Error: no matching function definition -``` - -In Elixir, these type annotations are analyzed at compile-time for some errors, and the compiled BEAM file can also be run through [Dialyzer][dialyzer] for static analysis. Together, this two step process can find type errors _without even running your code,_ very similarly to statically typed languages. - -## Type Annotations in Ruby using Contracts - -The [Contracts gem][contracts] is able to do a lot of the same kind of type annotation that Elixir can do. It even adds support for multiple definitions of the same method! - -```ruby -require "contracts" - -class Math - include Contracts - - # Tuple version - # Ruby doesn't have tuples, so we'll use a two-element array - Contract [Num, Num] => Num - def add(numbers) - add numbers[0], numbers[1] - end - - # Two arguments version - Contract Num, Num => Num - def add(a, b) - a + b - end -end - -math = Math.new -math.add(1, 2) # => 3 -math.add([1, 2]) # => 3 -math.add([1, 2, 3]) # => Error, no matching contract -math.add("hello") # => Error, no matching contract -``` - -It also adds some of the pattern matching goodness that Elixir brings to the table. Suppose I had a function `handle_response`, that is supposed to deal with a response from a web API. In Elixir, that function might look like this: - -```elixir -def handle_response(:error, body) do - # Handle error case -end - -def handle_response(:success, body) do - # Handle success case -end -``` - -In Ruby without the Contracts gem, it would look like this: - -```ruby -def handle_response(status, body) - case status - when :error then # handle error - when :success then # handle success - end -end -``` - -With the Contracts gem, it looks a lot more like Elixir: - -```ruby -Contract :error, String => Any -def handle_response(status, body) - # handle error -end - -Contract :success, String => Any -def handle_response(status, body) - # Handle success case -end -``` - -## Important Differences between Contracts and Elixir - -There are a few important differences between the [Contracts][contracts] gem and Elixir's type annotations. - -| Feature | Contracts | Elixir | -| :--------------- | :----------------------------: | :----------------------------: | -| Compile-time | N/A | Yes | -| Static analysis | No | Yes | -| Violation errors | Yes | Yes | -| Pattern matching | Yes | Yes | -| Custom types | Yes | Yes | - -Since Ruby isn't compiled, the contracts are not statically analyzed prior to running your code. You won't catch a contract error before it occurs, so your tests would still need to exercise most of your code. - -## Benefits - -Why should you want type annotations? I can think of several reasons: - -- **They're optional**. You don't have to use annotations when you want - duck-typing. But when types are actually important, you can use them. -- **Type errors do exist.** Even if they don't matter to [@dhh][dhh]. -- **Contracts provide free documentation.** Very few functions can handle _any_ input. This means that developers always have to look up what types the function can handle, and if you document that, it will make their lives easier. Documentation can easily be generated off these annotations. -- **Code with greater confidence.** You will know that functions won't run without receiving valid input, cutting down on the number of bad things that could occur. - -Overall, I think the [Contracts][contracts] gem looks great, and I'm excited to use it in one of my next Ruby projects. - -## Additional Resources - -- [Contract Gem Tutorial](http://egonschiele.github.io/contracts.ruby/) -- [Contracts.coffee](http://disnetdev.com/contracts.coffee/) - -P.S. This is _not_ an April Fool's joke. - -[dhh]: http://twitter.com/dhh -[dialyzer]: http://www.erlang.org/doc/man/dialyzer.html -[elixir]: http://elixir-lang.org -[contracts]: https://rubygems.org/gems/contracts diff --git a/_posts/2015-04-03-run-dialyzer-on-elixir-on-travis.md b/_posts/2015-04-03-run-dialyzer-on-elixir-on-travis.md deleted file mode 100644 index f527ece7f..000000000 --- a/_posts/2015-04-03-run-dialyzer-on-elixir-on-travis.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -layout: post -title: "Run Dialyzer on Elixir on Travis CI" -categories: - - elixir -date: 2015-04-03 07:00:00 -0800 ---- - -In [my last post][contracts-gem], I talked about Elixir's typespec annotations and Erlang's static analysis tool, [Dialyzer][dialyzer]. All that talk was great and all, but how do you actually _use_ Dialyzer on Elixir projects? - - - -In particular, how do you run Dialyzer on an Elixir project on Travis CI? In today's blog post, I'll show you how. - -## Persistent Lookup Table - -Dialyzer requires a "persistent lookup table", or PLT, in order to perform its analysis. This PLT holds references to all the system Erlang libraries, and Elixir's source code itself. In order to run Dialyzer on an Elixir project, you've got to first generate a PLT for your system. - -Yes, the PLT has to be generated _for the system that you're going to run -Dialyzer on_, because the paths in the PLT are _hard coded_ when it is generated. A PLT generated on your Mac won't work on Travis. - -What's more, the PLT takes quite a while to generate. If you generated it every time you ran your build on Travis, you'd slow down your builds by minutes. - -Luckily for you, I've built a bunch of PLTs for Elixir on Travis already. You can find them here: [danielberkompas/travis_elixir_plts][plts]. You're welcome. - -## .travis.yml - -With a prebuilt PLT, running dialyzer on Travis is simple: - -```yaml -language: elixir -otp_release: - - 17.4 -before_script: - # Set download location - - export PLT_FILENAME=elixir-${TRAVIS_ELIXIR_VERSION}_${TRAVIS_OTP_RELEASE}.plt - - export PLT_LOCATION=/home/travis/$PLT_FILENAME - # Download PLT from danielberkompas/travis_elixir_plts on Github - # Store in $PLT_LOCATION - - wget -O $PLT_LOCATION https://raw.github.com/danielberkompas/travis_elixir_plts/master/$PLT_FILENAME -script: - - mix test - - dialyzer --no_check_plt --plt $PLT_LOCATION --no_native _build/test/lib/$YOUR_PROJECT_NAME/ebin -``` - -A few notes on the options passed to Dialyzer here: - -- `--no_check_plt`: Increases speed. Dialyzer assumes PLT is up to date. -- `--no_native`: Also increases speed. Because it is used to analyze large projects, Dialyzer likes to compile parts into native code before doing analysis. This takes time. -- `_build/test/lib/$YOUR_PROJECT_NAME/ebin`: The directory in which the compiled BEAM files created by Mix are. Replace `$YOUR_PROJECT_NAME` with whatever your folder name happens to be. - -Obviously, you could pass any options supported by Dialyzer here. Run `dialyzer --help` to see them all. And that's that! You can now ensure that both you and all your collaborators pay attention to Dialyzer. - -## Additional Resources - -- [Dialyxir][dialyxir]: Adds a `mix dialyzer` task. Also adds a `mix - dialyzer.plt` task which can be very helpful when generating local PLTs on your dev machine. - -- [Dialyzer docs][dialyzer] - -[contracts-gem]: /elixir/ruby/2015/04/01/contracts-gem.html -[dialyxir]: https://github.com/jeremyjh/dialyxir -[dialyzer]: http://www.erlang.org/doc/man/dialyzer.html -[plts]: https://github.com/danielberkompas/travis_elixir_plts diff --git a/_posts/2015-04-08-generate-dialyzer-plts-on-travis.md b/_posts/2015-04-08-generate-dialyzer-plts-on-travis.md deleted file mode 100644 index 600a9b84a..000000000 --- a/_posts/2015-04-08-generate-dialyzer-plts-on-travis.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: post -title: "Build Dialyzer PLTs on Travis CI" -categories: - - elixir -date: 2015-04-08 07:00:00 -0800 ---- - -In a [previous post][previous], I wrote about how to easily get a prebuilt PLT for your Elixir builds on Travis. But what if that doesn't work for you? What if you have special requirements that my [prebuilt PLTs][prebuilt] don't meet? - - - -## TL,DR; -You can fork my [Travis PLT generator][generator]. You'll just need an Amazon S3 bucket to store the resulting PLT files. - -## How It Works -Since I'm not an expert at setting up Chef infrastructure, I knew that I -wouldn't be able to reliably get an identical virtual machine like the ones used by Travis in production. So, building the PLTs locally was not an option. - -It seemed both easier and more reliable to have Travis build the PLTs itself. Since Travis is free for open source, I whipped up a simple repository and added a `.travis.yml` that: - -1. Runs `mix dialyzer` from the [dialyxir][dialyxir] package. -2. Uses the Travis [Artifacts][artifacts] script to upload the resulting file(s) to Amazon S3. - -Since Travis allows running builds against multiple versions of Elixir and OTP, getting a PLT for each possible combination is a cinch: - -```yaml -language: elixir -elixir: - - 1.0.0 - - 1.0.1 - - 1.0.2 - - 1.0.3 - - 1.0.4 -otp_release: - - 17.4 - - 17.3 - - 17.1 - - 17.0 -``` - -You'll find more details in the [README][generator]. - -[dialyxir]: https://github.com/jeremyjh/dialyxir -[artifacts]: https://github.com/travis-ci/artifacts -[previous]: http://blog.danielberkompas.com/elixir/2015/04/08/generate-dialyzer-plts-on-travis.html -[prebuilt]: https://github.com/danielberkompas/travis_elixir_plts -[generator]: https://github.com/danielberkompas/travis_elixir_plt_generator diff --git a/_posts/2015-04-17-keep-your-ets-tables-alive.md b/_posts/2015-04-17-keep-your-ets-tables-alive.md deleted file mode 100644 index 5b51290c6..000000000 --- a/_posts/2015-04-17-keep-your-ets-tables-alive.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: post -title: "On Keeping Your ETS Tables Alive" -categories: - - elixir -date: 2015-04-17 07:00:00 -0800 ---- - -In my ongoing quest to make Elixir libraries that integrate with -[Twilio][twilio], I found that I needed a lookup table to store the state of ongoing calls in. - -In Rails, this table would probably be a Postgres table or a list key in Redis. But before jumping to one of these familiar solutions, I thought, "What does Elixir/Erlang already have that would meet this need?" - - - -## Erlang Term Storage (ETS) - -I was not disappointed. It turns out that Erlang has an in-memory store called [ETS][ets], which is perfect for my use case, and here's why. - -In Ruby/Rails apps, most of your state is thrown away after every request unless you save it in a database. Then, on the next request, you have to load it all up again and convert it back into Ruby data types, making the request take longer than it needs to. This is because Rails doesn't really offer a persistent in-memory store that survives between requests. - -I didn't want to have to calculate the previous state of a call or load it out of a database. After all, I just calculated it! It should still be available in memory when the user takes another action. The [ETS][ets] store is great for this because: - -1. It persists between requests, and -2. The data stored in it (regular Erlang tuples) doesn't have to be converted back into Erlang types, so there's very little overhead. - -## The Problem: Process Death - -There was just one little problem. ETS tables are erased as soon as the process which owns them dies. So, if there is ever a crash in my table owner, my entire ETS table will be erased, leaving on-going calls in an inconsistent state. - -This is a case where Erlang's "Let it crash" philosophy shouldn't be followed. The OTP supervisor _can_ reboot my crashed process, but the ETS table it owned will be gone, making for a poor crash recovery. This is a case where the defaults need to be improved. - -## The Solution: A Table Manager - -To prevent this, I set up a very simple `TableManager` process. It has to be very simple to ensure that _it_ never crashes. This process creates the table, sets itself as the "[heir][heir]", and then [gives away][give-away] the table to a designated consumer process. - -If that consumer process ever dies, the `TableManager` will receive back -control, wait until the consumer is rebooted, and then hand it back. The ETS table stays alive, and everybody's happy. - -Since I've been open sourcing my Elixir work so far, I decided to open source this `TableManager` process. You can find it in my [Immortal][immortal] library on Github. - -## Final Thoughts - -I don't need Redis any more! - -Seriously, I was impressed at how easy it was to create a durable state storage system without any external dependencies in Elixir/Erlang. This concept of persistent state makes my applications feel more like OS programming rather than transactional, web programming. So far, I like the difference. - -[ets]: http://www.erlang.org/doc/man/ets.html -[heir]: http://www.erlang.org/doc/man/ets.html#heir -[give-away]: http://www.erlang.org/doc/man/ets.html#give_away-3 -[immortal]: https://github.com/danielberkompas/immortal -[twilio]: http://twilio.com diff --git a/_posts/2015-04-23-telephonist-on-github.md b/_posts/2015-04-23-telephonist-on-github.md deleted file mode 100644 index f091471bc..000000000 --- a/_posts/2015-04-23-telephonist-on-github.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: post -title: "Telephonist: State Machines for Twilio" -categories: - - elixir -date: 2015-04-23 07:00:00 -0800 ---- - -After a couple months of work, I've finally got the library I've been working toward for Twilio, and I'm calling it "[Telephonist][telephonist]". You can read all about it [over on Github][telephonist], but here's a taste: - - - -```elixir -defmodule CustomCallFlow do - use Telephonist.StateMachine, initial_state: :choose_language - - state :choose_language, twilio, options do - say "#{options[:error]}" # say any error, if present - gather timeout: 10 do - say "For English, press 1" - say "Para español, presione 2" - end - end - - state :english, twilio, options do - say "Proceeding in English..." - end - - state :spanish, twilio, options do - say "Procediendo en español..." - end - - # If the user pressed "1" on their keypad, transition to English state - def transition(:choose_language, %{Digits: "1"} = twilio, options) do - state :english, twilio, options - end - - # If the user pressed "2" on their keypad, transition to Spanish state - def transition(:choose_language, %{Digits: "2"} = twilio, options) do - state :spanish, twilio, options - end - - # If neither of the above are true, append an error to the options and - # remain on the current state - def transition(:choose_language, twilio, options) do - options = Map.put(options, :error, "You pressed an invalid digit. Please try again.") - state :choose_language, twilio, options - end -end -``` - -Use the state machine from your web framework like this: - -```elixir -# The web framework shown here is pseudo-code -def index(conn, twilio) do - options = %{} # Whatever I want to be able to use in my states and transitions - state = Telephonist.CallProcessor.process(CustomCallFlow, twilio, options) - render conn, xml: state.twiml -end -``` - -Which will render some nice TwiML: - -```xml - - - - For English, press 1 - Para español, presione 2 - - -``` - -Unlike my [previous attempt at this][twiliomenu], Telephonist has _no dependency_ on any ORM layer, is very concurrent, and produces code that is easy to maintain. - -Telephonist will be released on [Hex][hex] soon, (once I get the documentation finished) and in the future, I may create a Ruby version. - -[telephonist]: https://github.com/danielberkompas/telephonist -[twiliomenu]: https://github.com/danielberkompas/twiliomenu -[hex]: http://hex.pm diff --git a/_posts/2015-04-28-why-a-static-blog-is-a-good-idea.md b/_posts/2015-04-28-why-a-static-blog-is-a-good-idea.md deleted file mode 100644 index ee951004e..000000000 --- a/_posts/2015-04-28-why-a-static-blog-is-a-good-idea.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -layout: post -title: "Why a Static Blog is a Good Idea" -date: 2015-04-28 07:00:00 -0800 ---- - -Rather than use a popular blogging solution like [Wordpress][wordpress], [Blogger][blogger], or even the new kid on the block, [Ghost][ghost], I chose to use an old school method to write my blog: plain text. - -This blog is a static site. My posts are written in plain text in a text editor on my computer, and are converted into simple HTML pages when I deploy. There is no admin panel. There is no pretty online editor. Just text. - - - -Why do I do this? Wouldn't it be better to use something less _manual_? There are several reasons, and I think they're worth considering for anyone who wants to write a blog. - -## 1. Simplicity - -There's nothing magical about this blog. It's just a collection of plain text files which get converted into HTML files. There's nothing complex about a bunch of plain text files in a folder, and so in that sense, it's easy to use. - -Further, writing in plain text helps you focus. There are no shiny doodads to distract you, no fonts to adjust, no colors to select. Because there's nothing to do but write, you can find out quickly whether you're more interested in having a pretty website or in _actually writing_. - -## 2. Redundancy - -Normally, your posts get subsumed into whatever blogging service you're using. If that tool dies for whatever reason, you could lose your posts. In contrast, my posts are stored locally on my computer, in my backup systems, and [on Github][github]. Because of this redundancy, there's much less chance I'll lose anything important that I have written. - -## 3. Portability - -Since my posts are in plain text in a folder, they are _very_ portable. If I decide to use a different system in 2030, it will be much less difficult to get my posts out of the old and into the new system than it would be if I were using Wordpress. - -## 4. Speed - -Since my blog ends up being a simple HTML site, it is very fast. The server I host it on has to do very little work, which means that even a very modest host can handle a lot of traffic. - -## 5. Security - -Since there is no admin portal, there are far fewer ways that my blog could be hacked. There are no zero-day vulnerabilities in an HTML webpage. There are no security patches to install. - -Further, since I host this on my own domain, there is no need to get an SSL certificate for the blog. I would need one if I were self-hosting a tool that had an admin portal. - -An attacker _could_ attempt to bring my blog down by making millions of requests against it. However, this is easily mitigated, because the entire blog can be moved to a global content delivery network _designed_ to serve millions of requests. What's more, this can be done cheaply and quickly. - -## 6. Change Tracking - -My blog is tracked with [Git](http://git-scm.org), a popular change-tracking tool used by programmers everywhere. This means that _every_ change I make to my blog posts is tracked. This revision history can be publicly available, [like mine is now][github-commits], or private, depending on your taste. - -Personally, I think the world might be a better place if more bloggers opened themselves up to this kind of accountability. - -## 7. Few Compromises - -I get all of the above with few compromises. I can still do all of the -following quite easily: - -- Upload images -- Change the theme -- Reformat text -- Add dynamic behavior (like comments) - -Granted, it's easier for me because web programming is my profession, but with some help, most semi-technical people could do the same. - -## Conclusion - -So, that's why I think it's a good idea to have a static blog rather than use Wordpress or some other tool. If you need pointers on how to get one set up, hit me up [on Twitter][twitter], or check out [Jekyll][jekyll]. - -[ghost]: https://ghost.org -[blogger]: http://blogger.com -[wordpress]: http://wordpress.com -[markdown]: http://daringfireball.net/projects/markdown/ -[github]: http://github.com/danielberkompas/danielberkompas.github.io -[github-commits]: https://github.com/danielberkompas/danielberkompas.github.io/commits/master -[twitter]: https://twitter.com/dberkom -[jekyll]: https://jekyllrb.com diff --git a/_posts/2015-05-08-testing-ecto-validations.md b/_posts/2015-05-08-testing-ecto-validations.md deleted file mode 100644 index 73294db08..000000000 --- a/_posts/2015-05-08-testing-ecto-validations.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -layout: "post" -title: "Testing Ecto Validations" -categories: - - elixir -date: 2015-05-08 07:00:00 -0800 ---- - -I recently was playing around with [Phoenix][phoenix] and [Ecto][ecto], Elixir's database library, and I wanted to test my validations. In the process, I wrote a [little library][Ecto.ValidationCase] along the lines of [Shoulda][shoulda] from Ruby. However, when José Valim saw it, he -[suggested a much better approach][better] which I think illustrates what makes Elixir great. - - - -## Validating Ecto Models - -In Ecto, a model looks like this: - -```elixir -defmodule MyApp.User do - use Ecto.Model - - schema "users" do - field :username, :string - field :email, :string - field :encrypted_password, :string - end -end -``` - -If you want to validate that a new user has a username and email address before inserting it into the database, you can define a `changeset/2` function: - -```elixir -@required_params ~w(username, email) -@optional_params ~w() - -def changeset(model, params) do - model - |> cast(params, @required_params, @optional_params) -end -``` - -`cast/4` will return a `Changeset` struct which has `valid?` attribute, which can be true or false. It can perform basic presence validation, using a list of required and optional fields. Everything not in those fields will be ignored. - -If you want to perform more advanced validation, Ecto has helpers for that as well, such as `validate_unique`: - -```elixir -def changeset(model, params) do - model - |> cast(params, @required_params, @optional_params) - |> validate_unique(:email) - |> validate_format(:email, ~r/@/) -end -``` - -Since all the validators take a changeset as their first argument, they can be made into pipelines quite easily, like the above. - -With a `changeset/2` function in place, you can then design your controller action like so: - -```elixir -def create(conn, params) do - user = MyApp.User.changeset(%MyApp.User{}, params) - - if user.valid? - MyApp.Repo.insert(user) - # return success - else - # render error - end -end -``` - -There are several reasons this is great. - -1. There are no global validations gumming things up in places they shouldn't. If you do Rails long enough, you'll have a time when its global validations cause you pain. -2. Testing models is easy, because you don't _have_ to insert valid models in every test. Changesets are an opt-in behavior. -3. If you need different validations depending on context, just define multiple `changesets`, like this: - -```elixir -# :create is an action name, or whatever else you want -def changeset(:create, model, params) do - model - |> cast(params, ["username"], []) -end - -def changeset(:update, model, params) do - model - |> cast(params, ["email"], []) -end -``` - -## Testing Validations -Testing an Ecto validation is a simple process: - -```elixir -# Generate a changeset for a given field/value pair -changeset = MyApp.User.changeset(%MyApp.User{}, %{username: nil}) - -# Assert that an error is present -assert {:username, "can't be blank"} in changeset.errors - -# Or, assert that the validation passed -refute {:username, "can't be blank"} in changeset.errors -``` - -As José suggested, this can be cleaned up more by adding a simple private function to your test case module: - -```elixir -defp errors_on(model \\ %MyApp.User{}, params) do - model.__struct__.changeset(model, params).errors -end -``` - -You can then write the above like this: - -```elixir -assert {:username, "can't be blank"} in errors_on(%{username: nil}) -refute {:email, "can't be blank"} in errors_on(%{email: nil}) -``` - -The code is explicit, and there is no magic. What's more, there's no need for a gem like `shoulda` to clean things up here, because it's simple enough! - -This `errors_on/2` function can easily be extracted to a module and reused, just like any other Elixir code. New developers looking at your code will easily be able to figure out what is going on, unlike `shoulda`, where they must accept what it's doing on faith. - -## A Simpler Way to Code - -I love how Elixir encourages this simpler way of coding. There are more moving parts than there are in Ruby land, perhaps, but they're conceptually simpler. Everything is made up of state and functions when you get down to it. It's certainly possible to do complex things, but more often than not, you _don't have to._ - -It all comes down to the difference between _explicitness_ and _implicitness_. Elixir code is meant to be mostly _explicit_, whereas Ruby code often is _implicit_. Explicit code doesn't hide what it's doing. Implicit code hides its implementation and thus has a steeper learning curve should you need to dig into its innards. - -If you write code explicitly, it may be a little longer than the implicit version, but it will be easier to change, making for a more maintainable system. - -I'm still a recovering Ruby developer, (where we DSL all the things!) but learning this explicit style has been an enlightening experience so far, and one I'd recommend to any Rubyist. - -## Resources - -- [Ecto.Changeset Documentation][Ecto.Changeset] -- [José Valim Taking Me to the Woodshed][better] (It was good, though) - -[shoulda]: https://github.com/thoughtbot/shoulda -[better]: https://groups.google.com/forum/#!topic/elixir-lang-talk/kwLLyCiarls -[Ecto.ValidationCase]: https://github.com/danielberkompas/ecto_validation_case -[Ecto.Changeset]: http://hexdocs.pm/ecto/0.11.2/Ecto.Changeset.html -[phoenix]: http://phoenixframework.org -[ecto]: https://github.com/elixir-lang/ecto diff --git a/_posts/2015-05-12-a-personal-development-plan.md b/_posts/2015-05-12-a-personal-development-plan.md deleted file mode 100644 index 5ab9d48f5..000000000 --- a/_posts/2015-05-12-a-personal-development-plan.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: "post" -title: "A Personal Development Plan" -author: "Daniel Berkompas" -date: 2015-05-12 07:00:00 -0800 ---- - -It's important to keep your skills sharp. Nowhere is this more true than in web development, where it sometimes feels like progress moves at light speed. As is obvious from my posts on this blog, I've been learning a lot of new stuff lately, mostly [Elixir][elixir]-related. - - - -But no matter how much I learn, there's always more and more possible technologies out there _to learn_. In fact, a few new ones were probably invented while I wrote this post. The more I know, the more I know I don't know. - -What I need is focus: a plan to cut through the noise and focus on the things I really want to learn, and why. To that end, I just put together a [personal development plan](/personal-development-plan) to structure my learning around. - -## Searching for Permanence - -In this fast-paced environment, I'm looking for tried and true technology that has already passed the test of time or has the potential to do so. I don't have time to keep up with everything, so I want to keep up with the _right_ things. - -This is why I plan on studying arcane stuff like regular expressions, SQL, algorithms, and Elixir. The first three have proven themselves useful over time, and aren't going anywhere. Elixir is a new, brighter face of Erlang, which has been in production for 30 years now, and is known for rock-solid stability. - -This is also why I'm _not_ casting aside Ruby on Rails either. For all its quirks, it has been run in production for 10 years now, and is used successfully by many companies. Ruby isn't on my development plan simply because I believe I'm already sufficiently competent in it, at least for now. - -## Conclusion - -It was a really valuable exercise to put this plan together, and perhaps someone else out there will find it useful. I plan on updating it throughout the year as I accomplish milestones, and I'll write a blog post at the end of this year reviewing my progress. - -#### [View My Plan](/personal-development-plan) - -[elixir]: http://elixir-lang.org diff --git a/_posts/2015-05-20-useful-ecto-validators.md b/_posts/2015-05-20-useful-ecto-validators.md deleted file mode 100644 index 6bcd61165..000000000 --- a/_posts/2015-05-20-useful-ecto-validators.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Useful Ecto Validators" -categories: - - elixir -date: 2015-05-20 07:00:00 -0800 ---- - -Over the past week, I've created a couple custom validators for my Elixir projects which use [Ecto][ecto]. Since validators are just functions that take a changeset and return a changeset, they're very easy to write. - - - -I'll update this post in the future if I find or write any more useful validators, or refactor ones I've already written. - -## validate_url - -This validator will ensure that a URL is parseable by the Erlang `http_uri` module. I obviously could have gone for a huge regular expression here, but this seemed a little more trustworthy. - -```elixir -def validate_url(changeset, field, options \\ []) do - validate_change changeset, field, fn _, url -> - case url |> String.to_char_list |> :http_uri.parse do - {:ok, _} -> [] - {:error, msg} -> [{field, options[:message] || "invalid url: #{inspect msg}"}] - end - end -end -``` - -### Usage - -```elixir -validate_url(changeset, :url, message: "URL is not a valid URL!") -``` - -## validate_array_inclusion - -I wrote this validator because of PostgreSQL's Array field type. Ecto already has a `validate_inclusion` validator which will ensure that a given value is a member of a given array. However, in order to validate a Postgres array field, I needed to validate that all _members_ of array A are also members of array B. - -This function does the trick: - -```elixir -def validate_array_inclusion(changeset, field, allowed, options \\ []) do - validate_change changeset, field, fn _field, values -> - valid? = Enum.all? for value <- values, do: value in allowed - if valid?, do: [], else: [{field, options[:message] || "is invalid"}] - end -end -``` - -### Usage - -```elixir -validate_array_inclusion(changeset, :notify, ~w(email sms phone), message: "custom message here") -``` - -This will ensure that the "notify" array only contains some subset of the values `["email", "sms", "phone"]`. - -[ecto]: https://github.com/elixir-lang/ecto diff --git a/_posts/2015-06-09-number-helpers-for-elixir.md b/_posts/2015-06-09-number-helpers-for-elixir.md deleted file mode 100644 index 05a372537..000000000 --- a/_posts/2015-06-09-number-helpers-for-elixir.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Number Helpers For Elixir" -categories: - - elixir -date: 2015-06-09 07:00:00 -0800 ---- - -Since I started working on a [Phoenix](http://phoenixframework.org) app, I was frustrated by the lack of number conversion helpers in Elixir/Erlang. I didn't want to have to rewrite `number_to_currency` every time I want to use it. - -So, I created [Number](https://github.com/danielberkompas/number). It's basically a shallow clone of NumberHelper from ActionView in Rails. Now, Elixir users can have `number_to_currency` too! - - - -```elixir -import Number.Currency - -number_to_currency(nil) -nil - -number_to_currency(1000) -"$1,000" - -number_to_currency(1000, unit: "£") -"£1,000" - -number_to_currency(-1000) -"-$1,000" - -number_to_currency(-234234.23) -"-$234,234.23" - -number_to_currency(1234567890.50) -"$1,234,567,890.50" - -number_to_currency(1234567890.506) -"$1,234,567,890.51" - -number_to_currency(1234567890.506, precision: 3) -"$1,234,567,890.506" - -number_to_currency(-1234567890.50, negative_format: "(%u%n)") -"($1,234,567,890.50)" - -number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "") -"R$1234567890,50" - -number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u") -"1234567890,50 R$" -``` - -As my free time permits, I expect to add more features to it, implementing those parts of Rails's NumberHelper that I consider useful to a broader audience. diff --git a/_posts/2015-06-10-how-to-write-guard-macros.md b/_posts/2015-06-10-how-to-write-guard-macros.md deleted file mode 100644 index 4b6c5e03f..000000000 --- a/_posts/2015-06-10-how-to-write-guard-macros.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "How to Write Guard Macros" -categories: - - elixir -date: 2015-06-10 07:00:00 -0800 ---- - -I recently discovered that it is possible to write custom guard macros for Elixir, provided that the macro expands to expressions that are supported in guards natively. - -I used this to create an `is_blank` guard. Elixir doesn't come with a `blank?` function, so you have to do it manually. Blank values are `" "`, `""`, and `nil`. To check `blank?` in Elixir, you can check if a given value is `in` this array of blank values. - - - -```elixir -value in [" ", "", nil] # => true / false -``` - -You can do this in a guard: - -```elixir -def foo(bar) when bar in [" ", "", nil] do - # baz ... -end -``` - -Using `in` directly works great if you only have to do it once. But if you find yourself wanting `is_blank` all over your code, you can write a macro like so: - -```elixir -defmacro is_blank(value) do - quote do - unquote(value) in [" ", "", nil] - end -end -``` - -You can then use `is_blank` in your guard statement, because the `in` clause is supported by guards natively: - -```elixir -def foo(bar) when is_blank(bar) do - # baz ... -end -``` - -You can also use `is_blank` like any other function in other parts of your code, outside of guards. - -## Gotchas - -- Macros used in guards must be defined in a **different module** than the one where they are being used. This is due to the way Elixir compiles macros. - -- You must `require` or `import` your other module in order to be able to use the Macro. If you `require`, you'll have to use the macro like this: `OtherModule.macro`. If you use `import`, you can just use `macro`. diff --git a/_posts/2015-06-16-rate-limiting-a-phoenix-api.md b/_posts/2015-06-16-rate-limiting-a-phoenix-api.md deleted file mode 100644 index 63105184b..000000000 --- a/_posts/2015-06-16-rate-limiting-a-phoenix-api.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -layout: "post" -title: "Rate Limiting a Phoenix API" -author: "Daniel Berkompas" -categories: - - elixir -date: 2015-06-16 07:00:00 -0800 ---- - -In my spare time, I've been working on a little [Phoenix][phoenix] project that involves a JSON API. Developers frequently neglect rate limiting when they build an API, assuming they are even aware that it is a best practice. - -It's true that in many cases rate limiting isn't worth the effort, but when it comes to authentication, it definitely is. For example, the recent high-profile [iCloud][icloud] security breach which released celebrity photos in to the internet could have been prevented had Apple implemented rate limiting on one of their authentication APIs. This would have prevented the brute-force attack that the hackers used to guess the celebrities' passwords. - - - -My little API isn't likely to hold any sensitive information, but I decided to rate limit my authentication API anyway. Here's how I did it. - -## Introducing Plugs - -In [Phoenix][phoenix], the request lifecycle is handled through "plugs", which are simple functions or modules that take a connection, modify it, and then return the modified connection. Plugs come from an Elixir core project, aptly named "[Plug][plug]". - -If you're familiar with Ruby, plugs work very similarly to Rack middleware. However, instead of being a rare form of voodoo like Rack sometimes is to Ruby developers, plugs are very accessible in Elixir/Phoenix, and are used often. - -In its simplest form, a plug looks like this: - -```elixir -def my_custom_plug(conn, options \\ []) do - conn -end -``` - -This plug does nothing. It just takes a connection and returns it, which is all a good little plug _has_ to do. To use this plug in a Phoenix controller, you just have to import the function and add it to the controller's plug pipeline: - -```elixir -defmodule MyApp.Controller do - use MyApp.Web, :controller - import MyPlug, only: [my_custom_plug: 2] - - plug :my_custom_plug - plug :action -end -``` - -The `my_custom_plug` function will then run on the connection before the -controller action is called. - -## The RateLimit Plug - -The [Plug][plug] interface is ideal for rate limiting because it allows us to intercept the request before any real work is done. This prevents rejected requests from producing any serious load on the server. - -For my plug, I used a library called [ExRated][ex_rated] to do the brunt of the rate limiting work. It has a simple API: - -```elixir -ExRated.check_rate("bucket_name", interval_in_milliseconds, maximum_requests) -# => {:ok, count} or {:fail, count} -``` - -Using this, it's very easy to construct a plug: - -```elixir -defmodule MyApp.RateLimit do - import Phoenix.Controller, only: [json: 2] - import Plug.Conn, only: [put_status: 2] - - def rate_limit(conn, options \\ []) do - case check_rate(conn, options) do - {:ok, _count} -> conn # Do nothing, allow execution to continue - {:fail, _count} -> render_error(conn) - end - end - - defp check_rate(conn, options) do - interval_milliseconds = options[:interval_seconds] * 1000 - max_requests = options[:max_requests] - ExRated.check_rate(bucket_name(conn), interval_milliseconds, max_requests) - end - - # Bucket name should be a combination of ip address and request path, like so: - # - # "127.0.0.1:/api/v1/authorizations" - defp bucket_name(conn) do - path = Enum.join(conn.path_info, "/") - ip = conn.remote_ip |> Tuple.to_list |> Enum.join(".") - "#{ip}:#{path}" - end - - defp render_error(conn) do - conn - |> put_status(:forbidden) - |> json(%{error: "Rate limit exceeded."}) - |> halt # Stop execution of further plugs, return response now - end -end -``` - -You can then use this plug to rate limit specific actions in your controller like so: - -```elixir -import MyApp.RateLimit - -plug :rate_limit, max_requests: 5, interval_seconds: 60 when action in [:create] -plug :action -``` - -## Customizing for Authentication - -The RateLimit plug as it currently exists is great for most use cases, but it still isn't adequate for authentication. This is because limiting based on IP address still allows an attacker to make thousands of attempts on a user account from different computers. - -To protect against this, we need to rate limit login attempts based on the _username_, not the IP address. The plug needs an optional `:bucket_name` parameter, so that this can be customized. - -```elixir -defp check_rate(conn, options) do - interval_milliseconds = options[:interval_seconds] * 1000 - max_requests = options[:max_requests] - bucket_name = options[:bucket_name] || bucket_name(conn) - - ExRated.check_rate(bucket_name, interval_milliseconds, max_requests) -end -``` - -We then add a new function to our controller, setting the dynamic `:bucket_name` to "authorization:{email}": - -```elixir -def rate_limit_authentication(conn, options \\ []) do - options = Dict.merge(options, [bucket_name: "authorization:" <> conn.params.email]) - MyApp.RateLimit.rate_limit(conn, options) -end -``` - -And use this instead of the old `:rate_limit` plug: - -```elixir -import MyApp.RateLimit - -plug :rate_limit_authentication, max_requests: 5, interval_seconds: 60 -plug :action -``` - -This will then rate limit requests against the user's email address, not the IP address that the requests are coming from. So, no matter how many IPs the hacker has access to, he can't get around the rate limit. - -## The Finished Plug - -```elixir -defmodule MyApp.RateLimit do - import Phoenix.Controller, only: [json: 2] - import Plug.Conn, only: [put_status: 2] - - def rate_limit(conn, options \\ []) do - case check_rate(conn, options) do - {:ok, _count} -> conn # Do nothing, pass on to the next plug - {:fail, _count} -> render_error(conn) - end - end - - defp check_rate(conn, options) do - interval_milliseconds = options[:interval_seconds] * 1000 - max_requests = options[:max_requests] - bucket_name = options[:bucket_name] || bucket_name(conn) - - ExRated.check_rate(bucket_name, interval_milliseconds, max_requests) - end - - # Bucket name should be a combination of ip address and request path, like so: - # - # "127.0.0.1:/api/v1/authorizations" - defp bucket_name(conn) do - path = Enum.join(conn.path_info, "/") - ip = conn.remote_ip |> Tuple.to_list |> Enum.join(".") - "#{ip}:#{path}" - end - - defp render_error(conn) do - conn - |> put_status(:forbidden) - |> json(%{error: "Rate limit exceeded."}) - |> halt # Stop execution of further plugs, return response now - end -end -``` - -## Performance - -The performance of this plug is really stunning. On my relatively modest Macbook Pro, I'm seeing sub-millisecond response times when the rate limit kicks into effect. Rails devs, let that sink in for a second. - -_Sub-millisecond response times..._ - -So, using this plug doesn't slow down valid requests in any measurable way, and with response times like that, it will be hard to overwhelm your API with bogus traffic. - -## Conclusion - -I was pleasantly surprised at how easy this was to implement and how elegant the solution ended up being. Let's review what we implemented: - -- A rate limiter that can be customized per action and per controller. -- Without any external dependencies. (I'm looking at you, Redis) -- With sub-millisecond response times. - -Elixir and Phoenix continue to impress me! This exercise also made me think that I should look more into Rack in Ruby-land. It's probably under-utilized for problems like this. Perhaps that will be the subject of a future post. For now though, so long! - -[ex_rated]: http://hex.pm/packages/ex_rated -[icloud]: http://icloud.com -[plug]: https://github.com/elixir-lang/plug -[phoenix]: http://phoenixframework.org diff --git a/_posts/2015-07-03-encrypting-data-with-ecto.md b/_posts/2015-07-03-encrypting-data-with-ecto.md deleted file mode 100644 index 96b406781..000000000 --- a/_posts/2015-07-03-encrypting-data-with-ecto.md +++ /dev/null @@ -1,254 +0,0 @@ ---- -layout: "post" -title: "Encrypting Data With Ecto" -author: "Daniel Berkompas" -categories: - - elixir - - security -date: 2015-07-03 07:00:00 -0800 ---- - -**Author's Note: This post has been substantially updated since [it was first posted][original-post]. A much stronger crypto implementation has been used and the code has been reworked to be cleaner and more efficient.** - -**I've also released an open-source Hex package that implements the approach to encryption I describe in this post. [Read the announcement post here](/2015/09/22/cloak-your-ecto-data.html)**. - -In the future, as privacy becomes more and more of an issue, we're going to be encrypting a lot more of the data we store on the web. With that in mind, I thought it would be a good idea to figure out a good way to integrate data encryption with [Elixir's][elixir] database library, [Ecto][ecto]. - - - -## Requirements - -In Rails, we have a gem called [attr_encrypted][attr_encrypted] which makes it easy to have encrypted attributes on ActiveRecord models. The important features are: - -- Transparent encryption/decryption of fields -- Custom encryptors, allowing for customizable security -- Automatic query support for encrypted fields - -Let's take a look at how to replicate this in [Ecto][ecto]. - -## Building Our Encryptor - -Before we can encrypt anything, we're going to need to create a module to handle encryption and decryption. Erlang comes with a good [crypto][crypto] module, which will serve as our base. - -For this use case, I've chosen to use AES encryption in CTR mode, but you could just as easily use any other type of encryption supported by [crypto][crypto]. - -```elixir -defmodule MyApp.AES do - # Encrypts each plaintext with a different, random IV. This is much more - # secure than reusing the same IV, and is highly recommended. - def encrypt(plaintext) do - iv = :crypto.strong_rand_bytes(16) # Random IVs for each encryption - state = :crypto.stream_init(:aes_ctr, key, iv) - - {_state, ciphertext} = :crypto.stream_encrypt(state, to_string(plaintext)) - iv <> ciphertext # Prepend IV to ciphertext, for easier decryption - end - - def decrypt(ciphertext) do - # Split the IV that was used off the front of the binary. It's the first - # 16 bytes. - <> = ciphertext - state = :crypto.stream_init(:aes_ctr, key, iv) - - {_state, plaintext} = :crypto.stream_decrypt(state, ciphertext) - plaintext - end - - # Convenience function to get the application's configuration key. - defp key do - Application.get_env(:encryption, MyApp.AES)[:key] - end -end -``` - -The module can then be used pretty simply: - -```elixir -MyApp.AES.encrypt("hello world!") -|> MyApp.AES.decrypt -# => "hello world!" -``` - -You can configure the key to use in the `config.exs` for your app: - -```elixir -config :my_app, MyApp.AES, - key: :base64.decode("..."), # assuming your key is in base64 -``` - -Now that we have an encryptor, we can look at integrating it with Ecto. - -## Ecto.Type - -To implement transparent encryption and decryption of fields, we need to add a layer of code in Ecto's row insertion and loading logic, so that we can encrypt the fields on save, and decrypt them when they are read. Fortunately, Ecto has exactly what we need in [Ecto.Type][ecto_type]. - -[Ecto.Type][ecto_type] lets you define custom field types for Ecto's `schema`, allowing you to modify the value of a field when it is loaded or saved. Here's a custom `EncryptedField` type: - -```elixir -defmodule MyApp.EncryptedField do - import MyApp.AES - - # Assert that this module behaves like an Ecto.Type so that the compiler can - # warn us if we forget to implement the 4 callback functions below. - @behaviour Ecto.Type - - # This defines the base type of this kind of field in the database. - def type, do: :binary - - # This is called on a value in queries if it is not a string. - def cast(value) do - {:ok, to_string(value)} - end - - # This is called when the field value is about to be written to the database - def dump(value) do - ciphertext = value |> to_string |> encrypt - {:ok, ciphertext} - end - - # This is called when the field is loaded from the database - def load(value) do - {:ok, decrypt(value)} - end -end -``` - -We're almost done! Now, since the encryptor we wrote operates directly on binary, the fields we encrypt should be `:binary` fields in the database. Suppose we have an `Ecto.Model` with a binary name attribute like this: - -```elixir -defmodule MyApp.User do - use Ecto.Model - - schema "users" do - field :name, :binary - end -end -``` - -To encrypt the name field, you just need to specify the `MyApp.EncryptedField` type instead of `:binary`: - -```elixir -defmodule MyApp.User do - use Ecto.Model - - schema "users" do - field :name, MyApp.EncryptedField - end -end -``` - -That's it! The field will be transparently encrypted and decrypted as the model struct is saved to or loaded from the database. - -## Querying - -Querying encrypted fields is difficult, because our encryptor is designed -specifically _not_ to produce the same ciphertext twice for security reasons. To understand the difficulty, consider the following example. - -- Suppose you have a user with the email address `test@example.com`. This value is encrypted in a binary field in the database. - -- Now, someone comes along and tries to log in as `test@example.com`, and you want to query the database for a user with that email address. - -- You can't search for `test@example.com`, because that value doesn't exist in the database. Neither can you run the email address through the encryptor, because that will produce a different value than what is in the database. - -So, how can you query for the email address? The answer is to **add another field** to the database called `:email_hash`, which will contain a _hash_ of the `:email` field's contents. This is both secure and convenient: secure because the contents can't be reconstructed from a good hash; convenient, because hash algorithms produce the same result for the same plaintext every time. - -Let's implement another `Ecto.Type`, which will automatically hash the value of a field using the recommended SHA256 algorithm: - -```elixir -defmodule MyApp.HashField do - @behaviour Ecto.Type - - def type, do: :binary - - def cast(value) do - {:ok, to_string(value)} - end - - def dump(value) do - {:ok, hash(value)} - end - - def load(value) do - {:ok, value} - end - - def hash(value) do - :crypto.hash(:sha256, value) - end -end -``` - -Then, we add this field to the schema: - -```elixir -defmodule MyApp.User do - use Ecto.Model - - schema "users" do - field :name, MyApp.EncryptedField - field :email, MyApp.EncryptedField - field :email_hash, MyApp.HashField - end - - # We must ensure that the email_hash field is always a hash of the same value - # held in the email field, or queries will be inaccurate. - before_insert :set_hashed_fields - before_update :set_hashed_fields - - defp set_hashed_fields(changeset) do - changeset - |> put_change(:email_hash, get_field(changeset, :email)) - end -end -``` - -And now, we can easily query on the `:email_hash` field, because Ecto will automatically use our `HashField` module to convert our search term to a hash: - -```elixir -email = "test@example.com" - -MyApp.Repo.get_by(MyApp.User, email_hash: email) -MyApp.Repo.one(from u in MyApp.User, where: u.email_hash == ^email) - -# Both produce this query: -# -# SELECT u0."id", u0."name", u0."email", u0."email_hash", u0."inserted_at", -# u0."updated_at" FROM "users" AS u0 WHERE (u0."email_hash" = $1) [<<151, 61, -# 254, 70, 62, 200, 87, 133, 245, 249, 90, 245, 186, 57, 6, 238, 219, 45, 147, -# 28, 36, 230, 152, 36, 168, 158, 166, 93, 186, 78, 129, 59>>] -``` - -And, we're done! - -## Get the Code - -All this code and more is over on a Phoenix sample project I put up on Github. Here's [the relevant commit][commit]. It includes tests and more examples, including how to validate the uniqueness of an encrypted field. - -## Conclusion - -We implemented the main features of [attr_encrypted][attr_encrypted], a somewhat complicated Ruby gem, in about 60 lines of code, without any monkey patching! This implementation is also substantially more secure than [attr_encrypted][attr_encrypted]'s default settings, which seem to rely on reusing IVs and using AES in CBC mode. - -Even better, this solution is very easy to understand and is very extensible. For example, if you wanted to use a physical Hardware Security Module to do the encryption and decryption, you could just write a custom encryptor and use it instead in your `EncryptedField` module. - -I wasn't sure how easy this would be to implement, and I'm very happy with the result. My confidence in Elixir as a tool continues to rise. - -**READ THIS NEXT: [Changing Your Ecto Encryption Key][change-key]** - -
- -**Credits** - -Credit to [@victorluft](http://twitter.com/victorluft) for suggesting much better crypto, and [@josevalim](http://twitter.com/josevalim) for suggesting that I not make the encryptor a GenServer, since this could be a performance bottleneck. - -**Security Note** - -Adding a hashed version of a field is a slight security risk, because it reveals rows which have the same value in that field. Their hashes will match. Depending on your threat model, this may mean you'll need a different solution for querying on encrypted fields. - -[change-key]: /elixir/security/2015/07/09/changing-your-ecto-encryption-key.html -[original-post]: https://github.com/danielberkompas/danielberkompas.github.io/blob/c6eb249e5019e782e891bfeb591bc75f084fd97c/_posts/2015-07-03-encrypting-data-with-ecto.md -[commit]: https://github.com/danielberkompas/phoenix_ecto_encryption_sample/commit/80c9b75a39f89a203f80617e2c00c062e9904217 -[crypto]: http://www.erlang.org/doc/man/crypto.html -[elixir]: https://github.com/elixir-lang/elixir -[ecto]: https://github.com/elixir-lang/ecto -[ecto_type]: http://hexdocs.pm/ecto/Ecto.Type.html -[attr_encrypted]: https://github.com/attr-encrypted/attr_encrypted diff --git a/_posts/2015-07-09-changing-your-ecto-encryption-key.md b/_posts/2015-07-09-changing-your-ecto-encryption-key.md deleted file mode 100644 index 9adae249a..000000000 --- a/_posts/2015-07-09-changing-your-ecto-encryption-key.md +++ /dev/null @@ -1,224 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Changing Your Ecto Encryption Key" -categories: - - elixir - - security -date: 2015-07-09 07:00:00 -0800 ---- - -**Author's Note: I've released an open-source Hex package that implements the approach to encryption I describe in this post. [Read the announcement post here](/2015/09/22/cloak-your-ecto-data.html)**. - -READ THIS FIRST: [Encrypting Data with Ecto][ecto-encryption] - -In [an earlier post][ecto-encryption], I wrote about how to encrypt data with [Ecto][ecto], [Elixir's][elixir] database library. However, I didn't cover how to change your encryption key, which you'll definitely want to do periodically. I want to show how do that in this post. - - - -## Tag Your Ciphertext - -In order to migrate to a new key, you're going to need to be able to distinguish which key was used to encrypt any given text. This can be done very simply by prepending a single byte to every encrypted binary: - -```elixir -key_id = <<1>> -key_id <> <<5, 138, ...> -``` - -This byte is the ID of the key that was used, and the decryptor can easily use it to find the correct key. - -If you're not looking to migrate to a new key right now, this is really all you need to do. If all your ciphertext includes a key ID, you'll be able to distinguish between ciphertexts, and you'll be able to follow the rest of these steps later when you want to migrate to a new key. - -## Change Config - -We need to change our `config.exs` to support multiple keys: - -```elixir -config :my_app, MyApp.AES, - keys: %{ - <<1>> => :base64.decode("..."), # assuming you store keys in base64 - <<2>> => :base64.decode("...") # otherwise, straight binary will do - }, - default: <<1>> # The ID of the key we want to use -``` - -The first key, `<<1>>`, is the legacy key we're migrating away from. `<<2>>` is the key we are migrating to. We'll leave the `:default` as key `<<1>>` until we are ready to make the switch. - -## Change Encryptor - -Now, we need to upgrade the `encrypt/1` function to prepend the key ID of the key that was used for the encryption: - -```elixir -# Store config in module attributes for slightly faster lookup at runtime, -# and the ability to use them as function parameter defaults -@config Application.get_env(:my_app, MyApp.AES) -@keys @config[:keys] -@default @config[:default] - -# Add another parameter, to allow encrypting with a specific key -def encrypt(ciphertext, key_id \\ @default) do - iv = :crypto.strong_rand_bytes(16) - state = :crypto.stream_init(:aes_ctr, @keys[key_id], iv) # use specified key - {_state, ciphertext} = :crypto.stream_encrypt(state, to_string(plaintext)) - key_id <> iv <> ciphertext # prepend key byte to iv and ciphertext -end -``` - -The key ID will now be included in every ciphertext. We now just have to update the `decrypt/1` function to find this key ID properly: - -```elixir -def decrypt(ciphertext) do - # The first byte is the key ID - <> = ciphertext - state = :crypto.stream_init(:aes_ctr, @keys[key_id], iv) # use specified key - {_state, plaintext} = :crypto.stream_decrypt(state, ciphertext) - plaintext -end -``` - -With that, our encryptor can encrypt and decrypt values generated by any of our keys. By extension, this means that Ecto can read encrypted data from the database, no matter which key was used to generate it. - -It's also a good idea to add a simple function to the encryptor so that other modules can access the current default key ID without having to know where it is stored in `config.exs` - -```elixir -def key_id do - @default -end -``` - -## Change Model(s) - -There isn't actually a lot that we _have_ to change on our model to get things basically working. If we switch to a new key at this point, nothing will break, and records will be gradually upgraded to the new key. This is how that works: - -- Suppose that a given `User` record was encrypted with key `<<1>>`. -- We then switch the `config` so that the `:default` key is now `<<2>>`. -- When our user record is loaded, the decryptor will detect that it is encrypted - with key `<<1>>` and successfully decrypt it using that key. -- When it is saved, it will be encrypted with key `<<2>>`. - -This means that the software can continue to function without any downtime after we switch the keys. **This is a big win**, especially if you're dealing with a lot of encrypted data that takes a long time to migrate. - -#### Gradual Upgrading Not Enough - -In the likely event that you want to re-encrypt your data with the new key more quickly, you'll need to make some changes to your `User` model. -First off, you want to track which rows have been migrated and which have not. Add an indexed binary field called `:encryption_key_id` to the model. - -```elixir -schema "users" do - field :name, MyApp.EncryptedField - field :email, MyApp.EncryptedField - field :email_hash, MyApp.HashField - field :encryption_key_id, :binary -end -``` - -Then, set this field with a callback. I've renamed the `set_hashed_fields` callback to `set_defaults` for this example: - -```elixir -before_insert :set_defaults -before_update :set_defaults - -defp set_defaults(changeset) do - changeset - |> put_change(:encryption_key_id, MyApp.AES.key_id) - |> put_change(:email_hash, get_field(changeset, :email)) -end -``` - -The `:encryption_key_id` field will now contain the key ID which was used to encrypt the row. This will help us find rows which haven't been upgraded yet. - -## Mix Task - -We've come to the final step, creating a mix task to proactively migrate up all of our data to the new key. Here's what it could look like: - -```elixir -# lib/mix/tasks/encryption.migrate.ex -defmodule Mix.Tasks.Encryption.Migrate do - use Mix.Task - - import Ecto.Query - import Logger, only: [info: 1] - - alias MyApp.Repo - - # Store the current key ID in a module attribute. This is the key that we are - # going to migrate to, controlled by `:default` in `config.exs`. - # - # I'm doing it this way rather than calling the function directly so we can - # use it in pattern matching below. - @key_id MyApp.AES.key_id - - def run(args) do - # Ensure that MyApp.Repo is started and available for use - Mix.Task.run "app.start", args - - # Migrate our User model - # You could migrate any other models that use encryption here. - migrate MyApp.User - end - - defp migrate(model) do - info "=== Migrating #{model} Model ===" - ids = ids_for(model) - info "#{length(ids)} records found needing migration" - - # Migrate all the records found - for id <- ids do - # Records are fetched individually to ensure that they haven't changed - # between being loaded and saved. We don't want to overwrite any changes - # that occur while this task is running. - Repo.get(model, id) |> migrate_record - end - - info "=== Migration Complete ===" - end - - # Returns all the IDs of records which, according to :encryption_key_id, have - # not yet migrated to @key_id. - defp ids_for(model) do - query = from m in model, where: m.encryption_key_id != ^@key_id, - select: m.id - Repo.all(query) - end - - # Do nothing if the record has been automagically migrated by app usage since - # we queried for IDs at the start of this task. - defp migrate_record(%{encryption_key_id: @key_id}), do: nil - - # If the record doesn't match the above definition, and therefore has not been - # migrated, then simply save it back to the database. This will trigger - # MyApp.AES.encrypt, which will save the data encrypted with the new key. - defp migrate_record(record), do: Repo.update!(record) -end -``` - -## The Great Migration - -We're ready to migrate! We can just change the `:default` in `config.exs` from `<<1>>` to `<<2>>`, and then run our mix task: - -``` -$ mix encryption.migrate -[info] === Migrating Elixir.MyApp.User Model === -[info] 10 records found needing migration -[info] === Migration Complete === -``` - -## Conclusion - -This approach has many things to recommend it: - -- It's simple, and therefore maintainable. -- There's zero downtime while switching encryption keys. -- A minimum of extra fields are needed. (1 per table) -- You can verify that all the data has been migrated with a simple query on `:encryption_key_id`. -- You can switch to a new key in three steps: - - Add a new key to the `:keys` map. - - Change the `:default` to the new key ID. - - Run `mix encryption.migrate`. - -If you want to see all the code, I've [posted it on Github][commit]. Hope this helps someone out there! - -[commit]: https://github.com/danielberkompas/phoenix_ecto_encryption_sample/commit/629f4f4de6987a0d9106cbe7280201595cbb79a5 -[ecto]: https://github.com/elixir-lang/ecto -[elixir]: https://github.com/elixir-lang/elixir -[ecto-encryption]: /elixir/security/2015/07/03/encrypting-data-with-ecto.html diff --git a/_posts/2015-07-16-fixtures-for-ecto.md b/_posts/2015-07-16-fixtures-for-ecto.md deleted file mode 100644 index ad1c889b1..000000000 --- a/_posts/2015-07-16-fixtures-for-ecto.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Fixtures for Ecto" -categories: - - elixir -date: 2015-07-16 07:00:00 -0800 ---- - -When you test an [Elixir][elixir] app that uses [Ecto][ecto], you will find yourself needing a way to insert test data into the database. There are many different approaches to doing this, and I thought I'd cover a few, and then describe what I think the best approach is for Elixir. - - - -## Global Fixtures - -By default, Rails solves this problem by automatically inserting a set of rows into the database before every test case is executed. These rows are configured by YAML files in the `test/fixtures` directory. - -While you could probably hook into `mix test` to insert your data before every test, I don't think the Rails approach should be imitated here, for the following reasons. - -**First, global fixtures are _implicit_ behavior.** There's nothing in your test file that tells you where the data is coming from. It's all happening behind the scenes, and you have to know to go look in the `fixtures/` directory to figure out what's going on. - -In contrast, Elixir emphasizes _explicit_ behavior. It should be obvious where the fixtures are coming from, or at least there should be a breadcrumb trail _in my test file_ that I can follow to figure that out. - -**Second, global fixtures can be brittle.** It assumes that your app can be adequately tested with one or only a few versions of data. This is often a bad assumption. - -**Third, global fixtures are unecessary performance overhead.** Inserting the data that you need for _all_ your tests before _every_ test is unecessary. It may not slow down your tests by much, but why slow them down at all? - -**Fourth, YAML is a bad DSL.** Why write data for your ActiveRecord models in YAML? Why not just explicitly call `Model.create`? Rails itself acknowledges that YAML is a bad DSL by making the `db/seeds.rb` file a plain Ruby file where you use `Model.create` to create data. (The dichotomy between the fixtures YAML and the seeds.rb file confuses every Rails newbie I've taught!) - -How exactly is this: - -```yaml -daniel: - name: "Daniel Berkompas" - email: "test@example.com" -``` - -Better than this? - -```ruby -User.create name: "Daniel Berkompas", email: "test@example.com" -``` - -Of course, the reason Rails does this is because inserting a row through ActiveRecord is _slow_, and since all the data has to be inserted before _every_ test, fixtures would slow down your test suite a lot if it went through ActiveRecord. - -## Factory Girl - -Many Rails developers use [FactoryGirl][factory_girl] instead of Rails' built in fixtures. Using FactoryGirl, you define your fixtures using a DSL in Ruby files under `factories/model_name.rb`. - -```ruby -FactoryGirl.define do - factory :user do - name "Daniel Berkompas" - email "test@example.com" - end -end -``` - -Then, to create the data: - -```ruby -user = FactoryGirl.create(:user) -``` - -This is better than Rails' default in several respects. The fixtures are not loaded globally, and the code is more explicit. A user knows that they need to look for "factory" related stuff to figure out what values `:user` has. - -However, it still has two faults which annoy me in the projects where I use it. First, since it runs through ActiveRecord, it can be very slow to insert data. Second, the DSL seems unnecessary. How is the above better than this? - -```ruby -User.create name: "Daniel Berkompas", email: "test@example.com" -``` - -I would prefer to see the code be still more explicit, fast, and without any DSLs, so that new developers don't have to learn a new DSL to write tests. - -## Fixtures for Elixir - -I suggest that you add a simple `MyApp.Fixtures` module to your `test/support` directory in your mix project. It could look something like this: - -```elixir -defmodule MyApp.Fixtures do - alias MyApp.User - - def fixture(:user) do - %User{name: "Daniel Berkompas", email: "test@example.com"} - end -end -``` - -If you wanted, the `fixture/1` function could also automatically insert the `User` record by calling `Repo.insert!/1`: - -```elixir -defmodule MyApp.Fixtures do - alias MyApp.Repo - alias MyApp.User - - def fixture(:user) do - Repo.insert! %User{name: "Daniel Berkompas", email: "test@example.com"} - end -end -``` - -It's trivial to add support for other models, and even associations: - -```elixir -defmodule MyApp.Fixtures do - alias MyApp.Repo - alias MyApp.User - alias MyApp.Post - - def fixture(:user) do - Repo.insert! %User{name: "Daniel Berkompas", email: "test@example.com"} - end - - def fixture(:post, assoc \\ []) do - user = assoc[:user] || fixture(:user) - Repo.insert! %Post{ - title: "Fixtures for Ecto" - user_id: user.id - } - end -end -``` - -You can then use this `Fixtures` module in your test like so: - -```elixir -defmodule MyApp.MyTest do - use ExUnit.Case - import MyApp.Fixtures - - test "some behavior" do - user = fixture(:user) - post = fixture(:post, user: user) - - # test logic here - end -end -``` - -## Benefits - -I think this approach provides several benefits: - -- **Simplicity**. There are no DSLs to learn. This means that new developers will be able to get up to speed with it very fast. - -- **Explicitness**. There's no mystery here. I can look at the test file and know exactly how the data is getting created. - -- **Speed**. Each test inserts only the data it needs. `Repo.insert!/1` is also much more performant than `ActiveRecord::Base#create`, since it's not worrying about validations. Ecto model structs are pretty lightweight compared to ActiveRecord objects, so that helps too. - -- **Customization**. Since we're only dealing with modules and functions here, you can compose your fixtures however you like. Nothing's off limits because the DSL doesn't support it, because there is no DSL! - -On the whole, I think this simpler approach is much better than either Rails fixtures or FactoryGirl, and you don't need to add a dependency to get it! - -[ecto]: https://github.com/elixir-lang/ecto -[elixir]: http://elixir-lang.org -[factory_girl]: https://github.com/thoughtbot/factory_girl diff --git a/_posts/2015-07-28-long-running-branches.md b/_posts/2015-07-28-long-running-branches.md deleted file mode 100644 index 3f401fbaa..000000000 --- a/_posts/2015-07-28-long-running-branches.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Avoid Long-Lived Feature Branches" -categories: - - scm -date: 2015-07-28 07:00:00 -0800 ---- - -Today, I intend to rail against the evils of long-lived feature branches. Having collaborated on a number of projects where they happened, I'm now convinced that they are almost always the wrong way to go. - - - -## Why We Do Long-Lived Branches - -The reasoning behind these branches is simple. Your boss wants the program to keep running like it is, but he wants a large, complex feature to be implemented that will touch a lot of the codebase. - -Many teams respond to this by creating a feature branch in source control, in which the feature will be developed until such a time that it is "ready". Normal development continues in the main branches while the feature is being developed. - -## What's Wrong With Them - -There are many problems with this approach, but I think they can be summarized as two large problems. - -First, **code review becomes messy and ineffective.** When the feature branch is finally merged into the `master` branch, the diff is too large to review effectively. I've been handed diffs like this to review before, and my eyes glaze over after about 300 lines of code. The sheer volume of code to review reduces the quality of my review, because I don't want to sit and review code all day, so I rush through it. Rushed code reviews are worse than no code review at all, because they give the illusion that code has been reviewed when in fact it has not. - -Second, **merging becomes hard.** The longer a branch lives, the more different the main branch becomes, and the more difficult it can be to keep the two in sync. You can mitigate this by doing frequent merges and rebases in from `master`, but many developers neglect this. - -In the end, you have a giant blob of code that isn't very well tested or reviewed, and which probably conflicts with your `master` branch in a number of significant ways. In the projects I worked on, this either resulted in serious bugs or significant delays getting the feature done. - -## A Better Way - -Like many others have written, I recommend that you develop each feature as incrementally as possible, merging as often as possible into your `master` branch. Ensure the tests are all passing after each incremental change. Put the feature behind a feature flipper if possible, or, if the feature changes too many things, start a new "main" branch where all development will occur and use `master` only for hotfixes until the feature is done. - -This approach ensures that pull requests are small, and therefore makes code review a meaningful process. Also, there's no fear that `master` will get very far ahead in the two or three hours that each little feature branch lives. - -So, please, no more massive pull requests! Keep your branches small, your -commits focused, and merge often. diff --git a/_posts/2015-08-03-elixir-as-an-operating-system.md b/_posts/2015-08-03-elixir-as-an-operating-system.md deleted file mode 100644 index db451c126..000000000 --- a/_posts/2015-08-03-elixir-as-an-operating-system.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Elixir as an Operating System" -categories: - - elixir -date: 2015-08-03 07:00:00 -0800 ---- - -I recently was using a [Synology Diskstation][synology], and I was very impressed by their web admin interface. They have successfully emulated a desktop operating system, complete with downloadable programs, file browsing, and more. You can manage the whole system from the browser in a way that feels very much like Windows 7. - - - -Even more impressively, Diskstations can update themselves and their apps with one click, and sometimes, with no clicks at all. It's a remarkable achievement, and it makes their products great. - -However, I suspect all is not well. Perhaps its just cynicism on my part, but I think the code underlying all of this is probably a mess. I don't know if it's PHP or not, but given the vast number of features that Synology offers and the fact that most mass-market software is a mess underneath the surface, I doubt that Synology is an exception. In other words, I doubt that I would enjoy _writing_ an app for a Diskstation as much as I might enjoy _using_ one. - -Wordpress is somewhat similar to Synology, in that it provides a core package which can then be extended by various plugins. It now also has automatic updates, and maybe even automatic plugin updates. (I haven't checked for a while) But, as every developer who has ventured into the dark heart of Wordpress knows, it isn't very well built at bottom. That's putting it _very_ mildly. - -Developers hate working with it, and it costs the poor saps who use it boatloads of money whenever they want to do something remotely custom. Wordpress sites are buggy and prone to security issues, and are very difficult to administer due to its convoluted UI, made worse by still more convoluted plugin UIs. Yet, Wordpress is still the most extensible and user-friendly website creation tool. - -My experience with Diskstation got me thinking, could Elixir be used to build a Wordpress replacement? Something that mere mortals could use and customize, but which wasn't terrible underneath the surface? Something that wouldn't cause developers everywhere to cringe when its name is mentioned? I think such a thing might be possible. - -## Elixir's Advantages - -Elixir has several advantages over PHP for such a project: - -- It's a better language. It is more explicit, more consistent, and has better developer tools. Documentation (essential for plugins!) is easy to write and HTML docs can be generated easily. - -- Plugins == OTP Apps. Elixir has the concept of "apps" (persistent processes that are supervised and automatically restarted) built right in. Plugins could easily be written as OTP apps. Elixir already "thinks" like an operating system. - -- Hot code swapping. Elixir supports upgrading a process to new code while the process stays running. This makes the challenge of one-click installs and upgrades a little easier. - -- High availability. Elixir can maintain nearly zero-downtime through supervision trees, and plugins can maintain their own supervision trees. - -- High performance. Budget conscious website creators could squeeze a lot of performance out of low end servers. - -## Elixir's Disadvantages - -Elixir also has a couple disadvantages: - -- Installation could be more complicated. Erlang and Elixir are not as common as PHP, and would have to be present on the server for the system to run. Many web hosting companies offer a "Wordpress" button to install Wordpress on their servers, while nothing similar would exist for Elixir. - -- Elixir is not a mainstream language. This would mean that for a while at least, very few plugins would be available. - -## Worth a Shot? - -I'm intrigued by the possibility of building a Wordpress replacement in Elixir. It really does seem like Elixir could be an ideal fit for this kind of thing. However, rebuilding Wordpress in any language would certainly be no easy task, even with the helps that Elixir provides. If I get the chance, I may hack on it for a few evenings and see what happens, and I'll definitely post about it. - -If anyone else out there is inspired by this post and decides to steal the idea, go ahead! Just let me know where you're hosting the project so I can contribute. - -**UPDATED**: When I tweeted this article, a fellow twitterer pointed me to [Zotonic][zotonic], an Erlang CMS that seems to be doing exactly what I'm talking about. Since Elixir is built on Erlang, it should be possible to write Elixir plugins for it. - -[zotonic]: http://zotonic.org -[synology]: https://www.synology.com/ diff --git a/_posts/2015-08-21-genservers-as-concurrent-objects.md b/_posts/2015-08-21-genservers-as-concurrent-objects.md deleted file mode 100644 index 1a55961dc..000000000 --- a/_posts/2015-08-21-genservers-as-concurrent-objects.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "GenServers as Concurrent Objects" -categories: - - elixir -date: 2015-08-21 07:00:00 -0800 ---- - -This is a post for fellow object-oriented developers trying to get their heads around how Elixir/Erlang use processes as a basic abstraction, rather than classes and objects. - - - -## A Sample Object - -In an object oriented language, such as Ruby, we might implement a `BankAccount` class like this: - -```ruby -class BankAccount - attr_reader :balance - - def initialize(starting_balance) - @balance = starting_balance - end - - def deposit(amount) - @balance += amount - end - - def withdraw(amount) - @balance -= amount - end -end -``` - -This class could then be used like this: - -```ruby -account = BankAccount.new(0.0) -account.deposit(50.0) -account.withdraw(25.0) -account.balance # => 25.0 -``` - -## A GenServer Equivalent - -In Elixir, we can implement a `GenServer` that behaves very similarly: - -```elixir -# Create a new GenServer process with an initial state (balance) of 0, using the -# BankAccount module to process messages to the process. Returns a process ID. -{:ok, account} = GenServer.start(BankAccount, [0.0]) - -# Send messages to the process to deposit or withdraw amounts. -# cast/2 runs asynchronously and doesn't wait for a response. -GenServer.cast(account, {:deposit, 50.0}) -GenServer.cast(account, {:withdraw, 25.0}) - -# Query the process for the balance, and waits for a response. -GenServer.call(account, :balance) # => 25.0 -``` - -`GenServer` here will fire callbacks on the `BankAccount` module in response to the messages sent to the process. - -```elixir -defmodule BankAccount do - use GenServer - - # Casts don't reply to the caller, but simply modify the state - def handle_cast({:deposit, amount}, balance) do - {:noreply, balance + amount} - end - - def handle_cast({:withdraw, amount}, balance) do - {:noreply, balance - amount} - end - - # Calls return a value, as well as the modified state - def handle_call(:balance, balance) do - {:reply, balance, balance} - end -end -``` - -We can hide all the GenServer implementation behind a public API on the `BankAccount` module. (Not shown) Our code will then look very similar to Ruby's: - -```elixir -{:ok, account} = BankAccount.start(0.0) -BankAccount.deposit(account, 50.0) -BankAccount.withdraw(account, 25.0) -BankAccount.balance(account) # => 25.0 -``` - -## GenServer "Singletons" - -It is possible to refer to `GenServer` processes by a name rather than by a process ID. In this case, a `GenServer` behaves more like a singleton rather than an object instance, because there is only one process running. - -This naming is accomplished by passing the `:name` option to `GenServer.start`: - -```elixir -GenServer.start(BankAccount, [0.0], name: BankAccount) -``` - -You can then send messages to the `BankAccount` process using its name: - -```elixir -GenServer.cast(BankAccount, {:deposit, 50.0}) -``` - -If we reimplemented our `BankAccount` module as a named GenServer, (not shown) we could use it like this: - -```elixir -# In our application initialization -BankAccount.start([0.0]) - -# Then, anywhere in our code: -BankAccount.deposit(50.0) -BankAccount.withdraw(25.0) -BankAccount.balance # => 25.0 -``` - -This `BankAccount` process can then be called from other nodes (think, microservices) in your cluster like so: - -```elixir -GenServer.call({BankAccount, :accounts@localhost}, :balance) # => 25.0 -``` - -No JSON APIs required! - -## Contrasts - -We see then that `GenServer` modules can behave very much like objects because they are a construct that both holds state and can perform operations on that state. However, they are different from objects in Ruby in a few important ways: - -- A `GenServer` can hold only one value as its state. In Ruby, you can have as many instance variables as you like. However, this isn't a big limitation, since the state you store in a `GenServer` can be as complex as you want. - -- `GenServer` operations are automatically spread across your CPU cores. If the operation doesn't require a response, then the caller won't be blocked. However, it's important to keep in mind that a `GenServer` process can only do one thing at a time. - -- Named `GenServer` processes can be called from other computers in the cluster, as shown above. - -- `GenServer` processes shouldn't really be used as often as objects or in the same ways. They are designed for concurrency, not managing data structures like objects are. I compare them to objects just to relate them to something familiar. - -## Conclusion - -I know that it was a real "lightbulb" moment for me when I realized that GenServers are Elixir/Erlang's answer to objects. Hopefully you've found this article helpful! If something isn't clear, ask me a question [on Twitter](http://twitter.com/dberkom) and I'll see if I can clarify the post. diff --git a/_posts/2015-08-28-how-to-run-elixir-cloud9-ide.md b/_posts/2015-08-28-how-to-run-elixir-cloud9-ide.md deleted file mode 100644 index 6269f227b..000000000 --- a/_posts/2015-08-28-how-to-run-elixir-cloud9-ide.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: "post" -title: "How to Run Elixir in Cloud9's IDE" -categories: - - elixir ---- - -[Cloud9](http://c9.io) is a great web-based development platform. If you don't have access to a dedicated machine you can set up for development, or if you just prefer to keep all your coding in neat, tiny VMs, Cloud9 could be just what you're looking for. It's particularly good for students learning to code. - -Cloud9 doesn't provide an Elixir-specific workspace template, so you have to configure one yourself. Here's how to do that: - -1. Create a Cloud9 account. If you already have a Github account, just log in with that. -2. Click "Create a new workspace". -3. Give it a name, say, "Elixir". -4. Select the "Custom" template. -5. When the workspace loads, run the following commands in the console: - -```bash -# For some reason, installing Elixir tries to remove this file -# and if it doesn't exist, Elixir won't install. So, we create it. -sudo touch /etc/init.d/couchdb - -# Standard Ubuntu Elixir installation instructions -wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb -sudo dpkg -i erlang-solutions_1.0_all.deb -sudo apt-get update -sudo apt-get install elixir -``` - -With that, you're ready to code! `iex`, `elixir`, and `mix` should all be available, and Cloud9 has syntax coloring for Elixir code as well. Happy coding! diff --git a/_posts/2015-09-03-better-pipelines-with-monadex.md b/_posts/2015-09-03-better-pipelines-with-monadex.md deleted file mode 100644 index 8c1cf9b2c..000000000 --- a/_posts/2015-09-03-better-pipelines-with-monadex.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -layout: "post" -title: "Better Pipelines with Monadex" -author: "Daniel Berkompas" ---- - -Before I get started, let me be blunt, **this is not another monad tutorial**. The world has enough of those already. I'm not going to write about functors and applicatives or other technical theory. This is just a post about how a particular monad made my life better. - -## The Problem: Network Requests - -I'm building a [Phoenix][phoenix] app where people can buy something. So, I needed to integrate with my favorite gateway, [Stripe][stripe], which involves making a series of network requests each time a user makes a purchase. - -Specifically, I needed to do these things for each purchase: - -1. Ensure the user hasn't already purchased the thing. -2. **[Network]** Create a Stripe Customer for the user, if it doesn't exist. -3. **[Network]** Create a Stripe Charge for the purchase amount. -4. Update the database to reflect the fact that the purchase has been made. - -If any one of steps 1-3 fail, I don't want to perform the remaining steps. Instead, the process should fail immediately and return the error. - -## The Pipeline Operator Isn't Enough - -I started out with a naive solution, using Elixir's normally adequate pipe operator to tie the steps together: - -```elixir -result = user - |> assert_not_purchased_yet - |> create_stripe_customer(stripe_token) # From Stripe.js - |> create_stripe_charge - |> update_database -``` - -The obvious problem with this is that regardless of the result of each function, the next function will be called. For example, `create_stripe_charge/1` will still run even if `create_stripe_customer/2` failed. - -I can work around this by making each function return either `{:ok, state}` or `{:error, reason}`. If a function gets `{:error, reason}`, it should do nothing, which would have the same result as if it wasn't called at all. - -Here's how that looks: - -```elixir -def create_stripe_customer({:ok, state}, stripe_token) do - # Create Stripe customer, return {:ok, state} or {:error, reason} -end -def create_stripe_customer({:error, _} = error, _stripe_token) do - error # just return the error -end - -def create_stripe_charge({:ok, state}) do - # Create stripe charge, return {:ok, state} or {:error, reason} -end -def create_stripe_charge({:error, _} = error) do - error -end -``` - -This way, if `create_stripe_customer/2` returns an error, then `create_stripe_charge/1` will do nothing. We can repeat this pattern all the way down the chain, and pipeline will then look something like this: - -```elixir -result = user - |> assert_not_purchased_yet - |> create_stripe_customer(stripe_token) # From Stripe.js - |> create_stripe_charge - |> update_database - -case result do - {:ok, state} -> # Display success to user - {:error, reason} -> # Display error reason to user -end -``` - -This works, but it isn't very elegant. I have to add a new function definition for each function in the pipeline to handle the error case. - -## Monads to the Rescue! - -I knew enough about monads at this point to vaguely understand that they are a bit like pipelines. Maybe there was a kind of monad that could make this better? - -It turns out that there is. The excellent [Monadex][monadex] library for Elixir provides just what I needed, the `Monad.Result` monad. Rather than talk theory, let's just look at how we use this thing: - -```elixir -defmodule MyApp.Purchase do - use Monad.Operators # Brings in the ~>> bind operator - - # Import functions from the Monad.Result module. - # These will be used to wrap the state that we pass through - # all of our functions. - import Monad.Result, only: [success?: 1, - unwrap!: 1, - success: 1, - error: 1] - - def create(user, stripe_token) do - result = success(user) # Wrap user with the "success" monad state - ~>> fn user -> assert_not_purchased_yet(user) end - ~>> fn user -> create_stripe_customer(user, stripe_token) end - ~>> fn user -> create_stripe_charge(user) end - ~>> fn user -> update_database(user) end - - if success?(result) # %Monad.Result{type: :success, value: user} - value = unwrap!(result) # Same as result.value - # Display success to user - else - # Display error to user - end - end - - # ... -end -``` - -First, we `use` the `Monad.Operators` module. This brings in the `~>>` operator which we'll get to in a minute. Next, we import most of the functions from `Monad.Result`. - -Underneath the hood, a `Monad.Result` is just a struct that wraps state, not all that different from a `{:ok, state}` or `{:error, reason}` tuple. - -```elixir -%Monad.Result{type: :error | :success, value: state} -``` - -So, the first thing we do is wrap our state, the `user` variable, with a -`Monad.Result` struct. Since this is the first part of our pipeline, we'll start with a `:success` struct. - -```elixir -success(user) # => %Monad.Result{type: :success, value: user} -``` - -The `~>>` operator, when used with the `Monad.Result` monad, will ensure that the next function in the pipeline only runs if the previous one returned a `:success` result. Otherwise, it terminates immediately and returns the last `Monad.Result` that was returned. - -If we use a `~>>` pipeline instead of the regular `|>` pipeline, our functions can then look like this: - -```elixir -def assert_not_purchased_yet(user) do - case purchased?(user) do - true -> error("Already purchased.") - false -> success(user) # Return whatever state the next function needs - end -end - -def create_stripe_customer(%{stripe_customer_id: id} = user) when id != nil do - success(user) # The user already has a stripe customer id, so do nothing -end -def create_stripe_customer(user, stripe_token) do - case Stripe.Customer.create(user) do # pseudocode - {:ok, _customer} -> success(user) - {:error, reason} -> error(reason) - end -end - -def create_stripe_charge(user) do - case Stripe.Charge.create(...) do # pseudocode - {:ok, _charge} -> success(user) - {:error, reason} -> error(reason) - end -end - -# etc ... -``` - -Much cleaner! Now, we'll only make the Stripe network calls if the previous step was successful. There's no nested tree of `if` statements, just a simple pipeline. And we can operate on the result of all these operations very simply: - -```elixir -if success?(result) do - # Get the value out of the monad - value = unwrap!(result) - # render success -else - # render error -end -``` - -## Errors Prevented - -This implementation elegantly handles each of the following situations: - -- The user has already purchased the product. -- The user is already associated with a Stripe customer. -- The Stripe customer could not be created. -- The Stripe customer could be created but the charge could not. - -Further, it only does as much work as necessary to determine the result. It fails fast, allowing the user to get feedback as soon as possible. - -## Conclusion - -This is the first time I understood how a monad would help me, and I hope it was useful to you too! Whenever you find yourself wishing for a better type of pipeline, give [Monadex][monadex] a try. - -[phoenix]: http://phoenixframework.org -[stripe]: http://stripe.com -[monadex]: https://github.com/rob-brown/MonadEx diff --git a/_posts/2015-09-22-cloak-your-ecto-data.md b/_posts/2015-09-22-cloak-your-ecto-data.md deleted file mode 100644 index 491757311..000000000 --- a/_posts/2015-09-22-cloak-your-ecto-data.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Cloak Your Ecto Data" ---- - -I've written previously about how to encrypt your data when you are using [Ecto][ecto], here: - -[Encrypting Data With Ecto](http://blog.danielberkompas.com/elixir/security/2015/07/03/encrypting-data-with-ecto.html) -[Changing Your Ecto Encryption Key](http://blog.danielberkompas.com/elixir/security/2015/07/09/changing-your-ecto-encryption-key.html) - -After deciding to use encryption for one of my personal projects, I decided that the techniques I wrote about should be built into a [Hex][hex] package. With that in mind... - -## Presenting Cloak - -I'm pleased to present [Cloak][cloak], a simple encryption package designed to be used with Ecto. It implements all the ideas I've written about, and then some. It supports: - -- Seamless encryption/decryption of model fields -- Zero-downtime migrations to new keys _and cipher suites_ -- Multiple ciphers in use at once (though only one will be used for encryption) - -### Configuration - -It's simple to use. Select a cipher (there's only one currently, AES CTR), and then configure it in your `config/config.exs`: - -```elixir -config :cloak, Cloak.AES.CTR, - tag: "AES", - default: true, - keys: [ - %{tag: <<1>>, key: :base64.decode("your key here"), default: true} - ] -``` - -The `:tag` for the given cipher module will be prepended to any ciphertext generated by that module. `:default` controls whether this cipher should be the default for encrypting new values, and the `:keys` array holds a list of keys to use. - -Each key also has a `:tag`, which will become part of each ciphertext, and a `:default` setting to control whether it should be used for new encryption. - -You can then use the `Cloak.encrypt/1` and `Cloak.decrypt/1` functions directly to encrypt and decrypt values using the default cipher module: - -```elixir -# Delegates encryption work to Cloak.AES.CTR.encrypt/1 -Cloak.encrypt("Hello, World!") -# => <<65, 69, 83, 1, 235, 128, 59, ...>> - -Cloak.encrypt("Hello, World!") -|> Cloak.decrypt -# => "Hello, World!" -``` - -`Cloak` is able to distinguish between ciphertext based on the `:tag`s. For example, when using the `Cloak.AES.CTR` cipher module, `Cloak` will generate ciphertext in this format: - -| Cipher Tag
(n bytes) | Key Tag
(1 byte) | IV
(16 bytes) | Ciphertext
(n bytes) | -| ------- | ------- | ------ | ------ | -| "AES" | 1 | <<235, 128, 59, 167, 97, ...>> | <<89, 228, 1, 111, 197, 201, ...>> | - -When you call `Cloak.decrypt/1`, `Cloak` will use the cipher tag to determine which module created the ciphertext, and will then pass _the rest_ of the binary to that module's `decrypt/1` function for decryption. - -This allows `Cloak` to continue to decrypt old values seamlessly, even if you've set up a new `:default` encryption module or key. - -## Ecto Integration - -It's easy to integrate `Cloak` with Ecto. Add the field you intend to encrypt to your database; it should be a `:binary` field. - -```elixir -defmodule User do - use Ecto.Model - - schema "users" do - field :name, :binary - end -end -``` - -Then, replace the `:binary` type with `Cloak.EncryptedBinaryField`: - -```elixir -defmodule User do - use Ecto.Model - - schema "users" do - field :name, Cloak.EncryptedBinaryField - end -end -``` - -And that's it! The `:name` field will be transparently encrypted and decrypted with the cipher module you configured. - -Cloak supports string fields, map fields, integer fields, float fields, and SHA256 hash fields as of this writing. I'm definitely open to contributions! - -## Migrating to a New Key or Cipher - -Because Cloak tags every piece of ciphertext with metadata, you don't _have_ to do anything when you switch to a new key. Old data will be gradually converted to the new key or cipher as your app is used. - -At the very least, this means that your app can stay up during the transition. However, you usually will want to proactively migrate rows to the new key. To do so, you will need to track which encryption configuration was used to encrypt each row, so that you can know which rows need to be migrated. - -Add an `:encryption_version` field to your module, with a `:binary` type, and `use` the `Cloak.Model` module. - -```diff -defmodule User do - use Ecto.Model -+ use Cloak.Model, :encryption_version - - schema "users" do - field :name, Cloak.EncryptedBinaryField -+ field :encryption_version, :binary - end -end -``` - -This will set up `before_insert` and `before_update` hooks on your model to save metadata about the encryption that was used to encrypt that module in the `:encryption_version` field. - -Next, configure your `:migration` settings in `config/config.exs`: - -```elixir -config :cloak, :migration, - repo: Repo, - models: [User] # A list of modules that you want to migrate -``` - -Then, simply run the following mix task, and all the rows in your database will be proactively migrated to the new `:default` encryption configuration! - -```bash -$ mix cloak.migrate -``` - -## Conclusion - -I hope this package will be useful to others out there. If you have any -suggestions or ideas, please let me know over [on Github][cloak]. - -
- -#### More Documentation? - -See the [Hex documentation for Cloak](http://hexdocs.pm/cloak) for more in-depth information. - -#### Contributing - -I realize that only supporting AES CTR encryption is somewhat limiting. If you'd like to contribute more encryptors, I'm accepting PRs at -[Cloak's Github repo][cloak]. - -[hex]: http://hex.pm -[cloak]: https://github.com/danielberkompas/cloak -[ecto]: https://github.com/elixir-lang/ecto diff --git a/_posts/2015-09-25-announcing-learn-elixir-tv.md b/_posts/2015-09-25-announcing-learn-elixir-tv.md deleted file mode 100644 index 608f14654..000000000 --- a/_posts/2015-09-25-announcing-learn-elixir-tv.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Announcing LearnElixir.tv" ---- - -Over the past year and a half or so, I have been teaching a programming class. We started out learning the command line and Git, and then moved on to Ruby. In the process, I quickly found out that Ruby isn't quite as easy to understand as I thought it was. - - - -This year, I pressed the reset button, and am teaching the class Elixir instead of Ruby, in the hope that functional programming will make more sense to beginners than object-oriented programming does. - -Toward that end, I started working on a screencast which I'm now happy to announce to the public: [LearnElixir.tv][learn_elixir]. Please check it out! - -[![LearnElixir.tv Screenshot](https://dl.dropboxusercontent.com/u/2736799/blog/learn-elixir-screenshot.png)][learn_elixir] - -## Frequently Asked Questions - -### How is this different from [ElixirSips][elixirsips]? - -I'm a happy subscriber to Josh Adams' [ElixirSips][elixirsips] screencast. If you're not already a subscriber, go sign up for it now! It's great. - -However, I felt like there was room for a screencast oriented more toward -complete beginners who want to learn Elixir's features from start to finish, like they would read a book. That's the need [LearnElixir.tv][learn_elixir] tries to fill. - -### What topics does [LearnElixir.tv][learn_elixir] cover? - -It is still a work in progress, but it will eventually cover Elixir's main features and the most important parts of its standard library. When it is finished, a beginner should be able to get up and running from the screencast alone. - -[Read the topic list](http://www.learnelixir.tv/upcoming) - -### How many episodes will there be? - -At least 20, but beyond that, as many as it takes to cover the subject matter to my satisfaction. - -### What does it cost? - -Right now, **$9.** One time. It's a steal. This price covers the episodes on Elixir's built-in features and standard library. I'll raise it soon, so get it while it's this cheap! - -Future episodes on special topics, like Phoenix or Ecto, may require payment per episode or require a subscription. - -### How to submit suggestions/corrections - -Email me at `admin at learnelixir dot tv`. Or, if you've bought the series, just comment on the relevant video using Disqus. - -[learn_elixir]: http://www.learnelixir.tv -[elixirsips]: http://elixirsips.com diff --git a/_posts/2015-10-15-why-im-checking-out-graphql.md b/_posts/2015-10-15-why-im-checking-out-graphql.md deleted file mode 100644 index 2d12c8c03..000000000 --- a/_posts/2015-10-15-why-im-checking-out-graphql.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -layout: "post" -title: "Why I'm Checking Out GraphQL" -author: "Daniel Berkompas" ---- - -Like many other Elixir programmers, I had heard rumblings about [GraphQL][graphql-spec], but only recently decided to take a deeper look at it after Chris McCord mentioned it in his [recent talk at ElixirConf 2015][phoenix-next]. - -I think it shows great promise and I want to help out with implementing an Elixir version, and I'll explain why. But first, let me outline some of the problems with the way we build REST APIs today. - -## The Mess We're In - -It's increasingly common to serve a Javascript front-end app from a REST API these days. The merits of Javascript apps vs. server-rendered apps have been debated ad nauseum and I'm not going to repeat that debate here. - -Suffice to say, I believe there _is_ something qualitatively different _and better_ about the kind of user interfaces that client-side Javascript can create, which servers cannot match. This means that I believe it is desirable in many cases to have a client-rendered interface, which then raises the question of how we ship data between the client and the server. - -REST has been a decent solution, but when compared to GraphQL, it suffers from a number of severe problems. - -### REST is a Large, Complicated Middleman - -An API should serve three purposes: - -1. Normalize your data into a predictable schema -2. Authorization -3. Apply business logic to user input - -Everything beyond this is just bloat, and REST API servers tend to get clogged up with a lot of other concerns. - -I think we should ask ourselves this question more often: "Do we even need a server here? Could we implement this app by just talking straight to Postgres?" Most of the time the answer would be no, but the mental exercise would help keep the API server you do write as tiny and simple as possible. It really is a database wrapper, after all. - -### REST APIs are Rigid, Not Responsive - -Implementing a REST API is a complicated and rigid process, because you need to specify all the URL endpoints, as well as the format of the response for each resource and endpoint, before you build anything. - -While you do this designing, you make assumptions about what data the -client interface will need to fetch, and how. Many of these assumptions will turn out to be wrong, and you'll have to modify your design. - -Since REST APIs are rigid _by design_, changing your API can mean creating a whole new version of your API just to support one feature you didn't think of in the beginning. This gets expensive fast. - -This is also exactly the opposite of the way it should be. The server exists to serve the client, so it needs to be as responsive as it possibly can be to the needs of the client. Instead, today, we tend to implement our clients around the server, because the server is the more rigid part of the system and would be the most difficult to change. - -### REST APIs are Wasteful - -Network bandwidth is still a limited commodity, and minimizing the amount of data that travels over the wire makes your app render faster and the experience smoother. However, REST APIs dictate what data can and must be sent for each request, regardless of what the client actually needs in order to render something to the user. - -As a result, APIs serve a bunch of data that isn't needed, thereby wasting all the time it takes to fetch and transmit that data. Also, due to the way REST is designed, you often have to make multiple requests to get the data you need, which is also wasteful. - -We need a better way to batch requests and limit the data returned. - -### Documenting a REST API is Hard - -If you write a REST API, you really should document it, even if it's only for internal use! If someone other than you will ever use it, you need to document it to save them the effort of having to read through your potentially complicated source code. Especially if _they don't even have access to your source code._ - -The trouble is, documentation is hard to do right. [Stripe][stripe-docs] hit it out of the park with the quality of their docs, but few others have ever done that good of a job. That's because it's hard. - -## How GraphQL Helps - -In GraphQL, the client is in charge. The client specifies what data it needs, and the server does only what is necessary to return that data. The server becomes a lightweight app dedicated to resolving GraphQL queries into data, with a minimum of other concerns. It also becomes much more agile, because it's much easier to expose more data on the server without breaking or slowing down existing requests. This allows you to elegantly expand your API as your knowledge of your clients' needs grows. - -Both queries and mutations can be batched, allowing for maximum network efficiency. If you need even more efficiency, I don't see any reason you couldn't use something like [MessagePack][message-pack] as your data format rather than JSON. - -Further, the server is self-documenting, because it broadcasts its schema to the outside world. This also allows the client to make sure that its requests are valid before it even tries to make them. - -If all that intrigues you, GraphQL is easy to learn. I've found the [Learn GraphQL website][learngraphql] to be very helpful. Elixir support isn't quite there yet, but I hope to help out with that. - -[learngraphql]: http://learngraphql.com -[message-pack]: http://msgpack.org/ -[stripe-docs]: https://stripe.com/docs/api -[graphql-spec]: http://facebook.github.io/graphql/ -[phoenix-next]: http://confreaks.tv/videos/elixirconf2015-what-s-next-for-phoenix diff --git a/_posts/2015-12-08-use-gen-event-with-ecto-callbacks.md b/_posts/2015-12-08-use-gen-event-with-ecto-callbacks.md deleted file mode 100644 index 02d1267b7..000000000 --- a/_posts/2015-12-08-use-gen-event-with-ecto-callbacks.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -layout: "post" -title: "Using GenEvent With Ecto Callbacks" -author: "Daniel Berkompas" ---- - -Callbacks. It's common to write _tons_ of callback methods in Ruby ActiveRecord models, and they're one reason ActiveRecord models tend to end up so complicated. - -Ecto also has callback functions such as `before_insert`, `before_update` and so on. In my most recent Elixir project, I didn't want to have my Ecto models end up as cluttered as my ActiveRecord models did. - -## Eliminating Callbacks - -The most obvious way to eliminate callbacks in your models is simply not to use them at all. Every time you need to perform an action `before_insert` or `after_update`, you simply do that action. - -So, in your controller, if you need to send an email after a user is created, you could do: - -```elixir -def create(conn, %{"user" => user_params}) do - changeset = User.changeset(%User{}, user_params) - - case Repo.insert(changeset) do - {:ok, user} -> - send_email(user) - # ... - {:error, changeset} -> - # ... - end -end -``` - -However, this can become fragile if you start creating users in _multiple_ places. You have to remember to add the `send_email/1` call in each place. So, for this sort of thing, you really _need_ an `after_insert` callback function to do it reliably after every user is inserted. - -But then we're back where we started, with the threat of an ever-expanding set of callback functions polluting our Ecto models. - -## Use GenEvent - -The solution I came up with for this is very simple. We can think of the model's lifecycle as a series of events: `insert`, `update` and `delete`. Therefore, we could use OTP's built-in event management feature to handle this: `GenEvent`. - -First, set up an Event broadcaster, like so: - -```elixir -defmodule MyApp.Event do - @handlers [ - # Insert names of handler modules here - ] - - def start_link do - {:ok, manager} = GenEvent.start_link(name: __MODULE__) - Enum.each(@handlers, &GenEvent.add_handler(manager, &1, [])) - {:ok, manager} - end - - def broadcast(event) do - GenEvent.notify(__MODULE__, event) - end -end -``` - -We're setting up a publish/subscribe model, and so the `@handlers` attribute is a list of subscriber modules. `start_link/0` will be used to start the event broadcaster, and add register all of the subscribers. The `broadcast/1` function will notify all subscribers of a new event. - -We then add `MyApp.Event` to our supervisor to ensure it starts up: - -```elixir -children = [ - worker(MyApp.Event, []) -] -``` - -### Broadcasting All Lifecyle Events - -We now will create a module which will automatically broadcast lifecyle events. It looks like this: - -```elixir -defmodule MyApp.ModelLifecycle do - alias MyApp.Event - - defmacro __using__(_) do - quote do - import unquote(__MODULE__) - - after_insert :broadcast_event, [:insert] - after_update :broadcast_event, [:update] - after_delete :broadcast_event, [:delete] - end - end - - # Broadcasts events in this format: - # {:model, :update, changeset} - def broadcast_event(changeset, type) do - Event.broadcast({:model, type, changeset}) - changeset - end -end -``` - -The `__using__` macro imports the module and inserts all of the needed callbacks, pointing at `broadcast_event/2`, which then broadcasts that event on `MyApp.Event`, notifying all subscribers. - -We can then `use` this `ModelLifecyle` module in our model: - -```elixir -defmodule MyApp.User do - use Ecto.Model - use MyApp.ModelLifecyle - - # ... -end -``` - -Now, our `User` model will automatically broadcast each lifecyle event after it occurs. Using the GenEvent API, we can _subscribe_ to those notifications and do something with them. - -For example, we could write an `EmailSubscriber`, like so: - -```elixir -defmodule MyApp.EmailSubscriber do - use GenEvent - - alias MyApp.User - - def handle_event({:model, :insert, %{model: %User{}} = changeset}, state) do - send_email(changeset.model) - {:ok, state} - end -end -``` - -With a little tender love and care, (not shown) we can pretty up this API to look like so: - -```elixir -defmodule MyApp.EmailSubscriber do - use GenEvent - - alias MyApp.User - - def handle_insert(%User{} = user) do - send_email(user) - end - - # ... -end -``` - -## Benefits - -This approach provides a few benefits: - -- We can have multiple subscribers to the same events -- New subscribers require no changes to the model -- Event handling logic for multiple models can live in one place -- Lifecycle events are handled in a separate process, and therefore don't prevent your model from being saved if they fail. This can be a good or bad thing depending on your use case. - -## Downsides - -Like everything, this approach also has its downsides. - -- Added complexity -- Implicit behaviour: it isn't immediately obvious how the logic for certain events is triggered. You need to know about the pub/sub behaviour to know where to look. - -If either of these downsides is too much for you, you can also reference a foreign module in an Ecto callback, like so: - -```elixir -after_insert OtherModule, :function_name -``` - -This allows you to delegate callback work to other modules, keeping your model clean, but requires you to change each model if you want to add another "subscriber". - -## Conclusion - -I hope you found this as interesting as I did when I discovered it. Hopefully I'll have more time for blog posts once my [LearnElixir.tv](https://www.learnelixir.tv) screencast series is finished. diff --git a/_posts/2016-01-28-seo-tags-in-phoenix.md b/_posts/2016-01-28-seo-tags-in-phoenix.md deleted file mode 100644 index df0a8f8bb..000000000 --- a/_posts/2016-01-28-seo-tags-in-phoenix.md +++ /dev/null @@ -1,289 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "SEO Tags In Phoenix" -comments: true ---- - -Public facing websites need to have some basic search engine optimization (SEO) tags, such as `` and `<meta name="description">`. In Rails, you could achieve this pretty simply by putting a `yield :head` tag in the appropriate layout: - -``` erb -<head> - <%= yield :head %> -</head> -``` - -Inside any of your views, you could then use `content_for` to populate this section: - -```erb -<% content_for :head do %> - <title>My Awesome Site - -<% end %> - - -``` - -This is nice, in that it keeps the HTML all together in more or less the same place. In [Phoenix][phoenix], this has to be done differently. - -## How Phoenix is Different - -In Phoenix, your layout is rendered _before_ your page template is rendered. Take this layout, for example: - -```erb - - - - - - - <%= render @view_module, @view_template, assigns %> - - -``` - -Because Phoenix renders templates as functions, by the time your `@view_template` is rendered, the `` is already rendered. So, there's nothing that your view template can do to inject content back up into ``. The `content_for` approach won't work. - -I think there are at least four approaches to dealing with this. - -## 1. `render_existing/2` - -Both Chris McCord and José Valim mentioned `render_existing/2` when they saw this post. I knew about it, (I believe it was one of my questions on IRC that prompted its creation, actually) and while I don't necessarily prefer it, it really should be mentioned here. - -To use `render_existing` for your meta tags, you'd change your layout to look like this: - -```erb - - - <%= render_existing(@view_module, "meta." <> @view_template, assigns) || - render(MyApp.LayoutView, "meta.html", assigns) %> - - - <%= render @view_module, @view_template, assigns %> - - -``` - -The `render_existing` function will silently render nothing if the given template doesn't exist. The alternate `render` call will take over if you don't specify a meta file for your view/action, and will render the defaults. - -Anyway, after modifying your layout, you could then create a `meta.index.html.eex` file in your view's template folder that contains whatever content you want: - -``` -My Awesome App - -``` - -This feels a bit more like Rails, but you end up with an extra file for each action in your controller, which feels a little odd. - -``` -meta.edit.html.eex -meta.index.html.eex -meta.new.html.eex -edit.html.eex -index.html.eex -new.html.eex -``` - -You can avoid this by defining these templates as functions in your view rather than template files: - -```elixir -def render("meta.index.html", _assigns) do - ~E{ - My Awesome App - - } -end -``` - -The `~E` sigil allows you to write EEX code inline in your view. This is fine, but it still can get a little messy looking with long meta descriptions, and you end up with a lot of function definitions. - -I personally think this is likely to confuse the other devs on the projects I work on, so I've chosen not to establish it as the "standard" way at [Infinite Red](http://infinite.red). It is definitely a valid option though, and seems to be the "official" way. - -## 2. Assigns in Each Controller Action - -The second approach modifies the layout to look like this: - -```erb - - - <%= @title %> - - - - <%= render @view_module, @view_template, assigns %> - - -``` - -Then, in each controller action in the app, the developer must specify `title` and `meta` assigns: - -```elixir -def index(conn, _params) do - conn - |> assign(:title, "My Awesome App") - |> assign(:meta, "...") - |> render("index.html") -end -``` - -There are a few downsides to this. First, the title and meta description can dominate the controller action, making it harder to see the business logic. Second, it can easily end up being pretty ugly if you don't put it all on one line: - -```elixir -def index(conn, _params) do - conn - |> assign(:title, "My Awesome App") - |> assign(:meta, """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam feugiat - nibh ligula. Maecenas egestas nibh cursus erat sodales, vitae congue nisi - tempus. Nam mattis et velit eu lacinia. - """) - |> render("index.html") -end -``` - -I think we can do better. - -## 3. Delegated Functions - -Another alternative I've seen is to delegate to functions on your view module, like this: - -```erb - - - <%= @view_module.title(@view_template, assigns) %> - - - - <%= render @view_module, @view_template, assigns %> - - -``` - -Then, in your view module, you can define different titles or meta descriptions for different layouts, or set a default. - -```elixir -defmodule MyApp.PageView do - use MyApp.Web, :view - - def title("index.html", _assigns), do: "My Awesome App" - def title(_other, _assigns), do: "Default Title" - - def meta("index.html", _assigns), do: "..." - def meta(_other, _assigns), do: "Default Meta" -end -``` - -This is pretty similar to the first approach outlined above. - -If you didn't want to have to define these functions on every view, you could create a mixin that would define default implementations of these `title` and `meta` functions. - -```elixir -defmodule MyApp.SEO.Defaults do - defmacro __using__(_) do - quote do - def title(_other, _assigns), do: "Default Title" - def meta(_other, _assigns), do: "Default Meta" - - defoverridable [title: 2, meta: 2] - end - end -end -``` - -Then, `use` this module in your `web/web.ex` to make it affect all views. - -```elixir -def view do - quote do - # ... - use MyApp.SEO.Defaults - end -end -``` - -This approach has the advantage of cleaning up the controller, but I think it's a bit less clear where the titles and meta descriptions are being generated, and it's not clearly better than the `render_existing` approach. - -## 4. Use a Plug - -A huge number of problems can be solved with Plug, so today, I thought I would try to use it to solve this problem. It's pretty simple to create a custom plug for this: - -```elixir -defmodule MyApp.SEO.Plug do - def put_seo(%{private: %{phoenix_action: action}} = conn, settings) do - settings = settings[action] || [] - - conn - |> assign(:title, settings[:title]) - |> assign(:meta, settings[:meta]) - end -end -``` - -The plug takes a "settings" argument, which we read from to determine what the `title` and `meta` assigns should be. I just include it in the `controller` section of `web/web.ex`: - -```elixir -def controller do - quote do - # ... - import MyApp.SEO.Plug - end -end -``` - -And then I can use it in my controllers, like so: - -```elixir -defmodule MyApp.PageController do - use MyApp.Web, :controller - - @meta %{ - index: %{ - title: "My Awesome App", - meta: """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam feugiat - nibh ligula. Maecenas egestas nibh cursus erat sodales, vitae congue nisi - tempus. Nam mattis et velit eu lacinia. - """ - }, - contact: %{ - title: "Contact Us" - meta: "..." - } - } - - plug :put_seo, @meta - - def index(conn, _params) do - # ... - end - - def contact(conn, _params) do - # ... - end -end -``` - -And my layout looks like this: - -```erb - - - <%= assigns[:title] || "Default" %> - " /> - - - <%= render @view_module, @view_template, assigns %> - - -``` - -While I'm not completely happy with it, (it would be nice to get all that text out of the controller), I think this is pretty clear. The controller actions are also uncluttered, which is a plus. - -## Other Approaches - -I'm pretty sure you could create a plug that used the [Gettext][gettext] API to extract all the strings out to .po files. I'm just not sure that's worth the added complexity and explaining to new developers. - -I hope you found this helpful! - -[phoenix]: https://phoenixframework.org -[gettext]: http://hexdocs.pm/gettext/Gettext.html diff --git a/_posts/2016-04-05-background-jobs-in-phoenix.md b/_posts/2016-04-05-background-jobs-in-phoenix.md deleted file mode 100644 index 8ec66cbbd..000000000 --- a/_posts/2016-04-05-background-jobs-in-phoenix.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Background Jobs in Phoenix" -comments: true ---- - -In Ruby on Rails, it's very common to use background worker libraries like [Sidekiq][sidekiq] to speed up requests and do work asynchronously. Rather than doing all the work that needs to be done inline (and blocking other requests), a background job can speed things up significantly. Sidekiq is great, and you should definitely use it in your Ruby projects. - -However, because Phoenix is built with Elixir, we can get pretty far with just the built-in concurrency primitives and OTP, without any outside libraries. - -## [Task.start_link](http://elixir-lang.org/docs/stable/elixir/Task.html) - -If you don't care about whether the background job succeeds or not, and you don't need to get the result back, you can use `Task.start_link/1`. Simply pass an anonymous function to the `start_link/1` function, and it will be run in a background process. - -```elixir -Task.start_link(fn -> IO.puts "Hello, World" end) -``` - -## [Task.async](https://elixir-lang.org/docs/stable/elixir/Task.html) - -If you care about the result, then you should use the `Task` module's `async/1` function. Here too, you can pass an anonymous function to `async`, and it will be run in a new, separate process. - -```elixir -Task.async fn -> - do_something_in_the_background -end -``` - -Alternatively, you can give a module name, function name, and the function's arguments, instead of an anonymous function. To retrieve the result of the function once it finishes running, you wait for it using `Task.await/1`. - -```elixir -# The `task` that is returned here is a struct, containing a reference -# to the process that was started -task = Task.async(ModuleName, :function, [args]) - -# `Task.await/1` will wait for a limited time for the background process -# to complete and return the result. If the background job doesn't -# complete within the time, an exception will be thrown. -result = Task.await(task) - -# `result` is now bound to whatever value ModuleName.function(args) returns -``` - -This alone gets you a pretty long way, but there are situations where more is required. - -## [Task.Supervisor](http://elixir-lang.org/docs/stable/elixir/GenServer.html) - -If you have tasks that can be safely retried, you can use a `Task.Supervisor` to manage them. Just add the supervisor to your app's supervision tree in `lib/myapp.ex`. - -```diff -children = [ - supervisor(MyApp.Endpoint, []), - supervisor(MyApp.Repo, []), -+ supervisor(Task.Supervisor, [[name: MyApp.TaskSupervisor, restart: :transient]]) -] -``` - -The `restart: :transient` option is important, because it causes the `Task.Supervisor` to retry failed tasks. The default setting is `:temporary`, and does not retry tasks. - -With this in place, you can start a supervised task like so: - -```elixir -Task.Supervisor.start_child MyApp.TaskSupervisor, fn -> - do_background_work -end -``` - -Or, if you care about the result, you can use `Task.Supervisor.async/2` and await the results: - -```elixir -task = Task.Supervisor.async MyApp.TaskSupervisor, fn -> - do_background_work -end - -result = Task.await(task) -``` - -If the tasks managed by this supervisor fail, they will be retried automatically until they succeed. It's also possible to start tasks on a separate node (separate hardware!): - -```elixir -Task.Supervisor.async({MyApp.TaskSupervisor, :remote@local}, - ModuleName, :function, [args]) -``` - -This only works if the other node has a supervisor named `MyApp.TaskSupervisor` and is also running a version of `ModuleName`, of course. - -## When are Libraries Needed? - -You may still need a library if you need queues and job prioritization, or if your nodes don't have network access to each other, and you therefore have to share a queue through Redis. However, you can get pretty far using only the `Task` module. - -[sidekiq]: http://sidekiq.org/ diff --git a/_posts/2016-04-23-multidimensional-arrays-in-elixir.md b/_posts/2016-04-23-multidimensional-arrays-in-elixir.md deleted file mode 100644 index 68f3a352a..000000000 --- a/_posts/2016-04-23-multidimensional-arrays-in-elixir.md +++ /dev/null @@ -1,221 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Multidimensional Arrays in Elixir" -comments: true ---- - -I recently picked up a copy of [Game Programming Patterns][game-patterns], and started messing around with implementing some of them in Elixir. I very quickly ran into some trouble dealing with multidimensional arrays. - - - -Take the game of Tic-tac-toe for example. - -![Tic-tac-toe Game Board](/assets/img/tic_tac_toe.png) - -Most programming languages would represent this Tic-tac-toe board as a multidimensional array, like this: - -```javascript -var board = [ - ["x", "o", "x"], - ["x", "o", "o"], - ["o", "x", "o"] -] -``` - -You could then access and change squares on the board with the `board[x][y]` syntax. - -```javascript -board[0][0] // "x" -board[0][0] = "o" -``` - -However, if you try this in Elixir, it won't work. - -```elixir -board = [ - ["x", "o", "x"], - ["x", "o", "o"], - ["o", "x", "o"] -] - -board[0][0] -** (ArgumentError) the Access calls for keywords expect the key to be an atom, got: 0 - (elixir) lib/access.ex:136: Access.fetch/2 - (elixir) lib/access.ex:149: Access.get/3 -i - -board[0][0] = "y" -** (CompileError) iex:3: cannot invoke remote function Access.get/2 inside match - (stdlib) lists.erl:1353: :lists.mapfoldl/3 - -``` - -This is for several reasons. First of all, Elixir's `[]` syntax is powered by the [Access behaviour][access], which is only implemented for maps and keyword lists. So, if you wanted to get the value of the game board at coords `[0,0]`, while representing the board as an ordinary list, you'd need to do this: - -```elixir -board -|> Enum.at(0) -|> Enum.at(0) -# => "x" -``` - -It's no easier if you decide to use a multidimensional tuple to represent the board; you just have to use the `elem/2` function instead of `Enum.at/2`: - -```elixir -board = { - {"x", "o", "x"}, - {"x", "o", "o"}, - {"o", "x", "o"} -} - -board -|> elem(0) -|> elem(0) -# => "x" -``` - -While this is a little weird, it's manageable for _reading_ from the board. But what about _writing_ to the board? That's where the second difficulty comes in. - -Elixir variables are _immutable_. This means that we can't just mutate the game board variable at the `[0,0]` coords, we have to make a modified _copy_ of the entire board. - -This is easy to do in a one-dimensonal list or tuple: - -```elixir -# Use List.replace_at/3 for lists -modified = List.replace_at(list, 0, "y") - -# And put_elem/3 for tuples -modified = put_elem(tuple, 0, "y") -``` - -However, it becomes pretty complicated when you need to write to a position in a nested list. In that case, you would need to: - -1. Get the nested list you need to modify out of the container list. -2. Make a modified copy of the nested list. -3. Recursively rebuild the entire data structure. - -Every nested Elixir data structure has this problem, and the good news is that Elixir has macros that do all of this for you. See the [`put_in`][put_in] macro, for example: - -```elixir -map = %{user: %{age: 20}} -put_in map[:user][:age], 21 -# => %{user: %{age: 21}} -``` - -The bad news is that the [`put_in`][put_in] macro also relies on the [Access behaviour][access], and therefore only works on maps and keyword lists, not regular lists and tuples. What to do? - -## Solution: Use a Map - -We can eliminate all of these difficulties if we represent the game board as a map rather than a list or tuple. - -```elixir -board = %{ - 0 => %{0 => "x", 1 => "o", 2 => "x"}, - 1 => %{0 => "x", 1 => "o", 2 => "o"}, - 2 => %{0 => "o", 1 => "x", 2 => "o"} -} -``` - -The keys of each nested map are zero-indexed, just like arrays in other languages. This allows you to use the [Access behaviour][access]: - -```elixir -board[0][0] # => "x" -``` - -It also allows you to use the [`put_in`][put_in] macro. - -```elixir -board = put_in board[0][0], "y" -``` - -This is just about as user-friendly as other languages, but it is still harder to manually write this map structure than it would be to write a nested list. - -We can make the experience nicer by creating a module that can generate these maps from lists and vice versa. - -```elixir -defmodule Matrix do - @moduledoc """ - Helpers for working with multidimensional lists, also called matrices. - """ - - @doc """ - Converts a multidimensional list into a zero-indexed map. - - ## Example - - iex> list = [["x", "o", "x"]] - ...> Matrix.from_list(list) - %{0 => %{0 => "x", 1 => "o", 2 => "x"}} - """ - def from_list(list) when is_list(list) do - do_from_list(list) - end - - defp do_from_list(list, map \\ %{}, index \\ 0) - defp do_from_list([], map, _index), do: map - defp do_from_list([h|t], map, index) do - map = Map.put(map, index, do_from_list(h)) - do_from_list(t, map, index + 1) - end - defp do_from_list(other, _, _), do: other - - @doc """ - Converts a zero-indexed map into a multidimensional list. - - ## Example - - iex> matrix = %{0 => %{0 => "x", 1 => "o", 2 => "x"}} - ...> Matrix.to_list(matrix) - [["x", "o", "x"]] - """ - def to_list(matrix) when is_map(matrix) do - do_to_list(matrix) - end - - defp do_to_list(matrix) when is_map(matrix) do - for {_index, value} <- matrix, - into: [], - do: do_to_list(value) - end - defp do_to_list(other), do: other -end -``` - -## Conclusion - -With the `Matrix` module in place, we can now manipulate multidimensional arrays just as well as other languages: [^memory] - -```elixir -# Create the game board -board = Matrix.from_list([ - ["x", "o", "x"], - ["x", "o", "o"], - ["o", "x", "o"] -]) - -# Access a value with coords -board[1][2] # => "o" - -# Update the value at coords -board = put_in board[2][0], "x" - -# Get a multidimensional list back: -Matrix.to_list(board) -# [ -# ["x", "o", "x"], -# ["x", "o", "o"], -# ["x", "x", "o"] -# ] -``` - -It would be nice if something like this was included in the standard library, but perhaps it isn't a common enough problem. Anyway, I hope it was helpful! - -[^memory]: This approach will still consume more memory to update the game board than other languages would because we copy the board for each mutation, rather than mutate the board in place. You can fix this by representing each square on the board with an [Agent][agent], but this slows down reading the state of the board. It's a tradeoff. - -[agent]: http://elixir-lang.org/docs/stable/elixir/Agent.html -[put_in]: http://elixir-lang.org/docs/stable/elixir/Kernel.html#put_in/2 -[access]: http://elixir-lang.org/docs/stable/elixir/Access.html -[game-patterns]: http://www.amazon.com/Game-Programming-Patterns-Robert-Nystrom/dp/0990582906/ref=sr_1_1?ie=UTF8&qid=1461424993&sr=8-1&keywords=game+programming+patterns - - diff --git a/_posts/2016-09-27-ecto-multi-services.md b/_posts/2016-09-27-ecto-multi-services.md deleted file mode 100644 index dd7ae0ff4..000000000 --- a/_posts/2016-09-27-ecto-multi-services.md +++ /dev/null @@ -1,182 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Replace Callbacks with Ecto.Multi" -comments: true ---- - -We all have logic in our applications like this: - -- When a user is created, send a notification to an admin -- When a post is deleted, remove it from the search cache -- When a password is reset, log out that user's active sessions - -These side effects need to be predictable and reliable. Often, they're some of the key business logic of the whole application. - - - -Many Rails applications store this kind of logic in their models, using ActiveRecord callbacks. However, since [Ecto 2.0 removed callbacks][remove-callbacks], this is not a viable (or desirable) approach in Elixir. - -Instead, Elixir follows a simple rule: - -> **If you have shared business logic, create a shared module.** - -One of the ways you can do this is by building simple "Service" modules around [Ecto.Multi][ecto-multi].[^1] Since business logic usually revolves around an [Ecto.Schema][ecto-schema], I often name service modules after the schema they interact with, such as `UserService` or `PostService`. - -```elixir -defmodule MyApp.UserService do - alias MyApp.{Mailer, NotificationEmail} - alias Ecto.Multi - - def insert(user_changeset) do - Multi.new - |> Multi.insert(:user, user_changeset) - |> Multi.run(:notify_admin, ¬ify_admin/1) - end - - defp notify_admin(%{user: user}) do - user - |> NotificationEmail.notify_admin - |> Mailer.deliver - end -end -``` - -I often name the service module functions after the operations they enhance, such as `insert`, `update`, or `delete`. Of course, there's nothing preventing you from using other names for more custom functionality. - -As you can see in the example above, [Ecto.Multi][ecto-multi] allows us to encapsulate our business logic into a struct which can then be passed around, appended to, and run as a database transaction. - -We can add arbitrary functions to the `Multi` operation, such as `notify_admin/1` which will cause the transaction to rollback if they return `{:error, ...}`. We can then call the whole thing with `Repo.transaction/1`: - -```elixir -result = - %User{} - |> User.changeset(user_params) - |> UserService.insert - |> Repo.transaction - -case result do - {:ok, %{user: user}} -> - # handle success - {:error, :notify_admin, _failed_value, _changes_so_far} -> - # handle the case that the email didn't send - {:error, _failed_operation, _failed_value, _changes_so_far} -> - # handle the generic error case -end -``` - -The error states are very detailed and allow you to be as specific or general as you want to be with your error handling. - -Since each service function returns an [Ecto.Multi][ecto-multi], services can be chained together to execute as a single database transaction: [^2] - -```elixir -user_operation = UserService.insert(user) -log_operation = LogService.insert(user) - -user_operation -|> Multi.append(log_operation) -|> Repo.transaction -``` - -[Ecto.Multi][ecto-multi] structs are also inspectable using `Ecto.Multi.to_list/1`. This makes them pretty easy to unit test. - -```elixir -user -|> UserService.insert -|> Multi.to_list -# => [ -# {:user, {:insert, user, []}} -# {:notify_admin, {:run, fun, []}} -# ] -``` - -There are at least several other advantages to having a service layer built around [Ecto.Multi][ecto-multi]: - -- Schemas can contain only pure functions, with no ActiveRecord bloat. All your external side effects can be kept in your service layer. -- Side-effects are opt-in, making testing **much easier**. Don't want all the side effects? Just `Repo.insert!` your data instead. -- Transactions can be automatically rolled back on failure, leaving no inconsistent mess behind. [^3] - -Those are some pretty nice features for such a simple abstraction. Even so, you don't have to use [Ecto.Multi][ecto-multi] for everything, as we'll see in the next section. - -## A Guide for Rails Developers - -For `before_create` or `before_update` logic, update your schema's changeset function(s) rather than using a service. Usually, this type of business logic only affects data, so it's a natural fit for your changeset function. - -For example, if we want to automatically promote or demote articles to our home page based on a `like_count` field, we could do that with changeset functions like so: - -```elixir -def changeset(schema, params) do - schema - |> cast(params) - |> promote_popular -end - -defp promote_popular(changeset) do - if get_field(changeset, :like_count) > 1000 do - put_change(changeset, :promoted, true) - else - put_change(changeset, :promoted, false) - end -end -``` - -Or if we wanted to automatically encrypt a user's password: - -```elixir -def changeset(schema, params) do - schema - |> cast(params) - |> validate_required([:password]) - |> validate_confirmation(:password) - |> encrypt_password -end - -defp encrypt_password(changeset) do - password = get_change(changeset, :password) - - if password do - encrypted = Comeonin.Bcrypt.hashpwsalt(password) - changeset - |> put_change(:encrypted_password, encrypted) - |> delete_change(:password) - |> delete_change(:password_confirmation) - else - changeset - end -end -``` - -For `after_create` or `after_update` logic, use an [Ecto.Multi][ecto-multi] service. This logic usually has to do with other database tables or external side effects, and therefore is a good fit for a service module. The documentation provides a good example: - -```elixir -defmodule UserService do - alias Ecto.Multi - import Ecto - - def password_reset(account, params) do - Multi.new - |> Multi.update(:account, Account.password_reset_changeset(account, params)) - |> Multi.insert(:log, Log.password_reset_changeset(account, params)) - |> Multi.delete_all(:sessions, assoc(account, :sessions)) - end -end -``` - -The only important convention for service modules is that they return an Ecto.Multi structs: what you name the modules and functions themselves is much less important. Do what makes sense for your application. - -## Conclusion - -I've been looking for a viable alternative to ActiveRecord callbacks and their associated bloat for a long time, and I finally feel like I've found it with [Ecto.Multi][ecto-multi]. Be sure to check out its documentation, and if you're interested, I've also covered its API in more detail in [this episode of LearnPhoenix.tv][ecto-multi-episode]. - - -[^1]: This is only _one_ available option. You could also share business logic with a `Plug` or a plain module, depending on your application. - -[^2]: As long as every operation has a unique name. - -[^3]: This automatic rollback behavior is also opt-in. If you don't want one of your operations to ever cause a rollback, just ensure it always returns `{:ok, ...}`. - -[ecto-multi]: https://hexdocs.pm/ecto/Ecto.Multi.html -[ecto-multi-episode]: https://www.learnphoenix.tv/episodes/ecto-multi -[remove-callbacks]: https://github.com/elixir-ecto/ecto/issues/1104 -[run/3]: https://hexdocs.pm/ecto/Ecto.Multi.html#run/3 -[ecto-schema]: https://hexdocs.pm/ecto/Ecto.Schema.html diff --git a/_posts/2017-01-17-reusable-templates-in-phoenix.md b/_posts/2017-01-17-reusable-templates-in-phoenix.md deleted file mode 100644 index cbe254a1f..000000000 --- a/_posts/2017-01-17-reusable-templates-in-phoenix.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Reusable Templates in Phoenix" -comments: true ---- - -If you've spent any time with [React](https://facebook.github.io/react/) or its [look-a-likes](https://github.com/developit/preact), you've probably realized that most web apps have a lot of duplication in their templates. This is particularly true of server-rendered apps. - - - -Templates should be organized as a set of reusable components, like this: - -```html - - - - -``` - -This way, if you ever decide to change the markup for `` or ``, you only have to change one place, and all the `` across your project will be updated automatically. - -However, just because server-rendered apps aren't usually organized this way _doesn't mean they can't be._ In Phoenix, it's actually quite easy. Here's all you need to do. - -1) Create a `ComponentView` and a `templates/component` directory: - -```elixir -defmodule MyApp.Web.ComponentView do - use MyApp.Web, :view -end -``` - -2) Define templates for each component you want to have. For example: - -```erb - -
    - <%= assigns[:do] %> -
- - -
  • <%= assigns[:name] %>
  • -``` - -3) Use these templates whenever you want tabs: - -```erb -<%= render ComponentView, "tabs.html" do %> - <%= render ComponentView, "tab.html", name: "All Products" %> - <%= render ComponentView, "tab.html", name: "Featured" %> -<% end %> -``` - -So far, so good, but it's too verbose. Let's add a `ComponentHelpers` module like so: - -```elixir -# views/component_helpers.ex -defmodule MyApp.Web.ComponentHelpers do - def component(template, assigns) do - MyApp.Web.ComponentView.render(template, assigns) - end - - def component(template, assigns, do: block) do - MyApp.Web.ComponentView.render(template, Keyword.merge(assigns, [do: block])) - end -end -``` - -Then, import it into your `view` function in the `MyApp.Web` module: - -```elixir -def view do - quote do - # ... - import MyApp.Web.ComponentHelpers - end -end -``` - -Your markup can then be much more concise: - -```erb -<%= component "tabs.html" do %> - <%= component "tab.html", name: "All Products" %> - <%= component "tab.html", name: "Featured" %> -<% end %> -``` - -There's no reason you couldn't shorten it up even more if you wanted: - -```erb -<%= c "tabs.html" do %> - <%= c "tab.html", name: "All Products" %> - <%= c "tab.html", name: "Featured" %> -<% end %> -``` - -It's still not as concise as React, but I think it's close enough. - -```html - - - - -``` - -Obviously, this is a contrived example with very simple markup, so it might seem like too much effort. However, I think it's a very useful approach whenever you're dealing with complicated markup that you need to reuse often. - diff --git a/_posts/2017-09-30-why-your-software-projects-fail.md b/_posts/2017-09-30-why-your-software-projects-fail.md deleted file mode 100644 index c3215c91f..000000000 --- a/_posts/2017-09-30-why-your-software-projects-fail.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: "post" -author: "Daniel Berkompas" -title: "Why Your Software Projects Fail" -comments: true ---- - -Software projects fail all the time. In fact, if you do client or startup work, you may never yet have built a piece of software that was commercially successful. - -Why is this? Is it because of you? If you just learned one more code design pattern or chose the right language or framework for your next project, would it be a success? - -No, probably not. Why? - -**Because code doesn't determine commercial success.** No matter how well you code, the project can still fail, and it probably will. - -This is because a successful business needs the following three things: - -> 1) A product that customers want, 2) sold for a price customers are willing to pay, but which is still profitable for the business, 3) which customers are aware of. - -In the context of software, this means that you need: - -1. Correct Product Design -2. Correct Pricing -3. Effective Marketing - -If any of these three are lacking, the software will not be successful and the business relying on it will fail. - -### 1. Correct Product Design - -The software must be designed _correctly_ for the target customer. The customer defines what is correct: not the programmer[^1] or the business owner. - -If the design is wrong, all the work the programmer puts into making the code conform to that design is wasted. It will all have to be thrown away when the design changes, if the business survives that long. - -If whoever does the product design is not finding out what the correct design is from the _customer's perspective_, you might as well shut down the project. It will go nowhere. - -Programmers often are not involved in product design. This means that most of our work is irrelevant to success. - -### 2. Correct Pricing - -The price for the software must be a price customers are willing to pay, but it also must be a price that is profitable for the business in the long run. - -Programmers have no control over pricing, and limited impact on profitability. Their salaries affect the bottom line, obviously, but their code only matters to the degree that it costs the business extra money. - -Programmers can ensure that the software runs with as few resources as possible, with as few bugs as possible. While this helps profitability, it's not usually enough to make or break the business. - -If the price is too high for the customers or too low for the business, the business will fail, and there's not much a programmer can do about it. - -### 3. Effective Marketing - -Customers have to know about the product in order to buy it. Everything else can be perfect, but without effective marketing leading to adequate sales, the business dies. - -Programmers don't do marketing. So if whoever is doing it does a bad job, the business can fail without the programmer doing anything wrong. - -### What Should I Do? - -Is it hopeless then? For some projects, yes; for others, no. Here's what you can do. - -**Be more than a programmer.** _Programmers_ may not contribute much to the three items above, but that doesn't mean _you_ can't. If you want your software projects to stop failing, you need to know more than code: you need to know design, business operations, and marketing. - -**Don't accept your role.** Get involved in the design, operations, and marketing of your project if you care about it succeeding. Don't slot yourself into a programmer role when you could contribute more. - -**Accept your role.** When your boss or client puts you in the programmer box, and won't involve you in design, operations and marketing, then accept it. As a programmer, and only a programmer, the success of the project or business is not your responsibility. It's on them. Don't feel bad or be surprised when it fails, because it wasn't your fault. - - - - -[^1]: Or "Software Engineer", "Engineer", "Coder", "Writer" or whatever we are calling ourselves these days. - - - - diff --git a/assets/css/all.css b/assets/css/all.css deleted file mode 100644 index 5d3566056..000000000 --- a/assets/css/all.css +++ /dev/null @@ -1,7 +0,0 @@ ---- ---- -{% include css/base.css %} -{% include css/skeleton.css %} -{% include css/screen.css %} -{% include css/layout.css %} -{% include css/pygments_themes/manni.css %} diff --git a/assets/img/algorithms-manual.jpg b/assets/img/algorithms-manual.jpg deleted file mode 100644 index b24d6a24d7b895e2be2b5309300afecba626d17c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20268 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 z 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 diff --git a/assets/img/tic_tac_toe.png b/assets/img/tic_tac_toe.png deleted file mode 100644 index cce0f2453c6691f657603467e3f1072256584007..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15269 zcmdU$byOVRw&!uz;1DDb+`4fmNN`KAAdS1by9Z5h*8m~76I=oWx8T}1!QIIezu$fD z-gjr-fAeOoVXdaStIj@E=hTtCKl|GsRg|PL(MZu?U|=w1WhCDN?|08H6eQq(#DTUj z@P=S6rYHsjQyqu?V2B8Or!tm#uLuL=23=F)KrP>FV4~hx`#`d;sh9>q#rfeRz4nS!b7$FY< z;G?aniy@VVt&N?tfQK;kUnK;9&(DY1sj2=d;$kgK{XtQMO2XdBl!}{;iw#6Af<{F} zCFEpcCh%TT>L2F7H(_cE7Z(Qsc6N7ncQ$uUHhU*?b}&CbKRbwnor8lFD8cINY3E|- z!D{DB^LHixQIDjlv$2z6g@0K~IGGx{*gL7&+uMk!SbCV+*gk)y z;$-9e+wxz}|BLB!D+H9CEKPy#eeOgNun_zIcI@BFzc+QZw{d+gu5M@PBEs z?^Y7_whm6F&d$$Q;r@rozaIOS^3RyN<`Js;9esWqfqLw0~vM?dpO=l zqPVcay&G?7urTVThC7s?`hQ+E@r=m(OGd}1**9Uev)bU7oLuAYB+4gkX<3&^rHB=| zr_}=W%O~8I?$1@piiVF(x2I##z336JkYb{>^f%RRQFF=ydVW4Q++Q_zK!_yT-SdZk z7dN!%3VuGegHFeyG;>@J(C35{XL~VWknxnoT|HiR{2@0>w=Ak!avI`RU&w!aI3EkY zUiDukX-I|{ZeEXM4%;irsJK$KT&;yuqN7*NeyQhuSbuUh#2)Ux>dV`DIimwTY+S`k zpi@NS9OW}kbzkx9@i`yaxdLjUB-Hmg1wCF&6^pZF%A?QmlBkLdjh2$*x>$a;EJ~w$ zfMWZjq(I!XIv&oJoX0{7leNF)6~wDEfDR|+*_YWzavl%Nq28u0gZ>{}h@tV+wfYlhhN7ewS64aJ4Gt2tV&x~FiBNf-Ch zrJzeUKe(i7PP)?09M};mUOAMx~h1h93w*XWP9X)H1D6+SiXV9gU(X* zhU0q4nZ9;R#G7L$)0wVlq)jOKgV9G5pr$nHrFYWgNv#)?^4Y;khI}|egHz>=E9vO% zk(D8>0wkHN{?b!lj#6{B+k%^5{~7q(mCQx1lyOri4iKB(>&!bl3c zEP1b;@B7?xxl)Qpv!%r3d_>6J8wN!hLAKgq4tWlro0A{uI|F|nSO0v^Kv=CyF%Dzp zwb|FEow2zd42b-&`t)?&!3!_H_`sy^i7J4~&RDt4ysPhbYg+SowI~Rmr~X3*L^EdL z&h?(q!T)w!IrOB`|B-p#vN%gSM%nA8zzVZdBq(l67!ueS5!MaHRk-E&T|Ib+t10bd3!K8n-qyB zNIA_gR#g8yP3DP$C2@n;ge;b$U-C~ns+gt`7#M~w!Xm%?wB*o(IRJ5APFnK+^Ee=U zy_B%zwrD#5^h9a{%x=#t&)&3EexU))b!^f4F;1P5#?3bCu4KhSQix_0?nRvF6JNDF zY8JnFhHZn|hv|zIU~1iC45QvmDT}sYs}{H#Y*Id*gbx(w`2AL({`*#paDytEDqD^O z##%-`eBqNDC&}GRtH3DF3)DXd$z`SW2hg)9ubc zod1LIm|(EIL2gdSLrTYkskpzYl!uw^M9)}Zir#N7tBli&)3kvA5eT9JPo`kqt?~`w zt6h_~rzruN=R__r_`#Vjld_|HFVs?h5{@;IshTz5ARXJmEN}NdW^jo<5nOF1D7*RR zKPpzmY8!>LB0^4AfcY7VoMfs2Um99FK$r1_^=0TD_NRmE)rW0fCC#KzCTz<~QD8WH zHz?;Kpxrfj|L|+0Rl6*AN;eTHC7?%b@@yD+NGA0cQGbrqzGE<<6k>rxR$Xs|92t+) z(eo2M^ZbD;-So!F242BpZ+vW9Ysx65Nczd9Iwm|^!_7*`^rC?Bk-^X=cS>q+4@vCU zQIAz>+z^L=I#>*3su5ravOFZx_nFAZW-hObzgIo~4EpxkAcRHHe*miN$sj&%85>6o z?gkHF$;lBg}@AR5CrSTzVM~^OFJ$#6%&iGl|b4n0*;TuEFz2sPNM_Jl(%a zl&d9%FI@D`?O50LN3sQ!f-2K$Tm1lY+R}1h6SG=P>qFK-oZV>=#5kk(YB*ZvZCoQS zXM;aVHP7*#aZK^|WiM3}QJj;gJhwVhbO(nJ_ab=&ja+*(DaMF zX=A7_`?*zKkdmThun@GW2g_N{FshIn`bZMEBZw5Hiuk63La~RJAi$gHHs|baRPfkC zsrJ$Nl6GF1jg{rkNVhm2+1$CoCBkDcwusD7q1Hm%Z%PHPEKPJg7c>Zl)p;GXSoE>v z9CldS+J=;Ij!T;Fux_gRc{USyg_d9HY`ZhHT(5YK)uvAG4ss@Kt6(-QV1y?Opfxi= zuq;#x1%EYpF}B{6YkJnJh!LHGB5kaveJ{vk-dWptmI^L=?qmfP(Qw-kq{aA@_VzfJ zTN2S(v6}2LiZI~`&-UqWU+e8mcO#I~`qK#00OP3Om@^ zS4=G$7S~3^Ta786^*CZg#?NbHSF$tGoiGc+=fJe_iyYspG;EBImJ>IrkJqcK(wh?; zcSTjEjq10uJ@NWjK_=S6SMQKBklj(zd*43Usza(qn3ZT2#XsdP&P+EWJ9!1XcuES@ zbS82kMx%RcAss`yRrdKXg;Rf^rQ97`@SrN#+A{{O6xF^ZvKx^|S4F&mR7hT6o)DT` zMe|7u*ySc?!x6F)j7tL2jA*b(Fykw#y3vka!;W8bESRoOFCjuOPJ!n+K!RplaqCov zxL^XT_NZ3k4u6_99+=7N=l!TE@&V!#a9J9y%A|aIHoyme|y;KU+Plm+PgCb zYN)&31w8WcU^foM@!Xen7YajpRz>)&R`nWoQu1V!{tu4&+5;=*#s>alKAnOelUsmQ@2b=%SX)Po3K(}udvX~ zaL8QE8Dke`LBd!TGPRATyjdqS?#QZp>EE&is*`$UYM$%EStA~3Z%47WqjcO&SL!mJ zL<@#>@$o1;0FDLr5PL@}yULq{1Hct}%7Uei;VMP_^OGhn@1rQ#v$O!k@1p=_(X8Hn)CxCSbsm|Hv6l}`_eU+>H#S75UcV`# ztAtMy;ia}F*}Go(;k3U+X=<{a~QBH8(A+dh7DKUnrF1hD|G?fkU^b~9^9g0nt6{~?Ea2npRd9^4OG%|gp^Z3 zU7pfU``}4?|9hS54B^4lDl6@VW}+57UHI>eF>g@>_FhOVSX6D~?U#3oi;gd z%2_gIyT6pB^`o;slg6FQLT-#>3KoC--T8~Kv8z+-y}EtKEtB^6#2_-c%O~_kh(Ow? zfsD!?#RS}-QH6btC9Y}Qm-rSYle)(@jk|T|Q!M1rjN)A8(fCs;b(5Est@ioaqO-idWZ8tkaDP9p_Y^`_3H0@X zDsJR{mSz#SfT?DQKR4IazYMDlesd%LN7J`RWsU zsW}d2+ktW3vz4Mxaj+g9w##J0zR;aer{gd1VU{+v3Q1>z;6UKF!$u4*lF|{$Pn+*s z$?YiSfX{-(T+x)DLmlN`g>=qAb_$vIbY)^z;Ci!&X@cA76-|v~R7f@k*D^2prlX@u z?KO(8|7u1I^ZsgGz|MZG;L!<9v0`ob<*dFaWF+M>W2bhqkgqEM&+>yKiiXYmt{4tj zZ|hrxhPL!RWli!I4au>)Tc*r@Sg+>|f-%L5Pg%j0P82u4!gj#q6T2V;xUgJIlQ8Pd z@z8e#x0G<-)Nt`n8bzAy^=(aP&`i6qN%PM|2e4V8+cAzY3S~1AU>Ca3*w1ef zBE%yYL?KiNkMJQhLBxaw~4{`MT{7ggB zUT%C>56R^p*3Krv)N!3w*P&NE{Y2?|@tLMG%lskVk{yoH@5bt(hXA3NfD zbFPtBFlIernDXrIzi8teK^6Qj@6X9k53$p>mq{FX+cVuhWY$VcK^VAKrXRs9%N>QsT3=9L! z%3^Q_B3q5tcZVU!wVGbe35+3`>8 zvDPpn%8_v_S~p3F*-PZPlcA34N(5Ze_`;)dDGagicPyU$!ra?4)fHsgT{56X`o_wz z^!=?76;c_d>iwu;^kuh1OXN;eSH!$eu&A_(|4A1zG(lMe_1U*uW#a@Q@(>d9Oz<$z zqD`PVTLDh2mRUt_%eQ0G&U%_4HfY%QSb^UR9bE`WKgfGH>5E5!;1(J|6N>`G(Y_r7 z0231LROa7GJo%-n%wO;xCtuL>%*1@nIF`T2viWlTHzF?-vtsiPKp?JkO!{KTh$wt7 zCQimBu%}1*(3s0`o^e(cz{A3TfLDM804=fP*>7v@IwSeeg5R%MrR_z4q0Y@I`hNVA z%g4t6i*oia@D)6oGNMp7*AiU-ZSah$NwA2k;@ zMK!pYH6z7ylLgCS#WK&M?wEkZw2J|*DTRa&@=R%_$y!T)jG|bR!}M+Ir{nu6yjgk` z=8Cb%{Z!f`U%2$@!|4JW}>|v?- zNzF*!BEyyj5W6qB_`aP!bZ*D%c!*DiK$+K2 z>ZyBDLX2w{(mIoKxrk@+718Z{gtswZbUc{B{%^Oc;ub~VEgNhb00FZC$GMDNA3%G; z@9%-=YxgB}ZtVdzDGJ!o24|o?epiNULb3B#!RJEWU*;3+a^nOYOezj&z6@sL_6QEf(JnUsU8>-gICb%`=*n@#sxz%w=AA0Zm8ql1AEy7Q0KeZJ4Li1(plw zf^{Y4j0yH}+jaJR^;7O*Kc&A<695DEv2raXZ!(=n9Xl{q!12*wDNNnUmSwlaP}YXI z?&Zx@eYq?1p!MLirTYdT*z-PWa;&?Onnfi1K=omY)8}rl_{bb+D0b0^Z;0>Y7uneP zjX2Sx)iP58J^#l`m#T|p_ti@={;3B;V5fEK&=7MTYv_k?$OsD-_Sb(Vb^Ge4raF1d zsfNcr@3~FSs;^ALeBgV%yyI7^=$4+{H-7X4U>PrtE&s2U6b!BF3~~ht4GBsDmS_hd zv^?;3a~Kr>_i{xwrixB7@jveP(~iAz*mB%{G;8+e4r6>o^v}bSZ9loYg268l*)$D@v_VxaWi}y@H0_2xFp2y0)S^iXIs=3OL(c7+mb_H%7(u zU4-p(@~riV{>DY{1zZ?X?hLC0(tr6(5!Q}Dc8W(K?;xoWVz0nSbr0436d7j`Xls$o z6%PKB5A#(PtWxXXZ^ZB6$h}mW&j?+%jxVN^-G#n<4}GPbwiApcz)|*TTid%F6jC?qgb|R^GDMD4*p+&g3#GDnM zbH(eZZSnJ=%I;^zb);E06+rHzqU(v)oO%l2^s5PH2ZIJNk8x_F*IC!kgw=U9!XT23 zphrpYa1%1{k_>;&SGV)r?y_+vFb=HG@o0z;SfTd>f4GHw=tmNb_g+t+s=vZMNM> zss;|Wxri!G9P1TR1CHo&2XtggpDNL)36E-68uu0;f8<*(v>+3@MJabDO`)~nKg8PT zfmNHTa@%%3DkwtGx8SP4$@&snyvNmDFm2lYa3=5X1H@`U+3Ev7l`9v3*0_s>zxojZ ztk#BnC!O%V*~vxpQX@Zs6_srXK+U5LR+Fl)K;|M&n?I`nEZO7_F6l^oz_!_~F_>$m zHZ~@DvY^?7JzR+l7}pH0EPqx#GE_IFud8Yw3g*0!w#yLIiR7Y+wFE zku{i0Wq{J6qL!N|_+$Vy6Nu|)WZ-Mxrx6uk7lc6W6)6FqKQeyc)}h{64-zXr>6Ov3 z`))dC7*6g(Vy;mTA@-qJHo+B$@z8bb+v+o!%~enH-q{8Z;s>!&g%q#oR8N2VM&P0j zW2Ig%-lEf^20JT`2tkZ1YT(!QFE0jjwbeeH4b4K(*#%7N3-fSAFjNhlHXvh%I4;eS z#M&77$iBi;Xv|T#cEI(YnN=pRZTT^wjW}-yS;w%Zqhid=N3x5uhB}xUH7z{Mw4>AW zoEzBCuHi2e`Vwu-gxPOHNHdC>L8I%eBP$pqc9+ac-vX=w`b5{fU(wR-*iTMmQ>arl zLR7MCkf^DE2ycz^I{+R_hNO)=dbwQT13QFkK1J6_HpeVsyEn(-CtGd_Q9Ca0L_qs4 zHI9?!R6DMA+j=q3-PF4v9C*vJ6Qge!66fSSyi$v}Hn%x{c9M2(ctcZ;5`J`ijWWGXA?=!zgxIk z`qiPqJKYqb+9RtsUCp{ruoRTj(ZLm)6)pX&)E3<6kE;}vnMI+?FGiG~Tnl--tMw`P zQ|%v08Xz}N;6Erq;4lIi$5nvw13-Sy*LMXzUW&toMFKv?I`loL^qFfcKNj39iMRyK zfQ0LL4?V+c-*ar*CyDVn`hbZg{xQ6H-ikOrb+&9OSo?<=(7p#`lZ9Uo2@*j=6kubtuAh z&#_*7wD&{p_(rwJ_hZ*>E<9r?f1V@R>mp|p{Jw3T<%V`6uSBr4NH!ohbtVIz!go3Z9c(awP!&Xi6ML4xPLaN2K5Nt0-PdS)zDDRG8 z1FIMr3L40seg+EnvQ^kZCvS}4T~XnN-a$edy^klWmb?m1YTn)hxX}%Tz`fz(vLsRnoU=i3&O26llZFyjI@ZjV$dNq|3;5 z6GmSZLKM!p0^Gj1+|tgb3!I|(t8J2^_qA1N6375K1{JF>4y)m12h)Qi1$6=ry$BEU z5gQW?wPou^tXC*&vJ*XMF zOFPNxt8LH?v0n+&6My!m;S`ITx;=R4vpJILPD+Zie{Zq;sSmAiCumnJ=f_Mxo@Wb4 zKL_~~XPn5%zYD^m>yApsVI?`)k`4{$sU$E6p=iCChtU;?S0Y|v%m_Wyrq9ag0M<#m z2GUWerk^@tRs(_lN{~`J(2GpFuJRWL5zsD~ zTX(6&avUGpW!eg7fQIqfD-_%$VFaCOb=F^ixV0r@u68ebK6@hmqOkQ zQK^lz`KrQi@J0!}vSXzc%~no*B<8QvnZw)9N}A4U2<&l5A)YL#6)^31_buYjD2Qq?3kw;SfWgiGBLp~yEBI9_^ZQR zQD;U@39*PkH7=U0U|PZd|YK2eUigSpgdR%t4>y6cUDuoNuP z0u@S0CA!)A4%lNg_O7KX7nf_rnGd?CT9~VNu+OCDY;Lsu)&0iSsaw4;XJgU4=n+{t zwmb7@8LndP$oLw5``F@FNbi!ho{Mw!;oGFz?6fw+IU{07!77A>Dh6 z(2N5B2gnBouqAW3Fv6ZNW+tbE>#*Y9AjLz0cbbisHt>`jp8c<>EW2$ zk4E8Czu*nc9wXz* zg_DW8uz;$Bm=ozK5rmM^g9v-;xdj4U{l0hCgjK5gFGqquvtAR*^ih!@gcLX=v0g{d z-ps+kh2^+%6JciNAHGRqi!0w|33?Z${Z`Rt_K3oiRUBVg~JUdd)Lvxy?sSRkyP#y zA#FO;X3^pxw!@(RW&-;S5*(_*t?45XrcrbUDar*$CE#QS6qQCB;Ef(^$9H3k+)(+P z4bbai?AW>8_@n}Svd5-&gwUfQiheSZ3f+hM!(j(sNq*@F=aFtTx5`^BvZAZU{mPCC zGKz)yvu~K1+t)y9^ayZHLoMk5WmUG{-5$tsU9!V%2gosFKCh6FY%Rl3g6{Pwb_$+l zm#HcM7=#0$r7x6nEMJS)K=x&lG*eeWtN@b_5F)U7G#Pg$=s6R7F6n|^`lmYa9~*X{78}KNp64M!{)QTrzyw($#YDQ zm0~}on2)7yT>Qyqq~b^t1N1zg zq1pCzUW5t%fwkt?De?nx@MiATR=i%2Cm}8)M4uh7lRv5IdZIV4v*K131+7eU@ogYe zz6R2*anCHmRtP`nvZV*`tk=ixbPNI!gSZpApLIt{z@YL3*&fjxJg<7sjK$;OwEr;! zh=K0*EAQXnVI8;MS776(ZC|(4omxj9^Ja2Hyf>>I;aLm;_FE=hKsMNwegJK|UO7Kl zXbSx=0LOoiJ)(W@%7(iYfX_)Zg&juF}R{dH*fy(TYELd z^$uHeqwhKaFtN_)B1TW9sW zX~%yjzSC}gJ6=AJ1-ggH#?V7EhdKkI!6FrgLrq=B(EEh#v9LndUKt^&t<)Dkp<SdVA@j#**Af-T1ekYV7dQN3id_-Kou|PYlnxC`gsFw2 zp`w6E+6Kyv;hgiVs>B+{99@B-iGw zB0rOjy%?O<)L*{enz#3{@2e`1lNz>!=ZCe8n}EyA`|;|o&46YcMJOoN5d4`*V=&lU zTNH)g=SW;9fq# z-wMcvy>J#+cC^SXk*yKlCx0caV(Tkjwa2+S$doyN?;(>Aj2G;90c8i2AggfE8QMs~ z=;G+8m478;4H>w;*{Y<8kE>(7P-ts3lGm9Agi$b-rGSpeiWu4q#G7H{ZgX8JWac^| zgGBPtmK8@0i*|xa0@&8+B}N0sg!1LUy2gHE*>U$bwC5ftQXMzZ2eh=l zzs1v2>|dN5z>$Vp&JbemMzMv`yK^6A6fTxNsdoW!1Oy65w9p$SS?B2T>Oy;LLYPEP zCJpwU<*6D!yQ?+97PQOiYtyl)0tNf&LnHqQrPkuO1csTBLbI3lc+`47`Jau-Nfr%) zxK_bF#&zUf$)Zb^w(m%ug{_9eyro+XmFXpLl?{;b>6bx!zh1|aVZ3JL@O!vv4t9wF zrR`e2is0fi#PuTFq}n_v?fcpB3`PTex6{nKPoO=)SB2JcF|NqZgv-J34ORyi^e`8# zUv5<%OMQ?CxVr}Q=2Unw<40~O=N%zygP&Id%HZjqMk!2;L710OMxb<#buDe5f*2k& zw=)bS%fLH%)IT^1AkTktuB6=b-zJL#SwJ2`l z%TN+9sL1VI47uRU^2LZg(rrTSDxkACN-)j-ov@KBXIhw~adylkrB!#9d@O$j7(e2@BapJEEUz2sC8rcs}iT9w7JyL(6@(swp9s zjEt3TZlcZLSZnyAMza!IWz=CK<<=;()uq>IA+O^iS}Gj3nAVWiRb)oZE#B-(cY? z7|xan{G{MWBUBW`C|pU}c+W0q9>o)HvhJR~-i zBas+gQD52P62cQM`nv|{viG*AG}s1DT3jE!Am}`W=g&vns246Z68q)=fOco8rC{8VCE9gjmF!bfTZh@~`=H+EwmnIYIN0{+vi8IAtMiYQg3`Kwndiw&}iNpAB8&|xT+h#r{n9RG6 ztYb5OaY6W=c5xZ0KSK4%e0Y$#GBZ0LUWDb58|-(GTbOFm~MOACe|iVHPuy+XpFp|1_EMUaAs!3~AqN~D4sNpH)We}5YQ zWDTD!Dqea`!U?%AFjNR}H&@kq&mjGfX&hvV2{wGytPw~@BNa{jsezngZxi1Pv{3G+ zxeztJE9cA;Cp=twYvrPTFo}d@R3+lE_Ci!i+^{_*Nvmvn8%`?Oq~=x6o9iSyAX~BZ zVrN*(d9^+{xp^Hq*o~y=NKD!*K^kpZJ)BkhDolbggt=k8 zFUy0paCvC!9gp5f;hvt(8Yq(ZV<_4T@&#q_r<&bh`#Y?)2a9%epIy_A*-7?&!KU(- zqh{MNil(3)2OX9k_VhomR0#E0CC3B7%yky17`bNdI^T`Am7h>DFmq1d-$@&e>q~W2 z5zrWFA}&zI{6(~gmTFt+9=u}!HS~oIEbpLD%-zr=+oCf)_^T7*0z(d0q<_H^X^i{V zC+*Uwa8hdwO5BM5QN`1Lug?NT#j?VrLep*|JAg;-jV7zmb*#WOWLU()JRtyFSysv2 zEnx#r?RQV67T8%QEr0mBTlx;cMYiZbs^_i0FK+7K73H7nh~)Owe0g{j2UAY^7Rf0R z1nPE)?n`EL;rhcoQ97%OS5ONEsaKTLLGZ|NH(1fi1^NZKypd`b0AVu(;N7(YwD*fN f0_oeeKd@=ChqiNG4~EY_hLwG*Bv~PD5cIzQFzYIQ diff --git a/favicon.png b/favicon.png deleted file mode 100644 index 6fecf95c56c09e262bd03e512107d54c2855a505..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3141 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 -

    Blog Posts

    -
      - {% for post in site.posts %} -
    • {{ post.date | date_to_string }} »{{ post.title }}
    • - {% endfor %} -
    -
    diff --git a/keybase.txt b/keybase.txt deleted file mode 100644 index a8fde5358..000000000 --- a/keybase.txt +++ /dev/null @@ -1,82 +0,0 @@ -================================================================== -https://keybase.io/dberkom --------------------------------------------------------------------- - -I hereby claim: - - * I am an admin of http://blog.danielberkompas.com - * I am dberkom (https://keybase.io/dberkom) on keybase. - * I have a public key with fingerprint 3231 92F3 2380 DF70 97E9 DB82 A53C DADD 426D 840C - -To claim this, I am signing this object: - -{ - "body": { - "client": { - "name": "keybase.io node.js client", - "version": "0.7.5" - }, - "key": { - "fingerprint": "323192f32380df7097e9db82a53cdadd426d840c", - "host": "keybase.io", - "key_id": "A53CDADD426D840C", - "uid": "be52cabab60d492928e419f25779a300", - "username": "dberkom" - }, - "merkle_root": { - "ctime": 1423757063, - "hash": "e935a1a4c73383f76d3986159a976dd02030bf904f8d62002c5922302a0b3a0ae95346b0253b6bce0347f2471851894c7386c99f58306bd40f400beace5ded59", - "seqno": 154402 - }, - "service": { - "hostname": "blog.danielberkompas.com", - "protocol": "http:" - }, - "type": "web_service_binding", - "version": 1 - }, - "ctime": 1423757196, - "expire_in": 157680000, - "prev": "484cb266e060d94b9f0547581fdbe81a660fbfe03b962c9ea3a5f05764434f18", - "seqno": 6, - "tag": "signature" -} - -which yields the PGP signature: - ------BEGIN PGP MESSAGE----- -Comment: GPGTools - https://gpgtools.org - -owFVkn9QFGUYx09A5jw4ZczSElMWKGe8ud79ve+pFXqakYyO0YwGcu6Pd+82727P -2+OQLi1EVJRGSEwDkmkENXOGgqYsZkzSDMcfaNrgkL+mDHE0FA1DSOtdoyn3n312 -38/zfZ7v87yV9niLbUTjjK4LswJrk0ccuyZZcs93VMQISVeKCVeMkP0aCkbMKCgG -EOEilqNiSTSQU9OnBHUFOd8wpgwzDiKKwoamBzEFnLyTJVY5TNxMVrWgF4VDYc3U -ImiKJiGl4pcAFJUHkEdQkQRKZGlZERWFoThFYICMJX26EXmkKvFQ06Mp+G8WS892 -Z7ndmHdjfjY+K3x4ICGWkkVJlDigMJCClIAYEqoUy/NQpAEwQQOFhy0pEgov1wNm -uwEc+ZEnrOsPPcsRzSRIhqJ5lgccjRsSDR/OQZBmRVJkZJ6mBVrlOYWGAkeyUIQ4 -VgAFaCCpEDCqoHAUAJTMQoqiASUCiRaBiCBLM5wEKJaWOElGgGZ4lWJ4UmBJAZqy -AidDqLICDThJYYDKACAhUUasghQWYgcGWhHUcXMswwBqlfkdjmoyMvs2pzZsTvLr -XqciBjXk/8dmSDScMnbrIEJhPaLLuh9Tvkgk5DIHsD9SHDLTipDkGRb0SFpQwfv7 -/4JJjD46HBJyDgKtDGlh5NFMguU5AeDHrIOiWJIRGFmiOA4BvBXISFAFLMOzAqni -BQikyHFAlVQ8CQlylAyRSIssRniOYWhGJYX/LONKEdGLJQ3NGxQjhWFErCqPS02w -jLBZEkfGmTfYYhuV8u+1vvdhimXzzRvWpJ6BQethZml3anG2Mnfrrw1FRxp2rU0q -HX2qiiyjx1d1nvd99H7Z6J+OJz5Wl/HgfP701Dl5a6adG9kY12Wbs+x6QF0RHWe5 -Ir9ST5Y8O/iM4+BnibUTv720fFbrm973FuhlT83ssVufW//gmqu+0OiNTWkbVxFq -7yudsL/3RmHSofKsDtuZkZc2D11Je7fnYu9LStO8085ow+sXpt4cv+Uivyht3eE7 -+XvyXLA6I3nahB1HfR2ryc7u6/avUkBu3K1uR/bUrWMdof4fm2uEk1njfm78ZO/x -zLYnCIToxoGVe6p/2ZLpvn6yeaaj6dVt/tuXr5YmpBeF28oXvTD9bMnpg3BfQ3/6 -azPOFqaXrLz5/BcDSkZo8dEJNZfeack40tZU8Odde/Ku6pox9LnfL2/+rSqmP5m8 -bXDM9lZ4p77+zttlYtbQ48uoSWOuveW93dt+xtoqFLzYeiCno6Rgp9B8f/exhUU9 -3y3ZcLTOZomPuX2Ga/7qRQnl23tOnRh77uSB6iufpzEBF/x0YfDuZCI7s3JuRXns -j5B1SemF+5Xzsr+UFg9V5vgiS4/3xQ9027fY+vIH+4OjcuraN+291aRPbjnkOnaj -7+mNu/8qsefl5lS5TtT2Rzq/31EXnZHa7Pl4X62xvsvevOHsXXZBSy4zn/j6g3XZ -STs3/nBV+4a8Z4yPn1RpfbljzeiJfwM= -=1w8N ------END PGP MESSAGE----- - -And finally, I am proving ownership of this host by posting or -appending to this document. - -View my publicly-auditable identity here: https://keybase.io/dberkom - -================================================================== diff --git a/logo.jpg b/logo.jpg deleted file mode 100644 index 849b24f57093f2f2025f206909e106388959d71a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7099 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[View Changelog](https://github.com/danielberkompas/danielberkompas.github.io/commits/master/personal-development-plan/index.md) -
    - -## Vision -While I am interested in other types of programming, I plan to focus on my -current forte in 2015: web programming. My personal development plan in 2015 -assumes the following about the future of web technology: - -- Customers will continue to demand more interactivity. Javascript frameworks - will therefore become even more prevalent. - -- Scalability will become a dominant factor in web framework choice. This is - because successful businesses will have to serve ever increasing volumes of - traffic. - -- New platforms will use the web as a backbone. For example, the "Internet of - Things" will continue to grow. These new clients will have a strong emphasis - on real-time data. - -- 100% uptime will become extremely important. - -- Maintainability will become a focus for businesses investing in technology. - Recycling their investments every two or three years will become unappealing - as silly investments fail. The tech bubble will pop. - -The plan has three parts. "Concepts" are programming ideas I want to get a more -solid grasp of, "Tools" are libraries and languages I'm interested in learning, -and "Projects" are ideas I plan to implement during the year. - -## Concepts - -**Algorithms** -Most web development is just putting a pretty face on a SQL database. -However, with a deep understanding of algorithms, much harder problems can be -solved, and a different kind of innovation becomes possible. Being self-taught, -I haven't given as much attention to this before as I ought to. - -[![Algorithms Manual][algorithms-manual-img]][algorithms-manual] - -**Regular Expressions** -Regular expressions are extremely powerful. Many times, it is possible to -implement a feature with a language-agnostic regular expression rather than -using some specific feature of a specific language. As a result, Regex knowledge -is both _useful_ and _very portable_. In an age where programming languages are -a dime a dozen, I'd really like to have as much permanent knowledge as possible. - -[![Mastering Regular Expressions][mastering-regex-img]][mastering-regex] - - -## Tools - -- [Elixir][elixir]: A relatively new functional programming language written on - top of the Erlang VM, with a Ruby-esque syntax and strong concurrency, - stability, tooling, and metaprogramming. - -
    [![Metaprogramming Elixir][m-elixir-img]][m-elixir]
    -
    [![Programming Elixir][p-elixir-img]][p-elixir]
    -
    - -- [Phoenix][phoenix]: An exciting Elixir web framework with excellent - performance and real-time features. - -- [Ember.js][emberjs] and [Ember CLI][embercli]. With Ember 2.0, I'm pretty - confident that Ember.js will be a good Javascript framework to spend time and - energy learning. The team is in this for the long term. - -- **SQL**. Similar to regular expressions, SQL can also be used to solve a vast - array of data analysis problems in a language-agnostic way. This skill is - portable between languages and database systems, and is often a more elegant - way to solve problems. My knowledge here isn't as broad or as deep as I would - like. - -## Projects - -- **"OnSale"**. A [Phoenix][phoenix] app capable of monitoring prices - of products on any website and sending notifications when prices change. - Contains a significant Javascript component as well. - [in-progress] - -- **[LeadSimple][leadsimple]**. Render customer UI with Ember.js as a single, - unified Ember CLI app, rather than the current hybrid approach. - [unstarted] - -## Completed Projects - -- **[ExTwiml][extwiml]**. An Elixir library for rendering TwiML for Twilio. -- **[ExTwilio][extwilio]**. An Elixir library for interacting with Twilio's API. -- **[Telephonist][telephonist]**. An Elixir library for handling Twilio phone calls - using state machines. Depends on [ExTwiml][extwiml] and complements - [ExTwilio][extwilio]. -- **[Phoenix Routes][phoenix-routes]**. A toy [Phoenix][phoenix] app to test - Phoenix routing code and see the resulting routes immediately. - -[leadsimple]: http://leadsimple.com -[phoenix-routes]: http://phoenixroutes.herokuapp.com/ -[extwiml]: https://github.com/danielberkompas/ex_twiml -[extwilio]: https://github.com/danielberkompas/ex_twilio -[telephonist]: https://github.com/danielberkompas/telephonist -[algorithms-manual-img]: /assets/img/algorithms-manual.jpg -[algorithms-manual]: http://www.amazon.com/gp/product/1848000693/ -[mastering-regex]: http://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/0596528124 -[mastering-regex-img]: /assets/img/mastering-regex.jpg -[p-elixir]: https://pragprog.com/book/elixir/programming-elixir -[p-elixir-img]: https://imagery.pragprog.com/products/361/elixir_xlargecover.jpg?1368724397 -[m-elixir]: https://pragprog.com/book/cmelixir/metaprogramming-elixir -[m-elixir-img]: https://imagery.pragprog.com/products/430/cmelixir_xlargecover.jpg?1415371472 -[embercli]: http://ember-cli.com -[emberjs]: http://emberjs.com -[elixir]: http://elixir-lang.org -[phoenix]: http://phoenixframework.org diff --git a/site.json b/site.json deleted file mode 100644 index e449ad57e..000000000 --- a/site.json +++ /dev/null @@ -1,11 +0,0 @@ ---- ---- -[ -{% for item in site.posts %} -{"date": {{ item.date | date_to_rfc822 | jsonify }}, -"categories": {{ item.categories | jsonify }}, -"tags": {{ item.tags | jsonify}}, -"title": {{ item.title | jsonify}}, -"url": {{ item.url | jsonify}}}, -{% endfor %} -false]