Add typespecs to IEx public functions (#8827)

defmodule IEx do
  @moduledoc ~S"""
  Elixir's interactive shell.

  Some of the functionalities described here will not be available
  depending on your terminal. In particular, if you get a message
  saying that the smart terminal could not be run, some of the
  features described here won't work.

  ## Helpers

  IEx provides a bunch of helpers. They can be accessed by typing
  `h()` into the shell or as a documentation for the `IEx.Helpers` module.

  ## Autocomplete

  To discover a module's public functions or other modules, type the module name
  followed by a dot, then press tab to trigger autocomplete. For example:

      Enum.

  A module may export functions that are not meant to be used directly: these
  functions won't be autocompleted by IEx. IEx will not autocomplete functions
  annotated with `@doc false`, `@impl true`, or functions that aren't explicitly
  documented and where the function name is in the form of `__foo__`.

  Autocomplete may not be available on some Windows shells. You may need
  to pass the `--werl` option when starting IEx, as in `iex --werl` for it
  to work. `--werl` may be permanently enabled by setting the `IEX_WITH_WERL`
  environment variable.

  ## Shell history

  It is possible to get shell history by passing some options that enable it
  in the VM. This can be done on a per-need basis when starting IEx:

      iex --erl "-kernel shell_history enabled"

  If you would rather enable it on your system as a whole, you can use
  the `ERL_AFLAGS` environment variable and make sure that it is set
  accordingly on your terminal/shell configuration.

  On Unix-like / Bash:

      export ERL_AFLAGS="-kernel shell_history enabled"

  On Windows:

      set ERL_AFLAGS "-kernel shell_history enabled"

  On Windows 10 / PowerShell:

      $env:ERL_AFLAGS = "-kernel shell_history enabled"

  ## Expressions in IEx

  As an interactive shell, IEx evaluates expressions. This has some
  interesting consequences that are worth discussing.

  The first one is that the code is truly evaluated and not compiled.
  This means that any benchmarking done in the shell is going to have
  skewed results. So never run any profiling nor benchmarks in the shell.

  Second, IEx allows you to break an expression into many lines,
  since this is common in Elixir. For example:

      iex(1)> "ab
      ...(1)> c"
      "ab\nc"

  In the example above, the shell will be expecting more input until it
  finds the closing quote. Sometimes it is not obvious which character
  the shell is expecting, and the user may find themselves trapped in
  the state of incomplete expression with no ability to terminate it other
  than by exiting the shell.

  For such cases, there is a special break-trigger (`#iex:break`) that when
  encountered on a line by itself will force the shell to break out of any
  pending expression and return to its normal state:

      iex(1)> ["ab
      ...(1)> c"
      ...(1)> "
      ...(1)> ]
      ...(1)> #iex:break
      ** (TokenMissingError) iex:1: incomplete expression

  ## Pasting multiline expressions into IEx

  IEx evaluates its input line by line in an eagerly fashion which means
  that if at the end of a line the code seen so far is a complete expression
  IEx will evaluate it at that point. This behaviour may produce errors for
  expressions that have been formatted across multiple lines which is often
  the case for piped expressions. Consider the following expression using
  the `|>/2` operator:

      iex(1)> [1, [2], 3] |> List.flatten()
      [1, 2, 3]

  When written in multiline form and pasted into IEx this valid expression
  produces a syntax error:

      iex(1)> [1, [2], 3]
      [1, [2], 3]
      iex(2)> |> List.flatten()
      ** (SyntaxError) iex:2: syntax error before: '|>'

  As IEx evaluates its input line by line, it will first encounter
  `[1, [2], 3]`. As a list is a valid expression, IEx will evaluate
  it immediately before looking at the next input line. Only then
  will IEx attempt to evaluate the now incomplete expression
  `|> List.flatten()`, which on its own is missing its left operand.
  The evaluation thus fails with the above syntax error.

  In order to help IEx understand that an expression consists of multiple
  lines we can wrap it into parentheses:

      iex(1)> (
      ...(1)> [1, [2], 3]
      ...(1)> |> List.flatten()
      ...(1)> )
      [1, 2, 3]

  Note that this not only works with single expressions but also with
  arbitrary code blocks.

  ## The BREAK menu

  Inside IEx, hitting `Ctrl+C` will open up the `BREAK` menu. In this
  menu you can quit the shell, see process and ETS tables information
  and much more.

  ## Exiting the shell

  There are a few ways to quit the IEx shell:

    * via the `BREAK` menu (available via `Ctrl+C`) by typing `q`, pressing enter
    * by hitting `Ctrl+C`, `Ctrl+C`
    * by hitting `Ctrl+\ `

  If you are connected to remote shell, it remains alive after disconnection.

  ## Prying and breakpoints

  IEx also has the ability to set breakpoints on Elixir code and
  "pry" into running processes. This allows the developer to have
  an IEx session run inside a given function.

  `IEx.pry/0` can be used when you are able to modify the source
  code directly and recompile it:

      def my_fun(arg1, arg2) do
        require IEx; IEx.pry()
        ... implementation ...
      end

  When the code is executed, it will ask you for permission to be
  introspected.

  Alternatively, you can use `IEx.break!/4` to setup a breakpoint
  on a given module, function and arity you have no control of.
  While `IEx.break!/4` is more flexible, it does not contain
  information about imports and aliases from the source code.

  ## The User switch command

  Besides the `BREAK` menu, one can type `Ctrl+G` to get to the
  `User switch command` menu. When reached, you can type `h` to
  get more information.

  In this menu, developers are able to start new shells and
  alternate between them. Let's give it a try:

      User switch command
       --> s 'Elixir.IEx'
       --> c

  The command above will start a new shell and connect to it.
  Create a new variable called `hello` and assign some value to it:

      hello = :world

  Now, let's roll back to the first shell:

      User switch command
       --> c 1

  Now, try to access the `hello` variable again:

      hello
      ** (UndefinedFunctionError) undefined function hello/0

  The command above fails because we have switched shells.
  Since shells are isolated from each other, you can't access the
  variables defined in one shell from the other one.

  The `User switch command` can also be used to terminate an existing
  session, for example when the evaluator gets stuck in an infinite
  loop or when you are stuck typing an expression:

      User switch command
       --> i
       --> c

  The `User switch command` menu also allows developers to connect to
  remote shells using the `r` command. A topic which we will discuss next.

  ## Remote shells

  IEx allows you to connect to another node in two fashions.
  First of all, we can only connect to a shell if we give names
  both to the current shell and the shell we want to connect to.

  Let's give it a try. First start a new shell:

      $ iex --sname foo
      iex(foo@HOST)1>

  The string between the parentheses in the prompt is the name
  of your node. We can retrieve it by calling the `node/0`
  function:

      iex(foo@HOST)1> node()
      :"foo@HOST"
      iex(foo@HOST)2> Node.alive?()
      true

  For fun, let's define a simple module in this shell too:

      iex(foo@HOST)3> defmodule Hello do
      ...(foo@HOST)3>   def world, do: "it works!"
      ...(foo@HOST)3> end

  Now, let's start another shell, giving it a name as well:

      $ iex --sname bar
      iex(bar@HOST)1>

  If we try to dispatch to `Hello.world`, it won't be available
  as it was defined only in the other shell:

      iex(bar@HOST)1> Hello.world()
      ** (UndefinedFunctionError) undefined function Hello.world/0

  However, we can connect to the other shell remotely. Open up
  the `User switch command` prompt (Ctrl+G) and type:

      User switch command
       --> r 'foo@HOST' 'Elixir.IEx'
       --> c

  Now we are connected into the remote node, as the prompt shows us,
  and we can access the information and modules defined over there:

      iex(foo@HOST)1> Hello.world()
      "it works!"

  In fact, connecting to remote shells is so common that we provide
  a shortcut via the command line as well:

      $ iex --sname baz --remsh foo@HOST

  Where "remsh" means "remote shell". In general, Elixir supports:

    * remsh from an Elixir node to an Elixir node
    * remsh from a plain Erlang node to an Elixir node (through the ^G menu)
    * remsh from an Elixir node to a plain Erlang node (and get an `erl` shell there)

  Connecting an Elixir shell to a remote node without Elixir is
  **not** supported.

  ## The .iex.exs file

  When starting, IEx looks for a local `.iex.exs` file (located in the current
  working directory), then a global one (located at `~/.iex.exs`) and loads the
  first one it finds (if any). The code in the loaded `.iex.exs` file is
  evaluated in the shell's context. So, for instance, any modules that are
  loaded or variables that are bound in the `.iex.exs` file will be available in the
  shell after it has booted.

  For example, take the following `.iex.exs` file:

      # Load another ".iex.exs" file
      import_file("~/.iex.exs")

      # Import some module from lib that may not yet have been defined
      import_if_available(MyApp.Mod)

      # Print something before the shell starts
      IO.puts("hello world")

      # Bind a variable that'll be accessible in the shell
      value = 13

  Running IEx in the directory where the above `.iex.exs` file is located
  results in:

      $ iex
      Erlang/OTP 20 [...]

      hello world
      Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help)
      iex(1)> value
      13

  It is possible to load another file by supplying the `--dot-iex`
  option to IEx. See `iex --help`.

  ## Configuring the shell

  There are a number of customization options provided by IEx. Take a look
  at the docs for the `IEx.configure/1` function by typing `h IEx.configure/1`.

  Those options can be configured in your project configuration file or globally
  by calling `IEx.configure/1` from your `~/.iex.exs` file. For example:

      # .iex.exs
      IEx.configure(inspect: [limit: 3])

  Now run the shell:

      $ iex
      Erlang/OTP 20 [...]

      Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help)
      iex(1)> [1, 2, 3, 4, 5]
      [1, 2, 3, ...]

  """

  @doc """
  Configures IEx.

  The supported options are:

    * `:colors`
    * `:inspect`
    * `:width`
    * `:history_size`
    * `:default_prompt`
    * `:alive_prompt`

  They are discussed individually in the sections below.

  ## Colors

  A keyword list that encapsulates all color settings used by the
  shell. See documentation for the `IO.ANSI` module for the list of
  supported colors and attributes.

  List of supported keys in the keyword list:

    * `:enabled` - boolean value that allows for switching the coloring on and off
    * `:eval_result` - color for an expression's resulting value
    * `:eval_info` - ... various informational messages
    * `:eval_error` - ... error messages
    * `:eval_interrupt` - ... interrupt messages
    * `:stack_info` - ... the stacktrace color
    * `:blame_diff` - ... when blaming source with no match
    * `:ls_directory` - ... for directory entries (ls helper)
    * `:ls_device` - ... device entries (ls helper)

  When printing documentation, IEx will convert the Markdown
  documentation to ANSI as well. Colors for this can be configured
  via:

    * `:doc_code`        - the attributes for code blocks (cyan, bright)
    * `:doc_inline_code` - inline code (cyan)
    * `:doc_headings`    - h1 and h2 (yellow, bright)
    * `:doc_title`       - the overall heading for the output (reverse, yellow, bright)
    * `:doc_bold`        - (bright)
    * `:doc_underline`   - (underline)

  IEx will also color inspected expressions using the `:syntax_colors`
  option. Such can be disabled with:

      IEx.configure(colors: [syntax_colors: false])

  You can also configure the syntax colors, however, as desired:

      IEx.configure(colors: [syntax_colors: [atom: :red]])

  Configuration for most built-in data types are supported: `:atom`,
  `:string`, `:binary`, `:list`, `:number`, `:boolean`, `:nil`, etc.
  The default is:

      [number: :magenta, atom: :cyan, string: :green,
       boolean: :magenta, nil: :magenta]

  ## Inspect

  A keyword list containing inspect options used by the shell
  when printing results of expression evaluation. Default to
  pretty formatting with a limit of 50 entries.

  To show all entries, configure the limit to `:infinity`:

      IEx.configure(inspect: [limit: :infinity])

  See `Inspect.Opts` for the full list of options.

  ## Width

  An integer indicating the maximum number of columns to use in output.
  The default value is 80 columns. The actual output width is the minimum
  of this number and result of `:io.columns`. This way you can configure IEx
  to be your largest screen size and it should always take up the full width
  of your current terminal screen.

  ## History size

  Number of expressions and their results to keep in the history.
  The value is an integer. When it is negative, the history is unlimited.

  ## Prompt

  This is an option determining the prompt displayed to the user
  when awaiting input.

  The value is a keyword list with two possible keys representing prompt types:

    * `:default_prompt` - used when `Node.alive?/0` returns `false`
    * `:alive_prompt`   - used when `Node.alive?/0` returns `true`

  The following values in the prompt string will be replaced appropriately:

    * `%counter` - the index of the history
    * `%prefix`  - a prefix given by `IEx.Server`
    * `%node`    - the name of the local node

  """
  @spec configure(keyword()) :: :ok
  def configure(options) do
    IEx.Config.configure(options)
  end

  @doc """
  Returns IEx configuration.
  """
  @spec configuration() :: keyword()
  def configuration do
    IEx.Config.configuration()
  end

  @doc """
  Registers a function to be invoked after the IEx process is spawned.
  """
  @spec after_spawn(fun()) :: :ok
  def after_spawn(fun) when is_function(fun) do
    IEx.Config.after_spawn(fun)
  end

  @doc """
  Returns registered `after_spawn` callbacks.
  """
  @spec after_spawn() :: [fun()]
  def after_spawn do
    IEx.Config.after_spawn()
  end

  @doc """
  Returns `true` if IEx was started, `false` otherwise.
  """
  @spec started?() :: boolean()
  def started? do
    IEx.Config.started?()
  end

  @doc """
  Returns `string` escaped using the specified `color`.

  ANSI escapes in `string` are not processed in any way.
  """
  @spec color(atom(), String.t()) :: String.t()
  def color(color, string) do
    case IEx.Config.color(color) do
      nil ->
        string

      ansi ->
        [ansi | string] |> IO.ANSI.format(true) |> IO.iodata_to_binary()
    end
  end

  @doc """
  Returns the IEx width for printing.

  Used by helpers and it has a default maximum cap of 80 chars.
  """
  @spec width() :: pos_integer()
  def width do
    IEx.Config.width()
  end

  @doc """
  Returns the options used for inspecting.
  """
  @spec inspect_opts() :: keyword()
  def inspect_opts do
    IEx.Config.inspect_opts()
  end

  @doc """
  Pries into the process environment.

  This is useful for debugging a particular chunk of code
  when executed by a particular process. The process becomes
  the evaluator of IEx commands and is temporarily changed to
  have a custom group leader. Those values are reverted by
  calling `IEx.Helpers.respawn/0`, which starts a new IEx shell,
  freeing up the pried one.

  When a process is pried, all code runs inside IEx and has
  access to all imports and aliases from the original code.
  However, the code is evaluated and therefore cannot access
  private functions of the module being pried. Module functions
  still need to be accessed via `Mod.fun(args)`.

  Alternatively, you can use `IEx.break!/4` to setup a breakpoint
  on a given module, function and arity you have no control of.
  While `IEx.break!/4` is more flexible,  it does not contain
  information about imports and aliases from the source code.

  ## Examples

  Let's suppose you want to investigate what is happening
  with some particular function. By invoking `IEx.pry/0` from
  the function, IEx will allow you to access its binding
  (variables), verify its lexical information and access
  the process information. Let's see an example:

      import Enum, only: [map: 2]

      defmodule Adder do
        def add(a, b) do
          c = a + b
          require IEx; IEx.pry()
        end
      end

  When invoking `Adder.add(1, 2)`, you will receive a message in
  your shell to pry the given environment. By allowing it,
  the shell will be reset and you gain access to all variables
  and the lexical scope from above:

      pry(1)> map([a, b, c], &IO.inspect(&1))
      1
      2
      3

  Keep in mind that `IEx.pry/0` runs in the caller process,
  blocking the caller during the evaluation cycle. The caller
  process can be freed by calling `respawn/0`, which starts a
  new IEx evaluation cycle, letting this one go:

      pry(2)> respawn()
      true

      Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help)

  Setting variables or importing modules in IEx does not
  affect the caller's environment. However, sending and
  receiving messages will change the process state.

  ## Pry and macros

  When setting up Pry inside a code defined by macros, such as:

      defmacro __using__(_) do
        quote do
          def add(a, b) do
            c = a + b
            require IEx; IEx.pry()
          end
        end
      end

  The variables defined inside `quote` won't be available during
  prying due to the hygiene mechanism in quoted expressions. The
  hygiene mechanism changes the variable names in quoted expressions
  so they don't collide with variables defined by the users of the
  macros. Therefore the original names are not available.

  ## Pry and `mix test`

  To use `IEx.pry/0` during tests, you need to run `mix` inside
  the `iex` command and pass the `--trace` to `mix test` to avoid running
  into timeouts:

      iex -S mix test --trace
      iex -S mix test path/to/file:line --trace

  """
  defmacro pry() do
    quote do
      IEx.Pry.pry(binding(), __ENV__)
    end
  end

  @doc """
  Macro-based shortcut for `IEx.break!/4`.
  """
  @doc since: "1.5.0"
  defmacro break!(ast, stops \\ 1) do
    quote do
      IEx.__break__!(unquote(Macro.escape(ast)), unquote(Macro.escape(stops)), __ENV__)
    end
  end

  def __break__!({:/, _, [call, arity]} = ast, stops, env) when is_integer(arity) do
    with {module, fun, []} <- Macro.decompose_call(call),
         module when is_atom(module) <- Macro.expand(module, env) do
      IEx.Pry.break!(module, fun, arity, stops)
    else
      _ ->
        raise_unknown_break_ast!(ast)
    end
  end

  def __break__!({{:., _, [module, fun]}, _, args} = ast, stops, env) do
    __break__!(ast, module, fun, args, true, stops, env)
  end

  def __break__!({:when, _, [{{:., _, [module, fun]}, _, args}, guards]} = ast, stops, env) do
    __break__!(ast, module, fun, args, guards, stops, env)
  end

  def __break__!(ast, _stops) do
    raise_unknown_break_ast!(ast)
  end

  defp __break__!(ast, module, fun, args, guards, stops, env) do
    module = Macro.expand(module, env)

    unless is_atom(module) do
      raise_unknown_break_ast!(ast)
    end

    IEx.Pry.break!(module, fun, args, guards, env, stops)
  end

  defp raise_unknown_break_ast!(ast) do
    raise ArgumentError, """
    unknown expression to break on, expected one of:

      * Mod.fun/arity, such as: URI.parse/1
      * Mod.fun(arg1, arg2, ...), such as: URI.parse(_)
      * Mod.fun(arg1, arg2, ...) when guard, such as: URI.parse(var) when is_binary(var)

    Got #{Macro.to_string(ast)}
    """
  end

  @doc """
  Sets up a breakpoint in `module`, `function` and `arity` with
  the given number of `stops`.

  This function will instrument the given module and load a new
  version in memory with breakpoints at the given function and
  arity. If the module is recompiled, all breakpoints are lost.

  When a breakpoint is reached, IEx will ask if you want to `pry`
  the given function and arity. In other words, this works similar
  to `IEx.pry/0` as the running process becomes the evaluator of
  IEx commands and is temporarily changed to have a custom group
  leader. However, differently from `IEx.pry/0`, aliases and imports
  from the source code won't be available in the shell.

  IEx helpers includes many conveniences related to breakpoints.
  Below they are listed with the full module, such as `IEx.Helpers.breaks/0`,
  but remember it can be called directly as `breaks()` inside IEx.
  They are:

    * `IEx.Helpers.break!/2` - sets up a breakpoint for a given `Mod.fun/arity`
    * `IEx.Helpers.break!/4` - sets up a breakpoint for the given module, function, arity
    * `IEx.Helpers.breaks/0` - prints all breakpoints and their IDs
    * `IEx.Helpers.continue/0` - continues until the next breakpoint in the same shell
    * `IEx.Helpers.open/0` - opens editor on the current breakpoint
    * `IEx.Helpers.remove_breaks/0` - removes all breakpoints in all modules
    * `IEx.Helpers.remove_breaks/1` - removes all breakpoints in a given module
    * `IEx.Helpers.reset_break/1` - sets the number of stops on the given ID to zero
    * `IEx.Helpers.reset_break/3` - sets the number of stops on the given module, function, arity to zero
    * `IEx.Helpers.respawn/0` - starts a new shell (breakpoints will ask for permission once more)
    * `IEx.Helpers.whereami/1` - shows the current location

  By default, the number of stops in a breakpoint is 1. Any follow-up
  call won't stop the code execution unless another breakpoint is set.

  Alternatively, the number of stops can be increased by passing the `stops`
  argument. `IEx.Helpers.reset_break/1` and `IEx.Helpers.reset_break/3`
  can be used to reset the number back to zero. Note the module remains
  "instrumented" even after all stops on all breakpoints are consumed.
  You can remove the instrumentation in a given module by calling
  `IEx.Helpers.remove_breaks/1` and on all modules by calling
  `IEx.Helpers.remove_breaks/0`.

  To exit a breakpoint, the developer can either invoke `continue()`,
  which will block the shell until the next breakpoint is found or
  the process terminates, or invoke `respawn()`, which starts a new IEx
  shell, freeing up the pried one.

  ## Examples

  The examples below will use `break!`, assuming that you are setting
  a breakpoint directly from your IEx shell. But you can set up a break
  from anywhere by using the fully qualified name `IEx.break!`.

  The following sets up a breakpoint on `URI.decode_query/2`:

      break! URI, :decode_query, 2

  This call will setup a breakpoint that stops once.
  To set a breakpoint that will stop 10 times:

      break! URI, :decode_query, 2, 10

  `IEx.break!/2` is a convenience macro that allows breakpoints
  to be given in the `Mod.fun/arity` format:

      break! URI.decode_query/2

  Or to set a breakpoint that will stop 10 times:

      break! URI.decode_query/2, 10

  This function returns the breakpoint ID and will raise if there
  is an error setting up the breakpoint.

  ## Patterns and guards

  `IEx.break!/2` allows patterns to be given, triggering the
  breakpoint only in some occasions. For example, to trigger
  the breakpoint only when the first argument is the "foo=bar"
  string:

      break! URI.decode_query("foo=bar", _)

  Or to trigger it whenever the second argument is a map with
  more than one element:

      break! URI.decode_query(_, map) when map_size(map) > 0

  Only a single break point can be set per function. So if you call
  `IEx.break!` multiple times with different patterns, only the last
  pattern is kept.

  Notice that, while patterns may be given to macros, remember that
  macros receive ASTs as arguments, and not values. For example, if
  you try to break on a macro with the following pattern:

      break! MyModule.some_macro(pid) when pid == self()

  This breakpoint will never be reached, because a macro never receives
  a PID. Even if you call the macro as `MyModule.some_macro(self())`,
  the macro will receive the AST representing the `self()` call, and not
  the PID itself.

  ## Breaks and `mix test`

  To use `IEx.break!/4` during tests, you need to run `mix` inside
  the `iex` command and pass the `--trace` to `mix test` to avoid running
  into timeouts:

      iex -S mix test --trace
      iex -S mix test path/to/file:line --trace

  """
  @doc since: "1.5.0"
  def break!(module, function, arity, stops \\ 1) when is_integer(arity) do
    IEx.Pry.break!(module, function, arity, stops)
  end

  ## Callbacks

  # This is a callback invoked by Erlang shell utilities
  # when someone presses Ctrl+G and adds 's Elixir.IEx'.
  @doc false
  def start(opts \\ [], mfa \\ {IEx, :dont_display_result, []}) do
    spawn(fn ->
      case :init.notify_when_started(self()) do
        :started -> :ok
        _ -> :init.wait_until_started()
      end

      :ok = start_iex()
      :ok = set_expand_fun()
      :ok = run_after_spawn()
      IEx.Server.run_from_shell(opts, mfa)
    end)
  end

  @doc false
  def dont_display_result, do: :"do not show this result in output"

  ## Helpers

  defp start_iex() do
    {:ok, _} = Application.ensure_all_started(:iex)
    :ok
  end

  defp set_expand_fun do
    gl = Process.group_leader()

    expand_fun =
      if node(gl) != node() do
        IEx.Autocomplete.remsh(node())
      else
        &IEx.Autocomplete.expand/1
      end

    # expand_fun is not supported by a shell variant
    # on Windows, so we do two IO calls, not caring
    # about the result of the expand_fun one.
    _ = :io.setopts(gl, expand_fun: expand_fun)
    :io.setopts(gl, binary: true, encoding: :unicode)
  end

  defp run_after_spawn do
    _ = for fun <- Enum.reverse(after_spawn()), do: fun.()
    :ok
  end
end

defmodule IEx do
  @moduledoc ~S"""
  Elixir's interactive shell.

  Some of the functionalities described here will not be available
  depending on your terminal. In particular, if you get a message
  saying that the smart terminal could not be run, some of the
  features described here won't work.

  ## Helpers

  IEx provides a bunch of helpers. They can be accessed by typing
  `h()` into the shell or as a documentation for the `IEx.Helpers` module.

  ## Autocomplete

  To discover a module's public functions or other modules, type the module name
  followed by a dot, then press tab to trigger autocomplete. For example:

      Enum.

  A module may export functions that are not meant to be used directly: these
  functions won't be autocompleted by IEx. IEx will not autocomplete functions
  annotated with `@doc false`, `@impl true`, or functions that aren't explicitly
  documented and where the function name is in the form of `__foo__`.

  Autocomplete may not be available on some Windows shells. You may need
  to pass the `--werl` option when starting IEx, as in `iex --werl` for it
  to work. `--werl` may be permanently enabled by setting the `IEX_WITH_WERL`
  environment variable.

  ## Shell history

  It is possible to get shell history by passing some options that enable it
  in the VM. This can be done on a per-need basis when starting IEx:

      iex --erl "-kernel shell_history enabled"

  If you would rather enable it on your system as a whole, you can use
  the `ERL_AFLAGS` environment variable and make sure that it is set
  accordingly on your terminal/shell configuration.

  On Unix-like / Bash:

      export ERL_AFLAGS="-kernel shell_history enabled"

  On Windows:

      set ERL_AFLAGS "-kernel shell_history enabled"

  On Windows 10 / PowerShell:

      $env:ERL_AFLAGS = "-kernel shell_history enabled"

  ## Expressions in IEx

  As an interactive shell, IEx evaluates expressions. This has some
  interesting consequences that are worth discussing.

  The first one is that the code is truly evaluated and not compiled.
  This means that any benchmarking done in the shell is going to have
  skewed results. So never run any profiling nor benchmarks in the shell.

  Second, IEx allows you to break an expression into many lines,
  since this is common in Elixir. For example:

      iex(1)> "ab
      ...(1)> c"
      "ab\nc"

  In the example above, the shell will be expecting more input until it
  finds the closing quote. Sometimes it is not obvious which character
  the shell is expecting, and the user may find themselves trapped in
  the state of incomplete expression with no ability to terminate it other
  than by exiting the shell.

  For such cases, there is a special break-trigger (`#iex:break`) that when
  encountered on a line by itself will force the shell to break out of any
  pending expression and return to its normal state:

      iex(1)> ["ab
      ...(1)> c"
      ...(1)> "
      ...(1)> ]
      ...(1)> #iex:break
      ** (TokenMissingError) iex:1: incomplete expression

  ## Pasting multiline expressions into IEx

  IEx evaluates its input line by line in an eagerly fashion which means
  that if at the end of a line the code seen so far is a complete expression
  IEx will evaluate it at that point. This behaviour may produce errors for
  expressions that have been formatted across multiple lines which is often
  the case for piped expressions. Consider the following expression using
  the `|>/2` operator:

      iex(1)> [1, [2], 3] |> List.flatten()
      [1, 2, 3]

  When written in multiline form and pasted into IEx this valid expression
  produces a syntax error:

      iex(1)> [1, [2], 3]
      [1, [2], 3]
      iex(2)> |> List.flatten()
      ** (SyntaxError) iex:2: syntax error before: '|>'

  As IEx evaluates its input line by line, it will first encounter
  `[1, [2], 3]`. As a list is a valid expression, IEx will evaluate
  it immediately before looking at the next input line. Only then
  will IEx attempt to evaluate the now incomplete expression
  `|> List.flatten()`, which on its own is missing its left operand.
  The evaluation thus fails with the above syntax error.

  In order to help IEx understand that an expression consists of multiple
  lines we can wrap it into parentheses:

      iex(1)> (
      ...(1)> [1, [2], 3]
      ...(1)> |> List.flatten()
      ...(1)> )
      [1, 2, 3]

  Note that this not only works with single expressions but also with
  arbitrary code blocks.

  ## The BREAK menu

  Inside IEx, hitting `Ctrl+C` will open up the `BREAK` menu. In this
  menu you can quit the shell, see process and ETS tables information
  and much more.

  ## Exiting the shell

  There are a few ways to quit the IEx shell:

    * via the `BREAK` menu (available via `Ctrl+C`) by typing `q`, pressing enter
    * by hitting `Ctrl+C`, `Ctrl+C`
    * by hitting `Ctrl+\ `

  If you are connected to remote shell, it remains alive after disconnection.

  ## Prying and breakpoints

  IEx also has the ability to set breakpoints on Elixir code and
  "pry" into running processes. This allows the developer to have
  an IEx session run inside a given function.

  `IEx.pry/0` can be used when you are able to modify the source
  code directly and recompile it:

      def my_fun(arg1, arg2) do
        require IEx; IEx.pry()
        ... implementation ...
      end

  When the code is executed, it will ask you for permission to be
  introspected.

  Alternatively, you can use `IEx.break!/4` to setup a breakpoint
  on a given module, function and arity you have no control of.
  While `IEx.break!/4` is more flexible, it does not contain
  information about imports and aliases from the source code.

  ## The User switch command

  Besides the `BREAK` menu, one can type `Ctrl+G` to get to the
  `User switch command` menu. When reached, you can type `h` to
  get more information.

  In this menu, developers are able to start new shells and
  alternate between them. Let's give it a try:

      User switch command
       --> s 'Elixir.IEx'
       --> c

  The command above will start a new shell and connect to it.
  Create a new variable called `hello` and assign some value to it:

      hello = :world

  Now, let's roll back to the first shell:

      User switch command
       --> c 1

  Now, try to access the `hello` variable again:

      hello
      ** (UndefinedFunctionError) undefined function hello/0

  The command above fails because we have switched shells.
  Since shells are isolated from each other, you can't access the
  variables defined in one shell from the other one.

  The `User switch command` can also be used to terminate an existing
  session, for example when the evaluator gets stuck in an infinite
  loop or when you are stuck typing an expression:

      User switch command
       --> i
       --> c

  The `User switch command` menu also allows developers to connect to
  remote shells using the `r` command. A topic which we will discuss next.

  ## Remote shells

  IEx allows you to connect to another node in two fashions.
  First of all, we can only connect to a shell if we give names
  both to the current shell and the shell we want to connect to.

  Let's give it a try. First start a new shell:

      $ iex --sname foo
      iex(foo@HOST)1>

  The string between the parentheses in the prompt is the name
  of your node. We can retrieve it by calling the `node/0`
  function:

      iex(foo@HOST)1> node()
      :"foo@HOST"
      iex(foo@HOST)2> Node.alive?()
      true

  For fun, let's define a simple module in this shell too:

      iex(foo@HOST)3> defmodule Hello do
      ...(foo@HOST)3>   def world, do: "it works!"
      ...(foo@HOST)3> end

  Now, let's start another shell, giving it a name as well:

      $ iex --sname bar
      iex(bar@HOST)1>

  If we try to dispatch to `Hello.world`, it won't be available
  as it was defined only in the other shell:

      iex(bar@HOST)1> Hello.world()
      ** (UndefinedFunctionError) undefined function Hello.world/0

  However, we can connect to the other shell remotely. Open up
  the `User switch command` prompt (Ctrl+G) and type:

      User switch command
       --> r 'foo@HOST' 'Elixir.IEx'
       --> c

  Now we are connected into the remote node, as the prompt shows us,
  and we can access the information and modules defined over there:

      iex(foo@HOST)1> Hello.world()
      "it works!"

  In fact, connecting to remote shells is so common that we provide
  a shortcut via the command line as well:

      $ iex --sname baz --remsh foo@HOST

  Where "remsh" means "remote shell". In general, Elixir supports:

    * remsh from an Elixir node to an Elixir node
    * remsh from a plain Erlang node to an Elixir node (through the ^G menu)
    * remsh from an Elixir node to a plain Erlang node (and get an `erl` shell there)

  Connecting an Elixir shell to a remote node without Elixir is
  **not** supported.

  ## The .iex.exs file

  When starting, IEx looks for a local `.iex.exs` file (located in the current
  working directory), then a global one (located at `~/.iex.exs`) and loads the
  first one it finds (if any). The code in the loaded `.iex.exs` file is
  evaluated in the shell's context. So, for instance, any modules that are
  loaded or variables that are bound in the `.iex.exs` file will be available in the
  shell after it has booted.

  For example, take the following `.iex.exs` file:

      # Load another ".iex.exs" file
      import_file("~/.iex.exs")

      # Import some module from lib that may not yet have been defined
      import_if_available(MyApp.Mod)

      # Print something before the shell starts
      IO.puts("hello world")

      # Bind a variable that'll be accessible in the shell
      value = 13

  Running IEx in the directory where the above `.iex.exs` file is located
  results in:

      $ iex
      Erlang/OTP 20 [...]

      hello world
      Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help)
      iex(1)> value
      13

  It is possible to load another file by supplying the `--dot-iex`
  option to IEx. See `iex --help`.

  ## Configuring the shell

  There are a number of customization options provided by IEx. Take a look
  at the docs for the `IEx.configure/1` function by typing `h IEx.configure/1`.

  Those options can be configured in your project configuration file or globally
  by calling `IEx.configure/1` from your `~/.iex.exs` file. For example:

      # .iex.exs
      IEx.configure(inspect: [limit: 3])

  Now run the shell:

      $ iex
      Erlang/OTP 20 [...]

      Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help)
      iex(1)> [1, 2, 3, 4, 5]
      [1, 2, 3, ...]

  """

  @doc """
  Configures IEx.

  The supported options are:

    * `:colors`
    * `:inspect`
    * `:width`
    * `:history_size`
    * `:default_prompt`
    * `:alive_prompt`

  They are discussed individually in the sections below.

  ## Colors

  A keyword list that encapsulates all color settings used by the
  shell. See documentation for the `IO.ANSI` module for the list of
  supported colors and attributes.

  List of supported keys in the keyword list:

    * `:enabled` - boolean value that allows for switching the coloring on and off
    * `:eval_result` - color for an expression's resulting value
    * `:eval_info` - ... various informational messages
    * `:eval_error` - ... error messages
    * `:eval_interrupt` - ... interrupt messages
    * `:stack_info` - ... the stacktrace color
    * `:blame_diff` - ... when blaming source with no match
    * `:ls_directory` - ... for directory entries (ls helper)
    * `:ls_device` - ... device entries (ls helper)

  When printing documentation, IEx will convert the Markdown
  documentation to ANSI as well. Colors for this can be configured
  via:

    * `:doc_code`        - the attributes for code blocks (cyan, bright)
    * `:doc_inline_code` - inline code (cyan)
    * `:doc_headings`    - h1 and h2 (yellow, bright)
    * `:doc_title`       - the overall heading for the output (reverse, yellow, bright)
    * `:doc_bold`        - (bright)
    * `:doc_underline`   - (underline)

  IEx will also color inspected expressions using the `:syntax_colors`
  option. Such can be disabled with:

      IEx.configure(colors: [syntax_colors: false])

  You can also configure the syntax colors, however, as desired:

      IEx.configure(colors: [syntax_colors: [atom: :red]])

  Configuration for most built-in data types are supported: `:atom`,
  `:string`, `:binary`, `:list`, `:number`, `:boolean`, `:nil`, etc.
  The default is:

      [number: :magenta, atom: :cyan, string: :green,
       boolean: :magenta, nil: :magenta]

  ## Inspect

  A keyword list containing inspect options used by the shell
  when printing results of expression evaluation. Default to
  pretty formatting with a limit of 50 entries.

  To show all entries, configure the limit to `:infinity`:

      IEx.configure(inspect: [limit: :infinity])

  See `Inspect.Opts` for the full list of options.

  ## Width

  An integer indicating the maximum number of columns to use in output.
  The default value is 80 columns. The actual output width is the minimum
  of this number and result of `:io.columns`. This way you can configure IEx
  to be your largest screen size and it should always take up the full width
  of your current terminal screen.

  ## History size

  Number of expressions and their results to keep in the history.
  The value is an integer. When it is negative, the history is unlimited.

  ## Prompt

  This is an option determining the prompt displayed to the user
  when awaiting input.

  The value is a keyword list with two possible keys representing prompt types:

    * `:default_prompt` - used when `Node.alive?/0` returns `false`
    * `:alive_prompt`   - used when `Node.alive?/0` returns `true`

  The following values in the prompt string will be replaced appropriately:

    * `%counter` - the index of the history
    * `%prefix`  - a prefix given by `IEx.Server`
    * `%node`    - the name of the local node

  """
  def configure(options) do
    IEx.Config.configure(options)
  end

  @doc """
  Returns IEx configuration.
  """
  def configuration do
    IEx.Config.configuration()
  end

  @doc """
  Registers a function to be invoked after the IEx process is spawned.
  """
  def after_spawn(fun) when is_function(fun) do
    IEx.Config.after_spawn(fun)
  end

  @doc """
  Returns registered `after_spawn` callbacks.
  """
  def after_spawn do
    IEx.Config.after_spawn()
  end

  @doc """
  Returns `true` if IEx was started.
  """
  def started? do
    IEx.Config.started?()
  end

  @doc """
  Returns `string` escaped using the specified `color`.

  ANSI escapes in `string` are not processed in any way.
  """
  def color(color, string) do
    case IEx.Config.color(color) do
      nil ->
        string

      ansi ->
        [ansi | string] |> IO.ANSI.format(true) |> IO.iodata_to_binary()
    end
  end

  @doc """
  Gets the IEx width for printing.

  Used by helpers and it has a default maximum cap of 80 chars.
  """
  def width do
    IEx.Config.width()
  end

  @doc """
  Gets the options used for inspecting.
  """
  def inspect_opts do
    IEx.Config.inspect_opts()
  end

  @doc """
  Pries into the process environment.

  This is useful for debugging a particular chunk of code
  when executed by a particular process. The process becomes
  the evaluator of IEx commands and is temporarily changed to
  have a custom group leader. Those values are reverted by
  calling `IEx.Helpers.respawn/0`, which starts a new IEx shell,
  freeing up the pried one.

  When a process is pried, all code runs inside IEx and has
  access to all imports and aliases from the original code.
  However, the code is evaluated and therefore cannot access
  private functions of the module being pried. Module functions
  still need to be accessed via `Mod.fun(args)`.

  Alternatively, you can use `IEx.break!/4` to setup a breakpoint
  on a given module, function and arity you have no control of.
  While `IEx.break!/4` is more flexible,  it does not contain
  information about imports and aliases from the source code.

  ## Examples

  Let's suppose you want to investigate what is happening
  with some particular function. By invoking `IEx.pry/0` from
  the function, IEx will allow you to access its binding
  (variables), verify its lexical information and access
  the process information. Let's see an example:

      import Enum, only: [map: 2]

      defmodule Adder do
        def add(a, b) do
          c = a + b
          require IEx; IEx.pry()
        end
      end

  When invoking `Adder.add(1, 2)`, you will receive a message in
  your shell to pry the given environment. By allowing it,
  the shell will be reset and you gain access to all variables
  and the lexical scope from above:

      pry(1)> map([a, b, c], &IO.inspect(&1))
      1
      2
      3

  Keep in mind that `IEx.pry/0` runs in the caller process,
  blocking the caller during the evaluation cycle. The caller
  process can be freed by calling `respawn/0`, which starts a
  new IEx evaluation cycle, letting this one go:

      pry(2)> respawn()
      true

      Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help)

  Setting variables or importing modules in IEx does not
  affect the caller's environment. However, sending and
  receiving messages will change the process state.

  ## Pry and macros

  When setting up Pry inside a code defined by macros, such as:

      defmacro __using__(_) do
        quote do
          def add(a, b) do
            c = a + b
            require IEx; IEx.pry()
          end
        end
      end

  The variables defined inside `quote` won't be available during
  prying due to the hygiene mechanism in quoted expressions. The
  hygiene mechanism changes the variable names in quoted expressions
  so they don't collide with variables defined by the users of the
  macros. Therefore the original names are not available.

  ## Pry and `mix test`

  To use `IEx.pry/0` during tests, you need to run `mix` inside
  the `iex` command and pass the `--trace` to `mix test` to avoid running
  into timeouts:

      iex -S mix test --trace
      iex -S mix test path/to/file:line --trace

  """
  defmacro pry() do
    quote do
      IEx.Pry.pry(binding(), __ENV__)
    end
  end

  @doc """
  Macro-based shortcut for `IEx.break!/4`.
  """
  @doc since: "1.5.0"
  defmacro break!(ast, stops \\ 1) do
    quote do
      IEx.__break__!(unquote(Macro.escape(ast)), unquote(Macro.escape(stops)), __ENV__)
    end
  end

  def __break__!({:/, _, [call, arity]} = ast, stops, env) when is_integer(arity) do
    with {module, fun, []} <- Macro.decompose_call(call),
         module when is_atom(module) <- Macro.expand(module, env) do
      IEx.Pry.break!(module, fun, arity, stops)
    else
      _ ->
        raise_unknown_break_ast!(ast)
    end
  end

  def __break__!({{:., _, [module, fun]}, _, args} = ast, stops, env) do
    __break__!(ast, module, fun, args, true, stops, env)
  end

  def __break__!({:when, _, [{{:., _, [module, fun]}, _, args}, guards]} = ast, stops, env) do
    __break__!(ast, module, fun, args, guards, stops, env)
  end

  def __break__!(ast, _stops) do
    raise_unknown_break_ast!(ast)
  end

  defp __break__!(ast, module, fun, args, guards, stops, env) do
    module = Macro.expand(module, env)

    unless is_atom(module) do
      raise_unknown_break_ast!(ast)
    end

    IEx.Pry.break!(module, fun, args, guards, env, stops)
  end

  defp raise_unknown_break_ast!(ast) do
    raise ArgumentError, """
    unknown expression to break on, expected one of:

      * Mod.fun/arity, such as: URI.parse/1
      * Mod.fun(arg1, arg2, ...), such as: URI.parse(_)
      * Mod.fun(arg1, arg2, ...) when guard, such as: URI.parse(var) when is_binary(var)

    Got #{Macro.to_string(ast)}
    """
  end

  @doc """
  Sets up a breakpoint in `module`, `function` and `arity` with
  the given number of `stops`.

  This function will instrument the given module and load a new
  version in memory with breakpoints at the given function and
  arity. If the module is recompiled, all breakpoints are lost.

  When a breakpoint is reached, IEx will ask if you want to `pry`
  the given function and arity. In other words, this works similar
  to `IEx.pry/0` as the running process becomes the evaluator of
  IEx commands and is temporarily changed to have a custom group
  leader. However, differently from `IEx.pry/0`, aliases and imports
  from the source code won't be available in the shell.

  IEx helpers includes many conveniences related to breakpoints.
  Below they are listed with the full module, such as `IEx.Helpers.breaks/0`,
  but remember it can be called directly as `breaks()` inside IEx.
  They are:

    * `IEx.Helpers.break!/2` - sets up a breakpoint for a given `Mod.fun/arity`
    * `IEx.Helpers.break!/4` - sets up a breakpoint for the given module, function, arity
    * `IEx.Helpers.breaks/0` - prints all breakpoints and their IDs
    * `IEx.Helpers.continue/0` - continues until the next breakpoint in the same shell
    * `IEx.Helpers.open/0` - opens editor on the current breakpoint
    * `IEx.Helpers.remove_breaks/0` - removes all breakpoints in all modules
    * `IEx.Helpers.remove_breaks/1` - removes all breakpoints in a given module
    * `IEx.Helpers.reset_break/1` - sets the number of stops on the given ID to zero
    * `IEx.Helpers.reset_break/3` - sets the number of stops on the given module, function, arity to zero
    * `IEx.Helpers.respawn/0` - starts a new shell (breakpoints will ask for permission once more)
    * `IEx.Helpers.whereami/1` - shows the current location

  By default, the number of stops in a breakpoint is 1. Any follow-up
  call won't stop the code execution unless another breakpoint is set.

  Alternatively, the number of stops can be increased by passing the `stops`
  argument. `IEx.Helpers.reset_break/1` and `IEx.Helpers.reset_break/3`
  can be used to reset the number back to zero. Note the module remains
  "instrumented" even after all stops on all breakpoints are consumed.
  You can remove the instrumentation in a given module by calling
  `IEx.Helpers.remove_breaks/1` and on all modules by calling
  `IEx.Helpers.remove_breaks/0`.

  To exit a breakpoint, the developer can either invoke `continue()`,
  which will block the shell until the next breakpoint is found or
  the process terminates, or invoke `respawn()`, which starts a new IEx
  shell, freeing up the pried one.

  ## Examples

  The examples below will use `break!`, assuming that you are setting
  a breakpoint directly from your IEx shell. But you can set up a break
  from anywhere by using the fully qualified name `IEx.break!`.

  The following sets up a breakpoint on `URI.decode_query/2`:

      break! URI, :decode_query, 2

  This call will setup a breakpoint that stops once.
  To set a breakpoint that will stop 10 times:

      break! URI, :decode_query, 2, 10

  `IEx.break!/2` is a convenience macro that allows breakpoints
  to be given in the `Mod.fun/arity` format:

      break! URI.decode_query/2

  Or to set a breakpoint that will stop 10 times:

      break! URI.decode_query/2, 10

  This function returns the breakpoint ID and will raise if there
  is an error setting up the breakpoint.

  ## Patterns and guards

  `IEx.break!/2` allows patterns to be given, triggering the
  breakpoint only in some occasions. For example, to trigger
  the breakpoint only when the first argument is the "foo=bar"
  string:

      break! URI.decode_query("foo=bar", _)

  Or to trigger it whenever the second argument is a map with
  more than one element:

      break! URI.decode_query(_, map) when map_size(map) > 0

  Only a single break point can be set per function. So if you call
  `IEx.break!` multiple times with different patterns, only the last
  pattern is kept.

  Notice that, while patterns may be given to macros, remember that
  macros receive ASTs as arguments, and not values. For example, if
  you try to break on a macro with the following pattern:

      break! MyModule.some_macro(pid) when pid == self()

  This breakpoint will never be reached, because a macro never receives
  a PID. Even if you call the macro as `MyModule.some_macro(self())`,
  the macro will receive the AST representing the `self()` call, and not
  the PID itself.

  ## Breaks and `mix test`

  To use `IEx.break!/4` during tests, you need to run `mix` inside
  the `iex` command and pass the `--trace` to `mix test` to avoid running
  into timeouts:

      iex -S mix test --trace
      iex -S mix test path/to/file:line --trace

  """
  @doc since: "1.5.0"
  def break!(module, function, arity, stops \\ 1) when is_integer(arity) do
    IEx.Pry.break!(module, function, arity, stops)
  end

  ## Callbacks

  # This is a callback invoked by Erlang shell utilities
  # when someone presses Ctrl+G and adds 's Elixir.IEx'.
  @doc false
  def start(opts \\ [], mfa \\ {IEx, :dont_display_result, []}) do
    spawn(fn ->
      case :init.notify_when_started(self()) do
        :started -> :ok
        _ -> :init.wait_until_started()
      end

      :ok = start_iex()
      :ok = set_expand_fun()
      :ok = run_after_spawn()
      IEx.Server.run_from_shell(opts, mfa)
    end)
  end

  @doc false
  def dont_display_result, do: :"do not show this result in output"

  ## Helpers

  defp start_iex() do
    {:ok, _} = Application.ensure_all_started(:iex)
    :ok
  end

  defp set_expand_fun do
    gl = Process.group_leader()

    expand_fun =
      if node(gl) != node() do
        IEx.Autocomplete.remsh(node())
      else
        &IEx.Autocomplete.expand/1
      end

    # expand_fun is not supported by a shell variant
    # on Windows, so we do two IO calls, not caring
    # about the result of the expand_fun one.
    _ = :io.setopts(gl, expand_fun: expand_fun)
    :io.setopts(gl, binary: true, encoding: :unicode)
  end

  defp run_after_spawn do
    _ = for fun <- Enum.reverse(after_spawn()), do: fun.()
    :ok
  end
end


Repo.create_table accepts options

Repo.create_table accepts options

defmodule Mnesia.RecordNotFoundError do
  defexception [:table, :id]

  def message(%{table: table, id: id}),
    do: "Couldn't find #{inspect table} with id=#{id}"
end

defmodule Mnesia.Repo do
  @moduledoc """
  Thin wrapper between Ecto and Mnesia, allows Ecto.Changeset's to be used in
  conjunction with ETS.
  """

  defmacro __using__(_) do
    quote do
      use Ecto.Schema

      def table, do: [attributes: fields()]

      def fields, do: __schema__(:fields)

      def record(struct) do
        get = &Map.get(struct, &1)
        attrs = Enum.map(fields(), get)
        List.to_tuple [__MODULE__ |attrs]
      end

      def load(record) do
        [__MODULE__ |attrs] = Tuple.to_list(record)
        attrs = Enum.zip(fields(), attrs)
        struct(__MODULE__, attrs)
      end

      def all do
        fn ->
          :qlc.e(:mnesia.table __MODULE__)
        end
        |> :mnesia.async_dirty()
        |> Enum.map(&load/1)
      end

      def last do
        load(:mnesia.last(__MODULE__))
      end

      def save(struct) do
        write = fn ->
          :mnesia.write(record(struct))
        end

        {:atomic, :ok} = :mnesia.transaction(write)

        struct
      end

      def get(id) do
        read = fn ->
          :mnesia.read(__MODULE__, id)
        end

        with {:atomic, [record]} <- :mnesia.transaction(read) do
          {:ok, load(record)}
        else
          {:atomic, []} ->
            {:error, %Mnesia.RecordNotFoundError{id: id, table: __MODULE__}}
        end
      end

      @spec create_table(Keyword.t) :: {:atomic, :ok} | {:aborted, any}
      def create_table(opts \\ []) do
        :mnesia.create_table(__MODULE__, opts ++ table())
      end

      def delete_table do
        :mnesia.delete_table(__MODULE__)
      end
    end
  end
end

defmodule Mnesia.RecordNotFoundError do
  defexception [:table, :id]

  def message(%{table: table, id: id}),
    do: "Couldn't find #{inspect table} with id=#{id}"
end

defmodule Mnesia.Repo do
  @moduledoc """
  Thin wrapper between Ecto and Mnesia, allows Ecto.Changeset's to be used in
  conjunction with ETS.
  """

  defmacro __using__(_) do
    quote do
      use Ecto.Schema

      def table, do: [attributes: fields()]

      def fields, do: __schema__(:fields)

      def record(struct) do
        get = &Map.get(struct, &1)
        attrs = Enum.map(fields(), get)
        List.to_tuple [__MODULE__ |attrs]
      end

      def load(record) do
        [__MODULE__ |attrs] = Tuple.to_list(record)
        attrs = Enum.zip(fields(), attrs)
        struct(__MODULE__, attrs)
      end

      def all do
        fn ->
          :qlc.e(:mnesia.table __MODULE__)
        end
        |> :mnesia.async_dirty()
        |> Enum.map(&load/1)
      end

      def last do
        load(:mnesia.last(__MODULE__))
      end

      def save(struct) do
        write = fn ->
          :mnesia.write(record(struct))
        end

        {:atomic, :ok} = :mnesia.transaction(write)

        struct
      end

      def get(id) do
        read = fn ->
          :mnesia.read(__MODULE__, id)
        end

        with {:atomic, [record]} <- :mnesia.transaction(read) do
          {:ok, load(record)}
        else
          {:atomic, []} ->
            {:error, %Mnesia.RecordNotFoundError{id: id, table: __MODULE__}}
        end
      end

      @spec create_table() :: {:atomic, :ok} | {:aborted, any}
      def create_table do
        :mnesia.create_table(__MODULE__, table())
      end

      def delete_table do
        :mnesia.delete_table(__MODULE__)
      end
    end
  end
end


OK, removing the comment from DocTest

OK, removing the comment from DocTest

defmodule Floki do
  alias Floki.{Finder, FilterOut, HTMLTree}

  @moduledoc """
  Floki is a simple HTML parser that enables search for nodes using CSS selectors.

  ## Example

  Assuming that you have the following HTML:

  ```html
  <!doctype html>
  <html>
  <body>
    <section id="content">
      <p class="headline">Floki</p>
      <a href="http://github.com/philss/floki">Github page</a>
      <span data-model="user">philss</span>
    </section>
  </body>
  </html>
  ```

  To parse this, you can use the function `Floki.parse_document/1`:

  ```elixir
  {:ok, html} = Floki.parse_document(doc)
  # =>
  # [{"html", [],
  #   [
  #     {"body", [],
  #      [
  #        {"section", [{"id", "content"}],
  #         [
  #           {"p", [{"class", "headline"}], ["Floki"]},
  #           {"a", [{"href", "http://github.com/philss/floki"}], ["Github page"]},
  #           {"span", [{"data-model", "user"}], ["philss"]}
  #         ]}
  #      ]}
  #   ]}]
  ```

  With this document you can perform queries such as:

    * `Floki.find(html, "#content")`
    * `Floki.find(html, ".headline")`
    * `Floki.find(html, "a")`
    * `Floki.find(html, "[data-model=user]")`
    * `Floki.find(html, "#content a")`
    * `Floki.find(html, ".headline, a")`

  Each HTML node is represented by a tuple like:

      {tag_name, attributes, children_nodes}

  Example of node:

      {"p", [{"class", "headline"}], ["Floki"]}

  So even if the only child node is the element text, it is represented
  inside a list.
  """

  @type html_declaration :: {:pi, String.t(), [html_attribute()]}
  @type html_comment :: {:comment, String.t()}
  @type html_doctype :: {:doctype, String.t(), String.t(), String.t()}
  @type html_attribute :: {String.t(), String.t()}
  @type html_tag :: {String.t(), [html_attribute()], [html_tag() | String.t() | html_comment()]}
  @type html_node :: html_comment() | html_doctype() | html_tag() | html_declaration()
  @type html_tree :: [html_node()]

  @doc """
  Parses a HTML Document from a String.

  The expect string is a valid HTML, but the parser will try
  to parse even with errors.
  """

  @spec parse(binary()) :: html_tag() | html_tree() | String.t()

  @deprecated "Please use parse_document/1 or parse_fragment/1"
  def parse(html) do
    with {:ok, document} <- Floki.HTMLParser.parse_document(html) do
      if length(document) == 1 do
        hd(document)
      else
        document
      end
    end
  end

  @doc """
  Parses a HTML Document from a string.

  It will use the available parser.
  Check https://github.com/philss/floki#alternative-html-parsers for more details.

  ## Example

      iex> Floki.parse_document("hello")
      {:ok, [{"html", [], [{"head", [], []}, {"body", [], ["hello"]}]}]}

  """

  @spec parse_document(binary()) :: {:ok, html_tree()} | {:error, String.t()}

  defdelegate parse_document(document), to: Floki.HTMLParser

  @doc """
  Parses a HTML Document from a string.

  Similar to `Floki.parse_document/1`, but raises `Floki.ParseError` if there was an
  error parsing the document.

  ## Example

      iex> Floki.parse_document!("hello")
      [{"html", [], [{"head", [], []}, {"body", [], ["hello"]}]}]

  """

  @spec parse_document!(binary()) :: html_tree()

  def parse_document!(document) do
    case parse_document(document) do
      {:ok, parsed_document} -> parsed_document
      {:error, message} -> raise Floki.ParseError, message: message
    end
  end

  @doc """
  Parses a HTML fragment from a string.

  It will use the available parser.
  Check https://github.com/philss/floki#alternative-html-parsers for more details.
  """

  @spec parse_fragment(binary()) :: {:ok, html_tree()} | {:error, String.t()}

  defdelegate parse_fragment(fragment), to: Floki.HTMLParser

  @doc """
  Parses a HTML fragment from a string.

  Similar to `Floki.parse_fragment/1`, but raises `Floki.ParseError` if there was an
  error parsing the fragment.
  """

  @spec parse_fragment!(binary()) :: html_tree()

  def parse_fragment!(fragment) do
    case parse_fragment(fragment) do
      {:ok, parsed_fragment} -> parsed_fragment
      {:error, message} -> raise Floki.ParseError, message: message
    end
  end

  @doc """
  Converts HTML tree to raw HTML.
  Note that the resultant HTML may be different from the original one.
  Spaces after tags and doctypes are ignored.

  Floki.raw_html/2 accepts a keyword list of options. Currently, the
  only supported option is `:encode`, which can be set to `true` or `false`.

  You can also control the encoding behaviour at the application level via
  `config :floki, :encode_raw_html, true | false`

  ## Examples

      iex> Floki.raw_html({"div", [{"class", "wrapper"}], ["my content"]})
      ~s(
my content
)

      iex> Floki.raw_html({"div", [{"class", "wrapper"}], ["10 > 5"]}, encode: true)
      ~s(
10 > 5
)

      iex> Floki.raw_html({"div", [{"class", "wrapper"}], ["10 > 5"]}, encode: false)
      ~s(
10 > 5
)
  """

  @spec raw_html(html_tree | binary, keyword) :: binary

  defdelegate raw_html(html_tree, options \\ []), to: Floki.RawHTML

  @doc """
  Find elements inside a HTML tree or string.

  ## Examples

      iex> {:ok, html} = Floki.parse_fragment("
hello
")
      iex> Floki.find(html, ".hint")
      [{"span", [{"class", "hint"}], ["hello"]}]

      iex> {:ok, html} = Floki.parse_fragment("

Content

")
      iex> Floki.find(html, "#important")
      [{"div", [{"id", "important"}], [{"div", [], ["Content"]}]}]

      iex> {:ok, html} = Floki.parse_fragment("
Google
")
      iex> Floki.find(html, "a")
      [{"a", [{"href", "https://google.com"}], ["Google"]}]

      iex> Floki.find([{ "div", [], [{"a", [{"href", "https://google.com"}], ["Google"]}]}], "div a")
      [{"a", [{"href", "https://google.com"}], ["Google"]}]

  """

  @spec find(binary | html_tree, binary) :: html_tree

  def find(html, selector) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using find/2"
    )

    with {:ok, document} <- Floki.parse_document(html) do
      {tree, results} = Finder.find(document, selector)

      Enum.map(results, fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
    end
  end

  def find(html_tree_as_tuple, selector) do
    {tree, results} = Finder.find(html_tree_as_tuple, selector)

    Enum.map(results, fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  @doc """
  Changes the attribute values of the elements matched by `selector`
  with the function `mutation` and returns the whole element tree

  ## Examples

      iex> Floki.attr([{"div", [{"id", "a"}], []}], "#a", "id", fn(id) -> String.replace(id, "a", "b") end)
      [{"div", [{"id", "b"}], []}]

      iex> Floki.attr([{"div", [{"class", "name"}], []}], "div", "id", fn _ -> "b" end)
      [{"div", [{"id", "b"}, {"class", "name"}], []}]

  """
  @spec attr(binary | html_tree, binary, binary, (binary -> binary)) :: html_tree

  def attr(html_elem_tuple, selector, attribute_name, mutation) when is_tuple(html_elem_tuple) do
    attr([html_elem_tuple], selector, attribute_name, mutation)
  end

  def attr(html, selector, attribute_name, mutation) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using attr/4"
    )

    with {:ok, document} <- Floki.parse_document(html) do
      attr(document, selector, attribute_name, mutation)
    end
  end

  def attr(html_tree_list, selector, attribute_name, mutation) when is_list(html_tree_list) do
    {tree, results} = Finder.find(html_tree_list, selector)
    mutate_attrs(html_tree_list, tree, results, attribute_name, mutation)
  end

  defp add_nodes_to_tree(tree, [html_node]) do
    nodes = Map.put(tree.nodes, html_node.node_id, html_node)
    Map.put(tree, :nodes, nodes)
  end

  defp add_nodes_to_tree(tree, [html_node | tail]) do
    nodes = Map.put(tree.nodes, html_node.node_id, html_node)

    tree
    |> Map.put(:nodes, nodes)
    |> add_nodes_to_tree(tail)
  end

  defp mutate_attrs(html_tree_list, _, [], _, _), do: html_tree_list

  defp mutate_attrs(_, tree, results, attribute_name, mutation_fn) do
    mutated_nodes =
      Enum.map(
        results,
        fn result ->
          mutated_attributes =
            if Enum.any?(result.attributes, &match?({^attribute_name, _}, &1)) do
              Enum.map(
                result.attributes,
                fn attribute ->
                  with {^attribute_name, attribute_value} <- attribute do
                    {attribute_name, mutation_fn.(attribute_value)}
                  end
                end
              )
            else
              [{attribute_name, mutation_fn.(nil)} | result.attributes]
            end

          Map.put(result, :attributes, mutated_attributes)
        end
      )

    tree = add_nodes_to_tree(tree, mutated_nodes)

    tree.root_nodes_ids
    |> Enum.reverse()
    |> Enum.map(fn id -> Map.get(tree.nodes, id) end)
    |> Enum.map(fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  @deprecated """
  Use `find_and_update/3` or `Enum.map/2` instead.
  """
  def map(_html_tree_or_list, _fun)

  def map(html_tree_list, fun) when is_list(html_tree_list) do
    Enum.map(html_tree_list, &Finder.map(&1, fun))
  end

  def map(html_tree, fun), do: Finder.map(html_tree, fun)

  @doc """
  Searchs for elements inside the HTML tree and update those that matches the selector.

  It will return the updated HTML tree.

  This function works in a way similar to `traverse_and_update`, but instead of updating
  the children nodes, it will only updates the `tag` and `attributes` of the matching nodes.

  If `fun` returns `nil`, the HTML node will be removed from the tree.

  ## Examples

      iex> Floki.find_and_update([{"a", [{"href", "http://elixir-lang.com"}], ["Elixir"]}], "a", fn
      iex>   {"a", [{"href", href}]} ->
      iex>     {"a", [{"href", String.replace(href, "http://", "https://")}]}
      iex>   other ->
      iex>     other
      iex> end)
      [{"a", [{"href", "https://elixir-lang.com"}], ["Elixir"]}]
  """

  @spec find_and_update(
          html_tree(),
          Finder.selector(),
          ({String.t(), [html_attribute()]} -> {String.t(), [html_attribute()]} | nil)
        ) :: html_tree()
  def find_and_update(html_tree, selector, fun) do
    {tree, results} = Finder.find(html_tree, selector)

    operations_with_nodes =
      Enum.map(results, fn
        html_node = %Floki.HTMLTree.HTMLNode{} ->
          case fun.({html_node.type, html_node.attributes}) do
            {updated_tag, updated_attrs} ->
              {:update, %{html_node | type: updated_tag, attributes: updated_attrs}}

            nil ->
              {:delete, html_node}

            _ ->
              {:no_op, html_node}
          end

        other ->
          {:no_op, other}
      end)

    tree
    |> HTMLTree.patch_nodes(operations_with_nodes)
    |> HTMLTree.to_tuple()
  end

  @doc """
  Traverses and updates a HTML tree structure.

  This function returns a new tree structure that is the result of applying the
  given `fun` on all nodes. The tree is traversed in a post-walk fashion, where
  the children are traversed before the parent.

  When the function `fun` encounters HTML tag, it receives a tuple with
  `{name, attributes, children}`, and should either return a similar tuple or
  `nil` to delete the current node.

  The function `fun` can also encounter HTML doctype, comment or declaration and
  will receive, and should return, different tuple for these types. See the
  documentation for `t:html_comment/0`, `t:html_doctype/0` and
  `t:html_declaration/0` for details.

  ## Examples

      iex> html = [{"div", [], ["hello"]}]
      iex> Floki.traverse_and_update(html, fn
      ...>   {"div", attrs, children} -> {"p", attrs, children}
      ...>   other -> other
      ...> end)
      [{"p", [], ["hello"]}]

      iex> html = [{"div", [], [{:comment, "I am comment"}, {"span", [], ["hello"]}]}]
      iex> Floki.traverse_and_update(html, fn
      ...>   {"span", _attrs, _children} -> nil
      ...>   {:comment, text} -> {"span", [], text}
      ...>   other -> other
      ...> end)
      [{"div", [], [{"span", [], "I am comment"}]}]
  """

  @spec traverse_and_update(html_tree(), (html_node() -> html_node() | nil)) :: html_tree()

  defdelegate traverse_and_update(html_tree, fun), to: Floki.Traversal

  @doc """
  Traverses and updates a HTML tree structure with an accumulator.

  This function returns a new tree structure and the final value of accumulator
  which are the result of applying the given `fun` on all nodes. The tree is
  traversed in a post-walk fashion, where the children are traversed before
  the parent.

  When the function `fun` encounters HTML tag, it receives a tuple with
  `{name, attributes, children}` and an accumulator. It and should return a
  2-tuple like `{new_node, new_acc}`, where `new_node` is either a similar tuple
  or `nil` to delete the current node, and `new_acc` is an updated value for the
  accumulator.

  The function `fun` can also encounter HTML doctype, comment or declaration and
  will receive, and should return, different tuple for these types. See the
  documentation for `t:html_comment/0`, `t:html_doctype/0` and
  `t:html_declaration/0` for details.

  ## Examples

      iex> html = [{"div", [], [{:comment, "I am a comment"}, "hello"]}, {"div", [], ["world"]}]
      iex> Floki.traverse_and_update(html, 0, fn
      ...>   {"div", attrs, children}, acc ->
      ...>     {{"p", [{"data-count", to_string(acc)} | attrs], children}, acc + 1}
      ...>   other, acc -> {other, acc}
      ...> end)
      {[
         {"p", [{"data-count", "0"}], [{:comment, "I am a comment"}, "hello"]},
         {"p", [{"data-count", "1"}], ["world"]}
       ], 2}

      iex> html = {"div", [], [{"span", [], ["hello"]}]}
      iex> Floki.traverse_and_update(html, [deleted: 0], fn
      ...>   {"span", _attrs, _children}, acc ->
      ...>     {nil, Keyword.put(acc, :deleted, acc[:deleted] + 1)}
      ...>   tag, acc ->
      ...>     {tag, acc}
      ...> end)
      {{"div", [], []}, [deleted: 1]}
  """

  @spec traverse_and_update(
          html_tree(),
          traverse_acc,
          (html_node(), traverse_acc -> {html_node() | nil, traverse_acc})
        ) :: {html_node(), traverse_acc}
        when traverse_acc: any()

  defdelegate traverse_and_update(html_tree, acc, fun), to: Floki.Traversal

  @doc """
  Returns the text nodes from a HTML tree.
  By default, it will perform a deep search through the HTML tree.
  You can disable deep search with the option `deep` assigned to false.
  You can include content of script tags with the option `js` assigned to true.
  You can specify a separator between nodes content.

  ## Examples

      iex> Floki.text({"div", [], [{"span", [], ["hello"]}, " world"]})
      "hello world"

      iex> Floki.text({"div", [], [{"span", [], ["hello"]}, " world"]}, deep: false)
      " world"

      iex> Floki.text({"div", [], [{"script", [], ["hello"]}, " world"]})
      " world"

      iex> Floki.text({"div", [], [{"script", [], ["hello"]}, " world"]}, js: true)
      "hello world"

      iex> Floki.text({"ul", [], [{"li", [], ["hello"]}, {"li", [], ["world"]}]}, sep: "-")
      "hello-world"

      iex> Floki.text([{"div", [], ["hello world"]}])
      "hello world"

      iex> Floki.text([{"p", [], ["1"]},{"p", [], ["2"]}])
      "12"

      iex> Floki.text({"div", [], [{"style", [], ["hello"]}, " world"]}, style: false)
      " world"

      iex> Floki.text({"div", [], [{"style", [], ["hello"]}, " world"]}, style: true)
      "hello world"

  """

  @spec text(html_tree | binary) :: binary

  def text(html, opts \\ [deep: true, js: false, style: true, sep: ""]) do
    cleaned_html_tree =
      html
      |> parse_it()
      |> clean_html_tree(:js, opts[:js])
      |> clean_html_tree(:style, opts[:style])

    search_strategy =
      case opts[:deep] do
        false -> Floki.FlatText
        _ -> Floki.DeepText
      end

    case opts[:sep] do
      nil -> search_strategy.get(cleaned_html_tree)
      sep -> search_strategy.get(cleaned_html_tree, sep)
    end
  end

  @doc """
  Returns the direct child nodes of a HTML node.

  By default, it will also include all texts. You can disable
  this behaviour by using the option `include_text` to `false`.

  If the given node is not an HTML tag, then it returns nil.

  ## Examples

      iex> Floki.children({"div", [], ["text", {"span", [], []}]})
      ["text", {"span", [], []}]

      iex> Floki.children({"div", [], ["text", {"span", [], []}]}, include_text: false)
      [{"span", [], []}]

      iex> Floki.children({:comment, "comment"})
      nil

  """

  @spec children(html_node(), Keyword.t()) :: html_tree() | nil

  def children(html_node, opts \\ [include_text: true]) do
    case html_node do
      {_, _, subtree} ->
        filter_children(subtree, opts[:include_text])

      _ ->
        nil
    end
  end

  defp filter_children(children, false), do: Enum.filter(children, &is_tuple(&1))
  defp filter_children(children, _), do: children

  @doc """
  Returns a list with attribute values for a given selector.

  ## Examples

      iex> Floki.attribute([{"a", [{"href", "https://google.com"}], ["Google"]}], "a", "href")
      ["https://google.com"]

      iex> Floki.attribute([{"a", [{"class", "foo"}, {"href", "https://google.com"}], ["Google"]}], "a", "class")
      ["foo"]

  """

  @spec attribute(binary | html_tree, binary, binary) :: list

  def attribute(html, selector, attribute_name) do
    html
    |> find(selector)
    |> attribute_values(attribute_name)
  end

  @doc """
  Returns a list with attribute values from elements.

  ## Examples

      iex> Floki.attribute([{"a", [{"href", "https://google.com"}], ["Google"]}], "href")
      ["https://google.com"]

  """

  @spec attribute(binary | html_tree, binary) :: list
  def attribute(html, attribute_name) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using attribute/2"
    )

    with {:ok, document} <- Floki.parse_document(html) do
      attribute_values(document, attribute_name)
    end
  end

  def attribute(elements, attribute_name) do
    attribute_values(elements, attribute_name)
  end

  defp attribute_values(element, attr_name) when is_tuple(element) do
    attribute_values([element], attr_name)
  end

  defp attribute_values(elements, attr_name) do
    values =
      Enum.reduce(
        elements,
        [],
        fn
          {_, attributes, _}, acc ->
            case attribute_match?(attributes, attr_name) do
              {_attr_name, value} ->
                [value | acc]

              _ ->
                acc
            end

          _, acc ->
            acc
        end
      )

    Enum.reverse(values)
  end

  defp attribute_match?(attributes, attribute_name) do
    Enum.find(
      attributes,
      fn {attr_name, _} ->
        attr_name == attribute_name
      end
    )
  end

  defp parse_it(html) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using text/2"
    )

    {:ok, document} = Floki.parse_document(html)
    document
  end

  defp parse_it(html), do: html

  defp clean_html_tree(html_tree, :js, true), do: html_tree
  defp clean_html_tree(html_tree, :js, _), do: filter_out(html_tree, "script")

  defp clean_html_tree(html_tree, :style, true), do: html_tree
  defp clean_html_tree(html_tree, :style, _), do: filter_out(html_tree, "style")

  @doc """
  Returns the nodes from a HTML tree that don't match the filter selector.

  ## Examples

      iex> Floki.filter_out({"div", [], [{"script", [], ["hello"]}, " world"]}, "script")
      {"div", [], [" world"]}

      iex> Floki.filter_out([{"body", [], [{"script", [], []}, {"div", [], []}]}], "script")
      [{"body", [], [{"div", [], []}]}]

      iex> Floki.filter_out({"div", [], [{:comment, "comment"}, " text"]}, :comment)
      {"div", [], [" text"]}

      iex> Floki.filter_out({"div", [], ["text"]}, :text)
      {"div", [], []}

  """

  @spec filter_out(binary() | html_tree() | html_tag(), FilterOut.selector()) ::
          html_tree() | html_tag()

  def filter_out(html, selector) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using filter_out/2"
    )

    with {:ok, document} <- Floki.parse_document(html) do
      FilterOut.filter_out(document, selector)
    end
  end

  def filter_out(elements, selector) do
    FilterOut.filter_out(elements, selector)
  end
end

defmodule Floki do
  alias Floki.{Finder, FilterOut, HTMLTree}

  @moduledoc """
  Floki is a simple HTML parser that enables search for nodes using CSS selectors.

  ## Example

  Assuming that you have the following HTML:

  ```html
  <!doctype html>
  <html>
  <body>
    <section id="content">
      <p class="headline">Floki</p>
      <a href="http://github.com/philss/floki">Github page</a>
      <span data-model="user">philss</span>
    </section>
  </body>
  </html>
  ```

  To parse this, you can use the function `Floki.parse_document/1`:

  ```elixir
  {:ok, html} = Floki.parse_document(doc)
  # =>
  # [{"html", [],
  #   [
  #     {"body", [],
  #      [
  #        {"section", [{"id", "content"}],
  #         [
  #           {"p", [{"class", "headline"}], ["Floki"]},
  #           {"a", [{"href", "http://github.com/philss/floki"}], ["Github page"]},
  #           {"span", [{"data-model", "user"}], ["philss"]}
  #         ]}
  #      ]}
  #   ]}]
  ```

  With this document you can perform queries such as:

    * `Floki.find(html, "#content")`
    * `Floki.find(html, ".headline")`
    * `Floki.find(html, "a")`
    * `Floki.find(html, "[data-model=user]")`
    * `Floki.find(html, "#content a")`
    * `Floki.find(html, ".headline, a")`

  Each HTML node is represented by a tuple like:

      {tag_name, attributes, children_nodes}

  Example of node:

      {"p", [{"class", "headline"}], ["Floki"]}

  So even if the only child node is the element text, it is represented
  inside a list.
  """

  @type html_declaration :: {:pi, String.t(), [html_attribute()]}
  @type html_comment :: {:comment, String.t()}
  @type html_doctype :: {:doctype, String.t(), String.t(), String.t()}
  @type html_attribute :: {String.t(), String.t()}
  @type html_tag :: {String.t(), [html_attribute()], [html_tag() | String.t() | html_comment()]}
  @type html_node :: html_comment() | html_doctype() | html_tag() | html_declaration()
  @type html_tree :: [html_node()]

  @doc """
  Parses a HTML Document from a String.

  The expect string is a valid HTML, but the parser will try
  to parse even with errors.
  """

  @spec parse(binary()) :: html_tag() | html_tree() | String.t()

  @deprecated "Please use parse_document/1 or parse_fragment/1"
  def parse(html) do
    with {:ok, document} <- Floki.HTMLParser.parse_document(html) do
      if length(document) == 1 do
        hd(document)
      else
        document
      end
    end
  end

  @doc """
  Parses a HTML Document from a string.

  It will use the available parser.
  Check https://github.com/philss/floki#alternative-html-parsers for more details.

  ## Example

      iex> Floki.parse_document("hello")
      {:ok, [{"html", [], [{"head", [], []}, {"body", [], ["hello"]}]}]}

  """

  @spec parse_document(binary()) :: {:ok, html_tree()} | {:error, String.t()}

  defdelegate parse_document(document), to: Floki.HTMLParser

  @doc """
  Parses a HTML Document from a string.

  Similar to `Floki.parse_document/1`, but raises `Floki.ParseError` if there was an
  error parsing the document.

  ## Example

      iex> Floki.parse_document!("hello")
      [{"html", [], [{"head", [], []}, {"body", [], ["hello"]}]}]

  """

  @spec parse_document!(binary()) :: html_tree()

  def parse_document!(document) do
    case parse_document(document) do
      {:ok, parsed_document} -> parsed_document
      {:error, message} -> raise Floki.ParseError, message: message
    end
  end

  @doc """
  Parses a HTML fragment from a string.

  It will use the available parser.
  Check https://github.com/philss/floki#alternative-html-parsers for more details.
  """

  @spec parse_fragment(binary()) :: {:ok, html_tree()} | {:error, String.t()}

  defdelegate parse_fragment(fragment), to: Floki.HTMLParser

  @doc """
  Parses a HTML fragment from a string.

  Similar to `Floki.parse_fragment/1`, but raises `Floki.ParseError` if there was an
  error parsing the fragment.
  """

  @spec parse_fragment!(binary()) :: html_tree()

  def parse_fragment!(fragment) do
    case parse_fragment(fragment) do
      {:ok, parsed_fragment} -> parsed_fragment
      {:error, message} -> raise Floki.ParseError, message: message
    end
  end

  @doc """
  Converts HTML tree to raw HTML.
  Note that the resultant HTML may be different from the original one.
  Spaces after tags and doctypes are ignored.

  Floki.raw_html/2 accepts a keyword list of options. Currently, the
  only supported option is `:encode`, which can be set to `true` or `false`.

  You can also control the encoding behaviour at the application level via
  `config :floki, :encode_raw_html, true | false`

  ## Examples

      iex> Floki.raw_html({"div", [{"class", "wrapper"}], ["my content"]})
      ~s(
my content
)

      iex> Floki.raw_html({"div", [{"class", "wrapper"}], ["10 > 5"]}, encode: true)
      ~s(
10 > 5
)

      iex> Floki.raw_html({"div", [{"class", "wrapper"}], ["10 > 5"]}, encode: false)
      ~s(
10 > 5
)
  """

  @spec raw_html(html_tree | binary, keyword) :: binary

  defdelegate raw_html(html_tree, options \\ []), to: Floki.RawHTML

  @doc """
  Find elements inside a HTML tree or string.

  ## Examples

      iex> {:ok, html} = Floki.parse_fragment("
hello
")
      iex> Floki.find(html, ".hint")
      [{"span", [{"class", "hint"}], ["hello"]}]

      iex> {:ok, html} = Floki.parse_fragment("

Content

")
      iex> Floki.find(html, "#important")
      [{"div", [{"id", "important"}], [{"div", [], ["Content"]}]}]

      iex> {:ok, html} = Floki.parse_fragment("
Google
")
      iex> Floki.find(html, "a")
      [{"a", [{"href", "https://google.com"}], ["Google"]}]

      iex> Floki.find([{ "div", [], [{"a", [{"href", "https://google.com"}], ["Google"]}]}], "div a")
      [{"a", [{"href", "https://google.com"}], ["Google"]}]

  """

  @spec find(binary | html_tree, binary) :: html_tree

  def find(html, selector) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using find/2"
    )

    with {:ok, document} <- Floki.parse_document(html) do
      {tree, results} = Finder.find(document, selector)

      Enum.map(results, fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
    end
  end

  def find(html_tree_as_tuple, selector) do
    {tree, results} = Finder.find(html_tree_as_tuple, selector)

    Enum.map(results, fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  @doc """
  Changes the attribute values of the elements matched by `selector`
  with the function `mutation` and returns the whole element tree

  ## Examples

      iex> Floki.attr([{"div", [{"id", "a"}], []}], "#a", "id", fn(id) -> String.replace(id, "a", "b") end)
      [{"div", [{"id", "b"}], []}]

      iex> Floki.attr([{"div", [{"class", "name"}], []}], "div", "id", fn _ -> "b" end)
      [{"div", [{"id", "b"}, {"class", "name"}], []}]

  """
  @spec attr(binary | html_tree, binary, binary, (binary -> binary)) :: html_tree

  def attr(html_elem_tuple, selector, attribute_name, mutation) when is_tuple(html_elem_tuple) do
    attr([html_elem_tuple], selector, attribute_name, mutation)
  end

  def attr(html, selector, attribute_name, mutation) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using attr/4"
    )

    with {:ok, document} <- Floki.parse_document(html) do
      attr(document, selector, attribute_name, mutation)
    end
  end

  def attr(html_tree_list, selector, attribute_name, mutation) when is_list(html_tree_list) do
    {tree, results} = Finder.find(html_tree_list, selector)
    mutate_attrs(html_tree_list, tree, results, attribute_name, mutation)
  end

  defp add_nodes_to_tree(tree, [html_node]) do
    nodes = Map.put(tree.nodes, html_node.node_id, html_node)
    Map.put(tree, :nodes, nodes)
  end

  defp add_nodes_to_tree(tree, [html_node | tail]) do
    nodes = Map.put(tree.nodes, html_node.node_id, html_node)

    tree
    |> Map.put(:nodes, nodes)
    |> add_nodes_to_tree(tail)
  end

  defp mutate_attrs(html_tree_list, _, [], _, _), do: html_tree_list

  defp mutate_attrs(_, tree, results, attribute_name, mutation_fn) do
    mutated_nodes =
      Enum.map(
        results,
        fn result ->
          mutated_attributes =
            if Enum.any?(result.attributes, &match?({^attribute_name, _}, &1)) do
              Enum.map(
                result.attributes,
                fn attribute ->
                  with {^attribute_name, attribute_value} <- attribute do
                    {attribute_name, mutation_fn.(attribute_value)}
                  end
                end
              )
            else
              [{attribute_name, mutation_fn.(nil)} | result.attributes]
            end

          Map.put(result, :attributes, mutated_attributes)
        end
      )

    tree = add_nodes_to_tree(tree, mutated_nodes)

    tree.root_nodes_ids
    |> Enum.reverse()
    |> Enum.map(fn id -> Map.get(tree.nodes, id) end)
    |> Enum.map(fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  @deprecated """
  Use `find_and_update/3` or `Enum.map/2` instead.
  """
  def map(_html_tree_or_list, _fun)

  def map(html_tree_list, fun) when is_list(html_tree_list) do
    Enum.map(html_tree_list, &Finder.map(&1, fun))
  end

  def map(html_tree, fun), do: Finder.map(html_tree, fun)

  @doc """
  Searchs for elements inside the HTML tree and update those that matches the selector.

  It will return the updated HTML tree.

  This function works in a way similar to `traverse_and_update`, but instead of updating
  the children nodes, it will only updates the `tag` and `attributes` of the matching nodes.

  If `fun` returns `nil`, the HTML node will be removed from the tree.

  ## Examples

      iex> Floki.find_and_update([{"a", [{"href", "http://elixir-lang.com"}], ["Elixir"]}], "a", fn
      iex>   {"a", [{"href", href}]} ->
      iex>     {"a", [{"href", String.replace(href, "http://", "https://")}]}
      iex>   other ->
      iex>     other
      iex> end)
      iex> # => [{"a", [{"href", "https://elixir-lang.com"}], ["Elixir"]}]
  """

  @spec find_and_update(
          html_tree(),
          Finder.selector(),
          ({String.t(), [html_attribute()]} -> {String.t(), [html_attribute()]} | nil)
        ) :: html_tree()
  def find_and_update(html_tree, selector, fun) do
    {tree, results} = Finder.find(html_tree, selector)

    operations_with_nodes =
      Enum.map(results, fn
        html_node = %Floki.HTMLTree.HTMLNode{} ->
          case fun.({html_node.type, html_node.attributes}) do
            {updated_tag, updated_attrs} ->
              {:update, %{html_node | type: updated_tag, attributes: updated_attrs}}

            nil ->
              {:delete, html_node}

            _ ->
              {:no_op, html_node}
          end

        other ->
          {:no_op, other}
      end)

    tree
    |> HTMLTree.patch_nodes(operations_with_nodes)
    |> HTMLTree.to_tuple()
  end

  @doc """
  Traverses and updates a HTML tree structure.

  This function returns a new tree structure that is the result of applying the
  given `fun` on all nodes. The tree is traversed in a post-walk fashion, where
  the children are traversed before the parent.

  When the function `fun` encounters HTML tag, it receives a tuple with
  `{name, attributes, children}`, and should either return a similar tuple or
  `nil` to delete the current node.

  The function `fun` can also encounter HTML doctype, comment or declaration and
  will receive, and should return, different tuple for these types. See the
  documentation for `t:html_comment/0`, `t:html_doctype/0` and
  `t:html_declaration/0` for details.

  ## Examples

      iex> html = [{"div", [], ["hello"]}]
      iex> Floki.traverse_and_update(html, fn
      ...>   {"div", attrs, children} -> {"p", attrs, children}
      ...>   other -> other
      ...> end)
      [{"p", [], ["hello"]}]

      iex> html = [{"div", [], [{:comment, "I am comment"}, {"span", [], ["hello"]}]}]
      iex> Floki.traverse_and_update(html, fn
      ...>   {"span", _attrs, _children} -> nil
      ...>   {:comment, text} -> {"span", [], text}
      ...>   other -> other
      ...> end)
      [{"div", [], [{"span", [], "I am comment"}]}]
  """

  @spec traverse_and_update(html_tree(), (html_node() -> html_node() | nil)) :: html_tree()

  defdelegate traverse_and_update(html_tree, fun), to: Floki.Traversal

  @doc """
  Traverses and updates a HTML tree structure with an accumulator.

  This function returns a new tree structure and the final value of accumulator
  which are the result of applying the given `fun` on all nodes. The tree is
  traversed in a post-walk fashion, where the children are traversed before
  the parent.

  When the function `fun` encounters HTML tag, it receives a tuple with
  `{name, attributes, children}` and an accumulator. It and should return a
  2-tuple like `{new_node, new_acc}`, where `new_node` is either a similar tuple
  or `nil` to delete the current node, and `new_acc` is an updated value for the
  accumulator.

  The function `fun` can also encounter HTML doctype, comment or declaration and
  will receive, and should return, different tuple for these types. See the
  documentation for `t:html_comment/0`, `t:html_doctype/0` and
  `t:html_declaration/0` for details.

  ## Examples

      iex> html = [{"div", [], [{:comment, "I am a comment"}, "hello"]}, {"div", [], ["world"]}]
      iex> Floki.traverse_and_update(html, 0, fn
      ...>   {"div", attrs, children}, acc ->
      ...>     {{"p", [{"data-count", to_string(acc)} | attrs], children}, acc + 1}
      ...>   other, acc -> {other, acc}
      ...> end)
      {[
         {"p", [{"data-count", "0"}], [{:comment, "I am a comment"}, "hello"]},
         {"p", [{"data-count", "1"}], ["world"]}
       ], 2}

      iex> html = {"div", [], [{"span", [], ["hello"]}]}
      iex> Floki.traverse_and_update(html, [deleted: 0], fn
      ...>   {"span", _attrs, _children}, acc ->
      ...>     {nil, Keyword.put(acc, :deleted, acc[:deleted] + 1)}
      ...>   tag, acc ->
      ...>     {tag, acc}
      ...> end)
      {{"div", [], []}, [deleted: 1]}
  """

  @spec traverse_and_update(
          html_tree(),
          traverse_acc,
          (html_node(), traverse_acc -> {html_node() | nil, traverse_acc})
        ) :: {html_node(), traverse_acc}
        when traverse_acc: any()

  defdelegate traverse_and_update(html_tree, acc, fun), to: Floki.Traversal

  @doc """
  Returns the text nodes from a HTML tree.
  By default, it will perform a deep search through the HTML tree.
  You can disable deep search with the option `deep` assigned to false.
  You can include content of script tags with the option `js` assigned to true.
  You can specify a separator between nodes content.

  ## Examples

      iex> Floki.text({"div", [], [{"span", [], ["hello"]}, " world"]})
      "hello world"

      iex> Floki.text({"div", [], [{"span", [], ["hello"]}, " world"]}, deep: false)
      " world"

      iex> Floki.text({"div", [], [{"script", [], ["hello"]}, " world"]})
      " world"

      iex> Floki.text({"div", [], [{"script", [], ["hello"]}, " world"]}, js: true)
      "hello world"

      iex> Floki.text({"ul", [], [{"li", [], ["hello"]}, {"li", [], ["world"]}]}, sep: "-")
      "hello-world"

      iex> Floki.text([{"div", [], ["hello world"]}])
      "hello world"

      iex> Floki.text([{"p", [], ["1"]},{"p", [], ["2"]}])
      "12"

      iex> Floki.text({"div", [], [{"style", [], ["hello"]}, " world"]}, style: false)
      " world"

      iex> Floki.text({"div", [], [{"style", [], ["hello"]}, " world"]}, style: true)
      "hello world"

  """

  @spec text(html_tree | binary) :: binary

  def text(html, opts \\ [deep: true, js: false, style: true, sep: ""]) do
    cleaned_html_tree =
      html
      |> parse_it()
      |> clean_html_tree(:js, opts[:js])
      |> clean_html_tree(:style, opts[:style])

    search_strategy =
      case opts[:deep] do
        false -> Floki.FlatText
        _ -> Floki.DeepText
      end

    case opts[:sep] do
      nil -> search_strategy.get(cleaned_html_tree)
      sep -> search_strategy.get(cleaned_html_tree, sep)
    end
  end

  @doc """
  Returns the direct child nodes of a HTML node.

  By default, it will also include all texts. You can disable
  this behaviour by using the option `include_text` to `false`.

  If the given node is not an HTML tag, then it returns nil.

  ## Examples

      iex> Floki.children({"div", [], ["text", {"span", [], []}]})
      ["text", {"span", [], []}]

      iex> Floki.children({"div", [], ["text", {"span", [], []}]}, include_text: false)
      [{"span", [], []}]

      iex> Floki.children({:comment, "comment"})
      nil

  """

  @spec children(html_node(), Keyword.t()) :: html_tree() | nil

  def children(html_node, opts \\ [include_text: true]) do
    case html_node do
      {_, _, subtree} ->
        filter_children(subtree, opts[:include_text])

      _ ->
        nil
    end
  end

  defp filter_children(children, false), do: Enum.filter(children, &is_tuple(&1))
  defp filter_children(children, _), do: children

  @doc """
  Returns a list with attribute values for a given selector.

  ## Examples

      iex> Floki.attribute([{"a", [{"href", "https://google.com"}], ["Google"]}], "a", "href")
      ["https://google.com"]

      iex> Floki.attribute([{"a", [{"class", "foo"}, {"href", "https://google.com"}], ["Google"]}], "a", "class")
      ["foo"]

  """

  @spec attribute(binary | html_tree, binary, binary) :: list

  def attribute(html, selector, attribute_name) do
    html
    |> find(selector)
    |> attribute_values(attribute_name)
  end

  @doc """
  Returns a list with attribute values from elements.

  ## Examples

      iex> Floki.attribute([{"a", [{"href", "https://google.com"}], ["Google"]}], "href")
      ["https://google.com"]

  """

  @spec attribute(binary | html_tree, binary) :: list
  def attribute(html, attribute_name) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using attribute/2"
    )

    with {:ok, document} <- Floki.parse_document(html) do
      attribute_values(document, attribute_name)
    end
  end

  def attribute(elements, attribute_name) do
    attribute_values(elements, attribute_name)
  end

  defp attribute_values(element, attr_name) when is_tuple(element) do
    attribute_values([element], attr_name)
  end

  defp attribute_values(elements, attr_name) do
    values =
      Enum.reduce(
        elements,
        [],
        fn
          {_, attributes, _}, acc ->
            case attribute_match?(attributes, attr_name) do
              {_attr_name, value} ->
                [value | acc]

              _ ->
                acc
            end

          _, acc ->
            acc
        end
      )

    Enum.reverse(values)
  end

  defp attribute_match?(attributes, attribute_name) do
    Enum.find(
      attributes,
      fn {attr_name, _} ->
        attr_name == attribute_name
      end
    )
  end

  defp parse_it(html) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using text/2"
    )

    {:ok, document} = Floki.parse_document(html)
    document
  end

  defp parse_it(html), do: html

  defp clean_html_tree(html_tree, :js, true), do: html_tree
  defp clean_html_tree(html_tree, :js, _), do: filter_out(html_tree, "script")

  defp clean_html_tree(html_tree, :style, true), do: html_tree
  defp clean_html_tree(html_tree, :style, _), do: filter_out(html_tree, "style")

  @doc """
  Returns the nodes from a HTML tree that don't match the filter selector.

  ## Examples

      iex> Floki.filter_out({"div", [], [{"script", [], ["hello"]}, " world"]}, "script")
      {"div", [], [" world"]}

      iex> Floki.filter_out([{"body", [], [{"script", [], []}, {"div", [], []}]}], "script")
      [{"body", [], [{"div", [], []}]}]

      iex> Floki.filter_out({"div", [], [{:comment, "comment"}, " text"]}, :comment)
      {"div", [], [" text"]}

      iex> Floki.filter_out({"div", [], ["text"]}, :text)
      {"div", [], []}

  """

  @spec filter_out(binary() | html_tree() | html_tag(), FilterOut.selector()) ::
          html_tree() | html_tag()

  def filter_out(html, selector) when is_binary(html) do
    IO.warn(
      "deprecation: parse the HTML with parse_document or parse_fragment before using filter_out/2"
    )

    with {:ok, document} <- Floki.parse_document(html) do
      FilterOut.filter_out(document, selector)
    end
  end

  def filter_out(elements, selector) do
    FilterOut.filter_out(elements, selector)
  end
end