Skip to content

Add structured text output for AI model context #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,52 @@ Or use one of the other [output formats](https://www.graphviz.org/doc/info/outpu
php artisan generate:erd output.svg --format=svg
```

### Text Output

If you want to generate a text representation of the ER diagram instead of an image, you can use the `--text-output` option:

```bash
php artisan generate:erd output.txt --text-output
```

This will generate a text file with the GraphViz DOT representation of the ER diagram.

### Structured Text Output for AI Models

If you want to generate a structured text representation of the ER diagram that is more suitable for AI models, simply specify a filename with a `.txt` extension:

```bash
php artisan generate:erd output.txt
```

This will automatically generate a Markdown file with a structured representation of the entities and their relationships, which can be used as context for AI models.

#### Output Format

The structured output format looks like this:

```markdown
# Entity Relationship Diagram

## Entities

### User (`App\Models\User`)

#### Attributes:
- `id` (integer)
- `name` (string)
- `email` (string)
...

## Relationships

### User Relationships
- **HasMany** `posts` to Post (Local Key: `id`, Foreign Key: `user_id`)
...
```

This format is particularly useful when providing context to AI models about your database structure.

## Customization

Please take a look at the published `erd-generator.php` configuration file for all available customization options.
Expand Down Expand Up @@ -121,4 +167,4 @@ If you discover any security related issues, please email [email protected] ins

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
38 changes: 33 additions & 5 deletions src/GenerateDiagramCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class GenerateDiagramCommand extends Command
*
* @var string
*/
protected $signature = 'generate:erd {filename?} {--format=png}';
protected $signature = 'generate:erd {filename?} {--format=png} {--text-output : Output as text file instead of image}';

/**
* The console command description.
Expand Down Expand Up @@ -70,17 +70,40 @@ public function handle()
);
});

// Check if output filename has .txt extension
$outputFileName = $this->getOutputFileName();
if (pathinfo($outputFileName, PATHINFO_EXTENSION) === 'txt') {
// Generate structured text output for .txt files
$textOutput = $this->graphBuilder->generateStructuredTextRepresentation($models);
file_put_contents($outputFileName, $textOutput);
$this->info(PHP_EOL);
$this->info('Wrote structured ER diagram to ' . $outputFileName);
return;
}

$graph = $this->graphBuilder->buildGraph($models);

if ($this->option('format') === self::FORMAT_TEXT) {
$this->info($graph->__toString());
if ($this->option('text-output') || $this->option('format') === self::FORMAT_TEXT) {
$textOutput = $graph->__toString();

// If text-output option is set, write to file
if ($this->option('text-output')) {
$outputFileName = $this->getTextOutputFileName();
file_put_contents($outputFileName, $textOutput);
$this->info(PHP_EOL);
$this->info('Wrote text diagram to ' . $outputFileName);
return;
}

// Otherwise just output to console
$this->info($textOutput);
return;
}

$graph->export($this->option('format'), $this->getOutputFileName());
$graph->export($this->option('format'), $outputFileName);

$this->info(PHP_EOL);
$this->info('Wrote diagram to ' . $this->getOutputFileName());
$this->info('Wrote diagram to ' . $outputFileName);
}

protected function getOutputFileName(): string
Expand All @@ -89,6 +112,11 @@ protected function getOutputFileName(): string
static::DEFAULT_FILENAME . '.' . $this->option('format');
}

protected function getTextOutputFileName(): string
{
return $this->argument('filename') ?: static::DEFAULT_FILENAME . '.txt';
}

protected function getModelsThatShouldBeInspected(): Collection
{
$directories = config('erd-generator.directories');
Expand Down
62 changes: 62 additions & 0 deletions src/GraphBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,68 @@ public function buildGraph(Collection $models) : Graph
return $this->graph;
}

/**
* Generate a structured text representation of the ER diagram
*
* @param Collection $models
* @return string
*/
public function generateStructuredTextRepresentation(Collection $models) : string
{
$output = "# Entity Relationship Diagram\n\n";

// First list all models/entities with their attributes
$output .= "## Entities\n\n";

foreach ($models as $model) {
/** @var Model $model */
$eloquentModel = app($model->getModel());
$output .= "### " . $model->getLabel() . " (`" . $model->getModel() . "`)\n\n";

// Add table columns if available
if (config('erd-generator.use_db_schema')) {
$columns = $this->getTableColumnsFromModel($eloquentModel);
if (count($columns) > 0) {
$output .= "#### Attributes:\n\n";
foreach ($columns as $column) {
$columnType = config('erd-generator.use_column_types') ? ' (' . $column->getType()->getName() . ')' : '';
$output .= "- `" . $column->getName() . "`" . $columnType . "\n";
}
$output .= "\n";
}
}
}

// Then list all relationships
$output .= "## Relationships\n\n";

foreach ($models as $model) {
/** @var Model $model */
if (count($model->getRelations()) > 0) {
$output .= "### " . $model->getLabel() . " Relationships\n\n";

foreach ($model->getRelations() as $relation) {
/** @var ModelRelation $relation */
// Find the related model by comparing model class names
$relatedModelClass = $relation->getModel();
$relatedModel = $models->first(function ($m) use ($relatedModelClass) {
return $m->getModel() === $relatedModelClass;
});

if ($relatedModel) {
$output .= "- **" . $relation->getType() . "** `" . $relation->getName() . "` to " .
$relatedModel->getLabel() . " (Local Key: `" . $relation->getLocalKey() .
"`, Foreign Key: `" . $relation->getForeignKey() . "`)\n";
}
}

$output .= "\n";
}
}

return $output;
}

protected function getTableColumnsFromModel(EloquentModel $model)
{
try {
Expand Down
84 changes: 84 additions & 0 deletions tests/GenerationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,88 @@ public function it_generated_graphviz_in_jpeg_format()

$this->assertStringContainsString('Wrote diagram to graph.jpeg', Artisan::output());
}

/** @test */
public function it_generates_text_output_file_with_text_output_option()
{
$this->app['config']->set('erd-generator.directories', [__DIR__ . '/Models']);

$outputFile = __DIR__ . '/output_test.txt';

// Make sure the file doesn't exist before the test
if (file_exists($outputFile)) {
unlink($outputFile);
}

Artisan::call('generate:erd', [
'filename' => $outputFile,
'--text-output' => true
]);

$this->assertFileExists($outputFile);
$this->assertStringContainsString('Wrote text diagram to ' . $outputFile, Artisan::output());

// Check if the file contains GraphViz DOT content
$fileContent = file_get_contents($outputFile);
$this->assertStringContainsString('digraph', $fileContent);

// Clean up
if (file_exists($outputFile)) {
unlink($outputFile);
}
}

/** @test */
public function it_generates_structured_text_output_for_txt_extension()
{
$this->app['config']->set('erd-generator.directories', [__DIR__ . '/Models']);

$outputFile = __DIR__ . '/structured_test.txt';

// Make sure the file doesn't exist before the test
if (file_exists($outputFile)) {
unlink($outputFile);
}

Artisan::call('generate:erd', [
'filename' => $outputFile
]);

$this->assertFileExists($outputFile);
$this->assertStringContainsString('Wrote structured ER diagram to ' . $outputFile, Artisan::output());

// Check if the file contains structured Markdown content
$fileContent = file_get_contents($outputFile);
$this->assertStringContainsString('# Entity Relationship Diagram', $fileContent);
$this->assertStringContainsString('## Entities', $fileContent);
$this->assertStringContainsString('## Relationships', $fileContent);

// Clean up
if (file_exists($outputFile)) {
unlink($outputFile);
}
}

/** @test */
public function it_generates_structured_text_output_with_correct_content()
{
$this->app['config']->set('erd-generator.directories', [__DIR__ . '/Models']);

// Get the structured text output directly from the GraphBuilder
$models = $this->app->make('BeyondCode\ErdGenerator\ModelFinder')
->getModelsInDirectory(__DIR__ . '/Models')
->transform(function ($model) {
return new \BeyondCode\ErdGenerator\Model(
$model,
(new \ReflectionClass($model))->getShortName(),
$this->app->make('BeyondCode\ErdGenerator\RelationFinder')->getModelRelations($model)
);
});

$structuredOutput = $this->app->make('BeyondCode\ErdGenerator\GraphBuilder')
->generateStructuredTextRepresentation($models);

// Assert the structured output matches the snapshot
$this->assertMatchesSnapshot($structuredOutput);
}
}