jekyll-github-code 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.
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+
5
+ module Jekyll
6
+ module GitHubCode
7
+ # Renders the code block with syntax highlighting
8
+ class CodeRenderer
9
+ LANGUAGE_MAP = {
10
+ 'rb' => 'ruby',
11
+ 'py' => 'python',
12
+ 'js' => 'javascript',
13
+ 'ts' => 'typescript',
14
+ 'yml' => 'yaml',
15
+ 'md' => 'markdown',
16
+ 'sh' => 'bash',
17
+ 'dockerfile' => 'dockerfile',
18
+ 'containerfile' => 'dockerfile'
19
+ }.freeze
20
+
21
+ def initialize(reference, code, options = {})
22
+ @reference = reference
23
+ @code = code
24
+ @options = options
25
+ end
26
+
27
+ def render
28
+ processed_code = process_line_range(@code)
29
+ escaped_code = CGI.escapeHTML(processed_code)
30
+ language = detect_language
31
+
32
+ <<~HTML
33
+ <div class="github-code-block">
34
+ <div class="github-code-header">
35
+ <span class="github-code-filename">
36
+ <svg class="github-icon" viewBox="0 0 16 16" width="16" height="16">
37
+ <path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
38
+ </svg>
39
+ <a href="#{@reference.github_url}" target="_blank" rel="noopener noreferrer">#{@reference.filename}#{line_range_label}</a>
40
+ </span>
41
+ <button class="github-code-copy" onclick="copyGitHubCode(this)" title="Copy code">
42
+ <svg viewBox="0 0 16 16" width="16" height="16">
43
+ <path fill="currentColor" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"/>
44
+ <path fill="currentColor" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"/>
45
+ </svg>
46
+ </button>
47
+ </div>
48
+ <div class="github-code-content">
49
+ <pre><code class="language-#{language}">#{escaped_code}</code></pre>
50
+ </div>
51
+ </div>
52
+ HTML
53
+ end
54
+
55
+ private
56
+
57
+ def process_line_range(code)
58
+ return code unless @reference.line_range?
59
+
60
+ lines = code.lines
61
+ start_idx = @reference.start_line - 1
62
+ end_idx = (@reference.end_line || @reference.start_line) - 1
63
+
64
+ # Clamp to valid range
65
+ start_idx = [0, start_idx].max
66
+ end_idx = [lines.length - 1, end_idx].min
67
+
68
+ lines[start_idx..end_idx].join
69
+ end
70
+
71
+ def detect_language
72
+ ext = @reference.extension.downcase
73
+ LANGUAGE_MAP[ext] || ext
74
+ end
75
+
76
+ def line_range_label
77
+ return '' unless @reference.line_range?
78
+
79
+ if @reference.end_line
80
+ " (L#{@reference.start_line}-L#{@reference.end_line})"
81
+ else
82
+ " (L#{@reference.start_line})"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module GitHubCode
5
+ # Represents a parsed GitHub URL with repository, path, and optional line range
6
+ class GitHubReference
7
+ attr_reader :owner, :repo, :branch, :path, :start_line, :end_line
8
+
9
+ GITHUB_URL_PATTERN = %r{
10
+ ^
11
+ (?:https?://github\.com/)? # Optional full URL prefix
12
+ ([^/]+)/ # Owner
13
+ ([^/]+)/ # Repo
14
+ blob/
15
+ ([^/]+)/ # Branch
16
+ (.+?) # File path
17
+ (?:\#L(\d+)(?:-L(\d+))?)? # Optional line range
18
+ $
19
+ }x.freeze
20
+
21
+ def initialize(reference)
22
+ match = reference.strip.match(GITHUB_URL_PATTERN)
23
+ raise ArgumentError, "Invalid GitHub reference: #{reference}" unless match
24
+
25
+ @owner = match[1]
26
+ @repo = match[2]
27
+ @branch = match[3]
28
+ @path = match[4]
29
+ @start_line = match[5]&.to_i
30
+ @end_line = match[6]&.to_i
31
+ end
32
+
33
+ def raw_url
34
+ "https://raw.githubusercontent.com/#{@owner}/#{@repo}/#{@branch}/#{@path}"
35
+ end
36
+
37
+ def github_url
38
+ base = "https://github.com/#{@owner}/#{@repo}/blob/#{@branch}/#{@path}"
39
+ return base unless @start_line
40
+
41
+ line_fragment = @end_line ? "L#{@start_line}-L#{@end_line}" : "L#{@start_line}"
42
+ "#{base}##{line_fragment}"
43
+ end
44
+
45
+ def filename
46
+ File.basename(@path)
47
+ end
48
+
49
+ def extension
50
+ File.extname(@path).delete_prefix('.')
51
+ end
52
+
53
+ def line_range?
54
+ !@start_line.nil?
55
+ end
56
+ end
57
+ end
58
+ end
59
+
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'liquid'
4
+ require 'cgi'
5
+
6
+ module Jekyll
7
+ module GitHubCode
8
+ # The main Liquid tag for embedding GitHub code
9
+ class Tag < Liquid::Tag
10
+ def initialize(tag_name, markup, tokens)
11
+ super
12
+ @markup = markup.strip
13
+ end
14
+
15
+ def render(context)
16
+ # Resolve any Liquid variables in the markup
17
+ resolved_markup = context[@markup] || @markup
18
+
19
+ reference = GitHubReference.new(resolved_markup)
20
+ code = CodeFetcher.fetch(reference.raw_url)
21
+ renderer = CodeRenderer.new(reference, code)
22
+ renderer.render
23
+ rescue ArgumentError => e
24
+ error_html("Invalid GitHub reference: #{e.message}")
25
+ rescue CodeFetcher::FetchError => e
26
+ error_html("Failed to fetch code: #{e.message}")
27
+ rescue StandardError => e
28
+ error_html("Error: #{e.message}")
29
+ end
30
+
31
+ private
32
+
33
+ def error_html(message)
34
+ <<~HTML
35
+ <div class="github-code-error">
36
+ <strong>GitHub Code Error:</strong> #{CGI.escapeHTML(message)}
37
+ </div>
38
+ HTML
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ Liquid::Template.register_tag('github_code', Jekyll::GitHubCode::Tag)
45
+
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module GitHubCode
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
8
+
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jekyll-github-code/version'
4
+ require_relative 'jekyll-github-code/github_reference'
5
+ require_relative 'jekyll-github-code/code_fetcher'
6
+ require_relative 'jekyll-github-code/code_renderer'
7
+ require_relative 'jekyll-github-code/tag'
8
+
9
+ module Jekyll
10
+ module GitHubCode
11
+ end
12
+ end
13
+
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-github-code
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rodolfo Olivieri
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.7'
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.7'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '5.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: liquid
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '4.0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '4.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: minitest
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '5.20'
53
+ type: :development
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '5.20'
60
+ - !ruby/object:Gem::Dependency
61
+ name: rake
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '13.0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '13.0'
74
+ - !ruby/object:Gem::Dependency
75
+ name: rubocop
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.57'
81
+ type: :development
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '1.57'
88
+ - !ruby/object:Gem::Dependency
89
+ name: webmock
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '3.19'
95
+ type: :development
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '3.19'
102
+ description: Embed code snippets from GitHub repositories directly in your Jekyll
103
+ blog posts with automatic syntax highlighting, line range support, and clickable
104
+ links to the source.
105
+ email:
106
+ - rodolfo.olivieri3@gmail.com
107
+ executables: []
108
+ extensions: []
109
+ extra_rdoc_files: []
110
+ files:
111
+ - ".bundle/config"
112
+ - CHANGELOG.md
113
+ - Gemfile
114
+ - LICENSE
115
+ - README.md
116
+ - Rakefile
117
+ - assets/css/github-code.scss
118
+ - assets/js/github-code.js
119
+ - demo.html
120
+ - jekyll-github-code.gemspec
121
+ - lib/jekyll-github-code.rb
122
+ - lib/jekyll-github-code/code_fetcher.rb
123
+ - lib/jekyll-github-code/code_renderer.rb
124
+ - lib/jekyll-github-code/github_reference.rb
125
+ - lib/jekyll-github-code/tag.rb
126
+ - lib/jekyll-github-code/version.rb
127
+ homepage: https://github.com/r0x0d/jekyll-github-code
128
+ licenses:
129
+ - MIT
130
+ metadata:
131
+ bug_tracker_uri: https://github.com/r0x0d/jekyll-github-code/issues
132
+ changelog_uri: https://github.com/r0x0d/jekyll-github-code/blob/main/CHANGELOG.md
133
+ documentation_uri: https://github.com/r0x0d/jekyll-github-code#readme
134
+ homepage_uri: https://github.com/r0x0d/jekyll-github-code
135
+ source_code_uri: https://github.com/r0x0d/jekyll-github-code
136
+ rubygems_mfa_required: 'true'
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: 2.7.0
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubygems_version: 3.6.9
152
+ specification_version: 4
153
+ summary: A Jekyll plugin to embed code from GitHub repositories
154
+ test_files: []