# `Fil.Op`
[🔗](https://github.com/pehbehbeh/fil/blob/v0.1.0/lib/fil/op.ex#L1)

One call to a `Fil` function, as plugins see it.

Every operation builds a `%Fil.Op{}` and passes it through the disk's plugins to the adapter. See the
[Plugins guide](plugins.md) for how to write a plugin.

  * `:disk`: the `Fil.Disk` the operation runs on
  * `:name`: the operation, `:read`, `:write`, `:stat`, `:ls`, `:rm`, `:rm_rf`, `:cp`, `:rename`, `:url` or
    `:signed_url`
  * `:path`: the normalized path, relative to the disk root
  * `:dest`: the destination path of a `:cp` or `:rename` on the same disk, `nil` otherwise
  * `:content`: the content of a `:write`, `nil` otherwise. Change it with `update_content/2`
  * `:options`: the validated options of the call
  * `:result`: `nil` on the way in, then `{:ok, value}` or `{:error, exception}` with the same value the `Fil`
    function returns. Change a read result with `update_result/2`
  * `:private`: a map for plugins to pass data along

# `name`

```elixir
@type name() ::
  :read
  | :write
  | :stat
  | :ls
  | :rm
  | :rm_rf
  | :cp
  | :rename
  | :url
  | :signed_url
```

# `t`

```elixir
@type t() :: %Fil.Op{
  content: iodata() | nil,
  dest: Path.t() | nil,
  disk: Fil.Disk.t(),
  name: name(),
  options: keyword(),
  path: Path.t(),
  private: map(),
  result: {:ok, term()} | {:error, Exception.t()} | nil
}
```

# `transform`

```elixir
@type transform() :: [binary: (binary() -&gt; iodata()), chunk: (binary() -&gt; iodata())]
```

# `get_option`

```elixir
@spec get_option(t(), atom(), term()) :: term()
```

Returns an option of the call.

    iex> op = %Fil.Op{disk: nil, name: :write, path: "a.txt", options: [content_type: "text/plain"]}
    iex> Fil.Op.get_option(op, :content_type)
    "text/plain"
    iex> Fil.Op.get_option(op, :missing, :default)
    :default

# `get_private`

```elixir
@spec get_private(t(), term(), term()) :: term()
```

Returns a private value.

    iex> op = %Fil.Op{disk: nil, name: :read, path: "a.txt"}
    iex> Fil.Op.get_private(op, :seen, false)
    false

# `materialize`

```elixir
@spec materialize(t()) :: t()
```

Makes the content of a `:write` whole, for plugins that need all of it at once.

Content is always whole for now, so this only flattens iodata into a binary. Once streaming lands, it collects a
stream into memory, so use it only when a plugin can't work chunk by chunk.

    iex> op = %Fil.Op{disk: nil, name: :write, path: "a.txt", content: ["a", ["b"]]}
    iex> Fil.Op.materialize(op).content
    "ab"

# `put_new_option`

```elixir
@spec put_new_option(t(), atom(), term()) :: t()
```

Sets an option unless the call already has it.

    iex> op = %Fil.Op{disk: nil, name: :write, path: "a.txt", options: [content_type: "text/plain"]}
    iex> op |> Fil.Op.put_new_option(:content_type, "text/csv") |> Fil.Op.get_option(:content_type)
    "text/plain"

# `put_option`

```elixir
@spec put_option(t(), atom(), term()) :: t()
```

Sets an option before the adapter sees it.

    iex> op = %Fil.Op{disk: nil, name: :write, path: "a.txt", options: [content_type: "text/plain"]}
    iex> op |> Fil.Op.put_option(:content_type, "text/csv") |> Fil.Op.get_option(:content_type)
    "text/csv"

# `put_private`

```elixir
@spec put_private(t(), term(), term()) :: t()
```

Stores a private value, for a later plugin or for the way back.

    iex> op = %Fil.Op{disk: nil, name: :read, path: "a.txt"}
    iex> op |> Fil.Op.put_private(:seen, true) |> Fil.Op.get_private(:seen)
    true

# `put_result`

```elixir
@spec put_result(t(), {:ok, term()} | {:error, Exception.t()}) :: t()
```

Sets the result.

A plugin that sets the result instead of calling `next` answers the call itself, and the adapter never runs. An error
is an exception: one of `Fil`'s (see [Errors](Fil.Adapter.html#module-errors)) or the plugin's own. `Fil` fills in
the `:op`, `:path` and `:disk` of its own errors where the plugin left them `nil`.

# `update_content`

```elixir
@spec update_content(t(), transform()) :: t()
```

Transforms the content of a `:write`. Other operations are returned unchanged.

Pass `binary:` to transform the whole content at once and `chunk:` to transform it piece by piece:

    Fil.Op.update_content(op, binary: &:zlib.gzip/1)

Content is always whole for now, so `binary:` runs and gets a binary (iodata is flattened first). Without `binary:`,
the content is passed to `chunk:` as a single chunk. Once streaming lands, `chunk:` runs on each chunk of a stream,
and a stream is collected first if there's only `binary:`.

# `update_result`

```elixir
@spec update_result(t(), transform()) :: t()
```

Transforms the content returned by a successful `:read`. Other operations and errors are returned unchanged.

Takes the same `binary:` and `chunk:` functions as `update_content/2`.

