From 966a3248a4107739cbf4cd6af99555d557aa4d35 Mon Sep 17 00:00:00 2001 From: Correl Roush Date: Thu, 27 Jun 2019 18:22:29 -0400 Subject: [PATCH] Add generated markdown This will be removed once travis-ci can take care of running emacs & ox-hugo. --- ...mating-my-apartment-with-home-assistant.md | 302 +++++++ content/blog/birthday-puzzle.md | 210 +++++ .../cleaner-recursive-http-with-elm-tasks.md | 226 +++++ content/blog/coders-at-work.md | 252 ++++++ content/blog/erlang-the-movie.md | 13 + .../blog/getting-organized-with-org-mode.md | 164 ++++ content/blog/git-graphs.md | 796 ++++++++++++++++++ content/blog/hue-wake-up.md | 381 +++++++++ ...earning-functional-programming-part-one.md | 116 +++ content/blog/meh-php.md | 42 + content/blog/org-publish-with-theme.md | 30 + content/blog/potatoes-and-portal-guns.md | 14 + .../blog/recursive-http-requests-with-elm.md | 329 ++++++++ content/blog/sicp.md | 76 ++ content/blog/syncing.md | 85 ++ content/blog/transmission-rss-and-xbmc.md | 118 +++ content/blog/types-in-python.md | 18 + 17 files changed, 3172 insertions(+) create mode 100644 content/blog/automating-my-apartment-with-home-assistant.md create mode 100644 content/blog/birthday-puzzle.md create mode 100644 content/blog/cleaner-recursive-http-with-elm-tasks.md create mode 100644 content/blog/coders-at-work.md create mode 100644 content/blog/erlang-the-movie.md create mode 100644 content/blog/getting-organized-with-org-mode.md create mode 100644 content/blog/git-graphs.md create mode 100644 content/blog/hue-wake-up.md create mode 100644 content/blog/learning-functional-programming-part-one.md create mode 100644 content/blog/meh-php.md create mode 100644 content/blog/org-publish-with-theme.md create mode 100644 content/blog/potatoes-and-portal-guns.md create mode 100644 content/blog/recursive-http-requests-with-elm.md create mode 100644 content/blog/sicp.md create mode 100644 content/blog/syncing.md create mode 100644 content/blog/transmission-rss-and-xbmc.md create mode 100644 content/blog/types-in-python.md diff --git a/content/blog/automating-my-apartment-with-home-assistant.md b/content/blog/automating-my-apartment-with-home-assistant.md new file mode 100644 index 0000000..8af9a35 --- /dev/null +++ b/content/blog/automating-my-apartment-with-home-assistant.md @@ -0,0 +1,302 @@ ++++ +title = "Automating My Apartment With Home Assistant" +author = ["Correl Roush"] +date = 2019-06-27T18:13:00-04:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["home-automation"] +draft = false ++++ + +A while ago, I [posted about]({{< relref "hue-wake-up.md" >}}) my experiments with the Phillips Hue API +to create an automated morning sunrise effect. The end result was +nice, but all that mucking about with their HTTP APIs was a hassle any +time I wanted to tweak something. I wanted to define what I wanted in +a more declarative style, and have all the API calls managed behind +the scenes. [Home Assistant](https://www.home-assistant.io/) allowed me to do exactly that, and more. + +While the Home Assistant docs are geared heavily towards setting up a +raspberry pi appliance to run everything 24/7, I don't own one, and I +already have a server going. I opted instead to get the home assistant +server running using [Docker](https://www.home-assistant.io/docs/installation/docker/), and setting up a git repository to hold +my configuration. + + +## A Brand New Day {#a-brand-new-day} + +Setting up my sunrise was actually _really_ easy. I already had the +scenes I wanted from my [previous attempt]({{< relref "hue-wake-up.md" >}}), so it was just a matter of +codifying them in the YAML config. I split them into four scenes - a +start (dawn) and end (daylight) pair for the standing lamp at the wall +beyond the foot of the bed, and a pair for the two nightstand lights. +The end scenes include the transition time to fade in (30 minutes). + +```yaml +scene: + - name: Dawn Sun + entities: + light.standing_lamp: + state: on + brightness: 1 + xy_color: [0.6042, 0.3739] + - name: Dawn Daylight + entities: + light.correls_nightstand: + state: on + brightness: 1 + xy_color: [0.2376, 0.1186] + light.stephanies_nightstand: + state: on + brightness: 1 + xy_color: [0.2376, 0.1186] + - name: Sunrise Sun + entities: + light.standing_lamp: + state: on + transition: 1800 + brightness: 254 + xy_color: [0.3769, 0.3639] + - name: Sunrise Daylight + entities: + light.correls_nightstand: + state: on + transition: 1800 + brightness: 203 + xy_color: [0.2698, 0.295] + light.stephanies_nightstand: + state: on + transition: 1800 + brightness: 203 + xy_color: [0.2698, 0.295] +``` + +Breaking them apart this way means I can trigger the "sun" first for a +splash of orange, then start up the nightstand "daylight" lights a +little bit later! This worked out well, too, since even at the lowest +brightness, having them turn on right at the start when the room is +totally dark had a tendency to jolt me awake. Staggering them produces +a much gentler effect. Scripting all of this took very little work... + +```yaml +script: + sunrise: + alias: Sunrise + sequence: + - service: scene.turn_on + data: + entity_id: scene.dawn_sun + - service: scene.turn_on + data: + entity_id: scene.sunrise_sun + - delay: + seconds: 180 + - service: scene.turn_on + data: + entity_id: scene.dawn_daylight + - service: scene.turn_on + data: + entity_id: scene.sunrise_daylight +``` + +... and the end result really is quite pleasant: + + + +
+
+ +![](/ox-hugo/ha-lights-1.png) +![](/ox-hugo/ha-lights-2.png) +![](/ox-hugo/ha-lights-3.png) + +
+ +That just leaves the automation, which fires a half an hour before the +_actual_ sunrise, so long as the lights aren't already on and somebody +is home (using a binary sensor I defined elsewhere based on phones +detected in the house plus an override toggle). + +```yaml +automation: + - alias: Sunrise + action: + - service: script.sunrise + data: {} + trigger: + - platform: sun + event: sunrise + offset: '-00:30:00' + condition: + - condition: state + entity_id: binary_sensor.occupied + state: 'on' + - condition: state + entity_id: group.bedroom_lights + state: 'off' +``` + +I later extended the automation with some configuration inputs, which +tie into some new triggers and conditions. I added a "latest start +time" to make sure it always gets me up in time for me to get ready +for work, and an option to disable the wake-up on weekends. + +```yaml +input_select: + sunrise_days: + name: Days to wake up + options: + - Every Day + - Weekdays + initial: Every Day + icon: mdi:weather-sunset +input_datetime: + sunrise_time: + name: Latest start time + has_date: false + has_time: true + initial: '06:30' +automation: + - alias: Sunrise + action: + - service: script.sunrise + data: {} + trigger: + - platform: sun + event: sunrise + offset: '-00:30:00' + - platform: template + value_template: >- + {{ states('sensor.time') == ( + states.input_datetime.sunrise_time.attributes.timestamp + | int | timestamp_custom('%H:%M', False) + ) + }} + condition: + - condition: state + entity_id: binary_sensor.occupied + state: 'on' + - condition: state + entity_id: group.bedroom_lights + state: 'off' + - condition: or + conditions: + - condition: state + entity_id: input_select.sunrise_days + state: Every Day + - condition: and + conditions: + - condition: state + entity_id: input_select.sunrise_days + state: Weekdays + - condition: time + weekday: + - mon + - tue + - wed + - thu + - fri +``` + +Sprinkle in some groups, and I've got a nice panel in my Home +Assistant UI to manage everything: + +{{< figure src="/images/ha-sunrise-ui.png" caption="Figure 1: The completed sunrise panel" >}} + + +## Keep It Down! {#keep-it-down} + +Determined to find more things to automate, I realized that since I +have my TV audio going through a Sonos sound bar, I could very easily +automate the rather annoying ritual of leaping for the app on my phone +to turn on night mode when a movie I'm watching is getting explodey +and I realize it's a bit late in the evening to be shaking my +neighbor's walls. + +```yaml +automation: + - alias: Toggle Sonos night mode + action: + - service: media_player.sonos_set_option + entity_id: media_player.den + data_template: + night_sound: >- + {{ now().hour >= 22 }} + trigger: + - platform: time + at: '22:30:00' + - platform: time + at: '08:00:00' +``` + +Boom. Happier neighbors, and I can fall asleep in front of movies +without worry! + +Just because I could, I also added some configurability to this +automation as well. The logic got a bit tricky, since I wanted to +configure a window that crosses a 24-hour boundary. I also added a +binary sensor so I could see when night mode was enabled from Home +Assistant. + +```yaml +automation: + - alias: Toggle Sonos night mode + action: + - service: media_player.sonos_set_option + entity_id: media_player.den + data_template: + night_sound: >- + {% set start = states.input_datetime.sonos_nightmode_start.attributes %} + {% set end = states.input_datetime.sonos_nightmode_end.attributes %} + {% set now_ = (now().hour, now().minute, now().second) %} + {% set start_ = (start.hour, start.minute, start.second) %} + {% set end_ = (end.hour, end.minute, end.second) %} + {% if start_ > end_ -%} + {{ now_ >= start_ or now_ < end_ }} + {%- else -%} + {{ now_ >= start_ and now_ < end_ }} + {%- endif -%} + trigger: + - platform: template + value_template: "{{ states('sensor.time') == (states.input_datetime.sonos_nightmode_start.attributes.timestamp | int | timestamp_custom('%H:%M', False)) }}" + - platform: template + value_template: "{{ states('sensor.time') == (states.input_datetime.sonos_nightmode_end.attributes.timestamp | int | timestamp_custom('%H:%M', False)) }}" +sensor: + - platform: time_date + display_options: + - time +input_datetime: + sonos_nightmode_start: + name: Start Night Mode + has_date: false + has_time: true + initial: '22:30' + sonos_nightmode_end: + name: End Night Mode + has_date: false + has_time: true + initial: '08:00' +binary_sensor: + - platform: template + sensors: + den_night_mode: + friendly_name: Sonos Den Night Mode + value_template: >- + {{ state_attr('media_player.den', 'night_sound') }} +``` + +And, voilà, a dashboard for my speakers, which I pretty much never +need to look at anymore! + +{{< figure src="/images/ha-sonos-ui.png" >}} + + +## But Wait, There's More! {#but-wait-there-s-more} + +It's a too much to cover in a single blog post, but there's plenty +more going on in my config. Over time, I've tweaked and added to my +device tracking to make sure Home Assistant knows when someone's home. +I set up some text-to-speech to announce the weather in the morning, +and welcome the first person to get home. I even re-purposed an old +phone as a webcam so I can check on the cat while I'm out. My config +is on my personal gitlab server, feel free to check it out and see if +there's anything there you can use or learn from: + diff --git a/content/blog/birthday-puzzle.md b/content/blog/birthday-puzzle.md new file mode 100644 index 0000000..2ebe992 --- /dev/null +++ b/content/blog/birthday-puzzle.md @@ -0,0 +1,210 @@ ++++ +title = "Birthday Puzzle" +author = ["Correl Roush"] +date = 2015-04-18T00:00:00-04:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming", "prolog"] +draft = false ++++ + +This logic puzzle has been floating around the internet lately. When I +caught wind of it, I thought it would be a great exercise to tackle +using Prolog. I'm not especially good with the language yet, so it +added to the challenge a bit, but it was a pretty worthwhile +undertaking. When I got stumped, I discovered that mapping out the +birthdays into a grid helped me visualize the problem and ultimately +solve it, so I've included that with my prolog code so you can see how +I arrived at the answer. + + +## The Puzzle {#the-puzzle} + +Albert and Bernard have just met Cheryl. “When is your birthday?” +Albert asked Cheryl. Cheryl thought for a moment and said, “I won’t +tell you, but I’ll give you some clues”. She wrote down a list of +ten dates: + +- May 15, May 16, May 19 +- June 17, June 18 +- July 14, July 16 +- August 14, August 15, August 17 + +“One of these is my birthday,” she said. + +Cheryl whispered in Albert’s ear the month, and only the month, of +her birthday. To Bernard, she whispered the day, and only the +day. “Can you figure it out now?” she asked Albert. + +Albert: “I don’t know when your birthday is, but I know Bernard +doesn’t know, either.” + +Bernard: “I didn’t know originally, but now I do.” + +Albert: “Well, now I know, too!” + +_When is Cheryl’s birthday?_ + + +## The Solution {#the-solution} + + +### The Dates {#the-dates} + +To start off, i entered each of the possible birthdays as facts: + +```prolog +possible_birthday(may, 15). +possible_birthday(may, 16). +possible_birthday(may, 19). +possible_birthday(june, 17). +possible_birthday(june, 18). +possible_birthday(july, 14). +possible_birthday(july, 16). +possible_birthday(august, 14). +possible_birthday(august, 15). +possible_birthday(august, 17). +``` + +And here they are, mapped out in a grid: + +| | May | June | July | August | +|----|:---:|:----:|:----:|:------:| +| 14 | | | X | X | +| 15 | X | | | X | +| 16 | X | | X | | +| 17 | | X | | X | +| 18 | | X | | | +| 19 | X | | | | + + +### Albert's Statement {#albert-s-statement} + +> I don’t know when your birthday is,... + +Albert only knows the month, and the month isn't enough to uniquely +identify Cheryl's birthday. + +```prolog +month_is_not_unique(M) :- + bagof(D, possible_birthday(M, D), Days), + length(Days, Len), + Len > 1. +``` + +> ... but I know Bernard doesn’t know, either. + +Albert knows that Bernard doesn't know Cheryl's +birthday. Therefore, the day alone isn't enough to know Cheryl's +birthday, and we can infer that the month of Cheryl's birthday does +not include any of the unique dates. + +```prolog +day_is_not_unique(D) :- + bagof(M, possible_birthday(M, D), Months), + length(Months, Len), + Len > 1. + +month_has_no_unique_days(M) :- + forall(possible_birthday(M,D), + day_is_not_unique(D)). +``` + +Based on what Albert knows at this point, let's see how we've +reduced the possible dates: + +```prolog +part_one(M,D) :- + possible_birthday(M,D), + month_is_not_unique(M), + month_has_no_unique_days(M), + day_is_not_unique(D). +``` + +```text +Results = [ (july, 14), (july, 16), (august, 14), (august, 15), (august, 17)]. +``` + +So the unique days (the 18th and 19th) are out, as are the months +that contained them (May and June). + +| | July | August | +|----|:----:|:------:| +| 14 | X | X | +| 15 | | X | +| 16 | X | | +| 17 | | X | + + +### Bernard's Statement {#bernard-s-statement} + +> I didn’t know originally, but now I do. + +For Bernard to know Cheryl's birthday, the day he knows must be +unique within the constraints we have so far. + +```prolog +day_is_unique(Month, Day) :- + findall(M, part_one(M, Day), [Month]). +part_two(Month, Day) :- + possible_birthday(Month, Day), + day_is_unique(Month, Day). +``` + +```text +Results = [ (july, 16), (august, 15), (august, 17)]. +``` + +Both July and August contain the 14th, so that row is out. + +| | July | August | +|----|------|--------| +| 15 | | X | +| 16 | X | | +| 17 | | X | + + +### Albert's Second Statement {#albert-s-second-statement} + +> Well, now I know, too! + +Albert's month must be the remaining unique month: + +```prolog +month_is_not_unique(Month, Day) :- + findall(D, part_two(Month, D), [Day]). +part_three(Month, Day) :- + possible_birthday(Month, Day), + month_is_not_unique(Month, Day). +``` + +```text +Results = [ (july, 16)]. +``` + +August had two possible days, so it's now clear that the only +possible unique answer is July 16th. + +| | July | +|----|:----:| +| 15 | | +| 16 | X | +| 17 | | + + +### Cheryl's Birthday {#cheryl-s-birthday} + +```prolog +cheryls_birthday(Month, Day) :- + part_three(Month, Day). +``` + +```text +Month = july, +Day = 16. +``` + +So, there we have it. Cheryl's birthday is July 16th! + +| | July | +|----|:----:| +| 16 | X | diff --git a/content/blog/cleaner-recursive-http-with-elm-tasks.md b/content/blog/cleaner-recursive-http-with-elm-tasks.md new file mode 100644 index 0000000..67ac4e7 --- /dev/null +++ b/content/blog/cleaner-recursive-http-with-elm-tasks.md @@ -0,0 +1,226 @@ ++++ +title = "Cleaner Recursive HTTP Requests with Elm Tasks" +author = ["Correl Roush"] +date = 2018-01-23T00:00:00-05:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming", "elm"] +draft = false ++++ + +_Continued from part one, [Recursive HTTP Requests with Elm]({{< relref "recursive-http-requests-with-elm.md" >}})._ + +In [my last post]({{< relref "recursive-http-requests-with-elm.md" >}}), I described my first pass at building a library to +fetch data from a paginated JSON REST API. It worked, but it wasn't +too clean. In particular, the handling of the multiple pages and +concatenation of results was left up to the calling code. Ideally, +both of these concerns should be handled by the library, letting the +application focus on working with a full result set. Using Elm's +Tasks, we can achieve exactly that! + + +## What's a Task? {#what-s-a-task} + +A [Task](http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Task) is a data structure in Elm which represents an asynchronous +operation that may fail, which can be mapped and **chained**. What this +means is, we can create an action, transform it, and chain it with +additional actions, building up a complex series of things to do into +a single `Task`, which we can then package up into a [Cmd](http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Platform-Cmd#Cmd) and hand to +the Elm runtime to perform. You can think of it like building up a +[Future or Promise](https://en.wikipedia.org/wiki/Futures%5Fand%5Fpromises), setting up a sort of [callback](https://en.wikipedia.org/wiki/Callback%5F(computer%5Fprogramming)) chain of mutations +and follow-up actions to be taken. The Elm runtime will work its way +through the chain and hand your application back the result in the +form of a `Msg`. + +So, tasks sound great! + + +## Moving to Tasks {#moving-to-tasks} + +Just to get things rolling, let's quit using `Http.send`, and instead +prepare a simple `toTask` function leveraging the very handy +`Http.toTask`. This'll give us a place to start building up some more +complex behavior. + +```elm +send : + (Result Http.Error (Response a) -> msg) + -> Request a + -> Cmd msg +send resultToMessage request = + toTask request + |> Task.attempt resultToMessage + + +toTask : Request a -> Task Http.Error (Response a) +toTask = + httpRequest >> Http.toTask +``` + + +## Shifting the recursion {#shifting-the-recursion} + +Now, for the fun bit. We want, when a request completes, to inspect +the result. If the task failed, we do nothing. If it succeeded, we +move on to checking the response. If we have a `Complete` response, +we're done. If we do not, we want to build another task for the next +request, and start a new iteration on that. + +All that needs to be done here is to chain our response handling using +`Task.andThen`, and either recurse to continue the chain with the next +`Task`, or wrap up the final results with `Task.succeed`! + +```elm +recurse : + Task Http.Error (Response a) + -> Task Http.Error (Response a) +recurse = + Task.andThen + (\response -> + case response of + Partial request _ -> + httpRequest request + |> Http.toTask + |> recurse + + Complete _ -> + Task.succeed response + ) +``` + +That wasn't so bad. The function recursion almost seems like cheating: +I'm able to build up a whole chain of requests _based_ on the results +without actually _having_ the results yet! The `Task` lets us define a +complete plan for what to do with the results, using what we know +about the data structures flowing through to make decisions and tack +on additional things to do. + + +## Accumulating results {#accumulating-results} + +There's just one thing left to do: we're not accumulating results yet. +We're just handing off the results of the final request, which isn't +too helpful to the caller. We're also still returning our Response +structure, which is no longer necessary, since we're not bothering +with returning incomplete requests anymore. + +Cleaning up the types is pretty easy. It's just a matter of switching +out some instances of `Response a` with `List a` in our type +declarations... + +```elm +send : + (Result Http.Error (List a) -> msg) + -> Request a + -> Cmd msg + + +toTask : Request a -> Task Http.Error (List a) + + +recurse : + Task Http.Error (Response a) + -> Task Http.Error (List a) +``` + +...then changing our `Complete` case to return the actual items: + +```elm +Complete xs -> + Task.succeed xs +``` + +The final step, then, is to accumulate the results. Turns out this is +**super** easy. We already have an `update` function that combines two +responses, so we can map _that_ over our next request task so that it +incorporates the previous request's results! + +```elm +Partial request _ -> + httpRequest request + |> Http.toTask + |> Task.map (update response) + |> recurse +``` + + +## Tidying up {#tidying-up} + +Things are tied up pretty neatly, now! Calling code no longer needs to +care whether the JSON endpoints its calling paginate their results, +they'll receive everything they asked for as though it were a single +request. Implementation details like the `Response` structure, +`update` method, and `httpRequest` no longer need to be exposed. +`toTask` can be exposed now as a convenience to anyone who wants to +perform further chaining on their calls. + +Now that there's a cleaner interface to the module, the example app is +looking a lot cleaner now, too: + +```elm +module Example exposing (..) + +import Html exposing (Html) +import Http +import Json.Decode exposing (field, string) +import Paginated + + +type alias Model = + { repositories : Maybe (List String) } + + +type Msg + = GotRepositories (Result Http.Error (List String)) + + +main : Program Never Model Msg +main = + Html.program + { init = init + , update = update + , view = view + , subscriptions = \_ -> Sub.none + } + + +init : ( Model, Cmd Msg ) +init = + ( { repositories = Nothing } + , getRepositories + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + GotRepositories result -> + ( { model | repositories = Result.toMaybe result } + , Cmd.none + ) + + +view : Model -> Html Msg +view model = + case model.repositories of + Nothing -> + Html.div [] [ Html.text "Loading" ] + + Just repos -> + Html.ul [] <| + List.map + (\x -> Html.li [] [ Html.text x ]) + repos + + +getRepositories : Cmd Msg +getRepositories = + Paginated.send GotRepositories <| + Paginated.get + "http://git.phoenixinquis.net/api/v4/projects?per_page=5" + (field "name" string) +``` + +So, there we have it! Feel free to check out the my complete +`Paginated` library on the [Elm package index](http://package.elm-lang.org/packages/correl/elm-paginated/latest), or on [GitHub](https://github.com/correl/elm-paginated). Hopefully +you'll find it or this post useful. I'm still finding my way around +Elm, so any and all feedback is quite welcome :) diff --git a/content/blog/coders-at-work.md b/content/blog/coders-at-work.md new file mode 100644 index 0000000..a1eab6f --- /dev/null +++ b/content/blog/coders-at-work.md @@ -0,0 +1,252 @@ ++++ +title = "Coders at Work" +author = ["Correl Roush"] +date = 2015-01-28T00:00:00-05:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming", "books"] +draft = false ++++ + +A few days before leaving work for a week and a half of flying and +cruising to escape frigid Pennsylvania, I came across a [Joe Armstrong +quote](#orgfd943c5) during my regularly scheduled slacking off on twitter and Hacker +News. I'd come across a couple times before, only this time I noticed +it had a source link. This led me to discovering (and shortly +thereafter, buying) Peter Seibel's "[Coders at Work -- Reflections on +the Craft of Programming](http://www.codersatwork.com/)". I loaded it onto my nook, and off I went. + +The book is essentially a collection of interviews with a series of +highly accomplished software developers. Each of them has their own +fascinating insights into the craft and its rich history. + +While making my way through the book, I highlighted some excerpts +that, for one reason or another, resonated with me. I've organized and +elaborated on them below. + + +## Incremental Changes {#incremental-changes} + + + +> I've seen young programmers say, "Oh, shit, it doesn't work," and then +> rewrite it all. Stop. Try to figure out what's going on. **Learn how to +> write things incrementally so that at each stage you could verify it.**
+> -- Brad Fitzpatrick + +I can remember doing this to myself when I was still relatively new to +coding (and even worse, before I discovered source control!). Some +subroutine or other would be misbehaving, and rather than picking it +apart and figuring out what it was I'd done wrong, I'd just blow it +away and attempt to write it fresh. While I _might_ be successful, +that likely depended on the issue being some sort of typo or missed +logic; if it was broken because I misunderstood something or had a bad +plan to begin with, rewriting it would only result in more broken +code, sometimes in more or different ways than before. I don't think +I've ever rewritten someone else's code without first at least getting +a firm understanding of it and what it was trying to accomplish, but +even then, breaking down changes piece by piece makes it all the +easier to maintain sanity. + +I do still sometimes catch myself doing too much at once when building +a new feature or fixing a bug. I may have to fix a separate bug that's +in my way, or I may have to make several different changes in various +parts of the code. If I'm not careful, things can get out of hand +pretty quickly, and before I know it I have a blob of changes strewn +across the codebase in my working directory without a clear picture of +what's what. If something goes wrong, it can be pretty tough to sort +out which change broke things (or fixed them). Committing changes +often helps tremendously to avoid this sort of situation, and when I +catch myself going off the rails I try to find a stopping point and +split changes up into commits as soon as possible to regain +control. Related changes and fixes can always be squashed together +afterwards to keep things tidy. + + +## Specifications & Documentation {#specifications-and-documentation} + + + +> **Many customers won't tell you a problem; they'll tell you a +> solution.** A customer might say, for instance, "I need you to add +> support for the following 17 attributes to this system. Then you have +> to ask, 'Why? What are you going to do with the system? How do you +> expect it to evolve?'" And so on. You go back and forth until you +> figure out what all the customer really needs the software to +> do. These are the use cases.
+> -- Joshua Bloch + +Whether your customer is your customer, or your CEO, the point stands: +customers are _really bad_ at expressing what they want. It's hard to +blame them, though; analyzing what you really want and distilling it +into a clear specification is tough work. If your customer is your +boss, it can be intimidating to push back with questions like "Why?", +but if you can get those questions answered you'll end up with a +better product, a better _understanding_ of the product, and a happy +customer. The agile process of doing quick iterations to get tangible +results in front of them is a great way of getting the feedback and +answers you need. + + + +> The code shows me what it _does_. It doesn't show me what it's +> supposed to do. I think the code is the answer to a problem. +> **If you don't have the spec or you don't have any documentation, you have to guess what the problem is from the answer. You might guess wrong.**
+> -- Joe Armstrong + +Once you've got the definition of what you've got to build and how +it's got to work, it's extremely important that you get it +documented. Too often, I'm faced with code that's doing something in +some way that somebody, either a customer or a developer reading it, +takes issue with, and there's no documentation anywhere on why it's +doing what it's doing. What happens next is anybody's guess. Code +that's clear and conveys its intent is a good start towards avoiding +this sort of situation. Comments explaining intent help too, though +making sure they're kept up to date with the code can be +challenging. At the very least, I try to promote useful commit +messages explaining what the purpose of a change is, and reference a +ticket in our issue tracker which (hopefully) has a clear accounting +of the feature or bugfix that prompted it. + + +## Pair Programming {#pair-programming} + + + +> ... **if you don't know what you're doing then I think it can be very +> helpful with someone who also doesn't know what they're doing.** If you +> have one programmer who's better than the other one, then there's +> probably benefit for the weaker programmer or the less-experienced +> programmer to observe the other one. They're going to learn something +> from that. But if the gap's too great then they won't learn, they'll +> just sit there feeling stupid.
+> -- Joe Armstrong + +Pairing isn't something I do much. At least, it's pretty rare that I +have someone sitting next to me as I code. I **do** involve peers while +I'm figuring out what I want to build as often as I can. The tougher +the problem, the more important it is, I think, to get as much +feedback and brainstorming in as possible. This way, everybody gets to +tackle the problem and learn together, and anyone's input, however +small it might seem, can be the key to the "a-ha" moment to figuring +out a solution. + + +## Peer Review {#peer-review} + + + +> **I think an hour of code reading is worth two weeks of QA.** It's just +> a really effective way of removing errors. If you have someone who is +> strong reading, then the novices around them are going to learn a lot +> that they wouldn't be learning otherwise, and if you have a novice +> reading, he's going to get a lot of really good advice.
+> -- Douglas Crockford + +Just as important as designing the software as a team, I think, is +reviewing it as a team. In doing so, each member of the team has an +opportunity to understand _how_ the system has been implemented, and +to offer their suggestions and constructive criticisms. This helps the +team grow together, and results in a higher quality of code overall. +This benefits QA as well as the developers themselves for the next +time they find themselves in that particular bit of the system. + + +## Object-Oriented Programming {#object-oriented-programming} + + + +> I think the lack of reusability comes in object-oriented languages, +> not in functional languages. +> **Because the problem with object-oriented languages is they've got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.**
+> -- Joe Armstrong + +A lot has been written on why OOP isn't the great thing it claims to +be, or was ever intended to be. Having grappled with it myself for +years, attempting to find ways to keep my code clean, concise and +extensible, I've more or less come to the same conclusion as Armstrong +in that coupling data structures with behaviour makes for a terrible +mess. Dividing the two led to a sort of moment of clarity; there was +no more confusion about what methods belong on what object. There was +simply the data, and the methods that act on it. I am still struggling +a bit, though, on how to bring this mindset to the PHP I maintain at +work. The language seems particularly ill-suited to managing complex +data structures (or even simple ones -- vectors and hashes are +bizarrely intertwined). + + +## Writing {#writing} + + + +> You should read _[Elements of Style]_ for two reasons: The first is +> that a large part of every software engineer's job is writing +> prose. **If you can't write precise, coherent, readable specs, nobody +> is going to be able to use your stuff.** So anything that improves your +> prose style is good. The second reason is that most of the ideas in +> that book are also applicable to programs.
+> -- Joshua Bloch + + + +> **My advice to everybody is pretty much the same, to read and write.**
+> ...
+> Are you a good Java programmer, a good C programmer, or whatever? I +> don't care. I just want to know that you know how to put an algorithm +> together, you understand data structures, and you know how to document +> it.
+> -- Douglas Crockford + + + +> This is what literate programming is so great for --
+> **I can talk to myself. I can read my program a year later and know +> exactly what I was thinking.**
+> -- Donald Knuth + +The more I've program professionally, the clearer it is that writing +(and communication in general) is a very important skill to +develop. Whether it be writing documentation, putting together a +project plan, or whiteboarding and discussing something, clear and +concise communication skills are a must. Clarity in writing translates +into clarity in coding as well, in my opinion. Code that is short, to +the point, clear in its intention, making good use of structure and +wording (in the form of function and variable names) is far easier to +read and reason about than code that is disorganized and obtuse. + + +## Knuth {#knuth} + + + +> I tried to make familiarity with Knuth a hiring criteria, and I was +> disappointed that I couldn't find enough people that had read him. In +> my view, +> **anybody who calls himself a professional programmer should have read +> Knuth's books or at least should have copies of his books.**
+> -- Douglas Crockford + + + +> ... Knuth is really good at telling a story about code. When you read +> your way through _The Art of Computer Programming_ and you read your +> way through an algorithm, he's explained it to you and showed you some +> applications and given you some exercises to work, and **you feel like +> you've been led on a worthwhile journey.**
+> -- Guy Steele + + + +> At one point I had _[The Art of Computer Programming]_ as my monitor +> stand because it was one of the biggest set of books I had, and it was +> just the right height. That was nice because it was always there, and +> I guess then I was more prone to use it as a reference because it was +> right in front of me.
+> -- Peter Norvig + +I haven't read any of Knuth's books yet, which is something I'll have +to rectify soon. I don't think I have the mathematical background +necessary to get through some of his stuff, but I expect it will be +rewarding nonetheless. I'm also intrigued by his concept of literate +programming, and I'm curious to learn more about TeX. I imagine I'll +be skimming through [TeX: The Program](http://brokestream.com/tex-web.html) pretty soon now that I've +finished Coders at Work :) diff --git a/content/blog/erlang-the-movie.md b/content/blog/erlang-the-movie.md new file mode 100644 index 0000000..a20e74f --- /dev/null +++ b/content/blog/erlang-the-movie.md @@ -0,0 +1,13 @@ ++++ +title = "Erlang: The Movie" +author = ["Correl Roush"] +date = 2013-11-27T00:00:00-05:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming", "erlang"] +draft = false ++++ + +Hopping through [Joe Armstrong's blog](http://joearms.github.io/), I happened across Erlang: The Movie. +More programming languages need videos like this. + + diff --git a/content/blog/getting-organized-with-org-mode.md b/content/blog/getting-organized-with-org-mode.md new file mode 100644 index 0000000..33d4212 --- /dev/null +++ b/content/blog/getting-organized-with-org-mode.md @@ -0,0 +1,164 @@ ++++ +title = "Getting Organized with Org Mode" +author = ["Correl Roush"] +date = 2014-11-25T00:00:00-05:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["emacs", "org-mode", "git", "graphviz"] +draft = false ++++ + +Org Mode logo + +I've been using Emacs Org mode for nearly a year now. For a while I +mostly just used it to take and organize notes, but over time I've +discovered it's an incredibly useful tool for managing projects and +tasks, writing and publishing documents, keeping track of time and +todo lists, and maintaining a journal. + + +## Project Management {#project-management} + +Most of what I've been using [Org mode](http://orgmode.org/) for has been breaking down large +projects at work into tasks and subtasks. It's really easy to enter +projects in as a hierarchy of tasks and task groupings. Using +[Column View](http://orgmode.org/worg/org-tutorials/org-column-view-tutorial.html), I was able to dive right into scoping them individually +and reporting total estimates for each major segment of work. + +{{< figure src="/images/emacs-projects.png" alt="Example projects org file" >}} + +Because Org Mode makes building and modifying an outline structure +like this so quick and easy, I usually build and modify the project +org document while planning it out with my team. Once done, I then +manually load that information into our issue tracker and get +underway. Occasionally I'll also update tags and progress status in +the org document as well as the project progresses, so I can use the +same document to plan subsequent development iterations. + + +## Organizing Notes and Code Exercises {#organizing-notes-and-code-exercises} + +More recently, I've been looking into various ways to get more +things organized with Org mode. I've been stepping through +[Structure and Interpretation of Computer Programs](http://sarabander.github.io/sicp/) with some other +folks from work, and discovered that Org mode was an ideal fit for +keeping my notes and exercise work together. The latter is neatly +managed by [Babel](http://orgmode.org/worg/org-contrib/babel/intro.html), which let me embed and edit source examples and +my excercise solutions right in the org document itself, and even +export them to one or more scheme files to load into my +interpreter. + + +## Exporting and Publishing Documents {#exporting-and-publishing-documents} + +Publishing my notes with org is also a breeze. I've published +project plans and proposals to PDF to share with colleagues, and +exported my [SICP notes](https://github.com/correl/sicp) to html and [dropped them into a site](http://sicp.phoenixinquis.net/) built +with [Jekyll](http://jekyllrb.com/). Embedding graphs and diagrams into exported documents +using [Graphviz](http://www.graphviz.org/), [Mscgen](http://www.mcternan.me.uk/mscgen/), and [PlantUML](http://plantuml.sourceforge.net/) has also really helped with +putting together some great project plans and documentation. A lot of +great examples using those tools (and more!) can be found [here](http://home.fnal.gov/~neilsen/notebook/orgExamples/org-examples.html). + + +## Emacs Configuration {#emacs-configuration} + +While learning all the cool things I could do with Org mode and Babel, +it was only natural I'd end up using it to reorganize my [Emacs +configuration](https://github.com/correl/dotfiles/tree/master/.emacs.d). Up until that point, I'd been managing my configuration +in a single init.el file, plus a directory full of mode or +purpose-specific elisp files that I'd loop through and load. Inspired +primarily by the blog post, ["Making Emacs Work For Me"](http://zeekat.nl/articles/making-emacs-work-for-me.html), and later by +others such as [Sacha Chua's Emacs configuration](http://pages.sachachua.com/.emacs.d/Sacha.html), I got all my configs +neatly organized into a single org file that gets loaded on +startup. I've found it makes it far easier to keep track of what I've +got configured, and gives me a reason to document and organize things +neatly now that it's living a double life as a [published document](https://github.com/correl/dotfiles/blob/master/.emacs.d/emacs.org) on +GitHub. I've still got a directory lying around with autoloaded +scripts, but now it's simply reserved for [tinkering and sensitive +configuration](https://github.com/correl/dotfiles/blob/master/.emacs.d/emacs.org#auto-loading-elisp-files). + + +## Tracking Habits {#tracking-habits} + +Another great feature of Org mode that I've been taking advantage +of a lot more lately is the [Agenda](http://orgmode.org/manual/Agenda-Views.html). By defining some org files as +being agenda files, Org mode can examine these files for TODO +entries, scheduled tasks, deadlines and more to build out useful +agenda views to get a quick handle on what needs to be done and +when. While at first I started by simply syncing down my google +calendars as org-files (using [ical2org.awk](http://orgmode.org/worg/code/awk/ical2org.awk)), I've started +managing TODO lists in a dedicated org file. By adding tasks to +this file, scheduling them, and setting deadlines, I've been doing +a much better job of keeping track of things I need to get done +and (even more importantly) _when_ I need to get them done. + +{{< figure src="/images/emacs-org-agenda.png" alt="Agenda view snippet" >}} + +This works not only for one-shot tasks, but also [habits and other +repetitive tasks](http://orgmode.org/manual/Tracking-your-habits.html). It's possible to schedule a task that should be +done every day, every few days, or maybe every first sunday of a +month. For example, I've set up repeating tasks to write a blog +post at least once a month, practice guitar every two to three +days, and to do the dishes every one or two days. The agenda view +can even show a small, colorized graph next to each repeating task +that paints a picture of how well (or not!) I've been getting +those tasks done on time. + + +## Keeping a Journal and Tracking Work {#keeping-a-journal-and-tracking-work} + +The last thing I've been using (which I'm still getting a handle +on) is using [Capture](http://orgmode.org/manual/Capture.html) to take and store notes, keep a journal, and +even [track time on tasks at work](http://orgmode.org/manual/Clocking-work-time.html). + +```emacs-lisp +(setq org-capture-templates + '(("j" "Journal Entry" plain + (file+datetree "~/org/journal.org") + "%U\n\n%?" :empty-lines-before 1) + ("w" "Log Work Task" entry + (file+datetree "~/org/worklog.org") + "* TODO %^{Description} %^g\n%?\n\nAdded: %U" + :clock-in t + :clock-keep t))) + +(global-set-key (kbd "C-c c") 'org-capture) + +(setq org-clock-persist 'history) +(org-clock-persistence-insinuate) +``` + +For my journal, I've configured a capture template that I can use +to write down a new entry that will be stored with a time stamp +appended into its own org file, organized under headlines by year, +month and date. + +For work tasks, I have another capture template configured that +will log and tag a task into another org file, also organized by +date, which will automatically start tracking time for that +task. Once done, I can simply clock out and check the time I've +spent, and can easily find it later to clock in again, add notes, +or update its status. This helps me keep track of what I've gotten +done during the day, keep notes on what I was doing at any point +in time, and get a better idea of how long it takes me to do +different types of tasks. + + +## Conclusion {#conclusion} + +There's a lot that can be done with Org mode, and I've only just +scratched the surface. The simple outline format provided by Org mode +lends itself to doing all sorts of things, be it organizing notes, +keeping a private or work journal, or writing a book or technical +document. I've even written this blog post in Org mode! There's tons +of functionality that can be built on top of it, yet the underlying +format itself remains simple and easy to work with. I've never been +great at keeping myself organized, but Org mode is such a delight to +use that I can't help trying anyway. If it can work for me, maybe it +can work for you, too! + +There's tons of resources for finding new ways for using Org mode, and +I'm still discovering cool things I can track and integrate with it. I +definitely recommend reading through [Sacha Chua's Blog](http://sachachua.com/blog/), as well as +posts from [John Wiegley](http://newartisans.com/2007/08/using-org-mode-as-a-day-planner/). I'm always looking for more stuff to try +out. Feel free to drop me a line if you find or are using something +you think is cool or useful! diff --git a/content/blog/git-graphs.md b/content/blog/git-graphs.md new file mode 100644 index 0000000..0169593 --- /dev/null +++ b/content/blog/git-graphs.md @@ -0,0 +1,796 @@ ++++ +title = "Drawing Git Graphs with Graphviz and Org-Mode" +author = ["Correl Roush"] +date = 2015-07-12T00:00:00-04:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["emacs", "org-mode", "git", "graphviz"] +draft = false ++++ + + + +Digging through Derek Feichtinger's [org-babel examples](https://github.com/dfeich/org-babel-examples) (which I came +across via [irreal.org](http://irreal.org/blog/?p=4162)), I found he had some great examples of +displaying git-style graphs using graphviz. I thought it'd be a fun +exercise to generate my own graphs based on his graphviz source using +elisp, and point it at actual git repos. + + +## Getting Started {#getting-started} + +I started out with the goal of building a simple graph showing a +mainline branch and a topic branch forked from it and eventually +merged back in. + +Using Derek's example as a template, I described 5 commits on a master +branch, plus two on a topic branch. + + +```dot +digraph G { + rankdir="LR"; + bgcolor="transparent"; + node[width=0.15, height=0.15, shape=point, color=white]; + edge[weight=2, arrowhead=none, color=white]; + node[group=master]; + 1 -> 2 -> 3 -> 4 -> 5; + node[group=branch]; + 2 -> 6 -> 7 -> 4; +} +``` + +The resulting image looks like this: + +{{< figure src="/ox-hugo/git-graphs-example.svg" >}} + + +### Designing the Data Structure {#designing-the-data-structure} + +The first thing I needed to do was describe my data structure. Leaning +on my experiences reading and working through [SICP](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0CB8QFjAA&url=https%3A%2F%2Fmitpress.mit.edu%2Fsicp%2F&ei=lH6gVau5OIGR-AG8j7yACQ&usg=AFQjCNHTCXQK7qN-kYibdy%5FMqRBWxlr8og&sig2=Lu9WIhyuTJS92e8hxne0Aw&bvm=bv.97653015,d.cWw), I got to work +building a constructor function, and several accessors. + +I decided to represent each node on a graph with an id, a list of +parent ids, and a group which will correspond to the branch on the +graph the commit belongs to. + +```emacs-lisp +(defun git-graph/make-node (id &optional parents group) + (list id parents group)) + +(defun git-graph/node-id (node) + (nth 0 node)) + +(defun git-graph/node-parents (node) + (nth 1 node)) + +(defun git-graph/node-group (node) + (nth 2 node)) +``` + + +### Converting the structure to Graphviz {#converting-the-structure-to-graphviz} + +Now that I had my data structures sorted out, it was time to step +through them and generate the graphviz source that'd give me the +nice-looking graphs I was after. + +The graph is constructed using the example above as a template. The +nodes are defined first, followed by the edges between them. + + +```emacs-lisp +(defun git-graph/to-graphviz (id nodes) + (string-join + (list + (concat "digraph " id " {") + "bgcolor=\"transparent\";" + "rankdir=\"LR\";" + "node[width=0.15,height=0.15,shape=point,fontsize=8.0,color=white,fontcolor=white];" + "edge[weight=2,arrowhead=none,color=white];" + (string-join + (-map #'git-graph/to-graphviz-node nodes) + "\n") + (string-join + (-uniq (-flatten (-map + (lambda (node) (git-graph/to-graphviz-edges node nodes)) + nodes))) + "\n") + "}") + "\n")) +``` + +For the sake of readability, I'll format the output: + + +```emacs-lisp +(defun git-graph/to-graphviz-pretty (id nodes) + (with-temp-buffer + (graphviz-dot-mode) + (insert (git-graph/to-graphviz id nodes)) + (indent-region (point-min) (point-max)) + (buffer-string))) +``` + +Each node is built, setting its group attribute when applicable. + +```emacs-lisp +(defun git-graph/to-graphviz-node (node) + (let ((node-id (git-graph/to-graphviz-node-id + (git-graph/node-id node)))) + (concat node-id + (--if-let (git-graph/node-group node) + (concat "[group=\"" it "\"]")) + ";"))) +``` + +Graphviz node identifiers are quoted to avoid running into issues with +spaces or other special characters. + + +```emacs-lisp +(defun git-graph/to-graphviz-node-id (id) + (format "\"%s\"" id)) +``` + +For each node, an edge is built connecting the node to each of its +parents. + + +```emacs-lisp +(defun git-graph/to-graphviz-edges (node &optional nodelist) + (let ((node-id (git-graph/node-id node)) + (parents (git-graph/node-parents node)) + (node-ids (-map #'git-graph/node-id nodelist))) + (-map (lambda (parent) + (unless (and nodelist (not (member parent node-ids))) + (git-graph/to-graphviz-edge node-id parent))) + parents))) + +(defun git-graph/to-graphviz-edge (from to) + (concat + (git-graph/to-graphviz-node-id to) + " -> " + (git-graph/to-graphviz-node-id from) + ";")) +``` + +With that done, the simple graph above could be generated with the +following code: + + +```emacs-lisp +(git-graph/to-graphviz-pretty + "example" + (list (git-graph/make-node 1 nil "master") + (git-graph/make-node 2 '(1) "master") + (git-graph/make-node 3 '(2) "master") + (git-graph/make-node 4 '(3 7) "master") + (git-graph/make-node 5 '(4) "master") + (git-graph/make-node 6 '(2) "branch") + (git-graph/make-node 7 '(6) "branch"))) +``` + +Which generates the following graphviz source: + + +```dot +nil +``` + +The generated image matches the example exactly: + +{{< figure src="/ox-hugo/git-graphs-generated-example.svg" >}} + + +## Adding Labels {#adding-labels} + +The next thing my graph needed was a way of labeling nodes. Rather +than trying to figure out some way of attaching a separate label to a +node, I decided to simply draw a labeled node as a box with text. + +```dot +digraph G { + rankdir="LR"; + bgcolor="transparent"; + node[width=0.15, height=0.15, shape=point,fontsize=8.0,color=white,fontcolor=white]; + edge[weight=2, arrowhead=none,color=white]; + node[group=main]; + 1 -> 2 -> 3 -> 4 -> 5; + 5[shape=box,label=master]; + node[group=branch1]; + 2 -> 6 -> 7 -> 4; + 7[shape=box,label=branch]; +} +``` + +{{< figure src="/ox-hugo/git-graphs-labels.svg" >}} + + +### Updating the Data Structure {#updating-the-data-structure} + +I updated my data structure to support an optional label applied to a +node. I opted to store it in an associative list alongside the group. + + +```emacs-lisp +(defun git-graph/make-node (id &optional parents options) + (list id parents options)) + +(defun git-graph/node-id (node) + (nth 0 node)) + +(defun git-graph/node-parents (node) + (nth 1 node)) + +(defun git-graph/node-group (node) + (cdr (assoc 'group (nth 2 node)))) + +(defun git-graph/node-label (node) + (cdr (assoc 'label (nth 2 node)))) +``` + + +### Updating the Graphviz node generation {#updating-the-graphviz-node-generation} + +The next step was updating the Graphviz generation functions to handle +the new data structure, and set the shape and label attributes of +labeled nodes. + + +```emacs-lisp +(defun git-graph/to-graphviz-node (node) + (let ((node-id (git-graph/to-graphviz-node-id (git-graph/node-id node)))) + (concat node-id + (git-graph/to-graphviz-node--attributes node) + ";"))) + +(defun git-graph/to-graphviz-node--attributes (node) + (let ((attributes (git-graph/to-graphviz-node--compute-attributes node))) + (and attributes + (concat "[" + (mapconcat (lambda (pair) + (format "%s=\"%s\"" + (car pair) (cdr pair))) + attributes + ", ") + "]")))) + +(defun git-graph/to-graphviz-node--compute-attributes (node) + (-filter #'identity + (append (and (git-graph/node-group node) + (list (cons 'group (git-graph/node-group node)))) + (and (git-graph/node-label node) + (list (cons 'shape 'box) + (cons 'label (git-graph/node-label node))))))) +``` + +I could then label the tips of each branch: + + +```emacs-lisp +(git-graph/to-graphviz-pretty + "labeled" + (list (git-graph/make-node 1 nil '((group . "master"))) + (git-graph/make-node 2 '(1) '((group . "master"))) + (git-graph/make-node 3 '(2) '((group . "master"))) + (git-graph/make-node 4 '(3 7) '((group . "master"))) + (git-graph/make-node 5 '(4) '((group . "master") + (label . "master"))) + (git-graph/make-node 6 '(2) '((group . "branch"))) + (git-graph/make-node 7 '(6) '((group . "branch") + (label . "branch"))))) +``` + +{{< figure src="/ox-hugo/git-graphs-labels-generated.svg" >}} + + +## Automatic Grouping Using Leaf Nodes {#automatic-grouping-using-leaf-nodes} + +Manually assigning groups to each node is tedious, and easy to +accidentally get wrong. Also, with the goal to graph git repositories, +I was going to have to figure out groupings automatically anyway. + +To do this, it made sense to traverse the nodes in [topological order](https://en.wikipedia.org/wiki/Topological%5Fsorting). + +Repeating the example above, + +```dot +digraph G { + rankdir="LR"; + bgcolor="transparent"; + node[width=0.15, height=0.15, shape=circle, color=white, fontcolor=white]; + edge[weight=2, arrowhead=none, color=white]; + node[group=main]; + 1 -> 2 -> 3 -> 4 -> 5; + node[group=branch1]; + 2 -> 6 -> 7 -> 4; +} +``` + +{{< figure src="/ox-hugo/git-graphs-topo.svg" >}} + +These nodes can be represented (right to left) in topological order as +either `5, 4, 3, 7, 6, 2, 1` or `5, 4, 7, 6, 3, 2, 1`. + +Having no further children, `5` is a leaf node, and can be used as a +group. All first parents of `5` can therefore be considered to be in +group `5`. + +`7` is a second parent to `4`, and so should be used as the group for +all of its parents not present in group `5`. + + +```emacs-lisp +(defun git-graph/group-topo (nodelist) + (reverse + (car + (-reduce-from + (lambda (acc node) + (let* ((grouped-nodes (car acc)) + (group-stack (cdr acc)) + (node-id (git-graph/node-id node)) + (group-from-stack (--if-let (assoc node-id group-stack) + (cdr it))) + (group (or group-from-stack node-id)) + (parents (git-graph/node-parents node)) + (first-parent (first parents))) + (if group-from-stack + (pop group-stack)) + (if (and first-parent (not (assoc first-parent group-stack))) + (push (cons first-parent group) group-stack)) + (cons (cons (git-graph/make-node node-id + parents + `((group . ,group) + (label . ,(git-graph/node-label node)))) + grouped-nodes) + group-stack))) + nil + nodelist)))) +``` + +While iterating through the node list, I maintained a stack of pairs +built from the first parent of the current node, and the current +group. To determine the group, the head of the stack is checked to see +if it contains a group for the current node id. If it does, that group +is used and it is popped off the stack, otherwise the current node id +is used. + +The following table illustrates how the stack is used to store and +assign group relationships as the process iterates through the node +list: + +
+ Table 1: + Progressing through the nodes +
+ +| Node | Parents | Group Stack | Group | +|------|---------|-----------------|-------| +| 5 | (4) | (4 . 5) | 5 | +| 4 | (3 7) | (3 . 5) | 5 | +| 3 | (2) | (2 . 5) | 5 | +| 7 | (6) | (6 . 7) (2 . 5) | 7 | +| 6 | (2) | (2 . 5) | 7 | +| 2 | (1) | (1 . 5) | 5 | +| 1 | | | 5 | + + +### Graph without automatic grouping {#graph-without-automatic-grouping} + + +```emacs-lisp +(git-graph/to-graphviz-pretty + "nogroups" + (list (git-graph/make-node 5 '(4) '((label . master))) + (git-graph/make-node 4 '(3 7)) + (git-graph/make-node 3 '(2)) + (git-graph/make-node 7 '(6) '((label . develop))) + (git-graph/make-node 6 '(2)) + (git-graph/make-node 2 '(1)) + (git-graph/make-node 1 nil))) +``` + +{{< figure src="/ox-hugo/git-graphs-no-auto-grouping.svg" >}} + + +### Graph with automatic grouping {#graph-with-automatic-grouping} + + +```emacs-lisp +(git-graph/to-graphviz-pretty + "autogroups" + (git-graph/group-topo + (list (git-graph/make-node 5 '(4) '((label . master))) + (git-graph/make-node 4 '(3 7)) + (git-graph/make-node 3 '(2)) + (git-graph/make-node 7 '(6) '((label . develop))) + (git-graph/make-node 6 '(2)) + (git-graph/make-node 2 '(1)) + (git-graph/make-node 1 nil)))) +``` + +{{< figure src="/ox-hugo/git-graphs-with-auto-grouping.svg" >}} + + +## Graphing a Git Repository {#graphing-a-git-repository} + +Satisfied that I had all the necessary tools to start graphing real +git repositories, I created an example repository to test against. + + +### Creating a Sample Repository {#creating-a-sample-repository} + +Using the following script, I created a sample repository to test +against. I performed the following actions: + +- Forked a develop branch from master. +- Forked a feature branch from develop, with two commits. +- Added another commit to develop. +- Forked a second feature branch from develop, with two commits. +- Merged the second feature branch to develop. +- Merged develop to master and tagged it. + +```sh +mkdir /tmp/test.git +cd /tmp/test.git +git init +touch README +git add README +git commit -m 'initial' +git commit --allow-empty -m 'first' +git checkout -b develop +git commit --allow-empty -m 'second' +git checkout -b feature-1 +git commit --allow-empty -m 'feature 1' +git commit --allow-empty -m 'feature 1 again' +git checkout develop +git commit --allow-empty -m 'third' +git checkout -b feature-2 +git commit --allow-empty -m 'feature 2' +git commit --allow-empty -m 'feature 2 again' +git checkout develop +git merge --no-ff feature-2 +git checkout master +git merge --no-ff develop +git tag -a 1.0 -m '1.0!' +``` + + +### Generating a Graph From a Git Branch {#generating-a-graph-from-a-git-branch} + +The first order of business was to have a way to call out to git and +return the results: + + +```emacs-lisp +(defun git-graph/git-execute (repo-url command &rest args) + (with-temp-buffer + (shell-command (format "git -C \"%s\" %s" + repo-url + (string-join (cons command args) + " ")) + t) + (buffer-string))) +``` + +Next, I needed to get the list of commits for a branch in topological +order, with a list of parent commits for each. It turns out git +provides exactly that via its `rev-list` command. + + +```emacs-lisp +(defun git-graph/git-rev-list (repo-url head) + (-map (lambda (line) (split-string line)) + (split-string (git-graph/git-execute + repo-url + "rev-list" "--topo-order" "--parents" head) + "\n" t))) +``` + +I also wanted to label branch heads wherever possible. To do this, I +looked up the revision name from git, discarding it if it was relative +to some other named commit. + + +```emacs-lisp +(defun git-graph/git-label (repo-url rev) + (let ((name (string-trim + (git-graph/git-execute repo-url + "name-rev" "--name-only" rev)))) + (unless (s-contains? "~" name) + name))) +``` + +Generating the graph for a single branch was as simple as iterating +over each commit and creating a node for it. + + +```emacs-lisp +(defun git-graph/git-graphs-head (repo-url head) + (git-graph/group-topo + (-map (lambda (rev-with-parents) + (let* ((rev (car rev-with-parents)) + (parents (cdr rev-with-parents)) + (label (git-graph/git-label repo-url rev))) + (git-graph/make-node rev parents + `((label . ,label))))) + (git-graph/git-rev-list repo-url head)))) +``` + +Here's the result of graphing the `master` branch: + + +```emacs-lisp +(git-graph/to-graphviz-pretty + "git" + (git-graph/git-graphs-head + "/tmp/test.git" + "master")) +``` + +```dot +nil +``` + +{{< figure src="/ox-hugo/git-graphs-branch.svg" >}} + + +### Graphing Multiple Branches {#graphing-multiple-branches} + +To graph multiple branches, I needed a function for combining +histories. To do so, I simply append any nodes I don't already know +about in the first history from the second. + + +```emacs-lisp +(defun git-graph/+ (a b) + (append a + (-remove (lambda (node) + (assoc (git-graph/node-id node) a)) + b))) +``` + +From there, all that remained was to accumulate the branch histories +and output the complete graph: + + +```emacs-lisp +(defun git-graph/git-load (repo-url heads) + (-reduce #'git-graph/+ + (-map (lambda (head) + (git-graph/git-graphs-head repo-url head)) + heads))) +``` + +And here's the example repository, graphed in full: + + +```emacs-lisp +(git-graph/to-graphviz-pretty + "git" + (git-graph/git-load + "/tmp/test.git" + '("master" "feature-1"))) +``` + +```dot +nil +``` + +{{< figure src="/ox-hugo/git-graphs-repo.svg" >}} + + +## Things I may add in the future {#things-i-may-add-in-the-future} + + +### Limiting Commits to Graph {#limiting-commits-to-graph} + +Running this against repos with any substantial history can make the +graph unwieldy. It'd be a good idea to abstract out the commit list +fetching, and modify it to support different ways of limiting the +history to display. + +Ideas would include: + +- Specifying commit ranges +- Stopping at a common ancestor to all graphed branches (e.g., using + `git-merge-base`). +- Other git commit limiting options, like searches, showing only merge + or non-merge commits, etc. + + +### Collapsing History {#collapsing-history} + +Another means of reducing the size of the resulting graph would be to +collapse unimportant sections of it. It should be possible to collapse +a section of the graph, showing a count of skipped nodes. + +The difficult part would be determining what parts aren't worth +drawing. Something like this would be handy, though, for concisely +graphing the state of multiple ongoing development branches (say, to +get a picture of what's been going on since the last release, and +what's still incomplete). + +```dot +digraph G { + rankdir="LR"; + bgcolor="transparent"; + node[width=0.15,height=0.15,shape=point,color=white]; + edge[weight=2,arrowhead=none,color=white]; + node[group=main]; + 1 -> 2 -> 3 -> 4 -> 5; + node[group=branch]; + 2 -> 6 -> 7 -> 8 -> 9 -> 10 -> 4; +} +``` + +{{< figure src="/ox-hugo/git-graphs-long.svg" caption="Figure 1: A graph with multiple nodes on a branch." >}} + +```dot +digraph G { + rankdir="LR"; + bgcolor="transparent"; + node[width=0.15,height=0.15,shape=point,color=white]; + edge[weight=2,arrowhead=none,color=white,fontcolor=white]; + node[group=main]; + 1 -> 2 -> 3 -> 4 -> 5; + node[group=branch]; + 2 -> 6; + 6 -> 10[style=dashed,label="+3"]; + 10 -> 4; +} +``` + +{{< figure src="/ox-hugo/git-graphs-collapsed.svg" caption="Figure 2: The same graph, collapsed." >}} + + +### Clean up and optimize the code a bit {#clean-up-and-optimize-the-code-a-bit} + +Some parts of this (particularly, the grouping) are probably pretty +inefficient. If this turns out to actually be useful, I may take +another crack at it. + + +## Final Code {#final-code} + +In case anyone would like to use this code for anything, or maybe just +pick it apart and play around with it, all the Emacs Lisp code in this +post is collected into a single file below: + +```emacs-lisp +;;; git-graph.el --- Generate git-style graphs using graphviz + +;; Copyright (c) 2015 Correl Roush + +;;; License: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;;; Code: + +(require 'dash) + +(defun git-graph/make-node (id &optional parents options) + (list id parents options)) + +(defun git-graph/node-id (node) + (nth 0 node)) + +(defun git-graph/node-parents (node) + (nth 1 node)) + +(defun git-graph/node-group (node) + (cdr (assoc 'group (nth 2 node)))) + +(defun git-graph/node-label (node) + (cdr (assoc 'label (nth 2 node)))) + +(defun git-graph/+ (a b) + (append a + (-remove (lambda (node) + (assoc (git-graph/node-id node) a)) + b))) + +(defun git-graph/to-graphviz (id nodes) + (string-join + (list + (concat "digraph " id " {") + "bgcolor=\"transparent\";" + "rankdir=\"LR\";" + "node[width=0.15,height=0.15,shape=point,fontsize=8.0,color=white,fontcolor=white];" + "edge[weight=2,arrowhead=none,color=white];" + (string-join + (-map #'git-graph/to-graphviz-node nodes) + "\n") + (string-join + (-uniq (-flatten (-map + (lambda (node) (git-graph/to-graphviz-edges node nodes)) + nodes))) + "\n") + "}") + "\n")) + +(defun git-graph/to-graphviz-node-id (id) + (format "\"%s\"" id)) + +(defun git-graph/to-graphviz-edges (node &optional nodelist) + (let ((node-id (git-graph/node-id node)) + (parents (git-graph/node-parents node)) + (node-ids (-map #'git-graph/node-id nodelist))) + (-map (lambda (parent) + (unless (and nodelist (not (member parent node-ids))) + (git-graph/to-graphviz-edge node-id parent))) + parents))) + +(defun git-graph/to-graphviz-edge (from to) + (concat + (git-graph/to-graphviz-node-id to) + " -> " + (git-graph/to-graphviz-node-id from) + ";")) + +(defun git-graph/group-topo (nodelist) + (reverse + (car + (-reduce-from + (lambda (acc node) + (let* ((grouped-nodes (car acc)) + (group-stack (cdr acc)) + (node-id (git-graph/node-id node)) + (group-from-stack (--if-let (assoc node-id group-stack) + (cdr it))) + (group (or group-from-stack node-id)) + (parents (git-graph/node-parents node)) + (first-parent (first parents))) + (if group-from-stack + (pop group-stack)) + (if (and first-parent (not (assoc first-parent group-stack))) + (push (cons first-parent group) group-stack)) + (cons (cons (git-graph/make-node node-id + parents + `((group . ,group) + (label . ,(git-graph/node-label node)))) + grouped-nodes) + group-stack))) + nil + nodelist)))) + +(defun git-graph/git-execute (repo-url command &rest args) + (with-temp-buffer + (shell-command (format "git -C \"%s\" %s" + repo-url + (string-join (cons command args) + " ")) + t) + (buffer-string))) + +(provide 'git-graph) +;;; git-graph.el ends here +``` + +Download: [git-graph.el](/files/git-graph.el) diff --git a/content/blog/hue-wake-up.md b/content/blog/hue-wake-up.md new file mode 100644 index 0000000..229b601 --- /dev/null +++ b/content/blog/hue-wake-up.md @@ -0,0 +1,381 @@ ++++ +title = "How Does The Phillips Hue Wake-Up Feature Work?" +author = ["Correl Roush"] +date = 2018-03-13T00:00:00-04:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["home-automation"] +draft = false ++++ + +I recently got myself a set of Phillips Hue White and Color Ambiance +lights. One of the features I was looking forward to in particular +(besides playing with all the color options) was setting a wake-up +alarm with the lights gradually brightening. This was pretty painless +to get set up using the phone app. I'm pretty happy with the result, +but there's certainly some things I wouldn't mind tweaking. For +example, the initial brightness of the bulbs (at the lowest setting) +still seems a bit bright, so I might want to delay the bedside lamps +and let the more distant lamp start fading in first. I also want to +see if I can fiddle it into transitioning between some colors to get +more of a sunrise effect (perhaps "rising" from the other side of the +room, with the light spreading towards the head of the bed). + +Figuring out how the wake-up settings that the app installed on my +bridge seemed a good first step towards introducing my own +customizations. + +Information on getting access to a Hue bridge to make REST API calls +to it can be found in the [Hue API getting started guide](https://www.developers.meethue.com/documentation/getting-started). + + +## My wake-up settings {#my-wake-up-settings} + +My wake-up is scheduled for 7:00 to gradually brighten the lights with +a half-hour fade-in each weekday. I also toggled on the setting to +automatically turn the lights off at 9:00. + + + +
+
+ +![](/images/Screenshot_20180313-182434.png) ![](/images/Screenshot_20180313-182438.png) + +
+ + +## Finding things on the bridge {#finding-things-on-the-bridge} + +The most natural starting point is to check the schedules. Right off +the bat, I find what I'm after: + + +### The schedule ... {#the-schedule-dot-dot-dot} + +```http +GET http://bridge/api/${username}/schedules/1 +``` + +```js +{ + "name": "Wake up", + "description": "L_04_fidlv_start wake up", + "command": { + "address": "/api/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/sensors/2/state", + "body": { + "flag": true + }, + "method": "PUT" + }, + "localtime": "W124/T06:30:00", + "time": "W124/T10:30:00", + "created": "2018-03-11T19:46:54", + "status": "enabled", + "recycle": true +} +``` + +This is a recurring schedule item that runs every weekday at 6:30. We +can tell this by looking at the `localtime` field. From the +documentation on [time patterns](https://www.developers.meethue.com/documentation/datatypes-and-time-patterns#16%5Ftime%5Fpatterns), we can see that it's a recurring time +pattern specifying days of the week as a bitmask, and a time (6:30). + +
+ Table 1: + Unraveling the weekday portion +
+ +| `0MTWTFSS` | +|:----------------------------| +| `01111100` (124 in decimal) | + +Since this schedule is enabled, we can be assured that it will run, +and in doing so, will issue a `PUT` to a sensors endpoint, setting a +flag to true. + + +### ... triggers the sensor ... {#dot-dot-dot-triggers-the-sensor-dot-dot-dot} + +```http +GET http://bridge/api/${username}/sensors/2 +``` + +```js +{ + "state": { + "flag": false, + "lastupdated": "2018-03-13T13:00:00" + }, + "config": { + "on": true, + "reachable": true + }, + "name": "Sensor for wakeup", + "type": "CLIPGenericFlag", + "modelid": "WAKEUP", + "manufacturername": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "swversion": "A_1801260942", + "uniqueid": "L_04_fidlv", + "recycle": true +} +``` + +The sensor is what's _really_ setting things in motion. Here we've got +a [generic CLIP flag sensor](https://www.developers.meethue.com/documentation/supported-sensors#clipSensors) that is triggered exclusively by our +schedule. Essentially, by updating the flag state, we trigger the +sensor. + + +### ... triggers a rule ... {#dot-dot-dot-triggers-a-rule-dot-dot-dot} + +```http +GET http://bridge/api/${username}/rules/1 +``` + +```js +{ + "name": "L_04_fidlv_Start", + "owner": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "created": "2018-03-11T19:46:51", + "lasttriggered": "2018-03-13T10:30:00", + "timestriggered": 2, + "status": "enabled", + "recycle": true, + "conditions": [ + { + "address": "/sensors/2/state/flag", + "operator": "eq", + "value": "true" + } + ], + "actions": [ + { + "address": "/groups/1/action", + "method": "PUT", + "body": { + "scene": "7GJer2-5ahGIqz6" + } + }, + { + "address": "/schedules/2", + "method": "PUT", + "body": { + "status": "enabled" + } + } + ] +} +``` + +Now things are happening. Looking at the conditions, we can see that +this rule triggers when the wakeup sensor updates, and its flag is set +to `true`. When that happens, the bridge will iterate through its +rules, find that the above condition has been met, and iterate through +each of the actions. + + +### ... which sets the scene ... {#dot-dot-dot-which-sets-the-scene-dot-dot-dot} + +The bedroom group (`/groups/1` in the rule's action list) is set to +the following scene, which turns on the lights at minimum brightness: + +```http +GET http://bridge/api/${username}/scenes/7GJer2-5ahGIqz6 +``` + +```js +{ + "name": "Wake Up init", + "lights": [ + "2", + "3", + "5" + ], + "owner": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "recycle": true, + "locked": true, + "appdata": {}, + "picture": "", + "lastupdated": "2018-03-11T19:46:50", + "version": 2, + "lightstates": { + "2": { + "on": true, + "bri": 1, + "ct": 447 + }, + "3": { + "on": true, + "bri": 1, + "ct": 447 + }, + "5": { + "on": true, + "bri": 1, + "ct": 447 + } + } +} +``` + + +### ... and schedules the transition ... {#dot-dot-dot-and-schedules-the-transition-dot-dot-dot} + +Another schedule (`/schedules/2` in the rule's action list) is enabled +by the rule. + +```http +GET http://bridge/api/${username}/schedules/2 +``` + +```js +{ + "name": "L_04_fidlv", + "description": "L_04_fidlv_trigger end scene", + "command": { + "address": "/api/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/groups/0/action", + "body": { + "scene": "gXdkB1um68N1sZL" + }, + "method": "PUT" + }, + "localtime": "PT00:01:00", + "time": "PT00:01:00", + "created": "2018-03-11T19:46:51", + "status": "disabled", + "autodelete": false, + "starttime": "2018-03-13T10:30:00", + "recycle": true +} +``` + +_This_ schedule is a bit different from the one we saw before. It is +normally disabled, and it's time pattern (in `localtime`) is +different. The `PT` prefix specifies that this is a timer which +expires after the given amount of time has passed. In this case, it is +set to one minute (the first 60 seconds of our wake-up will be spent +in minimal lighting). Enabling this schedule starts up the timer. When +one minute is up, another scene will be set. + +This one, strangely, is applied to group `0`, the meta-group including +all lights, but since the scene itself specifies to which lights it +applies, there's no real problem with it. + + +### ... to a fully lit room ... {#dot-dot-dot-to-a-fully-lit-room-dot-dot-dot} + +```http +GET http://bridge/api/${username}/scenes/gXdkB1um68N1sZL +``` + +```js +{ + "name": "Wake Up end", + "lights": [ + "2", + "3", + "5" + ], + "owner": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "recycle": true, + "locked": true, + "appdata": {}, + "picture": "", + "lastupdated": "2018-03-11T19:46:51", + "version": 2, + "lightstates": { + "2": { + "on": true, + "bri": 254, + "ct": 447, + "transitiontime": 17400 + }, + "3": { + "on": true, + "bri": 254, + "ct": 447, + "transitiontime": 17400 + }, + "5": { + "on": true, + "bri": 254, + "ct": 447, + "transitiontime": 17400 + } + } +} +``` + +This scene transitions the lights to full brightness over the next 29 +minutes (1740 seconds), per the specified `transitiontime` (which is +specified in deciseconds). + + +### ... which will be switched off later. {#dot-dot-dot-which-will-be-switched-off-later-dot} + +Finally, an additional rule takes care of turning the lights off and +the wake-up sensor at 9:00 (Two and a half hours after the initial +triggering of the sensor). + +```http +GET http://bridge/api/${username}/rules/2 +``` + +```js +{ + "name": "Wake up 1.end", + "owner": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "created": "2018-03-11T19:46:51", + "lasttriggered": "2018-03-13T13:00:00", + "timestriggered": 2, + "status": "enabled", + "recycle": true, + "conditions": [ + { + "address": "/sensors/2/state/flag", + "operator": "eq", + "value": "true" + }, + { + "address": "/sensors/2/state/flag", + "operator": "ddx", + "value": "PT02:30:00" + } + ], + "actions": [ + { + "address": "/groups/2/action", + "method": "PUT", + "body": { + "on": false + } + }, + { + "address": "/sensors/2/state", + "method": "PUT", + "body": { + "flag": false + } + } + ] +} +``` + +Unlike the first rule, this one doesn't trigger immediately. It has an +additional condition on the sensor state flag using the special `ddx` +operator, which (given the timer specified) is true **two and a half +hours after** the flag has been set. As the schedule sets it at 6:30, +that means that this rule will trigger at 9:00, turn the lights off in +the bedroom, and set the sensor's flag to `false`. + + +## Where to go from here {#where-to-go-from-here} + +The wake-up config in the phone app touched on pretty much every major +aspect of the Hue bridge API. Given the insight I now have into how it +works, I can start constructing my own schedules and transitions, and +playing with different ways of triggering them and even having them +trigger each other. + +If I get around to building my rolling sunrise, I'll be sure to get a +post up on it :) diff --git a/content/blog/learning-functional-programming-part-one.md b/content/blog/learning-functional-programming-part-one.md new file mode 100644 index 0000000..7770f8d --- /dev/null +++ b/content/blog/learning-functional-programming-part-one.md @@ -0,0 +1,116 @@ ++++ +title = "Learning Functional Programming, Part One" +author = ["Correl Roush"] +date = 2012-04-09T00:00:00-04:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming", "python"] +draft = false ++++ + +## Part One: Lambdas? In my Python? + +Over the past few months, I've decided to take a stab at learning some +functional programming. I'd been doing python for a few years (and +completely falling in love with it), and so I'd been exposed to a few +functional concepts it offers - primarily higher-order functions and list +comprehensions, both of which allow for very clear, concise and powerful code. +Since that's where I started my journey, that's where my post will begin as +well. + + + +### Functions are objects, too + +Having graduated to python from PHP and C/C++, perhaps the biggest new thing to +wrap my head around (besides readable code, whitespace-as-syntax, +[programming being fun again](http://xkcd.com/353/), and all that), is that in +python, functions (and classes!) are objects, just like anything else. They +can still be defined in the usual way, but they can also be assigned, passed +as arguments, even modified and replaced like any other value or object in your +program. + +```python +def do_a(): + print "Doing something" + +do_b = do_a + +do_b() + +# Prints "Doing something" +``` + +Functions themselves no longer require formal definitions, either, they can be +created *[anonymously](http://en.wikipedia.org/wiki/Anonymous_function)*: + +```python +my_send = lambda person, thing: send(person.upper(), thing, subject="Check this out!") +ucase_people = map(lambda name: name.upper(), ["Joe", "Mary", "Zach"]) +``` + + +### Abstracting behaviour + +You'll find you can now start abstracting away common idioms. For +example, you probably very often find yourself looping over some list of items, +performing some set of actions on them, or passing them to some other function +or method: + +```python +people = ["Joe", "Chris", "Matt", "Jennifer"] +for person in people: + u_person = person.upper() + send(person, super_fun_thing) +``` + +Instead of that, you could have a function that takes a list as one argument, +and a function to apply to each item in it as another: + +```python +def dostuff(action, things): + result = [] + for thing in things: + result.append(action(thing)) + return result + +dostuff(send, people) +``` + +The above example is actually just a simple definition of one of the most +common higher-order functions, +[map](http://docs.python.org/library/functions.html#map), which python already +provides for you. Another particularly useful higher-order function is +[filter](http://docs.python.org/library/functions.html#filter) which, given a +function that returns true of false if its criteria are met by the passed item, +will return the subset of the passed list that satisfy the filtering function: + +```python +stuff = ["My notes.txt", "Matt's notes.txt", "My music.pls"] +my_stuff = filter(lambda s: s.startswith("My "), stuff) + +# my_stuff = ["My notes.txt", "My music.pls"] +``` + +[List comprehensions](http://docs.python.org/tutorial/datastructures.html#list-comprehensions) +provide a cleaner, easier to read way to perform mapping and/or filtering on a +list: + +```python +stuff = ["My notes.txt", "Matt's notes.txt", "My music.pls"] + +my_stuff = [file for file in stuff if file.startswith("My ")] +# ["My notes.txt", "My music.pls"] + +upper_stuff = [file.upper() for file in stuff] +# ["MY NOTES.TXT", "MATT'S NOTES.TXT", "MY MUSIC.PLS"] + +music = [file.upper() for file in stuff if file.endswith(".pls")] +# ["MY MUSIC.PLS"] +``` + + +### Tip of the iceberg + +This is just a very small taste of functional programming concepts. Later, I'll +introduce a couple of functional languages, and explain what sets them apart +from object-oriented and imperative programming languages. diff --git a/content/blog/meh-php.md b/content/blog/meh-php.md new file mode 100644 index 0000000..7b84503 --- /dev/null +++ b/content/blog/meh-php.md @@ -0,0 +1,42 @@ ++++ +title = "Meh.php" +author = ["Correl Roush"] +date = 2011-04-27T00:00:00-04:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming"] +draft = false ++++ + +```php +give_a_shit(); +echo $bwuh->concerns; + +class SuperDuperBillingProcessor extends Meh {} + +$p = new SuperDuperBillingProcessor(); +$p->calculateEverything(); +$p->profit(); +``` diff --git a/content/blog/org-publish-with-theme.md b/content/blog/org-publish-with-theme.md new file mode 100644 index 0000000..f28bf10 --- /dev/null +++ b/content/blog/org-publish-with-theme.md @@ -0,0 +1,30 @@ ++++ +title = "Use a different theme when publishing Org files" +author = ["Correl Roush"] +date = 2016-02-23T00:00:00-05:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["emacs", "org-mode"] +draft = false ++++ + +I've been using [material-theme](https://github.com/cpaulik/emacs-material-theme) lately, and I sometimes switch around, +but I've found that [solarized](https://github.com/bbatsov/solarized-emacs) produces the best exported code block +results. To avoid having to remember to switch themes when exporting, +I wrote a quick wrapper for org-export to do it for me: + +```emacs-lisp +(defun my/with-theme (theme fn &rest args) + (let ((current-themes custom-enabled-themes)) + (mapcar #'disable-theme custom-enabled-themes) + (load-theme theme t) + (let ((result (apply fn args))) + (mapcar #'disable-theme custom-enabled-themes) + (mapcar (lambda (theme) (load-theme theme t)) current-themes) + result))) + +(advice-add #'org-export-to-file :around (apply-partially #'my/with-theme 'solarized-dark)) +(advice-add #'org-export-to-buffer :around (apply-partially #'my/with-theme 'solarized-dark)) +``` + +Voilà, no more bizarrely formatted code block exports from whatever +theme I might have loaded at the time :) diff --git a/content/blog/potatoes-and-portal-guns.md b/content/blog/potatoes-and-portal-guns.md new file mode 100644 index 0000000..a356ac0 --- /dev/null +++ b/content/blog/potatoes-and-portal-guns.md @@ -0,0 +1,14 @@ ++++ +title = "Potatoes and Portal Guns" +author = ["Correl Roush"] +date = 2011-04-26T00:00:00-04:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["gaming"] +draft = false ++++ + +[Portal 2 Logo](/images/portal_2_logo.jpg) Got my hands on Portal 2 and finished a run through the single player campaign. Was a *lot* of fun, the characters were bursting with humor and personality. Just like the first game, it was hard to stop playing. *Unlike* the first game, it's got some length, so I stayed up late a couple nights with my eyes glued to the television. I already want to play through it again to find any little things I my tired eyes may have missed. + +I'm itching to give co-op a try, so if you happen to have it on xbox or care to drop by, let me know. + +**Update:** Played some co-op with Jen, had fun navigating puzzles together :) diff --git a/content/blog/recursive-http-requests-with-elm.md b/content/blog/recursive-http-requests-with-elm.md new file mode 100644 index 0000000..47b4476 --- /dev/null +++ b/content/blog/recursive-http-requests-with-elm.md @@ -0,0 +1,329 @@ ++++ +title = "Recursive HTTP Requests with Elm" +author = ["Correl Roush"] +date = 2018-01-22T00:00:00-05:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming", "elm"] +draft = false ++++ + +So I got the idea in my head that I wanted to pull data from the +GitLab / GitHub APIs in my Elm app. This seemed straightforward +enough; just wire up an HTTP request and a JSON decoder, and off I go. +Then I remember, oh crap... like any sensible API with a potentially +huge amount of data behind it, the results come back _paginated_. For +anyone unfamiliar, this means that a single API request for a list of, +say, repositories, is only going to return up to some maximum number +of results. If there are more results available, there will be a +reference to additional _pages_ of results, that you can then fetch +with _another_ API request. My single request decoding only the +results returned _from_ that single request wasn't going to cut it. + +I had a handful of problems to solve. I needed to: + +- Detect when additional results were available. +- Parse out the URL to use to fetch the next page of results. +- Continue fetching results until none remained. +- Combine all of the results, maintaining their order. + + +## Are there more results? {#are-there-more-results} + +The first two bullet points can be dealt with by parsing and +inspecting the response header. Both GitHub and GitLab embed +pagination links in the [HTTP Link header](https://www.w3.org/wiki/LinkHeader). As I'm interested in +consuming pages until no further results remain, I'll be looking for a +link in the header with the relationship "next". If I find one, I know +I need to hit the associated URL to fetch more results. If I don't +find one, I'm done! + +```http +Link: ; rel="next", + ; rel="last" +``` + +
+ Code Snippet 1: + Example GitHub Link header +
+ +Parsing this stuff out went straight into a utility module. + +```elm +module Paginated.Util exposing (links) + +import Dict exposing (Dict) +import Maybe.Extra +import Regex + + +{-| Parse an HTTP Link header into a dictionary. For example, to look +for a link to additional results in an API response, you could do the +following: + + Dict.get "Link" response.headers + |> Maybe.map links + |> Maybe.andThen (Dict.get "next") + +-} +links : String -> Dict String String +links s = + let + toTuples xs = + case xs of + [ Just a, Just b ] -> + Just ( b, a ) + + _ -> + Nothing + in + Regex.find + Regex.All + (Regex.regex "<(.*?)>; rel=\"(.*?)\"") + s + |> List.map .submatches + |> List.map toTuples + |> Maybe.Extra.values + |> Dict.fromList +``` + +A little bit of regular expression magic, tuples, and +`Maybe.Extra.values` to keep the matches, and now I've got my +(`Maybe`) URL. + + +## Time to make some requests {#time-to-make-some-requests} + +Now's the time to define some types. I'll need a `Request`, which will +be similar to a standard `Http.Request`, with a _slight_ difference. + +```elm +type alias RequestOptions a = + { method : String + , headers : List Http.Header + , url : String + , body : Http.Body + , decoder : Decoder a + , timeout : Maybe Time.Time + , withCredentials : Bool + } + + +type Request a + = Request (RequestOptions a) +``` + +What separates it from a basic `Http.Request` is the `decoder` field +instead of an `expect` field. The `expect` field in an HTTP request is +responsible for parsing the full response into whatever result the +caller wants. For my purposes, I always intend to be hitting a JSON +API returning a list of items, and I have my own designs on parsing +bits of the request to pluck out the headers. Therefore, I expose only +a slot for including a JSON decoder representing the type of item I'll +be getting a collection of. + +I'll also need a `Response`, which will either be `Partial` +(containing the results from the response, plus a `Request` for +getting the next batch), or `Complete`. + +```elm +type Response a + = Partial (Request a) (List a) + | Complete (List a) +``` + +Sending the request isn't too bad. I can just convert my request into +an `Http.Request`, and use `Http.send`. + +```elm +send : + (Result Http.Error (Response a) -> msg) + -> Request a + -> Cmd msg +send resultToMessage request = + Http.send resultToMessage <| + httpRequest request + + +httpRequest : Request a -> Http.Request (Response a) +httpRequest (Request options) = + Http.request + { method = options.method + , headers = options.headers + , url = options.url + , body = options.body + , expect = expect options + , timeout = options.timeout + , withCredentials = options.withCredentials + } + + +expect : RequestOptions a -> Http.Expect (Response a) +expect options = + Http.expectStringResponse (fromResponse options) +``` + +All of my special logic for handling the headers, mapping the decoder +over the results, and packing them up into a `Response` is baked into +my `Http.Request` via a private `fromResponse` translator: + +```elm +fromResponse : + RequestOptions a + -> Http.Response String + -> Result String (Response a) +fromResponse options response = + let + items : Result String (List a) + items = + Json.Decode.decodeString + (Json.Decode.list options.decoder) + response.body + + nextPage = + Dict.get "Link" response.headers + |> Maybe.map Paginated.Util.links + |> Maybe.andThen (Dict.get "next") + in + case nextPage of + Nothing -> + Result.map Complete items + + Just url -> + Result.map + (Partial (request { options | url = url })) + items +``` + + +## Putting it together {#putting-it-together} + +Now, I can make my API request, and get back a response with +potentially partial results. All that needs to be done now is to make +my request, and iterate on the results I get back in my `update` +method. + +To make things a bit easier, I add a method for concatenating two +responses: + +```elm +update : Response a -> Response a -> Response a +update old new = + case ( old, new ) of + ( Complete items, _ ) -> + Complete items + + ( Partial _ oldItems, Complete newItems ) -> + Complete (oldItems ++ newItems) + + ( Partial _ oldItems, Partial request newItems ) -> + Partial request (oldItems ++ newItems) +``` + +Putting it all together, I get a fully functional test app that +fetches a paginated list of repositories from GitLab, and renders them +when I've fetched them all: + +```elm +module Example exposing (..) + +import Html exposing (Html) +import Http +import Json.Decode exposing (field, string) +import Paginated exposing (Response(..)) + + +type alias Model = + { repositories : Maybe (Response String) } + + +type Msg + = GotRepositories (Result Http.Error (Paginated.Response String)) + + +main : Program Never Model Msg +main = + Html.program + { init = init + , update = update + , view = view + , subscriptions = \_ -> Sub.none + } + + +init : ( Model, Cmd Msg ) +init = + ( { repositories = Nothing } + , getRepositories + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + GotRepositories (Ok response) -> + ( { model + | repositories = + case model.repositories of + Nothing -> + Just response + + Just previous -> + Just (Paginated.update previous response) + } + , case response of + Partial request _ -> + Paginated.send GotRepositories request + + Complete _ -> + Cmd.none + ) + + GotRepositories (Err _) -> + ( { model | repositories = Nothing } + , Cmd.none + ) + + +view : Model -> Html Msg +view model = + case model.repositories of + Nothing -> + Html.div [] [ Html.text "Loading" ] + + Just (Partial _ _) -> + Html.div [] [ Html.text "Loading..." ] + + Just (Complete repos) -> + Html.ul [] <| + List.map + (\x -> Html.li [] [ Html.text x ]) + repos + + +getRepositories : Cmd Msg +getRepositories = + Paginated.send GotRepositories <| + Paginated.get + "http://git.phoenixinquis.net/api/v4/projects?per_page=5" + (field "name" string) +``` + + +## There's got to be a better way {#there-s-got-to-be-a-better-way} + +I've got it working, and it's working well. However, it's kind of a +pain to use. It's nice that I can play with the results as they come +in by peeking into the `Partial` structure, but it's a real chore to +have to stitch the results together in my application's `update` +method. It'd be nice if I could somehow encapsulate that behavior in +my request and not have to worry about the pagination at all in my +app. + +It just so happens that, with Tasks, I can. + +_Feel free to check out the full library documentation and code +referenced in this post [here](http://package.elm-lang.org/packages/correl/elm-paginated/1.0.1)._ + +_Continue on with part two, [Cleaner Recursive HTTP Requests with Elm +Tasks]({{< relref "cleaner-recursive-http-with-elm-tasks.md" >}})._ diff --git a/content/blog/sicp.md b/content/blog/sicp.md new file mode 100644 index 0000000..086c273 --- /dev/null +++ b/content/blog/sicp.md @@ -0,0 +1,76 @@ ++++ +title = "Adventuring Through SICP" +author = ["Correl Roush"] +date = 2015-01-01T00:00:00-05:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming", "lisp"] +draft = false ++++ + +Back in May, a coworker and I got the idea to start up a little +seminar after work every couple of weeks with the plan to set aside +some time to learn and discuss new ideas together, along with anyone +else who cared to join us. + + +## Learning Together {#learning-together} + +Over the past several months, we've read our way through the first +three chapters of the book, watched the [related video lectures](http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-001-structure-and-interpretation-of-computer-programs-spring-2005/video-lectures/), and +did (most of) the exercises. + +Aside from being a great excuse to unwind with friends after work +(which it is!), it's proved to be a great way to get through the +material. Doing a section of a chapter every couple of weeks is an +easy goal to meet, and meeting up to discuss it becomes something to +look forward to. We all get to enjoy a sense of accomplishment in +learning stuff that can be daunting or difficult to set aside time for +alone. + +The best part, by far, is getting different perspectives on the +material. Most of my learning tends to be solitary, so it's refreshing +to do it with a group. By reviewing the different concepts together, +we're able to gain insights and clarity we'd never manage on our +own. Even the simplest topics can spur interesting conversations. + + +## SICP {#sicp} + +Our first adventure together so far has been the venerable [Structure +and Interpretation of Computer Programs](http://mitpress.mit.edu/sicp/). This book had been on my todo +list for a long time, but never quite bubbled to the top. I'm glad to +have the opportunity to go through it in this format, since there's +plenty of time to let really get into the excercises and let the +lessons sink in. + +SICP was originally an introductory textbook for MIT computer +programming courses. What sets it apart from most, though, is that it +doesn't focus so much on learning a particular programming language +(while the book does use and cover MIT Scheme) as it does on +identifying and abstracting out patterns common to most programming +problems. Because of that, the book is every bit as useful and +illuminating as ever, especially now that functional paradigms are +re-entering the spotlight and means of abstracting and composing +systems are as important as ever. + + +## What's next? {#what-s-next} + +We've still got plenty of SICP left to get through. We've only just +gotten through Chapter 4, section 1, which has us building a scheme +interpreter **in** scheme, so there's plenty of fun left to be had +there. + +We're also staring to do some smaller, lunchtime review meetings +following the evening discussions to catch up the folks that can't +make it. I may also try sneaking in some smaller material, like +interesting blog posts, to keep things lively. + +--- + +If anyone's interested, I have the exercise work along with some notes +taken during the meetings [hosted online](http://sicp.phoenixinquis.net/). I apologize for the lack of +notes early on, I've been trying to get better at capturing memorable +excerpts and conversation topics recently. I may have to put some more +posts together later on summarizing what we discussed for each +chapter; if and when I do, they'll be posted on the [seminar website](http://extreme-tech-seminar.github.io/). diff --git a/content/blog/syncing.md b/content/blog/syncing.md new file mode 100644 index 0000000..89f8919 --- /dev/null +++ b/content/blog/syncing.md @@ -0,0 +1,85 @@ ++++ +title = "Keeping Files And Configuration In Sync" +author = ["Correl Roush"] +date = 2015-04-20T00:00:00-04:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["git"] +draft = false ++++ + +I have a few computers I use on a daily basis, and I like to keep the +same emacs and shell configuration on all of them, along with my org +files and a handful of scripts. Since I'm sure other people have this +problem as well, I'll share what I'm doing so anyone can learn from +(or criticise) my solutions. + + +## Git for configuration and projects {#git-for-configuration-and-projects} + +I'm a software developer, so keeping things in git just makes sense +to me. I keep my org files in a privately hosted git repository, and +[Emacs](https://www.gnu.org/software/emacs/) and [Zsh](http://www.zsh.org/) configurations in a [public repo on github](https://github.com/correl/dotfiles). My blog is +also hosted and published on github as well; I like having it cloned +to all my machines so I can work on drafts wherever I may be. + +My [.zshrc](https://github.com/correl/dotfiles/blob/master/.zshrc) installs [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh) if it isn't installed already, and sets +up my shell theme, path, and some other environmental things. + +My [Emacs configuration](https://github.com/correl/dotfiles/blob/master/.emacs.d/emacs.org) behaves similarly, making use of John +Wiegley's excellent [use-package](https://github.com/jwiegley/use-package) tool to ensure all my packages are +installed if they're not already there and configured the way I like +them. + +All I have to do to get running on a new system is to install git, +emacs and zsh, clone my repo, symlink the files, and grab a cup of +tea while everything installs. + + +## Bittorrent sync for personal settings & books {#bittorrent-sync-for-personal-settings-and-books} + +For personal configuration that doesn't belong in and/or is too +sensitive to be in a public repo, I have a folder of dotfiles and +things that I sync between my machines using [Bittorrent Sync](https://www.getsync.com/). The +dotfiles are arranged into directories by their purpose: + +```text +[correlr@reason:~/dotenv] +% tree -a -L 2 +. +├── authinfo +│   └── .authinfo.gpg +├── bin +│   └── .bin +├── emacs +│   ├── .bbdb +│   └── .emacs.local.d +├── mail +│   ├── .gnus.el +│   ├── .signature +├── README.org +├── .sync +│   ├── Archive +│   ├── ID +│   ├── IgnoreList +│   └── StreamsList +├── tex +│   └── texmf +├── xmonad +│   └── .xmonad +└── zsh + └── .zshenv +``` + +This folder structure allows my configs to be easily installed using +[GNU Stow](https://www.gnu.org/software/stow/) from my `dotenv` folder: + +```text +stow -vvS * +``` + +Running that command will, for each file in each of the directories, +create a symlink to it in my home folder if there isn't a file or +directory with that name there already. + +Bittorrent sync also comes in handy for syncing my growing [Calibre](http://calibre-ebook.com/) ebook +collection, which outgrew my [Dropbox](https://www.dropbox.com/) account a while back. diff --git a/content/blog/transmission-rss-and-xbmc.md b/content/blog/transmission-rss-and-xbmc.md new file mode 100644 index 0000000..f00c245 --- /dev/null +++ b/content/blog/transmission-rss-and-xbmc.md @@ -0,0 +1,118 @@ ++++ +title = "Transmission, RSS, and XBMC" +author = ["Correl Roush"] +date = 2011-04-27T00:01:00-04:00 +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming", "python"] +draft = false ++++ + +I'm a huge fan of [XBMC](http://www.xbmc.org/). My pc (currently running Ubuntu 10.04) has taken root in my +living room, piping all my movies and tv shows straight to my HDTV. + +While my pc is set up as a DVR using [MythTV](http://www.mythtv.org) to record shows off my FIOS box, it tends to be a little unreliable, which can suck when it's time to catch up on Daily Show and Colbert episodes. +I've had [Transmission](http://www.transmissionbt.com/) set up for a while for all my torrenting needs, and +I've even written an [XBMC script to manage torrents](https://github.com/correl/Transmission-XBMC), so I got to looking for +tools to track tv show torrent rss feeds. + + + +My first stop was [TED](http://ted.nu/). TED worked well enough, but would occasionally hang. +Since it's a GUI java app running in the taskbar, it would require me to dig +out my mouse and break out of full screen XBMC to fiddle with it. I eventually +got tired of dealing with TED and went back to prodding Myth. + +Recently I've been itching to reliably watch my shows again, so I checked around +for a simple command-line utility to track rss feeds and download torrents. +Finding none, I loaded up vim and threw together a python script to handle it +all for me. + +I also have another, simple script from when I was using TED (or just manually +downloading shows) which looks at completed torrents, compares their names with +the folders in my TV directory, and moves the shows into them for XBMC to see. + +A couple cron jobs and a few rss feeds later, and I've got all my shows +automatically delivered straight to XBMC for my lazy evening viewing pleasure. + +### trss.py +[Download](https://github.com/correl/trss/raw/master/trss.py) + +``` +Usage: + trss.py add [] + Adds an RSS feed to follow + rss-url: Full URL to the RSS feed + recent-items: (Optional) number of recent items to queue + for downloading + trss.py remove + Remove an RSS feed + index: Numeric index of the feed to remove as + reported by the list command + trss.py list + Displays a list of followed feeds + + trss.py download + Fetch all feeds and download new items + + trss.py set [ []] + Set or view configuration settings + Call without any arguments to list all settings and their values + Call with a setting and no value to see the current value for that setting + + Currently, the only used setting is 'download_dir', which allows you to set + a directory to store all retrieved torrents, such as a directory your + torrent application watches for new downloads. If 'download_dir' is not set, + the current directory will be used. +``` + +### transmission-tv.py +```python +#!/usr/bin/python +import os +import re + +import transmissionrpc + +TV_PATH = '/media/Gaia/Video/TV/' + +class TVShowCollection: + def __init__(self, path): + self.path = path + self.shows = os.listdir(path) + self.patterns = [[s.lower().replace(' ', '.?'), s] for s in sorted(self.shows, key=len, reverse=True)] + def match(self, filename): + for pattern, show in self.patterns: + if re.findall(pattern, filename.lower()): + return show + return None + +def move(self, ids, location): + """Move torrent data to the new location.""" + self._rpc_version_warning(6) + args = {'location': location, 'move': True} + self._request('torrent-set-location', args, ids, True) + +if float(transmissionrpc.__version__) < 0.4: + # The move function is not present in versions 0.3 and older + transmissionrpc.Client.move = move + +collection = TVShowCollection(TV_PATH) +client = transmissionrpc.Client() + +torrents = client.info() +for i, torrent in torrents.iteritems(): + status = torrent.status + if status not in ['seeding', 'stopped']: + continue + show = collection.match(torrent.name) + if show is None: + continue + path = '{0}{1}/'.format(TV_PATH, show) + if torrent.downloadDir.startswith(path): + continue + print 'Found {0} torrent \'{1}\' in show \'{2}\', moving...'.format(status, torrent.name, show) + result = client.move(i, path) + if status == 'seeding': + print 'Re-starting torrent to continue seeding' + client.start(i) +``` diff --git a/content/blog/types-in-python.md b/content/blog/types-in-python.md new file mode 100644 index 0000000..be9f35b --- /dev/null +++ b/content/blog/types-in-python.md @@ -0,0 +1,18 @@ ++++ +title = "Types in Python" +author = ["Correl Roush"] +keywords = ["emacs", "org-mode", "themes"] +tags = ["programming", "python"] +draft = true ++++ + +## Why Use Types? {#why-use-types} + + +## Success Typing {#success-typing} + + +## Running Mypy {#running-mypy} + + +## Specifying Types {#specifying-types}