Packages
metastatic
0.3.4
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.3
0.20.2
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.1
0.15.0
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Cross-language code meta-model library using unified MetaAST representation. Parse, transform, and translate code across Python, Elixir, Ruby, Erlang, Haskell, and more via a shared three-tuple AST format.
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/metastatic.inspect.ex
defmodule Mix.Tasks.Metastatic.Inspect do
@shortdoc "Inspect MetaAST structure of source code"
@moduledoc """
Inspect the MetaAST structure of source code files.
## Usage
mix metastatic.inspect FILE [OPTIONS]
## Arguments
- `FILE` - Source file to inspect
## Options
- `--format FORMAT` - Output format (tree, json, plain) [default: tree]
- `--layer LAYER` - Filter by layer (core, extended, native, all) [default: all]
- `--variables` - Show only variables
- `--language LANG` - Source language (optional, auto-detected from extension)
## Examples
Inspect a Python file (tree format):
mix metastatic.inspect hello.py
Show MetaAST in JSON format:
mix metastatic.inspect hello.py --format json
Filter to show only core layer nodes:
mix metastatic.inspect hello.py --layer core
Extract only variables:
mix metastatic.inspect hello.py --variables
## Output Formats
- **tree** (default) - Human-readable tree structure with indentation
- **json** - Machine-readable JSON for programmatic processing
- **plain** - Simple text representation
## Layer Filtering
- **core** - Universal constructs (literals, variables, operators, etc.)
- **extended** - Common patterns (loops, lambdas, collections)
- **native** - Language-specific constructs
- **all** - Show everything (default)
"""
use Mix.Task
alias Metastatic.{Builder, CLI}
alias Metastatic.CLI.{Formatter, Inspector}
@switches [
format: :string,
layer: :string,
variables: :boolean,
language: :string
]
@impl Mix.Task
def run(args) do
Mix.Task.run("app.start")
{opts, paths, _invalid} = OptionParser.parse(args, strict: @switches)
case validate_args(opts, paths) do
{:ok, file_path, options} ->
inspect_file(file_path, options)
{:error, message} ->
CLI.fatal(message)
end
end
# Validation
@spec validate_args(keyword(), [String.t()]) ::
{:ok, String.t(), keyword()} | {:error, String.t()}
defp validate_args(opts, paths) do
with {:ok, file_path} <- validate_file_path(paths),
{:ok, language} <- validate_language(opts, file_path),
{:ok, format} <- validate_format(opts),
{:ok, layer} <- validate_layer(opts) do
options = [
language: language,
format: format,
layer: layer,
variables_only: Keyword.get(opts, :variables, false)
]
{:ok, file_path, options}
end
end
@spec validate_file_path([String.t()]) :: {:ok, String.t()} | {:error, String.t()}
defp validate_file_path([]), do: {:error, "No source file provided"}
defp validate_file_path([path | _]) do
if File.exists?(path) && !File.dir?(path) do
{:ok, path}
else
{:error, "File does not exist or is a directory: #{path}"}
end
end
@spec validate_language(keyword(), String.t()) :: {:ok, atom()} | {:error, String.t()}
defp validate_language(opts, file_path) do
case Keyword.get(opts, :language) do
nil ->
case CLI.detect_language(file_path) do
{:ok, lang} -> {:ok, lang}
{:error, _} -> {:error, "Cannot detect language. Use --language option"}
end
lang_str ->
case String.downcase(lang_str) do
"python" -> {:ok, :python}
"elixir" -> {:ok, :elixir}
"erlang" -> {:ok, :erlang}
_ -> {:error, "Invalid language: #{lang_str}. Supported: python, elixir, erlang"}
end
end
end
@spec validate_format(keyword()) :: {:ok, atom()} | {:error, String.t()}
defp validate_format(opts) do
case Keyword.get(opts, :format, "tree") do
"tree" -> {:ok, :tree}
"json" -> {:ok, :json}
"plain" -> {:ok, :plain}
invalid -> {:error, "Invalid format: #{invalid}. Supported: tree, json, plain"}
end
end
@spec validate_layer(keyword()) :: {:ok, atom()} | {:error, String.t()}
defp validate_layer(opts) do
case Keyword.get(opts, :layer, "all") do
"all" -> {:ok, :all}
"core" -> {:ok, :core}
"extended" -> {:ok, :extended}
"native" -> {:ok, :native}
invalid -> {:error, "Invalid layer: #{invalid}. Supported: all, core, extended, native"}
end
end
# Inspection
@spec inspect_file(String.t(), keyword()) :: no_return()
defp inspect_file(file_path, options) do
language = Keyword.fetch!(options, :language)
format = Keyword.fetch!(options, :format)
layer = Keyword.fetch!(options, :layer)
variables_only = Keyword.fetch!(options, :variables_only)
with {:ok, source} <- CLI.read_file(file_path),
{:ok, doc} <- Builder.from_source(source, language) do
if variables_only do
show_variables(doc)
else
show_ast(doc, format, layer)
end
exit({:shutdown, 0})
else
{:error, reason} ->
CLI.fatal("Failed to inspect file: #{inspect(reason)}")
end
end
@spec show_variables(Metastatic.Document.t()) :: :ok
defp show_variables(doc) do
{:ok, variables} = Inspector.extract_variables(doc)
if MapSet.size(variables) == 0 do
Mix.shell().info("No variables found")
else
Mix.shell().info("Variables:")
variables
|> Enum.sort()
|> Enum.each(fn var ->
Mix.shell().info(" #{var}")
end)
end
end
@spec show_ast(Metastatic.Document.t(), atom(), atom()) :: :ok
defp show_ast(doc, format, layer) do
{:ok, result} = Inspector.inspect_document(doc, layer: layer)
Mix.shell().info(CLI.format_info("Language: #{doc.language}"))
Mix.shell().info(CLI.format_info("Layer: #{result.layer}"))
Mix.shell().info(CLI.format_info("Depth: #{result.depth}"))
Mix.shell().info(CLI.format_info("Nodes: #{result.node_count}"))
Mix.shell().info(CLI.format_info("Variables: #{MapSet.size(result.variables)}"))
Mix.shell().info("")
if layer != :all do
Mix.shell().info("Filtered AST (#{layer} layer):")
else
Mix.shell().info("MetaAST:")
end
output = Formatter.format(result.ast, format)
Mix.shell().info(output)
end
end