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.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile +10 -0
- data/LICENSE +22 -0
- data/README.md +245 -0
- data/Rakefile +20 -0
- data/assets/css/github-code.scss +381 -0
- data/assets/js/github-code.js +30 -0
- data/demo.html +606 -0
- data/jekyll-github-code.gemspec +60 -0
- data/lib/jekyll-github-code/code_fetcher.rb +30 -0
- data/lib/jekyll-github-code/code_renderer.rb +88 -0
- data/lib/jekyll-github-code/github_reference.rb +59 -0
- data/lib/jekyll-github-code/tag.rb +45 -0
- data/lib/jekyll-github-code/version.rb +8 -0
- data/lib/jekyll-github-code.rb +13 -0
- metadata +154 -0
|
@@ -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,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: []
|