Skip to content

Commit

Permalink
feat(Jam): twerkin
Browse files Browse the repository at this point in the history
  • Loading branch information
brekk committed Jul 28, 2024
1 parent c98ad8c commit 5ca314f
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 62 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ node_modules
.tests
.vscode

.repl
1 change: 1 addition & 0 deletions fixture-file
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
this is a fixture!
21 changes: 21 additions & 0 deletions src/FS.mad
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Dict from "Dictionary"
import { fromMaybe } from "Maybe"
import Process from "Process"
import String from "String"



normalize :: String -> String
export normalize = (input) => pipe(
Dict.get("HOME"),
map(String.replace("~", $, input)),
fromMaybe(input),
)(Process.Env)

// TODO: check behavior for non-OSX files
tempdir :: String -> String
export tempdir = (file) => pipe(
Dict.get("TMPDIR"),
fromMaybe("/private/var/tmp/"),
mappend(file),
)(Process.Env)
206 changes: 152 additions & 54 deletions src/Jam.mad
Original file line number Diff line number Diff line change
@@ -1,87 +1,185 @@
import type { Error } from "IO"

import File from "File"
import { always } from "Function"
import { always, complement, ifElse, when } from "Function"
import {} from "Monad"
import String from "String"
import Wish from "Wish"

import FS from "@/FS"


// Preserve(dirty, file)
export type Preserve = Preserve(Boolean, String)
export alias Memory a = { get :: {} -> a, isDirty :: {} -> Boolean, set :: a -> a }

// accessors
// Preserve
// - clean - has the value been written to source?
// - sourceFile - where does the value get saved?
// - value - the preserved value
export type Preserve = Preserve(Boolean, String, String)
export alias Memory = Wish Error Preserve

isDirty :: Preserve -> Boolean
export isDirty = where {
Preserve(d, _) =>

//## ACCESSORS

/**
* Has the value been saved?
* @since 0.0.1
*/
isClean :: Preserve -> Boolean
export isClean = where {
Preserve(d, _, _) =>
d
}

isDirty :: Preserve -> Boolean
export isDirty = complement(isClean)

/**
* Where is the value saved?
* @since 0.0.1
*/
source :: Preserve -> String
export source = where {
Preserve(_, f) =>
Preserve(_, f, _) =>
f
}

// constructors
/**
* What is the saved value?
* @since 0.0.1
*/
value :: Preserve -> String
export value = where {
Preserve(_, _, v) =>
v
}

//## CONSTRUCTORS

export create = Preserve(false)
/**
* Create a Preserved value in memory
* @since 0.0.1
*/
export create = (file, v) => pipe(
FS.normalize,
Preserve(true, $, v),
)(file)

// methods
/**
* Load a Preserved value from source
* @since 0.0.1
*/
load :: String -> Memory
export load = (file) => pipe(
FS.normalize,
File.read,
map(Preserve(true, file)),
)(file)

save :: Preserve -> String -> Wish Error {}
export save = (src, data) => where(src) {
Preserve(dirty, file) =>
dirty ? File.write(file, data) : Wish.good({})
//## METHODS

/**
* Set the preserved value and set clean to false
*/
set :: String -> Preserve -> Preserve
export set = (x, p) => where(p) {
Preserve(d, s, _) =>
Preserve(false, s, x)
}

forceSave :: Preserve -> String -> Wish Error {}
export forceSave = (src, data) => where(src) {
Preserve(dirty, file) =>
pipe(
File.write(file),
map(always({})),
)(data)
/**
* Transform the preserved value and set clean to false
* @since 0.0.1
*/
update :: (String -> String) -> Preserve -> Preserve
export update = (fn, p) => where(p) {
Preserve(d, s, v) =>
Preserve(false, s, fn(v))
}

load :: Preserve -> Wish Error String
export load = where {
Preserve(_, file) =>
File.read(file)
/**
* Set dirtiness
* @since 0.0.1
*/
setClean :: Preserve -> Preserve
export setClean = where {
Preserve(_, s, v) =>
Preserve(true, s, v)
}

flush :: Preserve -> String -> Wish Error {}
export flush = (src, data) => where(src) {
Preserve(dirty, file) =>
if (dirty) {
/**
* Set cleanliness
* @since 0.0.1
*/
setDirty :: Preserve -> Preserve
export setDirty = where {
Preserve(_, s, v) =>
Preserve(false, s, v)
}

isEmpty :: Preserve -> Boolean
export isEmpty = where {
Preserve(_, _, v) =>
v == ""
}

/**
* Save the file to source only if it is dirty
* @since 0.0.1
*/
save :: Preserve -> Memory
export save = (pres) => ifElse(
isDirty,
pipe(
setClean,
flush,
),
Wish.good,
)(pres)

/**
* Mount the preserved value from source (or write a default value if no value is present)
* @since 0.0.1
*/
ensure :: String -> String -> Memory
export ensure = (src, defaultValue) => pipe(
load,
chain(
(loaded) => if (isEmpty(loaded)) {
pipe(
File.write(file),
map(always({})),
)(data)
set(defaultValue),
flush,
)(loaded)
} else {
Wish.good({})
}
Wish.good(loaded)
},
),
)(src)

/**
* Write the value to source and set clean to true
* @since 0.0.1
*/
flush :: Preserve -> Memory
export flush = (pres) => {
v = value(pres)
f = source(pres)
return pipe(
File.write(f),
map(always(Preserve(true, f, v))),
)(v)
}

remember :: Preserve -> String -> Wish Error (Memory String)
export remember = (src, initialValue) => {
x = initialValue
changed = false
controller = {
get: () => x,
set: (y) => if (!changed) {
y
} else do {
x := y
changed := true
return y
},
isDirty: () => changed,
}
/**
* Write the value to a temp file and set clean to true
* @since 0.0.1
*/
backup :: Preserve -> Memory
export backup = (pres) => {
v = value(pres)
raw = source(pres)
tmp = FS.tempdir(raw)
return pipe(
forceSave(src),
map(always(controller)),
)(x)
File.write(tmp),
map(always(Preserve(true, raw, v))),
)(v)
}
92 changes: 92 additions & 0 deletions src/Jam.spec.mad
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Error, assertEquals, test } from "Test"
import Wish from "Wish"

import FS from "@/FS"
import {
Preserve,
create,
ensure,
isClean,
isDirty,
isEmpty,
load,
set,
setClean,
setDirty,
source,
update,
value,
} from "@/Jam"
import { asyncReport, report } from "@/test"



CLEAN = Preserve(true, "src", "raw")
DIRTY = Preserve(false, "src", "raw")

report(isClean, "isClean", [#[CLEAN, true], #[DIRTY, false]])
report(isDirty, "isDirty", [#[CLEAN, false], #[DIRTY, true]])
report(source, "source", [#[DIRTY, "src"]])
report(value, "value", [#[DIRTY, "raw"]])

report(
create("~/whatever"),
"create",
[#["blah", Preserve(true, FS.normalize("~/whatever"), "blah")]],
)

report(
set("test value!"),
"set",
[#[Preserve(true, "./test-location", "yyy"), Preserve(false, "./test-location", "test value!")]],
)

report(
update((raw) => "hello " ++ raw),
"update",
[
#[
Preserve(true, "./test-location", "there"),
Preserve(false, "./test-location", "hello there"),
],
],
)

report(
setClean,
"setClean",
[#[Preserve(false, "./clean-me", "value"), Preserve(true, "./clean-me", "value")]],
)

report(
setDirty,
"setDirty",
[#[Preserve(true, "./me-dirty", "value"), Preserve(false, "./me-dirty", "value")]],
)

report(
isEmpty,
"isEmpty",
[
#[Preserve(true, "./some-place", ""), true],
#[Preserve(true, "./some-place", "not empty"), false],
],
)

asyncReport(
pipe(
load,
Wish.mapRej((_) => Error("Broken!")),
),
"load",
[#["./fixture-file", Preserve(true, "./fixture-file", "this is a fixture!\n")]],
)

asyncReport(
pipe(
ensure("./fixture-file"),
Wish.mapRej((_) => Error("Broken!")),
),
"ensure",
[#["default value", Preserve(true, "./fixture-file", "this is a fixture!\n")]],
)
Loading

0 comments on commit 5ca314f

Please sign in to comment.