about summary refs log tree commit diff stats
path: root/src/file.lua
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2022-03-06 09:13:34 -0800
committerKartik K. Agaram <vc@akkartik.com>2022-03-06 09:21:32 -0800
commit38ff2ddf11dc686a83be721d705e86c48538c837 (patch)
tree3d37e37a7fd0b1ece6934864b2e2710d818b357f /src/file.lua
parentdb7fd08e05dc62497bf836c9ff781d91ee2b1c8b (diff)
downloadteliva-38ff2ddf11dc686a83be721d705e86c48538c837.tar.gz
move start_reading/start_writing out of template
When should code go in the template used by new apps vs the .lua files
distributed with Teliva?

- from a privilege perspective there's no difference
- from a compatibility perspective stuff in .tlv will not get upgraded
  with Teliva.
- for me the maintainer, functions in .lua files are easier to upgrade
  in a single place.
- for the reader of an app, functions in .lua files will not show up to
  be edited. They can still be overloaded, but the current version isn't
  as discoverable. Putting something in the app is a slight nudge to
  readers that they're encouraged to mess with it.
- Stuff in .lua files can use local functions and so have more internal
  complexity. Apps can also hide details within functions, but that'll
  make them more likely to run into limitations with Teliva's editing
  environment.

I'm not yet sure how to reason about the second point in practice.
  - Stuff in .tlv files I don't have to worry about compatibility
    guarantees for.
  - Stuff in .lua files I _do_ have to worry about compatibility
    guarantees for.

Perhaps this means I'm doing things exactly wrong in this commit?
Functions like map/reduce/filter/append seem more timeless, whereas I'm
still just feeling my way around with start_reading and start_writing.

We'll see. For now I'm ruled by the fourth point. Messing with tasks and
the scheduler is much more advanced than anything else in template.tlv;
it seems to make sense to add some friction to modifying them.

Bottomline: Complex sub-systems go in .lua files. Simple, self-contained
definitions go into apps. Both are probably equally burdensome now from
a compatibility perspective.
Diffstat (limited to 'src/file.lua')
-rw-r--r--src/file.lua45
1 files changed, 45 insertions, 0 deletions
diff --git a/src/file.lua b/src/file.lua
new file mode 100644
index 0000000..faac0c3
--- /dev/null
+++ b/src/file.lua
@@ -0,0 +1,45 @@
+-- primitive for reading files from a file system (or, later, network)
+-- returns a channel or nil on error
+-- read lines from the channel using :recv()
+-- recv() on the channel will indicate end of file.
+function start_reading(fs, filename)
+  local result = task.Channel:new()
+  local infile = io.open(filename)
+  if infile == nil then return nil end
+  task.spawn(reading_task, infile, result)
+  return result
+end
+
+local function reading_task(infile, chanout)
+  for line in infile:lines() do
+    chanout:send(line)
+  end
+  chanout:send(nil)  -- eof
+end
+
+-- primitive for writing files to a file system (or, later, network)
+-- returns a channel or nil on error
+-- write to the channel using :send()
+-- indicate you're done writing by calling :close()
+-- file will not be externally visible until :close()
+function start_writing(fs, filename)
+  local result = task.Channel:new()
+  local initial_filename = os.tmpname()
+  local outfile = io.open(initial_filename, 'w')
+  if outfile == nil then return nil end
+  result.close = function()
+    result:send(nil)  -- end of file
+    outfile:close()
+    os.rename(initial_filename, filename)
+  end
+  task.spawn(writing_task, outfile, result)
+  return result
+end
+
+local function writing_task(outfile, chanin)
+  while true do
+    local line = chanin:recv()
+    if line == nil then break end  -- end of file
+    outfile:write(line)
+  end
+end