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
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 thesource
.filetype
- astruct
implementing the behaviourRewrite.Filetype
. Thefiletype
is nil when no additional implementation for thefiletype
is available.from
- contains:file
or:string
depending on whether thesource
is created from a file or a string.hash
- of thesource
. Thehash
is built from thecontent
andpath
.history
- of thesource
.issues
- of thesource
.owner
- of thesource
.path
- of thesource
. Can benil
if thesource
was created by astring
.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 astring
, 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 value() :: term()
@type version() :: pos_integer()
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
.
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 |
"""
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
Sets the filetype
for the source
.
Formats the given source
.
If the source
was formatted the source
gets a new :history
entry,
otherwise the unchanged source
is returned.
Options
by
- anatom
ormodule
that is used as:by
key when thesource
is updated. Defaults toRewrite
.dot_formatter
- defaults toRewrite.DotFormatter.default/0
.Accepts also the same options as
Code.format_string!/2
.
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
"""
Same as format/2
, but raises an exception in case of failure.
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
Creates a new %Source{}
from the given string
.
Options
:owner
- an association to the module that owns thesource
.:dot_formatter
- a fromatter for thesource
.path
- the path of thesource
.
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
Gets the value for :content
, :path
in source
or a specific key
in
filetype
.
Raises Rewrite.SourceKeyError
if the key
can't be found.
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"
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
.
Returns all issues of the given source
.
Returns the owner of the given source
.
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
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
.
Sets the timestamp
to the current POSIX timestamp.
Does not touch the underlying file.
Sets the timestamp
of the given source
to the given timestamp
.
Does not touch the underlying file.
@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"
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"}]
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
Returns the version
of the given source
. The value 1
indicates that the
source has no changes.
@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 tofalse
.
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)
Same as write/1
, but raises a Rewrite.SourceError
exception in case of
failure.