dbml2mmd 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 261de0186edf17eb6c43954acedcfb0757cd20eedd3e4071614b8f02338ea2f0
4
+ data.tar.gz: 23f8cb563d34fc9b5a585da92809015207a56bfe08726fab98769764a2143894
5
+ SHA512:
6
+ metadata.gz: c587842ee993f2a9ccb2b71c246ebb53620beef4406d2b1d832becdc40864099b06d4fe1f8a2fc5af285ecdc8fe6544415c9b4ba033b41b1f1b08c3b6ef8942d
7
+ data.tar.gz: '09ed6b16f069788d6381991256e85dedc543b8a3f69d5446240faad92d805dc60b0543bb158c4832e378e01add97402599d013c803ed92135d1c32a0b2df0a4c'
data/LICENSE ADDED
@@ -0,0 +1,32 @@
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
12
+
13
+ The licenses for most software and other practical works are designed
14
+ to take away your freedom to share and change the works. By contrast,
15
+ the GNU General Public License is intended to guarantee your freedom to
16
+ share and change all versions of a program--to make sure it remains free
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ them if you wish), that you receive source code or can get it if you
26
+ want it, that you can change the software or use pieces of it in new
27
+ free programs, and that you know you can do these things.
28
+
29
+ To protect your rights, we need to prevent others from denying you
30
+ these rights or asking you to surrender the rights. Therefore, you have
31
+ certain responsibilities if you distribute copies of the software, or if
32
+ you modify it: responsibilities to respect the freedom of others.
data/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # dbml2mmd
2
+
3
+ A command-line tool to convert DBML (Database Markup Language) files to Mermaid Markdown format for database diagram visualization.
4
+
5
+ ## Overview
6
+
7
+ dbml2mmd transforms your DBML schema definitions into Mermaid Markdown diagrams, making it easy to include your database schema diagrams in documentation, GitHub READMEs, or any platform that supports Mermaid.
8
+
9
+ ## Installation
10
+
11
+ ## Usage
12
+
13
+ ### Basic Usage
14
+
15
+ Convert a single DBML file to Mermaid Markdown:
16
+
17
+ ```bash
18
+ dbml2mmd input.dbml
19
+ ```
20
+
21
+ This will create `input.mmd` in the same directory.
22
+
23
+ ### Specify Output File
24
+
25
+ ```bash
26
+ dbml2mmd input.dbml -o output.mmd
27
+ ```
28
+
29
+ ### Process Multiple Files
30
+
31
+ ```bash
32
+ dbml2mmd *.dbml
33
+ ```
34
+
35
+ ### Watch for Changes
36
+
37
+ ```bash
38
+ dbml2mmd input.dbml --watch
39
+ ```
40
+
41
+ ### Help
42
+
43
+ ```bash
44
+ dbml2mmd --help
45
+ ```
46
+
47
+ ## Example
48
+
49
+ ### Input (sample.dbml)
50
+
51
+ ```dbml
52
+ Table users {
53
+ id int [pk]
54
+ username varchar
55
+ email varchar
56
+ created_at timestamp
57
+ }
58
+
59
+ Table posts {
60
+ id int [pk]
61
+ title varchar
62
+ body text
63
+ user_id int [ref: > users.id]
64
+ created_at timestamp
65
+ }
66
+ ```
67
+
68
+ ### Output (sample.mmd)
69
+
70
+ ```
71
+ erDiagram
72
+ users {
73
+ int id PK
74
+ varchar username
75
+ varchar email
76
+ timestamp created_at
77
+ }
78
+
79
+ posts {
80
+ int id PK
81
+ varchar title
82
+ text body
83
+ int user_id FK
84
+ timestamp created_at
85
+ }
86
+
87
+ posts ||--o{ users : "user_id"
88
+ ```
89
+
90
+ ## Development
91
+
92
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
93
+
94
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
95
+
96
+ ## Contributing
97
+
98
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/ernie-brodeur-goxlabs/dbml2mmd>.
99
+
100
+ ## License
101
+
102
+ This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "dbml2mmd"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/dbml2mmd ADDED
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "dbml2mmd"
4
+ #!/usr/bin/env ruby
5
+
6
+ require 'json'
7
+ begin
8
+ require 'slop'
9
+ rescue LoadError
10
+ puts "Error: slop gem is required for CLI handling."
11
+ puts "Please install it using: gem install slop"
12
+ exit 1
13
+ end
14
+
15
+ # Try to load dbml gem if available
16
+ USING_GEM = begin
17
+ require 'dbml'
18
+ true
19
+ rescue LoadError
20
+ false
21
+ end
22
+
23
+ class DBMLParser
24
+ def self.parse(content)
25
+ if USING_GEM
26
+ # Use the dbml gem if available
27
+ parser = DBML::Parser.new
28
+ result = parser.parse(content)
29
+ convert_to_standard_format(result)
30
+ else
31
+ # Simple custom DBML parser implementation
32
+ parse_dbml(content)
33
+ end
34
+ end
35
+
36
+ # Convert dbml gem output to standard format
37
+ def self.convert_to_standard_format(result)
38
+ standard = { tables: [], refs: [] }
39
+
40
+ # Process tables
41
+ result.tables.each do |table|
42
+ fields = table.columns.map do |column|
43
+ {
44
+ name: column.name,
45
+ type: column.type,
46
+ attributes: column.settings&.join(',')
47
+ }
48
+ end
49
+
50
+ standard[:tables] << {
51
+ name: table.name,
52
+ fields: fields
53
+ }
54
+ end
55
+
56
+ # Process references
57
+ result.refs.each do |ref|
58
+ endpoint1 = ref.endpoints.first
59
+ endpoint2 = ref.endpoints.last
60
+
61
+ standard[:refs] << {
62
+ from: { table: endpoint1.tableName, field: endpoint1.columnName },
63
+ to: { table: endpoint2.tableName, field: endpoint2.columnName },
64
+ type: determine_relationship_type(ref)
65
+ }
66
+ end
67
+
68
+ standard
69
+ end
70
+
71
+ # Determine relationship type from dbml gem reference
72
+ def self.determine_relationship_type(ref)
73
+ endpoint1 = ref.endpoints.first
74
+ endpoint2 = ref.endpoints.last
75
+
76
+ if endpoint1.relation == '1' && endpoint2.relation == '1'
77
+ 'one_to_one'
78
+ elsif endpoint1.relation == '1' && endpoint2.relation == '*'
79
+ 'one_to_many'
80
+ elsif endpoint1.relation == '*' && endpoint2.relation == '1'
81
+ 'many_to_one'
82
+ elsif endpoint1.relation == '*' && endpoint2.relation == '*'
83
+ 'many_to_many'
84
+ else
85
+ 'one_to_one' # Default
86
+ end
87
+ end
88
+
89
+ # Simple DBML parser implementation
90
+ def self.parse_dbml(content)
91
+ result = { tables: [], refs: [] }
92
+
93
+ # Extract tables
94
+ table_blocks = content.scan(/Table\s+(\w+)\s*\{([^}]*)\}/m)
95
+ table_blocks.each do |table_name, table_content|
96
+ fields = []
97
+ field_lines = table_content.strip.split("\n")
98
+ field_lines.each do |line|
99
+ line = line.strip
100
+ next if line.empty?
101
+
102
+ # Basic field parsing
103
+ if line =~ /^(\w+)\s+(\w+)(?:\s+\[(.*)\])?/
104
+ field_name = $1
105
+ field_type = $2
106
+ attributes = $3
107
+
108
+ fields << {
109
+ name: field_name,
110
+ type: field_type,
111
+ attributes: attributes
112
+ }
113
+ end
114
+ end
115
+
116
+ result[:tables] << {
117
+ name: table_name,
118
+ fields: fields
119
+ }
120
+ end
121
+
122
+ # Extract references
123
+ ref_lines = content.scan(/Ref:\s*(.*?)$/m)
124
+ ref_lines.each do |ref_line|
125
+ ref_line = ref_line[0].strip
126
+ if ref_line =~ /(\w+)\.(\w+)\s*([<>])\s*(\w+)\.(\w+)/
127
+ from_table = $1
128
+ from_field = $2
129
+ type_symbol = $3
130
+ to_table = $4
131
+ to_field = $5
132
+
133
+ # Determine relationship type based on symbol
134
+ rel_type = case type_symbol
135
+ when '>' then 'many_to_one'
136
+ when '<' then 'one_to_many'
137
+ else 'one_to_one'
138
+ end
139
+
140
+ result[:refs] << {
141
+ from: { table: from_table, field: from_field },
142
+ to: { table: to_table, field: to_field },
143
+ type: rel_type
144
+ }
145
+ end
146
+ end
147
+
148
+ result
149
+ end
150
+ end
151
+
152
+ class Dbml2Mermaid
153
+ def initialize(options = {})
154
+ @options = options
155
+ @theme = options[:theme] || 'default'
156
+ end
157
+
158
+ def convert(dbml_content)
159
+ # Parse DBML content
160
+ dbml = DBMLParser.parse(dbml_content)
161
+ tables = dbml[:tables] || []
162
+ refs = dbml[:refs] || []
163
+
164
+ # Filter tables if specified
165
+ if @options[:only_tables] && !@options[:only_tables].empty?
166
+ table_list = @options[:only_tables].split(',').map(&:strip)
167
+ tables = tables.select { |table| table_list.include?(table[:name]) }
168
+ # Only keep references between included tables
169
+ refs = refs.select do |ref|
170
+ table_list.include?(ref[:from][:table]) && table_list.include?(ref[:to][:table])
171
+ end
172
+ end
173
+
174
+ # Generate Mermaid ERD
175
+ mermaid = ["erDiagram"]
176
+
177
+ # Add tables and their fields
178
+ tables.each do |table|
179
+ table_fields = []
180
+ table[:fields].each do |field|
181
+ field_type = field[:type].to_s.gsub(/\s+/, "")
182
+
183
+ # Check for primary key attribute and format appropriately
184
+ is_pk = field[:attributes]&.to_s&.include?('primary key')
185
+ is_fk = refs.any? { |ref| ref[:from][:table] == table[:name] && ref[:from][:field] == field[:name] }
186
+
187
+ field_annotation = []
188
+ field_annotation << "PK" if is_pk
189
+ field_annotation << "FK" if is_fk
190
+
191
+ annotation_str = field_annotation.empty? ? "" : " #{field_annotation.join(',')}"
192
+
193
+ # Format the field with proper spacing
194
+ table_fields << " #{field[:name]} #{field_type}#{annotation_str}"
195
+ end
196
+
197
+ mermaid << " #{table[:name]} {"
198
+ mermaid.concat(table_fields)
199
+ mermaid << " }"
200
+ end
201
+
202
+ # Add relationships with proper labels
203
+ refs.each do |ref|
204
+ from_table = ref[:from][:table]
205
+ to_table = ref[:to][:table]
206
+ relationship_type = get_relationship_type(ref[:type])
207
+
208
+ # Create a more descriptive label if available
209
+ label = "\"#{ref[:from][:field]} -> #{ref[:to][:field]}\""
210
+ if ref[:name]
211
+ label = "\"#{ref[:name]}: #{ref[:from][:field]} -> #{ref[:to][:field]}\""
212
+ end
213
+
214
+ mermaid << " #{from_table} #{relationship_type} #{to_table} : #{label}"
215
+ end
216
+
217
+ # Add a header comment with generation info and theme
218
+ theme_config = get_theme_config(@theme)
219
+ header = [
220
+ "%%{init: #{theme_config}}%%",
221
+ "% Generated by dbml2mmd on #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}",
222
+ "% https://mermaid.js.org/syntax/entityRelationshipDiagram.html"
223
+ ]
224
+
225
+ # Return the full diagram
226
+ (header + [mermaid.join("\n")]).join("\n")
227
+ end
228
+
229
+ def output_html
230
+ return nil unless @options[:html_output]
231
+
232
+ <<-HTML
233
+ <!DOCTYPE html>
234
+ <html>
235
+ <head>
236
+ <meta charset="utf-8">
237
+ <title>Database Diagram</title>
238
+ <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
239
+ <style>
240
+ body { font-family: sans-serif; margin: 20px; }
241
+ .mermaid { margin: 20px auto; }
242
+ </style>
243
+ </head>
244
+ <body>
245
+ <h1>Database Diagram</h1>
246
+ <p>Generated on #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}</p>
247
+ <div class="mermaid">
248
+ #{@last_output}
249
+ </div>
250
+ <script>
251
+ mermaid.initialize({ startOnLoad: true });
252
+ </script>
253
+ </body>
254
+ </html>
255
+ HTML
256
+ end
257
+
258
+ private
259
+
260
+ def get_relationship_type(type)
261
+ case type
262
+ when 'one_to_one'
263
+ "||--||"
264
+ when 'one_to_many'
265
+ "||--o{"
266
+ when 'many_to_one'
267
+ "}o--||"
268
+ when 'many_to_many'
269
+ "}o--o{"
270
+ else
271
+ "--"
272
+ end
273
+ end
274
+
275
+ def get_theme_config(theme)
276
+ case theme
277
+ when 'dark'
278
+ "{'theme': 'dark', 'themeVariables': { 'primaryColor': '#2A2A2A', 'primaryBorderColor': '#777', 'primaryTextColor': '#DDD' }}"
279
+ when 'neutral'
280
+ "{'theme': 'neutral', 'themeVariables': { 'primaryColor': '#f4f4f4', 'primaryBorderColor': '#888', 'primaryTextColor': '#333' }}"
281
+ when 'forest'
282
+ "{'theme': 'forest', 'themeVariables': { 'primaryColor': '#e6f5e6', 'primaryBorderColor': '#4d994d', 'primaryTextColor': '#1a331a' }}"
283
+ else # default theme
284
+ "{'theme': 'default', 'themeVariables': { 'primaryColor': '#f4f4f4', 'primaryBorderColor': '#aaa', 'primaryTextColor': '#333' }}"
285
+ end
286
+ end
287
+ end
288
+
289
+ # Parse command-line arguments with Slop
290
+ begin
291
+ opts = Slop.parse do |o|
292
+ o.banner = "Usage: dbml2mmd [options] [input_file]"
293
+
294
+ o.string '-o', '--output', 'Output to file instead of stdout'
295
+ o.bool '-h', '--help', 'Show this help message', default: false
296
+ o.string '-t', '--theme', 'Mermaid theme (default, dark, neutral, forest)', default: 'default'
297
+ o.bool '--html', 'Generate HTML output with embedded Mermaid viewer', default: false
298
+ o.string '--only', 'Only include specific tables (comma-separated list)'
299
+ o.bool '-v', '--verbose', 'Enable verbose output', default: false
300
+
301
+ o.on '--version', 'Print the version' do
302
+ puts "DBML to Mermaid Converter v0.1.0"
303
+ exit
304
+ end
305
+ end
306
+
307
+ # Show help by default when no arguments are provided
308
+ if (ARGV.empty? && STDIN.tty?) || opts.help?
309
+ puts opts
310
+ puts "\nExamples:"
311
+ puts " dbml2mmd input.dbml # Convert file and output to stdout"
312
+ puts " dbml2mmd -o output.mmd input.dbml # Convert file and save to output.mmd"
313
+ puts " dbml2mmd --html -o output.html input.dbml # Generate HTML with Mermaid viewer"
314
+ puts " dbml2mmd --theme dark input.dbml # Use dark theme for diagram"
315
+ puts " dbml2mmd --only users,posts input.dbml # Only include specific tables"
316
+ puts " cat input.dbml | dbml2mmd # Read from stdin and output to stdout"
317
+
318
+ if USING_GEM
319
+ puts "\nUsing dbml gem for DBML parsing."
320
+ else
321
+ puts "\nUsing built-in DBML parser (limited functionality)."
322
+ puts "For full DBML support, install the dbml gem: gem install dbml"
323
+ end
324
+
325
+ exit
326
+ end
327
+
328
+ # Get input from file or stdin
329
+ input = nil
330
+ if ARGV.empty?
331
+ input = ARGF.read
332
+ else
333
+ input = File.read(ARGV[0])
334
+ rescue Errno::ENOENT
335
+ puts "Error: File not found: #{ARGV[0]}"
336
+ exit 1
337
+ end
338
+
339
+ # Setup converter options
340
+ converter_options = {
341
+ theme: opts[:theme],
342
+ html_output: opts[:html],
343
+ only_tables: opts[:only],
344
+ verbose: opts[:verbose]
345
+ }
346
+
347
+ # Print verbose info if enabled
348
+ if opts[:verbose]
349
+ puts "Input source: #{ARGV.empty? ? 'STDIN' : ARGV[0]}"
350
+ puts "Using DBML gem: #{USING_GEM}"
351
+ puts "Theme: #{opts[:theme]}"
352
+ puts "HTML output: #{opts[:html]}"
353
+ puts "Filtering tables: #{opts[:only] ? opts[:only] : 'No'}"
354
+ end
355
+
356
+ # Convert DBML to Mermaid
357
+ converter = Dbml2Mermaid.new(converter_options)
358
+ mermaid = converter.convert(input)
359
+ converter.instance_variable_set(:@last_output, mermaid)
360
+
361
+ # Output result
362
+ if opts[:output]
363
+ if opts[:html]
364
+ File.write(opts[:output], converter.output_html)
365
+ else
366
+ File.write(opts[:output], mermaid)
367
+ end
368
+ puts "Output written to #{opts[:output]}"
369
+ else
370
+ puts mermaid
371
+ end
372
+
373
+ rescue Slop::Error => e
374
+ puts "Error: #{e.message}"
375
+ puts opts
376
+ exit 1
377
+ rescue => e
378
+ puts "Error: #{e.message}"
379
+ puts opts
380
+ exit 1
381
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbml2Mmd
4
+ VERSION = "0.1.0"
5
+ end
data/lib/dbml2mmd.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dbml2mmd/version"
4
+
5
+ module Dbml2mmd
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dbml2mmd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ernie Brodeur
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-02-27 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: slop
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '4.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '4.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: dbml
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.1'
40
+ description: A tool to convert Database Markup Language (DBML) schemas to Mermaid
41
+ Entity Relationship Diagrams (ERD) for visualization
42
+ email:
43
+ - ebrodeur@ujami.net
44
+ executables:
45
+ - dbml2mmd
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - LICENSE
50
+ - README.md
51
+ - bin/console
52
+ - bin/setup
53
+ - exe/dbml2mmd
54
+ - lib/dbml2mmd.rb
55
+ - lib/dbml2mmd/version.rb
56
+ homepage: https://github.com/ebrodeur/dbml2mmd
57
+ licenses:
58
+ - GPL-3.0
59
+ metadata:
60
+ allowed_push_host: https://rubygems.org
61
+ homepage_uri: https://github.com/ebrodeur/dbml2mmd
62
+ source_code_uri: https://github.com/ebrodeur/dbml2mmd
63
+ changelog_uri: https://github.com/ebrodeur/dbml2mmd/blob/main/CHANGELOG.md
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 2.5.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.6.2
79
+ specification_version: 4
80
+ summary: Convert DBML database schemas to Mermaid ERD diagrams
81
+ test_files: []