Open, pick and save files
Your app reaches files only through handles: folders and files it is granted when it starts, and files a person picks while it runs.
Files your app is given
A Dir handle is a folder your app may use, and a File handle one
file. Your app has no call that opens a path. Besides the files a
person picks (below),
handles come to it in three ways:
- When it starts. Each
--dir,--dir-rw,--fileand--file-rwon thedatumhue runcommand line grants a folder or a file under a name you choose. The app finds them indatumhue.args, indirsandfilesby that name. - From the app that starts it. An app passes handles it holds to the apps it launches; Launch other apps shows how.
- From its own package.
Package:diris a read-only folder of the files your package ships, throughdatumhue.package.
This program keeps notes in a folder it is granted as notes:
local bytes = datumhue.bytes
local notes = datumhue.args.dirs.notes
notes:write("today.txt", bytes("Buy flour")):ready()
local today = notes:open("today.txt")
print(today.name, today:stat():ready().size .. " bytes")
print(today:read():text())
for _, name in ipairs(notes:list():ready().value) do
print("in notes: " .. name)
end
In this example: datumhue.bytes datumhue.args Dir:write Op:ready Dir:open File.name File:stat Stat:ready Stat.size File:read Bytes:text Dir:list ListFetch:ready ListFetch.value
Make a folder notes next to it, and run it with that folder granted
read-write:
$ datumhue run notes.lua --dir-rw notes:notes
It prints notes/today.txt 9 bytes, then the note, then the folder's
one file.
Dir:writestarts writing at once and returns an operation;:ready()waits for it, and raises if it failed. Writing needs a read-write grant.Dir:readandFile:readreturn aByteshandle at once. The file is read when you decode it, here withBytes:text.Dir:opengives aFilehandle for a file that exists, andDir:subdiraDirhandle for a folder inside.- A handle's
nameis the grant's name and the file's name, never where the file is on disk. A path that leaves the folder, through.., from the root, or along a link, is refused.
Let people open and save files
An open button and a save button each show a file dialog when they are clicked, and only then. Your app can open no dialog by itself.
local ui = datumhue.ui
local bytes = datumhue.bytes
local page = ui.root:panel({
width = "100%",
height = "100%",
flex_direction = "column",
padding = 16,
gap = 12,
})
local bar = page:panel({
flex_direction = "row",
align_items = "center",
gap = 8,
})
local notes, status, save
bar:open_button({
text = "Open...",
mode = "file",
filters = { { "Text", { "txt", "md" } } },
on_pick = function(file)
local text = file:read():text()
notes.text = text
save:on_save_source(bytes(text))
status.text = "Opened " .. file.name
end,
})
save = bar:save_button({
text = "Save as...",
suggested_name = "notes.txt",
on_save = function(file)
status.text = "Saved " .. file.name
end,
})
status = bar:label({ text = "No file yet" })
notes = page:text_input({
multiline = true,
placeholder = "Type here, or open a text file",
flex_grow = 1,
on_change = function(text)
save:on_save_source(bytes(text))
end,
})
In this example: datumhue.ui datumhue.bytes datumhue.ui.root UiElement:panel UiElement:open_button UiElement:on_save_source UiElement:save_button UiElement:label UiElement:text_input
Type in the box, and Save as... writes what you typed to a file you choose. Open... reads a text file you pick into the box, ready to edit and save again.
UiElement:open_buttoncallson_pickwith what the person picked: aFile, a list of them, or aDir, as itsmodesays. Nothing is called if they cancel.UiElement:save_buttonwrites its bytes to the file the person chooses, whole or not at all, then callson_savewith a read-onlyFile.UiElement:on_save_sourcesets the bytes it writes next; here each edit does, and so does each file opened, since writingtextfrom code calls noon_change.filtersnarrows the files the dialog shows. Astart_diryou give is where a dialog opens natively; in a browser it is ignored.
Picked files and folders in some browsers
Some browsers do not let a web page change the person's files. In those
browsers, a folder the person picks is read-only: your app can list it
and read its files, but every write to it raises, and Dir:mode returns
"r" for it. A picked file is read-only everywhere. To let the person
keep a file your app makes, use a save button.
- An empty folder inside a picked folder does not show up, nor does a
folder that holds only empty folders. If the person picks a folder
with no files in it,
on_pickis not called, as when they cancel. - A picked folder keeps the list of files it had at the pick, so a file added to it later does not show up.
- A picked file, or a file in a picked folder, fails to read once it is deleted.
- Once such a file is changed, some of these browsers fail to read it.
Others read its new contents, but
File:statandDir:statstill report the size and modification time it had at the pick, and a query on the file can fail. To read a changed file, have the person pick it again.
When your app asks for a folder in these browsers, on_pick can get a
read-only Dir named files that holds what the person picked:
- If the browser shows a dialog of files instead of folders,
filesholds the files the person picked, side by side. - If the browser gives the files of a picked folder without the folders
they were in,
filesholds those files side by side. - If the person picks several folders of different names, or a folder
and some files,
filesholds each of them under its own name.
Several folders of the same name come as one folder of that name,
holding the files of each. If two files, or a file and a folder, would
have the same name in the same place, on_pick is not called.
Bytes and encoding
Every file read and every encoder gives you a Bytes handle, and every
file write takes one. You decode bytes with a method on the handle:
Bytes:text, Bytes:json, Bytes:csv, Bytes:image and the others
Bytes lists. You make bytes from a string with
datumhue.bytes, and from a Lua value with datumhue.bytes.json,
datumhue.bytes.kdl or datumhue.bytes.msgpack.
local bytes = datumhue.bytes
local settings = { name = "Trail", volume = 0.8, tags = { "a", "b" } }
local kdl = bytes.kdl(settings)
print(kdl:text())
local back = kdl:kdl()
print(back.name, back.volume, back.tags[2])
In this example: datumhue.bytes datumhue.bytes.kdl Bytes:text Bytes:kdl
It prints the settings as KDL, one node per key, then Trail 0.8 b.
How each KDL node reads as a Lua value, and back, is on Bytes:kdl.
Comments, order and layout do not carry over, so to keep a file as a
person wrote it, read it with Bytes:text.
datumhue.bytes.zlib packs bytes smaller, and Bytes:zlib unpacks
them. datumhue.bytes.base64 turns any bytes into plain text you can
paste in a chat message, and with url = true it also fits in a link.
Bytes:base64 turns that text back into bytes. Together they make a short code a person can share, such as
a level they built:
local bytes = datumhue.bytes
local level = { name = "Cave", walls = { 3, 5, 8, 13 } }
-- Pack the level into a code that is safe to paste anywhere.
local packed = bytes.zlib(bytes.json(level))
local code = bytes.base64(packed, { url = true }):text()
print(code)
-- Read the code back into the level.
local ok, back = pcall(function()
return bytes(code):base64():zlib():json()
end)
print(ok, back.name, back.walls[4])
In this example: datumhue.bytes datumhue.bytes.zlib datumhue.bytes.json datumhue.bytes.base64 Bytes:text Bytes:base64 Bytes:zlib Bytes:json
It prints the code, then true Cave 13. A code can be damaged or cut
short on its way, and Bytes:zlib refuses one that would unpack larger
than its max_size, so read a code someone pasted inside pcall, as
here.
datumhue.bytes.zlib and datumhue.bytes.base64 take a file's bytes
only once they are read, so wait for them with Bytes:ready first.
See also
- Save data between runs, for data your app keeps without a file.
- Load a table, which reads a CSV file it is granted.
- The
datumhue runcommand line, for every grant flag.
Reference used on this page: datumhue.bytes datumhue.args Dir:write Op:ready Dir:open File.name File:stat Stat:ready Stat.size File:read Bytes:text Dir:list ListFetch:ready ListFetch.value datumhue.ui datumhue.ui.root UiElement:panel UiElement:open_button UiElement:on_save_source UiElement:save_button UiElement:label UiElement:text_input datumhue.bytes.kdl Bytes:kdl datumhue.bytes.zlib datumhue.bytes.json datumhue.bytes.base64 Bytes:base64 Bytes:zlib Bytes:json Package:dir datumhue.package Dir:read Dir:subdir Dir:mode Dir:stat Bytes:csv Bytes:image datumhue.bytes.msgpack Bytes:ready