├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── jekyll_github_sample.gemspec ├── lib ├── jekyll_github_sample.rb └── jekyll_github_sample │ ├── code_tag.rb │ ├── file_helper.rb │ ├── reference_tag.rb │ ├── text_utils.rb │ └── version.rb └── spec ├── lib └── jekyll_github_sample │ ├── file_helper_spec.rb │ └── text_utils_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Specific to Rubymine 18 | .idea/* 19 | 20 | ## Documentation cache and generated files: 21 | /.yardoc/ 22 | /_yardoc/ 23 | /doc/ 24 | /rdoc/ 25 | 26 | ## Environment normalisation: 27 | /.bundle/ 28 | /lib/bundler/man/ 29 | 30 | # for a library or gem, you might want to ignore these files since the code is 31 | # intended to run in multiple environments; otherwise, check them in: 32 | # Gemfile.lock 33 | .ruby-version 34 | .ruby-gemset 35 | 36 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 37 | .rvmrc 38 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | jekyll_github_sample (0.3.2) 5 | activesupport (>= 4.0, < 7.0) 6 | jekyll (>= 3.0, < 5.0) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | activesupport (6.0.3.1) 12 | concurrent-ruby (~> 1.0, >= 1.0.2) 13 | i18n (>= 0.7, < 2) 14 | minitest (~> 5.1) 15 | tzinfo (~> 1.1) 16 | zeitwerk (~> 2.2, >= 2.2.2) 17 | addressable (2.8.0) 18 | public_suffix (>= 2.0.2, < 5.0) 19 | colorator (1.1.0) 20 | concurrent-ruby (1.1.6) 21 | diff-lcs (1.3) 22 | em-websocket (0.5.1) 23 | eventmachine (>= 0.12.9) 24 | http_parser.rb (~> 0.6.0) 25 | eventmachine (1.2.7) 26 | ffi (1.11.1) 27 | forwardable-extended (2.6.0) 28 | http_parser.rb (0.6.0) 29 | i18n (1.8.2) 30 | concurrent-ruby (~> 1.0) 31 | jekyll (4.0.0) 32 | addressable (~> 2.4) 33 | colorator (~> 1.0) 34 | em-websocket (~> 0.5) 35 | i18n (>= 0.9.5, < 2) 36 | jekyll-sass-converter (~> 2.0) 37 | jekyll-watch (~> 2.0) 38 | kramdown (~> 2.1) 39 | kramdown-parser-gfm (~> 1.0) 40 | liquid (~> 4.0) 41 | mercenary (~> 0.3.3) 42 | pathutil (~> 0.9) 43 | rouge (~> 3.0) 44 | safe_yaml (~> 1.0) 45 | terminal-table (~> 1.8) 46 | jekyll-sass-converter (2.0.1) 47 | sassc (> 2.0.1, < 3.0) 48 | jekyll-watch (2.2.1) 49 | listen (~> 3.0) 50 | kramdown (2.3.1) 51 | rexml 52 | kramdown-parser-gfm (1.1.0) 53 | kramdown (~> 2.0) 54 | liquid (4.0.3) 55 | listen (3.2.0) 56 | rb-fsevent (~> 0.10, >= 0.10.3) 57 | rb-inotify (~> 0.9, >= 0.9.10) 58 | mercenary (0.3.6) 59 | minitest (5.14.1) 60 | pathutil (0.16.2) 61 | forwardable-extended (~> 2.6) 62 | public_suffix (4.0.6) 63 | rake (13.0.0) 64 | rb-fsevent (0.10.3) 65 | rb-inotify (0.10.0) 66 | ffi (~> 1.0) 67 | rexml (3.2.5) 68 | rouge (3.12.0) 69 | rspec (3.9.0) 70 | rspec-core (~> 3.9.0) 71 | rspec-expectations (~> 3.9.0) 72 | rspec-mocks (~> 3.9.0) 73 | rspec-core (3.9.0) 74 | rspec-support (~> 3.9.0) 75 | rspec-expectations (3.9.0) 76 | diff-lcs (>= 1.2.0, < 2.0) 77 | rspec-support (~> 3.9.0) 78 | rspec-mocks (3.9.0) 79 | diff-lcs (>= 1.2.0, < 2.0) 80 | rspec-support (~> 3.9.0) 81 | rspec-support (3.9.0) 82 | safe_yaml (1.0.5) 83 | sassc (2.2.1) 84 | ffi (~> 1.9) 85 | terminal-table (1.8.0) 86 | unicode-display_width (~> 1.1, >= 1.1.1) 87 | thread_safe (0.3.6) 88 | tzinfo (1.2.7) 89 | thread_safe (~> 0.1) 90 | unicode-display_width (1.6.0) 91 | zeitwerk (2.3.0) 92 | 93 | PLATFORMS 94 | ruby 95 | 96 | DEPENDENCIES 97 | bundler 98 | jekyll_github_sample! 99 | rake 100 | rspec 101 | 102 | BUNDLED WITH 103 | 2.0.2 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jekyll-github-sample 2 | 3 | Two Jekyll Liquid tags to display a code sample from a file in a public Github repo and provide a reference to it. 4 | 5 | ## Install 6 | 7 | 1. Add the gem to your `Gemfile`. 8 | ```ruby 9 | gem 'jekyll_github_sample' 10 | ``` 11 | 1. Install. 12 | ```sh 13 | bundle install 14 | 1. Add the gem to your Jekyll `_config.yml`. 15 | ```yaml 16 | plugins: 17 | - jekyll_github_sample 18 | ``` 19 | 1. Start Jekyll. 20 | 21 | ## Live Examples 22 | 23 | A [write up](https://bwillis.github.io/2014/05/28/include-github-repo-code-in-jekyll/) on my Jekyll blog including samples referencing this repo's code, meta. 24 | 25 | ## github_sample Usage 26 | 27 | ```liquid 28 | {% github_sample URL_WITH_USERNAME_REPO_AND_FILE %} 29 | ``` 30 | 31 | * URL_WITH_USERNAME_REPO_AND_FILE - the relative path to the Github repo file, prefer a file with the commitish in it so it won't change when recompiling occurs. A url to this README would be: `bwillis/jekyll-github-sample/blob/a3bc9e82412d364aa76e9308ab53ff2bddaa2faf/README.md` 32 | * START_LINE_NUMBER - (optional) number that is the first line to include (0 based) 33 | * END_LINE_NUMBER - (optional) number that is the last line to include, if excluded will read to end of file 34 | 35 | One can also specify the lines to include based on markings in the file itself. 36 | This is done by invoking 37 | 38 | ```liquid 39 | {% github_sample URL_WITH_USERNAME_REPO_AND_FILE tag:TAG_NAME %} 40 | ``` 41 | 42 | And placing the strings `[START TAG_NAME]` and `[END TAG_NAME]` anywhere in the lines immediately before and after the content you wish to include. 43 | 44 | 45 | ## github_sample_ref Usage 46 | 47 | ```liquid 48 | {% github_sample_ref URL_WITH_USERNAME_REPO_AND_FILE %} 49 | ``` 50 | 51 | * URL_WITH_USERNAME_REPO_AND_FILE - the relative path to the Github repo file, prefer a file with the commitish in it so it won't change when recompiling occurs. A url to this README would be: `bwillis/jekyll-github-sample/blob/a3bc9e82412d364aa76e9308ab53ff2bddaa2faf/README.md` 52 | 53 | ## Example Usage 54 | 55 | This is how you would display, reference and highlight code in your Jekyll post. 56 | 57 | ```liquid 58 | {% github_sample_ref /bwillis/versioncake/989237901cb873f96df12be48cbf1239be496bd7/Appraisals %} 59 | 60 | {% highlight ruby %} 61 | {% github_sample /bwillis/versioncake/989237901cb873f96df12be48cbf1239be496bd7/Appraisals 0 5 %} 62 | {% endhighlight %} 63 | ``` 64 | 65 | ## Thanks! 66 | 67 | Thanks to all those who have helped make this really awesome: 68 | 69 | * [heedfull](https://github.com/heedfull) 70 | * [robertwb](https://github.com/robertwb) 71 | 72 | ## License 73 | 74 | Jekyll Github Sample is released under the [MIT license](https://www.opensource.org/licenses/MIT). 75 | 76 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | RSpec::Core::RakeTask.new(:spec) 4 | 5 | task :default => :spec -------------------------------------------------------------------------------- /jekyll_github_sample.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path('../lib', __FILE__) 3 | require 'jekyll_github_sample/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'jekyll_github_sample' 7 | s.version = JekyllGithubSample::VERSION 8 | s.license = 'MIT' 9 | s.authors = ['Ben Willis'] 10 | s.email = ['benjamin.willis@gmail.com'] 11 | s.homepage = 'https://github.com/bwillis/jekyll-github-sample' 12 | s.summary = %q{Include a sample of a Github repo file.} 13 | s.description = %q{Easily reference and include a Github repo file in your post.} 14 | 15 | s.files = `git ls-files -z`.split("\x0") 16 | s.test_files = `git ls-files -- {spec}/*`.split('\n') 17 | s.require_paths = ['lib'] 18 | 19 | s.add_dependency 'activesupport', '>= 4.0', '< 7.0' 20 | s.add_dependency 'jekyll', '>= 3.0', '< 5.0' 21 | 22 | s.add_development_dependency 'rspec' 23 | s.add_development_dependency 'bundler' 24 | s.add_development_dependency 'rake' 25 | 26 | end 27 | -------------------------------------------------------------------------------- /lib/jekyll_github_sample.rb: -------------------------------------------------------------------------------- 1 | require 'jekyll_github_sample/text_utils' 2 | require 'jekyll_github_sample/file_helper' 3 | require 'jekyll_github_sample/code_tag' 4 | require 'jekyll_github_sample/reference_tag' -------------------------------------------------------------------------------- /lib/jekyll_github_sample/code_tag.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | require 'open-uri' 3 | require 'active_support' 4 | require 'liquid' 5 | 6 | module JekyllGithubSample 7 | class CodeTag < ::Liquid::Tag 8 | 9 | include TextUtils 10 | 11 | def initialize(tag_name, params, tokens) 12 | github_file_path, @line_start, @line_end = params.split 13 | @github_file = FileHelper.new(github_file_path) 14 | super 15 | end 16 | 17 | def render(context) 18 | all_lines = cache.fetch(@github_file.raw_uri) do 19 | URI.open(@github_file.raw_uri).readlines 20 | end 21 | if @line_start.respond_to?(:match) and tag_match = @line_start.match(/^tag:(.*)/) 22 | lines = extract_tagged_lines(all_lines, tag_match[1]) 23 | else 24 | @line_start, @line_end = determine_line_numbers(@line_start, @line_end) 25 | lines = all_lines[@line_start..@line_end] 26 | end 27 | lines = remove_common_indentation(lines) 28 | lines.join 29 | end 30 | 31 | private 32 | 33 | def cache 34 | @@cache ||= ActiveSupport::Cache::MemoryStore.new 35 | end 36 | 37 | def determine_line_numbers(first, last) 38 | if first.nil? && last.nil? 39 | first = 0 40 | last = -1 41 | elsif last.nil? 42 | last = first 43 | end 44 | 45 | [first.to_i, last.to_i] 46 | end 47 | end 48 | end 49 | 50 | Liquid::Template.register_tag('github_sample', JekyllGithubSample::CodeTag) 51 | -------------------------------------------------------------------------------- /lib/jekyll_github_sample/file_helper.rb: -------------------------------------------------------------------------------- 1 | module JekyllGithubSample 2 | class FileHelper 3 | 4 | GITHUB_RAW_URI = 'https://raw.githubusercontent.com/' 5 | GITHUB_URI = 'https://github.com/' 6 | WEB_URI_PART = 'blob' 7 | 8 | attr_reader :filename, :user, :project_name 9 | 10 | def initialize(path) 11 | @path = path 12 | parts = @path.split('/').delete_if { |e| e.empty? } 13 | if parts.include? WEB_URI_PART 14 | @user, @project_name, @blob, @commitish_or_branch = parts[0..3] 15 | @filename = File.join(parts[4..-1]) 16 | else 17 | @user, @project_name, @commitish_or_branch = parts[0..2] 18 | @filename = File.join(parts[3..-1]) 19 | end 20 | end 21 | 22 | def user_uri 23 | File.join(GITHUB_URI, @user) 24 | end 25 | 26 | def web_uri 27 | File.join(GITHUB_URI, @user, @project_name, WEB_URI_PART, @commitish_or_branch, @filename) 28 | end 29 | 30 | def raw_uri 31 | File.join(GITHUB_RAW_URI, @user, @project_name, @commitish_or_branch, @filename) 32 | end 33 | end 34 | end -------------------------------------------------------------------------------- /lib/jekyll_github_sample/reference_tag.rb: -------------------------------------------------------------------------------- 1 | module JekyllGithubSample 2 | class ReferenceTag < CodeTag 3 | 4 | def render(context) 5 | < 7 | 10 |
11 | #{@github_file.filename} view raw 12 |
13 | 14 | MARKUP 15 | end 16 | end 17 | end 18 | 19 | Liquid::Template.register_tag('github_sample_ref', JekyllGithubSample::ReferenceTag) -------------------------------------------------------------------------------- /lib/jekyll_github_sample/text_utils.rb: -------------------------------------------------------------------------------- 1 | module JekyllGithubSample 2 | module TextUtils 3 | 4 | INDEN_REGEX = /^\s+/ 5 | 6 | def remove_common_indentation(lines) 7 | leading_spaces = [] 8 | lines.each do |line| 9 | next if line.length == 1 10 | if indentation_match = line.match(INDEN_REGEX) 11 | leading_spaces << indentation_match[0].length 12 | else 13 | leading_spaces << 0 14 | end 15 | end 16 | 17 | lines.collect do |line| 18 | line.length == 1 ? line : line[leading_spaces.min..-1] 19 | end 20 | end 21 | 22 | def extract_tagged_lines(lines, tag) 23 | start_tag = "[START #{tag}]" 24 | end_tag = "[END #{tag}]" 25 | tagged_lines = [] 26 | in_tagged_content = false 27 | lines.each do |line| 28 | if in_tagged_content 29 | if line.include? end_tag 30 | in_tagged_content = false 31 | else 32 | tagged_lines << line 33 | end 34 | else 35 | in_tagged_content = line.include? start_tag 36 | end 37 | end 38 | tagged_lines 39 | end 40 | end 41 | end -------------------------------------------------------------------------------- /lib/jekyll_github_sample/version.rb: -------------------------------------------------------------------------------- 1 | module JekyllGithubSample 2 | VERSION = '0.3.2' 3 | end 4 | -------------------------------------------------------------------------------- /spec/lib/jekyll_github_sample/file_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe JekyllGithubSample::FileHelper do 4 | 5 | let(:path) { '/bwillis/versioncake/blob/master/lib/versioncake/configuration.rb' } 6 | 7 | subject { JekyllGithubSample::FileHelper.new(path) } 8 | 9 | context '#initialize' do 10 | it 'does not fail to initialize with the path' do 11 | expect { 12 | subject 13 | }.not_to raise_error 14 | end 15 | end 16 | 17 | its(:user_uri) { should == 'https://github.com/bwillis' } 18 | its(:web_uri) { should == 'https://github.com/bwillis/versioncake/blob/master/lib/versioncake/configuration.rb' } 19 | its(:raw_uri) { should == 'https://raw.githubusercontent.com/bwillis/versioncake/master/lib/versioncake/configuration.rb' } 20 | its(:filename) { should == 'lib/versioncake/configuration.rb' } 21 | its(:user) { should == 'bwillis' } 22 | its(:project_name) { should == 'versioncake' } 23 | end 24 | -------------------------------------------------------------------------------- /spec/lib/jekyll_github_sample/text_utils_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe JekyllGithubSample::TextUtils do 4 | 5 | let(:text_utils) { class Tester; include JekyllGithubSample::TextUtils; end.new } 6 | 7 | context '#remove_common_indentation' do 8 | let(:lines) {[ 9 | ' def method', 10 | ' a = 1', 11 | ' end' 12 | ]} 13 | subject { text_utils.remove_common_indentation(lines) } 14 | 15 | it 'removes 3 spaces of common indentation' do 16 | should =~ [ 17 | 'def method', 18 | ' a = 1', 19 | 'end' 20 | ] 21 | end 22 | 23 | context 'for lines with no common indentation' do 24 | let(:lines) { [ 25 | 'def new_method', 26 | ' raise', 27 | 'end' 28 | ] } 29 | 30 | it 'does nothing to the indentation' do 31 | should =~ lines 32 | end 33 | end 34 | end 35 | 36 | context '#extract_tagged_lines' do 37 | let(:lines) { [ 38 | 'header', 39 | '[START tag]', 40 | 'content 1', 41 | 'content 2', 42 | '[END tag]', 43 | 'footer 1', 44 | 'footer 2' 45 | ] } 46 | subject { text_utils.extract_tagged_lines(lines, 'tag') } 47 | 48 | it 'extracts content' do 49 | should =~ [ 50 | 'content 1', 51 | 'content 2' 52 | ] 53 | end 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | require 'jekyll_github_sample' --------------------------------------------------------------------------------