drawer.cljs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. (ns frontend.util.drawer
  2. (:require [clojure.string :as string]
  3. [frontend.util :as util]
  4. [frontend.util.property :as property]
  5. [frontend.format.mldoc :as mldoc]))
  6. (defn drawer-start
  7. [typ]
  8. (util/format ":%s:" (string/upper-case typ)))
  9. (defonce drawer-end ":END:")
  10. (defonce logbook-start ":LOGBOOK:")
  11. (defn build-drawer-str
  12. ([typ]
  13. (build-drawer-str typ nil))
  14. ([typ value]
  15. (if value
  16. (string/join "\n" [(drawer-start typ) value drawer-end])
  17. (string/join "\n" [(drawer-start typ) drawer-end]))))
  18. (defn get-drawer-ast
  19. [format content typ]
  20. (let [ast (mldoc/->edn content (mldoc/default-config format))
  21. typ-drawer (ffirst (filter (fn [x]
  22. (mldoc/typ-drawer? x typ)) ast))]
  23. typ-drawer))
  24. (defn insert-drawer
  25. [format content typ value]
  26. (when (string? content)
  27. (try
  28. (let [ast (mldoc/->edn content (mldoc/default-config format))
  29. has-properties? (some (fn [x] (mldoc/properties? x)) ast)
  30. has-typ-drawer? (some (fn [x] (mldoc/typ-drawer? x typ)) ast)
  31. lines (string/split-lines content)
  32. title (first lines)
  33. body (rest lines)
  34. scheduled (filter #(string/starts-with? % "SCHEDULED") lines)
  35. deadline (filter #(string/starts-with? % "DEADLINE") lines)
  36. body-without-timestamps (vec
  37. (filter
  38. #(not (or (string/starts-with? % "SCHEDULED")
  39. (string/starts-with? % "DEADLINE")))
  40. body))
  41. start-idx (.indexOf body-without-timestamps (drawer-start typ))
  42. end-idx (let [[before after] (split-at start-idx body-without-timestamps)]
  43. (+ (count before) (.indexOf after drawer-end)))
  44. result (cond
  45. (not has-typ-drawer?)
  46. (let [drawer (build-drawer-str typ value)]
  47. (if has-properties?
  48. (cond
  49. (= :org format)
  50. (let [prop-start-idx (.indexOf body-without-timestamps property/properties-start)
  51. prop-end-idx (.indexOf body-without-timestamps property/properties-end)
  52. properties (subvec body-without-timestamps prop-start-idx (inc prop-end-idx))
  53. after (subvec body-without-timestamps (inc prop-end-idx))]
  54. (string/join "\n" (concat [title] scheduled deadline properties [drawer] after)))
  55. :else
  56. (let [properties-count (count (second (first (second ast))))
  57. properties (subvec body-without-timestamps 0 properties-count)
  58. after (subvec body-without-timestamps properties-count)]
  59. (string/join "\n" (concat [title] scheduled deadline properties [drawer] after))))
  60. (string/join "\n" (concat [title] scheduled deadline [drawer] body-without-timestamps))))
  61. (and has-typ-drawer?
  62. (>= start-idx 0) (> end-idx 0) (> end-idx start-idx))
  63. (let [before (subvec body-without-timestamps 0 start-idx)
  64. middle (conj
  65. (subvec body-without-timestamps (inc start-idx) end-idx)
  66. value)
  67. after (subvec body-without-timestamps (inc end-idx))
  68. lines (concat [title] scheduled deadline before
  69. [(drawer-start typ)] middle [drawer-end] after)]
  70. (string/join "\n" lines))
  71. :else
  72. content)]
  73. (string/trimr result))
  74. (catch js/Error e
  75. (js/console.error e)
  76. content))))
  77. (defn contains-logbook?
  78. [content]
  79. (and (util/safe-re-find (re-pattern (str "(?i)" logbook-start)) content)
  80. (util/safe-re-find (re-pattern (str "(?i)" drawer-end)) content)))
  81. ;; TODO: DRY
  82. (defn remove-logbook
  83. [content]
  84. (if (contains-logbook? content)
  85. (let [lines (string/split-lines content)
  86. [title-lines body] (split-with (fn [l]
  87. (not (string/starts-with? (string/upper-case (string/triml l)) ":LOGBOOK:")))
  88. lines)
  89. body (drop-while (fn [l]
  90. (let [l' (string/lower-case (string/trim l))]
  91. (or
  92. (not (string/starts-with? l' ":end:"))
  93. (string/blank? l))))
  94. body)
  95. body (if (and (seq body)
  96. (string/starts-with? (string/lower-case (string/triml (first body))) ":end:"))
  97. (let [line (string/replace (first body) #"(?i):end:\s?" "")]
  98. (if (string/blank? line)
  99. (rest body)
  100. (cons line (rest body))))
  101. body)]
  102. (->> (concat title-lines body)
  103. (string/join "\n")))
  104. content))
  105. (defn get-logbook
  106. [body]
  107. (-> (filter (fn [v] (and (vector? v)
  108. (= (first v) "Drawer")
  109. (= (second v) "logbook"))) body)
  110. first))
  111. (defn with-logbook
  112. [block content]
  113. (let [new-clocks (last (get-drawer-ast (:block/format block) content "logbook"))
  114. logbook (get-logbook (:block/body block))]
  115. (if logbook
  116. (let [content (remove-logbook content)
  117. clocks (->> (concat new-clocks (when-not new-clocks (last logbook)))
  118. (distinct))
  119. clocks (->> (map string/trim clocks)
  120. (remove string/blank?)
  121. (string/join "\n"))]
  122. (if (:block/title block)
  123. (insert-drawer (:block/format block) content "LOGBOOK" clocks)
  124. content))
  125. content)))