From dcf77a73d7fee38fdc00dfce3b6a7f50ebd27636 Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Sat, 11 Jul 2020 06:33:51 -0400 Subject: [PATCH] feat: :tada: Initial Commit --- .gem_release.yml | 3 + .github/FUNDING.yml | 12 ++ .github/ISSUE_TEMPLATE/bug_report.md | 24 +++ .github/ISSUE_TEMPLATE/feature_request.md | 24 +++ .github/PULL_REQUEST_TEMPLATE.md | 23 +++ .github/workflows/linters.yml | 34 ++++ .github/workflows/tests.yml | 34 ++++ .gitignore | 38 +++++ .rspec | 2 + .solargraph.yml | 15 ++ .vscode/settings.json | 22 +++ CHANGELOG.md | 5 + CODE_OF_CONDUCT.md | 74 +++++++++ CONTRIBUTING.md | 24 +++ Gemfile | 11 ++ LICENSE.txt | 21 +++ README.md | 128 +++++++++++++++ Rakefile | 8 + _README.md | 5 + bin/check | 3 + bin/checks/standardrb | 4 + bin/console | 14 ++ bin/format | 3 + bin/formatters/standardrb | 4 + bin/rspec | 29 ++++ bin/setup | 8 + bin/standardrb | 29 ++++ bridgetown-inline-svg.gemspec | 37 +++++ lib/bridgetown-inline-svg.rb | 9 ++ lib/bridgetown-inline-svg/svg-tag.rb | 142 +++++++++++++++++ lib/bridgetown-inline-svg/version.rb | 5 + spec/bridgetown-inline-svg_spec.rb | 150 ++++++++++++++++++ ...40b622142f1c98125abcfe89a76a661b0e8e343910 | 1 + spec/fixtures/bridgetown.config.yml | 1 + spec/fixtures/src/_data/.keep | 0 spec/fixtures/src/_layouts/default.html | 10 ++ spec/fixtures/src/images/square.svg | 5 + spec/fixtures/src/index.html | 42 +++++ spec/spec_helper.rb | 28 ++++ 39 files changed, 1031 insertions(+) create mode 100644 .gem_release.yml create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/linters.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 .rspec create mode 100644 .solargraph.yml create mode 100644 .vscode/settings.json create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Gemfile create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 Rakefile create mode 100644 _README.md create mode 100755 bin/check create mode 100755 bin/checks/standardrb create mode 100755 bin/console create mode 100755 bin/format create mode 100755 bin/formatters/standardrb create mode 100755 bin/rspec create mode 100755 bin/setup create mode 100755 bin/standardrb create mode 100644 bridgetown-inline-svg.gemspec create mode 100644 lib/bridgetown-inline-svg.rb create mode 100644 lib/bridgetown-inline-svg/svg-tag.rb create mode 100644 lib/bridgetown-inline-svg/version.rb create mode 100644 spec/bridgetown-inline-svg_spec.rb create mode 100644 spec/fixtures/.bridgetown-cache/Bridgetown/Cache/Bridgetown--Cache/b7/9606fb3afea5bd1609ed40b622142f1c98125abcfe89a76a661b0e8e343910 create mode 100644 spec/fixtures/bridgetown.config.yml create mode 100644 spec/fixtures/src/_data/.keep create mode 100644 spec/fixtures/src/_layouts/default.html create mode 100644 spec/fixtures/src/images/square.svg create mode 100644 spec/fixtures/src/index.html create mode 100644 spec/spec_helper.rb diff --git a/.gem_release.yml b/.gem_release.yml new file mode 100644 index 0000000..e6fd7b4 --- /dev/null +++ b/.gem_release.yml @@ -0,0 +1,3 @@ +bump: + file: lib/bridgetown-inline-svg/version.rb + skip_ci: true diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..4a5cab7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: andrewmcodes +# patreon: # Replace with a single Patreon username +# open_collective: # Replace with a single Open Collective username +# ko_fi: # Replace with a single Ko-fi username +# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +# liberapay: # Replace with a single Liberapay username +# issuehunt: # Replace with a single IssueHunt username +# otechie: # Replace with a single Otechie username +# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..c6054dc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: andrewmcodes + +--- + +## What did you do? + +## What did you expect to happen? + +## What actually happened? + +## Additional context + +## Environment + +**Ruby Version:** + +**Framework Version (Rails, whatever):** + +**Dev Api Version:** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..a9f255a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: andrewmcodes + +--- + +## Is your feature request related to a problem? Please describe. + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +## Describe the solution you'd like + +A clear and concise description of what you want to happen. + +## Describe alternatives you've considered + +A clear and concise description of any alternative solutions or features you've considered. + +## Additional context + +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..26447f4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ + + +## What is the purpose of this pull request? + + + +## What changes did you make? (overview) + +## Is there anything you'd like reviewers to focus on? + +## Checklist + +- [ ] I've added tests for this change +- [ ] I've added a Changelog entry +- [ ] I've updated a documentation diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 0000000..b5f3847 --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,34 @@ +name: Linters + +on: + pull_request: + branches: + - "*" + push: + branches: + - main + +jobs: + standardrb: + name: StandardRB Check Action + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'ci skip')" + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby 2.7 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.1 + - name: Cache gems + uses: actions/cache@v1 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Install gems + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Run StandardRB + run: bin/checks/standardrb diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..37b4c2d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,34 @@ +name: Tests + +on: + pull_request: + branches: + - "*" + push: + branches: + - main + +jobs: + tests: + name: Rspec Tests + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'ci skip')" + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby 2.7 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.1 + - name: Cache gems + uses: actions/cache@v1 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Install gems + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Run Rspec + run: bin/rspec diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e22231 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +/vendor +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +*.bundle +*.so +*.o +*.a +mkmf.log +*.gem +Gemfile.lock +.bundle +.ruby-version + +# Node +node_modules +.npm +.node_repl_history + +# Yarn +yarn-error.log +yarn-debug.log* +.pnp/ +.pnp.js + +# Yarn Integrity file +.yarn-integrity + +spec/dest +/.bridgetown-metadata/ +/.bridgetown-cache/ +/.bridgetown-webpack/ diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..5f16476 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--format progress diff --git a/.solargraph.yml b/.solargraph.yml new file mode 100644 index 0000000..7dfcf02 --- /dev/null +++ b/.solargraph.yml @@ -0,0 +1,15 @@ +--- +include: + - "**/*.rb" +exclude: + - spec/**/* + - test/**/* + - vendor/**/* + - ".bundle/**/*" +require: [] +domains: [] +reporters: + - require_not_found +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6d14166 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + // EDITOR + "editor.formatOnSave": true, + // "editor.defaultFormatter": "esbenp.prettier-vscode", + "[ruby]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "rebornix.ruby" + }, + // RUBY EXTENSION + "ruby.useLanguageServer": true, + "ruby.lintDebounceTime": 1500, + "ruby.useBundler": true, + "ruby.lint": { + "standard": true, + "rubocop": false + }, + "ruby.format": "standard", + // SOLARGRAPH EXTENSION + "solargraph.diagnostics": true, + // GIT + "git.ignoreLimitWarning": true +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..aff1cdb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Change log + +## main + +[@andrewmcodes]: https://github.com/andrewmcodes diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f911e37 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at andrewmcodes@protonmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [https://contributor-covenant.org/version/1/4][version] + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..234679a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# Contributing + +## Code of Conduct + +Everyone interacting with the bridgetown-inline-svg project’s codebase, issue trackers, etc. is expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md). + +## Linters + +This project uses [Standard](https://github.com/testdouble/standard) for Ruby code to minimize bike shedding related to source formatting. + +Please run `./bin/format` prior to submitting pull requests. + +## Testing + +* Run `bundle exec rspec` to run the test suite + +## Overview + +1. Fork it (https://github.com/andrewmcodes/bridgetown-inline-svg/fork) +2. Clone the fork using `git clone` to your local development machine. +3. Create your feature branch (`git checkout -b my-new-feature`) +4. Commit your changes (`git commit -am 'Add some feature'`) +5. Push to the branch (`git push origin my-new-feature`) +6. Create a new Pull Request diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..3c0f1f6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +source "https://rubygems.org" +gemspec + +gem "bridgetown", ENV["BRIDGETOWN_VERSION"] if ENV["BRIDGETOWN_VERSION"] + +gem "rake", "~> 12.0" +gem "rspec", "~> 3.0" +gem "standard", "~> 0.4" +gem "solargraph", "~> 0.39" diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..64a3ac3 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Andrew Mason + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8475c69 --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +

Welcome to bridgetown-inline-svg 👋

+

+ Version + + License: MIT + + + Twitter: andrewmcodes + +

+ +> SVG optimizer and inliner for Bridgetown + +### 🏠 [Homepage](https://github.com/andrewmcodes/bridgetown-inline-svg) + +- [Installation](#installation) + - [Optional configuration options](#optional-configuration-options) +- [Usage](#usage) + - [Optimizations](#optimizations) +- [Author](#author) +- [Contributing](#contributing) +- [Show your support](#show-your-support) +- [License](#license) + +## Installation + +Run this command to add this plugin to your site's Gemfile: + +```shell +bundle add bridgetown-inline-svg -g bridgetown_plugins +``` + +or add the following to your `Gemfile`: + +```ruby +group :bridgetown_plugins do + gem "bridgetown-inline-svg", "~> 0.0.1" +end +``` + +### Optional configuration options + +Optimization is opt-in and can be enabled by adding this to your `bridgetown.config.yml` + +``` +svg: + optimize: true +``` + +## Usage + +Use the Liquid tag in your pages : + +```liquid +{% svg /path/to/square.svg width=24 foo="bar" %} +``` + +Bridgetown will include the svg file in your output HTML like this : + +```html + + + +``` + +**Note** : You will generally want to set the width/height of your SVG or a `style` attribute, but anything can be passed through. + +Paths with a space should be quoted : + +```liquid +{% svg "/path/to/foo bar.svg" %} +# or : +{% svg '/path/to/foo bar.svg' %} +``` +Otherwise anything after the first space will be considered an attribute. + +Liquid variables will be interpreted if enclosed in double brackets : + +```liquid +{% assign size=40 %} +{% svg "/path/to/{{site.foo-name}}.svg" width="{{size}}" %} +``` + +`height` is automatically set to match `width` if omitted. It can't be left unset because IE11 won't use the viewport attribute to calculate the image's aspect ratio. + +Relative paths and absolute paths will both be interpreted from Bridgetown's `src` directory. So both: + +```liquid +{% svg "/path/to/foo.svg" %} +{% svg "path/to/foo.svg" %} +``` + +Should resolve to `/your/site/src/path/to/foo.svg`. + +### Optimizations + +Some processing is done to remove useless data when enabled: + +- metadata +- comments +- unused groups +- Other filters from [svg_optimizer](https://github.com/fnando/svg_optimizer) +- default size + +If any important data gets removed, or the output SVG looks different from input, it's a bug. Please file an issue to this repository describing your problem. + +It does not perform any input validation on attributes. They will be appended as-is to the root node. + +## Author + +👤 **Andrew Mason** + +* Website: https://www.andrewm.codes +* Twitter: [@andrewmcodes](https://twitter.com/andrewmcodes) +* Github: [@andrewmason](https://github.com/andrewmason) + +## Contributing + +Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/andrewmcodes/bridgetown-inline-svg/issues). You can also take a look at the [contributing guide](https://github.com/andrewmcodes/bridgetown-inline-svg/blob/main/CONTRIBUTING.md). + +## Show your support + +Give a ⭐️ if this project helped you! + +## License + +Copyright © 2020 [Andrew Mason](https://github.com/andrewmason).
+This project is [MIT](LICENSE.txt) licensed. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..b6ae734 --- /dev/null +++ b/Rakefile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +task default: :spec diff --git a/_README.md b/_README.md new file mode 100644 index 0000000..39524f6 --- /dev/null +++ b/_README.md @@ -0,0 +1,5 @@ +# bridgetown-inline-svg + +SVG optimizer and inliner for [Bridgetown](https://www.bridgetownrb.com) + +This liquid tag will let you inline SVG images in your bridgetown sites. It will add `{%svg %}` to `Liquid::Tag`. diff --git a/bin/check b/bin/check new file mode 100755 index 0000000..589bf7d --- /dev/null +++ b/bin/check @@ -0,0 +1,3 @@ +#!/bin/bash + +bin/checks/standardrb diff --git a/bin/checks/standardrb b/bin/checks/standardrb new file mode 100755 index 0000000..e1fcdeb --- /dev/null +++ b/bin/checks/standardrb @@ -0,0 +1,4 @@ +#!/bin/bash + +echo "== Checking StandardRb ==" +bundle exec standardrb --format=progress diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..a9c76fb --- /dev/null +++ b/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "dev_api" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/bin/format b/bin/format new file mode 100755 index 0000000..a2f553e --- /dev/null +++ b/bin/format @@ -0,0 +1,3 @@ +#!/bin/bash + +bin/formatters/standardrb diff --git a/bin/formatters/standardrb b/bin/formatters/standardrb new file mode 100755 index 0000000..a274b56 --- /dev/null +++ b/bin/formatters/standardrb @@ -0,0 +1,4 @@ +#!/bin/bash + +echo "== StandardRb ==" +bundle exec standardrb --fix --format=progress diff --git a/bin/rspec b/bin/rspec new file mode 100755 index 0000000..a6c7852 --- /dev/null +++ b/bin/rspec @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rspec' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rspec-core", "rspec") diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/bin/standardrb b/bin/standardrb new file mode 100755 index 0000000..6fc2e1a --- /dev/null +++ b/bin/standardrb @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'standardrb' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("standard", "standardrb") diff --git a/bridgetown-inline-svg.gemspec b/bridgetown-inline-svg.gemspec new file mode 100644 index 0000000..d6e9ecb --- /dev/null +++ b/bridgetown-inline-svg.gemspec @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require_relative "lib/bridgetown-inline-svg/version" + +Gem::Specification.new do |spec| + spec.name = "bridgetown-inline-svg" + spec.version = BridgetownInlineSvg::VERSION + spec.authors = ["Andrew Mason"] + spec.email = ["andrewmcodes@protonmail.com"] + + spec.summary = "A SVG Inliner for Bridgetown" + spec.description = <<-EOF + A Liquid tag to inline and optimize SVG images in your HTML + Supports custom DOM Attributes parameters and variables interpretation. + EOF + spec.homepage = "https://github.com/andrewmcodes/bridgetown-inline-svg" + spec.license = "MIT" + + spec.metadata = { + "bug_tracker_uri" => "#{spec.homepage}/issues", + "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md", + "documentation_uri" => spec.homepage.to_s, + "homepage_uri" => spec.homepage.to_s, + "source_code_uri" => spec.homepage.to_s + } + + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|script|spec|features|frontend)/}) } + spec.test_files = spec.files.grep(%r{^spec/}) + spec.require_paths = ["lib"] + + spec.required_ruby_version = ">= 2.5.0" + + spec.add_dependency "bridgetown", ">= 0.15", "< 2.0" + spec.add_dependency "svg_optimizer", "~> 0.2.5" + + spec.add_development_dependency "nokogiri", "~> 1.6" +end diff --git a/lib/bridgetown-inline-svg.rb b/lib/bridgetown-inline-svg.rb new file mode 100644 index 0000000..58e7707 --- /dev/null +++ b/lib/bridgetown-inline-svg.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "bridgetown" + +module BridgetownInlineSvg + autoload :SvgTag, "bridgetown-inline-svg/svg-tag" +end + +Liquid::Template.register_tag("svg", BridgetownInlineSvg::SvgTag) diff --git a/lib/bridgetown-inline-svg/svg-tag.rb b/lib/bridgetown-inline-svg/svg-tag.rb new file mode 100644 index 0000000..51cbf9c --- /dev/null +++ b/lib/bridgetown-inline-svg/svg-tag.rb @@ -0,0 +1,142 @@ +require "nokogiri" +require "svg_optimizer" +require "bridgetown-core/liquid_extensions" + +PLUGINS_BLOCKLIST = [ + SvgOptimizer::Plugins::CleanupId +] + +PLUGINS = SvgOptimizer::DEFAULT_PLUGINS.delete_if { |plugin| + PLUGINS_BLOCKLIST.include? plugin +} + +module BridgetownInlineSvg + class SvgTag < Liquid::Tag + # import lookup_variable function + # https://github.com/bridgetownrb/bridgetown/blob/main/bridgetown-core/lib/bridgetown-core/liquid_extensions.rb + include Bridgetown::LiquidExtensions + + # For interpoaltion, look for liquid variables + VARIABLE = /\{\{\s*([\w.\-]+)\s*\}\}/i + + # Separate file path from other attributes + PATH_SYNTAX = %r{ + ^(?[^\s"']+|"[^"]+"|'[^']+') + (?.*) + }x + + # parse the first parameter in a string, giving : + # [full_match, param_name, double_quoted_val, single_quoted_val, unquoted_val] + # The Regex works like : + # - first group + # - match a group of characters that is alphanumeric, _ or -. + # - second group (non-capturing OR) + # - match a double-quoted string + # - match a single-quoted string + # - match an unquoted string matching the set : [\w\.\-#] + PARAM_SYNTAX = %r{ + ([\w-]+)\s*=\s* + (?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w.\-#]+)) + }x + + def initialize(tag_name, markup, tokens) + super + @svg, @params = BridgetownInlineSvg::SvgTag.parse_params(markup) + end + + # lookup Liquid variables from markup in context + def interpolate(markup, context) + markup.scan VARIABLE do |variable| + markup = markup.sub(VARIABLE, lookup_variable(context, variable.first)) + end + markup + end + + def split_params(markup, context) + params = {} + while (match = PARAM_SYNTAX.match(markup)) + markup = markup[match.end(0)..-1] + value = if match[2] + interpolate(match[2].gsub(%r{\\"}, '"'), context) + elsif match[3] + interpolate(match[3].gsub(%r{\\'}, "'"), context) + elsif match[4] + lookup_variable(context, match[4]) + end + params[match[1]] = value + end + params + end + + # Parse parameters. Returns : [svg_path, parameters] + # Does not interpret variables as it's done at render time + def self.parse_params(markup) + matched = markup.strip.match(PATH_SYNTAX) + unless matched + raise SyntaxError, <<~END + Syntax Error in tag 'highlight' while parsing the following markup: + #{markup} + Valid syntax: svg [property=value] + END + end + path = matched["path"].sub(%r{^["']}, "").sub(%r{["']$}, "").strip + params = matched["params"].strip + [path, params] + end + + def fmt(params) + r = params.to_a.select { |v| v[1] != "" }.map { |v| %(#{v[0]}="#{v[1]}") } + r.join(" ") + end + + def create_plugin(params) + mod = Class.new(SvgOptimizer::Plugins::Base) { + def self.set(p) + @@params = p + end + + def process + @@params.each { |key, val| xml.root.set_attribute(key, val) } + xml + end + } + mod.set(params) + mod + end + + def add_file_to_dependency(site, path, context) + if context.registers[:page]&.key?("path") + site.regenerator.add_dependency( + site.in_source_dir(context.registers[:page]["path"]), + path + ) + end + end + + def render(context) + # global site variable + site = context.registers[:site] + # check if given name is a variable. Otherwise use it as a file name + svg_file = Bridgetown.sanitized_path(site.source, interpolate(@svg, context)) + return unless svg_file + add_file_to_dependency(site, svg_file, context) + # replace variables with their current value + params = split_params(@params, context) + # because ie11 require to have a height AND a width + if params.key?("width") && !params.key?("height") + params["height"] = params["width"] + end + # params = @params + file = File.open(svg_file, "rb").read + conf = lookup_variable(context, "site.svg") + if conf["optimize"] == true + xml = SvgOptimizer.optimize(file, [create_plugin(params)] + PLUGINS) + else + xml = Nokogiri::XML(file) + params.each { |key, val| xml.root.set_attribute(key, val) } + xml = xml.root.to_xml + end + xml + end + end +end diff --git a/lib/bridgetown-inline-svg/version.rb b/lib/bridgetown-inline-svg/version.rb new file mode 100644 index 0000000..9cad9ce --- /dev/null +++ b/lib/bridgetown-inline-svg/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module BridgetownInlineSvg + VERSION = "0.0.1" +end diff --git a/spec/bridgetown-inline-svg_spec.rb b/spec/bridgetown-inline-svg_spec.rb new file mode 100644 index 0000000..2028493 --- /dev/null +++ b/spec/bridgetown-inline-svg_spec.rb @@ -0,0 +1,150 @@ +require "spec_helper" +require "nokogiri" + +describe(BridgetownInlineSvg::SvgTag) do + def read(f) + File.read(dest_dir(f)) + end + + # return an array of the page's svgs + def parse(f) + Nokogiri::HTML(read(f)) + end + + describe "#parse_params" do + it "parse XML root parameters" do + svg, params = BridgetownInlineSvg::SvgTag.parse_params("/path/to/foo size=40 style=\"hello\"") + expect(svg).to eq("/path/to/foo") + expect(params).to eq("size=40 style=\"hello\"") + end + + it "accepts double quoted path" do + svg, _params = BridgetownInlineSvg::SvgTag.parse_params("\"/path/to/foo space\"") + expect(svg).to eq("/path/to/foo space") + end + + it "accepts single quoted path" do + svg, _params = BridgetownInlineSvg::SvgTag.parse_params("'/path/to/foo space'") + expect(svg).to eq("/path/to/foo space") + end + + it "strip leading and trailing spaces" do + svg, _params = BridgetownInlineSvg::SvgTag.parse_params(" /path/to/foo ") + expect(svg).to eql("/path/to/foo") + end + + # required when a variable is defined with leading/trailing space then embedded. + it "strip in-quote leading and trailing spaces" do + svg, _params = BridgetownInlineSvg::SvgTag.parse_params("'/path/to/foo '") + expect(svg).to eql("/path/to/foo") + end + + it "keep Liquid variables" do + svg, _params = BridgetownInlineSvg::SvgTag.parse_params("/path/to/{{foo}}") + expect(svg).to eql("/path/to/{{foo}}") + end + + it "don't parse parameters" do + _svg, params = BridgetownInlineSvg::SvgTag.parse_params("'/path/to/foo space' id='bar' style=\"hello\"") + expect(params).to eq("id='bar' style=\"hello\"") + end + + it "raise error on invalid syntax" do + expect { BridgetownInlineSvg::SvgTag.parse_params("") }.to raise_error SyntaxError + end + end + [ + Bridgetown.configuration({ + "svg" => {"optimize" => true}, + "full_rebuild" => true, + "root_dir" => root_dir, + "source" => source_dir, + "destination" => dest_dir + }), + Bridgetown.configuration({ + "full_rebuild" => true, + "root_dir" => root_dir, + "source" => source_dir, + "destination" => dest_dir + }) + ].each do |config| + (is_opt = config["svg"]) && (config["svg"]["optimize"] == true) + describe "Integration (with #{is_opt ? "" : "no"} optimisation)" do + before(:context) do + site = Bridgetown::Site.new(config) + site.process + @text = read("index.html") + @data = parse("index.html") + @base = @data.css("#base").css("svg").first + end + + it "render site" do + expect(File.exist?(dest_dir("index.html"))).to be_truthy + end + + it "exports svg" do + data = @data.xpath("//svg") + expect(data).to be_truthy + expect(data.first).to be_truthy + expect(@base).to be_truthy + # Do not strip other width and height attributes + end + + it "add a height if only width is given" do + data = @data.css("#height").css("svg") + expect(data).to be_truthy + expect(data[0].get_attribute("height")).to eql("24") + expect(data[0].get_attribute("width")).to eql("24") + # do not set height if given + expect(data[1].get_attribute("height")).to eql("48") + expect(data[1].get_attribute("width")).to eql("24") + # do not set height if forced to empty string + expect(data[2].get_attribute("height")).to is_opt ? be_falsy : eql("") + + expect(data[2].get_attribute("width")).to eql("24") + end + + it "keep attributes" do + data = @data.css("#attributes").css("svg") + expect(data).to be_truthy + expect(data[0].get_attribute("role")).to eql("navigation") + expect(data[0].get_attribute("data-foo")).to eql("bar") + expect(data[0].get_attribute("fill")).to eql("#ffffff") + expect(data[0].get_attribute("stroke")).to eql("#000000") + end + + it "parse relative paths" do + data = @data.css("#path").css("svg") + expect(data.size).to eq(2) + expect(data[0].to_html).to eq(data[1].to_html) # should use to_xml? + end + + it "jails to Bridgetown source" do + data = @data.css("#jail").css("svg") + ref = @base.to_xml + expect(data.size).to eq(2) + data.each { |item| expect(item.to_xml).to eql(ref) } + end + + it "interpret variables" do + data = @data.css("#interpret").css("svg") + ref = @base.to_xml + expect(data.size).to eq(3) + expect(data[0].to_xml).to eql(ref) + expect(data[1].get_attribute("id")).to eql("name-square") + expect(data[1].get_attribute("class")).to eql("class-hello") + expect(data[2].get_attribute("class")).to eql("hyphens-var-test") + end + + it "#{is_opt ? "do" : "do not"} optimize" do + data = @data.css("#optimize").css("svg") + expect(data.first.get_attribute("data-foo")).to is_opt ? be_falsy : eql("") + expect(data.xpath("//comment()").first).to is_opt ? be_falsy : be_truthy + end + + it "don't add junk" do + expect(@text).not_to include(""/Users/andrew.mason/work/personal/OSS/bridgetown/bridgetown-inline-svg/spec/fixtures", "plugins_dir"=>"plugins", "source"=>"/Users/andrew.mason/work/personal/OSS/bridgetown/bridgetown-inline-svg/spec/fixtures/src", "destination"=>"/Users/andrew.mason/work/personal/OSS/bridgetown/bridgetown-inline-svg/spec/dest", "collections_dir"=>"", "cache_dir"=>".bridgetown-cache", "layouts_dir"=>"_layouts", "data_dir"=>"_data", "components_dir"=>"_components", "includes_dir"=>"_includes", "collections"=>{"posts"=>{"output"=>true, "permalink"=>"/:categories/:year/:month/:day/:title:output_ext"}}, "include"=>[".htaccess", "_redirects", ".well-known"], "exclude"=>[".sass-cache", ".bridgetown-cache", "gemfiles", "Gemfile", "Gemfile.lock", "node_modules", "vendor/bundle/", "vendor/cache/", "vendor/gems/", "vendor/ruby/"], "keep_files"=>[".git", ".svn", "_bridgetown"], "encoding"=>"utf-8", "markdown_ext"=>"markdown,mkdown,mkdn,mkd,md", "strict_front_matter"=>false, "slugify_categories"=>true, "limit_posts"=>0, "future"=>false, "unpublished"=>false, "ruby_in_front_matter"=>true, "markdown"=>"kramdown", "highlighter"=>"rouge", "lsi"=>false, "excerpt_separator"=>"\n\n", "incremental"=>false, "detach"=>false, "port"=>"4000", "host"=>"127.0.0.1", "baseurl"=>nil, "show_dir_listing"=>false, "permalink"=>"date", "timezone"=>"UTC", "quiet"=>false, "verbose"=>false, "defaults"=>[], "liquid"=>{"error_mode"=>"warn", "strict_filters"=>false, "strict_variables"=>false}, "kramdown"=>{"auto_ids"=>true, "toc_levels"=>[1, 2, 3, 4, 5, 6], "entity_output"=>"as_char", "smart_quotes"=>"lsquo,rsquo,ldquo,rdquo", "input"=>"GFM", "hard_wrap"=>false, "guess_lang"=>true, "footnote_nr"=>1, "show_warnings"=>false}, "full_rebuild"=>true}:ET \ No newline at end of file diff --git a/spec/fixtures/bridgetown.config.yml b/spec/fixtures/bridgetown.config.yml new file mode 100644 index 0000000..f421540 --- /dev/null +++ b/spec/fixtures/bridgetown.config.yml @@ -0,0 +1 @@ +timezone: UTC diff --git a/spec/fixtures/src/_data/.keep b/spec/fixtures/src/_data/.keep new file mode 100644 index 0000000..e69de29 diff --git a/spec/fixtures/src/_layouts/default.html b/spec/fixtures/src/_layouts/default.html new file mode 100644 index 0000000..17022f1 --- /dev/null +++ b/spec/fixtures/src/_layouts/default.html @@ -0,0 +1,10 @@ +--- +--- + + + + + THIS IS MY LAYOUT + {{ content }} + + diff --git a/spec/fixtures/src/images/square.svg b/spec/fixtures/src/images/square.svg new file mode 100644 index 0000000..0532afa --- /dev/null +++ b/spec/fixtures/src/images/square.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/spec/fixtures/src/index.html b/spec/fixtures/src/index.html new file mode 100644 index 0000000..1b92463 --- /dev/null +++ b/spec/fixtures/src/index.html @@ -0,0 +1,42 @@ +--- +layout: default +svgname: square +foo-bar: "hyphens-var-test" +svgclass: hello +--- + +
+{% svg images/square.svg %} +
+ +
+{% svg images/square.svg width=24 %} +{% svg images/square.svg width=24 height=48 %} +{% svg images/square.svg width=24 height="" %} +
+
+ {% svg images/square.svg role="navigation" data-foo="bar" fill="#ffffff" stroke=#000000 %} +
+
+ + {% svg images/square.svg %} + {% svg /images/square.svg %} +
+ +
+ + {% svg /../images/square.svg %} + {% svg ../images/square.svg %} +
+ +
+ + {% svg /images/{{page.svgname}}.svg %} + {% svg /images/square.svg id="name-{{page.svgname}}" class="class-{{page.svgclass}}" %} + {% svg /images/square.svg id="test-hyphens" class="{{page.foo-bar}}" %} + +
+ +
+ {% svg /images/{{page.svgname}}.svg data-foo="" %} +
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..b896684 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "bridgetown" +require File.expand_path("../lib/bridgetown-inline-svg", __dir__) + +Bridgetown.logger.log_level = :error + +RSpec.configure do |config| + config.run_all_when_everything_filtered = true + config.filter_run :focus + config.order = "random" + + ROOT_DIR = File.expand_path("fixtures", __dir__) + SOURCE_DIR = File.join(ROOT_DIR, "src") + DEST_DIR = File.expand_path("dest", __dir__) + + def root_dir(*files) + File.join(ROOT_DIR, *files) + end + + def source_dir(*files) + File.join(SOURCE_DIR, *files) + end + + def dest_dir(*files) + File.join(DEST_DIR, *files) + end +end