View Source Rewrite.Source (rewrite v1.0.1)

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 struct representing a source.

A timestamp as integer seconds since epoch.

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.

The default path for 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.

Formats the given source.

Same as format/2, but raises an exception in case of failure.

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.

Sets the timestamp to the current POSIX timestamp.

Sets the timestamp of the given source to the given timestamp.

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() :: term()
@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(),
  timestamp: timestamp()
}

The struct representing a source.

Fields

  • content - of the source.

  • filetype - a struct implementing the behaviour Rewrite.Filetype. The filetype is nil when no additional implementation for the filetype is available.

  • from - contains :file or :string depending on whether the source is created from a file or a string.

  • hash - of the source. The hash is built from the content and path.

  • history - of the source.

  • issues - of the source.

  • owner - of the source.

  • path - of the source. Can be nil if the source was created by a string.

  • private - a field for user defined data.

  • timestamp - is set to the timestamp of the last modification of the file on disk at the time it was read.

    If the source was created by a string, the timestamp is the creation time.

    The timestamp will be updated when the source is updated.

@type timestamp() :: integer()

A timestamp as integer seconds since epoch.

@type updater() :: (term() -> term())
@type value() :: term()
@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.

@spec default_path(t()) :: Path.t()

The default path for 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 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.

Link to this function

format(source, opts \\ [])

View Source
@spec format(t(), opts()) :: {:ok, t()} | {:errror, term()}
@spec format(t(), opts()) :: t()

Formats the given source.

If the source was formatted the source gets a new :history entry, otherwise the unchanged source is returned.

Options

Examples

iex> source = Source.Ex.from_string("""
...> defmodule    Foo do
...>     def   foo(x),   do:    bar x
...>    end
...> """)
iex> {:ok, formatted} = Source.format(source, force_do_end_blocks: true)
iex> formatted.content
"""
defmodule Foo do
  def foo(x) do
    bar(x)
  end
end
"""
iex> dot_formatter = DotFormatter.from_formatter_opts(locals_without_parens: [bar: 1])
iex> {:ok, formatted} = Source.format(source, 
...>   dot_formatter: dot_formatter, force_do_end_blocks: true
...> )
iex> formatted.content
"""
defmodule Foo do
  def foo(x) do
    bar x
  end
end
"""
Link to this function

format!(source, opts \\ [])

View Source

Same as format/2, but raises an exception in case of failure.

@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, opts \\ [])

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

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

Options

  • :owner - an association to the module that owns the source.

  • :dot_formatter - a fromatter for the source.

  • path - the path of the source.

Examples

iex> source = Source.from_string("hello")
iex> source.content
"hello"
iex> source.path
nil
iex> source.owner
Rewrite

iex> source = Source.from_string("hello", path: "hello.md", owner: MyApp)
iex> source.path
"hello.md"
iex> source.owner
MyApp
@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(path: "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(path: "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()) :: non_neg_integer()

Calculates the current hash from the given source.

@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(), 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.

@spec touch(t()) :: t()

Sets the timestamp to the current POSIX timestamp.

Does not touch the underlying file.

Link to this function

touch(source, timestamp)

View Source
@spec touch(t(), timestamp()) :: t()

Sets the timestamp of the given source to the given timestamp.

Does not touch the underlying file.

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", path: "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, key, value, opts \\ [])

View Source
@spec update(t(), key(), value() | updater(), opts()) :: t()

Updates the content or the path of a source.

The given value can be of type value/0 or an updater function that gets the current value and returns the new value.

Examples

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

iex> source =
...>   "foo"
...>   |> Source.from_string()
...>   |> Source.update(:content, fn content -> content <> "bar" end)
iex> source.content
"foobar"

With a Rewrite.Source.Ex. Note that the AST is generated by Sourceror.

iex> source =
...>   ":a"
...>   |> Source.Ex.from_string()
...>   |> Source.update(:quoted, fn quoted ->
...>     {:__block__, meta, [atom]} = quoted 
...>     {:__block__, meta, [{:ok, atom}]}
...>   end)
iex> source.content
"{:ok, :a}\n"

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

iex> source =
...>   "42"
...>   |> Source.from_string()
...>   |> Source.update(:content, "21", by: Example)
...>   |> Source.update(:content, "21", by: Example)
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.