Category Archives: geek

Experimenting my way to an awesome life

“The question I really want to answer is: How can I live a fuller life, a happier life, a more productive life?” said someone in a recent e-mail about Quantified Self.

This made me think: The ideal life differs from person to person. What kind of awesome life am I moving towards? What motivates my choices and experiments, and how can I explore and learn more effectively?

I have role models for this, so I can imagine what it looks like. I can look at the differences between our lives to get a better understanding of the gaps and divergences.

My parents have full, happy, productive, and significant lives (although I think my mom thinks that what she’s doing isn’t as awesome or as significant as what my dad does). They make things happen. In particular, my dad touches lots of people’s lives. He has this really big scope.

W- lives a full, happy, productive life. I think he focuses on doing a good job at work, doing the right thing, knowing (and applying!) all sorts of good knowledge, and being a great husband and dad. We’re probably not going to get added to any tribal epics or history books, but that’s okay. I tend to think of his scope as smaller, more local, and he’s totally awesome within it. He sometimes reaches beyond that scope to support interesting things, like Kickstarters for well-designed products.

I think I live a decently full, happy, and productive life as well. Definitely yes to the happy bit; yay for high genetic set-points for happiness, good coping mechanisms, and a tremendous amount of luck. I keep some slack in my life, so I don’t feel like it’s super-full or super-productive. But people tell me that I do a lot, so maybe this is like the way my mom’s not as sure about her own contributions. I could probably do more, but this is as good a start as any.

My scope tends to be similar to W-‘s, focusing on our little world. But I also have these odd outgrowths for things like Emacs, visual thinking, social business, Hacklab… These aren’t as driven as my dad’s initiatives or my friends’ startups. They’re more… curiosity-based, maybe? I enjoy exploring those playgrounds and sharing what I’m learning. I think W- is like this too – he follows his curiosity into new areas.

So, if that helps me understand a little of who I am now, what does that tell me about the future Sacha I’m gradually inching towards, and what experiments can help me learn more?

I imagine Awesome Sacha to be this capable, curious person with lots of skills, including practical DIY stuff. Her equanimity and optimism lets her handle whatever life throws at her (and learn from it!). Maybe she’s more involved in the community now, helping her favourite causes, but probably more from a position of lifting people up rather than going on crusades. She takes plenty of notes and shares them, helping other people learn faster and see the connections among different ideas.

If that’s a potentially interesting Future Sacha I could become, what can I track to measure my progress along the way, and what kinds of experiments could stretch me a little bit more towards that?

  • I can pick up and practise more skills: Cooking, sewing, electronics, DIY repair, etc. I can track this through journal entries, blog posts, comfort level, and decisions to do things myself versus asking or paying someone else to do things. For example, I now feel comfortable cooking, and I remember feeling a lot more uncertain about it before. I feel moderately okay about repairing small appliances and doing simple woodworking, but could use more practice. I have hardly any experience with plumbing or tiling.
  • I can observe more, and write about more of what I’m learning: The little hiccups and challenges in my life feel so much smaller than the ones that other people go through, and I usually don’t write about them. Keeping a journal (even for the small stuff) might result in interesting reading later on, though. I already bounce back pretty quickly. It might be interesting to see how I respond to larger and larger changes, though, so deliberately taking on more commitments and more risks can help me develop this part of my life.
  • I can help out more. I think it’s okay even if I don’t try to maximize utility on this for now. I’ll start with the things that resonate with me. It’s easy enough to track hours and money for this; maybe later I can add stories too.
  • I can get better at taking, organizing, and sharing my notes. I can see the gap in my note-taking by noticing when I’m annoyed that I can’t find my old notes (either because I hadn’t written them up properly or I didn’t make them findable enough). As for organizing and sharing my notes, perhaps I can track the number of longer guides I put together, and whether I can get the hang of working with outlines and pipelines…

Most of my little experiments come from looking at ideas that are close by and saying, “Hmm, that’s interesting. Maybe I can explore that.” Sometimes it helps to look a little further ahead–to sketch out an ideal life, or even just a slightly-better-than-this life–and to plan little steps forward, going roughly in the right direction. Some ideals fit you better than others do, and some ideals just won’t resonate with you. For example, I currently don’t wish to be a highly-paid jetsetting public speaker. Thinking about this helps you figure out what kind of future you might want, and maybe figure out a few ways to try it on for size and track your progress as you grow into it.

What kind of person would Awesome You be like, and how can you inch a little closer?

Continuous integration and code coverage for Emacs packages with Travis and Coveralls

Do you maintain an Emacs package hosted on Github? Would you like to get those confidence-building, bragging-rights-granting, other-developers-inspiring build: passing and coverage: 100% badges into your README file?

It turns out that this is pretty easy with ERT, Cask, Travis CI, undercover.el, and Coveralls.io.

  1. Log on to Travis and enable continuous integration for your repository.
  2. Log on to Coveralls.io and enable coverage testing for your repository.
  3. Set up a git branch, since you’ll probably be making lots of small commits while you smooth out the testing workflow.
  4. Define your tests with ERT. See https://github.com/abo-abo/tiny/blob/master/tiny-test.el for an example. For undercover support, you’ll want to include something like:
    (when (require 'undercover nil t)
      (undercover "tiny.el"))
    
  5. Define your dependencies with Cask. Include undercover. For example, here’s a simple Cask file:
    (source gnu)
    (source melpa)
    
    (development
      (depends-on "undercover"))
    
  6. Add a .travis.yml that specifies how to test your package on Travis. For example, see this .travis.yml and Makefile.
  7. Commit and push.
  8. Check your repository status in Travis to see if it ran properly.
  9. Check your coverage status in Coveralls.io to see if it displayed properly.
  10. Get the badge code from Travis and Coveralls, and add them to your README (probably using Markdown). You can get the badge code from Travis by clicking on your build status badge next to your repository name. Coveralls has prominent instructions for getting your badge. Yay!

Incidentally, if you want to see your test coverage locally, you can (require 'testcover) and then use testcover-this-defun or testcover-start to instrument the macros and functions for coverage. Run your tests, then use testcover-mark-all to look at the results. See the documentation in testcover.el to find out what the coloured overlays mean. Edebug has a test coverage tool too, so you can explore that one if you prefer it.

Additional notes on testing:

2015-02-03 Better Emacs Testing -- index card #testing #emacs

2015-02-03 Better Emacs Testing – index card #testing #emacs

2015-02-04 Yay, testing in Emacs -- index card #testing #emacs

2015-02-04 Yay, testing in Emacs – index card #testing #emacs

Resources:

Getting started with Emacs? Empty your cup

Frustrated with Emacs because you’re just not as productive as you are with your old editor? Copying configuration snippets from the Web in order to force it to work like what you’re used to, but running into problems with conflicting code or obscure error messages?

Here’s something I’ve realized. To learn Emacs well, try emptying your cup. This is the story as told on the C2 wiki:

A master was trying to explain something to a student. Now this student was not a brand new student, but a senior student who had learned many things. He had knowledge and experience aplenty to draw upon. But each time the master tried to explain something new to the student, the student kept trying to hold it up against his own notions of the way the world is and how it ought be, and he was unable to see the lessons in what the master was trying to teach him.

Finally, the master poured a full serving of tea into his own cup, and into the cup of the student. Then he told the student he wanted to give to him some of the tea from his own cup. He began pouring tea from his cup into the student’s cup, but the student’s cup was already full, and all the tea from the master’s cup spilled out over the cup onto the surface below.

The student said, “Master, you can’t pour anything into my cup until I empty it to make room for what you are trying to give me.”, and the master replied “Yes I know.” “And I can’t give you any new thoughts or ideas or perspectives on life’s lessons until you clear out some thoughts that are already teeming in your mind to make room for what I have to teach you.” Then the master paused for a brief moment, meeting the student’s eyes with his own knowing look and calmly but sternly said: ” If you truly seek understanding, then first, empty your cup!”

The student pondered for a moment with a look of absolute bewilderment. Then a look of enlightenment came over him, followed by a smile, and a look of receptiveness. The master started to explain again, and this time the student saw what the master was trying to say.

2015-01-13 Emacs and the beginner's mind -- index card #emacs #beginner

2015-01-13 Emacs and the beginner’s mind – index card #emacs #beginner

It’s natural to get frustrated when you expect something should work a particular way and it doesn’t, or you’re used to working quickly and you have to slow down. “Why can’t I use Ctrl-X to cut? Why is it called ‘killing text’? Why doesn’t it work like __?” I know what that’s like; even after years of using Emacs, I sometimes still struggle to configure things that people who use other editors take for granted.

Some differences seem easy to address with code you can get on the Net. But if you do that – if you give in to your initial discomfort – you may find yourself fighting more and more of Emacs as you go along, without the skills to properly understand or integrate your changes.

It’s better, I think, to approach Emacs as a beginner. Empty your cup and let go of your expectations. Pretend this is your first editor. Go through the tutorial. Start with the basics. Try doing things the Emacs way.

In the beginning, you might feel agonizingly slow. You may need to do this after hours instead of when everyone is counting on you to deliver a time-sensitive project. It’s okay to open up Vim or your previous editor so that you can get something done, while you keep learning Emacs in the background. It’s okay to use the menu and the toolbar while you get the hang of the most common keyboard shortcuts.

As you become familiar with the system, you learn how to work within it. Slowly configure things. Get used to the changes before you introduce more. Eventually, you’ll understand the configuration snippets that other people post. Reading Emacs Lisp is the first step to being able to modify Emacs Lisp, and from there you can learn how to write Emacs Lisp. But you don’t have to start knowing that right away. Many people use Emacs for years before writing Emacs Lisp, and many people use it without customizing it.

But if you start learning Emacs by getting frustrated with it and trying to force it to be what you want, you might never get to the point where you can enjoy it and work with it. Be a beginner. Give yourself time and space to learn, even if you feel you’re learning slowly. Then, as you improve your understanding and skills, you’ll learn how to coax Emacs to fit you even better.

Let’s have a virtual Emacs conference in August – help me make it happen!

Why August? It’s an arbitrary target, although it tickles my brain to think about celebrating my 32nd birthday with awesome people sharing awesome ideas. (Incidentally, I’ll also reach the point of having been using Emacs for about half my life – doubly neat!)

Anyway. I think it would be great to have some kind of knowledge-swapping thing. Since I’m not particularly keen on travelling, not everyone can make it out to Canada, and it’s hard to make awesome in-person conference recordings anyway, maybe a virtual conference would be a great bet. I’m willing to spend what I would have paid for airfare on things like organization, speaker honoraria, and other good things.

I enjoyed the Emacs Conference in 2013, and I think we should figure out how to have these kinds of get-togethers more often. Emacs Chats and Emacs Hangouts are tiny steps in that direction, and I’d appreciate help in making this and many other community-ish things even better. =)

2015-02-02 Imagining an Emacs conference -- index card #emacs #conference #plans #organizing-people

2015-02-02 Imagining an Emacs conference – index card #emacs #conference #plans #organizing-people

So here’s what I imagine a virtual Emacs conference might be like. People volunteer, and somehow we organize a schedule of fascinating talks. This could be a full day, or maybe we’d spread it out over a couple of half-days (maybe even scheduled for different timezones so that everyone has something they can interact with life). We use Google Hangout on Air or a similar platform that can stream and automatically record. There’s the speaker with slides and screensharing, and there’s a moderator who can pick up questions from IRC and Google Hangout in order to ask them out loud. We might even be able to pull off panel discussions. Afterwards, there’s a playlist and a webpage with all the videos/MP3s/OGGs, and people can share their notes/discussions/follow-ups.

All this is immensely doable with the technology we have today. For free, even. Anyway, the technology should be okay.

What about topics? Here’s what I’m particularly curious about:

  • New features in Emacs 25 (and beyond)
  • Demos, workflows, and setup tips for popular toolsets/needs (ex: awesome setups for Clojure/CL, Rails, Javascript, C++, Java, writing, research)
  • Fascinating uses of Emacs
  • Good practices for Emacs Lisp: automated testing, performance, reliability, coding style/idioms (maybe even workshops along these lines)
  • Demystifying cool stuff: how core modules work, how to contribute to Emacs
  • A hackathon: get package.el headers on everything! fix bugs! make improvements! document!
  • Emacs microhabits, learning
  • Workshops: intermediate/advanced use of Org Mode, Calc, ESS, and other powerful packages
  • Emacs community-building and sharing

And people can suggest other topics, too. =) Maybe we can even figure out some kind of unconference setup: people suggesting topics they can share, quickly voting on what they’re interested in, and breaking up into separate “rooms” to share/discuss.

2015-02-02 Making a virtual Emacs conference happen -- index card #emacs #organizing-people #conference #planning #questions

2015-02-02 Making a virtual Emacs conference happen – index card #emacs #organizing-people #conference #planning #questions

An Emacs conference would be awesome. Here are my (pitiful) excuses for why I haven’t figured out how to organize one yet, and things I want to figure out (especially with people’s help):

  • Who might be interested in speaking? How does one go about organizing speakers, schedules, topics, tech, etc? I’m still slowly getting the hang of reaching out to people and inviting them to Emacs Chats.
  • Will people show up and ask questions? Part of me is worried that I’ll pick entirely the wrong date/time/topics and there’ll be awkward silence.
  • How can we handle questions? IRC, probably, so that people can chat about stuff too. I think I’m pretty comfortable at keeping an eye on stuff and repeating people’s questions. Or maybe people can join the Emacs Hangout if we can get the flow to be smooth?
  • Will the experience be pleasant and worthwhile? Maybe not as goosebump-inducingly awesome as being in a room with 80+ other Emacs geeks, but I think it will be worthwhile.
  • How can we harvest and share resources? Hangouts on Air will put videos on Youtube automatically, so that’ll be taken care of.
  • What would we need to do leading up to it? Something about a mailing list, and a webpage, and lots and lots of coordination.
  • Do I need to gain experience/confidence with smaller steps? Or maybe find some accomplices?

Of course, if someone wants to organize an in-person one, that’s cool too. Especially in Toronto. That would be awesome. =) (Although I might be able to get to New York or similar places too…)

My evil plans for a conference like this include:

  • Getting cool stuff out of people’s heads/fingers/configs and into a form that other people can look at, learn from, and link to
  • Ditto for good practices that can help us develop better code (performance)
  • Discovering resources and tips we might not have found out about otherwise
  • Sparking more conversations and follow-ups
  • Spurring people to create and share more resources

What could help the Emacs community learn even faster?

2015-02-01 Accelerating the Emacs community -- index card #accelerating #emacs

2015-02-01 Accelerating the Emacs community – index card #accelerating #emacs

How can we get more people sharing their configs, or learning from other people’s configs? How can we make it easier for people to share through blog posts, videos, animated GIFs, and presentations? How can we create spaces for people to connect, either with virtual meetups or in person? How can we swap interesting ideas, workflows, and mental habits? How can we improve our skills? How can we keep the conversation going?

Mm. Figuring out how to do virtual conferences might be a good start. Also, I’ve got this idea noodling around in my head on having some kind of an intermediate/advanced Org Mode workshop: something that covers clocking workflows, table calculations, literate programming, data analysis, publishing. Figuring out how to do virtual workshops would be awesome too.

Okay. First things first. Some kind of date and some kind of time, and some kind of help sorting out a schedule. August 8 and/or August 15, maybe? If librarians can hold an online conference through Google Hangouts, we should be able to figure this out too. (Librarians are super-cool!) If you have lots of experience in organizing virtual conferences or you have ideas for how to make this less intimidating for a non-organizer-y introvert, I’d love to hear from you in the comments or at [email protected]. Let’s make this happen!

Digital index piles with Emacs: Rapid categorization of Org Mode items

Somewhat daunted by the prospect of categorizing more than a hundred sketches and blog posts for my monthly review, I spent some time figuring out how to create the digital equivalent of sorting index cards into various piles.

2015-02-01 Digital piles of index card -- index card #indexing #organization #pkm

2015-02-01 Digital piles of index cards – index card #indexing #organization #pkm

In fact, wouldn’t it be super-cool if the items could automatically guess which category they should probably go in, prompting me only if it wasn’t clear?

I wanted to write a function that could take a list structured like this:

  • Keyword A
    • Previous links
  • Keyword B
    • Previous links
  • Link 1 with Keyword A
  • Link 2 with Keyword B
  • Link 3 with Keyword A
  • Link 4

It should file Link 1 and 3 under Keyword A, Link 2 under Keyword B, and prompt me for the category for Link 4. At that prompt, I should be able to select Keyword A or Keyword B, or specify a new category.

Inspired by John Kitchin’s recent post on defining a Helm source, I wanted to get it to work with Helm.

First step: I needed to figure out the structure of the list, maybe including a sample from the category to make it clearer what’s included. org-list.el seemed to have useful functions for this. org-list-struct gave me the structure of the current list. Let’s say that a category is anything whose text does not match org-bracket-link-regexp.

(defun sacha/org-get-list-categories ()
  "Return a list of (category indent matching-regexp sample).
List categories are items that don't contain links."
  (let ((list (org-list-struct)) last-category results)
    (save-excursion
      (mapc
       (lambda (x)
         (goto-char (car x))
         (let ((current-item
                (buffer-substring-no-properties
                 (+ (point)
                    (elt x 1)
                    (length (elt x 2)))
                 (line-end-position))))
           (if (string-match
                org-bracket-link-regexp
                (buffer-substring-no-properties
                 (point)
                 (line-end-position)))
               ;; Link - update the last category
               (when last-category
                 (if (< (elt x 1) (elt last-category 1))
                     (setq results
                           (cons (append last-category
                                         (list
                                          (match-string-no-properties
                                           3
                                           (buffer-substring-no-properties
                                            (point)
                                            (line-end-position)))))
                                 (cdr results))))
                 (setq last-category nil))
             ;; Category
             (setq results
                     (cons
                      (setq last-category
                            (list
                             current-item
                             (elt x 1)
                             (concat "^"
                                     (make-string (elt x 1) ?\ )
                                     (regexp-quote
                                      (concat (elt x 2)
                                              current-item))
                                     "$")))
                      results)))))
       list))
    results))

The next step was to write a function that guessed the list category based on the item text, and moved the item there.

(defvar sacha/helm-org-list-candidates nil)
(defun sacha/helm-org-list-categories-init-candidates ()
  "Return a list of categories from this list in a form ready for Helm."
  (setq sacha/helm-org-list-candidates
        (mapcar (lambda (x)
                  (cons (if (elt x 3)
                            (format "%s - %s" (car x) (elt x 3))
                          (car x))
                        x))
                (sacha/org-get-list-categories))))

(defun sacha/org-move-current-item-to-category (category)
  (when category
    (let* ((beg (line-beginning-position))
           (end (line-end-position))
           (string (buffer-substring-no-properties beg end)))
      (save-excursion
        (when (re-search-backward (elt category 2) nil t)
          (delete-region beg (min (1+ end) (point-max)))
          (forward-line 1)
          (insert (make-string (+ 2 (elt category 1)) ?\ )
                  string "\n")))) t))

(defun sacha/org-guess-list-category (&optional categories)
  (interactive)
  (require 'cl-lib)
  (unless categories
    (setq categories
          (sacha/helm-org-list-categories-init-candidates)))
  (let* ((beg (line-beginning-position))
         (end (line-end-position))
         (string (buffer-substring-no-properties beg end))
         (found
          (cl-member string
                     categories
                     :test
                     (lambda (string cat-entry)
                       (string-match (regexp-quote (downcase (car cat-entry)))
                                     string)))))
    (when (car found)
      (sacha/org-move-current-item-to-category
       (cdr (car found)))
      t)))

After that, I wrote a function that used Helm to prompt me for a category in case it couldn’t guess the category. It took me a while to figure out that I needed to use :init instead of :candidates because I wanted to read information from the buffer before Helm kicked in.

(setq sacha/helm-org-list-category-source
      (helm-build-sync-source
          "Non-link categories in the current list"
        :init 'sacha/helm-org-list-categories-init-candidates
        :candidates 'sacha/helm-org-list-candidates
        :action 'sacha/org-move-current-item-to-category
        :fuzzy-match t))

(defun sacha/org-guess-uncategorized ()
  (interactive)
  (sacha/helm-org-list-categories-init-candidates)
  (let (done)
    (while (not done)
      (save-excursion
        (unless (sacha/org-guess-list-category sacha/helm-org-list-candidates)
          (unless
              (helm :sources
                    '(sacha/helm-org-list-category-source
                      sacha/helm-org-list-category-create-source))
            (setq done t))))
      (unless done
        (setq done (not (looking-at "^[-+] \\[")))))))

The :action above refers to this function, which creates a category if it doesn’t exist yet.

(setq sacha/helm-org-list-category-create-source
      (helm-build-dummy-source
          "Create category"
        :action (helm-make-actions
                 "Create category"
                 (lambda (candidate)
                   (save-excursion
                     (let* ((beg (line-beginning-position))
                            (end (line-end-position))
                            (string (buffer-substring beg end)))
                       (delete-region beg (min (1+ end) (point-max)))
                       (org-beginning-of-item-list)
                       (insert "- " candidate "\n  " string "\n")))
                   (sacha/helm-org-list-categories-init-candidates)))))

I’m new to fiddling with Helm, so this implementation is not the best it could be. But it’s nifty and it works the way I want it to, hooray! Now I can generate a list of blog posts and unblogged sketches, categorize them quickly, and then tweak the categorizations afterwards.

2015-02-01 Index card sketches and monthly reviews -- index card #organization #pkm #indexing

2015-02-01 Index card sketches and monthly reviews – index card #organization #pkm #indexing

You can see the results in my January 2015 review, or check my config to see if the code has changed.

My next step for learning more about Helm sources is probably to write a Helm command that creates a montage of selected images. John Kitchin has a post about handling multiple selection in Helm, so I just need to combine that with my code for using Imagemagick to create a montage of images. Whee!

De-dupe and link: Using the Flickr API to neaten up my archive and link sketches to blog posts

I’ve been thinking about how to manage the relationships between my blog posts and my Flickr sketches. Here’s the flow of information:

2015-01-06 Figuring out information flow -- index card

2015.01.06 Figuring out information flow – index card

I scan my sketches or draw them on the computer, and then I upload these sketches to Flickr using photoSync, which synchronizes folders with albums. I include these sketches in my outlines and blog posts, and I update my index of blog posts every month. I recently added a tweak to make it possible for people to go from a blog post to its index entry, so it should be easier to see a post in context. I’ve been thinking about keeping an additional info index to manage blog posts and sketches, including unpublished ones. We’ll see how well that works. Lastly, I want to link my Flickr posts to my blog posts so that people can see the context of the sketch.

My higher goal is to be able to easily see the open ideas that I haven’t summarized or linked to yet. There’s no shortage of new ideas, but it might be interesting to revisit old ones that had a chance to simmer a bit. I wrote a little about this in Learning from artists: Making studies of ideas. Let me flesh out what I want this archive to be like.

2015-01-05 Thinking about my archive -- index card
2015.01.05 Thinking about my archive

When I pull on an idea, I’d like to be able to see other open topics attached to it. I also want to be able to see open topics that might jog my memory.

How about the technical details? How can I organize my data so that I can get what I want from it?

2015-01-05 Figuring out the technical details of this idea or visual archive I want -- index card
2015.01.05 Figuring out the technical details of this idea or visual archive I want – index card

Because blog posts link to sketches and other blog posts, I can model this as a directed graph. When I initially drew this, I thought I might be able to get away with an acyclic graph (no loops). However, since I habitually link to future posts (the time traveller’s problem!), I can’t make that simplifying assumption. In addition, a single item might be linked from multiple things, so it’s not a simple tree (and therefore I can’t use an outline). I’ll probably start by extracting all the link information from my blog posts and then figuring out some kind of Org Mode-based way to update the graph.

2015-01-07 Mapping the connections in my blog -- index card
2015.01.07 Mapping the connections in my blog – index card

To get one step closer to being able to see open thoughts and relationships, I decided that my sketches on Flickr:

  • should not have duplicates despite my past mess-ups, so that:
    • I can have an accurate count
    • it’s easier for me to categorize
    • people get less confused
  • should have hi-res versions if possible, despite the IFTTT recipe I tried that imported blog posts but unfortunately picked up the low-res thumbnails instead of the hi-res links
  • should link to the blog posts they’re mentioned in, so that:
    • people can read more details if they come across a sketch in a search
    • I can keep track of which sketches haven’t been blogged yet

I couldn’t escape doing a bit of manual cleaning up, but I knew I could automate most of the fiddly bits. I installed node-flickrapi and cheerio (for HTML parsing), and started playing.

Removing duplicates

Most of the duplicates had resulted from the Great Renaming, when I added tags in the form of #tag1 #tag2 etc. to selected filenames. It turns out that adding these tags en-masse using Emacs’ writable Dired mode broke photoSync’s ability to recognize the renamed files. As a result, I had files like this:

  • 2013-05-17 How I set up Autodesk Sketchbook Pro for sketchnoting.png
  • 2013-05-17 How I set up Autodesk Sketchbook Pro for sketchnoting #tech #autodesk-sketchbook-pro #drawing.png

This is neatly resolved by the following Javascript:

exports.trimTitle = function(str) {
    return str.replace(/ --.*$/g, '').replace(/#[^ ]+/g, '').replace(/[- _]/g, '');
};

and a comparison function that compared the titles and IDs of two photos:

exports.keepNewPhoto = function(oldPhoto, newPhoto) {
    if (newPhoto.title.length > oldPhoto.title.length)
        return true;
    if (newPhoto.title.length < oldPhoto.title.length)
        return false;
    if (newPhoto.id < oldPhoto.id) 
        return true;
    return false;
};

So then this code can process the photos:

exports.processPhoto = function(p, flickr) {
    var trimmed = exports.trimTitle(p.title);
    if (trimmed && hash[trimmed] && p.id != hash[trimmed].id) {
        // We keep the one with the longer title or the newer date
        if (exports.keepNewPhoto(hash[trimmed], p)) {
            exports.possiblyDeletePhoto(hash[trimmed], flickr);
            hash[trimmed] = p;
        }
        else if (p.id != hash[trimmed].id) {
            exports.possiblyDeletePhoto(p, flickr);
        }
    } else {
        hash[trimmed] = p;
    }
};

You can see the code on Gist: duplicate_checker.js.

High-resolution versions

I couldn’t easily automate this, but fortunately, the IFTTT script had only imported twenty images or so, clearly marked by a description that said: “via sacha chua :: living an awesome life…”. I searched for each image, deleting the low-res entry if a high-resolution image was already in the system and replacing the low-res entry if that was the only one there.

Linking to blog posts

This was the trickiest part, but also the most fun. I took advantage of the fact that WordPress transforms uploaded filenames in a mostly consistent way. I’d previously added a bulk view that displayed any number of blog posts with very little additional markup, and I modified the relevant code in my theme to make parsing easier.

See this on Gist:

/**
 * Adds "Blogged" links to Flickr for images that don't yet have "Blogged" in their description.
 * Command-line argument: URL to retrieve and parse
 */

var secret = require('./secret');
var flickrOptions = secret.flickrOptions;
var Flickr = require("flickrapi");
var fs = require('fs');
var request = require('request');
var cheerio = require('cheerio');
var imageData = {};
var $;

function setDescriptionsFromURL(url) {
  request(url, function(error, response, body) {
    // Parse the images
    $ = cheerio.load(body);
    $('article').each(function() {
      var prettyLink = $(this).find("h2 a").attr("href");
      if (!prettyLink.match(/weekly/i) && !prettyLink.match(/monthly/i)) {
        collectLinks($(this), prettyLink, imageData);
      }
    });
    updateFlickrPhotos();
  });
}

function updateFlickrPhotos() {
    Flickr.authenticate(flickrOptions, function(error, flickr) {
      flickr.photos.search(
        {user_id: flickrOptions.user_id,
         per_page: 500,
         extras: 'description',
         text: ' -blogged'}, function(err, result) {
           processPage(result, flickr);
           for (var i = 2 ; i < result.photos.pages; i++) {
             flickr.photos.search(
               {user_id: flickrOptions.user_id, per_page: 500, page: i,
                extras: 'description', text: ' -blogged'},
               function(err, result) {
                 processPage(err, result, flickr);
               });
           }
         });
    });
}

function collectLinks(article, prettyLink, imageData) {
  var results = [];
  article.find(".body a").each(function() {
    var link = $(this);
    if (link.attr('href')) {
      if (link.attr('href').match(/sachachua/)
          || !link.attr('href').match(/^http/)) {
        imageData[exports.trimTitle(link.attr('href'))] = prettyLink;
      } else if (link.attr('href').match(/flickr.com/)) {
        imageData[exports.trimTitle(link.text())] = prettyLink;
      }
    }
  });
  return results;
}

exports.trimTitle = function(str) {
  return str.replace(/^.*\//, '').replace(/^wpid-/g, '').replace(/[^A-Za-z0-9]/g, '').replace(/png$/, '').replace(/[0-9]$/, '');
};

function processPage(result, flickr) {
  if (!result) return;
  for (var i = 0; i < result.photos.photo.length; i++) {
    var p = result.photos.photo[i];
    var trimmed = exports.trimTitle(p.title);
    var noTags = trimmed.replace(/#.*/g, '');
    var withTags = trimmed.replace(/#/g, '');
    var found = imageData[noTags] || imageData[withTags];
    if (found) {
      var description = p.description._content;
      if (description.match(found)) continue;
      if (description) {
        description += " - ";
      }
      description += '<a href="' + found + '">Blogged</a>';
      console.log("Updating " + p.title + " with " + description);
      flickr.photos.setMeta(
        {photo_id: p.id,
         description: description},
        function(err, res) {
          if (err) { console.log(err, res); }
        } );
    }
  }
}

setDescriptionsFromURL(process.argv[2]);

And now sketches like 2013-11-11 How to think about a book while reading it are now properly linked to their blog posts. Yay! Again, this script won’t get everything, but it gets a decent number automatically sorted out.

Next steps:

  • Run the image extraction and set description scripts monthly as part of my indexing process
  • Check my list of blogged images to see if they’re matched up with Flickr sketches, so that I can identify images mysteriously missing from my sketchbook archive or not correctly linked

Yay code!