But What If KISS? đź‘€
(ns clojure-is-awesome.kiss
(:require [clojure.spec.alpha :as s]))
(s/def ::id string?)
(s/def ::type #{"work" "break"})
(s/def ::duration (s/and int? pos?))
(s/def ::start-time #(instance? java.time.Instant %))
(s/def ::end-time #(instance? java.time.Instant %))
(s/def ::status #{:running :completed :paused :cancelled})
(s/def ::session (s/keys :req-un [::id ::type ::duration ::start-time ::end-time]))
(s/def ::sessions (s/coll-of ::session :kind vector))
(defn create-session
"Creates a Pomodoro session with computed end-time."
[id type duration]
(let [start-time (java.time.Instant/now)
end-time (.plusSeconds start-time (* duration 60))
session {:id id
:type type
:duration duration
:start-time start-time
:end-time end-time}]
(if (s/valid? ::session session)
session
(throw (ex-info "Invalid session" {:errors (s/explain-data ::session session)})))))
(defn set-session-status
"Sets a session’s status (e.g., paused, cancelled)."
[session status]
{:pre [(s/valid? ::status status)]}
(assoc session :status status))
(defn session-status
"Returns session status and remaining time as data, given current time."
[session now]
{:pre [(s/valid? ::session session)
(instance? java.time.Instant now)]}
(if (:status session)
{:status (:status session) :remaining-minutes 0}
(let [end-time (:end-time session)
remaining-secs (max 0 (- (.getEpochSecond end-time) (.getEpochSecond now)))]
{:status (if (pos? remaining-secs) :running :completed)
:remaining-minutes (int (/ remaining-secs 60))})))
(defn format-session
"Formats a session’s status for display."
[session now]
(let [{:keys [status remaining-minutes]} (session-status session now)]
(str (:id session) ": " (:type session) " "
(case status
:running (str "running, " remaining-minutes " minutes remaining")
:completed "completed"
:paused "paused"
:cancelled "cancelled"))))
(defn generate-report
"Generates a status report for all sessions."
[sessions now]
{:pre [(s/valid? ::sessions sessions)
(instance? java.time.Instant now)]}
(str "Pomodoro Status Report\n"
(apply str
(map #(str (format-session % now) "\n") sessions))))
(comment
(def sample-sessions
[(create-session "s1" "work" 25)
(create-session "s2" "break" 5)])
(session-status (first sample-sessions) (java.time.Instant/now))
;; => {:status :running, :remaining-minutes 25} (or less, depending on timing)
(generate-report sample-sessions (java.time.Instant/now))
;; => "Pomodoro Status Report\ns1: work running, 25 minutes remaining\ns2: break running, 5 minutes remaining"
;; Simulate paused and cancelled states
(def paused-session (set-session-status (first sample-sessions) :paused))
(session-status paused-session (java.time.Instant/now))
;; => {:status :paused, :remaining-minutes 0}
;; Simulate completed session (adjust start-time for testing)
(def completed-session
(assoc (second sample-sessions)
:start-time (.minusSeconds (java.time.Instant/now) (* 6 60))))
(session-status completed-session (java.time.Instant/now))
;; => {:status :completed, :remaining-minutes 0}
)
Top comments (0)