View Source Rewrite.Source (rewrite v0.9.0)

A representation of some source in a project.

The %Source{} contains the content of the file given by path. The module contains update/3 to update the path and/or the content. The changes are recorded in the history list.

The struct also holds issues for the source.

The different versions of content and path are available via get/3.

A source is extensible via filetype, see Rewrite.Filetype.

Summary

Types

t()

The version of a %Source{}. The version 1 indicates that the source has no changes.

Functions

Adds the given issue to the source.

Adds the given issues to the source.

Returns iodata showing all diffs of the given source.

Returns true if the file has been modified since it was read.

Sets the filetype for the source.

Returns true when from matches to value for key :from.

Creates a new %Source{} from the given string.

Gets the value for :content, :path in source or a specific key in filetype.

Gets the value for :content, :path in source or a specific key in filetype for the given version.

Returns true if the source has issues for the given version.

Calculates the current hash from the given source.

Returns all issues of the given source.

Returns the owner of the given source.

Assigns a private key and value to the source.

Creates a new %Source{} from the given path.

Tries to delete the file source.

Same as rm/1, but raises a Rewrite.SourceError exception in case of failure. Otherwise :ok.

Undoes the given number of changes.

Updates the content or the path of a source.

Returns true if the source was updated.

Returns the version of the given source. The value 1 indicates that the source has no changes.

Writes the source to disk.

Same as write/1, but raises a Rewrite.SourceError exception in case of failure.

Types

@type by() :: module()
@type content() :: String.t()
@type extension() :: String.t()
@type filetype() :: map()
@type from() :: :file | :string
@type issue() :: any()
@type key() :: atom()
@type kind() :: :content | :path
@type opts() :: keyword()
@type owner() :: module()
@type t() :: %Rewrite.Source{
  content: String.t(),
  filetype: filetype(),
  from: from(),
  hash: String.t(),
  history: [{kind(), by(), String.t()}],
  issues: [{version(), issue()}],
  owner: owner(),
  path: Path.t() | nil,
  private: map()
}
@type value() :: any()
@type version() :: pos_integer()

The version of a %Source{}. The version 1 indicates that the source has no changes.

Functions

Link to this function

add_issue(source, issue)

View Source
@spec add_issue(t(), issue()) :: t()

Adds the given issue to the source.

Link to this function

add_issues(source, issues)

View Source
@spec add_issues(t(), [issue()]) :: t()

Adds the given issues to the source.

Link to this function

diff(source, opts \\ [])

View Source
@spec diff(t(), opts()) :: iodata()

Returns iodata showing all diffs of the given source.

See Rewrite.TextDiff.format/3 for options.

Examples

iex> code = """
...> def foo( x ) do
...>   {:x,
...>     x}
...> end
...> """
iex> formatted = code |> Code.format_string!() |> IO.iodata_to_binary()
iex> source = Source.Ex.from_string(code)
iex> source |> Source.diff() |> IO.iodata_to_binary()
""
iex> source
...> |> Source.update(:content, formatted)
...> |> Source.diff(color: false)
...> |> IO.iodata_to_binary()
"""
1   - |def foo( x ) do
2   - |  {:x,
3   - |    x}
  1 + |def foo(x) do
  2 + |  {:x, x}
4 3   |end
5 4   |
"""
@spec file_changed?(t()) :: boolean()

Returns true if the file has been modified since it was read.

If the key :from does not contain :file the function returns false.

Examples

iex> File.write("tmp/hello.txt", "hello")
iex> source = Source.read!("tmp/hello.txt")
iex> Source.file_changed?(source)
false
iex> File.write("tmp/hello.txt", "Hello, world!")
iex> Source.file_changed?(source)
true
iex> source = Source.update(source, :path, nil)
iex> Source.file_changed?(source)
true
iex> File.write("tmp/hello.txt", "hello")
iex> Source.file_changed?(source)
false
iex> File.rm!("tmp/hello.txt")
iex> Source.file_changed?(source)
true

iex> source = Source.from_string("hello")
iex> Source.file_changed?(source)
false
Link to this function

filetype(source, filetype)

View Source
@spec filetype(t(), filetype()) :: t()

Sets the filetype for the source.

@spec from?(t(), :file | :string) :: boolean()

Returns true when from matches to value for key :from.

Examples

iex> source = Source.from_string("hello")
iex> Source.from?(source, :file)
false
iex> Source.from?(source, :string)
true
Link to this function

from_string(content, path \\ nil, opts \\ [])

View Source
@spec from_string(String.t(), Path.t() | nil, opts()) :: t()

Creates a new %Source{} from the given string.

Examples

iex> source = Source.from_string("hello")
iex> source.content
"hello"
@spec get(t(), key()) :: value()

Gets the value for :content, :path in source or a specific key in filetype.

Raises Rewrite.SourceKeyError if the key can't be found.

Link to this function

get(source, key, version)

View Source
@spec get(t(), key(), version()) :: value()

Gets the value for :content, :path in source or a specific key in filetype for the given version.

Raises Rewrite.SourceKeyError if the key can't be found.

Examples

iex> bar =
...>   """
...>   defmodule Bar do
...>      def bar, do: :bar
...>   end
...>   """
iex> foo =
...>   """
...>   defmodule Foo do
...>      def foo, do: :foo
...>   end
...>   """
iex> source = Source.Ex.from_string(bar)
iex> source = Source.update(source, :content, foo)
iex> Source.get(source, :content) == foo
true
iex> Source.get(source, :content, 2) == foo
true
iex> Source.get(source, :content, 1) == bar
true

iex> source =
...>   "hello"
...>   |> Source.from_string("some/where/hello.txt")
...>   |> Source.update(:path, "some/where/else/hello.txt")
...> Source.get(source, :path, 1)
"some/where/hello.txt"
iex> Source.get(source, :path, 2)
"some/where/else/hello.txt"
Link to this function

has_issues?(source, version \\ :actual)

View Source
@spec has_issues?(t(), version() | :actual | :all) :: boolean()

Returns true if the source has issues for the given version.

The version argument also accepts :actual and :all to check whether the source has problems for the actual version or if there are problems at all.

Examples

iex> source =
...>   "a + b"
...>   |> Source.Ex.from_string("some/where/plus.exs")
...>   |> Source.add_issue(%{issue: :foo})
...>   |> Source.update(:path, "some/where/else/plus.exs")
...>   |> Source.add_issue(%{issue: :bar})
iex> Source.has_issues?(source)
true
iex> Source.has_issues?(source, 1)
true
iex> Source.has_issues?(source, :all)
true
iex> source = Source.update(source, :content, "a - b")
iex> Source.has_issues?(source)
false
iex> Source.has_issues?(source, 2)
true
iex> Source.has_issues?(source, :all)
true
@spec hash(t()) :: binary()

Calculates the current hash from the given source.

Examples

iex> source = Source.from_string("""
...> defmodule Foo do
...>   def bar, do: :bar
...> end
...> """)
iex> Source.hash(source)
<<76, 24, 5, 252, 117, 230, 0, 217, 129, 150, 68, 248, 6, 48, 72, 46>>
@spec issues(t()) :: [issue()]

Returns all issues of the given source.

@spec owner(t()) :: module()

Returns the owner of the given source.

Link to this function

put_private(source, key, value)

View Source
@spec put_private(t(), key :: any(), value()) :: t()

Assigns a private key and value to the source.

This is not used or accessed by Rewrite, but is intended as private storage for users or libraries that wish to store additional data about a source.

Examples

iex> source =
...>   "a + b"
...>   |> Source.from_string()
...>   |> Source.put_private(:origin, :example)
iex> source.private[:origin]
:example
@spec read!(Path.t(), opts()) :: t()

Creates a new %Source{} from the given path.

Examples

iex> source = Source.read!("test/fixtures/source/hello.txt")
iex> source.content
"""
hello
"""
@spec rm(t()) :: :ok | {:error, Rewrite.SourceError.t()}

Tries to delete the file source.

Returns :ok if successful, or {:error, reason} if an error occurs.

Note the file is deleted even if in read-only mode.

@spec rm!(t()) :: :ok

Same as rm/1, but raises a Rewrite.SourceError exception in case of failure. Otherwise :ok.

Link to this function

undo(source, number \\ 1)

View Source
@spec undo(t(), non_neg_integer()) :: t()

Undoes the given number of changes.

Examples

iex> a = Source.from_string("test-a", "test/foo.txt")
iex> b = Source.update(a, :content, "test-b")
iex> c = Source.update(b, :path, "test/bar.txt")
iex> d = Source.update(c, :content, "test-d")
iex> d |> Source.undo() |> Source.get(:content)
"test-b"
iex> d |> Source.undo(1) |> Source.get(:content)
"test-b"
iex> d |> Source.undo(2) |> Source.get(:path)
"test/foo.txt"
iex> d |> Source.undo(3) |> Source.get(:content)
"test-a"
iex> d |> Source.undo(9) |> Source.get(:content)
"test-a"
iex> d |> Source.undo(9) |> Source.updated?()
false
iex> d |> Source.undo(-9) |> Source.get(:content)
"test-d"
Link to this function

update(source, by \\ Rewrite, key, value)

View Source
@spec update(t(), by(), key(), value()) :: t()

Updates the content or the path of a source.

Examples

iex> source =
...>   "foo"
...>   |> Source.from_string()
...>   |> Source.update(Example, :path, "test/fixtures/new.exs")
...>   |> Source.update(:content, "bar")
iex> source.history
[{:content, Rewrite, "foo"}, {:path, Example, nil}]
iex> source.content
"bar"

If the new value is equal to the current value, no history will be added.

iex> source =
...>   "42"
...>   |> Source.from_string()
...>   |> Source.update(Example, :content, "21")
...>   |> Source.update(Example, :content, "21")
...>   |> Source.update(Example, :content, "21")
iex> source.history
[{:content, Example, "42"}]
Link to this function

updated?(source, kind \\ :any)

View Source
@spec updated?(t(), kind :: :content | :path | :any) :: boolean()

Returns true if the source was updated.

The optional argument kind specifies whether only :code changes or :path changes are considered. Defaults to :any.

Examples

iex> source = Source.from_string("foo")
iex> Source.updated?(source)
false
iex> source = Source.update(source, :content, "bar")
iex> Source.updated?(source)
true
iex> Source.updated?(source, :path)
false
iex> Source.updated?(source, :content)
true
@spec version(t()) :: version()

Returns the version of the given source. The value 1 indicates that the source has no changes.

Link to this function

write(source, opts \\ [])

View Source
@spec write(t(), opts()) :: {:ok, t()} | {:error, Rewrite.SourceError.t()}

Writes the source to disk.

Returns {:ok, source} when the file was written successfully. The returned source does not include any previous changes or issues.

If there's an error, this function returns {:error, error} where error is a Rewrite.SourceError. You can raise it manually with raise/1.

Returns {:error, error} with reason: :nopath if the current path is nil.

Returns {:error, error} with reason: :changed if the file was changed since reading. See also file_changed?/1. The option force: true forces overwriting a changed file.

If the source :path was updated then the old file will be deleted.

Missing directories are created.

Options

  • :force, default: false - forces the saving to overwrite changed files.
  • :rm, default: true - prevents file deletion when set to false.

Examples

iex> ":test" |> Source.from_string() |> Source.write()
{:error, %SourceError{reason: :nopath, path: nil, action: :write}}

iex> path = "tmp/foo.txt"
iex> File.write(path, "foo")
iex> source = path |> Source.read!() |> Source.update(:content, "bar")
iex> Source.updated?(source)
true
iex> {:ok, source} = Source.write(source)
iex> File.read(path)
{:ok, "bar\n"}
iex> Source.updated?(source)
false

iex> source = Source.from_string("bar")
iex> Source.write(source)
{:error, %SourceError{reason: :nopath, path: nil, action: :write}}
iex> source |> Source.update(:path, "tmp/bar.txt") |> Source.write()
iex> File.read("tmp/bar.txt")
{:ok, "bar\n"}

iex> path = "tmp/ping.txt"
iex> File.write(path, "ping")
iex> source = Source.read!(path)
iex> new_path = "tmp/pong.ex"
iex> source = Source.update(source, :path, new_path)
iex> Source.write(source)
iex> File.exists?(path)
false
iex> File.read(new_path)
{:ok, "ping\n"}

iex> path = "tmp/ping.txt"
iex> File.write(path, "ping")
iex> source = Source.read!(path)
iex> new_path = "tmp/pong.ex"
iex> source = Source.update(source, :path, new_path)
iex> Source.write(source, rm: false)
iex> File.exists?(path)
true

iex> path = "tmp/ping.txt"
iex> File.write(path, "ping")
iex> source = path |> Source.read!() |> Source.update(:content, "peng")
iex> File.write(path, "pong")
iex> Source.write(source)
{:error, %SourceError{reason: :changed, path: "tmp/ping.txt", action: :write}}
iex> {:ok, _source} = Source.write(source, force: true)
@spec write!(t()) :: t()

Same as write/1, but raises a Rewrite.SourceError exception in case of failure.