jekyll-database-tables 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: 6b6ef16fd80c0260fe03a2d55fe293a39c7ddfc0350e1ace10199052b034bf0e
4
+ data.tar.gz: d6e6b26601c76f96bc19bb40ce06fda25bb6cb2de40578579219e93b9870f6d5
5
+ SHA512:
6
+ metadata.gz: '08d3dcf78d286c9e7c1d522bd8ec38686e5aca1c64614b9a38b44556770a75fa87c76046dc8cb2891c0cbf771e2c2c47069db90c6fb85105b3c9fbd98528a0f6'
7
+ data.tar.gz: 718f97cbe8be09c7e721137d6a77a9c3559a6cf67a94116393ee9f0c94c2336240c96eee714ef9c8ae7cd452f1ca9103e16e421cbddf549308d8dd4ad42651bf
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gustavo Aguiar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # jekyll-database-tables
2
+
3
+ Render Markdown pipe tables as databse-styled terminal output inside `<pre>` blocks for [Jekyll](https://jekyllrb.com/).
4
+
5
+ ## Installation
6
+
7
+ Install via RubyGems:
8
+
9
+ ``` bash
10
+ gem install jekyll-database-tables
11
+ ```
12
+
13
+ Or add it to your `Gemfile`:
14
+
15
+ ``` ruby
16
+ gem 'jekyll-database-tables'
17
+ ```
18
+
19
+ Then enable the plugin in `_config.yml`:
20
+
21
+ ``` yaml
22
+ plugins:
23
+ - jekyll-database-tables
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ Write a standard GFM pipe table in any page or document:
29
+
30
+ ``` markdown
31
+ | Name | Age |
32
+ |-------|-----|
33
+ | Alice | 30 |
34
+ | Bob | 25 |
35
+ ```
36
+
37
+ The plugin replaces it with a `<pre class="[db]-table">` block during the build:
38
+
39
+ ``` html
40
+ Name | Age
41
+ ------+----
42
+ Alice | 30
43
+ Bob | 25
44
+ ```
45
+
46
+ Content inside fenced code blocks is never processed, so example tables in documentation stay intact.
47
+
48
+ ### Configuration
49
+
50
+ All options go under the `jekyll_database_tables` key in `_config.yml`:
51
+
52
+ ``` yaml
53
+ jekyll_database_tables:
54
+ formatter: psql
55
+ ```
56
+
57
+ #### Formatters
58
+
59
+ - **Default (`psql`)**: PostgreSQL-styled terminal output with space-padded columns separated by `|` and a `-+-` divider.
60
+
61
+ Custom formatters can also be provided (see [Development](#development)).
62
+
63
+ ## Development
64
+
65
+ ### Nix
66
+
67
+ ``` bash
68
+ git clone https://github.com/gdiasag/jekyll-database-tables
69
+ cd jekyll-database-tables
70
+ nix develop
71
+ rake test
72
+ ```
73
+
74
+ Starting a shell with the build environment provided in `flake.nix` provides you with Ruby, Bundler, and all gem dependencies pinned to exact versions from the lock file, so no `bundle install` needed. Gem executables (e.g. `rake` and `rubocop`) are on `PATH` directly.
75
+
76
+ ### Others
77
+
78
+ Requires [Ruby] v3.3+ and [Bundler] v2.7+.
79
+
80
+ ``` bash
81
+ git clone https://github.com/gdiasag/jekyll-database-tables
82
+ cd jekyll-database-tables
83
+ bundle install
84
+ bundle exec rake test
85
+ ```
86
+
87
+ [Ruby]: https://www.ruby-lang.org/en
88
+ [Bundler]: https://bundler.io
89
+
90
+ ### Writing a custom formatter
91
+
92
+ Create a subclass of `Jekyll::DatabaseTables::Formatter` implementing `#render`, `#format_row`, and `#separator`. Then register it in `Jekyll::DatabaseTables::Converter#formatter_instance`:
93
+
94
+ ``` ruby
95
+ class CustomFormatter < Jekyll::DatabaseTables::Formatter
96
+ include Formatter::Helpers
97
+
98
+ def render(table, title: nil)
99
+ # ...
100
+ end
101
+
102
+ def format_row(cells, widths)
103
+ # ...
104
+ end
105
+
106
+ def separator(widths)
107
+ # ...
108
+ end
109
+ end
110
+ ```
111
+
112
+ Users select it via `_config.yml`:
113
+
114
+ ``` yaml
115
+ jekyll_database_tables:
116
+ formatter: custom
117
+ ```
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module DatabaseTables
5
+ # Scans document content and replaces Markdown pipe tables with
6
+ # database-style terminal table HTML.
7
+ #
8
+ # Lines inside fenced blocks (+```+ or +~~~+) are never treated as tables,
9
+ # regardless of whether they contain pipe characters.
10
+ class Converter
11
+ FENCE_PATTERN = /^\s*(`{3,}|~{3,})/
12
+
13
+ # Convenience method - equivalent to +Converter.new(formatter:).call(content)+.
14
+ #
15
+ # @param content [String] the raw document content
16
+ # @param formatter [String] the formatter to use (default: +'psql'+)
17
+ # @return [String] the content with any Markdown tables converted
18
+ def self.call(content, formatter: 'psql')
19
+ new(formatter:).call(content)
20
+ end
21
+
22
+ def initialize(formatter: 'psql')
23
+ @formatter = formatter
24
+ end
25
+
26
+ # Scans +content+ line by line, buffering pipe-containing lines and
27
+ # flushing the buffer through {Parser} and {Formatter} whenever a
28
+ # non-pipe line (or end of input) is reached.
29
+ #
30
+ # @param content [String] the raw document content
31
+ # @return [String] the content with any Markdown tables converted
32
+ def call(content)
33
+ lines = content.lines
34
+ result = +''
35
+ buffer = []
36
+ fenced = false
37
+
38
+ lines.each do |line|
39
+ if fenced || !line.include?('|')
40
+ flush_buffer(result, buffer, line)
41
+ fenced = !fenced if fence_line?(line)
42
+ else
43
+ buffer << line
44
+ end
45
+ end
46
+
47
+ flush_buffer(result, buffer)
48
+ result
49
+ end
50
+
51
+ private
52
+
53
+ def fence_line?(line)
54
+ line.match?(FENCE_PATTERN)
55
+ end
56
+
57
+ def flush_buffer(result, buffer, line = nil)
58
+ result << process_buffer(buffer)
59
+ buffer.clear
60
+ result << line if line
61
+ end
62
+
63
+ def process_buffer(buffer)
64
+ return buffer.join if buffer.empty?
65
+
66
+ table = Parser.parse(buffer)
67
+
68
+ if table.nil? && buffer.any? { |l| l.match?(Parser::SEPARATOR_PATTERN) }
69
+ warn "[jekyll-database-tables] Could not parse table: #{buffer.join}"
70
+ end
71
+
72
+ table ? formatter_instance.render(table) : buffer.join
73
+ end
74
+
75
+ def formatter_instance
76
+ case @formatter
77
+ when 'psql' then PsqlFormatter.new
78
+ else raise ArgumentError, "Unknown formatter: #{@formatter.inspect}"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module DatabaseTables
5
+ # Abstract base class for table formatters.
6
+ #
7
+ # Subclasses must implement {#render}, {#format_row}, and {#separator}.
8
+ # Shared rendering utilities are available via {Formatter::Helpers}.
9
+ class Formatter
10
+ # Optional mixin providing geometry utilities for formatter subclasses.
11
+ #
12
+ # Include this modules to gain access to {#column_widths}, which is useful
13
+ # for any formatter that needs to align output by column.
14
+ module Helpers
15
+ # Computes the minimum column widths needed to fit all cell values.
16
+ #
17
+ # @param table [Table] the table to measure
18
+ # @return [Array<Integer>] the maximum cell width for each column, in order
19
+ def column_widths(table)
20
+ col_count = table.headers.length
21
+ all_rows = [table.headers] + table.rows.map(&:cells)
22
+
23
+ (0...col_count).map do |index|
24
+ all_rows.map { |row| row[index].to_s.length }.max
25
+ end
26
+ end
27
+ end
28
+
29
+ # Renders a {Table} to a string.
30
+ #
31
+ # @param table [Table] the table to render
32
+ # @param title [String, nil] an optional title to display above headers
33
+ # @return [String] the rendered output
34
+ # @raise [NotImplementedError] if the subclass does not implement this method
35
+ def render(table, title: nil)
36
+ raise NotImplementedError, "#{self.class} must implement #render"
37
+ end
38
+
39
+ # Formats a single row of cells into a string
40
+ #
41
+ # @param cells [Array<#to_s>] the cell values to render
42
+ # @param widths [Array<Integer>] the column widths to align against
43
+ # @return [String] the formatted row
44
+ # @raise [NotImplementedError] if the subclass does not implement this method
45
+ def format_row(cells, widths)
46
+ raise NotImplementedError, "#{self.class} must implement #format_row"
47
+ end
48
+
49
+ # Renders the separator line between headers and data rows.
50
+ #
51
+ # @param widths [Array<Integer>] the column widths to size the separator against
52
+ # @return [String] the separator line
53
+ # @raise [NotImplementedError] if the subclass does not implement this method
54
+ def separator(widths)
55
+ raise NotImplementedError, "#{self.class} must implement #separator"
56
+ end
57
+ end
58
+
59
+ # Renders a {Table} as a psql-style terminal table inside a +<pre>+ block.
60
+ #
61
+ # Output uses space-padded columns separated by +|+, with a +-+-+ divider
62
+ # between the header and data rows. Intended to evoke a database query result.
63
+ #
64
+ # @example
65
+ # puts PsqlFormatter.new.render(table)
66
+ #
67
+ # <pre class="psql-table">
68
+ # Name | Age
69
+ # ------+----
70
+ # Alice | 30
71
+ # </pre>
72
+ class PsqlFormatter < Formatter
73
+ include Formatter::Helpers
74
+
75
+ # Renders the table as a +<pre class="psql-table">+ block.
76
+ #
77
+ # @param table [Table] the table to render
78
+ # @param title [String, nil] an optional title, centered above the headers
79
+ # @return [String] the HTML +<pre>+ block
80
+ def render(table, title: nil)
81
+ widths = column_widths(table)
82
+
83
+ lines = +''
84
+ lines << "#{title.center(total_width(widths)).rstrip}\n" if title
85
+
86
+ lines << format_row(table.headers, widths)
87
+ lines << "\n"
88
+ lines << separator(widths)
89
+ lines << "\n"
90
+
91
+ table.rows.each do |row|
92
+ lines << format_row(row.cells, widths)
93
+ lines << "\n"
94
+ end
95
+
96
+ "<pre class=\"psql-table\">\n#{lines}</pre>\n"
97
+ end
98
+
99
+ # Formats a row as space-padded cells joined by +|+.
100
+ #
101
+ # @param cells [Array<#to_s>] the cell values to render
102
+ # @param widths [Array<Integer>] the column widths to left-justify against
103
+ # @return [String] the formatted row, with trailing whitespace stripped
104
+ def format_row(cells, widths)
105
+ cells.each_with_index.map do |cell, i|
106
+ cell.to_s.ljust(widths[i])
107
+ end.join(' | ').rstrip
108
+ end
109
+
110
+ # Renders a +-+-+ separator sized to the given column widths.
111
+ #
112
+ # @param widths [Array<Integer>] the column widths
113
+ # @return [String] the separator line
114
+ def separator(widths)
115
+ widths.map { |w| '-' * w }.join('-+-')
116
+ end
117
+
118
+ private
119
+
120
+ def total_width(widths)
121
+ widths.sum + ((widths.length - 1) * 3)
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module DatabaseTables
5
+ # Parsers an array of Markdown table lines into a {Table}.
6
+ #
7
+ # Accepts both fenced (`| col |`) and unfenced (`col |`) pipe styles.
8
+ # Returns +nil+ for any input that does not constitute a valid table.
9
+ module Parser
10
+ # Matches a GFM table separator line.
11
+ SEPARATOR_PATTERN = /^\|?[\s:-]+\|/
12
+
13
+ module_function
14
+
15
+ # Parses a buffer of lines into a {Table}.
16
+ #
17
+ # Expects at least two lines where the second matches {SEPARATOR_PATTERN}.
18
+ # Any additional lines are treated as data rows.
19
+ #
20
+ # @param lines [Array<String>] raw lines from the document buffer
21
+ # @return [Table, nil] the parsed table, or +nil+ if the input isn't valid
22
+ def parse(lines)
23
+ return if lines.length < 2
24
+ return unless lines[1].match?(SEPARATOR_PATTERN)
25
+
26
+ headers = split_cells(lines[0])
27
+ rows = lines.drop(2).map { |line| Row.new(split_cells(line)) }
28
+
29
+ Table.new(headers:, rows:)
30
+ end
31
+
32
+ def split_cells(line)
33
+ line
34
+ .strip
35
+ .delete_prefix('|')
36
+ .delete_suffix('|')
37
+ .split('|')
38
+ .map(&:strip)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module DatabaseTables
5
+ # Immutable value object representing a parsed Markdown file.
6
+ #
7
+ # @!attribute [r] headers
8
+ # @return [Array<String>] the column header labels
9
+ # @!attribute [r] rows
10
+ # @return [Array<Row>] the data rows, in document order
11
+ Table = Data.define(:headers, :rows)
12
+
13
+ # Immutable value object representing a single table row.
14
+ #
15
+ # @!attribute [r] cells
16
+ # @return [Array<String>] the cell values for this row, in column order
17
+ Row = Data.define(:cells)
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module DatabaseTables
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll'
4
+
5
+ require 'jekyll-database-tables/version'
6
+ require 'jekyll-database-tables/table'
7
+ require 'jekyll-database-tables/parser'
8
+ require 'jekyll-database-tables/formatters'
9
+ require 'jekyll-database-tables/converter'
10
+
11
+ Jekyll::Hooks.register [:pages, :documents], :pre_render do |doc|
12
+ formatter = doc.site.config.dig('jekyll_database_tables', 'formatter') || 'psql'
13
+
14
+ doc.content = Jekyll::DatabaseTables::Converter.call(doc.content, formatter:)
15
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-database-tables
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gustavo Aguiar
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: jekyll
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '3.9'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '5.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '3.9'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '5.0'
32
+ email:
33
+ - gdiasag@gmail.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files:
37
+ - LICENSE
38
+ - README.md
39
+ files:
40
+ - LICENSE
41
+ - README.md
42
+ - lib/jekyll-database-tables.rb
43
+ - lib/jekyll-database-tables/converter.rb
44
+ - lib/jekyll-database-tables/formatters.rb
45
+ - lib/jekyll-database-tables/parser.rb
46
+ - lib/jekyll-database-tables/table.rb
47
+ - lib/jekyll-database-tables/version.rb
48
+ homepage: https://github.com/gdiasag/jekyll-database-tables
49
+ licenses:
50
+ - MIT
51
+ metadata:
52
+ rubygems_mfa_required: 'true'
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '3.1'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 4.0.14
68
+ specification_version: 4
69
+ summary: A Jekyll plugin to render markdown tables as database-styled terminal outputs
70
+ test_files: []