├── .ruby-version ├── .github ├── release-drafter.yml └── workflows │ └── release-drafter.yml ├── lib └── awestruct │ ├── ibeams │ ├── version.rb │ ├── errors.rb │ ├── debuggable_partial.rb │ ├── datadir.rb │ └── asciidoc_sections.rb │ └── ibeams.rb ├── .gitignore ├── Rakefile ├── spec ├── spec_helper.rb └── awestruct │ └── ibeams │ ├── datadir_spec.rb │ ├── debuggable_partial_spec.rb │ └── asciidoc_sections_spec.rb ├── Gemfile ├── renovate.json ├── Jenkinsfile ├── README.adoc ├── awestruct-ibeams.gemspec ├── LICENSE.adoc └── Gemfile.lock /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.1 2 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | -------------------------------------------------------------------------------- /lib/awestruct/ibeams/version.rb: -------------------------------------------------------------------------------- 1 | module Awestruct 2 | module Ibeams 3 | VERSION = "0.4.5" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | *.sw* 10 | .idea 11 | -------------------------------------------------------------------------------- /lib/awestruct/ibeams.rb: -------------------------------------------------------------------------------- 1 | require "awestruct/ibeams/version" 2 | 3 | module Awestruct 4 | module Ibeams 5 | # Your code goes here... 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rspec/core/rake_task' 3 | 4 | 5 | RSpec::Core::RakeTask.new(:spec) 6 | 7 | 8 | task :default => [:spec, :build] 9 | -------------------------------------------------------------------------------- /lib/awestruct/ibeams/errors.rb: -------------------------------------------------------------------------------- 1 | 2 | module Awestruct 3 | module IBeams 4 | module Errors 5 | class InvalidPath < Exception; end; 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | 3 | $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib/')) 4 | 5 | RSpec.configure do |r| 6 | r.color = true 7 | end 8 | -------------------------------------------------------------------------------- /spec/awestruct/ibeams/datadir_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'awestruct/ibeams/datadir' 3 | 4 | describe Awestruct::IBeams::DataDir do 5 | it { should respond_to :execute } 6 | it { should respond_to :watch } 7 | end 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in awestruct-ibeams.gemspec 4 | gemspec 5 | 6 | group :development do 7 | gem 'rake', '~> 13.0.6' 8 | gem 'pry' 9 | gem 'rspec' 10 | gem 'rspec-its' 11 | end 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | ":semanticCommitsDisabled" 6 | ], 7 | "regexManagers": [ 8 | { 9 | "fileMatch": ["Jenkinsfile"], 10 | "matchStrings": ["image 'ruby:(?.*?)'\n"], 11 | "depNameTemplate": "ruby", 12 | "datasourceTemplate": "docker" 13 | } 14 | ], 15 | "rebaseWhen": "conflicted" 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Drafts your next Release notes as Pull Requests are merged into "main" 14 | - uses: release-drafter/release-drafter@v5 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /spec/awestruct/ibeams/debuggable_partial_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'awestruct/ibeams/debuggable_partial' 3 | 4 | describe Awestruct::IBeams::DebuggablePartial do 5 | it { should be_instance_of Module } 6 | 7 | context 'when mixed into a dummy pipeline' do 8 | subject(:pipeline) do 9 | Class.new { extend Awestruct::IBeams::DebuggablePartial} 10 | end 11 | 12 | it { should be_instance_of Class } 13 | it { should respond_to :partial } 14 | 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | pipeline { 4 | agent { 5 | docker { 6 | image 'ruby:3.2.1' 7 | label 'docker&&linux' 8 | } 9 | } 10 | 11 | stages { 12 | stage('Build and Test') { 13 | steps { 14 | sh 'gem install bundler -N && bundle install' 15 | sh 'bundle exec rake' 16 | } 17 | } 18 | } 19 | 20 | post { 21 | always { 22 | archiveArtifacts('pkg/*.gem') 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Awestruct I-Beams 2 | 3 | The `awestruct-ibeams` gem adds more structural support with a variety of 4 | different extensions to the link:http://awestruct.org[Awestruct] static site 5 | generator. 6 | 7 | 8 | == Installation 9 | 10 | Add this line to your application's Gemfile: 11 | 12 | [source, ruby] 13 | ---- 14 | gem 'awestruct-ibeams' 15 | ---- 16 | 17 | And then execute: 18 | 19 | $ bundle 20 | 21 | Or install it yourself as: 22 | 23 | $ gem install awestruct-ibeams 24 | 25 | == Usage 26 | 27 | == Contributing 28 | 29 | Bug reports and pull requests are welcome 30 | link:https://github.com/jenkins-infra/awestruct-ibeams[on GitHub]. 31 | -------------------------------------------------------------------------------- /lib/awestruct/ibeams/debuggable_partial.rb: -------------------------------------------------------------------------------- 1 | require 'awestruct/extensions/partial' 2 | 3 | module Awestruct 4 | module IBeams 5 | # Debuggbale partial is just like a regular partial in awestruct except 6 | # there are HTML comments which make it clearer which files are being 7 | # included where in the rendered output 8 | module DebuggablePartial 9 | include Awestruct::Extensions::Partial 10 | alias_method :original_partial, :partial 11 | 12 | def partial(path, params={}) 13 | return [ 14 | "", 15 | original_partial(path, params), 16 | "", 17 | ].join("\n") 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /awestruct-ibeams.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'awestruct/ibeams/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "awestruct-ibeams" 8 | spec.version = Awestruct::Ibeams::VERSION 9 | spec.authors = ["R. Tyler Croy"] 10 | spec.email = ["tyler@monkeypox.org"] 11 | 12 | spec.summary = "Collection of helpful Awestruct extensions and helpers" 13 | spec.description = "Collection of helpful Awestruct extensions and helpers" 14 | spec.homepage = 'https://github.com/jenkins-infra/awestruct-ibeams' 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.bindir = "exe" 18 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 2.3.26" 22 | spec.add_dependency 'awestruct', '>= 0.5.6', '< 0.7.0' 23 | spec.add_dependency 'naturally', '~> 2.2.1' 24 | end 25 | -------------------------------------------------------------------------------- /LICENSE.adoc: -------------------------------------------------------------------------------- 1 | = License 2 | 3 | == Code 4 | 5 | This license applies code contributed to this repository 6 | 7 | .MIT License 8 | "" 9 | Copyright (c) 2015-2016 CloudBees, Inc, and respective contributors 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of 12 | this software and associated documentation files (the "Software"), to deal in 13 | the Software without restriction, including without limitation the rights to 14 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 15 | of the Software, and to permit persons to whom the Software is furnished to do 16 | so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | "" 29 | -------------------------------------------------------------------------------- /lib/awestruct/ibeams/datadir.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module Awestruct 4 | module IBeams 5 | # The +DataDir+ extension provided in +Awestruct::IBeams+ is a fork 6 | # of the original +Awestruct::Extensions::DataDir+ with some cleaned up 7 | # code to generate paths in a more cross-platform manner but to also 8 | # support +.yml+ files as native providers of data. 9 | # 10 | # That is to say, you can create a directory "bar/" which contains +foo.yml+ and 11 | # that will be loaded into +site.bar[:foo]+ directly 12 | class DataDir 13 | DEFAULT_DATADIR = '_data'.freeze 14 | 15 | def initialize(data_dir=DEFAULT_DATADIR) 16 | @data_dir = data_dir 17 | end 18 | 19 | def watch(watched_dirs) 20 | watched_dirs << @data_dir 21 | end 22 | 23 | def execute(site) 24 | Dir.glob(File.join(site.dir, @data_dir, '*')).each do |entry| 25 | next unless File.directory? entry 26 | data_key = File.basename(entry) 27 | data_map = {} 28 | Dir.glob(File.join(entry, '*')).each do |chunk| 29 | File.basename(chunk) =~ /^([^\.]+)/ 30 | key = $1.to_sym 31 | chunk_page = nil 32 | if chunk.end_with?('.yml') 33 | chunk_page = YAML.load_file(chunk) 34 | else 35 | chunk_page = site.engine.load_page(chunk) 36 | end 37 | data_map[key] = chunk_page 38 | end 39 | site.send("#{data_key}=", data_map) 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/awestruct/ibeams/asciidoc_sections_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'awestruct/ibeams/asciidoc_sections' 3 | 4 | describe Awestruct::IBeams::AsciidocSections do 5 | it { should be_instance_of Module } 6 | 7 | context 'when mixed into a dummy pipeline' do 8 | subject(:pipeline) do 9 | Class.new { extend Awestruct::IBeams::AsciidocSections } 10 | end 11 | 12 | it { should be_instance_of Class } 13 | 14 | it { should respond_to :sections_from } 15 | 16 | describe '.sections_from' do 17 | context 'error cases' do 18 | it 'should throw InvalidPath when a nil path is passed' do 19 | expect { 20 | pipeline.sections_from(nil) 21 | }.to raise_error(Awestruct::IBeams::Errors::InvalidPath) 22 | end 23 | 24 | it 'should throw InvalidPath when a bad path is passed' do 25 | expect { 26 | pipeline.sections_from('/tmp/there/is/no/way/this/path/exists') 27 | }.to raise_error(Awestruct::IBeams::Errors::InvalidPath) 28 | end 29 | end 30 | 31 | end 32 | 33 | it { should respond_to :section_anchor } 34 | describe '.section_anchor' do 35 | let(:doc) { double('Asciidoctor::Document', :attributes => {} ) } 36 | 37 | it 'should hyphenate the title by default' do 38 | expect( 39 | pipeline.section_anchor('Hello World', doc) 40 | ).to eql('hello-world') 41 | end 42 | 43 | it 'should downcase a single-word title' do 44 | expect( 45 | pipeline.section_anchor('RSpec!', doc) 46 | ).to eql('rspec') 47 | end 48 | 49 | it 'should delete leading separator' do 50 | expect( 51 | pipeline.section_anchor('deleteDir: Recursively delete the current directory from the workspace', doc) 52 | ).to eql('deletedir-recursively-delete-the-current-directory-from-the-workspace') 53 | end 54 | 55 | it 'should skip forbidden characters' do 56 | expect( 57 | pipeline.section_anchor('step([$class: \'BuildScanner\']): Acunetix', doc) 58 | ).to eql('stepclass-buildscanner-acunetix') 59 | end 60 | end 61 | 62 | 63 | it { should respond_to :find_subsections_from } 64 | 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | awestruct-ibeams (0.4.3) 5 | awestruct (>= 0.5.6, < 0.7.0) 6 | naturally (~> 2.2.1) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | addressable (2.8.1) 12 | public_suffix (>= 2.0.2, < 6.0) 13 | ansi (1.5.0) 14 | asciidoctor (2.0.18) 15 | ast (2.4.2) 16 | awestruct (0.6.1) 17 | asciidoctor (>= 1.5.2, < 3.0) 18 | git (~> 1.2, >= 1.2.6) 19 | guard (~> 2.0, >= 2.13.0) 20 | guard-livereload (~> 2.0, >= 2.1.2) 21 | haml (>= 4.0.5, < 6.0) 22 | listen (~> 3.1) 23 | logging (~> 2.2) 24 | mime-types (~> 3.0) 25 | oga (~> 2.0) 26 | parallel (~> 1.0, > 1.1.1) 27 | rack (~> 2.0) 28 | rest-client (~> 2.0) 29 | tilt (~> 2.0, >= 2.0.1) 30 | coderay (1.1.3) 31 | diff-lcs (1.5.0) 32 | domain_name (0.5.20190701) 33 | unf (>= 0.0.5, < 1.0.0) 34 | em-websocket (0.5.3) 35 | eventmachine (>= 0.12.9) 36 | http_parser.rb (~> 0) 37 | eventmachine (1.2.7) 38 | ffi (1.15.5) 39 | formatador (1.1.0) 40 | git (1.13.0) 41 | addressable (~> 2.8) 42 | rchardet (~> 1.8) 43 | guard (2.18.0) 44 | formatador (>= 0.2.4) 45 | listen (>= 2.7, < 4.0) 46 | lumberjack (>= 1.0.12, < 2.0) 47 | nenv (~> 0.1) 48 | notiffany (~> 0.0) 49 | pry (>= 0.13.0) 50 | shellany (~> 0.0) 51 | thor (>= 0.18.1) 52 | guard-compat (1.2.1) 53 | guard-livereload (2.5.2) 54 | em-websocket (~> 0.5) 55 | guard (~> 2.8) 56 | guard-compat (~> 1.0) 57 | multi_json (~> 1.8) 58 | haml (5.1.2) 59 | temple (>= 0.8.0) 60 | tilt 61 | http-accept (1.7.0) 62 | http-cookie (1.0.5) 63 | domain_name (~> 0.5) 64 | http_parser.rb (0.8.0) 65 | listen (3.7.1) 66 | rb-fsevent (~> 0.10, >= 0.10.3) 67 | rb-inotify (~> 0.9, >= 0.9.10) 68 | little-plugger (1.1.4) 69 | logging (2.3.1) 70 | little-plugger (~> 1.1) 71 | multi_json (~> 1.14) 72 | lumberjack (1.2.8) 73 | method_source (1.0.0) 74 | mime-types (3.4.1) 75 | mime-types-data (~> 3.2015) 76 | mime-types-data (3.2022.0105) 77 | multi_json (1.15.0) 78 | naturally (2.2.1) 79 | nenv (0.3.0) 80 | netrc (0.11.0) 81 | notiffany (0.1.3) 82 | nenv (~> 0.1) 83 | shellany (~> 0.0) 84 | oga (2.15) 85 | ast 86 | ruby-ll (~> 2.1) 87 | parallel (1.22.1) 88 | pry (0.14.1) 89 | coderay (~> 1.1) 90 | method_source (~> 1.0) 91 | public_suffix (5.0.1) 92 | rack (2.2.6.3) 93 | rake (13.0.6) 94 | rb-fsevent (0.11.2) 95 | rb-inotify (0.10.1) 96 | ffi (~> 1.0) 97 | rchardet (1.8.0) 98 | rest-client (2.1.0) 99 | http-accept (>= 1.7.0, < 2.0) 100 | http-cookie (>= 1.0.2, < 2.0) 101 | mime-types (>= 1.16, < 4.0) 102 | netrc (~> 0.8) 103 | rspec (3.12.0) 104 | rspec-core (~> 3.12.0) 105 | rspec-expectations (~> 3.12.0) 106 | rspec-mocks (~> 3.12.0) 107 | rspec-core (3.12.0) 108 | rspec-support (~> 3.12.0) 109 | rspec-expectations (3.12.1) 110 | diff-lcs (>= 1.2.0, < 2.0) 111 | rspec-support (~> 3.12.0) 112 | rspec-its (1.3.0) 113 | rspec-core (>= 3.0.0) 114 | rspec-expectations (>= 3.0.0) 115 | rspec-mocks (3.12.1) 116 | diff-lcs (>= 1.2.0, < 2.0) 117 | rspec-support (~> 3.12.0) 118 | rspec-support (3.12.0) 119 | ruby-ll (2.1.2) 120 | ansi 121 | ast 122 | shellany (0.0.1) 123 | temple (0.9.1) 124 | thor (1.2.1) 125 | tilt (2.0.11) 126 | unf (0.1.4) 127 | unf_ext 128 | unf_ext (0.0.8.2) 129 | 130 | PLATFORMS 131 | arm64-darwin-22 132 | x86_64-linux 133 | 134 | DEPENDENCIES 135 | awestruct-ibeams! 136 | bundler (~> 2.3.26) 137 | pry 138 | rake (~> 13.0.6) 139 | rspec 140 | rspec-its 141 | 142 | BUNDLED WITH 143 | 2.3.26 144 | -------------------------------------------------------------------------------- /lib/awestruct/ibeams/asciidoc_sections.rb: -------------------------------------------------------------------------------- 1 | require 'asciidoctor' 2 | require 'naturally' 3 | require 'awestruct/ibeams/errors' 4 | 5 | module Awestruct 6 | module IBeams 7 | module AsciidocSections 8 | # sections_from() will take the given 9 | # 10 | # @param [String] relative or absolute path to a directory containing 11 | # asciidoc files (`.ad`, `.adoc`) 12 | # @return [Array] Array of arrays containing: [section_title, filepath, 13 | # link_to_section, subsections] 14 | # @raise [Errors::InvalidPath] thrown when the provided +directory+ is 15 | # not a valid path to an existing directory 16 | def sections_from(directory) 17 | if directory.nil? 18 | raise Errors::InvalidPath.new("The provided `directory` was nil") 19 | end 20 | unless File.directory?(directory) 21 | raise Errors::InvalidPath.new("The provided `#{directory}` is not a valid directory") 22 | end 23 | 24 | # Generate a +Hash+ of all the pages in the site keyed by their path 25 | # for easy discovery 26 | pages_by_path = site.pages.map { |p| [p.source_path, p] }.to_h 27 | sections = [] 28 | 29 | Naturally.sort(Dir.glob(File.join(directory, '*.{ad,adoc}'))).each do |adoc| 30 | # Since all our documents are going to have front-matter so they render 31 | # properly, we need to look first at the Awestruct::Page and then 32 | # re-render to grab the sections from the asciidoc 33 | page = pages_by_path[adoc] 34 | url = page.url 35 | 36 | document = Asciidoctor.load(page.raw_content) 37 | 38 | document.sections.each do |section| 39 | sections << [ 40 | section.title, 41 | { 42 | :asciidoc => document, 43 | :path => adoc, 44 | :page => page, 45 | }, 46 | url, 47 | find_subsections_from(section, document) 48 | ] 49 | end 50 | end 51 | 52 | return sections 53 | end 54 | 55 | 56 | # This method will recurse through all the subsections of the given 57 | # +section+ to discover the full tree of subsections and compute a nested 58 | # array containing them. 59 | # 60 | # @param [Asciidoctor::Section] section to discover subsections from 61 | # @param [Asciidoctor::Document] Document objection the section anchor 62 | # belongs to, this is necessary to ensure the document's settinsg are 63 | # used to provide the appropriate anchors 64 | # @return [Array] of subsection arrays 65 | def find_subsections_from(section, in_document) 66 | found = [] 67 | 68 | section.sections.each do |subsection| 69 | title = subsection.title 70 | found << [ 71 | title, 72 | { 73 | :asciidoc => in_document, 74 | :path => in_document.file, 75 | }, 76 | section_anchor(title, in_document), 77 | find_subsections_from(subsection, in_document), 78 | ] 79 | end 80 | 81 | return found 82 | end 83 | 84 | 85 | # Compute an appropriate section anchor for the given title within the 86 | # given +Asciidoctor::Document+. This is effectively the same logic that 87 | # Asciidoctor itself inside the +Asciidoctor::Section#generate_id+ method 88 | # but extracted to be callable in an idempotent fashion 89 | # 90 | # @param [String] title of the section provided 91 | # @param [Asciidoctor::Document] Document objection the section anchor 92 | # belongs to, this is necessary to ensure the document's settinsg are 93 | # used to provide the appropriate anchor 94 | # @return [String] Computed section anchor 95 | def section_anchor(title, in_document) 96 | prefix = in_document.attributes['idprefix'] || '' 97 | separator = in_document.attributes['idseparator'] || '-' 98 | title = title.downcase.gsub(Asciidoctor::InvalidSectionIdCharsRx, '') 99 | 100 | return [ 101 | prefix, 102 | title.tr_s(" ._-", separator).chomp(separator).delete_prefix(separator), 103 | ].join('') 104 | end 105 | end 106 | end 107 | end 108 | --------------------------------------------------------------------------------