removed tco, added threading macros, added file comparisons
This commit is contained in:
parent
4956cde9bb
commit
e0a3f89e63
644
conf.org
644
conf.org
|
@ -2684,65 +2684,7 @@ These are =org-mode=-agnostic functions that pertain to sql. They are basically
|
|||
#+BEGIN_SRC emacs-lisp
|
||||
(require 'sql)
|
||||
|
||||
(defun nd/sql-cmd (db cmd)
|
||||
"Execute string CMD on database DB executing `sql-sqlite-program'.
|
||||
Returns the output of CMD. SQL should not contain any quotes as if it
|
||||
were entered on the shell."
|
||||
(shell-command-to-string (format "%s %s \"%s\"" sql-sqlite-program db cmd)))
|
||||
|
||||
(defun nd/sql-escape-text (txt)
|
||||
"Escape and quote TXT in order to insert into sqlite db via 'insert'.
|
||||
This assumes the insertion command will be run on a shell where the
|
||||
sql command string is in double quotes."
|
||||
(let* ((new-txt (replace-regexp-in-string "'" "''" txt nil t))
|
||||
(new-txt (replace-regexp-in-string "\"" "\\\"" new-txt nil t)))
|
||||
(concat "'" new-txt "'")))
|
||||
|
||||
(defun nd/sql-to-string (entry)
|
||||
"Convert ENTRY to a string suitable for insertion into SQLite db.
|
||||
Converts numbers to strings, flanks strings with '\"', and converts
|
||||
any other symbols to their symbol name."
|
||||
(cond ((stringp entry) (nd/sql-escape-text entry))
|
||||
((numberp entry) (number-to-string entry))
|
||||
(entry (symbol-name entry))
|
||||
(t "NULL")))
|
||||
|
||||
(defun nd/sql-construct-insert (tbl-name tbl-data)
|
||||
"Concatenate DATA into escaped comma-separated string for SQL insertion."
|
||||
;; column names are the properties in the plist
|
||||
(let* ((col-names (-slice tbl-data 0 nil 2))
|
||||
(col-names (mapcar (lambda (s) (substring (symbol-name s) 1)) col-names))
|
||||
(col-names (string-join col-names ","))
|
||||
;; column values are the values of each property
|
||||
(col-values (-slice tbl-data 1 nil 2))
|
||||
(col-values (mapcar #'nd/sql-to-string col-values))
|
||||
(col-values (string-join col-values ",")))
|
||||
(format "insert into %s (%s) values (%s);" (symbol-name tbl-name)
|
||||
col-names col-values )))
|
||||
|
||||
(defun nd/sql-construct-insert-transaction (all-data)
|
||||
"Construct transaction string to insert ALL-DATA into SQL.
|
||||
Does not actually execute the string."
|
||||
(let* ((scan-tbl
|
||||
(lambda (tbl)
|
||||
(let ((name (car tbl))
|
||||
(data (cdr tbl)))
|
||||
(string-join (mapcar
|
||||
(lambda (d)
|
||||
(nd/sql-construct-insert name d))
|
||||
data)))))
|
||||
(ins (mapcar (lambda (tbl) (funcall scan-tbl tbl)) all-data))
|
||||
(ins (string-join ins)))
|
||||
(format "begin transaction; %s commit;" ins)))
|
||||
|
||||
(defun nd/sql-insert (db tbl-name tbl-data)
|
||||
"Insert list TBL-DATA into TBL-NAME in sqlite database DB."
|
||||
(nd/sql-cmd db (nd/sql-construct-insertion tbl-name tbl-data)))
|
||||
|
||||
(defun nd/sql-insert-multi (db all-data)
|
||||
"Insert ALL-DATA into sqlite DB."
|
||||
(nd/sql-cmd db (nd/sql-construct-insert-transaction all-data)))
|
||||
|
||||
;; this needs a better home :/
|
||||
(defun nd/alist-put (alist prop value &optional front)
|
||||
"For given ALIST, append VALUE to the current values in prop.
|
||||
Current values (that is the cdr of each key) is assumed to be a list.
|
||||
|
@ -2763,49 +2705,180 @@ t, add to the front of current values list instead of the back."
|
|||
(t
|
||||
`((,prop ,value))))))
|
||||
|
||||
;; this somehow doesn't exist O.o
|
||||
;; this also needs a better home :/
|
||||
(defun nd/sql-plist-get-vals(plist)
|
||||
"Return all the values in PLIST."
|
||||
(-slice plist 1 nil 2))
|
||||
|
||||
;; (defun nd/sql-construct-insert-transaction (all-data)
|
||||
;; "Construct transaction string to insert ALL-DATA into SQL.
|
||||
;; Does not actually execute the string."
|
||||
;; (let* ((scan-tbl
|
||||
;; (lambda (tbl)
|
||||
;; (let ((name (car tbl))
|
||||
;; (data (cdr tbl)))
|
||||
;; (string-join (mapcar
|
||||
;; (lambda (d)
|
||||
;; (nd/sql-construct-insert name d))
|
||||
;; data)))))
|
||||
;; (ins (mapcar (lambda (tbl) (funcall scan-tbl tbl)) all-data))
|
||||
;; (ins (string-join ins)))
|
||||
;; (format "begin transaction; %s commit;" ins)))
|
||||
|
||||
;; SQL string parsing functions
|
||||
(defun nd/sql-to-plist (out &rest cols)
|
||||
"Parse SQL output string OUT to an plist representing the data.
|
||||
COLS are the column names as symbols used to obtain OUT."
|
||||
(unless (equal out "")
|
||||
(let* ((out-trim (string-trim out))
|
||||
(row-data (split-string out-trim "\n"))
|
||||
(cell-data (mapcar (lambda (s) (split-string s "|")) row-data)))
|
||||
(mapcar (lambda (d) (cl-mapcan #'list cols d)) cell-data)))
|
||||
(mapcar (lambda (d) (cl-mapcan #'list cols d)) cell-data))))
|
||||
|
||||
(defun nd/sql-select (db tbl-name &rest cols)
|
||||
;; sql to string functions
|
||||
(defun nd/sql-escape-text (txt)
|
||||
"Escape and quote TXT in order to insert into sqlite db via 'insert'.
|
||||
This assumes the insertion command will be run on a shell where the
|
||||
sql command string is in double quotes."
|
||||
(let* ((new-txt (replace-regexp-in-string "'" "''" txt nil t))
|
||||
(new-txt (replace-regexp-in-string "\"" "\\\"" new-txt nil t)))
|
||||
(concat "'" new-txt "'")))
|
||||
|
||||
(defun nd/sql-to-string (entry)
|
||||
"Convert ENTRY to a string suitable for insertion into SQLite db.
|
||||
Converts numbers to strings, flanks strings with '\"', and converts
|
||||
any other symbols to their symbol name."
|
||||
(cond ((stringp entry) (nd/sql-escape-text entry))
|
||||
((numberp entry) (number-to-string entry))
|
||||
(entry (symbol-name entry))
|
||||
(t "NULL")))
|
||||
|
||||
(defun nd/sql-kw-to-colname (kw)
|
||||
"Return string representation of KW for column in sql database."
|
||||
(substring (symbol-name kw) 1))
|
||||
|
||||
(defun nd/sql-plist-concat (plist &optional sep)
|
||||
"Concatenate a PLIST to string to be used in a SQL statement.
|
||||
Returns a string formatted like 'prop1 = value1 SEP prop2 = value2'
|
||||
from a plist like '(:prop1 value1 :prop2 value2)."
|
||||
(let* ((sep (or sep ","))
|
||||
(keys (plist-get-keys plist))
|
||||
(keys (mapcar #'nd/sql-kw-to-colname keys))
|
||||
(vals (nd/sql-plist-get-vals plist))
|
||||
(vals (mapcar #'nd/sql-to-string vals))
|
||||
(str (mapcar* (lambda (k v) (format "%s=%s" k v)) keys vals)))
|
||||
(string-join str sep)))
|
||||
|
||||
;; SQL formatting functions
|
||||
(defun nd/org-sql-fmt-insert (tbl-name tbl-data)
|
||||
"Format SQL insert command from TBL-NAME and TBL-DATA."
|
||||
(let* ((col-names (plist-get-keys tbl-data))
|
||||
(col-names (mapcar (lambda (s) (substring (symbol-name s) 1)) col-names))
|
||||
(col-names (string-join col-names ","))
|
||||
(col-values (nd/sql-plist-get-vals tbl-data))
|
||||
(col-values (mapcar #'nd/sql-to-string col-values))
|
||||
(col-values (string-join col-values ",")))
|
||||
(format "insert into %s (%s) values (%s);" (symbol-name tbl-name)
|
||||
col-names col-values )))
|
||||
|
||||
(defun nd/org-sql-fmt-update (tbl-name update)
|
||||
"Format SQL update command from TBL-NAME, UPDATE, and CONDS."
|
||||
(let ((upd-str (nd/sql-plist-concat (car update)))
|
||||
(conds-str (nd/sql-plist-concat (cdr update) " and ")))
|
||||
(format "update %s set %s where %s;" (symbol-name tbl-name)
|
||||
upd-str conds-str)))
|
||||
|
||||
(defun nd/org-sql-fmt-delete (tbl-name conds)
|
||||
"Format SQL update command from TBL-NAME and CONDS."
|
||||
(let ((conds-str (nd/sql-plist-concat conds " and ")))
|
||||
(format "delete from %s where %s;" (symbol-name tbl-name) conds-str)))
|
||||
|
||||
(defun nd/org-sql-fmt-trans (sql-str)
|
||||
"Format SQL transaction from list of SQL commands as strings SQL-STR."
|
||||
(when sql-str
|
||||
(nd/org-sql->> sql-str
|
||||
(-flatten)
|
||||
(string-join)
|
||||
(format "begin transaction; %s commit;"))))
|
||||
|
||||
(defun nd/org-sql-fmt-multi (tbl fun)
|
||||
(let ((name (car tbl))
|
||||
(data (cdr tbl)))
|
||||
(mapcar (lambda (r) (funcall fun name r)) data)))
|
||||
|
||||
(defun nd/org-sql-fmt-inserts (tbl)
|
||||
(nd/org-sql-fmt-multi tbl #'nd/org-sql-fmt-insert))
|
||||
|
||||
(defun nd/org-sql-fmt-updates (tbl)
|
||||
(nd/org-sql-fmt-multi tbl #'nd/org-sql-fmt-update))
|
||||
|
||||
(defun nd/org-sql-fmt-deletes (tbl)
|
||||
(nd/org-sql-fmt-multi tbl #'nd/org-sql-fmt-delete))
|
||||
|
||||
;; SQL command abstractions
|
||||
(defun nd/sql-cmd (db cmd &optional show-err foreign-keys)
|
||||
"Execute string CMD on database DB executing `sql-sqlite-program'.
|
||||
Returns the output of CMD. SQL should not contain any quotes as if it
|
||||
were entered on the shell."
|
||||
(when cmd
|
||||
(let* ((err (if show-err "" " 2> /dev/null"))
|
||||
(pragma (if foreign-keys
|
||||
"PRAGMA foreign_keys = ON;"
|
||||
"PRAGMA foreign_keys = OFF;"))
|
||||
;; TODO, there has to be a better way to fix this foreign key bs
|
||||
(cmd (format "%s %s \"%s%s\"%s" sql-sqlite-program db pragma cmd err)))
|
||||
(shell-command-to-string cmd))))
|
||||
|
||||
(defun nd/sql-select (db tbl-name cols &optional conds)
|
||||
"Select columns from TBL-NAME in DB where COLS is the list of columns.
|
||||
If COLS is nil, all columns will be returned. Columns is expected as
|
||||
a list of keywords like ':col1' and :col2'."
|
||||
a list of keywords like ':col1' and :col2'. CONDS, if supplied, is
|
||||
a plist of conditions to test in the select statement. (currently
|
||||
joined by AND)"
|
||||
(let* ((colnames
|
||||
(if (not cols) "*"
|
||||
(string-join
|
||||
(mapcar (lambda (s) (substring (symbol-name s) 1)) cols)
|
||||
",")))
|
||||
(cmd (format "select %s from %s;" colnames (symbol-name tbl-name)))
|
||||
(tbl-str (symbol-name tbl-name))
|
||||
(cmd (if (not conds)
|
||||
(format "select %s from %s;" colnames tbl-str)
|
||||
(let ((conds-str (nd/sql-plist-concat conds " and ")))
|
||||
(format "select %s from %s where %s;" colnames
|
||||
tbl-str conds-str))))
|
||||
(out (nd/sql-cmd db cmd)))
|
||||
(apply #'nd/sql-to-plist out cols)))
|
||||
|
||||
;; this somehow doesn't exist O.o
|
||||
(defun nd/sql-plist-get-vals(plist)
|
||||
"Return all the values in PLIST."
|
||||
(-slice plist 1 nil 2))
|
||||
|
||||
(defun nd/sql-kw-to-colname (kw)
|
||||
"Returns string representation of KW for column in sql database."
|
||||
(substring (symbol-name kw) 1))
|
||||
|
||||
(defun nd/sql-delete (db tbl-name cols)
|
||||
"Delete records from TBL-NAME in DB where COLS are true.
|
||||
COND is a plist of column names and values, '(:col1 val1 :col2 val2)',
|
||||
(defun nd/sql-delete (db tbl-name conds)
|
||||
"Delete records from TBL-NAME in DB where CONDS are true.
|
||||
CONDS is a plist of column names and values, '(:col1 val1 :col2 val2)',
|
||||
where values will be deleted if the listed columns have the listed
|
||||
values (AND condition)."
|
||||
(let* ((keys (plist-get-keys cols))
|
||||
(keys (mapcar #'nd/sql-kw-to-colname keys))
|
||||
(vals (nd/sql-plist-get-vals cols))
|
||||
(vals (mapcar #'nd/sql-to-string vals))
|
||||
(conds-str (mapcar* (lambda (k v) (format "%s=%s" k v)) keys vals))
|
||||
(conds-str (string-join conds-str " and "))
|
||||
(cmd (format "delete from %s where %s;" (symbol-name tbl-name) conds-str)))
|
||||
(let* ((conds-str (nd/sql-plist-concat " and "))
|
||||
(cmd (format "delete from %s where %s;"
|
||||
(symbol-name tbl-name) conds-str)))
|
||||
(nd/sql-cmd db cmd)))
|
||||
|
||||
(defun nd/sql-update (db tbl-name update conds)
|
||||
"Update records in TBL-NAME in DB with UPDATE where CONDS are true.
|
||||
VALUES is a plist containing the columns and new values as
|
||||
'(col1: newval1 col2: newval2) and CONDS is a similar plist
|
||||
where columns in UPDATE will be updated if values matching those in
|
||||
CONDS are found (AND condition)."
|
||||
(let* ((upd-str (nd/sql-plist-concat update))
|
||||
(conds-str (nd/sql-plist-to-condition conds " and "))
|
||||
(cmd (format "update %s set %s where %s;"
|
||||
(symbol-name tbl-name) upd-str conds-str)))
|
||||
(nd/sql-cmd db cmd)))
|
||||
|
||||
(defun nd/sql-insert (db tbl-name tbl-data)
|
||||
"Insert list TBL-DATA into TBL-NAME in sqlite database DB."
|
||||
(nd/sql-cmd db (nd/sql-construct-insertion tbl-name tbl-data)))
|
||||
|
||||
(defun nd/sql-insert-multi (db all-data)
|
||||
"Insert ALL-DATA into sqlite DB."
|
||||
(nd/sql-cmd db (nd/sql-construct-insert-transaction all-data)))
|
||||
#+END_SRC
|
||||
**** org parsing function
|
||||
Basic functions to parse org strings
|
||||
|
@ -2841,17 +2914,15 @@ If TS is nil or TS cannot be understood, nil will be returned."
|
|||
#+END_SRC
|
||||
**** org sql schemas
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defconst nd/org-sqlite-files-schema
|
||||
"CREATE TABLE files (
|
||||
(defconst nd/org-sql-schemas
|
||||
'("CREATE TABLE files (
|
||||
file_path TEXT PRIMARY KEY ASC,
|
||||
md5 TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
time_modified DATE,
|
||||
time_created DATE,
|
||||
time_accessed DATE);"
|
||||
"Schema to build the files table in the org db.")
|
||||
|
||||
(defconst nd/org-sqlite-header-schema
|
||||
"CREATE TABLE headlines (
|
||||
file_path TEXT,
|
||||
headline_offset INTEGER,
|
||||
|
@ -2867,10 +2938,9 @@ priority INTEGER,
|
|||
content TEXT,
|
||||
PRIMARY KEY (file_path ASC, headline_offset ASC),
|
||||
FOREIGN KEY (file_path) REFERENCES files (file_path)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE);"
|
||||
"Schema to build the headers table in the org db.")
|
||||
|
||||
(defconst nd/org-sqlite-tags-schema
|
||||
"CREATE TABLE tags (
|
||||
file_path TEXT,
|
||||
headline_offset INTEGER,
|
||||
|
@ -2878,11 +2948,10 @@ tag TEXT,
|
|||
inherited BOOLEAN,
|
||||
FOREIGN KEY (file_path, headline_offset)
|
||||
REFERENCES headlines (file_path, headline_offset)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
PRIMARY KEY (file_path, headline_offset, tag, inherited));"
|
||||
"Schema to build the tags table in the org db.")
|
||||
|
||||
(defconst nd/org-sqlite-properties-schema
|
||||
"CREATE TABLE properties (
|
||||
file_path TEXT,
|
||||
headline_offset INTEGER,
|
||||
|
@ -2892,11 +2961,10 @@ val_text TEXT NOT NULL,
|
|||
inherited BOOLEAN,
|
||||
FOREIGN KEY (file_path, headline_offset)
|
||||
REFERENCES headlines (file_path, headline_offset)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
PRIMARY KEY (file_path ASC, property_offset ASC));"
|
||||
"Schema to build the properties table in the org db.")
|
||||
|
||||
(defconst nd/org-sqlite-clocking-schema
|
||||
"CREATE TABLE clocking (
|
||||
file_path TEXT,
|
||||
headline_offset INTEGER,
|
||||
|
@ -2906,11 +2974,10 @@ time_end DATE,
|
|||
clock_note TEXT,
|
||||
FOREIGN KEY (file_path, headline_offset)
|
||||
REFERENCES headlines (file_path, headline_offset)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
PRIMARY KEY (file_path ASC, clock_offset ASC));"
|
||||
"Schema to build the clocking table in the org db.")
|
||||
|
||||
(defconst nd/org-sqlite-logbook-schema
|
||||
"CREATE TABLE logbook (
|
||||
file_path TEXT,
|
||||
headline_offset INTEGER,
|
||||
|
@ -2920,23 +2987,21 @@ header TEXT,
|
|||
note TEXT,
|
||||
FOREIGN KEY (file_path, headline_offset)
|
||||
REFERENCES headlines (file_path, headline_offset)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
PRIMARY KEY (file_path ASC, entry_offset ASC));"
|
||||
"Schema to build the logbook table in the org db.")
|
||||
|
||||
(defconst nd/org-sqlite-state-changes-schema
|
||||
"CREATE TABLE state_changes (
|
||||
file_path TEXT,
|
||||
entry_offset INTEGER,
|
||||
state_old TEXT NOT NULL,
|
||||
state_new TEXT NOT NULL,
|
||||
FOREIGN KEY (file_path, entry_offset)
|
||||
REFERENCES headlines (file_path, headline_offset)
|
||||
REFERENCES logbook (file_path, entry_offset)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
PRIMARY KEY (file_path ASC, entry_offset ASC));"
|
||||
"Schema to build the state_changes table in the org db.")
|
||||
|
||||
(defconst nd/org-sqlite-planning-changes-schema
|
||||
"CREATE TABLE planning_changes (
|
||||
file_path TEXT,
|
||||
entry_offset INTEGER,
|
||||
|
@ -2945,11 +3010,10 @@ time_new DATE,
|
|||
planning_type TEXT CHECK (planning_type = \\\"d\\\" or (planning_type = \\\"s\\\")),
|
||||
FOREIGN KEY (file_path, entry_offset)
|
||||
REFERENCES logbook (file_path, entry_offset)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
PRIMARY KEY (file_path ASC, entry_offset ASC));"
|
||||
"Schema to build the planning_changes table in the org db.")
|
||||
|
||||
(defconst nd/org-sqlite-links-schema
|
||||
"CREATE TABLE links (
|
||||
file_path TEXT,
|
||||
headline_offset INTEGER,
|
||||
|
@ -2959,9 +3023,10 @@ link_text TEXT,
|
|||
link_type TEXT,
|
||||
FOREIGN KEY (file_path, headline_offset)
|
||||
REFERENCES headlines (file_path, headline_offset)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
PRIMARY KEY (file_path ASC, link_offset ASC));"
|
||||
"Schema to build the links table in the org db.")
|
||||
PRIMARY KEY (file_path ASC, link_offset ASC));")
|
||||
"Table schemas for the org database.")
|
||||
#+END_SRC
|
||||
**** org element functions
|
||||
These are functions that operate on org-element objects to parse for insertion into the db.
|
||||
|
@ -3092,9 +3157,12 @@ and cdr is the match data."
|
|||
"Return `org-todo-keywords' as string list w/o selectors.
|
||||
Will likely match the value of `org-todo-keywords-1' in many cases,
|
||||
but this has the advantage of being always available and comprehensive."
|
||||
(mapcar
|
||||
(lambda (s) (replace-regexp-in-string "(.*)" "" s))
|
||||
(remove "|" (mapcan #'cdr (copy-tree org-todo-keywords)))))
|
||||
(nd/org-sql->>
|
||||
org-todo-keywords
|
||||
(copy-tree)
|
||||
(mapcan #'cdr)
|
||||
(remove "|")
|
||||
(mapcar (lambda (s) (replace-regexp-in-string "(.*)" "" s)))))
|
||||
|
||||
(defun nd/org-log-note-headings-matcher ()
|
||||
"Convert `org-log-note-headings' to a regex matcher.
|
||||
|
@ -3105,24 +3173,24 @@ of the escapes."
|
|||
(let* ((escapes '("%u" "%U" "%t" "%T" "%d" "%D" "%s" "%S"))
|
||||
(todo-list (nd/org-todo-keywords-stripped))
|
||||
(todo-regexp (mapconcat #'regexp-quote todo-list "\\|"))
|
||||
(ts-or-todo-regexp (concat "\"\\(" org-ts-regexp-inactive "\\|"
|
||||
todo-regexp "\\)\""))
|
||||
(org-ts-regexp-cap (concat "\\(" org-ts-regexp "\\)"))
|
||||
(org-ts-regexp-inactive-cap (concat "\\(" org-ts-regexp-inactive "\\)"))
|
||||
(re-matchers (list ".*"
|
||||
(ts-or-todo-regexp (format "\"\\(%s\\|%s\\)\""
|
||||
org-ts-regexp-inactive
|
||||
todo-regexp))
|
||||
(org-ts-regexp-cap (format "\\(%s\\)" org-ts-regexp))
|
||||
(org-ts-regexp-inactive-cap (format "\\(%s\\)" org-ts-regexp-inactive))
|
||||
(re-no-pad-alist (mapcar* #'cons escapes escapes))
|
||||
(re-match-alist
|
||||
(nd/org-sql->>
|
||||
(list ".*"
|
||||
".*"
|
||||
org-ts-regexp-inactive-cap
|
||||
org-ts-regexp-cap
|
||||
org-ts-regexp-inactive-cap
|
||||
org-ts-regexp-cap
|
||||
ts-or-todo-regexp
|
||||
ts-or-todo-regexp))
|
||||
(re-matchers-pad (mapcar
|
||||
(lambda (s)
|
||||
(concat "[[:space:]]*" s "[[:space:]]*"))
|
||||
re-matchers))
|
||||
(re-no-pad-alist (mapcar* #'cons escapes escapes))
|
||||
(re-match-alist (mapcar* #'cons escapes re-matchers-pad))
|
||||
ts-or-todo-regexp)
|
||||
(mapcar (lambda (s) (concat "[[:space:]]*" s "[[:space:]]*")))
|
||||
(mapcar* #'cons escapes)))
|
||||
(apply2note
|
||||
(lambda (n f)
|
||||
(let ((note-type (car n))
|
||||
|
@ -3137,17 +3205,18 @@ of the escapes."
|
|||
(lambda (n)
|
||||
(funcall apply2note
|
||||
n
|
||||
(lambda (s) (replace-regexp-in-string "\s+" " " s)))))
|
||||
(new org-log-note-headings))
|
||||
(lambda (s) (replace-regexp-in-string "\s+" " " s))))))
|
||||
(nd/org-sql->>
|
||||
org-log-note-headings
|
||||
;; remove padding information by replacing all escape sequences
|
||||
;; with their non-padded version and then removing extra spaces
|
||||
(setq new (mapcar (lambda (n) (funcall replace-esc n re-no-pad-alist)) new))
|
||||
(setq new (mapcar (lambda (n) (funcall shrink-space n)) new))
|
||||
(mapcar (lambda (n) (funcall replace-esc n re-no-pad-alist)))
|
||||
(mapcar (lambda (n) (funcall shrink-space n)))
|
||||
;; replace all escape sequences with regexps that match
|
||||
;; the data to be inserted via the escape sequences
|
||||
(setq new (mapcar (lambda (n) (funcall replace-esc n re-match-alist)) new))
|
||||
(mapcar (lambda (n) (funcall replace-esc n re-match-alist)))
|
||||
;; filter out anything that is blank (eg default clock-in)
|
||||
(seq-filter (lambda (s) (not (equal (cdr s) ""))) new)))
|
||||
(seq-filter (lambda (s) (not (equal (cdr s) "")))))))
|
||||
|
||||
(defconst nd/org-log-note-headings-regexp
|
||||
(nd/org-log-note-headings-matcher)
|
||||
|
@ -3253,21 +3322,26 @@ The header text is solely used for determining :type and :match-data."
|
|||
**** org sql db function
|
||||
These are the main functions to populate the db.
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun nd/org-init-db ()
|
||||
"Make a sqlite database for org archive files if it does not exist already."
|
||||
(unless (file-exists-p nd/org-sqlite-db-path)
|
||||
(process-file-shell-command (concat "touch " nd/org-sqlite-db-path))
|
||||
(nd/sql-cmd nd/org-sqlite-db-path nd/org-sqlite-files-schema)
|
||||
(nd/sql-cmd nd/org-sqlite-db-path nd/org-sqlite-header-schema)
|
||||
(nd/sql-cmd nd/org-sqlite-db-path nd/org-sqlite-properties-schema)
|
||||
(nd/sql-cmd nd/org-sqlite-db-path nd/org-sqlite-tags-schema)
|
||||
(nd/sql-cmd nd/org-sqlite-db-path nd/org-sqlite-clocking-schema)
|
||||
(nd/sql-cmd nd/org-sqlite-db-path nd/org-sqlite-logbook-schema)
|
||||
(nd/sql-cmd nd/org-sqlite-db-path nd/org-sqlite-state-changes-schema)
|
||||
(nd/sql-cmd nd/org-sqlite-db-path nd/org-sqlite-planning-changes-schema)
|
||||
(nd/sql-cmd nd/org-sqlite-db-path nd/org-sqlite-links-schema)))
|
||||
(defmacro nd/org-sql-> (&rest body)
|
||||
(let ((result (pop body)))
|
||||
(dolist (form body result)
|
||||
(setq result (append (list (car form) result) (cdr form))))))
|
||||
|
||||
(defun nd/org-sql-extract-lb-header (item-part acc)
|
||||
(defmacro nd/org-sql->> (&rest body)
|
||||
(let ((result (pop body)))
|
||||
(dolist (form body result)
|
||||
(setq result (append form (list result))))))
|
||||
|
||||
(defun nd/org-sql-extract (acc fun objs &rest args)
|
||||
"Iterate through OBJS and add them to accumulator ACC using FUN.
|
||||
FUN is a function that takes a single object from OBJS, the accumulator,
|
||||
and ARGS. FUN adds OBJ to ACC and returns a new ACC."
|
||||
(while objs
|
||||
(setq acc (apply fun acc (car objs) args)
|
||||
objs (cdr objs)))
|
||||
acc)
|
||||
|
||||
(defun nd/org-sql-extract-lb-header (acc item-part)
|
||||
"Add specific data from logbook entry ITEM-PART to accumulator ACC.
|
||||
ITEM-PART is a partitions logbook item as described in
|
||||
`nd/org-sql-partition-item'. Note headings are parsed according to
|
||||
|
@ -3326,7 +3400,7 @@ ITEM-PART is a partitioned logbook item as described in
|
|||
(set-match-data (alist-get :match-data item-part))
|
||||
(nd/org-ts-format-to-iso (match-string time-index header-text)))))
|
||||
|
||||
(defun nd/org-sql-extract-lb-entry (item-part acc)
|
||||
(defun nd/org-sql-extract-lb-entry (acc item-part)
|
||||
"Add data from logbook entry ITEM-PART to accumulator ACC.
|
||||
ITEM-PART is a partitioned logbook item as described in
|
||||
`nd/org-sql-partition-item'."
|
||||
|
@ -3344,9 +3418,11 @@ ITEM-PART is a partitioned logbook item as described in
|
|||
:entry_offset item-offset
|
||||
:time_logged time-logged
|
||||
:header hdr-text
|
||||
:note note-text))
|
||||
(acc* (nd/alist-put acc 'logbook logbook-data)))
|
||||
(nd/org-sql-extract-lb-header item-part acc*)))
|
||||
:note note-text)))
|
||||
(nd/org-sql->
|
||||
acc
|
||||
(nd/alist-put 'logbook logbook-data)
|
||||
(nd/org-sql-extract-lb-header item-part))))
|
||||
|
||||
(defun nd/org-logbook-parse-timestamp-range (ts)
|
||||
"Return start and end of timestamp TS depending on if it is a range.
|
||||
|
@ -3364,7 +3440,7 @@ Return value will be a list of two elements if range and one if not."
|
|||
(cons start end))
|
||||
`(,(funcall split ts))))))
|
||||
|
||||
(defun nd/org-sql-extract-lb-clock (clock acc hl-part &optional item)
|
||||
(defun nd/org-sql-extract-lb-clock (acc clock hl-part &optional item)
|
||||
"Add data from logbook CLOCK to accumulator ACC.
|
||||
HL-PART is an object as returned by `nd/org-sql-partition-headline'
|
||||
and represents the headline surrounding the clock.
|
||||
|
@ -3390,38 +3466,40 @@ added to the clock, else add it as a normal logbook entry."
|
|||
(if item-type
|
||||
;; if we know the type, add the clock and note
|
||||
;; separately
|
||||
(let ((acc* (nd/alist-put acc 'clocking clock-data)))
|
||||
(nd/org-sql-extract-lb-entry item-part acc*))
|
||||
(nd/org-sql->
|
||||
acc
|
||||
(nd/alist-put 'clocking clock-data)
|
||||
(nd/org-sql-extract-lb-entry item-part))
|
||||
;; else add it with the clocking table
|
||||
(let* ((hdr-text (alist-get :header-text item-part))
|
||||
(clock-data* `(,@clock-data :clock_note ,hdr-text)))
|
||||
(nd/alist-put acc 'clocking clock-data*)))))))
|
||||
|
||||
(defun nd/org-sql-extract-lb-items (items acc hl-part)
|
||||
(defun nd/org-sql-extract-lb-items (acc items hl-part)
|
||||
"Add data from logbook ITEMS to accumulator ACC.
|
||||
HL-PART is an object as returned by `nd/org-sql-partition-headline'
|
||||
and represents the headline surrounding the items."
|
||||
(let ((into
|
||||
(lambda (item acc hl-part)
|
||||
(let ((from
|
||||
(lambda (acc item hl-part)
|
||||
(let ((item-part (nd/org-sql-partition-item item hl-part)))
|
||||
(nd/org-sql-extract-lb-entry item-part acc)))))
|
||||
(nd/org-sql-extract items into acc hl-part)))
|
||||
(nd/org-sql-extract-lb-entry acc item-part)))))
|
||||
(nd/org-sql-extract acc from items hl-part)))
|
||||
|
||||
(defun nd/org-sql-extract-lb-one (entry acc hl-part)
|
||||
(defun nd/org-sql-extract-lb-one (acc entry hl-part)
|
||||
"Add data from logbook ENTRY to accumulator ACC.
|
||||
HL-PART is an object as returned by `nd/org-sql-partition-headline'
|
||||
and represents the headline surrounding the entry."
|
||||
(let ((type (org-element-type entry)))
|
||||
(cond
|
||||
((eq type 'clock)
|
||||
(nd/org-sql-extract-lb-clock entry acc hl-part))
|
||||
(nd/org-sql-extract-lb-clock acc entry hl-part))
|
||||
((eq type 'plain-list)
|
||||
(let ((items (org-element-contents entry)))
|
||||
(nd/org-sql-extract-lb-items items acc hl-part)))
|
||||
(nd/org-sql-extract-lb-items acc items hl-part)))
|
||||
;; TODO add an "UNKNOWN" logbook parser
|
||||
(t acc))))
|
||||
|
||||
(defun nd/org-sql-extract-lb-two (entry1 entry2 acc hl-part)
|
||||
(defun nd/org-sql-extract-lb-two (acc entry1 entry2 hl-part)
|
||||
"Add data from logbook ENTRY1 and ENTRY2 to accumulator ACC.
|
||||
HL-PART is an object as returned by `nd/org-sql-partition-headline'
|
||||
and represents the headline surrounding the entries. This assumes the
|
||||
|
@ -3429,9 +3507,11 @@ entries are org-element types clock and plain-list respectively, and
|
|||
will check if the first item in ENTRY2 is part of the clock."
|
||||
(let* ((items (org-element-contents entry2))
|
||||
(first-item (car items))
|
||||
(rem-items (cdr items))
|
||||
(acc* (nd/org-sql-extract-lb-clock entry1 acc hl-part first-item)))
|
||||
(nd/org-sql-extract-lb-items rem-items acc* hl-part)))
|
||||
(rem-items (cdr items)))
|
||||
(nd/org-sql->
|
||||
acc
|
||||
(nd/org-sql-extract-lb-clock entry1 hl-part first-item)
|
||||
(nd/org-sql-extract-lb-items rem-items hl-part))))
|
||||
|
||||
(defun nd/org-sql-find-logbook (contents)
|
||||
"Find the logbook drawer given CONTENTS from section of org headline.
|
||||
|
@ -3445,50 +3525,45 @@ ignored."
|
|||
(equal org-log-into-drawer (plist-get (car e) :drawer-name)))
|
||||
contents)))
|
||||
|
||||
(defun nd/org-sql-extract-lb (hl-part acc)
|
||||
(defun nd/org-sql-extract-lb (acc hl-part)
|
||||
"Add logbook data from HL-PART and add to accumulator ACC.
|
||||
HL-PART is an object as returned by `nd/org-sql-partition-headline'."
|
||||
(let* ((sec (alist-get :section hl-part))
|
||||
(lb-contents (nd/org-sql-find-logbook sec))
|
||||
(scan
|
||||
(lambda (contents &optional acc)
|
||||
(if (not contents)
|
||||
acc
|
||||
(lb-contents (nd/org-sql-find-logbook sec)))
|
||||
(while lb-contents
|
||||
;; Need two of the next entries here because clocks may
|
||||
;; have notes associated with them, but the only
|
||||
;; distinguishing characteristic they have is that they
|
||||
;; don't match anything in org-log-note-headings. If we
|
||||
;; end up processing two entries at once, skip over two
|
||||
;; instead of one on the next iteration.
|
||||
(let* ((cur1 (car contents))
|
||||
(cur2 (cadr contents))
|
||||
(let* ((cur1 (car lb-contents))
|
||||
(cur2 (cadr lb-contents))
|
||||
(type1 (org-element-type cur1))
|
||||
(type2 (org-element-type cur2))
|
||||
(try-clock-note (and (eq 'clock type1)
|
||||
(eq type2 'plain-list)))
|
||||
(acc*
|
||||
(eq type2 'plain-list))))
|
||||
(if try-clock-note
|
||||
(nd/org-sql-extract-lb-two cur1 cur2 acc hl-part)
|
||||
(nd/org-sql-extract-lb-one cur1 acc hl-part)))
|
||||
(rem (if try-clock-note (cddr contents) (cdr contents))))
|
||||
(funcall scan rem acc*))))))
|
||||
(funcall scan lb-contents acc)))
|
||||
(setq acc (nd/org-sql-extract-lb-two acc cur1 cur2 hl-part)
|
||||
lb-contents (cddr lb-contents))
|
||||
(setq acc (nd/org-sql-extract-lb-one acc cur1 hl-part)
|
||||
lb-contents (cdr lb-contents)))))
|
||||
acc))
|
||||
|
||||
(defun nd/org-sql-parse-ts-maybe (txt)
|
||||
"If TXT is a timestamp, return it in ISO 8601 format.
|
||||
Otherwise return it unchanged."
|
||||
;; assume the iso parser to return nil on failure
|
||||
(let ((txt* (nd/org-ts-format-to-iso txt)))
|
||||
(if txt* txt* txt)))
|
||||
(nd/org-sql-> txt (nd/org-ts-format-to-iso) (or txt)))
|
||||
|
||||
(defun nd/org-sql-extract-properties (hl-part acc)
|
||||
(defun nd/org-sql-extract-properties (acc hl-part)
|
||||
"Add properties data from HL-PART and add to accumulator ACC.
|
||||
HL-PART is an object as returned by `nd/org-sql-partition-headline'."
|
||||
(let* ((sec (alist-get :section hl-part))
|
||||
(prop-drawer (assoc 'property-drawer sec))
|
||||
(node-props (org-element-contents prop-drawer))
|
||||
(into
|
||||
(lambda (np acc hl-part)
|
||||
(from
|
||||
(lambda (acc np hl-part)
|
||||
(let ((key (org-element-property :key np)))
|
||||
(if (member key nd/org-sql-ignored-properties)
|
||||
acc
|
||||
|
@ -3506,23 +3581,24 @@ HL-PART is an object as returned by `nd/org-sql-partition-headline'."
|
|||
;; TODO add inherited flag
|
||||
:inherited nil)))
|
||||
(nd/alist-put acc 'properties prop-data)))))))
|
||||
(nd/org-sql-extract node-props into acc hl-part)))
|
||||
(nd/org-sql-extract acc from node-props hl-part)))
|
||||
|
||||
(defun nd/org-sql-extract-tags (hl-part acc)
|
||||
(defun nd/org-sql-extract-tags (acc hl-part)
|
||||
"Extract tags data from HL-PART and add to accumulator ACC.
|
||||
HL-PART is an object as returned by `nd/org-sql-partition-headline'."
|
||||
(let* ((hl (alist-get :headline hl-part))
|
||||
;; first retrieve tags and strip text props and whitespace
|
||||
(tags (org-element-property :tags hl))
|
||||
(tags (mapcar #'nd/strip-string tags))
|
||||
(tags (nd/org-sql->> hl
|
||||
(org-element-property :tags)
|
||||
(mapcar #'nd/strip-string)))
|
||||
;; then retrieve i-tags, optionally going up to parents
|
||||
(i-tags (org-element-property :ARCHIVE_ITAGS hl))
|
||||
(i-tags (when i-tags (split-string i-tags)))
|
||||
(i-tags (if nd/org-sql-use-tag-inheritance
|
||||
(nd/org-element-get-parent-tags hl i-tags)
|
||||
i-tags))
|
||||
(into
|
||||
(lambda (tag acc hl-part &optional inherited)
|
||||
(from
|
||||
(lambda (acc tag hl-part &optional inherited)
|
||||
(let* ((hl (alist-get :headline hl-part))
|
||||
(fp (alist-get :filepath hl-part))
|
||||
(offset (org-element-property :begin hl))
|
||||
|
@ -3531,28 +3607,20 @@ HL-PART is an object as returned by `nd/org-sql-partition-headline'."
|
|||
:headline_offset offset
|
||||
:tag tag
|
||||
:inherited i)))
|
||||
(nd/alist-put acc 'tags tags-data))))
|
||||
(acc* (nd/org-sql-extract tags into acc hl-part)))
|
||||
(nd/org-sql-extract i-tags into acc* hl-part t)))
|
||||
|
||||
(defun nd/org-sql-extract (objs fun acc &rest args)
|
||||
"Iterate through OBJS and add them to accumulator ACC using FUN.
|
||||
FUN is a function that takes a single object from OBJS, the accumulator,
|
||||
and ARGS. FUN adds OBJ to ACC and returns a new ACC."
|
||||
(if (not objs)
|
||||
(nd/alist-put acc 'tags tags-data)))))
|
||||
(nd/org-sql->
|
||||
acc
|
||||
(let* ((cur (car objs))
|
||||
(rem (cdr objs))
|
||||
(acc* (apply fun cur acc args)))
|
||||
(apply #'nd/org-sql-extract rem fun acc* args))))
|
||||
(nd/org-sql-extract from tags hl-part)
|
||||
(nd/org-sql-extract from i-tags hl-part t))))
|
||||
|
||||
(defun nd/org-sql-extract-links (hl-part &optional acc)
|
||||
|
||||
(defun nd/org-sql-extract-links (acc hl-part)
|
||||
"Add link data from headline HL-PART to accumulator ACC.
|
||||
HL-PART is an object as returned by `nd/org-sql-partition-headline'."
|
||||
(let* ((sec (alist-get :section hl-part))
|
||||
(links (org-element-map sec 'link #'identity))
|
||||
(into
|
||||
(lambda (ln acc hl-part)
|
||||
(from
|
||||
(lambda (acc ln hl-part)
|
||||
(let* ((fp (alist-get :filepath hl-part))
|
||||
(hl (alist-get :headline hl-part))
|
||||
(hl-offset (org-element-property :begin hl))
|
||||
|
@ -3569,9 +3637,9 @@ HL-PART is an object as returned by `nd/org-sql-partition-headline'."
|
|||
:link_text ln-text
|
||||
:link_type ln-type)))
|
||||
(nd/alist-put acc 'links ln-data)))))
|
||||
(nd/org-sql-extract links into acc hl-part)))
|
||||
(nd/org-sql-extract acc from links hl-part)))
|
||||
|
||||
(defun nd/org-sql-extract-headline (hl-part &optional acc)
|
||||
(defun nd/org-sql-extract-hl-meta (acc hl-part)
|
||||
"Add general data from headline HL-PART to accumulator ACC.
|
||||
HL-PART is an object as returned by `nd/org-sql-partition-headline'."
|
||||
(let* ((fp (alist-get :filepath hl-part))
|
||||
|
@ -3608,33 +3676,30 @@ HL-PART is an object as returned by `nd/org-sql-partition-headline'."
|
|||
:content nil)))
|
||||
(nd/alist-put acc 'headlines hl-data)))
|
||||
|
||||
(defun nd/org-sql-extract-headlines (headlines acc fp)
|
||||
(defun nd/org-sql-extract-hl (acc headlines fp)
|
||||
"Extract data from HEADLINES and add to accumulator ACC.
|
||||
FP is the path to the file containing the headlines."
|
||||
(let ((into
|
||||
(lambda (hl acc fp)
|
||||
(let ((from
|
||||
(lambda (acc hl fp)
|
||||
(let* ((hl-part (nd/org-sql-partition-headline hl fp))
|
||||
(hl-sub (alist-get :subheadlines hl-part))
|
||||
(acc* (nd/org-sql-extract-headline hl-part acc))
|
||||
(acc* (nd/org-sql-extract-links hl-part acc*))
|
||||
(acc* (nd/org-sql-extract-tags hl-part acc*))
|
||||
(acc* (nd/org-sql-extract-properties hl-part acc*))
|
||||
(acc* (nd/org-sql-extract-lb hl-part acc*)))
|
||||
(nd/org-sql-extract-headlines hl-sub acc* fp)))))
|
||||
(nd/org-sql-extract headlines into acc fp)))
|
||||
(hl-sub (alist-get :subheadlines hl-part)))
|
||||
(nd/org-sql-> acc
|
||||
(nd/org-sql-extract-hl-meta hl-part)
|
||||
(nd/org-sql-extract-links hl-part)
|
||||
(nd/org-sql-extract-tags hl-part)
|
||||
(nd/org-sql-extract-properties hl-part)
|
||||
(nd/org-sql-extract-lb hl-part)
|
||||
(nd/org-sql-extract-hl hl-sub fp))))))
|
||||
(nd/org-sql-extract acc from headlines fp)))
|
||||
|
||||
(defvar nd/org-sql-files '("test1.org_archive" "test2.org_archive")
|
||||
"A list of org files to put into sql database.")
|
||||
|
||||
(defun nd/org-sql-extract-files ()
|
||||
"Return a plist of data to be inserted into sql database."
|
||||
(let ((paths (mapcar (lambda (p) (expand-file-name p org-directory)) nd/org-sql-files))
|
||||
(into
|
||||
(lambda (fp acc)
|
||||
(let* ((buf (find-file-noselect fp t))
|
||||
(defun nd/org-sql-extract-file (cell acc)
|
||||
"Extract the file in the car of CELL for a sql insertion.
|
||||
The results are accumulated in ACC which is returned on exit."
|
||||
(let* ((fp (car cell))
|
||||
(md5sum (cdr cell))
|
||||
(buf (find-file-noselect fp t))
|
||||
(tree (with-current-buffer buf
|
||||
(org-element-parse-buffer)))
|
||||
(md5sum (md5 buf))
|
||||
(attr (file-attributes fp))
|
||||
(fsize (file-attribute-size attr))
|
||||
(contents (org-element-contents tree))
|
||||
|
@ -3643,15 +3708,136 @@ FP is the path to the file containing the headlines."
|
|||
contents))
|
||||
(file-data (list :file_path fp
|
||||
:md5 md5sum
|
||||
:size fsize))
|
||||
(acc* (nd/alist-put acc 'files file-data)))
|
||||
(nd/org-sql-extract-headlines headlines acc* fp)))))
|
||||
(nd/org-sql-extract paths into nil)))
|
||||
:size fsize)))
|
||||
(nd/org-sql->
|
||||
acc
|
||||
(nd/alist-put 'files file-data)
|
||||
(nd/org-sql-extract-hl headlines fp))))
|
||||
|
||||
(defun nd/org-archive-to-db ()
|
||||
(defun nd/org-sql-mk-insert (cell acc)
|
||||
(nd/org-sql->> (plist-get acc 'insert)
|
||||
(nd/org-sql-extract-file cell)
|
||||
(plist-put acc 'insert)))
|
||||
|
||||
(defun nd/org-sql-mk-update (cell acc)
|
||||
(let ((updt-acc (plist-get acc 'update)))
|
||||
(nd/org-sql->> `((:file_path ,(car cell)) . (:md5 ,(cdr cell)))
|
||||
(nd/alist-put updt-acc 'files)
|
||||
(plist-put acc 'update))))
|
||||
|
||||
(defun nd/org-sql-mk-delete (cell acc)
|
||||
(let ((dlt-acc (plist-get acc 'delete)))
|
||||
(nd/org-sql->> `(:file_path ,(car cell))
|
||||
(nd/alist-put dlt-acc 'files)
|
||||
(plist-put acc 'delete))))
|
||||
|
||||
(defun nd/org-sql-get-updates (cell fp-qry acc)
|
||||
"Returns cell where the car is accumulator ACC and cdr is current fp-qry."
|
||||
;; if perfect match, do nothing
|
||||
(if (find cell fp-qry :test #'equal)
|
||||
(cons acc (remove cell fp-qry))
|
||||
(let* ((match-cells
|
||||
(lambda (a b fun)
|
||||
(let ((car-a (car a))
|
||||
(cdr-a (cdr a))
|
||||
(car-b (car b))
|
||||
(cdr-b (cdr b)))
|
||||
(funcall fun car-a car-b cdr-a cdr-b))))
|
||||
(match-fp
|
||||
(lambda (fp-a fp-b md5-a md5-b)
|
||||
(and (equal fp-a fp-b) (not (equal md5-a md5-b)))))
|
||||
(match-md5
|
||||
(lambda (fp-a fp-b md5-a md5-b)
|
||||
(and (not (equal fp-a fp-b)) (equal md5-a md5-b))))
|
||||
(match-fp*
|
||||
(lambda (b)
|
||||
(funcall match-cells cell b match-fp)))
|
||||
(match-md5*
|
||||
(lambda (b)
|
||||
(funcall match-cells cell b match-md5)))
|
||||
(found-fp (find-if (lambda (q) (funcall match-fp* q)) fp-qry)))
|
||||
(cond
|
||||
;; delete qry in db and insert cell
|
||||
(found-fp
|
||||
(cons (nd/org-sql-mk-insert cell (nd/org-sql-mk-delete found-fp acc))
|
||||
(remove found-fp fp-qry)))
|
||||
;; update fp in db
|
||||
((find-if (lambda (q) (funcall match-md5* q)) fp-qry)
|
||||
(cons (nd/org-sql-mk-update cell acc)
|
||||
(remove-if (lambda (q) (funcall match-md5* q)) fp-qry)))
|
||||
;; insert cell
|
||||
(t
|
||||
(cons (nd/org-sql-mk-insert cell acc) fp-qry))))))
|
||||
|
||||
(defun nd/org-sql-compare-files (fp-dsk fp-qry)
|
||||
(let (acc)
|
||||
(while fp-dsk
|
||||
(let* ((cur (car fp-dsk))
|
||||
(rem (cdr fp-dsk))
|
||||
(found (nd/org-sql-get-updates cur fp-qry acc)))
|
||||
(setq fp-dsk rem
|
||||
acc (car found)
|
||||
fp-qry (cdr found))))
|
||||
(if fp-qry (nd/org-sql-mk-delete fp-qry acc)) acc))
|
||||
|
||||
(defun nd/org-sql-files-from-disk ()
|
||||
"Return alist of metadata for filepaths PATHS."
|
||||
(let ((paths (mapcar (lambda (p) (expand-file-name p org-directory)) nd/org-sql-files))
|
||||
(cons-md5
|
||||
(lambda (fp)
|
||||
(let* ((fp-buf (find-file-noselect fp t)))
|
||||
(cons fp (md5 fp-buf))))))
|
||||
(mapcar (lambda (p) (funcall cons-md5 p)) paths)))
|
||||
|
||||
(defun nd/org-sql-files-from-db ()
|
||||
"Get all files and their metadata from the database.
|
||||
Returns an alist where the each car is file_path and each cdr is
|
||||
the plist of metadata."
|
||||
;; TODO should probably make the table recreate itself if it is
|
||||
;; corrupted or missing
|
||||
(when (file-exists-p nd/org-sqlite-db-path)
|
||||
(nd/org-sql->> '(:file_path :md5)
|
||||
(nd/sql-select nd/org-sqlite-db-path 'files)
|
||||
(mapcar #'nd/sql-plist-get-vals)
|
||||
(mapcar (lambda (q) (cons (car q) (car (cdr q))))))))
|
||||
|
||||
(defun nd/org-sql-get-transactions ()
|
||||
(let ((fp-dsk (nd/org-sql-files-from-disk))
|
||||
(map-trns
|
||||
(lambda (op fun trans)
|
||||
(nd/org-sql->>
|
||||
(plist-get trans op)
|
||||
(mapcar (lambda (s) (funcall fun s)))
|
||||
(nd/org-sql-fmt-trans)
|
||||
(plist-put trans op)))))
|
||||
(nd/org-sql->>
|
||||
(nd/org-sql-files-from-db)
|
||||
(nd/org-sql-compare-files fp-dsk)
|
||||
(funcall map-trns 'insert #'nd/org-sql-fmt-inserts)
|
||||
(funcall map-trns 'update #'nd/org-sql-fmt-updates)
|
||||
(funcall map-trns 'delete #'nd/org-sql-fmt-deletes))))
|
||||
|
||||
(defvar nd/org-sql-files '("test1.org_archive" "test2.org_archive")
|
||||
"A list of org files to put into sql database.")
|
||||
|
||||
(defun nd/org-init-db ()
|
||||
"Make a sqlite database for org files if it does not exist already."
|
||||
(unless (file-exists-p nd/org-sqlite-db-path)
|
||||
(process-file-shell-command (concat "touch " nd/org-sqlite-db-path))
|
||||
(mapcar (lambda (s) (nd/sql-cmd nd/org-sqlite-db-path s)) nd/org-sql-schemas)))
|
||||
|
||||
(defun nd/org-archive-to-db (&optional show-err)
|
||||
"Transfer archive files to sqlite database."
|
||||
(let ((sql-data (nd/org-sql-extract-files)))
|
||||
(nd/sql-insert-multi nd/org-sqlite-db-path sql-data)))
|
||||
(let* ((trans (nd/org-sql-get-transactions))
|
||||
(trans-dlt (plist-get trans 'delete))
|
||||
(trans-upd (plist-get trans 'update))
|
||||
(trans-ins (plist-get trans 'insert)))
|
||||
;; note, the order of sql commands matters in transactions,
|
||||
;; so, we need to do deletes, update, then inserts in that order
|
||||
;; `(,(nd/sql-cmd nd/org-sqlite-db-path trans-dlt show-err))))
|
||||
`(,(nd/sql-cmd nd/org-sqlite-db-path trans-dlt show-err t)
|
||||
,(nd/sql-cmd nd/org-sqlite-db-path trans-upd show-err t)
|
||||
,(nd/sql-cmd nd/org-sqlite-db-path trans-ins show-err nil))))
|
||||
#+END_SRC
|
||||
* tools
|
||||
** printing
|
||||
|
|
Loading…
Reference in New Issue