(.using [library [lux (.except) ["_" test (.only Test)] [abstract ["[0]" monad (.only do)]] [control ["[0]" io (.only IO)] ["[0]" try (.only Try)] ["[0]" exception] [concurrency ["[0]" async (.only Async)] ["[0]" atom (.only Atom)]]] [data ["[0]" binary (.only Binary) ("[1]#[0]" monoid)] ["[0]" text ("[1]#[0]" equivalence)] [collection ["[0]" dictionary (.only Dictionary)] ["[0]" list]]] [macro ["^" pattern]] [math ["[0]" random]] [time ["[0]" instant (.only Instant)]]]] ["[0]" / ["[1][0]" watch]] [\\library ["[0]" /]] [\\specification ["$[0]" /]]) (type: Disk (Dictionary /.Path (Either [Instant Binary] (List Text)))) (def: (file? disk @) (-> (Atom Disk) (-> /.Path (IO Bit))) (do io.monad [disk (atom.read! disk)] (in (case (dictionary.value @ disk) {.#None} false {.#Some {.#Left _}} true {.#Some {.#Right _}} false)))) (def: (directory? disk @) (-> (Atom Disk) (-> /.Path (IO Bit))) (do io.monad [disk (atom.read! disk)] (in (case (dictionary.value @ disk) {.#None} false {.#Some {.#Left _}} false {.#Some {.#Right _}} true)))) (def: (alert_parent! disk alert @) (-> (Atom Disk) (-> (List /.Path) (List /.Path)) (-> /.Path (IO (Try Any)))) (do [! io.monad] [disk' (atom.read! disk)] (case (dictionary.value @ disk') {.#Some {.#Right siblings}} (do ! [_ (atom.compare_and_swap! disk' (dictionary.has @ {.#Right (alert siblings)} disk') disk)] (in {try.#Success []})) _ (in {try.#Failure ""})))) (def: (write fs disk @ it) (-> (/.System Async) (Atom Disk) (-> /.Path Binary (IO (Try Any)))) (do [! io.monad] [now instant.now disk' (atom.read! disk)] (case (dictionary.value @ disk') (^.or {.#None} {.#Some {.#Left _}}) (do ! [_ (atom.compare_and_swap! disk' (dictionary.has @ {.#Left [now it]} disk') disk)] (case (/.parent fs @) {.#Some parent} (alert_parent! disk (|>> (partial_list @)) parent) {.#None} (in {try.#Success []}))) _ (in {try.#Failure ""})))) (def: (read disk @) (-> (Atom Disk) (-> /.Path (IO (Try Binary)))) (do io.monad [disk (atom.read! disk)] (in (case (dictionary.value @ disk) {.#Some {.#Left [_ it]}} {try.#Success it} _ {try.#Failure ""})))) (def: (delete fs disk @) (-> (/.System Async) (Atom Disk) (-> /.Path (IO (Try Any)))) (do [! io.monad] [disk' (atom.read! disk)] (case (dictionary.value @ disk') {.#Some {.#Right children}} (if (list.empty? children) (do ! [_ (atom.compare_and_swap! disk' (dictionary.lacks @ disk') disk)] (in {try.#Success []})) (in {try.#Failure ""})) {.#Some {.#Left [_ data]}} (do ! [_ (atom.compare_and_swap! disk' (dictionary.lacks @ disk') disk)] (case (/.parent fs @) {.#Some parent} (alert_parent! disk (list.only (|>> (text#= @) not)) parent) {.#None} (in {try.#Success []}))) _ (in {try.#Failure ""})))) (def: (fs /) (-> Text (/.System IO)) (let [disk (is (Atom Disk) (atom.atom (dictionary.empty text.hash))) mock (/.mock /)] (implementation (def: separator /) (def: file? (..file? disk)) (def: directory? (..directory? disk)) (def: write (..write mock disk)) (def: read (..read disk)) (def: delete (..delete mock disk)) (def: (file_size @) (do [! io.monad] [disk (atom.read! disk)] (in (case (dictionary.value @ disk) {.#Some {.#Left [_ it]}} {try.#Success (binary.size it)} _ {try.#Failure ""})))) (def: (last_modified @) (do [! io.monad] [disk (atom.read! disk)] (in (case (dictionary.value @ disk) {.#Some {.#Left [it _]}} {try.#Success it} _ {try.#Failure ""})))) (def: (can_execute? @) (do [! io.monad] [disk (atom.read! disk)] (in (case (dictionary.value @ disk) {.#Some {.#Left _}} {try.#Success false} _ {try.#Failure ""})))) (def: (make_directory @) (do [! io.monad] [disk' (atom.read! disk)] (case (dictionary.value @ disk') {.#None} (do ! [_ (atom.compare_and_swap! disk' (dictionary.has @ {.#Right (list)} disk') disk)] (case (/.parent mock @) {.#Some parent} (alert_parent! disk (|>> (partial_list @)) parent) {.#None} (in {try.#Success []}))) _ (in {try.#Failure ""})))) (def: (directory_files @) (do [! io.monad] [disk' (atom.read! disk)] (case (dictionary.value @ disk') {.#Some {.#Right children}} (|> children (monad.only ! (..file? disk)) (# ! each (|>> {try.#Success}))) _ (in {try.#Failure ""})))) (def: (sub_directories @) (do [! io.monad] [disk' (atom.read! disk)] (case (dictionary.value @ disk') {.#Some {.#Right children}} (|> children (monad.only ! (..directory? disk)) (# ! each (|>> {try.#Success}))) _ (in {try.#Failure ""})))) (def: (append @ it) (do [! io.monad] [now instant.now disk' (atom.read! disk)] (case (dictionary.value @ disk') {.#None} (..write mock disk @ it) {.#Some {.#Left [_ old]}} (do ! [_ (atom.compare_and_swap! disk' (dictionary.has @ {.#Left [now (binary#composite old it)]} disk') disk)] (in {try.#Success []})) _ (in {try.#Failure ""})))) (def: (modify @ it) (do [! io.monad] [disk' (atom.read! disk)] (case (dictionary.value @ disk') {.#Some {.#Left [_ data]}} (do ! [_ (atom.compare_and_swap! disk' (dictionary.has @ {.#Left [it data]} disk') disk)] (in {try.#Success []})) _ (in {try.#Failure ""})))) (def: (move @ it) (do [! (try.with io.monad)] [data (..read disk @) write (..write mock disk it data)] (..delete mock disk @))) ))) (def: .public test Test (<| (_.covering /._) (do [! random.monad] [/ (random.upper_case 1) file (random.lower_case 1)] (all _.and (_.for [/.mock] ($/.spec (io.io (/.mock /)))) (_.for [/.async] ($/.spec (io.io (/.async (..fs /))))) (in (do async.monad [.let [fs (/.mock /)] ? (# fs delete file)] (_.coverage' [/.cannot_delete] (case ? {try.#Failure error} (exception.match? /.cannot_delete error) _ false)))) (in (do async.monad [.let [fs (/.mock /)] ? (# fs read file)] (_.coverage' [/.cannot_find_file] (case ? {try.#Failure error} (exception.match? /.cannot_find_file error) _ false)))) (in (do async.monad [.let [fs (/.mock /)] ?/0 (# fs directory_files file) ?/1 (# fs sub_directories file)] (_.coverage' [/.cannot_find_directory] (case [?/0 ?/1] [{try.#Failure error/0} {try.#Failure error/1}] (and (exception.match? /.cannot_find_directory error/0) (exception.match? /.cannot_find_directory error/1)) _ false)))) /watch.test ))))