markawesome 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: 0bfe7629915ad2e621b40c4bc478c90f00313fe5f19ff46739cf1d9ddbff4583
4
+ data.tar.gz: b898ba0b05ba42755ddee581d9cc10cfa8a405df6b29e0d7bfad9f6e86523ec3
5
+ SHA512:
6
+ metadata.gz: 24fc7fb6409b036053e0bfd475e1ea287c0cf34e28870ef11e2767ae78bb990c5a7038b5a84ecd776d976aeacc53afc531a6cab3a19a2f962f4845a8e27ea2e1
7
+ data.tar.gz: 5efa1542577c5bbbaf6417cc4c0b145a6c66725eca661b3b48241e791000eff6a4f66c7d1aac29971f45cf20a152b7ece83597f9a8ae426f0ab3c4fea94d8465
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - 2025-10-27
8
+
9
+ ### Added
10
+
11
+ - Initial release
12
+ - Core transformer functionality extracted from jekyll-webawesome
13
+ - Support for Web Awesome components:
14
+ - Badges
15
+ - Buttons
16
+ - Callouts
17
+ - Cards
18
+ - Comparisons
19
+ - Copy Buttons
20
+ - Details
21
+ - Icons
22
+ - Tabs
23
+ - Tags
24
+ - Configuration system for customizing component behavior
25
+ - Framework-agnostic Markdown to Web Awesome transformation
data/LICENSE.txt ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Janne Warén
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Markawesome
2
+
3
+ Markawesome is a Ruby library that transforms custom Markdown syntax into Web Awesome components. It provides a framework-agnostic way to convert Markdown to Web Awesome HTML.
4
+
5
+ Used as the transformation engine behind the [jekyll-webawesome](https://github.com/jannewaren/jekyll-webawesome) plugin for [Jekyll](https://jekyllrb.com/).
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'markawesome'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```bash
18
+ bundle install
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```bash
24
+ gem install markawesome
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ require 'markawesome'
31
+
32
+ content = <<~MARKDOWN
33
+ :::info
34
+ This is an info callout!
35
+ :::
36
+ MARKDOWN
37
+
38
+ transformed = Markawesome::Transformer.process(content)
39
+ ```
40
+
41
+ ## Supported Components
42
+
43
+ | Component | Primary Syntax | Alternative Syntax | HTML Output |
44
+ |-----------|----------------|-------------------|-------------|
45
+ | **Badge** | `!!!variant` | `:::wa-badge variant` | `<wa-badge variant="brand">content</wa-badge>` |
46
+ | **Button** | `%%%variant` | `:::wa-button variant` | `<wa-button variant="brand" href="url">text</wa-button>` or `<wa-button variant="brand">text</wa-button>` |
47
+ | **Callouts** | `:::info` | `:::wa-callout info` | `<wa-callout variant="brand"><wa-icon name="circle-info"></wa-icon>content</wa-callout>` |
48
+ | **Card** | `===` | `:::wa-card` | `<wa-card>content</wa-card>` |
49
+ | **Comparison** | `\|\|\|` or `\|\|\|25` | `:::wa-comparison` or `:::wa-comparison 25` | `<wa-comparison>` with before/after slots |
50
+ | **Copy Button** | `<<<` | `:::wa-copy-button` | `<wa-copy-button value="content">content</wa-copy-button>` |
51
+ | **Details** | `^^^appearance? icon-placement?` | `:::wa-details appearance? icon-placement?` | `<wa-details appearance="..." icon-placement="...">content</wa-details>` |
52
+ | **Tab Group** | `++++++` | `:::wa-tabs` | `<wa-tab-group><wa-tab>content</wa-tab></wa-tab-group>` |
53
+ | **Tag** | `@@@brand` | `:::wa-tag brand` | `<wa-tag variant="brand">content</wa-tag>` |
54
+
55
+ ## Configuration
56
+
57
+ ```ruby
58
+ Markawesome.configure do |config|
59
+ config.callout_icons = {
60
+ info: 'circle-info',
61
+ success: 'circle-check',
62
+ neutral: 'gear',
63
+ warning: 'triangle-exclamation',
64
+ danger: 'circle-exclamation'
65
+ }
66
+ end
67
+ ```
68
+
69
+ ## Contributing
70
+
71
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jannewaren/markawesome.
72
+
73
+ ## License
74
+
75
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kramdown'
4
+ require_relative 'transformers'
5
+
6
+ module Markawesome
7
+ # Main transformer that orchestrates all component transformers
8
+ class Transformer
9
+ def self.process(content)
10
+ content = BadgeTransformer.transform(content)
11
+ content = ButtonTransformer.transform(content)
12
+ content = CalloutTransformer.transform(content)
13
+ content = CardTransformer.transform(content)
14
+ content = ComparisonTransformer.transform(content)
15
+ content = CopyButtonTransformer.transform(content)
16
+ content = DetailsTransformer.transform(content)
17
+ content = IconTransformer.transform(content)
18
+ content = TagTransformer.transform(content)
19
+ TabsTransformer.transform(content)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms badge syntax into wa-badge elements
7
+ # Primary syntax: !!!variant?\ncontent\n!!!
8
+ # Alternative syntax: :::wa-badge variant?\ncontent\n:::
9
+ # Variants: brand, success, neutral, warning, danger
10
+ class BadgeTransformer < BaseTransformer
11
+ def self.transform(content)
12
+ # Define both regex patterns
13
+ primary_regex = /^!!!(brand|success|neutral|warning|danger)?\n(.*?)\n!!!/m
14
+ alternative_regex = /^:::wa-badge\s*(brand|success|neutral|warning|danger)?\n(.*?)\n:::/m
15
+
16
+ # Define shared transformation logic
17
+ transform_proc = proc do |variant, badge_content|
18
+ badge_content = badge_content.strip
19
+
20
+ build_badge_html(badge_content, variant)
21
+ end
22
+
23
+ # Apply both patterns
24
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
25
+ apply_multiple_patterns(content, patterns)
26
+ end
27
+
28
+ class << self
29
+ private
30
+
31
+ def build_badge_html(content, variant)
32
+ variant_attr = variant ? " variant=\"#{variant}\"" : ''
33
+ badge_html = markdown_to_html(content).strip
34
+
35
+ # Remove paragraph tags if the content is just text
36
+ badge_html = badge_html.gsub(%r{^<p>(.*)</p>$}m, '\1')
37
+
38
+ # Fix whitespace issues in Web Awesome badges by ensuring proper spacing
39
+ # Replace spaces after closing tags with non-breaking spaces to prevent CSS collapse
40
+ badge_html = badge_html.gsub(%r{(</\w+>)\s+}, '\1&nbsp;')
41
+
42
+ "<wa-badge#{variant_attr}>#{badge_html}</wa-badge>"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'kramdown'
5
+
6
+ module Markawesome
7
+ # Base class for all Web Awesome component transformers
8
+ # Each transformer should implement the transform method
9
+ class BaseTransformer
10
+ def self.transform(content)
11
+ raise NotImplementedError, 'Subclasses must implement the transform method'
12
+ end
13
+
14
+ class << self
15
+ protected
16
+
17
+ # Helper method to convert markdown content to HTML
18
+ def markdown_to_html(content)
19
+ Kramdown::Document.new(content).to_html
20
+ end
21
+
22
+ # Helper method to apply multiple regex patterns with the same transformation logic
23
+ # @param content [String] The content to transform
24
+ # @param patterns [Array<Hash>] Array of pattern hashes with :regex and :block
25
+ # @return [String] The transformed content
26
+ def apply_multiple_patterns(content, patterns)
27
+ patterns.each do |pattern|
28
+ content = content.gsub(pattern[:regex]) do |match|
29
+ pattern[:block].call(match, $LAST_MATCH_INFO)
30
+ end
31
+ end
32
+ content
33
+ end
34
+
35
+ # Helper method to create both primary and alternative syntax patterns
36
+ # @param primary_regex [Regexp] The primary syntax regex
37
+ # @param alternative_regex [Regexp] The alternative syntax regex
38
+ # @param transform_proc [Proc] The proc that takes captured groups and returns HTML
39
+ # @return [Array<Hash>] Array of pattern hashes
40
+ def dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
41
+ [
42
+ {
43
+ regex: primary_regex,
44
+ block: proc do |_match, matchdata|
45
+ # Get all captured groups from the matchdata
46
+ captures = matchdata.captures
47
+ transform_proc.call(*captures)
48
+ end
49
+ },
50
+ {
51
+ regex: alternative_regex,
52
+ block: proc do |_match, matchdata|
53
+ # Get all captured groups from the matchdata
54
+ captures = matchdata.captures
55
+ transform_proc.call(*captures)
56
+ end
57
+ }
58
+ ]
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms button syntax into wa-button elements
7
+ # Primary syntax: %%%variant?\ncontent\n%%%
8
+ # Alternative syntax: :::wa-button variant?\ncontent\n:::
9
+ # Variants: brand, success, neutral, warning, danger
10
+ #
11
+ # Link buttons: %%%brand\n[Text](url)\n%%%
12
+ # Regular buttons: %%%brand\nText\n%%%
13
+ class ButtonTransformer < BaseTransformer
14
+ def self.transform(content)
15
+ # Define both regex patterns
16
+ primary_regex = /^%%%(brand|success|neutral|warning|danger)?\n(.*?)\n%%%/m
17
+ alternative_regex = /^:::wa-button\s*(brand|success|neutral|warning|danger)?\n(.*?)\n:::/m
18
+
19
+ # Define shared transformation logic
20
+ transform_proc = proc do |variant, button_content|
21
+ button_content = button_content.strip
22
+
23
+ build_button_html(button_content, variant)
24
+ end
25
+
26
+ # Apply both patterns
27
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
28
+ apply_multiple_patterns(content, patterns)
29
+ end
30
+
31
+ class << self
32
+ private
33
+
34
+ def build_button_html(content, variant)
35
+ variant_attr = variant ? " variant=\"#{variant}\"" : ''
36
+
37
+ # Check if content contains a markdown link
38
+ link_match = content.match(/^\[([^\]]+)\]\(([^)]+)\)$/)
39
+
40
+ if link_match
41
+ # It's a link button
42
+ link_text = link_match[1]
43
+ link_url = link_match[2]
44
+
45
+ # Process any markdown in the link text (bold, italic, etc.)
46
+ button_html = markdown_to_html(link_text).strip
47
+ button_html = button_html.gsub(%r{^<p>(.*)</p>$}m, '\1')
48
+
49
+ # Fix whitespace issues like in badges
50
+ button_html = button_html.gsub(%r{(</\w+>)\s+}, '\1&nbsp;')
51
+
52
+ "<wa-button#{variant_attr} href=\"#{link_url}\">#{button_html}</wa-button>"
53
+ else
54
+ # It's a regular button
55
+ button_html = markdown_to_html(content).strip
56
+ button_html = button_html.gsub(%r{^<p>(.*)</p>$}m, '\1')
57
+
58
+ # Fix whitespace issues like in badges
59
+ button_html = button_html.gsub(%r{(</\w+>)\s+}, '\1&nbsp;')
60
+
61
+ "<wa-button#{variant_attr}>#{button_html}</wa-button>"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms callout syntax into wa-callout elements
7
+ # Primary syntax: :::variant\ncontent\n:::
8
+ # Alternative syntax: :::wa-callout variant\ncontent\n:::
9
+ # Variants: info, success, neutral, warning, danger
10
+ class CalloutTransformer < BaseTransformer
11
+ def self.transform(content)
12
+ # Define both regex patterns
13
+ primary_regex = /^:::(info|success|neutral|warning|danger)\n(.*?)\n:::/m
14
+ alternative_regex = /^:::wa-callout\s+(info|success|neutral|warning|danger)\n(.*?)\n:::/m
15
+
16
+ # Define shared transformation logic
17
+ transform_proc = proc do |variant, inner_content|
18
+ attrs = callout_attributes(variant)
19
+
20
+ element_tag = "wa-callout#{attrs[:additional_params]}"
21
+ html_content = "#{attrs[:inner_prepend]}#{markdown_to_html(inner_content)}"
22
+
23
+ "<#{element_tag}>#{html_content}</wa-callout>"
24
+ end
25
+
26
+ # Apply both patterns
27
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
28
+ apply_multiple_patterns(content, patterns)
29
+ end
30
+
31
+ class << self
32
+ private
33
+
34
+ def callout_attributes(variant)
35
+ config = Markawesome.configuration
36
+ icons = config&.callout_icons || default_callout_icons
37
+
38
+ case variant
39
+ when 'info'
40
+ {
41
+ additional_params: ' variant="brand"',
42
+ inner_prepend: "<wa-icon slot=\"icon\" name=\"#{icons[:info]}\" variant=\"solid\"></wa-icon>"
43
+ }
44
+ when 'success'
45
+ {
46
+ additional_params: ' variant="success"',
47
+ inner_prepend: "<wa-icon slot=\"icon\" name=\"#{icons[:success]}\" variant=\"solid\"></wa-icon>"
48
+ }
49
+ when 'neutral'
50
+ {
51
+ additional_params: ' variant="neutral"',
52
+ inner_prepend: "<wa-icon slot=\"icon\" name=\"#{icons[:neutral]}\" variant=\"solid\"></wa-icon>"
53
+ }
54
+ when 'warning'
55
+ {
56
+ additional_params: ' variant="warning"',
57
+ inner_prepend: "<wa-icon slot=\"icon\" name=\"#{icons[:warning]}\" variant=\"solid\"></wa-icon>"
58
+ }
59
+ when 'danger'
60
+ {
61
+ additional_params: ' variant="danger"',
62
+ inner_prepend: "<wa-icon slot=\"icon\" name=\"#{icons[:danger]}\" variant=\"solid\"></wa-icon>"
63
+ }
64
+ else
65
+ {
66
+ additional_params: '',
67
+ inner_prepend: ''
68
+ }
69
+ end
70
+ end
71
+
72
+ def default_callout_icons
73
+ {
74
+ info: 'circle-info',
75
+ success: 'circle-check',
76
+ neutral: 'gear',
77
+ warning: 'triangle-exclamation',
78
+ danger: 'circle-exclamation'
79
+ }
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms card syntax into wa-card elements
7
+ # Primary syntax: ===appearance?\ncontent\n===
8
+ # Alternative syntax: :::wa-card appearance?\ncontent\n:::
9
+ # Appearances: outlined (default), filled, filled-outlined, plain, accent
10
+ class CardTransformer < BaseTransformer
11
+ def self.transform(content)
12
+ # Define both regex patterns
13
+ primary_regex = /^===(outlined|filled|filled-outlined|plain|accent)?\n(.*?)\n===/m
14
+ alternative_regex = /^:::wa-card\s*(outlined|filled|filled-outlined|plain|accent)?\n(.*?)\n:::/m
15
+
16
+ # Define shared transformation logic
17
+ transform_proc = proc do |appearance_param, card_content|
18
+ card_content = card_content.strip
19
+
20
+ appearance = normalize_appearance(appearance_param)
21
+ card_parts = parse_card_content(card_content)
22
+
23
+ build_card_html(card_parts, appearance)
24
+ end
25
+
26
+ # Apply both patterns
27
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
28
+ apply_multiple_patterns(content, patterns)
29
+ end
30
+
31
+ class << self
32
+ private
33
+
34
+ def normalize_appearance(appearance_param)
35
+ case appearance_param
36
+ when 'filled', 'filled-outlined', 'plain', 'accent'
37
+ appearance_param
38
+ else
39
+ 'outlined' # default
40
+ end
41
+ end
42
+
43
+ def parse_card_content(content)
44
+ parts = {
45
+ media: nil,
46
+ header: nil,
47
+ content: content,
48
+ footer: nil
49
+ }
50
+
51
+ # Extract first image as media
52
+ if content.match(/^!\[([^\]]*)\]\(([^)]+)\)/)
53
+ parts[:media] = {
54
+ alt: ::Regexp.last_match(1),
55
+ src: ::Regexp.last_match(2)
56
+ }
57
+ # Remove the image from content
58
+ content = content.sub(/^!\[([^\]]*)\]\(([^)]+)\)\n?/, '')
59
+ end
60
+
61
+ # Extract first heading as header
62
+ if content.match(/^# (.+)$/)
63
+ parts[:header] = ::Regexp.last_match(1).strip
64
+ # Remove the heading from content
65
+ content = content.sub(/^# .+\n?/, '')
66
+ end
67
+
68
+ # Extract trailing buttons/links as footer
69
+ # Look for links or buttons at the end of the content
70
+ if content.match(/\n\[([^\]]+)\]\(([^)]+)\)\s*$/)
71
+ parts[:footer] = {
72
+ text: ::Regexp.last_match(1),
73
+ href: ::Regexp.last_match(2)
74
+ }
75
+ # Remove the footer link from content
76
+ content = content.sub(/\n\[([^\]]+)\]\(([^)]+)\)\s*$/, '')
77
+ end
78
+
79
+ # Update the main content after extractions
80
+ parts[:content] = content.strip
81
+ parts
82
+ end
83
+
84
+ def build_card_html(parts, appearance)
85
+ attributes = []
86
+ attributes << "appearance=\"#{appearance}\"" if appearance != 'outlined'
87
+
88
+ # Add SSR attributes if slots are present
89
+ attributes << 'with-media' if parts[:media]
90
+ attributes << 'with-header' if parts[:header]
91
+ attributes << 'with-footer' if parts[:footer]
92
+
93
+ attr_string = attributes.empty? ? '' : " #{attributes.join(' ')}"
94
+
95
+ html_parts = []
96
+
97
+ # Media slot
98
+ html_parts << "<img slot=\"media\" src=\"#{parts[:media][:src]}\" alt=\"#{parts[:media][:alt]}\">" if parts[:media]
99
+
100
+ # Header slot
101
+ if parts[:header]
102
+ header_html = markdown_to_html(parts[:header])
103
+ html_parts << "<div slot=\"header\">#{header_html}</div>"
104
+ end
105
+
106
+ # Main content
107
+ if parts[:content] && !parts[:content].empty?
108
+ content_html = markdown_to_html(parts[:content])
109
+ html_parts << content_html
110
+ end
111
+
112
+ # Footer slot
113
+ html_parts << "<div slot=\"footer\"><wa-button href=\"#{parts[:footer][:href]}\">#{parts[:footer][:text]}</wa-button></div>" if parts[:footer]
114
+
115
+ "<wa-card#{attr_string}>#{html_parts.join}</wa-card>"
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms comparison syntax into wa-comparison elements
7
+ # Primary syntax: |||\n![before](before.jpg)\n![after](after.jpg)\n|||
8
+ # Primary syntax with position: |||50\n![before](before.jpg)\n![after](after.jpg)\n|||
9
+ # Alternative syntax: :::wa-comparison\n![before](before.jpg)\n![after](after.jpg)\n:::
10
+ # Alternative syntax with position: :::wa-comparison 50\n![before](before.jpg)\n![after](after.jpg)\n:::
11
+ # Expects exactly two image elements inside the wrapper
12
+ class ComparisonTransformer < BaseTransformer
13
+ def self.transform(content)
14
+ # Process primary syntax
15
+ content = content.gsub(/^\|\|\|(\d+)?\n(.*?)\n\|\|\|/m) do |match|
16
+ position = Regexp.last_match(1)
17
+ inner_content = Regexp.last_match(2).strip
18
+ images = extract_images(inner_content)
19
+
20
+ if images.length == 2
21
+ build_comparison_html(inner_content, position)
22
+ else
23
+ match # Return original match if not exactly 2 images
24
+ end
25
+ end
26
+
27
+ # Process alternative syntax
28
+ content.gsub(/^:::wa-comparison\s*(\d+)?\n(.*?)\n:::/m) do |match|
29
+ position = Regexp.last_match(1)
30
+ inner_content = Regexp.last_match(2).strip
31
+ images = extract_images(inner_content)
32
+
33
+ if images.length == 2
34
+ build_comparison_html(inner_content, position)
35
+ else
36
+ match # Return original match if not exactly 2 images
37
+ end
38
+ end
39
+ end
40
+
41
+ class << self
42
+ private
43
+
44
+ def build_comparison_html(content, position = nil)
45
+ images = extract_images(content)
46
+
47
+ before_image = build_image_html(images[0], 'before')
48
+ after_image = build_image_html(images[1], 'after')
49
+
50
+ position_attr = position ? " position=\"#{position}\"" : ''
51
+
52
+ "<wa-comparison#{position_attr}>#{before_image}#{after_image}</wa-comparison>"
53
+ end
54
+
55
+ def extract_images(content)
56
+ # Extract markdown image syntax: ![alt](url)
57
+ image_regex = /!\[([^\]]*)\]\(([^)]+)\)/
58
+ content.scan(image_regex)
59
+ end
60
+
61
+ def build_image_html(image_match, slot)
62
+ alt_text = image_match[0]
63
+ src = image_match[1]
64
+
65
+ # Escape HTML characters in alt text
66
+ escaped_alt = alt_text.gsub('&', '&amp;').gsub('"', '&quot;').gsub('<', '&lt;').gsub('>', '&gt;')
67
+
68
+ "<img slot=\"#{slot}\" src=\"#{src}\" alt=\"#{escaped_alt}\" />"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms copy button syntax into wa-copy-button elements
7
+ # Primary syntax: <<<\ncontent\n<<<
8
+ # Alternative syntax: :::wa-copy-button\ncontent\n:::
9
+ #
10
+ # Usage:
11
+ # <<<
12
+ # This text will be copied to clipboard
13
+ # <<<
14
+ #
15
+ # :::wa-copy-button
16
+ # Copy this text
17
+ # :::
18
+ class CopyButtonTransformer < BaseTransformer
19
+ def self.transform(content)
20
+ # Define both regex patterns
21
+ primary_regex = /^<<<\n(.*?)\n<<</m
22
+ alternative_regex = /^:::wa-copy-button\n(.*?)\n:::/m
23
+
24
+ # Define shared transformation logic
25
+ transform_proc = proc do |copy_content|
26
+ copy_content = copy_content.strip
27
+
28
+ build_copy_button_html(copy_content)
29
+ end
30
+
31
+ # Apply both patterns
32
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
33
+ apply_multiple_patterns(content, patterns)
34
+ end
35
+
36
+ class << self
37
+ private
38
+
39
+ def build_copy_button_html(content)
40
+ # Escape the content for the value attribute
41
+ escaped_content = content.gsub('"', '&quot;').gsub("'", '&#39;')
42
+
43
+ "<wa-copy-button value=\"#{escaped_content}\"></wa-copy-button>"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms summary/details syntax into wa-details elements
7
+ # Primary syntax: ^^^appearance? icon-placement?\nsummary\n>>>\ndetails\n^^^
8
+ # Alternative syntax: :::wa-details appearance? icon-placement?\nsummary\n>>>\ndetails\n:::
9
+ # Appearances: outlined (default), filled, filled-outlined, plain
10
+ # Icon placement: start, end (default)
11
+ class DetailsTransformer < BaseTransformer
12
+ def self.transform(content)
13
+ # Define both regex patterns - capture parameter string
14
+ primary_regex = /^\^\^\^?(.*?)\n(.*?)\n^>>>\n(.*?)\n^\^\^\^?/m
15
+ alternative_regex = /^:::wa-details\s*(.*?)\n(.*?)\n^>>>\n(.*?)\n:::/m
16
+
17
+ # Define shared transformation logic
18
+ transform_proc = proc do |params_string, summary_content, details_content|
19
+ summary_content = summary_content.strip
20
+ details_content = details_content.strip
21
+
22
+ # Parse parameters from the params string
23
+ appearance_param, icon_placement_param = parse_parameters(params_string)
24
+
25
+ appearance_class = normalize_appearance(appearance_param)
26
+ icon_placement = normalize_icon_placement(icon_placement_param)
27
+ summary_html = markdown_to_html(summary_content)
28
+ details_html = markdown_to_html(details_content)
29
+
30
+ "<wa-details appearance='#{appearance_class}' icon-placement='#{icon_placement}'>" \
31
+ "<span slot='summary'>#{summary_html}</span>" \
32
+ "#{details_html}</wa-details>"
33
+ end
34
+
35
+ # Apply both patterns
36
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
37
+ apply_multiple_patterns(content, patterns)
38
+ end
39
+
40
+ class << self
41
+ private
42
+
43
+ def parse_parameters(params_string)
44
+ return [nil, nil] if params_string.nil? || params_string.strip.empty?
45
+
46
+ # Split by whitespace and extract known parameters
47
+ tokens = params_string.strip.split(/\s+/)
48
+
49
+ appearance_options = %w[outlined filled filled-outlined plain]
50
+ placement_options = %w[start end]
51
+
52
+ appearance_param = tokens.find { |token| appearance_options.include?(token) }
53
+ icon_placement_param = tokens.find { |token| placement_options.include?(token) }
54
+
55
+ [appearance_param, icon_placement_param]
56
+ end
57
+
58
+ def normalize_appearance(appearance_param)
59
+ case appearance_param
60
+ when 'filled'
61
+ 'filled'
62
+ when 'filled-outlined'
63
+ 'filled outlined'
64
+ when 'plain'
65
+ 'plain'
66
+ else
67
+ 'outlined'
68
+ end
69
+ end
70
+
71
+ def normalize_icon_placement(icon_placement_param)
72
+ case icon_placement_param
73
+ when 'start'
74
+ 'start'
75
+ when 'end'
76
+ 'end'
77
+ else
78
+ 'end'
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms icon syntax into wa-icon elements
7
+ # Primary syntax: $$$icon-name
8
+ # Alternative syntax: :::wa-icon icon-name
9
+ #
10
+ # Examples:
11
+ # $$$settings -> <wa-icon name="settings"></wa-icon>
12
+ # $$$home -> <wa-icon name="home"></wa-icon>
13
+ # $$$user-circle -> <wa-icon name="user-circle"></wa-icon>
14
+ class IconTransformer < BaseTransformer
15
+ def self.transform(content)
16
+ # Protect code blocks first
17
+ protected_content, code_blocks = protect_code_blocks(content)
18
+
19
+ # Apply primary syntax transformation
20
+ # Only block patterns that look like incomplete icon names:
21
+ # $$$icon name (where 'icon name' could be intended as one identifier)
22
+ result = protected_content.gsub(/\$\$\$([a-zA-Z0-9\-_]+)(?![a-zA-Z0-9\-_]|\s+name\b)/) do
23
+ icon_name = ::Regexp.last_match(1)
24
+ build_icon_html(icon_name)
25
+ end
26
+
27
+ # Apply alternative syntax transformation
28
+ result = result.gsub(/:::wa-icon\s+([a-zA-Z0-9\-_]+)\s*\n:::/m) do
29
+ icon_name = ::Regexp.last_match(1)
30
+ build_icon_html(icon_name)
31
+ end
32
+
33
+ # Restore code blocks
34
+ restore_code_blocks(result, code_blocks)
35
+ end
36
+
37
+ class << self
38
+ private
39
+
40
+ def build_icon_html(icon_name)
41
+ # Clean and validate icon name
42
+ clean_name = icon_name.strip
43
+
44
+ # Return the wa-icon element
45
+ "<wa-icon name=\"#{clean_name}\"></wa-icon>"
46
+ end
47
+
48
+ def protect_code_blocks(content)
49
+ code_blocks = {}
50
+ counter = 0
51
+
52
+ # Protect fenced code blocks
53
+ protected = content.gsub(/```.*?```/m) do |match|
54
+ placeholder = "<!--ICON_PROTECTED_CODE_BLOCK_#{counter}-->"
55
+ code_blocks[placeholder] = match
56
+ counter += 1
57
+ placeholder
58
+ end
59
+
60
+ # Protect inline code
61
+ protected = protected.gsub(/`[^`]+`/) do |match|
62
+ placeholder = "<!--ICON_PROTECTED_INLINE_CODE_#{counter}-->"
63
+ code_blocks[placeholder] = match
64
+ counter += 1
65
+ placeholder
66
+ end
67
+
68
+ [protected, code_blocks]
69
+ end
70
+
71
+ def restore_code_blocks(content, code_blocks)
72
+ result = content
73
+ code_blocks.each do |placeholder, original|
74
+ result = result.gsub(placeholder, original)
75
+ end
76
+ result
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms tabs syntax into wa-tab-group elements
7
+ # Primary syntax: ++++++placement?\n+++tab1\ncontent\n+++\n+++tab2\ncontent\n+++\n++++++
8
+ # Alternative syntax: :::wa-tabs placement?\n+++tab1\ncontent\n+++\n+++tab2\ncontent\n+++\n:::
9
+ # Placements: top (default), bottom, start, end
10
+ class TabsTransformer < BaseTransformer
11
+ def self.transform(content)
12
+ # Define both regex patterns
13
+ primary_regex = /^\+{6}(top|bottom|start|end)?\n((\+\+\+ [^\n]+\n.*?\n\+\+\+\n?)+)\+{6}/m
14
+ alternative_regex = /^:::wa-tabs\s*(top|bottom|start|end)?\n((\+\+\+ [^\n]+\n.*?\n\+\+\+\n?)+):::/m
15
+
16
+ # Define shared transformation logic
17
+ transform_proc = proc do |placement, tabs_block, _third_capture|
18
+ placement ||= 'top'
19
+
20
+ tabs, tab_panels = extract_tabs_and_panels(tabs_block)
21
+
22
+ "<wa-tab-group placement=\"#{placement}\">#{tabs.join}#{tab_panels.join}</wa-tab-group>"
23
+ end
24
+
25
+ # Apply both patterns
26
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
27
+ apply_multiple_patterns(content, patterns)
28
+ end
29
+
30
+ class << self
31
+ private
32
+
33
+ def extract_tabs_and_panels(tabs_block)
34
+ # Extract individual tabs
35
+ tab_contents = tabs_block.scan(/^\+\+\+ ([^\n]+)\n(.*?)\n\+\+\+/m)
36
+ tabs = []
37
+ tab_panels = []
38
+
39
+ tab_contents.each_with_index do |(title, panel_content), index|
40
+ tab_id = "tab-#{index + 1}"
41
+ tabs << "<wa-tab panel=\"#{tab_id}\">#{title.strip}</wa-tab>"
42
+
43
+ panel_html = markdown_to_html(panel_content.strip)
44
+ tab_panels << "<wa-tab-panel name=\"#{tab_id}\">#{panel_html}</wa-tab-panel>"
45
+ end
46
+
47
+ [tabs, tab_panels]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms tag syntax into wa-tag elements
7
+ # Primary syntax: @@@variant?\ncontent\n@@@
8
+ # Alternative syntax: :::wa-tag variant?\ncontent\n:::
9
+ # Variants: brand, success, neutral, warning, danger
10
+ class TagTransformer < BaseTransformer
11
+ def self.transform(content)
12
+ # Define both regex patterns
13
+ primary_regex = /^@@@(brand|success|neutral|warning|danger)?\n(.*?)\n@@@/m
14
+ alternative_regex = /^:::wa-tag\s*(brand|success|neutral|warning|danger)?\n(.*?)\n:::/m
15
+
16
+ # Define shared transformation logic
17
+ transform_proc = proc do |variant, tag_content|
18
+ tag_content = tag_content.strip
19
+
20
+ build_tag_html(tag_content, variant)
21
+ end
22
+
23
+ # Apply both patterns
24
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
25
+ apply_multiple_patterns(content, patterns)
26
+ end
27
+
28
+ class << self
29
+ private
30
+
31
+ def build_tag_html(content, variant)
32
+ variant_attr = variant ? " variant=\"#{variant}\"" : ''
33
+ tag_html = markdown_to_html(content).strip
34
+
35
+ # Remove paragraph tags if the content is just text
36
+ tag_html = tag_html.gsub(%r{^<p>(.*)</p>$}m, '\1')
37
+
38
+ "<wa-tag#{variant_attr}>#{tag_html}</wa-tag>"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Index file for all Web Awesome transformers
4
+ # This file makes it easy to require all transformers at once
5
+
6
+ require_relative 'transformers/base_transformer'
7
+ require_relative 'transformers/badge_transformer'
8
+ require_relative 'transformers/button_transformer'
9
+ require_relative 'transformers/callout_transformer'
10
+ require_relative 'transformers/card_transformer'
11
+ require_relative 'transformers/comparison_transformer'
12
+ require_relative 'transformers/copy_button_transformer'
13
+ require_relative 'transformers/details_transformer'
14
+ require_relative 'transformers/icon_transformer'
15
+ require_relative 'transformers/tabs_transformer'
16
+ require_relative 'transformers/tag_transformer'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markawesome
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'markawesome/version'
4
+ require_relative 'markawesome/transformer'
5
+
6
+ module Markawesome
7
+ class Error < StandardError; end
8
+
9
+ # Configuration options
10
+ class << self
11
+ attr_accessor :configuration
12
+
13
+ def configure
14
+ self.configuration ||= Configuration.new
15
+ yield(configuration) if block_given?
16
+ configuration
17
+ end
18
+ end
19
+
20
+ # Configuration class for customizing transformation behavior
21
+ class Configuration
22
+ attr_accessor :callout_icons, :custom_components
23
+
24
+ def initialize
25
+ @callout_icons = default_callout_icons
26
+ @custom_components = {}
27
+ end
28
+
29
+ private
30
+
31
+ def default_callout_icons
32
+ {
33
+ info: 'circle-info',
34
+ success: 'circle-check',
35
+ neutral: 'gear',
36
+ warning: 'triangle-exclamation',
37
+ danger: 'circle-exclamation'
38
+ }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/markawesome/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'markawesome'
7
+ spec.version = Markawesome::VERSION
8
+ spec.authors = ['Janne Waren']
9
+ spec.email = ['janne.waren@iki.fi']
10
+
11
+ spec.summary = 'Markdown to Web Awesome transformer'
12
+ spec.description = 'A library that transforms custom Markdown syntax into Web Awesome components.'
13
+ spec.homepage = 'https://github.com/jannewaren/markawesome'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.2'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = "#{spec.homepage}/tree/main"
19
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ Dir.glob('lib/**/*.rb') + %w[
24
+ README.md
25
+ CHANGELOG.md
26
+ LICENSE.txt
27
+ markawesome.gemspec
28
+ ]
29
+ end
30
+ spec.bindir = 'exe'
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ['lib']
33
+
34
+ spec.add_dependency 'kramdown', '~> 2.0'
35
+
36
+ spec.add_development_dependency 'bundler', '~> 2.0'
37
+ spec.add_development_dependency 'rake', '~> 13.0'
38
+ spec.add_development_dependency 'rspec', '~> 3.0'
39
+ spec.add_development_dependency 'rubocop', '~> 1.0'
40
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: markawesome
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Janne Waren
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: kramdown
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ description: A library that transforms custom Markdown syntax into Web Awesome components.
83
+ email:
84
+ - janne.waren@iki.fi
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - CHANGELOG.md
90
+ - LICENSE.txt
91
+ - README.md
92
+ - lib/markawesome.rb
93
+ - lib/markawesome/transformer.rb
94
+ - lib/markawesome/transformers.rb
95
+ - lib/markawesome/transformers/badge_transformer.rb
96
+ - lib/markawesome/transformers/base_transformer.rb
97
+ - lib/markawesome/transformers/button_transformer.rb
98
+ - lib/markawesome/transformers/callout_transformer.rb
99
+ - lib/markawesome/transformers/card_transformer.rb
100
+ - lib/markawesome/transformers/comparison_transformer.rb
101
+ - lib/markawesome/transformers/copy_button_transformer.rb
102
+ - lib/markawesome/transformers/details_transformer.rb
103
+ - lib/markawesome/transformers/icon_transformer.rb
104
+ - lib/markawesome/transformers/tabs_transformer.rb
105
+ - lib/markawesome/transformers/tag_transformer.rb
106
+ - lib/markawesome/version.rb
107
+ - markawesome.gemspec
108
+ homepage: https://github.com/jannewaren/markawesome
109
+ licenses:
110
+ - MIT
111
+ metadata:
112
+ homepage_uri: https://github.com/jannewaren/markawesome
113
+ source_code_uri: https://github.com/jannewaren/markawesome/tree/main
114
+ changelog_uri: https://github.com/jannewaren/markawesome/blob/main/CHANGELOG.md
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '3.2'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.6.9
130
+ specification_version: 4
131
+ summary: Markdown to Web Awesome transformer
132
+ test_files: []