Skip to content
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
62 changes: 62 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Deploy Documentation to GitHub Pages

on:
push:
branches:
- main
paths:
- 'docs/**'
- 'README.adoc'
- '.github/workflows/docs.yml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: false

- name: Install dependencies
working-directory: docs
run: |
bundle install

- name: Setup Pages
uses: actions/configure-pages@v5

- name: Build with Jekyll
working-directory: docs
run: |
bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
env:
JEKYLL_ENV: production

- name: Upload artifact
uses: actions/upload-pages-artifact@v4

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
35 changes: 35 additions & 0 deletions .github/workflows/link-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Link Check

on:
push:
branches:
- main
paths:
- 'docs/**'
- 'README.adoc'
- 'lychee.toml'
pull_request:
paths:
- 'docs/**'
- 'README.adoc'
- 'lychee.toml'
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:

permissions:
contents: read
pull-requests: write

jobs:
linkChecker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Link Checker
uses: lycheeverse/lychee-action@v1
with:
args: --config lychee.toml --verbose --no-progress 'docs/**/*.adoc' 'docs/**/*.md' 'README.adoc'
fail: false
32 changes: 0 additions & 32 deletions .github/workflows/main.yml

This file was deleted.

19 changes: 19 additions & 0 deletions .github/workflows/rake.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Auto-generated by Cimas: Do not edit it manually!
# See https://github.com/metanorma/cimas
name: rake

permissions:
contents: write
packages: write

on:
push:
branches: [ master, main ]
tags: [ v* ]
pull_request:

jobs:
rake:
uses: metanorma/ci/.github/workflows/generic-rake.yml@main
secrets:
pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
28 changes: 28 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Auto-generated by Cimas: Do not edit it manually!
# See https://github.com/metanorma/cimas
name: release

permissions:
contents: write
packages: write
id-token: write

on:
workflow_dispatch:
inputs:
next_version:
description: |
Next release version. Possible values: x.y.z, major, minor, patch or pre|rc|etc
required: true
default: "skip"
repository_dispatch:
types: [do-release]

jobs:
release:
uses: metanorma/ci/.github/workflows/rubygems-release.yml@main
with:
next_version: ${{ github.event.inputs.next_version }}
secrets:
rubygems-api-key: ${{ secrets.LUTAML_CI_RUBYGEMS_API_KEY }}
pat_token: ${{ secrets.GITHUB_TOKEN }}
125 changes: 125 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

`ea` is a standalone Ruby gem for parsing **Sparx Enterprise Architect** data
files. It parses the EA-native QEA format (SQLite database) and Sparx-flavored
XMI. It does NOT parse generic XMI, MagicDraw XMI, or Papyrus XMI — those have
different idiosyncrasies and belong in their own parser gems.

It optionally integrates with `lutaml-uml` for EA-to-UML model transformation.

**Gem identity**: `ea` (not `lutaml-ea`). Namespace: `Ea::*`. This gem is
usable standalone — `lutaml-uml` is an optional dependency for the UML bridge.

**Dependency graph**:
```
ea (standalone — sqlite3, rubyzip, nokogiri, xmi, liquid)
└── [optional] lutaml-uml (for Ea::Qea.to_uml bridge → Lutaml::Uml::Document)

lutaml-uml (UML metamodel + UmlRepository + SPA — no dependency on ea)
└── lutaml-lml
└── lutaml (meta-bundle)
```

**XMI parsing**: `Ea::Xmi::Parser` is hard-wired to the Sparx schema
(`::Xmi::Sparx::Root.parse_xml`). It cannot parse MagicDraw or Papyrus XMI.
When registering with `UmlRepository`, `ea` registers `.xmi` with content
detection (checks for Sparx EA signatures), so it never claims generic `.xmi`.
See TODO.next/04 and TODO.next/18.

## Build & Development Commands

```bash
bin/setup # Install dependencies
bundle exec rake # Run default task (specs + rubocop)
bundle exec rake spec # Run tests
bundle exec rspec # Run tests directly
bundle exec rspec spec/ea/qea/models/ea_object_spec.rb # Run a single spec file
bundle exec rubocop # Lint
bundle exec rake install # Install gem locally
bin/console # IRB with gem loaded
```

## Architecture

### Entry Point (`lib/ea.rb`)
Defines `Ea` module with `autoload` for four subsystems: `Qea`, `Diagram`,
`Transformations`, `Xmi`. No `require_relative` anywhere in lib — all internal
loading uses `autoload` defined in the immediate parent namespace file.

### Standalone Subsystems (no `lutaml-uml` dependency)

#### QEA Core (`lib/ea/qea/`)
- **Models** (25 files) — EA database row models inheriting from `BaseModel`
(extends `Lutaml::Model::Serializable`). Each model declares attributes,
`primary_key_column`, `table_name`, and `COLUMN_MAP`. Zero UML references.
- **Infrastructure** — `DatabaseConnection` wraps SQLite3, `SchemaReader`/
`TableReader` for raw DB access.
- **Services** — `Configuration` loads `config/qea_schema.yml`,
`DatabaseLoader` loads all tables into a `Database` container.
- **Database** — Immutable container with lazy-built hash indexes for O(1)
lookups by ID, GUID, foreign keys. Query methods: `constraints_for_object`,
`tagged_values_for_element`, `properties_for_object`, `xrefs_for_client`.
- **Repositories** — `BaseRepository` provides `Enumerable`, `find`, `where`,
`pluck`, `group_by`. `ObjectRepository` adds type-specific queries.

#### Diagram Core (`lib/ea/diagram/`)
- **Style** — `StyleResolver` is the single entry point. `StyleParser` parses
EA style strings. `Configuration` loads `config/diagram_styles.yml`.
- **Layout** — `LayoutEngine` calculates bounds and positioning.
- **Path** — `PathBuilder` generates SVG path data from connector geometry.

### UML-Bridge Subsystems (require `lutaml-uml`)

#### QEA Factory (`lib/ea/qea/factory/`)
- `EaToUmlFactory` orchestrates transformation using `TransformerRegistry`
for OCP-compliant type dispatch. Each transformer extends `BaseTransformer`.
`DocumentBuilder` constructs and validates the output `Lutaml::Uml::Document`.
`ReferenceResolver` maps EA GUIDs to UML xmi_ids.

#### QEA Validation (`lib/ea/qea/validation/`)
- `ValidationEngine` runs registered validators. Validators extend
`BaseValidator`. Operates on both EA data and UML documents.

#### QEA Verification (`lib/ea/qea/verification/`)
- Document comparison and equivalence testing for QEA vs XMI outputs.

#### Diagram Rendering (`lib/ea/diagram/`)
- **Element Renderers** — Maps element types to renderer classes. `BaseRenderer`
provides `style_to_css` and template methods. Concrete: `ClassRenderer`,
`PackageRenderer`, `ConnectorRenderer`.
- **Extractor** — Extracts diagram data from `Lutaml::Uml::Document` instances.

#### XMI Subsystem (`lib/ea/xmi/`)
- `Parser` for **Sparx-flavored** XMI files only (uses `::Xmi::Sparx::Root`).
Cannot parse MagicDraw or Papyrus XMI. `LookupService` for cross-referencing.
- Liquid drops for template-based XMI rendering.

#### Transformations (`lib/ea/transformations/`)
- `BaseParser` provides template method pattern. Concrete: `QeaParser`,
`XmiParser`. `FormatRegistry` maps file extensions to parser classes.
`TransformationEngine` orchestrates parsing. Returns `Lutaml::Uml::Document`.

### Configuration
- `config/qea_schema.yml` — EA database schema, table definitions, column types
- `config/diagram_styles.yml` — Default diagram styling
- `config/model_transformations.yml` — Parser configurations and transformation options

## Code Quality Rules

- **autoload only** — never `require_relative` or `require` for internal library code
- **No doubles in specs** — use real model instances or `Struct.new`
- **No `send` on private methods** — promote tested methods to public
- **No `instance_variable_get/set`** — test through public API
- **No `respond_to?` or `method_defined?`** — use `is_a?` for type checks, design interfaces properly
- **Registry pattern for OCP** — new types are added by registration, not by modifying `case/when`
- **COLUMN_MAP for models** — database column mapping uses the `COLUMN_MAP` constant pattern, not custom `from_db_row` overrides

## Configuration

- Ruby >= 3.2.0 (CI tests on 3.4.8)
- Double-quoted strings enforced by RuboCop
- RSpec: documentation formatter, color enabled, status persistence to `.rspec_status`
19 changes: 14 additions & 5 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@

source "https://rubygems.org"

# Specify your gem's dependencies in ea.gemspec
gemspec

gem "irb"
gem "rake", "~> 13.0"
# Sibling-repo path dependencies — used during local development when
# the sibling checkout exists (monorepo-style workflow). In CI and for
# gem install, fall back to the published rubygems versions.
#
# Set EA_FORCE_RUBYGEMS=1 to test the CI-resolved versions locally.
%w[lutaml-uml canon].each do |sibling_gem|
sibling_path = File.expand_path("../#{sibling_gem}", __dir__)
if File.directory?(sibling_path) && ENV["EA_FORCE_RUBYGEMS"] != "1"
gem sibling_gem, path: sibling_path
else
gem sibling_gem
end
end

gem "rake"
gem "rspec", "~> 3.0"

gem "rubocop", "~> 1.21"
Loading
Loading