View Source Rewrite (rewrite v1.1.1)

Rewrite is a tool for modifying, adding and removing files in a Mix project.

The package is intended for use in Mix tasks. Rewrite itself uses functions provided by Mix.

With Rewrite.read!/2 you can load the whole project. Then you can modify the project with a number of functions provided by Rewrite and Rewrite.Source without writing any changes back to the file system. All changes are stored in the source structs. Any version of a source is available in the project. To write the whole project back to the file system, the Rewrite.write_all/2 can be used.

Elixir source files can be modified by modifying the AST. For this Rewrite uses the Sourceror package to create the AST and to convert it back. The Sourceror package also provides all the utilities needed to manipulate the AST.

Sources can also receive a Rewrite.Issue to document problems or information with the source.

Rewrite respects the .formatter.exs in the project when rewriting sources. To do this, the formatter can be read by Rewrite.DotFormatter and the resulting DotFormatter struct can be used in the function to update the sources.

Summary

Functions

Counts the sources with the given extname in the rewrite project.

Creates a new %Source{} without putting it to the %Rewrite{} project.

Deletes the source for the given path from the rewrite.

Returns the DotFormatter for the given rewrite project.

Sets a dot_formatter for the given rewrite project.

Drops the sources with the given paths from the rewrite project.

Returns the extension of the given file.

Formats the given rewrite project with the given dot_formatter.

The same as format/2 but raises an exception in case of an error.

Formats a source in a rewrite project.

The same as format_source/3 but raises an exception in case of an error.

Creates a %Rewrite{} from the given sources.

Same as from_sources/2, but raises a Rewrite.Error exception in case of failure.

Returns true when the %Rewrite{} contains a %Source{} with the given path.

Returns true if any source has one or more issues.

Invokes fun for each source in the rewrite project and updates the rewirte project with the result of fun.

Return a rewrite project where each source is the result of invoking fun on each source of the given rewrite project.

Moves a source from one path to another.

Same as move/4, but raises an exception in case of failure.

Creates an empty project.

Creates a %Rewrite{} from the given inputs.

Creates a new %Source{} and puts the source to the %Rewrite{} project.

Same as new_source/4, but raises a Rewrite.Error exception in case of failure.

Returns a sorted list of all paths in the rewrite project.

Puts the given source to the given rewrite project.

Same as put/2, but raises a Rewrite.Error exception in case of failure.

Reads the given input/inputs and adds the source/sources to the project when not already readed.

Tries to delete the source file in the file system and removes the source from the rewrite project.

Same as source/2, but raises a Rewrite.Error exception in case of failure.

Returns the %Rewrite.Source{} for the given path.

Same as source/2, but raises a Rewrite.Error exception in case of failure.

Returns all sources sorted by path.

Updates the given source in the rewrite project.

Updates a source for the given path in the rewrite project.

The same as update/2 but raises a Rewrite.Error exception in case of an error.

The same as update/3 but raises a Rewrite.Error exception in case of an error.

Updates the source for the given path and key with the given fun.

The same as update_source/5 but raises a Rewrite.Error exception in case of an error.

Returns true if any source in the rewrite project returns true for Source.updated?/1.

Writes a source to disk.

The same as write/3 but raises an exception in case of an error.

Writes all sources in the rewrite project to disk.

Types

@type by() :: module()
@type input() :: Path.t() | wildcard() | GlobEx.t()
@type key() :: atom()
@type opts() :: keyword()
@type t() :: %Rewrite{
  dot_formatter: Rewrite.DotFormatter.t() | nil,
  excluded: [Path.t()],
  extensions: %{required(String.t()) => [module()]},
  hooks: [module()],
  sources: %{required(Path.t()) => Rewrite.Source.t()}
}
@type updater() :: (term() -> term())
@type wildcard() :: IO.chardata()

Functions

@spec count(t(), String.t()) :: non_neg_integer()

Counts the sources with the given extname in the rewrite project.

Link to this function

create_source(rewrite, path, content, opts \\ [])

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

Creates a new %Source{} without putting it to the %Rewrite{} project.

The :filetypes option of the project is used to create the source. If options have been specified for the file type, the given options will be merged into those options. If no path is given, the default file type is created.

The function does not check whether the %Rewrite{} project already has a %Source{} with the specified path.

Use new_source/4 if the source is to be inserted directly into the project.

@spec delete(t(), Path.t()) :: t()

Deletes the source for the given path from the rewrite.

The file system files are not removed, even if the project is written. Use rm/2 or rm!/2 to delete a file and source.

If the source is not part of the rewrite project the unchanged rewrite is returned.

Examples

iex> {:ok, project} = Rewrite.from_sources([
...>   Source.from_string(":a", path: "a.exs"),
...>   Source.from_string(":b", path: "b.exs"),
...>   Source.from_string(":a", path: "c.exs")
...> ])
iex> Rewrite.paths(project)
["a.exs", "b.exs", "c.exs"]
iex> project = Rewrite.delete(project, "a.exs")
iex> Rewrite.paths(project)
["b.exs", "c.exs"]
iex> project = Rewrite.delete(project, "b.exs")
iex> Rewrite.paths(project)
["c.exs"]
iex> project = Rewrite.delete(project, "b.exs")
iex> Rewrite.paths(project)
["c.exs"]
@spec dot_formatter(t()) :: Rewrite.DotFormatter.t()

Returns the DotFormatter for the given rewrite project.

When no formatter is set, the default formatter from Rewrite.DotFormatter.default/0 is returned. A dot formatter can be set with dot_formatter/2.

Link to this function

dot_formatter(rewrite, dot_formatter)

View Source
@spec dot_formatter(t(), Rewrite.DotFormatter.t() | nil) :: t()

Sets a dot_formatter for the given rewrite project.

@spec drop(t(), [Path.t()]) :: t()

Drops the sources with the given paths from the rewrite project.

The file system files are not removed, even if the project is written. Use rm/2 or rm!/2 to delete a file and source.

If paths contains paths that are not in rewrite, they're simply ignored.

Examples

iex> {:ok, project} = Rewrite.from_sources([
...>   Source.from_string(":a", path: "a.exs"),
...>   Source.from_string(":b", path: "b.exs"),
...>   Source.from_string(":a", path: "c.exs")
...> ])
iex> project = Rewrite.drop(project, ["a.exs", "b.exs", "z.exs"])
iex> Rewrite.paths(project)
["c.exs"]
Link to this function

extension_for_file(extensions, path)

View Source
@spec extension_for_file(t() | map(), Path.t() | nil) :: {module(), opts()}

Returns the extension of the given file.

Link to this function

format(rewrite, opts \\ [])

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

Formats the given rewrite project with the given dot_formatter.

Uses the formatter from dot_formatter/2 if no formatter ist set by :dot_formatter in the options. The other options are the same as for DotFormatter.read!/2.

Link to this function

format!(rewrite, opts \\ [])

View Source
@spec format!(t(), opts()) :: t()

The same as format/2 but raises an exception in case of an error.

Link to this function

format_source(rewrite, file, opts \\ [])

View Source
@spec format_source(t(), Path.t() | Rewrite.Source.t(), keyword()) ::
  {:ok, t()} | {:error, term()}

Formats a source in a rewrite project.

Uses the formatter from dot_formatter/2 if no formatter ist set by :dot_formatter in the options. The other options are the same as for Code.format_string!/2.

Link to this function

format_source!(rewrite, file, opts \\ [])

View Source
@spec format_source!(t(), Path.t() | Rewrite.Source.t(), keyword()) :: t()

The same as format_source/3 but raises an exception in case of an error.

Link to this function

from_sources(sources, opts \\ [])

View Source
@spec from_sources([Rewrite.Source.t()], opts()) :: {:ok, t()} | {:error, term()}

Creates a %Rewrite{} from the given sources.

Returns {:ok, rewrite} for a list of regular sources.

Returns {:error, error} for sources with a missing path and/or duplicated paths.

Link to this function

from_sources!(sources, opts \\ [])

View Source
@spec from_sources!([Rewrite.Source.t()], opts()) :: t()

Same as from_sources/2, but raises a Rewrite.Error exception in case of failure.

Link to this function

has_source?(rewrite, path)

View Source
@spec has_source?(t(), Path.t()) :: boolean()

Returns true when the %Rewrite{} contains a %Source{} with the given path.

Examples

iex> {:ok, project} = Rewrite.from_sources([
...>   Source.from_string(":a", path: "a.exs")
...> ])
iex> Rewrite.has_source?(project, "a.exs")
true
iex> Rewrite.has_source?(project, "b.exs")
false
@spec issues?(t()) :: boolean()

Returns true if any source has one or more issues.

@spec map(t(), (Rewrite.Source.t() -> Rewrite.Source.t())) ::
  {:ok, t()}
  | {:error, [{:nosource | :overwrites | :nopath, Rewrite.Source.t()}]}

Invokes fun for each source in the rewrite project and updates the rewirte project with the result of fun.

Returns a {:ok, rewrite} if any update is successful.

Returns {:error, errors, rewrite} where rewrite is updated for all sources that are updated successful. The errors are the errors of update/3.

@spec map!(t(), (Rewrite.Source.t() -> Rewrite.Source.t())) :: t()

Return a rewrite project where each source is the result of invoking fun on each source of the given rewrite project.

Link to this function

move(rewrite, from, to, by \\ Rewrite)

View Source
@spec move(t(), Rewrite.Source.t() | Path.t(), Path.t(), module()) ::
  {:ok, t()} | {:error, term()}

Moves a source from one path to another.

Link to this function

move!(rewrite, from, to, by \\ Rewrite)

View Source
@spec move!(t(), Rewrite.Source.t() | Path.t(), Path.t(), module()) :: t()

Same as move/4, but raises an exception in case of failure.

@spec new(keyword()) :: t()

Creates an empty project.

Options

  • :filetypes - a list of modules implementing the behavior Rewrite.Filetype. This list is used to add the filetype to the sources of the corresponding files. The list can contain modules representing a file type or a tuple of {module(), keyword()}. Rewrite uses the keyword list from the tuple as the options argument when a file is read.

    Defaults to [Rewrite.Source, Rewrite.Source.Ex].

  • :dot_formatter - a %DotFormatter{} that is used to format sources. To get and update a dot formatter see dot_formatter/2 and to create one see Rewrite.DotFormatter.

Examples

iex> project = Rewrite.new()
iex> path = "test/fixtures/source/hello.txt"
iex> project = Rewrite.read!(project, path)
iex> project |> Rewrite.source!(path) |> Source.get(:content)
"hello\n"
iex> project |> Rewrite.source!(path) |> Source.owner()
Rewrite

iex> project = Rewrite.new(filetypes: [{Rewrite.Source, owner: MyApp}])
iex> path = "test/fixtures/source/hello.txt"
iex> project = Rewrite.read!(project, path)
iex> project |> Rewrite.source!(path) |> Source.owner()
MyApp
Link to this function

new!(inputs, opts \\ [])

View Source
@spec new!(input() | [input()], opts()) :: t()

Creates a %Rewrite{} from the given inputs.

Options

  • Accepts the same options as new/1.

  • 'exclude' - a list of paths and/or glob expressions to exclude sources from the project. The option also accepts a predicate function which is called for each source path. The exclusion takes place before the file is read.

Link to this function

new_source(rewrite, path, content, opts \\ [])

View Source
@spec new_source(t(), Path.t(), String.t(), opts()) ::
  {:ok, t()} | {:error, Rewrite.Error.t()}

Creates a new %Source{} and puts the source to the %Rewrite{} project.

The :filetypes option of the project is used to create the source. If options have been specified for the file type, the given options will be merged into those options.

Use create_source/4 if the source is not to be inserted directly into the project.

Link to this function

new_source!(rewrite, path, content, opts \\ [])

View Source
@spec new_source!(t(), Path.t(), String.t(), opts()) :: t()

Same as new_source/4, but raises a Rewrite.Error exception in case of failure.

@spec paths(t()) :: [Path.t()]

Returns a sorted list of all paths in the rewrite project.

@spec put(t(), Rewrite.Source.t()) :: {:ok, t()} | {:error, Rewrite.Error.t()}

Puts the given source to the given rewrite project.

Returns {:ok, rewrite} if successful, {:error, reason} otherwise.

Examples

iex> project = Rewrite.new()
iex> {:ok, project} = Rewrite.put(project, Source.from_string(":a", path: "a.exs"))
iex> map_size(project.sources)
1
iex> Rewrite.put(project, Source.from_string(":b"))
{:error, %Rewrite.Error{reason: :nopath}}
iex> Rewrite.put(project, Source.from_string(":a", path: "a.exs"))
{:error, %Rewrite.Error{reason: :overwrites, path: "a.exs"}}
@spec put!(t(), Rewrite.Source.t()) :: t()

Same as put/2, but raises a Rewrite.Error exception in case of failure.

Link to this function

read!(rewrite, inputs, opts \\ [])

View Source
@spec read!(t(), input() | [input()], opts()) :: t()

Reads the given input/inputs and adds the source/sources to the project when not already readed.

Options

  • :force, default: false - forces the reading of sources. With force: true updates and issues for an already existing source are deleted.

  • :exclude - a list of paths and/or glob expressions to exclude sources from the project. The option also accepts a predicate function which is called for each source path. The exclusion takes place before the file is read.

@spec rm(t(), Rewrite.Source.t() | Path.t()) ::
  {:ok, t()} | {:error, Rewrite.Error.t() | Rewrite.SourceError.t()}

Tries to delete the source file in the file system and removes the source from the rewrite project.

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

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

@spec rm!(t(), Rewrite.Source.t() | Path.t()) :: t()

Same as source/2, but raises a Rewrite.Error exception in case of failure.

@spec source(t(), Path.t()) :: {:ok, Rewrite.Source.t()} | {:error, Rewrite.Error.t()}

Returns the %Rewrite.Source{} for the given path.

Returns an :ok tuple with the found source, if not exactly one source is available an :error is returned.

See also sources/2 to get a list of sources for a given path.

@spec source!(t(), Path.t()) :: Rewrite.Source.t()

Same as source/2, but raises a Rewrite.Error exception in case of failure.

@spec sources(t()) :: [Rewrite.Source.t()]

Returns all sources sorted by path.

@spec update(t(), Rewrite.Source.t()) :: {:ok, t()} | {:error, Rewrite.Error.t()}

Updates the given source in the rewrite project.

This function will be usually used if the path for the source has not changed.

Returns {:ok, rewrite} if successful, {:error, error} otherwise.

Link to this function

update(rewrite, path, source)

View Source
@spec update(t(), Path.t(), Rewrite.Source.t() | function()) ::
  {:ok, t()} | {:error, Rewrite.Error.t() | Rewrite.UpdateError.t()}

Updates a source for the given path in the rewrite project.

If source a Rewrite.Source struct the struct is used to update the rewrite project.

If source is a function the source for the given path is passed to the function and the result is used to update the rewrite project.

Returns {:ok, rewrite} if the update was successful, {:error, error} otherwise.

Examples

iex> a = Source.Ex.from_string(":a", path: "a.exs")
iex> b = Source.Ex.from_string(":b", path: "b.exs")
iex> {:ok, project} = Rewrite.from_sources([a, b])
iex> {:ok, project} = Rewrite.update(project, "a.exs", Source.Ex.from_string(":foo", path: "a.exs"))
iex> project |> Rewrite.source!("a.exs") |> Source.get(:content)
":foo"
iex> {:ok, project} = Rewrite.update(project, "a.exs", fn s -> Source.update(s, :content, ":baz") end)
iex> project |> Rewrite.source!("a.exs") |> Source.get(:content)
":baz"
iex> {:ok, project} = Rewrite.update(project, "a.exs", fn s -> Source.update(s, :path, "c.exs") end)
iex> Rewrite.paths(project)
["b.exs", "c.exs"]
iex> Rewrite.update(project, "no.exs", Source.from_string(":foo", path: "x.exs"))
{:error, %Rewrite.Error{reason: :nosource, path: "no.exs"}}
iex> Rewrite.update(project, "c.exs", Source.from_string(":foo"))
{:error, %Rewrite.UpdateError{reason: :nopath, source: "c.exs"}}
iex> Rewrite.update(project, "c.exs", fn _ -> b end)
{:error, %Rewrite.UpdateError{reason: :overwrites, path: "b.exs", source: "c.exs"}}
Link to this function

update!(rewrite, source)

View Source
@spec update!(t(), Rewrite.Source.t()) :: t()

The same as update/2 but raises a Rewrite.Error exception in case of an error.

Link to this function

update!(rewrite, path, new)

View Source
@spec update!(t(), Path.t(), Rewrite.Source.t() | function()) :: t()

The same as update/3 but raises a Rewrite.Error exception in case of an error.

Link to this function

update_source(rewrite, path, key, fun, opts \\ [])

View Source
@spec update_source(t(), Path.t(), key(), updater(), opts()) ::
  {:ok, t()} | {:error, term()}

Updates the source for the given path and key with the given fun.

The function combines update/3 and Source.update/4 in one call.

Examples

iex> project =
...>   Rewrite.new()
...>   |> Rewrite.new_source!("test.md", "foo")
...>   |> Rewrite.update_source!("test.md", :content, fn content ->
...>     content <> "bar"
...>   end)
...>   |> Rewrite.update_source!("test.md", :content, &String.upcase/1, by: MyApp)
iex> source = Rewrite.source!(project, "test.md")
iex> source.content
"FOOBAR"
iex> source.history
[{:content, MyApp, "foobar"}, {:content, Rewrite, "foo"}]
Link to this function

update_source!(rewrite, path, key, fun, opts \\ [])

View Source
@spec update_source!(t(), Path.t(), key(), updater(), opts()) :: t()

The same as update_source/5 but raises a Rewrite.Error exception in case of an error.

@spec updated?(t()) :: boolean()

Returns true if any source in the rewrite project returns true for Source.updated?/1.

Examples

iex> {:ok, project} = Rewrite.from_sources([
...>   Source.Ex.from_string(":a", path: "a.exs"),
...>   Source.Ex.from_string(":b", path: "b.exs"),
...>   Source.Ex.from_string("c", path: "c.txt")
...> ])
iex> Rewrite.updated?(project)
false
iex> project = Rewrite.update!(project, "a.exs", fn source ->
...>   Source.update(source, :quoted, ":z")
...> end)
iex> Rewrite.updated?(project)
true
Link to this function

write(rewrite, path, force \\ nil)

View Source
@spec write(t(), Path.t() | Rewrite.Source.t(), nil | :force) ::
  {:ok, t()} | {:error, Rewrite.Error.t() | Rewrite.SourceError.t()}

Writes a source to disk.

The function expects a path or a %Source{} as first argument.

Returns {:ok, rewrite} if the file was written successful. See also Source.write/2.

If the given source is not part of the rewrite project then it is added.

Link to this function

write!(rewrite, source, force \\ nil)

View Source
@spec write!(t(), Path.t() | Rewrite.Source.t(), nil | :force) :: t()

The same as write/3 but raises an exception in case of an error.

Link to this function

write_all(rewrite, opts \\ [])

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

Writes all sources in the rewrite project to disk.

This function calls Rewrite.Source.write/1 on all sources in the rewrite project.

Returns {:ok, rewrite} if all sources are written successfully.

Returns {:error, reasons, rewrite} where rewrite is updated for all sources that are written successfully.

Options

  • exclude - a list paths to exclude form writting.
  • force, default: false - forces the writting of unchanged files.