Tonλ's blog May the λ be with you

Literate org-trello

by @ardumont on

To help improving the maintenance of org-trello, we will describe it using literate programming. For this, we will use org-mode and babel.

We'll begin by:

  • a simple how to
  • a little description
  • the license
  • a manual
  • a full description step-by-step of the code (may need to find another organisation that the code order)

1 Howto

  • Open this file
  • Tangle (extract) the code

M-x org-babel-tangle or C-c C-v t

Or

``` sh make tangle ```

2 Description

org-trello is an emacs's org minor mode to permit the 2-way synchonization between org buffer and a trello board. The headers needs to start with ;;; as a prerequisite for packaging. This is an important step where we describe the different information about the project:

  • principal author
  • maintainer
  • current version
  • dependencies
;;; org-trello.el --- Org minor mode to synchronize with trello

;; Copyright (C) 2013 Antoine R. Dumont <eniotna.t AT gmail.com>

;; Author: Antoine R. Dumont <eniotna.t AT gmail.com>
;; Maintainer: Antoine R. Dumont <eniotna.t AT gmail.com>
;; Version: 0.1.1
;; Package-Requires: ((org "8.0.7") (dash "1.5.0") (request "0.2.0") (cl-lib "0.3.0") (json "1.2"))
;; Keywords: org-mode trello sync org-trello
;; URL: https://github.com/org-trello/org-trello

3 License

First, org-trello is a free software under the GPL v3.

;; This file is NOT part of GNU Emacs.

;; 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.

4 Manual

A fast setup that we, end-users, can see from the package installation procedure.

;;; Commentary:

;; Minor mode for org-mode to sync org-mode and trello
;;
;; 1) Add the following to your emacs init file
;; (require 'org-trello)
;;
;; Automatically
;; 2) Once - Install the consumer-key and the read-write token for org-trello to be able to work in your name with your trello boards (C-c o i)
;; M-x org-trello/install-key-and-token
;;
;; 3) Once per org-mode file/board you want to connect to (C-c o I)
;; M-x org-trello/install-board-and-lists-ids
;;
;; *Beware* you must setup your trello board with the name you use as keywords (TODO, DONE e.g) on your org-mode file.
;;
;; 4) You can also create a board directly from a org-mode buffer (C-c o b)
;; M-x org-trello/create-board
;;

5 Code

5.1 Setup

First, we will begin simply by showing the dependencies. Then the personal setup that the user can override.

  1. Library dependencies

    We depend on:

    lib description
    org As org-trello is a org minor mode
    json The transit of data to trello's api is in json
    dash To provide some specific lisp constructs
    request Awesome http client
    cl-lib To provide some common-lisp functions
    parse-time Some date time manipulation
    ;;; Code:
    
    (require 'org)
    (require 'json)
    (require 'dash)
    (require 'request)
    (eval-when-compile (require 'cl-lib))
    (require 'parse-time)
    
    
  2. Personal setup

    At the moment, we have only one possible setup. This is relative to the checklist behaviour. By default, the status of the checklist overrides the item's status.

    
    
    ;; #################### overriding setup
    
    (defvar *ORGTRELLO-CHECKLIST-UPDATE-ITEMS* t
      "A variable to permit the checklist's status to be pass along to its items. t, if checklist's status is DONE, the items are updated to DONE (org-mode buffer and trello board), nil only the items's status is used.
      To deactivate such behavior, update in your init.el:
      (require 'org-trello)
      (setq *ORGTRELLO-CHECKLIST-UPDATE-ITEMS* nil)")
    

    If the user does not want this, he/she can modify his/her setup in his/her emacs startup file:

    (setq *ORGTRELLO-CHECKLIST-UPDATE-ITEMS* nil)
    

5.2 Namespace

As each emacs developer knows, there is no real namespace in emacs-lisp. But, we like to separate function depending on perimeters, so we tried to keep the code as much separated as we can.

At the moment, we sliced the code into 6 namespaces:

name description
org-trello Main namespace which describe the minor mode, in charge of controls before executing anything
orgtrello Primitive routines that executes the code without checks
orgtrello-query Interface to the http request
orgtrello-api Interface to the trello api
orgtrello-data Interface to the org data
orgtrello-hash Utility interface to simplify the construction of data

5.3 org-trello

This is the main entry namespace of org-trello. Starting with the minor mode definition, we will show each higher interactive command after this.

  1. Minor mode

    As we said in the description step, org-trello is a minor mode.

    1. Description

      Simply put, we offer a simple interface to the user through a map. We declare the minor mode to have 'ot' as the emacs modeline name. And we propose default bindings for the user to access the org-trello routine.

      ;;;###autoload
      (define-minor-mode org-trello-mode "Sync your org-mode and your trello together."
        :lighter " ot" ;; the name on the modeline
        :keymap  (let ((map (make-sparse-keymap)))
                   ;; binding will change
                   (define-key map (kbd "C-c o i") 'org-trello/install-key-and-token)
                   (define-key map (kbd "C-c o I") 'org-trello/install-board-and-lists-ids)
                   (define-key map (kbd "C-c o b") 'org-trello/create-board)
                   (define-key map (kbd "C-c o c") 'org-trello/create-simple-entity)
                   (define-key map (kbd "C-c o C") 'org-trello/create-complex-entity)
                   (define-key map (kbd "C-c o s") 'org-trello/sync-to-trello)
                   (define-key map (kbd "C-c o S") 'org-trello/sync-from-trello)
                   (define-key map (kbd "C-c o k") 'org-trello/kill-entity)
                   (define-key map (kbd "C-c o h") 'org-trello/help-describing-bindings)
                   (define-key map (kbd "C-c o d") 'org-trello/check-setup)
                   ;; define other bindings...
                   map)
        :after-hook (message "ot is on! To begin with, hit C-c o h or M-x 'org-trello/help-describing-bindings"))
      
      (add-hook 'org-mode-hook 'org-trello-mode)
      
      (message "org-trello loaded!")
      
      
    2. Override

      If the user is not satisfied with the default bindings, he/she can always override in his/her own setup files. I kept the default one but the idea is to replace those bindings by the one you want.

      (define-key org-trello-mode-map (kbd "C-c o i") 'org-trello/install-key-and-token)
      (define-key org-trello-mode-map (kbd "C-c o I") 'org-trello/install-board-and-lists-ids)
      (define-key org-trello-mode-map (kbd "C-c o b") 'org-trello/create-board)
      (define-key org-trello-mode-map (kbd "C-c o c") 'org-trello/create-simple-entity)
      (define-key org-trello-mode-map (kbd "C-c o C") 'org-trello/create-complex-entity)
      (define-key org-trello-mode-map (kbd "C-c o s") 'org-trello/sync-to-trello)
      (define-key org-trello-mode-map (kbd "C-c o S") 'org-trello/sync-from-trello)
      (define-key org-trello-mode-map (kbd "C-c o k") 'org-trello/kill-entity)
      (define-key org-trello-mode-map (kbd "C-c o h") 'org-trello/help-describing-bindings)
      (define-key org-trello-mode-map (kbd "C-c o d") 'org-trello/check-setup)
      
  2. Help

    Let's begin simple, we offer an interactive command to display the current possible bindings. Hitting C-c o h, this will display a simple message on the minibuffer.

    (defun org-trello/help-describing-bindings ()
      "A simple message to describe the standard bindings used."
      (interactive)
      (message
    "C-c o i - M-x org-trello/install-key-and-token       - Install the keys and the access-token.
    C-c o I - M-x org-trello/install-board-and-lists-ids - Select the board and attach the todo, doing and done list.
    C-c o b - M-x org-trello/create-board                - Create interactively a board and attach the org-mode file to this trello board.
    C-c o c - M-x org-trello/create-simple-entity        - Create/Update an entity (card/checklist/item) depending on its level and status. Do not deal with level superior to 4.
    C-c o C - M-x org-trello/create-complex-entity       - Create/Update a complete entity card/checklist/item and its subtree (depending on its level).
    C-c o s - M-x org-trello/sync-to-trello              - Synchronize the org-mode file to the trello board (org-mode -> trello).
    C-c o S - M-x org-trello/sync-from-trello            - Synchronize the org-mode file from the trello board (trello -> org-mode).
    C-c o k - M-x org-trello/kill-entity                 - Kill the entity (and its arborescence tree).
    C-c o d - M-x org-trello/check-setup                 - Simple routine to check that the setup is ok. If everything is ok, will simply display 'Setup ok!'
    C-c o h - M-x org-trello/help-describing-bindings    - This help message."))
    
    
  3. Install the consumer-key and the read/write access token

    One of the first interaction we must have with org-trello is the setup. For this, we declare an interactive command. We delegate the code to the function org-trello/--msg-deco-control-and-do. This will:

    • log the actions "Setup key and token" in the mini-buffer.
    • execute no control as none is needed (thus the nil as second parameter)
    • as there are no control, directly execute the 'org-trello/do-install-key-and-token.
    • as there is writing involve, we ask to save the buffer at the end (t)
    (defun org-trello/install-key-and-token ()
      "No control, trigger the setup installation of the key and the read/write token."
      (interactive)
      (org-trello/--msg-deco-control-and-do "Setup key and token" nil 'orgtrello/do-install-key-and-token t))
    
    
  4. Decorator/Controller

    As we've seen before, we have a higher-order function org-trello/--msg-deco-control-and-do which is in charge of:

    • displaying the message msg in the mini-buffer
    • ask for the org-trello/--control-and-do function to execute
    • displaying the result string from the call of the previous function if there is some
    • optionally, we can ask for saving the buffer through the flag save-buffer-p
    
    
    ;; #################### org-trello
    
    (defun org-trello/--msg-deco-control-and-do (msg control-fns fn-to-control-and-execute &optional save-buffer-p)
      "A simple decorator function to display message in mini-buffer before and after the execution of the control"
      (message (concat msg "..."))
      (let ((org-trello/--result-action (org-trello/--control-and-do control-fns fn-to-control-and-execute)))
        ;; do we have to save the buffer
        (if save-buffer-p
            (progn
              (save-buffer)
              (org-mode-restart)))
        (if (string-or-null-p org-trello/--result-action)
          (message org-trello/--result-action)
          (message (concat msg " - done!")))))
    
    

    This is the main function which is in charge:

    • executing a list of controls control-fns
    • if there is no control or the controls are ok, execute the function fn-to-control-and-execute
    • returns the resulting string from the execution of the function
    (defun org-trello/--control-and-do (control-fns fn-to-control-and-execute)
      "Execute the function fn if control-fns is nil or if the result of apply every function to fn is ok."
      (if control-fns
          (let* ((org-trello/--error-messages (--filter (not (equal :ok (funcall it))) control-fns)))
            (if org-trello/--error-messages
                ;; there are some trouble, we display all the error messages to help the user understand the problem
                (message "List of errors:\n %s" (--mapcat (concat "- " it "\n") org-trello/--error-messages))
              ;; ok execute the function as the controls are ok
              (funcall fn-to-control-and-execute)))
        ;; no control, we simply execute the function
        (funcall fn-to-control-and-execute)))
    
    
  5. Setup trello

    To use trello, we need to either install a trello board or create one. In either case, this will do some action and update the org-mode buffer with some needed metadata:

    • board-id
    • board-name (for the user to see which board he/she uses)
    • every keyword (org) / list id (trello)
    1. Install the board

      To install a trello board, we need:

      • to display a message on the minibuffer "Install boards and lists"
      • execute the loading of the setup via setup-properties function (no control but a setup)
      • execute the control of the key and access-token are ok
      • if everything is ok, do install the board and lists on the trello buffer
      • as some update of the org-mode buffer is done, we ask for the buffer to be saved
      (defun org-trello/install-board-and-lists-ids ()
        "Control first, then if ok, trigger the setup installation of the trello board to sync with."
        (interactive)
        (org-trello/--msg-deco-control-and-do
           "Install boards and lists"
           '(orgtrello/--setup-properties orgtrello/--control-keys)
           'orgtrello/do-install-board-and-lists
           t))
      
      
    2. Create a board

      To create a trello board from scratch, we need:

      • to display a message on the minibuffer "Create boards and lists"
      • execute the loading of the setup via setup-properties function (no control but a setup)
      • execute the control of the key and access-token are ok
      • if everything is ok, do create the board and lists on the trello buffer
      • as some update of the org-mode buffer is done, we ask for the buffer to be saved
      (defun org-trello/create-board ()
        "Control first, then if ok, trigger the board creation."
        (interactive)
        (org-trello/--msg-deco-control-and-do
           "Create board and lists"
           '(orgtrello/--setup-properties orgtrello/--control-keys)
           'orgtrello/do-create-board-and-lists
           t))
      
      
  6. Check the installation

    Now that we setuped the org buffer to work with a trello board, we can ensure that the setup is ok. For this, we simply call the org-trello/--control-and-do routine with:

    • orgtrello/--setup-properties which will load the metadata from the file
    • orgtrello/--control-keys to ensure the key and access token are loaded
    • orgtrello/--control-properties to ensure the properties are properly setuped

    If any of those are badly setuped, a message will explicit the problem. Otherwise, a simple message "Setup ok!" will be displayed on the minibuffer.

    (defun org-trello/check-setup ()
      "Check the current setup."
      (interactive)
      (org-trello/--control-and-do
         '(orgtrello/--setup-properties orgtrello/--control-keys orgtrello/--control-properties orgtrello/--control-encoding)
         (lambda () (message "Setup ok!"))))
    
    
  7. Sync a simple entity

    To create/synchronize a simple entity (without its arborescence), we need to ensure some standard controls are ok. Then we can call the primitive routine orgtrello/do-create-simple-entity to do the actual creation. At last, saving the buffer as the creation involves some buffer updates (with the trello id).

    (defun org-trello/create-simple-entity ()
      "Control first, then if ok, create a simple entity."
      (interactive)
      (org-trello/--msg-deco-control-and-do
         "Synchronizing entity"
         '(orgtrello/--setup-properties orgtrello/--control-keys orgtrello/--control-properties orgtrello/--control-encoding)
         (lambda () (orgtrello/do-create-simple-entity t))
         t))
    
    
  8. Sync a complex entity

    To create/synchronize a complex entity (with its arborescence), we need to ensure some standard controls are ok. Then we can call the primitive routine orgtrello/do-create-complex-entity to do the actual creation. At last, saving the buffer as the creation involves some buffer updates.

    (defun org-trello/create-complex-entity ()
      "Control first, then if ok, create an entity and all its arborescence if need be."
      (interactive)
      (org-trello/--msg-deco-control-and-do
         "Synchronizing complex entity"
         '(orgtrello/--setup-properties orgtrello/--control-keys orgtrello/--control-properties orgtrello/--control-encoding)
         'orgtrello/do-create-complex-entity
         t))
    
    
  9. Synchronize the org-mode buffer to trello

    Now that we have the basic brick (synchronize simple/complex entity), we can use this to synchronize the all buffer to trello. But first, we need to ensure some standard controls are ok. Then calling the primitive routine orgtrello/do-sync-full-file which does the actual syncing. At last, we save the buffer as the buffer has been updated.

    (defun org-trello/sync-to-trello ()
      "Control first, then if ok, sync the org-mode file completely to trello."
      (interactive)
      (org-trello/--msg-deco-control-and-do
         "Synchronizing org-mode file to trello"
         '(orgtrello/--setup-properties orgtrello/--control-keys orgtrello/--control-properties orgtrello/--control-encoding)
         'orgtrello/do-sync-full-file
         t))
    
    
  10. Synchronize the org-mode buffer from trello

    The other way around is also possible. You know the drill by now, we must ensure we have the standard control that pass. Then calling the routine orgtrello/do-sync-full-from-trello which really does the action. Then saving the buffer as the action involved some buffer updates.

    (defun org-trello/sync-from-trello ()
      "Control first, then if ok, sync the org-mode file from the trello board."
      (interactive)
      (org-trello/--msg-deco-control-and-do
         "Synchronizing trello board to org-mode file"
         '(orgtrello/--setup-properties orgtrello/--control-keys orgtrello/--control-properties orgtrello/--control-encoding)
         'orgtrello/do-sync-full-from-trello
         t))
    
    
  11. Kill/Remove/Delete an entity

    As we can create entity on trello, we can also remove them (from trello and the current org buffer). As usual, we ensure standard controls are ok. Then calling the subroutine orgtrello/do-delete-simple which does the action. Then saving the buffer as the action involved some buffer updates.

    (defun org-trello/kill-entity ()
      "Control first, then if ok, delete the entity and all its arborescence."
      (interactive)
      (org-trello/--msg-deco-control-and-do
         "Delete entity"
         '(orgtrello/--setup-properties orgtrello/--control-keys orgtrello/--control-properties orgtrello/--control-encoding)
         (lambda () (orgtrello/do-delete-simple t))
         t))
    
    
  12. Providing

    Now that we defined all of our code, we need to provide the library we created. Simplify using emacs's primitive provide.

    (provide 'org-trello)
    
    ;;; org-trello.el ends here
    

5.4 orgtrello

This is the namespace in charge of the primitive functions that actually trigger the action. Those functions reflect the same action as we define earlier but without any controls first. They are not to be called directly from the user.

  1. Namespace setup

    First, we'll begin by some setup variables that are actually used throughout the namespace:

    • TODO representation of org's "TODO" keyword
    • DONE representation of org's "DONE" keyword
    • BOARD-ID will be the trello board's identifier for org-trello to know which board to use
    • BOARD-NAME will be the trello board's name for the user to know to which board he/she works with
    • LIST-NAMES is the keyword the user use with org in reverse order
    • HMAP-ID-NAME is a map of those same keyword with a sequence identifier
    • CONFIG-DIR is the org-trello's home folder
    • CONFIG-FILE is the org-trello's setup file (for consumer-key and access-token)
    • consumer-key is the user's consumer-key for trello
    • access-token is the user's read/write access-token for trello
    • ORGTRELLO-MARKER is a marker used by org-trello to know which entity it has synced. This is dependent on the consumer-key

    The user does not have to touch anything on this.

    
    
    ;; #################### orgtrello
    
    ;; Specific state - FIXME check if they do not already exist on org-mode to avoid potential collisions
    (defvar *TODO* "TODO" "org-mode todo state")
    (defvar *DONE* "DONE" "org-mode done state")
    
    ;; Properties key for the orgtrello headers #+PROPERTY board-id, etc...
    (defvar *BOARD-ID* "board-id" "orgtrello property board-id entry")
    (defvar *BOARD-NAME* "board-name" "orgtrello property board-name entry")
    
    (defvar *LIST-NAMES*   nil "orgtrello property names of the different lists. This use the standard 'org-todo-keywords property from org-mode.")
    (defvar *HMAP-ID-NAME* nil "orgtrello hash map containing for each id, the associated name (or org keyword).")
    
    (defvar *CONFIG-DIR*  (concat (getenv "HOME") "/" ".trello"))
    (defvar *CONFIG-FILE* (concat *CONFIG-DIR* "/config.el"))
    
    (defvar *consumer-key*     nil "Id representing the user")
    (defvar *access-token*     nil "Read/write Access token to use trello in the user's name ")
    (defvar *ORGTRELLO-MARKER* nil "Marker used for syncing the data in trello")
    
    
  2. Control/setup routines

    Some control/setup important routines because they will permit or not to launch the main actions.

    1. orgtrello/–setup-properties

      This is an important routine in charge of loading the keywords the user want to use. Note that this routine reverse the list of org keywords. This is a trick to help create the list in trello in the right order (assuming the user defines in the right order his/her keywords, e.g TODO DOING | DONE FAIL)

      (defun orgtrello/--setup-properties ()
        "Setup the properties according to the org-mode setup. Return :ok."
        (let* ((orgtrello/--list-keywords (nreverse (orgtrello/filtered-kwds)))
               (orgtrello/--hmap-id-name (cl-reduce
                                          (lambda (hmap name)
                                            (progn
                                              (puthash (assoc-default name org-file-properties) name hmap)
                                              hmap))
                                          orgtrello/--list-keywords
                                          :initial-value (make-hash-table :test 'equal))))
          (setq *LIST-NAMES*   orgtrello/--list-keywords)
          (setq *HMAP-ID-NAME* orgtrello/--hmap-id-name)
          :ok))
      
      
    2. orgtrello/filtered-kwds

      This is a function in charge of retrieving the specific keywords the user wants to use with trello. This will map later as the list in trello and as keyword in emacs's org-mode buffer.

      (defun orgtrello/filtered-kwds ()
        "org keywords used (based on org-todo-keywords-1)."
        org-todo-keywords-1)
      
      
    3. orgtrello/–control-encoding

      A simple message to make the user aware he needs to use utf-8 encoding.

      (defun orgtrello/--control-encoding ()
        "Use utf-8, otherwise, there will be trouble."
        (progn
          (message "Ensure you use utf-8 encoding for your org buffer.")
          :ok))
      
      
    4. orgtrello/–control-properties

      An important higher routine to ensure that the setup regarding the trello board is rightly setuped on the org buffer. We simply ensure that we have the right amount of properties regarding the org keywords. If all is ok, we return the :ok value, otherwise, we return an error message indicating the user what he must do.

      (defun orgtrello/--control-properties ()
        "org-trello needs the properties board-id and all list id from the trello board to be setuped on header property file. Returns :ok if everything is ok, or the error message if problems."
        (let ((orgtrello/--hmap-count   (hash-table-count *HMAP-ID-NAME*)))
          (if (and (assoc-default *BOARD-ID* org-file-properties)
                   (= (length *LIST-NAMES*) orgtrello/--hmap-count))
              :ok
            "Setup problem.\nEither you did not connect your org-mode buffer with a trello board, to correct this:\n  * attach to a board through C-c o I or M-x org-trello/install-board-and-lists-ids\n  * or create a board from scratch with C-c o b or M-x org-trello/create-board).\nEither your org-mode's todo keyword list and your trello board lists are not named the same way (which they must).\nFor this, connect to trello and rename your board's list according to your org-mode's todo list.\nAlso, you can specify on your org-mode buffer the todo list you want to work with, for example: #+TODO: TODO DOING | DONE FAIL (hit C-c C-c to refresh the setup)")))
      
      
    5. orgtrello/–control-keys

      Another important check routine to ensure that the user has rightfully setuped his/her consumer-key and read/write access-token. If everything is ok, we return :ok. Otherwise, we return an error message indicating what the user must do.

      (defun orgtrello/--control-keys ()
        "org-trello needs the *consumer-key* and the *access-token* to access the trello resources. Returns :ok if everything is ok, or the error message if problems."
        (if (or (and *consumer-key* *access-token*)
                ;; the data are not set,
                (and (file-exists-p *CONFIG-FILE*)
                     ;; trying to load them
                     (load *CONFIG-FILE*)
                     ;; still not loaded, something is not right!
                     (and *consumer-key* *access-token*)
                     ;; setting the marker once
                     (setq *ORGTRELLO-MARKER* (format "orgtrello-marker-%s" *consumer-key*))))
            :ok
          "Setup problem - You need to install the consumer-key and the read/write access-token - C-c o i or M-x org-trello/install-board-and-lists-ids"))
      
      
  3. Abstraction access routines

    Those are simple routines to abstract away the representation of the org data we manipulate.

    1. orgtrello/–keyword

      Extract the status/keyword from the current entity (e.g TODO, DONE, etc…).

      (defun orgtrello/--keyword (entity-meta &optional default-value)
        "Retrieve the keyword from the entity. If default-value is specified, this is the default value if no keyword is present"
        (gethash :keyword entity-meta default-value))
      
      
    2. orgtrello/–label

      To extract the label from the entity. This is what's map to the name of the entity on trello.

      (defun orgtrello/--label (entity-meta)
        "Retrieve the label from the entity."
        (gethash :title entity-meta))
      
      
    3. orgtrello/–id

      The identifier of the entity once it has been synchronized on trello.

      (defun orgtrello/--id (entity-meta)
        "Retrieve the id from the entity."
        (gethash :id entity-meta))
      
      
    4. orgtrello/–level

      The current level (mapped to the number of stars).

      (defun orgtrello/--level (entity-meta)
        "Retrieve the level from the entity."
        (gethash :level entity-meta))
      
      
    5. orgtrello/–due

      The deadline (org notion) mapped to due date (on trello).

      (defun orgtrello/--due (entity-meta)
        "Retrieve the due date from the entity."
        (gethash :due entity-meta))
      
      
  4. Create simple entity
    1. orgtrello/do-create-simple-entity

      The orchestration to trigger the simple synchronization of an entity (without any arborescence):

      • compute the metadata (current, parent, grandparent) from the current org entry (this may be need if too deep level)
      • if there is some metadata
        • if this is an error message, transit the message
        • otherwise
          • set the marker on org buffer for the current entry
          • compute and execute the http query
          • return a success message
      (defun orgtrello/do-create-simple-entity (&optional sync)
        "Do the actual simple creation of a card, checklist or task. Optionally, we can render the creation synchronous."
        (let ((entry-metadata (orgtrello-data/entry-get-full-metadata)))
          (if entry-metadata
              (let ((query-http-or-error-msg (orgtrello/--dispatch-create (gethash :current entry-metadata) (gethash :parent entry-metadata) (gethash :grandparent entry-metadata))))
                (if (hash-table-p query-http-or-error-msg)
                    ;; if it's a hash-table we can do the sync
                    (progn
                      ;; set the consumer-key to make a pointer to get back to when the request is finished
                      (orgtrello/--set-marker)
                      ;; request
                      (orgtrello-query/http query-http-or-error-msg sync 'orgtrello-query/--post-put-success-callback-update-id)
                      "Synchronizing simple entity done!")
                  ;; else it's a string to display
                  query-http-or-error-msg)))))
      
      
    2. creation/update routine

      Those functions does not actually do any request, they compute the map representing the request.

      1. card
        1. orgtrello/–card

          This is the main entry to create/update a card. There is a series of check done by the orgtrello/--checks-before-sync-card function. If ok, then we can continue with the main intent of the function. We extract from the metadata the:

          • keyword status of the card (TODO, DONE, etc…)
          • trello list identifier to which the card belongs to (depending on the keyword status)
          • card's identifier
          • card's name
          • card's due

          Then we create or update the card depending on the presence or not of the card identifier. if card id present update else create.

          (defun orgtrello/--card (card-meta &optional parent-meta grandparent-meta)
            "Deal with create/update card query build. If the checks are ko, the error message is returned."
            (let ((checks-ok-or-error-message (orgtrello/--checks-before-sync-card card-meta)))
              ;; title is mandatory
              (if (equal :ok checks-ok-or-error-message)
                  ;; parent and grandparent are useless here
                  (let* ((orgtrello/--card-kwd  (orgtrello/--retrieve-state-of-card card-meta))
                         (orgtrello/--list-id   (assoc-default orgtrello/--card-kwd org-file-properties))
                         (orgtrello/--card-id   (orgtrello/--id    card-meta))
                         (orgtrello/--card-name (orgtrello/--label card-meta))
                         (orgtrello/--card-due  (orgtrello/--due   card-meta)))
                    (if orgtrello/--card-id
                        ;; update
                        (orgtrello-api/move-card orgtrello/--card-id orgtrello/--list-id orgtrello/--card-name orgtrello/--card-due)
                      ;; create
                      (orgtrello-api/add-card orgtrello/--card-name orgtrello/--list-id orgtrello/--card-due)))
                checks-ok-or-error-message)))
          
          
        2. orgtrello/–retrieve-state-of-card

          This function helps computes the status of the card. If no status is present, TODO is assumed.

          (defun orgtrello/--retrieve-state-of-card (card-meta)
            "Given a card, retrieve its state depending on its :keyword metadata. If empty or no keyword then, its equivalence is *TODO*, otherwise, return its current state."
            (let* ((orgtrello/--card-kwd (orgtrello/--keyword card-meta *TODO*)))
              (if orgtrello/--card-kwd orgtrello/--card-kwd *TODO*)))
          
          
        3. orgtrello/–checks-before-sync-card

          Does some basic checks. Typically, here only the name/title/label is mandatory.

          (defun orgtrello/--checks-before-sync-card (card-meta)
            "Checks done before synchronizing the cards."
            (let ((orgtrello/--card-name (orgtrello/--label card-meta)))
              (if orgtrello/--card-name
                  :ok
                "Cannot synchronize the card - missing mandatory label. Skip it...")))
          
          
      2. checklist
        1. orgtrello/–checklist

          The idea is similar than the card function. First, checks using orgtrello–checks-before-sync-checklist/ function. If ok, continue otherwise return the error message. Then extract the needed metadata:

          • checklist identifier (optional)
          • card identifier (mandatory, a checklist belongs to a card)
          • checklist name (mandatory)

          Again, if checklist identifier present, we update otherwise we create.

          (defun orgtrello/--checklist (checklist-meta &optional card-meta grandparent-meta)
            "Deal with create/update checklist query build. If the checks are ko, the error message is returned."
            (let ((checks-ok-or-error-message (orgtrello/--checks-before-sync-checklist checklist-meta card-meta)))
              ;; title is mandatory
              (if (equal :ok checks-ok-or-error-message)
                  ;; grandparent is useless here
                  (let* ((orgtrello/--checklist-id   (orgtrello/--id checklist-meta))
                         (orgtrello/--card-id        (orgtrello/--id card-meta))
                         (orgtrello/--checklist-name (orgtrello/--label checklist-meta)))
                    (if orgtrello/--checklist-id
                        ;; update
                        (orgtrello-api/update-checklist orgtrello/--checklist-id orgtrello/--checklist-name)
                      ;; create
                      (orgtrello-api/add-checklist orgtrello/--card-id orgtrello/--checklist-name)))
                checks-ok-or-error-message)))
          
          
        2. orgtrello/–checks-before-sync-checklist

          A little more complex check are done. We need to ensure:

          • the card's id is present
          • the checklist's name is present too.

          If some are missing, send the error message corresponding.

          (defun orgtrello/--checks-before-sync-checklist (checklist-meta card-meta)
            "Checks done before synchronizing the checklist."
            (let ((orgtrello/--checklist-name (orgtrello/--label checklist-meta))
                  (orgtrello/--card-id        (orgtrello/--id card-meta)))
              (if orgtrello/--checklist-name
                  (if orgtrello/--card-id
                      :ok
                    "Cannot synchronize the checklist - the card must be synchronized first. Skip it...")
                "Cannot synchronize the checklist - missing mandatory label. Skip it...")))
          
          
      3. task/item
        1. orgtrello/–task

          Update the task/item. First checks. If ok, continue otherwise return the error message. Metadata extracted:

          • task/item's id (optional)
          • checklist's id (mandatory)
          • card's id (mandatory)
          • task/item's name (mandatory)
          • checklist's state (trello api distinguish between the state at update time from the check status at creation time)
          • checklist's check status

          Depending on the presence of the task/item's identifier, we update or create.

          (defun orgtrello/--task (task-meta &optional checklist-meta card-meta)
            "Deal with create/update task query build. If the checks are ko, the error message is returned."
            (let ((checks-ok-or-error-message (orgtrello/--checks-before-sync-item task-meta checklist-meta card-meta)))
              ;; title is mandatory
              (if (equal :ok checks-ok-or-error-message)
                  ;; card-meta is only usefull for the update part
                  (let* ((orgtrello/--task-id      (orgtrello/--id task-meta))
                         (orgtrello/--checklist-id (orgtrello/--id checklist-meta))
                         (orgtrello/--card-id      (orgtrello/--id card-meta))
                         (orgtrello/--task-name    (orgtrello/--label task-meta))
                         (orgtrello/--task-state   (orgtrello/--keyword task-meta))
                         (orgtrello/--checklist-state    (orgtrello/--keyword checklist-meta)))
          
                    (orgtrello/--update-item-according-to-checklist-status *ORGTRELLO-CHECKLIST-UPDATE-ITEMS* checklist-meta)
                    ;; update/create items
                    (if orgtrello/--task-id
                        ;; update - rename, check or uncheck the task
                        (orgtrello-api/update-task orgtrello/--card-id orgtrello/--checklist-id orgtrello/--task-id orgtrello/--task-name (orgtrello/--task-compute-state *ORGTRELLO-CHECKLIST-UPDATE-ITEMS* orgtrello/--task-state orgtrello/--checklist-state))
                      ;; create
                      (orgtrello-api/add-tasks orgtrello/--checklist-id orgtrello/--task-name (orgtrello/--task-compute-check *ORGTRELLO-CHECKLIST-UPDATE-ITEMS* orgtrello/--task-state orgtrello/--checklist-state))))
                checks-ok-or-error-message)))
          
          

          Also, there is a sublety. ORGTRELLO-CHECKLIST-UPDATE-ITEMS is a global setup that permits the override of the task/item's status (checked or not). if ORGTRELLO-CHECKLIST-UPDATE-ITEMS is set to 't, this will override such setup. Otherwise, the item's status is computed depending on org's keyword. This means that if ORGTRELLO-CHECKLIST-UPDATE-ITEMS is 't, we need to update the buffer accordingly to such status since this will be synchronized on trello. That's the responsibility of the function orgtrello–update-item-according-to-checklist-status/.

        2. orgtrello/–checks-before-sync-item

          The control function which ensures:

          • task/item's id
          • checklist's id
          • card's id

          are presents. If some are missing, the corresponding error message is sent, :ok otherwise.

          (defun orgtrello/--checks-before-sync-item (task-meta checklist-meta card-meta)
            "Checks done before synchronizing the checklist."
            (let ((orgtrello/--task-name    (orgtrello/--label task-meta))
                  (orgtrello/--checklist-id (orgtrello/--id checklist-meta))
                  (orgtrello/--card-id      (orgtrello/--id card-meta)))
              (if orgtrello/--task-name
                  (if orgtrello/--checklist-id
                      (if orgtrello/--card-id
                          :ok
                        "Cannot synchronize the item - the card must be synchronized first. Skip it...")
                    "Cannot synchronize the item - the checklist must be synchronized first. Skip it...")
                "Cannot synchronize the item - missing mandatory label. Skip it...")))
          
          
        3. orgtrello/–task-compute-state-or-check

          An HOF (higher order function) that captures the computation of the state (update) or the check (create) of an item/task.

          (defun orgtrello/--task-compute-state-or-check (checklist-update-items-p task-state checklist-state possible-states)
            "Compute the task's state/check (for creation/update). The 2 possible states are in the list possible states, first position is the 'checked' one, and second the unchecked one."
            (let* ((orgtrello/--task-checked   (first possible-states))
                   (orgtrello/--task-unchecked (second possible-states)))
              (cond ((and checklist-update-items-p (string= *DONE* checklist-state))                      orgtrello/--task-checked)
                    ((and checklist-update-items-p (or checklist-state (string= *TODO* checklist-state))) orgtrello/--task-unchecked)
                    ((string= *DONE* task-state)                                                          orgtrello/--task-checked)
                    (t                                                                                    orgtrello/--task-unchecked))))
          
          
        4. orgtrello/–task-compute-state

          A function to compute the state (update) of the task/item:

          • complete
          • incomplete

          There is a subtlety here. checklist-update-items-p represents a global setup to make the checklist's status more important than the current item/task's status.

          Here is the truth table:

          checklist-update-items-p checklist's status task/item's status Result
          t c-st X (= c-st DONE "complete" "incomplete")
          nil X t-st (= t-st DONE "complete" "incomplete")

          n

          (defun orgtrello/--task-compute-state (checklist-update-items-p task-state checklist-state)
            "Compute the task's state (for creation)."
            (orgtrello/--task-compute-state-or-check checklist-update-items-p task-state checklist-state '("complete" "incomplete")))
          
          
        5. orgtrello/–task-compute-check

          A function to compute the check status of the task/item:

          • t
          • nil

          There is a subtlety here. checklist-update-items-p represents a global setup to make the checklist's status more important than the current item/task's status.

          Here is the truth table:

          checklist-update-items-p checklist's status task/item's check status Result
          t c-st X (= c-st DONE t nil)
          nil X t-st (= t-st DONE t nil)
          (defun orgtrello/--task-compute-check (checklist-update-items-p task-state checklist-state)
            "Compute the task's check status (for update)."
              (orgtrello/--task-compute-state-or-check checklist-update-items-p task-state checklist-state '(t nil)))
          
          
        6. orgtrello/–update-item-according-to-checklist-status

          This is a function, depending on checklist-update-items-p, which is in charge of aligning the status of the keyword in the org buffer according to the checklist's keyword. if checklist-update-items-p is 't, then update else does nothing.

          (defun orgtrello/--update-item-according-to-checklist-status (checklist-update-items-p checklist-meta)
            "Update the item of the checklist according to the status of the checklist."
            (if checklist-update-items-p
                (let ((orgtrello/--checklist-status (orgtrello/--compute-state-from-keyword (orgtrello/--keyword checklist-meta))))
                  (org-todo orgtrello/--checklist-status))))
          
          
        7. orgtrello/–compute-state-from-keyword

          Given a state, compute its org equivalent:

          state org
          nil ""
          "" ""
          DONE DONE
          TODO TODO
          Otherwise TODO
          (defun orgtrello/--compute-state-from-keyword (state)
            "Given a state, compute the org equivalent (to use with org-todo function)"
            (cond ((or (not state) (string= "" state)) *TODO*)
                  ((string= *DONE* state)              'done)
                  ((string= *TODO* state)              *TODO*)
                  (t                                   *TODO*)))
          
          
    3. orgtrello/–too-deep-level

      A function which simply displays that the arborescence depth is too deep. We only deal with 3 levels (1 -> card, 2 -> checklist, 3 -> item/task).

      (defun orgtrello/--too-deep-level (meta &optional parent-meta grandparent-meta)
        "Deal with too deep level."
        "Your arborescence depth is too deep. We only support up to depth 3.\nLevel 1 - card\nLevel 2 - checklist\nLevel 3 - items/tasks")
      
      
    4. orgtrello/–dispatch-map-creation

      An initialization of the dispatch creation/update function depending on the level (1, 2, 3). We use this function to initialize once the MAP-DISPATCH-CREATE-UPDATE which will be use to dispatch on the level of the entity to sync.

      (defun orgtrello/--dispatch-map-creation ()
        "Dispatch map for the creation of card/checklist/item."
        (let* ((dispatch-map (make-hash-table :test 'equal)))
          (puthash 1 'orgtrello/--card      dispatch-map)
          (puthash 2 'orgtrello/--checklist dispatch-map)
          (puthash 3 'orgtrello/--task      dispatch-map)
          dispatch-map))
      
      (defvar *MAP-DISPATCH-CREATE-UPDATE* (orgtrello/--dispatch-map-creation) "Dispatch map for the creation/update of card/checklist/task")
      
      
    5. orgtrello/–set-marker

      A simple routine to install an orgtrello-marker before launching any synchronization.

      (defun orgtrello/--set-marker ()
        "Set the consumer-key to make a pointer to get back to when the request is finished"
        (org-set-property *ORGTRELLO-MARKER* *ORGTRELLO-MARKER*))
      
      
    6. orgtrello/–dispatch-create

      The function which will extract the current level of the entity and call the function to generate the create/update request for the current entity.

      (defun orgtrello/--dispatch-create (meta &optional parent-meta grandparent-meta)
        (let* ((level       (orgtrello/--level meta))
               (dispatch-fn (gethash level *MAP-DISPATCH-CREATE-UPDATE* 'orgtrello/--too-deep-level)))
          ;; then execute the call
          (funcall dispatch-fn meta parent-meta grandparent-meta)))
      
      
  5. Create complex entity
    1. orgtrello/–board-name

      Extract the board name from the org buffer's metadata.

      (defun orgtrello/--board-name ()
        "Compute the board's name"
        (assoc-default *BOARD-NAME* org-file-properties))
      
      
    2. orgtrello/do-create-complex-entity

      The main function in charge of synchronizing the entity (with its arborescence):

      • Get back to the upper level of the current entry
      • Map over the full arborescence (in order) and sync the entity (using the primitive orgtrello/do-create-simple-entity)
      • Return a success message
      (defun orgtrello/do-create-complex-entity ()
        "Do the actual full card creation - from card to task. Beware full side effects..."
        (let ((orgtrello/--board-name-to-sync (orgtrello/--board-name)))
          (message "Synchronizing full entity with its structure on board '%s'..." orgtrello/--board-name-to-sync)
          (save-excursion
            ;; iterate over the map of
            (org-map-tree (lambda () (orgtrello/do-create-simple-entity t))))
          (format "Synchronizing full entity with its structure on board '%s' - done" orgtrello/--board-name-to-sync)))
      
      
  6. Synchronize full org buffer to trello

    Synchronize the full org file to the trello board. The idea is to send all the cards presents in the buffer to the trello board (no merge, org buffer has all the rights here). At the end, return a success message.

    (defun orgtrello/do-sync-full-file ()
      "Full org-mode file synchronisation. Beware, this will block emacs as the request is synchronous."
      (let ((orgtrello/--board-name-to-sync (orgtrello/--board-name)))
        (message "Synchronizing org-mode file to the board '%s'. This may take some time, some coffee may be a good idea..." (orgtrello/--board-name))
        (org-map-entries (lambda () (orgtrello/do-create-simple-entity t)) t 'file)
        (format "Synchronizing org-mode file to the board '%s' - done!" orgtrello/--board-name-to-sync)))
    
    
  7. Synchronize full org buffer from trello

    Synchronize the full org file from the trello board. The idea is to compute all the trello cards from trello. Map over the current org buffer, synchronize from trello (no merge, trello has all power here) and overwrite the trello data for each entry. Each data remaining not already present on the buffer are then dumped in the current buffer.

    1. orgtrello/do-sync-full-from-trello

      Main function:

      • retrieve the board id from the org metadata
      • compute the cards from the trello board
      • synchronize all the entities present on the buffer with the data from trello (remove them as soon as they are synced)
      • synchronize all the remaining entities into the current buffer
      • return a successfull message
      (defun orgtrello/do-sync-full-from-trello ()
        "Full org-mode file synchronisation. Beware, this will block emacs as the request is synchronous."
        (let ((orgtrello/--board-name-to-sync (orgtrello/--board-name)))
          (message "Synchronizing the trello board '%s' to the org-mode file. This may take a moment, some coffee may be a good idea..." orgtrello/--board-name-to-sync)
          (let* ((orgtrello/--board-id           (assoc-default *BOARD-ID* org-file-properties))
                 (orgtrello/--cards              (orgtrello-query/http (orgtrello-api/get-cards orgtrello/--board-id) t))
                 (orgtrello/--entities-hash-map  (orgtrello/--compute-full-entities-from-trello orgtrello/--cards))
                 (orgtrello/--remaining-entities (orgtrello/--sync-buffer-with-trello-data orgtrello/--entities-hash-map)))
            (orgtrello/--update-buffer-with-remaining-trello-data orgtrello/--remaining-entities))
          (format "Synchronizing the trello board '%s' to the org-mode file - done!" orgtrello/--board-name-to-sync)))
      
      
    2. orgtrello/–sync-buffer-with-trello-data

      Given a map of entities to sync, update each entry with such data. After each entry update, the entity is removed. Return the map of entities with the sync entry removed.

      (defun orgtrello/--sync-buffer-with-trello-data (entities)
        "Given all the entities, update the current buffer with those."
        (with-current-buffer (current-buffer)
          (org-map-entries
           (lambda ()
             (let ((entry-metadata (orgtrello-data/entry-get-full-metadata)))
               (if entry-metadata ;; if level > 4, entry-metadata is not considered as this is not represented in trello board
                   ;; will search 'entities' hash table for updates (do not compute diffs, take them as is)
                   (let* ((orgtrello/--entity         (gethash :current entry-metadata))
                          (orgtrello/--entity-id      (orgtrello/--id orgtrello/--entity))
                          (orgtrello/--entity-updated (gethash orgtrello/--entity-id entities)))
                     (if orgtrello/--entity-updated
                         ;; found something, we update by squashing the current contents
                         (let* ((orgtrello/--entry-new-id    (orgtrello-query/--id   orgtrello/--entity-updated))
                                (orgtrello/--entity-due-date (orgtrello-query/--due  orgtrello/--entity-updated))
                                (orgtrello/--entry-new-name  (orgtrello-query/--name orgtrello/--entity-updated)))
                           ;; update the buffer with the new updates (there may be none but naively we will overwrite at the moment)
                           (message "Synchronizing entity '%s' with id '%s'..." orgtrello/--entry-new-name orgtrello/--entry-new-id)
                           (org-show-entry)
                           (kill-whole-line)
                           (if orgtrello/--entity-due-date (kill-whole-line))
                           (insert (orgtrello/--compute-entity-to-org-entry orgtrello/--entity-updated))
                           ;; remove the entry from the hash-table
                           (remhash orgtrello/--entity-id entities)))))))
           t
           'file))
        ;; return the entities which has been dryed
        entities)
      
      
    3. orgtrello/–update-buffer-with-remaining-trello-data

      Given a map of entities:

      • goes at the end of the file
      • add the entities present on such map in the org format
      • return at the beginning of the file
      • sort all the buffer on the org todo keywords order (only the first level -> card).
      (defun orgtrello/--update-buffer-with-remaining-trello-data (entities)
        "Given a map of entities, dump those entities in the current buffer."
        (if entities ;; could be empty
            (with-current-buffer (current-buffer)
              ;; go at the end of the file
              (goto-char (point-max))
              ;; dump the remaining entities
              (maphash
               (lambda (orgtrello/--entry-new-id orgtrello/--entity)
                 (let ((orgtrello/--entry-new-name  (orgtrello-query/--name orgtrello/--entity)))
                   (message "Synchronizing new entity '%s' with id '%s'..." orgtrello/--entry-new-name orgtrello/--entry-new-id)
                   (insert (orgtrello/--compute-entity-to-org-entry orgtrello/--entity))
                   (org-set-property *ORGTRELLO-ID* orgtrello/--entry-new-id)))
               entities)
              (goto-char (point-min))
              (org-sort-entries t ?o))))
      
      
    4. orgtrello/–compute-card-status

      Given a card list id (which represent an org keyword), compute the keywords (this works with the metadata of the file).

      (defun orgtrello/--compute-card-status (card-id-list)
        "Given a card's id, compute its status."
        (gethash card-id-list *HMAP-ID-NAME*))
      
      
    5. orgtrello/–compute-card-to-org-entry

      Given an entry which represents a card, compute its equivalent org format.

      (defun orgtrello/--compute-card-to-org-entry (card)
        "Given a card, compute its org-mode entry equivalence."
        (let* ((orgtrello/--card-name     (orgtrello-query/--name card))
               (orgtrello/--card-status   (orgtrello/--compute-card-status (orgtrello-query/--list-id card)))
               (orgtrello/--card-due-date (orgtrello-query/--due card)))
          (format "* %s %s\n%s" orgtrello/--card-status orgtrello/--card-name
                  (if orgtrello/--card-due-date (format "DEADLINE: <%s>\n" orgtrello/--card-due-date) ""))))
      
      
    6. orgtrello/–compute-checklist-to-org-entry

      Given an entry which represents a checklist, compute its equivalent org format.

      (defun orgtrello/--compute-checklist-to-org-entry (checklist)
        "Given a checklist, compute its org-mode entry equivalence."
        (let ((orgtrello/--checklist-name  (orgtrello-query/--name checklist)))
          (format "** %s\n" orgtrello/--checklist-name)))
      
      
    7. orgtrello/–compute-item-to-org-entry

      Given an entry which represents an item/task, compute its equivalent org format.

      (defun orgtrello/--compute-item-to-org-entry (item)
        "Given a checklist item, compute its org-mode entry equivalence."
        (let* ((orgtrello/--item-name  (orgtrello-query/--name  item))
               (orgtrello/--item-state (orgtrello-query/--state item)))
          (format "*** %s %s\n"
                  (if (string= "complete" orgtrello/--item-state) *DONE* *TODO*)
                  orgtrello/--item-name)))
      
      
    8. orgtrello/–compute-entity-to-org-entry

      Given an entry, determine according to its structure the nature of such entity:

      • list-id, it's a card
      • card-id, it's a checklist
      • state, it's an item/task
      (defun orgtrello/--compute-entity-to-org-entry (entity)
        "Given an entity, compute its org representation."
        (cond ((orgtrello-query/--list-id entity) (orgtrello/--compute-card-to-org-entry entity))           ;; card      (level 1)
              ((orgtrello-query/--card-id entity) (orgtrello/--compute-checklist-to-org-entry entity))      ;; checklist (level 2)
              ((orgtrello-query/--state entity)  (orgtrello/--compute-item-to-org-entry entity))))          ;; items     (level 3)
      
      
    9. orgtrello/–do-retrieve-checklists-from-card

      Given a card, retrieve its full checklist and return a list composed of the card cons'ed to such entities.

      (defun orgtrello/--do-retrieve-checklists-from-card (card)
        "Given a card, return the list containing the card, the checklists from this card, and the items from the checklists. The order is guaranted."
        (cl-reduce
         (lambda (acc-list checklist-id)
           (let ((orgtrello/--checklist (orgtrello-query/http (orgtrello-api/get-checklist checklist-id) t)))
             (append (cons orgtrello/--checklist (orgtrello/--do-retrieve-checklists-and-items orgtrello/--checklist)) acc-list)))
         (orgtrello-query/--checklist-ids card)
         :initial-value nil))
      
      
    10. orgtrello/–do-retrieve-checklists-and-items

      Given a checklist, retrieve its full items/tasks and return a list composed of the checklist cons'ed to such entities.

      (defun orgtrello/--do-retrieve-checklists-and-items (checklist)
        "Given a checklist id, retrieve all the items from the checklist and return a list containing first the checklist, then the items."
        (--map it (orgtrello-query/--check-items checklist)))
      
      
    11. orgtrello/–compute-full-entities-from-trello

      Given a list of cards, retrieve the full arborescence of such cards (computing their checklists and items/tasks) and return a map of entities.

      (defun orgtrello/--compute-full-entities-from-trello (cards)
        "Given a list of cards, compute the full cards data from the trello boards. The order from the trello board is now kept."
        ;; will compute the hash-table of entities (id, entity)
        (cl-reduce
         (lambda (orgtrello/--acc-hash orgtrello/--entity-card)
           (message "Computing card '%s' data..." (orgtrello-query/--name orgtrello/--entity-card))
           ;; adding the entity card
           (puthash (orgtrello-query/--id orgtrello/--entity-card) orgtrello/--entity-card orgtrello/--acc-hash)
           ;; fill in the other remaining entities (checklist/items)
           (mapc
            (lambda (it)
              (puthash (orgtrello-query/--id it) it orgtrello/--acc-hash))
            (orgtrello/--do-retrieve-checklists-from-card orgtrello/--entity-card))
           orgtrello/--acc-hash)
         cards
         :initial-value (make-hash-table :test 'equal)))
      
      
  8. Delete entity
    1. orgtrello/do-delete-simple

      The main function to delete the current entity. This checks if the id is present. If not present, return an error message explaining the entity must be synced first. Otherwise, execute the trello deletion. Return a message of success.

      (defun orgtrello/do-delete-simple (&optional sync)
        "Do the simple deletion of a card, checklist or task."
        (let* ((entry-metadata   (orgtrello-data/entry-get-full-metadata))
               (current-metadata (gethash :current entry-metadata))
               (id               (orgtrello/--id current-metadata)))
          (if (and current-metadata id)
              (let ((query-http-or-error-msg (orgtrello/--dispatch-delete (gethash :current entry-metadata) (gethash :parent entry-metadata))))
                (if (hash-table-p query-http-or-error-msg)
                    (progn
                      (orgtrello-query/http query-http-or-error-msg sync 'orgtrello-query/--delete-success-callback)
                      "Delete entity done!")
                  query-http-or-error-msg))
            "Entity not synchronized on trello yet!")))
      
      
    2. orgtrello/–dispatch-map-delete

      The computation of the MAP-DISPATCH-DELETE which will be used to dispatch the deletion of an entity.

      (defun orgtrello/--dispatch-map-delete ()
        "Dispatch map for the deletion of card/checklist/item."
        (let* ((dispatch-map (make-hash-table :test 'equal)))
          (puthash 1 'orgtrello/--card-delete      dispatch-map)
          (puthash 2 'orgtrello/--checklist-delete dispatch-map)
          (puthash 3 'orgtrello/--task-delete      dispatch-map)
          dispatch-map))
      
      (defvar *MAP-DISPATCH-DELETE* (orgtrello/--dispatch-map-delete) "Dispatch map for the deletion query of card/checklist/task.")
      
      
    3. orgtrello/–dispatch-delete

      The actual entity deletion using the MAP-DISPATCH-DELETE as a dispatch function.

      (defun orgtrello/--dispatch-delete (meta &optional parent-meta)
        (let* ((level       (orgtrello/--level meta))
               (dispatch-fn (gethash level *MAP-DISPATCH-DELETE* 'orgtrello/--too-deep-level)))
          (funcall dispatch-fn meta parent-meta)))
      
      
    4. orgtrello/–card-delete

      Specific card deletion request computation.

      (defun orgtrello/--card-delete (card-meta &optional parent-meta)
        "Deal with the deletion query of a card"
        ;; parent is useless here
        (orgtrello-api/delete-card (orgtrello/--id card-meta)))
      
      
    5. orgtrello/–checklist-delete

      Specific checklist deletion request computation.

      (defun orgtrello/--checklist-delete (checklist-meta &optional parent-meta)
        "Deal with the deletion query of a checklist"
        ;; parent is useless here
        (orgtrello-api/delete-checklist (orgtrello/--id checklist-meta)))
      
      
    6. orgtrello/–task-delete

      Specific task/item deletion request computation.

      (defun orgtrello/--task-delete (task-meta &optional checklist-meta)
        "Deal with create/update task query build"
        (let* ((orgtrello/--task-id      (orgtrello/--id task-meta))
               (orgtrello/--checklist-id (orgtrello/--id checklist-meta)))
          (orgtrello-api/delete-task orgtrello/--checklist-id orgtrello/--task-id)))
      
      
  9. Install key and token configuration
    1. orgtrello/do-install-key-and-token

      First routine to ask input for the user:

      • open the consumer key page for the user to retrieve his/her consumer key.
      • then open the access token page to ask for the user to permit org-trello to act on her/his behalf.
      • at last, generate the config.el file inside his/her home.
      • return a successfull message
      (defun orgtrello/do-install-key-and-token ()
        "Procedure to install the *consumer-key* and the token for the user in the config-file."
        (interactive)
        (browse-url "https://trello.com/1/appKey/generate")
        (let ((orgtrello/--*consumer-key* (read-string "*consumer-key*: ")))
          (browse-url (format "https://trello.com/1/authorize?response_type=token&name=org-trello&scope=read,write&expiration=never&key=%s" orgtrello/--*consumer-key*))
          (let ((orgtrello/--access-token (read-string "Access-token: ")))
            (orgtrello/--do-install-config-file orgtrello/--*consumer-key* orgtrello/--access-token)
            "Install key and read/write access token done!")))
      
      
    2. orgtrello/–do-install-config-file

      Given a consumer-key and an access token, generate a config.el file.

      (defun orgtrello/--do-install-config-file (*consumer-key* *access-token*)
        "Persist the file config-file with the input of the user."
        (make-directory *CONFIG-DIR* t)
        (with-temp-file *CONFIG-FILE*
          (erase-buffer)
          (goto-char (point-min))
          (insert (format "(setq *consumer-key* \"%s\")\n" *consumer-key*))
          (insert (format "(setq *access-token* \"%s\")" *access-token*))
          (write-file *CONFIG-FILE* 't)))
      
      
  10. Install board and lists configuration

    The install board and lists configuration. There is an alternative to such setup with the create board routine.

    1. orgtrello/do-install-board-and-lists

      Main routine to ask for the user's input to setup his/her board to attach to his/her current org buffer. This:

      • generates a list of his/her current board from his/her trello's account.
      • ask for the user to input which board he/she wants to use
      • then generate the metadata regarding his/her choice to the org buffer's beginning
      • return a message of success
      (defun orgtrello/do-install-board-and-lists ()
        "Interactive command to install the list boards"
        (interactive)
        (cl-destructuring-bind
            (orgtrello/--chosen-board-id orgtrello/--chosen-board-name) (-> (orgtrello/--list-boards)
                                                                            orgtrello/--id-name
                                                                            orgtrello/--choose-board)
          (let ((orgtrello/--board-lists-hname-id (-> orgtrello/--chosen-board-id
                                                      orgtrello/--list-board-lists
                                                      orgtrello/--name-id)))
            ;; remove any eventual present entry
            (orgtrello/--remove-properties-file orgtrello/--board-lists-hname-id t)
            ;; update with new ones
            (orgtrello/update-orgmode-file-with-properties
             orgtrello/--chosen-board-name
             orgtrello/--chosen-board-id
             orgtrello/--board-lists-hname-id
             t)))
        "Install board and list ids done!")
      
      
    2. orgtrello/–choose-board

      The routine that asks the user:

      • to select the board he/she wants to work with
      • return the list of board id, board name
      (defun orgtrello/--choose-board (boards)
        "Given a map of boards, display the possible boards for the user to choose which one he wants to work with."
        ;; ugliest ever
        (defvar orgtrello/--board-chosen nil)
        (setq orgtrello/--board-chosen nil)
        (let* ((str-key-val  "")
               (i            0)
               (i-id (make-hash-table :test 'equal)))
          (maphash (lambda (id name)
                     (setq str-key-val (format "%s%d: %s\n" str-key-val i name))
                     (puthash (format "%d" i) id i-id)
                     (setq i (+ 1 i)))
                   boards)
          (while (not (gethash orgtrello/--board-chosen i-id))
            (setq orgtrello/--board-chosen
                  (read-string (format "%s\nInput the number of the board desired: " str-key-val))))
          (let* ((orgtrello/--chosen-board-id   (gethash orgtrello/--board-chosen i-id))
                 (orgtrello/--chosen-board-name (gethash orgtrello/--chosen-board-id boards)))
            `(,orgtrello/--chosen-board-id ,orgtrello/--chosen-board-name))))
      
      
    3. orgtrello/–remove-properties-file

      Remove some file properties before applying new ones.

      (defun orgtrello/--delete-buffer-property (property-name)
        "A simple routine to delete a #+property: entry from the org-mode buffer."
        (let ((current-point (search-forward property-name nil t)))
          (if current-point
              (progn
                (goto-char current-point)
                (beginning-of-line)
                (kill-line)
                (kill-line)))))
      
      (defun orgtrello/--remove-properties-file (board-lists-hash-name-id &optional update-todo-keywords)
        "Remove the current org-trello properties"
        (with-current-buffer (current-buffer)
          (goto-char (point-min))
          (orgtrello/--delete-buffer-property (format "#+property: %s" *BOARD-ID*))
          (orgtrello/--delete-buffer-property (format "#+property: %s" *BOARD-NAME*))
          (maphash
           (lambda (name id)
             (orgtrello/--delete-buffer-property (format "#+property: %s" (orgtrello/convention-property-name name))))
           board-lists-hash-name-id)
          (if update-todo-keywords
              (orgtrello/--delete-buffer-property "#+TODO: "))))
      
      
    4. orgtrello/update-orgmode-file-with-properties

      Given a board name, board id, list of list ids, map of list names, insert such informations to the beginning of the org buffer. Then save the buffer and restart org-mode.

      (defun orgtrello/update-orgmode-file-with-properties (board-name board-id board-lists-hash-name-id &optional update-todo-keywords)
        "Update the orgmode file with the needed headers for org-trello to work."
        (with-current-buffer (current-buffer)
          (goto-char (point-min))
          ;; force utf-8
          (set-buffer-file-coding-system 'utf-8-auto)
          ;; install board-name and board-id
          (insert (format "#+property: %s    %s\n" *BOARD-NAME* board-name))
          (insert (format "#+property: %s      %s\n" *BOARD-ID* board-id))
          ;; install the other properties regarding the org keywords
          (maphash
           (lambda (name id)
             (insert (format "#+property: %s %s\n" (orgtrello/convention-property-name name) id)))
           board-lists-hash-name-id)
          (if update-todo-keywords
              (progn
                ;; install the todo list
                (insert "#+TODO: ")
                (maphash (lambda (name _) (insert (concat (orgtrello/convention-property-name name) " "))) board-lists-hash-name-id)
                (insert "\n")))
          ;; save the buffer
          (save-buffer)
          ;; restart org to make org-trello aware of the new setup
          (org-mode-restart)))
      
      
    5. orgtrello/–id-name

      Given a list of entities, return a map of (id . name)

      (defun orgtrello/--id-name (entities)
        "Given a list of entities, return a map of (id, name)."
        (let* ((id-name (make-hash-table :test 'equal)))
          (mapc (lambda (it) (puthash (orgtrello-query/--id it) (orgtrello-query/--name it) id-name)) entities)
          id-name))
      
      
    6. orgtrello/–name-id

      Given a list of entities, return a map of (name . id)

      (defun orgtrello/--name-id (entities)
        "Given a list of entities, return a map of (id, name)."
        (let* ((name-id (make-hash-table :test 'equal)))
          (mapc (lambda (it) (puthash (orgtrello-query/--name it) (orgtrello-query/--id it) name-id)) entities)
          name-id))
      
      
    7. orgtrello/–list-boards

      Execute the query that return a list of boards from the user's current trello account.

      (defun orgtrello/--list-boards ()
        "Return the map of the existing boards associated to the current account. (Synchronous request)"
        (cl-remove-if-not
         (lambda (board) (equal :json-false (orgtrello-query/--close-property board)))
         (orgtrello-query/http (orgtrello-api/get-boards) t)))
      
      
    8. orgtrello/–list-board-lists

      Execute the query that return a list of the board lists from the user's current trello account.

      (defun orgtrello/--list-board-lists (board-id)
        "Return the map of the existing list of the board with id board-id. (Synchronous request)"
        (orgtrello-query/http (orgtrello-api/get-lists board-id) t))
      
      
    9. orgtrello/convention-property-name

      A function to help eventually decorate the name of the keywords. Replace the space by dash at the moment.

      (defun orgtrello/convention-property-name (name)
        "Use the right convention for the property used in the headers of the org-mode file."
        (replace-regexp-in-string " " "-" name))
      
      
  11. Create board

    The main function regarding the creation of the board and the org attachment to such board.

    1. orgtrello/do-create-board-and-lists

      The main entry. We will ask the user to:

      • input the name of the board he/she wants.
      • input an optional board description

      Then launch the creation of the board. This will:

      • create the board
      • close the default lists created by trello
      • create as much list as the user's keywords with the same name as the corresponding keywords.
      • at last, update the beginning of the org buffer with the corresponding metadata:
        • board-name
        • board-id
        • list-id for all lists created
      (defun orgtrello/do-create-board-and-lists ()
        "Interactive command to create a board and the lists"
        (interactive)
        (defvar orgtrello/--board-name nil)        (setq orgtrello/--board-name nil)
        (defvar orgtrello/--board-description nil) (setq orgtrello/--board-description nil)
        (while (not orgtrello/--board-name) (setq orgtrello/--board-name (read-string "Please, input the desired board name: ")))
        (setq orgtrello/--board-description (read-string "Please, input the board description (empty for none): "))
        (cl-destructuring-bind (orgtrello/--board-id orgtrello/--board-name) (orgtrello/--create-board orgtrello/--board-name orgtrello/--board-description)
                               (let* ((orgtrello/--board-list-ids       (--map (orgtrello-query/--id it) (orgtrello/--list-board-lists orgtrello/--board-id)))  ;; first retrieve the existing lists (created by default on trello)
                                      (orgtrello/--lists-to-close       (orgtrello/--close-lists orgtrello/--board-list-ids))                                ;; close those lists (they may surely not match the name we want)
                                      (orgtrello/--board-lists-hname-id (orgtrello/--create-lists-according-to-keywords orgtrello/--board-id *LIST-NAMES*))) ;; create the list, this returns the ids list
                                 ;; remove eventual already present entry
                                 (orgtrello/--remove-properties-file orgtrello/--board-lists-hname-id)
                                 ;; update org buffer with new ones
                                 (orgtrello/update-orgmode-file-with-properties orgtrello/--board-name orgtrello/--board-id orgtrello/--board-lists-hname-id)))
        "Create board and lists done!")
      
      (message "org-trello - orgtrello loaded!")
      
      
    2. orgtrello/–create-board

      The actual board creation. This return a list of board id, board name.

      (defun orgtrello/--create-board (board-name &optional board-description)
        "Create a board with name and eventually a description."
        (progn
          (message "Creating board '%s'" board-name)
          (let* ((board-data (orgtrello-query/http (orgtrello-api/add-board board-name board-description) t)))
            (list (orgtrello-query/--id board-data) (orgtrello-query/--name board-data)))))
      
      
    3. orgtrello/–close-lists

      The close list routine. Given a list of list ids, close each of those list.

      (defun orgtrello/--close-lists (list-ids)
        "Given a list of ids, close those lists."
        (mapc (lambda (list-id)
                (progn
                  (message "Closing default list with id %s" list-id)
                  (orgtrello-query/http (orgtrello-api/close-list list-id) nil nil 'simple-error-callback)))
              list-ids))
      
      
    4. orgtrello/–create-lists-according-to-keywords

      Create as much list as the keywords to the board board-id.

      (defun orgtrello/--create-lists-according-to-keywords (board-id list-keywords)
        "Given a list of names, build those lists on the trello boards. Return the hashmap (name, id) of the new lists created."
        (cl-reduce
         (lambda (acc-hash-name-id list-name)
           (progn
             (message "Board id %s - Creating list '%s'" board-id list-name)
             (puthash list-name (orgtrello-query/--id (orgtrello-query/http (orgtrello-api/add-list list-name board-id) t)) acc-hash-name-id)
             acc-hash-name-id))
         list-keywords
         :initial-value (make-hash-table :test 'equal)))
      
      
  12. Utility function

    An utility decorator function to help in debugging without breakpoint. This is mostly for user when we need them to help us remotely debug.

    (defun trace (e &optional label)
      "Decorator for some inaccessible code to easily 'message'."
      (progn
        (if label
            (message "TRACE: %s: %S" label e)
            (message "TRACE: %S" e))
        e))
    
    

5.5 orgtrello-query

Namespace in charge of the http request to the trello api.

  1. Namespace Setup

    Static setup the user does not need to tinker with. This resumes to only one variable which is the prefix url for the trello api access.

    
    
    ;; #################### orgtrello-query/
    
    (defvar *TRELLO-URL* "https://api.trello.com/1" "The needed prefix url for trello")
    
    
  2. orgtrello-query/–make-dispatch-http-query

    As we do not have multi-methods (as in clojure), we tried to reproduce a similar behaviour. This describes a function which will setup a map to dispatch on the method (:get, :post, :put, :delete) on the other function describes in the namespace).

    This function is used only once to initialize the variable MAP-DISPATCH-HTTP-QUERY, which will be used later to dispatch on the http request built.

    (defun orgtrello-query/--make-dispatch-http-query ()
      "Make a map that will dispatch the function to call depending on the http verb :get, :put, :post, etc..."
      (let* ((map-dispatch (make-hash-table :test 'equal)))
        (puthash :get    'orgtrello-query/--get         map-dispatch)
        (puthash :put    'orgtrello-query/--post-or-put map-dispatch)
        (puthash :post   'orgtrello-query/--post-or-put map-dispatch)
        (puthash :delete 'orgtrello-query/--delete      map-dispatch)
        map-dispatch))
    
    (defvar *MAP-DISPATCH-HTTP-QUERY* (orgtrello-query/--make-dispatch-http-query))
    
    
  3. orgtrello-query/http

    This is the main function from the namespace in charge of executing the http request. We pass the query-map which is been built from the orgtrello-api namespace. Optionally, we pass a:

    • success-callback in charge of doing some action when the request finishes successfully.
    • error-callback in charge of doing some action when the request finishes erroneously.
    • sync flag to render the request synchronous (default is asynchronous)
    (defun orgtrello-query/http (query-map &optional sync success-callback error-callback)
      "Query the trello api asynchronously."
      (let* ((method      (gethash :method query-map))
             (fn-dispatch (gethash method *MAP-DISPATCH-HTTP-QUERY*)))
        (if sync
            (progn ;; synchronous request
              (puthash :sync t query-map)
              (let ((request-response (funcall fn-dispatch query-map success-callback error-callback)))
                (request-response-data request-response)))
          (funcall fn-dispatch query-map success-callback error-callback))))
    
    
  4. orgtrello-query/–map-dispatch-http-verb

    As described earlier, no multi-method, we use the same techniques to setup a map to dispatch on the method (:get, :post, :put, :delete) to map to the request http client DSL. This function is again called once.

    (defun orgtrello-query/--map-dispatch-http-verb ()
      (let* ((map-dispatch (make-hash-table :test 'equal)))
        (puthash :get    "GET"    map-dispatch)
        (puthash :put    "PUT"    map-dispatch)
        (puthash :post   "POST"   map-dispatch)
        (puthash :delete "DELETE" map-dispatch)
        map-dispatch))
    
    (defvar *MAP-DISPATCH-HTTP-VERB* (orgtrello-query/--map-dispatch-http-verb))
    
    
  5. orgtrello-query/–compute-method

    Exploiting the dispatch map, we compute the http method (GET, POST, PUT, DELETE). We could also have implemented this using simply cond.

    (defun orgtrello-query/--compute-method (method)
      "Given the keywords :get, :post, :put, :delete, map them into standard uppercase string."
      (gethash method *MAP-DISPATCH-HTTP-VERB*))
    
    
  6. orgtrello-query/–compute-url

    A function to compute the full trello url given an uri.

    (defun orgtrello-query/--compute-url (uri)
      "Compute the trello url from the given uri."
      (format "%s%s" *TRELLO-URL* uri))
    
    
  7. Input request abstraction extract functions

    Some functions around the data stored in the query-map, hiding the implementation details of such query-map.

    1. orgtrello-query/–method

      Extract the http method.

      (defun orgtrello-query/--method (query-map)
        "Retrieve the http method"
        (gethash :method query-map))
      
      
    2. orgtrello-query/–uri

      Extract the trello api uri.

      (defun orgtrello-query/--uri (query-map)
        "Retrieve the http uri"
        (gethash :uri query-map))
      
      
    3. orgtrello-query/–sync

      Retrieve the flag sync or not of the query-map.

      (defun orgtrello-query/--sync (query-map)
        "Retrieve the http sync flag"
        (gethash :sync query-map))
      
      
    4. orgtrello-query/–params

      Retrieve the params of the query.

      (defun orgtrello-query/--params (query-map)
        "Retrieve the http params"
        (gethash :params query-map))
      
      
  8. Output request abstraction extract function

    Some functions around the data stored in the http query response, hiding the implementation details.

    1. orgtrello-query/–id

      Extract the id of the entity from the entity-data.

      (defun orgtrello-query/--id (entity-data)
        "Extract the id of the entity from the entity"
        (assoc-default 'id entity-data))
      
      
    2. orgtrello-query/–name

      Extract the name of the entity from the entity-data.

      (defun orgtrello-query/--name (entity-data)
        "Extract the name of the entity from the entity"
        (assoc-default 'name entity-data))
      
      
    3. orgtrello-query/–list-id

      Extract the list identifier from the entity-data.

      (defun orgtrello-query/--list-id (entity-data)
        "Extract the list identitier of the entity from the entity"
        (assoc-default 'idList entity-data))
      
      
    4. orgtrello-query/–checklist-ids

      Extract the list of checklist ids from the entity-data.

      (defun orgtrello-query/--checklist-ids (entity-data)
        "Extract the checklist identifier of the entity from the entity"
        (assoc-default 'idChecklists entity-data))
      
      
    5. orgtrello-query/–check-items

      Extract the list of items/tasks from the entity-data.

      (defun orgtrello-query/--check-items (entity-data)
        "Extract the checklist identifier of the entity from the entity"
        (assoc-default 'checkItems entity-data))
      
      
    6. orgtrello-query/–card-id

      Extract the card id from the entity-data.

      (defun orgtrello-query/--card-id (entity-data)
        "Extract the card identifier of the entity from the entity"
        (assoc-default 'idCard entity-data))
      
      
    7. orgtrello-query/–due

      Extract the card id from the entity-data.

      (defun orgtrello-query/--due (entity-data)
        "Extract the due date of the entity from the query response"
        (assoc-default 'due entity-data))
      
      
    8. orgtrello-query/–state

      Extract the state from the entity-data.

      (defun orgtrello-query/--state (entity-data)
        "Extract the state of the entity"
        (assoc-default 'state entity-data))
      
      
    9. orgtrello-query/–close-property

      Extract the close flag from the entity-data.

      (defun orgtrello-query/--close-property (entity-data)
        "Extract the closed property of the entity"
        (assoc-default 'closed entity-data))
      
      
  9. HTTP request functions

    Those represent the actual http actions. They are closures on the trello identifier (consumer-key and access-token).

    1. orgtrello-query/–get

      The function around the GET http request. This will extract the method, uri and sync flag and build the http request. This is a closure around the consumer-key and access-token. The request's response is some json which will be parsed by the function 'json-read. In case of optional success or error callback are passed, they will be used. Otherwise, the 'standard-success-callback or the 'standard-error-callback will be used.

      (defun orgtrello-query/--get (query-map &optional success-callback error-callback)
        "GET"
        (let* ((method (orgtrello-query/--method query-map))
               (uri    (orgtrello-query/--uri    query-map))
               (sync   (orgtrello-query/--sync   query-map)))
          (request (orgtrello-query/--compute-url uri)
                   :sync    sync
                   :type    (orgtrello-query/--compute-method method)
                   :params  `((key . ,*consumer-key*)
                              (token . ,*access-token*))
                   :parser  'json-read
                   :success (if success-callback success-callback 'standard-success-callback)
                   :error   (if error-callback error-callback 'standard-error-callback))))
      
      
    2. orgtrello-query/–post-or-put

      The function around the POST/PUT verbs. This is similar to the get function but does some extract actions:

      • It extracts the params (which represents the needed information for the trello entity we will sync).
      • It encodes the payload in json with the json-encode function
      (defun orgtrello-query/--post-or-put (query-map &optional success-callback error-callback)
        "POST or PUT"
        (let* ((method  (orgtrello-query/--method query-map))
               (uri     (orgtrello-query/--uri    query-map))
               (payload (orgtrello-query/--params query-map))
               (sync    (orgtrello-query/--sync   query-map)))
          (request (orgtrello-query/--compute-url uri)
                   :sync    sync
                   :type    (orgtrello-query/--compute-method method)
                   :params  `((key . ,*consumer-key*)
                              (token . ,*access-token*))
                   :headers '(("Content-type" . "application/json"))
                   :data    (json-encode payload)
                   :parser  'json-read
                   :success (if success-callback success-callback 'standard-success-callback)
                   :error   (if error-callback error-callback 'standard-error-callback))))
      
      
    3. orgtrello-query/–delete

      The last verb we deal with, the DELETE action. Again similar as GET but does a DELETE.

      (defun orgtrello-query/--delete (query-map &optional success-callback error-callback)
        "DELETE"
        (let* ((method (orgtrello-query/--method query-map))
               (uri    (orgtrello-query/--uri    query-map))
               (sync   (orgtrello-query/--sync   query-map)))
          (request (orgtrello-query/--compute-url uri)
                   :sync    sync
                   :type    (orgtrello-query/--compute-method method)
                   :params  `((key . ,*consumer-key*)
                              (token . ,*access-token*))
                   :success (if success-callback success-callback 'standard-success-callback)
                   :error   (if error-callback error-callback 'standard-error-callback))))
      
      (message "org-trello - orgtrello-query/ loaded!")
      
      
  10. HTTP callbacks

    Those are actions done at the end of the http request.

    1. standard-error-callback

      The standard error-callback in charge of removing the orgtrello-marker written before synchronization. This display an error message on the minibuffer too.

      (cl-defun standard-error-callback (&key error-thrown &allow-other-keys)
        "Standard error callback"
        (save-excursion
            ;; find the current entry through the pointer
            (org-goto-local-search-headings *ORGTRELLO-MARKER* nil t)
            ;; remove the marker now that we're done
            (org-delete-property *ORGTRELLO-MARKER*))
        (message "There was some problem during the request to trello: %s" error-thrown))
      
      
    2. standard-success-callback

      A standard success callback that displays a simple message "Success." in the minibuffer.

      (cl-defun standard-success-callback ()
        "Standard success callback"
        (message "Success."))
      
      
    3. simple-error-callback

      A simpler error message callback which simply displays an error message on the minibuffer, indicating the error thrown by the execution of the request.

      (cl-defun simple-error-callback (&key error-thrown &allow-other-keys)
        "Standard error callback"
        (message "There was some problem during the request to trello: %s" error-thrown))
      
      
    4. orgtrello-query/–delete-success-callback

      This one is the default delete success callback. It needs to synchronize back the buffer to remove the entry we just destroyed on trello.

      • Return to the heading we want to delete
      • Delete the property which represents the trello identifier
      • Remove the full entity (for this we hide the subtree)
      • Go at the beginning of the line
      • Kill the next 2 lines
      • At last display a simple message to notify the end of the removal.
      (cl-defun orgtrello-query/--delete-success-callback (&key data response &allow-other-keys)
        "Callback function called at the end of a successful delete request."
        (progn
          (org-back-to-heading t)
          (org-delete-property *ORGTRELLO-ID*)
          (hide-subtree)
          (beginning-of-line)
          (kill-line)
          (kill-line)
          (message "Entity deleted!")))
      
      
    5. orgtrello-query/–post-put-success-callback-update-id

      This one is the default post/put success callback. It's in charge of updating the org buffer with the trello id for the entity that has been synchronized. What it does:

      • Get back to the current max header
      • Then place itself to the org-trello marker put at the beginning of the synchronization.
      • Remove such marker
      • Get the current header information
      • If an identifier is already present, simply display a message. Otherwise, add the property orgtrello-id with the identifier returned by the query.
      (cl-defun orgtrello-query/--post-put-success-callback-update-id (&key data &allow-other-keys)
        "Called back function at the end of the post/put request to update the trello id in the org-mode file."
        (let* ((orgtrello-query/--entry-new-id (orgtrello-query/--id data))
               (orgtrello-query/--entry-name   (orgtrello-query/--name data)))
          ;; will update via tag the trello id of the new persisted data (if needed)
          (save-excursion
            ;;(while (org-up-heading-safe))
            ;; find the current entry through the pointer
            (org-goto-local-search-headings *ORGTRELLO-MARKER* nil t)
            ;; remove the marker now that we're done
            (org-delete-property *ORGTRELLO-MARKER*)
            ;; now we extract the data
            (let* ((orgtrello-query/--entry-metadata (orgtrello-data/metadata))
                   (orgtrello-query/--entry-id       (orgtrello/--id orgtrello-query/--entry-metadata)))
              (if orgtrello-query/--entry-id ;; id already present in the org-mode file
                  ;; no need to add another
                  (message "Entity '%s' synced with id '%s'" orgtrello-query/--entry-name orgtrello-query/--entry-id)
                (progn
                  ;; not present, this was just created, we add a simple property
                  (org-set-property *ORGTRELLO-ID* orgtrello-query/--entry-new-id)
                  (message "Newly entity '%s' synced with id '%s'" orgtrello-query/--entry-name orgtrello-query/--entry-new-id)))))))
      
      

5.6 orgtrello-api

This is the namespace responsible for the interface with trello's public api. We have decoupled the http request from the http request data structure. This way, we can test the api's request.

  1. orgtrello-api/add-board

    A simple function to create a board. We need a name and an optional description.

    
    
    ;; #################### orgtrello-api
    
    (defun orgtrello-api/add-board (name &optional description)
      "Create a board"
      (let* ((payload (if description
                          `(("name" . ,name)
                            ("desc" . ,description))
                        `(("name" . ,name)))))
        (orgtrello-hash/make-hash :post "/boards" payload)))
    
    
  2. orgtrello-api/get-boards

    A function to retrieve the list of boards of the user.

    (defun orgtrello-api/get-boards ()
      "Retrieve the boards of the current user."
      (orgtrello-hash/make-hash :get "/members/me/boards"))
    
    
  3. orgtrello-api/get-board

    A function to retrieve all the information about the board with id.

    (defun orgtrello-api/get-board (id)
      "Retrieve the boards of the current user."
      (orgtrello-hash/make-hash :get (format "/boards/%s" id)))
    
    
  4. orgtrello-api/get-cards

    Retrieve all the cards from the board with id board-id.

    (defun orgtrello-api/get-cards (board-id)
      "cards of a board"
      (orgtrello-hash/make-hash :get (format "/boards/%s/cards" board-id)))
    
    
  5. orgtrello-api/get-card

    Retrieve the information of the specific card with id card-id.

    (defun orgtrello-api/get-card (card-id)
      "Detail of a card with id card-id."
      (orgtrello-hash/make-hash :get (format "/cards/%s" card-id)))
    
    
  6. orgtrello-api/delete-card

    Delete the card with id card-id.

    (defun orgtrello-api/delete-card (card-id)
      "Delete a card with id card-id."
      (orgtrello-hash/make-hash :delete (format "/cards/%s" card-id)))
    
    
  7. orgtrello-api/get-lists

    Retrieve the lists of the board with id board-id.

    (defun orgtrello-api/get-lists (board-id)
      "Display the lists of the board"
      (orgtrello-hash/make-hash :get (format "/boards/%s/lists" board-id)))
    
    
  8. orgtrello-api/close-list

    We need a routine to be able to close a list (typically when we create a board, we do not want the default list created by trello). So a function to close a specific list with id list-id.

    (defun orgtrello-api/close-list (list-id)
      "'Close' the list with id list-id."
      (orgtrello-hash/make-hash :put (format "/lists/%s/closed" list-id) '((value . t))))
    
    
  9. orgtrello-api/get-list

    Retrieve the information about the list with id list-id.

    (defun orgtrello-api/get-list (list-id)
      "Get a list by id"
      (orgtrello-hash/make-hash :get (format "/lists/%s" list-id)))
    
    
  10. orgtrello-api/add-list

    Adding a list named name to a board with id idBoard.

    (defun orgtrello-api/add-list (name idBoard)
      "Add a list - the name and the board id are mandatory (so i say!)."
      (orgtrello-hash/make-hash :post "/lists/" `(("name" . ,name) ("idBoard" . ,idBoard))))
    
    
  11. orgtrello-api/add-card

    Adding a card with name name to the list idList. Optionally, we can use a due date.

    (defun orgtrello-api/add-card (name idList &optional due)
      "Add a card to a board"
      (let* ((orgtrello-api/add-card--default-params `(("name" . ,name) ("idList" . ,idList)))
             (orgtrello-api/add-card--params (if due (cons `("due" . ,due) orgtrello-api/add-card--default-params) orgtrello-api/add-card--default-params)))
        (orgtrello-hash/make-hash :post "/cards/" orgtrello-api/add-card--params)))
    
    
  12. orgtrello-api/get-cards-from-list

    A function to retrieve the full list of cards a list with id list-id owns.

    (defun orgtrello-api/get-cards-from-list (list-id)
      "List all the cards"
      (orgtrello-hash/make-hash :get (format "/lists/%s/cards" list-id)))
    
    
  13. orgtrello-api/move-card

    An update function for the card card-id which belongs to the idList. This can simply move the card from its current list to another. Optionally, we can rename the card to name and update its due date.

    (defun orgtrello-api/move-card (card-id idList &optional name due)
      "Move a card to another list"
      (let* ((orgtrello-api/move-card--default-params `(("idList" . ,idList)))
             (orgtrello-api/move-card--params-name (if name (cons `("name" . ,name) orgtrello-api/move-card--default-params) orgtrello-api/move-card--default-params))
             (orgtrello-api/move-card--params-due  (if due (cons `("due" . ,due) orgtrello-api/move-card--params-name) orgtrello-api/move-card--params-name)))
        (orgtrello-hash/make-hash :put (format "/cards/%s" card-id) orgtrello-api/move-card--params-due)))
    
    
  14. orgtrello-api/add-checklist

    We can add a checklist entitled name to the card with id card-id.

    (defun orgtrello-api/add-checklist (card-id name)
      "Add a checklist to a card"
      (orgtrello-hash/make-hash :post
                 (format "/cards/%s/checklists" card-id)
                 `(("name" . ,name))))
    
    
  15. orgtrello-api/update-checklist

    Also, we can update the checklist with id checklist-id to a new name name.

    (defun orgtrello-api/update-checklist (checklist-id name)
      "Update the checklist's name"
      (orgtrello-hash/make-hash :put
                 (format "/checklists/%s" checklist-id)
                 `(("name" . ,name))))
    
    
  16. orgtrello-api/get-checklists

    Retrieve the list of checklist from the card with id card-id.

    (defun orgtrello-api/get-checklists (card-id)
      "List the checklists of a card"
      (orgtrello-hash/make-hash :get (format "/cards/%s/checklists" card-id)))
    
    
  17. orgtrello-api/get-checklist

    Or simply retrieve the detail about the checklist with id checklist-id.

    (defun orgtrello-api/get-checklist (checklist-id)
      "Retrieve all the information from a checklist"
      (orgtrello-hash/make-hash :get (format "/checklists/%s" checklist-id)))
    
    
  18. orgtrello-api/delete-checklist

    We need to be able to remove checklist checklist-id.

    (defun orgtrello-api/delete-checklist (checklist-id)
      "Delete a checklist with checklist-id"
      (orgtrello-hash/make-hash :delete (format "/checklists/%s" checklist-id)))
    
    
  19. orgtrello-api/add-tasks

    Adding a task/item entitled name to the checklist checklist-id with an optional checked state.

    (defun orgtrello-api/add-tasks (checklist-id name &optional checked)
      "Add todo tasks (trello items) to a checklist with id 'id'"
      (let* ((payload (if checked
                          `(("name"  . ,name) ("checked" . ,checked))
                        `(("name" . ,name)))))
        (orgtrello-hash/make-hash :post (format "/checklists/%s/checkItems" checklist-id) payload)))
    
    
  20. orgtrello-api/update-task

    A function to deal with the update of the task/item with id task-id and name name from the checklist checklist-id (trello api forces the card-id too). Optionally, we can change its checked state.

    (defun orgtrello-api/update-task (card-id checklist-id task-id name &optional state)
      "Update a task"
      (let* ((payload (if state
                          `(("name"  . ,name) ("state" . ,state))
                        `(("name" . ,name)))))
        (orgtrello-hash/make-hash
         :put
         (format "/cards/%s/checklist/%s/checkItem/%s" card-id checklist-id task-id)
         payload)))
    
    
  21. orgtrello-api/get-tasks

    Retrieve the list of tasks from the checklist checklist-id.

    (defun orgtrello-api/get-tasks (checklist-id)
      "List the checklist items."
        (orgtrello-hash/make-hash :get (format "/checklists/%s/checkItems/" checklist-id)))
    
    
  22. orgtrello-api/delete-task

    To keep the symmetry with the other entities, we need to be able to destroy the task/item task-id from the checklist checklist-id.

    (defun orgtrello-api/delete-task (checklist-id task-id)
      "Delete a task with id task-id"
      (orgtrello-hash/make-hash :delete (format "/checklists/%s/checkItems/%s" checklist-id task-id)))
    
    (message "org-trello - orgtrello-api loaded!")
    
    

5.7 orgtrello-data

Namespace in charge of manipulating/extracting the org buffer's data.

  1. orgtrello-data/–convert-orgmode-date-to-trello-date

    A simple function to help in transforming the org format used in deadline into trello format. This is really basic. We must improve this.

    
    
    ;; #################### orgtrello-data
    
    (defvar *ORGTRELLO-ID* "orgtrello-id" "Marker used for the trello identifier")
    
    (defun orgtrello-data/--convert-orgmode-date-to-trello-date (orgmode-date)
      "Convert the org-mode deadline into a time adapted for trello."
      (if (and orgmode-date (not (string-match-p "T*Z" orgmode-date)))
          (cl-destructuring-bind (sec min hour day mon year dow dst tz)
                                 (--map (if it
                                            (if (< it 10)
                                                (concat "0" (int-to-string it))
                                              (int-to-string it)))
                                        (parse-time-string orgmode-date))
                                 (let* ((year-mon-day (concat year "-" mon "-" day "T"))
                                        (hour-min-sec (if hour (concat hour ":" min ":" sec) "00:00:00")))
                                   (concat year-mon-day hour-min-sec ".000Z")))
        orgmode-date))
    
    
  2. orgtrello-data/metadata

    This is an important routine which will extract all the informations from the org buffer for a given entity (typically the entity under the caret's current position):

    • level
    • label
    • id
    • due

    This uses org's api and add some extra information specific to org-trello.

    (defun orgtrello-data/metadata ()
      "Compute the metadata from the org-heading-components entry, add the identifier and extract the metadata needed."
      (let* ((orgtrello-data/metadata--id       (org-entry-get (point) *ORGTRELLO-ID*))
             (orgtrello-data/metadata--due      (orgtrello-data/--convert-orgmode-date-to-trello-date (org-entry-get (point) "DEADLINE")))
             (orgtrello-data/metadata--metadata (org-heading-components)))
        (->> orgtrello-data/metadata--metadata
             (cons orgtrello-data/metadata--due)
             (cons orgtrello-data/metadata--id)
             orgtrello-data/--get-metadata)))
    
    
  3. orgtrello-data/–parent-metadata

    This function permits to retrieve the metadata of the current entry. Note that if we are to the upper level already, this will return the exact same data as the current entry.

    (defun orgtrello-data/--parent-metadata ()
      "Extract the metadata from the current heading's parent."
      (save-excursion
        (org-up-heading-safe)
        (orgtrello-data/metadata)))
    
    
  4. orgtrello-data/–grandparent-metadata

    Again, from a current entry, return the metadata of the grand-parent entry. Note again that if we are to the upper level, the same data as the current entry will be returned.

    (defun orgtrello-data/--grandparent-metadata ()
      "Extract the metadata from the current heading's grandparent."
      (save-excursion
        (org-up-heading-safe)
        (org-up-heading-safe)
        (orgtrello-data/metadata)))
    
    
  5. orgtrello-data/entry-get-full-metadata

    This is an orchestration from the previous functions. This will return a map with as key:

    • :current the current entity under the caret's position
    • :parent the current entity's parent
    • :grandparent the current entity's grandparent

    If the level of the current entity exceeds level 4, the entry is dismissed (only 3 levels can be compatible in trello) so the function will return nil.

    (defun orgtrello-data/entry-get-full-metadata ()
      "Compute the metadata needed for one entry into a map with keys :current, :parent, :grandparent.
       Returns nil if the level is superior to 4."
      (let* ((heading (orgtrello-data/metadata))
             (level   (gethash :level heading)))
        (if (< level 4)
            (let* ((parent-heading      (orgtrello-data/--parent-metadata))
                   (grandparent-heading (orgtrello-data/--grandparent-metadata))
                   (mapdata (make-hash-table :test 'equal)))
              ;; build the metadata with every important pieces
              (puthash :current     heading             mapdata)
              (puthash :parent      parent-heading      mapdata)
              (puthash :grandparent grandparent-heading mapdata)
              mapdata))))
    
    
  6. orgtrello-data/–get-metadata

    An adapter function to transform any org data into org-trello data.

    (defun orgtrello-data/--get-metadata (heading-metadata)
      "Given the heading-metadata returned by the function 'org-heading-components, make it a hashmap with key :level, :keyword, :title. and their respective value"
      (cl-destructuring-bind (id due level _ keyword _ title &rest) heading-metadata
                             (orgtrello-hash/make-hash-org level keyword title id due)))
    
    (message "org-trello - orgtrello-data loaded!")
    
    

5.8 orgtrello-hash

Utility namespace in charge of simplifying the construction of org-trello data.

  1. orgtrello-hash/make-hash-org

    A simple factory function to create a map from the needed org entity data.

    
    
    ;; #################### orgtrello-hash
    
    (defun orgtrello-hash/make-hash-org (level keyword title id due)
      "Utility function to ease the creation of the orgtrello-metadata"
      (let ((h (make-hash-table :test 'equal)))
        (puthash :level   level   h)
        (puthash :keyword keyword h)
        (puthash :title   title   h)
        (puthash :id      id      h)
        (puthash :due     due     h)
        h))
    
    
  2. orgtrello-hash/make-hash

    A simple utility function to help in creating the http request map to feed to the orgtrello-query namespace.

    (defun orgtrello-hash/make-hash (method uri &optional params)
      "Utility function to ease the creation of the map - wait, where are my clojure data again!?"
      (let ((h (make-hash-table :test 'equal)))
        (puthash :method method h)
        (puthash :uri    uri    h)
        (if params (puthash :params params h))
        h))
    
    (message "org-trello - orgtrello-hash loaded!")
    
    

6 Resulting

Now let's generate the full code.

Latest posts