Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions concepts/files/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"authors": [
"keiravillekode"
],
"blurb": "Read, write, and append to files on disk with the whole-file and scoped words of Factor's io.files vocabulary."
}
83 changes: 83 additions & 0 deletions concepts/files/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# About

A file is a stream with a name on disk. The [`io.files`][io.files] vocabulary
offers two ways to work with one: whole-file convenience words that read or
write everything in a single call, and stream openers (the `with-…`
combinators) for reading or writing incrementally. Both rest on the same
[stream protocol][stream-protocol] as the in-memory streams from the
`io-streams` concept — a `<file-reader>` and a `<string-reader>` answer the same
words.

Every file word takes an **encoding**, the scheme that maps bytes to characters
and back. For text this is almost always [`utf8`][utf8] from
`io.encodings.utf8`; binary work uses `latin1` or a `<byte-array>` encoding.

## Whole-file reading

```
file-contents ( path encoding -- str )
file-lines ( path encoding -- seq )
```

`file-contents` returns the entire file as one string. `file-lines` returns its
lines as an array, with the line terminators stripped off — handy when each
line is a record.

```factor
USING: io.encodings.utf8 io.files ;

"forecast.txt" utf8 file-contents ! => "sunny\ncloudy\n"
"forecast.txt" utf8 file-lines ! => { "sunny" "cloudy" }
```

## Whole-file writing

```
set-file-contents ( str path encoding -- )
set-file-lines ( seq path encoding -- )
```

Both **replace** the file, creating it if it does not exist.
`set-file-contents` writes a string verbatim; `set-file-lines` writes each
element of a sequence on its own line, supplying the newlines.

```factor
{ "sunny" "cloudy" } "forecast.txt" utf8 set-file-lines
! forecast.txt now holds "sunny\ncloudy\n"
```

## Scoped streams

When you want to add to a file, or read and write piece by piece, open it with a
`with-…` combinator and use the ambient stream words inside the quotation:

```
with-file-reader ( path encoding quot -- )
with-file-writer ( path encoding quot -- )
with-file-appender ( path encoding quot -- )
```

```factor
USING: io io.encodings.utf8 io.files ;

"log.txt" utf8 [ "another line" print ] with-file-appender
```

`with-file-writer` truncates the file first; `with-file-appender` keeps the
existing contents and writes after them. Each one is a destructor scope: the
file handle is closed when the quotation finishes, even if it throws — the same
guarantee the `boatswains-bilge` combinators provide, here specialised to
files.

## Whole-file or scoped?

Reach for `file-contents` / `file-lines` / `set-file-contents` /
`set-file-lines` when the file is small enough to hold in memory and you want
the whole thing at once. Reach for the `with-…` openers when you need to append,
to stream a large file a line at a time with `readln`, or to interleave reads
and writes — anywhere holding the entire file in memory would be wasteful or
wrong.

[io.files]: https://docs.factorcode.org/content/vocab-io.files.html
[utf8]: https://docs.factorcode.org/content/vocab-io.encodings.utf8.html
[stream-protocol]: https://docs.factorcode.org/content/article-stream-protocol.html
47 changes: 47 additions & 0 deletions concepts/files/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Introduction

A file is a stream with a name on disk. The [`io.files`][io.files] vocabulary
reads and writes files either whole — in a single call — or incrementally
through a scoped stream. Every file word takes an **encoding**; for text that
is almost always [`utf8`][utf8] from `io.encodings.utf8`.

## Reading

```
file-contents ( path encoding -- str )
file-lines ( path encoding -- seq )
```

`file-contents` returns the whole file as one string. `file-lines` returns its
lines as an array, with the line breaks removed.

## Writing

```
set-file-contents ( str path encoding -- )
set-file-lines ( seq path encoding -- )
```

Both replace the file (creating it if needed). `set-file-lines` writes one
element per line, adding the newlines for you.

## Appending and incremental I/O

The `with-…` combinators open a file as the ambient stream for a quotation and
close it afterward — a destructor scope, like the stream combinators in
`channel-chatter`.

```
with-file-reader ( path encoding quot -- )
with-file-writer ( path encoding quot -- )
with-file-appender ( path encoding quot -- )
```

```factor
USING: io io.encodings.utf8 io.files ;

"log.txt" utf8 [ "another line" print ] with-file-appender
```

[io.files]: https://docs.factorcode.org/content/vocab-io.files.html
[utf8]: https://docs.factorcode.org/content/vocab-io.encodings.utf8.html
14 changes: 14 additions & 0 deletions concepts/files/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"url": "https://docs.factorcode.org/content/vocab-io.files.html",
"description": "io.files — reading, writing, and appending to files"
},
{
"url": "https://docs.factorcode.org/content/vocab-io.encodings.utf8.html",
"description": "io.encodings.utf8 — the utf8 encoding for text files"
},
{
"url": "https://docs.factorcode.org/content/article-stream-protocol.html",
"description": "The stream protocol that file streams implement"
}
]
48 changes: 48 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,20 @@
"locals"
],
"status": "beta"
},
{
"slug": "vltava-weather-watch",
"name": "Vltava Weather Watch",
"uuid": "edbe1108-0c38-47a1-9917-d2d6f9df1752",
"concepts": [
"files"
],
"prerequisites": [
"io-streams",
"sequences",
"strings"
],
"status": "beta"
}
],
"practice": [
Expand Down Expand Up @@ -1796,6 +1810,21 @@
],
"difficulty": 5
},
{
"slug": "split-second-stopwatch",
"name": "Split-Second Stopwatch",
"uuid": "b4f33c97-64e9-41c1-804f-fa2453d3f1da",
"practices": [
"tuples"
],
"prerequisites": [
"tuples",
"errors",
"sequences",
"strings"
],
"difficulty": 5
},
{
"slug": "square-root",
"name": "Square Root",
Expand Down Expand Up @@ -2107,6 +2136,20 @@
],
"difficulty": 7
},
{
"slug": "grep",
"name": "Grep",
"uuid": "bb9023f5-0baf-4e6a-a219-99390fd0aebd",
"practices": [
"files"
],
"prerequisites": [
"files",
"sequences",
"strings"
],
"difficulty": 7
},
{
"slug": "paasio",
"name": "PaaS I/O",
Expand Down Expand Up @@ -2511,6 +2554,11 @@
"uuid": "6510ec99-2e86-4a29-a57a-a733f13a1365",
"slug": "combinatorics",
"name": "Combinatorics"
},
{
"uuid": "30538350-2959-4c56-b76b-2aedd7c919a0",
"slug": "files",
"name": "Files"
}
],
"key_features": [
Expand Down
13 changes: 13 additions & 0 deletions exercises/concept/backyard-birdwatcher/.docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ When you have a *start and a length*, add them to get the end:
(That's the index range `[2, 5)` — three characters starting at
position 2.)

The reverse question — *where* a subsequence occurs — is answered
by `subseq-index`, which returns the starting index of the first
match, or `f` when there is none:

```
subseq-index ( seq subseq -- i/f )
```

```factor
"hello world" "o w" subseq-index . ! => 4
"hello world" "xyz" subseq-index . ! => f
```

## Padding

```
Expand Down
17 changes: 17 additions & 0 deletions exercises/concept/joiners-journey/.docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,22 @@ bi* ( x y q1 q2 -- r1 r2 )
Reach for `bi@` when both values get the same treatment, and for
`bi*` when each needs its own.

## `tri*` — different operations on three values

`tri*` (in [`combinators`][combinators]) extends `bi*` to three
values and three quotations, applying each quotation to one value
in order:

```
tri* ( x y z q1 q2 q3 -- r1 r2 r3 )
```

```factor
1 2 3 [ sq ] [ neg ] [ 10 * ] tri* .s
! => 1
! => -2
! => 30
```

[kernel]: https://docs.factorcode.org/content/vocab-kernel.html
[combinators]: https://docs.factorcode.org/content/vocab-combinators.html
36 changes: 36 additions & 0 deletions exercises/concept/vltava-weather-watch/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Hints

## General

- Every reading and writing word lives in [`io.files`][io.files]; the `utf8`
encoding they all take comes from [`io.encodings.utf8`][utf8].

## 1. Read every reading

- `file-lines` returns an array of lines with the newlines removed.

## 2. Find the latest reading

- Read the lines with `file-lines`, then take the `last` one (from
`sequences`).

## 3. Read the raw log

- `file-contents` returns the file as a single string, newlines and all.

## 4. Record a reading

- Open the file for appending and `print` the reading inside the quotation:
`with-file-appender ( path encoding quot -- )`. The reading is used inside
the quotation while the path is consumed before it, so `::` locals keep the
body readable:
`:: record-reading ( reading path -- ) path utf8 [ reading print ]
with-file-appender ;`.

## 5. Rewrite the log

- `set-file-lines` writes each element of a sequence on its own line, adding
the newlines for you.

[io.files]: https://docs.factorcode.org/content/vocab-io.files.html
[utf8]: https://docs.factorcode.org/content/vocab-io.encodings.utf8.html
59 changes: 59 additions & 0 deletions exercises/concept/vltava-weather-watch/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Instructions

You run a little weather post on the bank of the Vltava in Prague, and every
shift you jot the temperature readings into a plain-text log — one reading per
line. Five small words handle a shift's worth of bookkeeping.

Each word is handed the path to the log file.

## 1. Read every reading

Define `read-readings` to return all of the log's readings as an array of
strings, one per line.

```factor
"weather.log" read-readings .
! => { "21.5" "19.0" "22.3" }
```

## 2. Find the latest reading

Define `latest-reading` to return the most recent reading — the last line of
the log.

```factor
"weather.log" latest-reading .
! => "22.3"
```

## 3. Read the raw log

Define `log-text` to return the whole log file as a single string, exactly as
stored.

```factor
"weather.log" log-text .
! => "21.5\n19.0\n22.3\n"
```

## 4. Record a reading

Define `record-reading` to append a new reading to the end of the log as its
own line, leaving the earlier readings untouched. Returns nothing.

```factor
"23.1" "weather.log" record-reading
"weather.log" log-text .
! => "21.5\n19.0\n22.3\n23.1\n"
```

## 5. Rewrite the log

Define `rewrite-log` to replace the whole log with a fresh array of readings,
one per line.

```factor
{ "10.0" "11.0" } "weather.log" rewrite-log
"weather.log" log-text .
! => "10.0\n11.0\n"
```
Loading
Loading