├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── gem-push.yml │ ├── rubocop-analysis.yml │ └── ruby.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── Makefile ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── Rakefile ├── bin └── console ├── jekyll-typogrify.gemspec ├── lib ├── jekyll-typogrify.rb └── jekyll │ ├── typogrify.rb │ └── typogrify │ └── version.rb ├── screenshots ├── after.png └── before.png └── spec ├── fixtures ├── _config.yml ├── _layouts │ └── default.html └── index.html ├── jekyll └── typogrify_spec.rb └── spec_helper.rb /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.rb] 2 | indent_style = space 3 | indent_size = 2 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/gem-push.yml: -------------------------------------------------------------------------------- 1 | name: Ruby Gem 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | name: Build + Publish 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Set up Ruby 2.6 15 | uses: actions/setup-ruby@v1 16 | with: 17 | ruby-version: 2.6.x 18 | 19 | - name: Publish to GPR 20 | run: | 21 | mkdir -p $HOME/.gem 22 | touch $HOME/.gem/credentials 23 | chmod 0600 $HOME/.gem/credentials 24 | printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 25 | gem build *.gemspec 26 | gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem 27 | env: 28 | GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}" 29 | OWNER: ${{ github.repository_owner }} 30 | 31 | - name: Publish to RubyGems 32 | run: | 33 | mkdir -p $HOME/.gem 34 | touch $HOME/.gem/credentials 35 | chmod 0600 $HOME/.gem/credentials 36 | printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 37 | gem build *.gemspec 38 | gem push *.gem 39 | env: 40 | GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}" 41 | -------------------------------------------------------------------------------- /.github/workflows/rubocop-analysis.yml: -------------------------------------------------------------------------------- 1 | # pulled from repo 2 | name: "Rubocop" 3 | 4 | on: push 5 | 6 | jobs: 7 | rubocop: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v2 15 | 16 | # If running on a self-hosted runner, check it meets the requirements 17 | # listed at https://github.com/ruby/setup-ruby#using-self-hosted-runners 18 | - name: Set up Ruby 19 | uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: 2.6 22 | 23 | # This step is not necessary if you add the gem to your Gemfile 24 | - name: Install Code Scanning integration 25 | run: bundle add code-scanning-rubocop --version 0.3.0 --skip-install 26 | 27 | - name: Install dependencies 28 | run: bundle install 29 | 30 | - name: Rubocop run 31 | run: | 32 | bash -c " 33 | bundle exec rubocop --require code_scanning --format CodeScanning::SarifFormatter -o rubocop.sarif 34 | [[ $? -ne 2 ]] 35 | " 36 | 37 | - name: Upload Sarif output 38 | uses: github/codeql-action/upload-sarif@v1 39 | with: 40 | sarif_file: rubocop.sarif 41 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake 6 | # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby 7 | 8 | name: Ruby 9 | 10 | on: 11 | push: 12 | branches: [ master ] 13 | pull_request: 14 | branches: [ master ] 15 | 16 | jobs: 17 | test: 18 | 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Set up Ruby 25 | # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, 26 | # change this to (see https://github.com/ruby/setup-ruby#versioning): 27 | uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: 2.6 30 | 31 | - name: Install dependencies 32 | run: bundle install 33 | 34 | - name: Run tests 35 | run: bundle exec rake 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | spec/fixtures/.jekyll-cache/ 3 | 4 | *.gem 5 | *.rbc 6 | /.config 7 | /coverage/ 8 | /InstalledFiles 9 | /pkg/ 10 | /spec/reports/ 11 | /spec/examples.txt 12 | /test/tmp/ 13 | /test/version_tmp/ 14 | /tmp/ 15 | 16 | # Used by dotenv library to load environment variables. 17 | .env 18 | 19 | # Ignore Byebug command history file. 20 | .byebug_history 21 | 22 | ## Specific to RubyMotion: 23 | .dat* 24 | .repl_history 25 | build/ 26 | *.bridgesupport 27 | build-iPhoneOS/ 28 | build-iPhoneSimulator/ 29 | 30 | ## Specific to RubyMotion (use of CocoaPods): 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # vendor/Pods/ 37 | 38 | ## Documentation cache and generated files: 39 | /.yardoc/ 40 | /_yardoc/ 41 | /doc/ 42 | /rdoc/ 43 | 44 | ## Environment normalization: 45 | /.bundle/ 46 | /vendor/bundle 47 | /lib/bundler/man/ 48 | 49 | # for a library or gem, you might want to ignore these files since the code is 50 | # intended to run in multiple environments; otherwise, check them in: 51 | # Gemfile.lock 52 | # .ruby-version 53 | # .ruby-gemset 54 | 55 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 56 | .rvmrc 57 | 58 | # Used by RuboCop. Remote config files pulled in from inherit_from directive. 59 | .rubocop-https?--* -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: enable 3 | Exclude: 4 | - "spec/**/*" 5 | - "vendor/**/*" 6 | TargetRubyVersion: 2.7 7 | 8 | Layout: 9 | Enabled: false 10 | 11 | Metrics: 12 | Enabled: false 13 | 14 | Style: 15 | Enabled: false 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | 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. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | 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. 28 | 29 | 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. 30 | 31 | ## Scope 32 | 33 | 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. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at me@mylesb.ca. The project team will review and investigate all complaints, and will respond in a way that it deems 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. 38 | 39 | 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. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | I love to get pull requests from everyone. By participating in this project, you agree to abide by the [code of conduct](CODE_OF_CONDUCT.md). 4 | 5 | [Fork](https://github.com/myles/jekyll-typogrify/fork), then clone the repo: 6 | 7 | git clone git@github.com:your-username/jekyll-typogrify.git 8 | 9 | Set up your machine: 10 | 11 | make setup 12 | 13 | Make sure the tests pass 14 | 15 | make test 16 | 17 | Make your change. Add tests for your change. Make the tests pass: 18 | 19 | make test 20 | 21 | Push to your fork and [submit a pull request](https://github.com/myles/jekyll-typogrify/compare/). 22 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "titlecase" 4 | gem "typogruby" 5 | 6 | group :test do 7 | gem "codeclimate-test-reporter", "~> 1.0.0" 8 | gem "pry" 9 | gem "rubocop" 10 | gem "rufo" 11 | gem "simplecov" 12 | end 13 | 14 | gemspec 15 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | jekyll-typogrify (0.3.5) 5 | titlecase 6 | typogruby 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | addressable (2.8.0) 12 | public_suffix (>= 2.0.2, < 5.0) 13 | ast (2.4.2) 14 | codeclimate-test-reporter (1.0.8) 15 | simplecov (<= 0.13) 16 | coderay (1.1.3) 17 | colorator (1.1.0) 18 | concurrent-ruby (1.1.6) 19 | diff-lcs (1.3) 20 | docile (1.1.5) 21 | em-websocket (0.5.1) 22 | eventmachine (>= 0.12.9) 23 | http_parser.rb (~> 0.6.0) 24 | eventmachine (1.2.7) 25 | ffi (1.13.1) 26 | forwardable-extended (2.6.0) 27 | http_parser.rb (0.6.0) 28 | i18n (1.8.3) 29 | concurrent-ruby (~> 1.0) 30 | jekyll (4.1.0) 31 | addressable (~> 2.4) 32 | colorator (~> 1.0) 33 | em-websocket (~> 0.5) 34 | i18n (~> 1.0) 35 | jekyll-sass-converter (~> 2.0) 36 | jekyll-watch (~> 2.0) 37 | kramdown (~> 2.1) 38 | kramdown-parser-gfm (~> 1.0) 39 | liquid (~> 4.0) 40 | mercenary (~> 0.4.0) 41 | pathutil (~> 0.9) 42 | rouge (~> 3.0) 43 | safe_yaml (~> 1.0) 44 | terminal-table (~> 1.8) 45 | jekyll-sass-converter (2.1.0) 46 | sassc (> 2.0.1, < 3.0) 47 | jekyll-watch (2.2.1) 48 | listen (~> 3.0) 49 | json (2.3.1) 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.1) 56 | rb-fsevent (~> 0.10, >= 0.10.3) 57 | rb-inotify (~> 0.9, >= 0.9.10) 58 | mercenary (0.4.0) 59 | method_source (1.0.0) 60 | parallel (1.24.0) 61 | parser (3.2.2.4) 62 | ast (~> 2.4.1) 63 | racc 64 | pathutil (0.16.2) 65 | forwardable-extended (~> 2.6) 66 | pry (0.14.2) 67 | coderay (~> 1.1) 68 | method_source (~> 1.0) 69 | public_suffix (4.0.6) 70 | racc (1.7.3) 71 | rainbow (3.1.1) 72 | rake (13.0.1) 73 | rb-fsevent (0.10.4) 74 | rb-inotify (0.10.1) 75 | ffi (~> 1.0) 76 | regexp_parser (2.8.3) 77 | rexml (3.2.5) 78 | rouge (3.20.0) 79 | rspec (3.7.0) 80 | rspec-core (~> 3.7.0) 81 | rspec-expectations (~> 3.7.0) 82 | rspec-mocks (~> 3.7.0) 83 | rspec-core (3.7.1) 84 | rspec-support (~> 3.7.0) 85 | rspec-expectations (3.7.0) 86 | diff-lcs (>= 1.2.0, < 2.0) 87 | rspec-support (~> 3.7.0) 88 | rspec-mocks (3.7.0) 89 | diff-lcs (>= 1.2.0, < 2.0) 90 | rspec-support (~> 3.7.0) 91 | rspec-support (3.7.0) 92 | rubocop (1.42.0) 93 | json (~> 2.3) 94 | parallel (~> 1.10) 95 | parser (>= 3.1.2.1) 96 | rainbow (>= 2.2.2, < 4.0) 97 | regexp_parser (>= 1.8, < 3.0) 98 | rexml (>= 3.2.5, < 4.0) 99 | rubocop-ast (>= 1.24.1, < 2.0) 100 | ruby-progressbar (~> 1.7) 101 | unicode-display_width (>= 1.4.0, < 3.0) 102 | rubocop-ast (1.30.0) 103 | parser (>= 3.2.1.0) 104 | ruby-progressbar (1.13.0) 105 | rubypants (0.6.0) 106 | rufo (0.16.3) 107 | safe_yaml (1.0.5) 108 | sassc (2.4.0) 109 | ffi (~> 1.9) 110 | simplecov (0.13.0) 111 | docile (~> 1.1.0) 112 | json (>= 1.8, < 3) 113 | simplecov-html (~> 0.10.0) 114 | simplecov-html (0.10.2) 115 | terminal-table (1.8.0) 116 | unicode-display_width (~> 1.1, >= 1.1.1) 117 | titlecase (0.1.1) 118 | typogruby (1.0.18) 119 | rubypants 120 | unicode-display_width (1.7.0) 121 | 122 | PLATFORMS 123 | ruby 124 | 125 | DEPENDENCIES 126 | bundler (~> 1.7) 127 | codeclimate-test-reporter (~> 1.0.0) 128 | jekyll 129 | jekyll-typogrify! 130 | pry 131 | rake (~> 13.0) 132 | rspec (~> 3.0) 133 | rubocop 134 | rufo 135 | simplecov 136 | titlecase 137 | typogruby 138 | 139 | BUNDLED WITH 140 | 1.16.2 141 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Myles Braithwaite 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: setup 2 | setup: 3 | bundle install --path vendor/bundle 4 | 5 | .PHONY: test 6 | test: 7 | bundle exec rake 8 | 9 | .PHONY: jekyll 10 | jekyll: 11 | bundle exec jekyll serve \ 12 | --config ./spec/fixtures/_config.yml \ 13 | -d ./spec/dest \ 14 | -s ./spec/fixtures/ \ 15 | --watch 16 | 17 | .PHONY: console 18 | console: 19 | bundle exec ruby bin/console 20 | 21 | .PHONY: lint 22 | lint: 23 | bundle exec rufo -c . 24 | bundle exec rubocop 25 | 26 | .PHONY: lintfix 27 | lintfix: 28 | bundle exec rufo . 29 | bundle exec rubocop -a 30 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Status 2 | 3 | READY | IN DEVELOPMENT | HOLD 4 | 5 | ## Description 6 | 7 | A few sentences describing the overall goals of the pull request's commits. 8 | 9 | ## Tasks 10 | 11 | - [ ] Tests 12 | - [ ] Documentation 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://travis-ci.org/myles/jekyll-typogrify) 2 | [](https://ci.appveyor.com/project/MylesBraithwaite/jekyll-typogrify/branch/develop) 3 | [](http://badge.fury.io/rb/jekyll-typogrify) 4 | [](https://codeclimate.com/github/myles/jekyll-typogrify) 5 | [](https://codeclimate.com/github/myles/jekyll-typogrify/coverage) 6 | 7 | # Jekyll::Typogrify 8 | 9 | Improves typography on your Jekyll site using [typogruby](http://avdgaag.github.io/typogruby/), [titlecase](https://github.com/samsouder/titlecase), and some other useful functions. 10 | 11 | | Before | After | 12 | | ------ | ----- | 13 | |  |  | 14 | 15 | ## Installation 16 | 17 | Add this line to your application's Gemfile: 18 | 19 | ```ruby 20 | gem 'jekyll-typogrify' 21 | ``` 22 | 23 | And then execute: 24 | 25 | $ bundle 26 | 27 | Or install it yourself as: 28 | 29 | $ gem install jekyll-typogrify 30 | 31 | You now need to enable the plugin in your Jekyll web site. Append it to the `gems` array in your `_config.yml` file: 32 | 33 | ```yaml 34 | gems: 35 | - jekyll-typogrify 36 | ``` 37 | 38 | ## Usage 39 | 40 | ### Ampersand 41 | 42 | Converts an ampersand (ex. `&`) converts a & surrounded by optional whitespace or a non-breaking space to the HTML entity and surrounds it in a span with a styled class. 43 | 44 | ```html 45 |
{{ "© Myles Braithwaite" | entities }}
77 | 78 |© Myles Braithwaite
79 | ``` 80 | 81 | ### Initial Quotes 82 | 83 | Encloses initial single or double quote, or their entities (optionally preceeded by a block element and perhaps an inline element) with a span that can be styled. 84 | 85 | ```html 86 |{{ "Today I'm going to the... coffeeshop." | smartypants }}
97 | 98 |Today I’m going to the… coffeeshop.
99 | ``` 100 | 101 | ### Widont 102 | 103 | Replaces space(s) before the last word (or tag before the last word) before an optional closing element (a, em, span, strong) before a closing tag (p, h[1-6], li, dt, dd) or the end of the string. 104 | 105 | ```html 106 |{{ "There’s more to love with every click." | letter_spacing }}
137 | 138 |There’s more to love with every click
. 139 | ``` 140 | 141 | ### Em dash 142 | 143 | Identify em dashes and surround them with a span. 144 | 145 | ```html 146 |{{ "Upon discovering the errors—all 124 of them—the publisher immediately recalled the books." | jt_emdash }}
147 | 148 |Upon discovering the errors—all 124 of them—the publisher immediately recalled the books.
149 | ``` 150 | 151 | ## Development 152 | 153 | After checking out the repo, run `make setup` to install dependencies. Then, run `make console` for an interactive prompt that will allow you to experiment. 154 | 155 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 156 | 157 | ## Contributing 158 | 159 | See [CONTRIBUTING.md](CONTRIBUTING.md). 160 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "jekyll/typogrify" 5 | 6 | require "pry" 7 | Pry.start 8 | -------------------------------------------------------------------------------- /jekyll-typogrify.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "jekyll/typogrify/version" 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "jekyll-typogrify" 8 | spec.version = Jekyll::Typogrify::VERSION 9 | spec.summary = %q{A Jekyll plugin that improves the typography of your Liquid templates.} 10 | spec.description = %q{A Jekyll plugin that improves the typography of your Liquid templates.} 11 | spec.license = "MIT" 12 | 13 | spec.authors = ["Myles Braithwaite"] 14 | spec.email = ["me@mylesbraithwaite.com"] 15 | 16 | spec.homepage = "https://myles.github.io/jekyll-typogrify/" 17 | 18 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 19 | spec.bindir = "exe" 20 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 21 | spec.require_paths = ["lib"] 22 | 23 | spec.add_runtime_dependency "titlecase" 24 | spec.add_runtime_dependency "typogruby" 25 | 26 | spec.add_development_dependency "bundler", "~> 1.7" 27 | spec.add_development_dependency "jekyll" 28 | spec.add_development_dependency "rake", "~> 13.0" 29 | spec.add_development_dependency "rspec", "~> 3.0" 30 | spec.metadata["rubygems_mfa_required"] = "true" 31 | end 32 | -------------------------------------------------------------------------------- /lib/jekyll-typogrify.rb: -------------------------------------------------------------------------------- 1 | # rubocop: disable Naming/FileName 2 | require "jekyll/typogrify" 3 | -------------------------------------------------------------------------------- /lib/jekyll/typogrify.rb: -------------------------------------------------------------------------------- 1 | require "jekyll/typogrify/version" 2 | 3 | require "rubypants" 4 | require "typogruby" 5 | require "titlecase" 6 | require "liquid" 7 | 8 | module Jekyll 9 | module TypogrifyFilter 10 | # converts a & surrounded by optional whitespace or a non-breaking space 11 | # to the HTML entity and surrounds it in a span with a styled class. 12 | # 13 | # @param [String] text input text 14 | # @return [String] input text with ampersands wrapped 15 | def amp(text) 16 | Typogruby.amp(text.to_s) 17 | end 18 | 19 | # surrounds two or more consecutive capital letters, perhaps with 20 | # interspersed digits and periods in a span with a styled class. 21 | # 22 | # @param [String] text input text 23 | # @return [String] input text with caps wrapped 24 | def caps(text) 25 | Typogruby.caps(text.to_s) 26 | end 27 | 28 | # Converts special characters (excluding HTML tags) to HTML entities. 29 | # 30 | # @param [String] text input text 31 | # @return [String] input text with all special characters converted to 32 | # HTML entities. 33 | def entities(text) 34 | Typogruby.entities(text.to_s) 35 | end 36 | 37 | # main function to do all the functions from the method. 38 | # 39 | # @param [String] text input text 40 | # @return [String] input text with all filters applied 41 | def improve(text) 42 | Typogruby.improve(text.to_s) 43 | end 44 | 45 | # encloses initial single or double quote, or their entities 46 | # (optionally preceded by a block element and perhaps an inline element) 47 | # with a span that can be styled. 48 | # 49 | # @param [String] text input text 50 | # @return [String] input text with initial quotes wrapped 51 | 52 | def initial_quotes(text) 53 | Typogruby.initial_quotes(text.to_s) 54 | end 55 | 56 | # Applies smartypants to a given piece of text 57 | # 58 | # @see https://rubygems.org/gems/rubypants 59 | # @param [String] text input text 60 | # @return [String] input text with smartypants applied 61 | def smartypants(text) 62 | Typogruby.smartypants(text.to_s) 63 | end 64 | 65 | # replaces space(s) before the last word (or tag before the last word) 66 | # before an optional closing element (a, em, 67 | # span, strong) before a closing tag (p, h[1-6], 68 | # li, dt, dd) or the end of the string. 69 | # 70 | # @see http://mucur.name/posts/widon-t-and-smartypants-helpers-for-rails 71 | # @see http://shauninman.com/archive/2006/08/22/widont_wordpress_plugin 72 | # @param [String] text input text 73 | # @return [String] input text with non-breaking spaces inserted 74 | def widont(text) 75 | Typogruby.widont(text.to_s) 76 | end 77 | 78 | # convert a given piece of text to titlecase 79 | # 80 | # @param [String] text input text 81 | # @return [String] input text convert to titlecase 82 | def titlecase(text) 83 | text.to_s.titlecase 84 | end 85 | 86 | # wraps words in a span class that can look like something else 87 | # 88 | # @param [String] text input text 89 | # @return [String] input text with words that look strange in a span 90 | def letter_spacing(text) 91 | text.gsub(/(click\S*|clint\S*|final\S*|curt\S*|flick\S*)\b/im) { |str| 92 | tag, before, word = $1, $2, $3 93 | "#{before}#{str}" 94 | } 95 | end 96 | 97 | # surrounds two or more consecutive capital letters, perhaps with 98 | # interspersed digits and periods in a span with a styled class. 99 | # 100 | # @param [String] text input text 101 | # @return [String] input text with caps wrapped 102 | def jt_caps(text) 103 | custom_caps(text.to_s) 104 | end 105 | 106 | # converts a — (em dash) by optional whitespace or a non-breaking space 107 | # to the HTML entity and surrounds it in a span with a styled class. 108 | # 109 | # @param [String] text input text 110 | # @return [String] input text with em dashes wrapped 111 | def jt_emdash(text) 112 | emdash(text.to_s) 113 | end 114 | 115 | private 116 | 117 | # custom modules to jekyll-typogrify 118 | 119 | # surrounds two or more consecutive capital letters, perhaps with 120 | # interspersed digits and periods in a span with a styled class. 121 | # 122 | # @param [String] text input text 123 | # @return [String] input text with caps wrapped 124 | def custom_caps(text) 125 | # $1 and $2 are excluded HTML tags, $3 is the part before the caps and $4 is the caps match 126 | text.gsub(%r{ 127 | (<[^/][^>]*?>)| # Ignore any opening tag, so we don't mess up attribute values 128 | (\s| |^|'|"|>|) # Make sure our capture is preceded by whitespace or quotes 129 | ([A-Z\d](?:(\.|'|-|&|&|&\#38;)?[A-Z\d][\.']?){1,}) # Capture capital words, with optional dots, numbers or ampersands in between 130 | (?!\w) # ...which must not be followed by a word character. 131 | }x) do |str| 132 | tag, before, caps = $1, $2, $3 133 | 134 | # Do nothing with the contents if ignored tags, the inside of an opening HTML element 135 | # so we don't mess up attribute values, or if our capture is only digits. 136 | if tag || caps =~ /^\d+\.?$/ 137 | str 138 | elsif $3 =~ /^[\d\.]+$/ 139 | before + caps 140 | else 141 | before + '' + caps + "" 142 | end 143 | end 144 | end 145 | 146 | # converts a — (em dash) by optional whitespace or a non-breaking space 147 | # to the HTML entity and surrounds it in a span with a styled class. 148 | # 149 | # @param [String] text input text 150 | # @return [String] input text with em dashes wrapped 151 | def emdash(text) 152 | text.gsub(/(\w|\s| )—(?:mdash;|#8212;)?(\w|\s| )/) { |_str| 153 | $1 + '—' + $2 154 | }.gsub(/(\w+)="(.*?)—<\/span>(.*?)"/, '\1="\2—\3"') 155 | end 156 | end 157 | end 158 | 159 | Liquid::Template.register_filter(Jekyll::TypogrifyFilter) 160 | -------------------------------------------------------------------------------- /lib/jekyll/typogrify/version.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module Typogrify 3 | VERSION = "0.3.5" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /screenshots/after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myles/jekyll-typogrify/1481c6443220ed86fa3a162512bc874caab608a6/screenshots/after.png -------------------------------------------------------------------------------- /screenshots/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myles/jekyll-typogrify/1481c6443220ed86fa3a162512bc874caab608a6/screenshots/before.png -------------------------------------------------------------------------------- /spec/fixtures/_config.yml: -------------------------------------------------------------------------------- 1 | timezone: UTC 2 | 3 | gems: 4 | - jekyll-typogrify 5 | 6 | defaults: 7 | - 8 | scope: 9 | path: "" 10 | type: pages 11 | values: 12 | layout: default -------------------------------------------------------------------------------- /spec/fixtures/_layouts/default.html: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 |{{ "This & That" | amp }}
7 | 8 |{{ "SCUBA is an acronym while IBM is an initialism" | caps }}
9 | 10 |{{ "©" | entities }}
11 | 12 |{{ '"Call me Ishmael" he said.' | initial_quotes }}
13 | 14 |{{ 'Myles "The Great" Braithwaite' | smartypants }}
15 | 16 |{{ "This is a rather long title and we don't want any widows or orphans." | widont }}
17 | 18 |{{ "'Neque porro quisquam' est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." | improve }}
19 | 20 |{{ "🤖s Fighting 🐵s" | improve }}
21 | 22 |{{ "welcome to beautiful 東京都 (tokyo), japan" | titlecase }}
23 | 24 |{{ page.title | titlecase | improve }}
25 | 26 |{% assign assign_value = 'this is an assigned value' %}{{ assign_value | titlecase | improve }}
27 | 28 |{{ post.nil | improve | titlecase }}
29 | 30 |{{ post.doesntexist | improve | titlecase }}
31 | 32 |{{ post.empty | improve | titlecase }}
33 | 34 |{{ site.timezone | improve | titlecase }}
35 | 36 |{{ "Click, Clint's, and Flick." | letter_spacing }}
37 | 38 |{{ "The M65-A aircraft weights 10 tons." | caps }}
39 | 40 |{{ "The M65-A aircraft weights 10 tons." | jt_caps }}
41 | 42 |{{ "And yet, when the car was finally delivered—nearly three months after it was ordered—she decided she no longer wanted it, leaving the dealer with an oddly equipped car that would be difficult to sell." | jt_emdash }}
43 | 44 |{{ "The flight Vancouver — Toronto is around 4 hours and 30 minutes." | jt_emdash }}
45 | 46 | 47 | -------------------------------------------------------------------------------- /spec/fixtures/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: this is the page title 3 | nil: nil 4 | empty: "" 5 | --- 6 | -------------------------------------------------------------------------------- /spec/jekyll/typogrify_spec.rb: -------------------------------------------------------------------------------- 1 | require "rbconfig" 2 | require "spec_helper" 3 | 4 | describe(Jekyll) do 5 | let(:overrides) do 6 | { 7 | "source" => source_dir, 8 | "destination" => dest_dir, 9 | "url" => "http://example.org", 10 | } 11 | end 12 | 13 | let(:config) do 14 | Jekyll.configuration(overrides) 15 | end 16 | 17 | let(:site) { Jekyll::Site.new(config) } 18 | let(:contents) { File.read(dest_dir("index.html")) } 19 | 20 | before(:each) do 21 | site.process 22 | end 23 | 24 | is_windows = (RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/) 25 | 26 | it "has a version number" do 27 | expect(Jekyll::Typogrify::VERSION).not_to be nil 28 | end 29 | 30 | it "wraps ampersands in span with the class amp" do 31 | expect(contents).to match /This &<\/span> That<\/p>/
32 | expect(contents).to_not match / This & That<\/p>/
33 | end
34 |
35 | it "wraps acronyms and initialism in span with the class caps" do
36 | expect(contents).to match /SCUBA<\/span> is an acronym while IBM<\/span> is an initialism/
37 | expect(contents).to_not match /SCUBA is an acronym while IBM is an initialism/
38 | end
39 |
40 | it "converts special characters (excluding HTML tags) to HTML entities" do
41 | if not is_windows
42 | expect(contents).to match /©<\/strong>/
43 | expect(contents).to_not match /©<\/strong>/
44 | expect(contents).to_not match /<strong>©<\/strong>/
45 | end
46 | end
47 |
48 | it "wraps the first double/single quote in a span with the class dquo" do
49 | expect(contents).to match /"<\/span>Call me Ishmael" he said./
50 | expect(contents).to_not match /"Call me Ishmael" he said./
51 | end
52 |
53 | it "will apply the smartypants filter" do
54 | expect(contents).to match /Myles “The Great” Braithwaite/
55 | expect(contents).to_not match /Myles "The Great" Braithwaite/
56 | end
57 |
58 | it "will add a non-breaking-space between the last two words" do
59 | expect(contents).to match /This is a rather long title and we don't want any widows or orphans./
60 | expect(contents).to_not match /This is a rather long title and we don't want any widows or orphans./
61 | end
62 |
63 | it "runs all the filters" do
64 | expect(contents).to match /‘<\/span>Neque porro quisquam’ est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…/
65 | expect(contents).to_not match /'Neque porro quisquam' est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.../
66 | end
67 |
68 | it "run improve on a couple of emojis" do
69 | unless is_windows
70 | expect(contents).to_not match /🤖s Fighting 🐵s/
71 | expect(contents).to match /🤖s Fighting 🐵s/
72 | end
73 | end
74 |
75 | it "will apply the titlecase filter" do
76 | unless is_windows
77 | expect(contents).to_not match /welcome to beautiful 東京都 (tokyo), japan/
78 | # expect(contents).to match /Welcome to Beautiful 東京都 (Tokyo), Japan/
79 | end
80 | end
81 |
82 | it "test filter from a page variable" do
83 | expect(contents).to_not match /this is the page title/
84 | expect(contents).to match /This Is the Page Title/
85 | end
86 |
87 | it "test filter from an assign variable" do
88 | expect(contents).to_not match /this is an assigned value/
89 | expect(contents).to match /This Is an Assigned Value/
90 | end
91 |
92 | it "nothing should happen with nil" do
93 | expect(contents).to match / <\/p>/
94 | end
95 |
96 | it "nothing should happen if hte variable doesn't exist" do
97 | expect(contents).to match / <\/p>/
98 | end
99 |
100 | it "if the value is empty it should return nothing" do
101 | expect(contents).to match / <\/p>/
102 | end
103 |
104 | it "test the letter spacing filter" do
105 | expect(contents).to_not match / Click, Clint's, and Flick.<\/p>/
106 | expect(contents).to match / Click<\/span>, Clint's<\/span>, and Flick<\/span>.<\/p>/
107 | end
108 |
109 | it "test caps will not work with a hyphenated words" do
110 | expect(contents).to_not match / The M65-A<\/span> aircraft weights 10 tons.<\/p>/
111 | end
112 |
113 | it "test jt_caps will work with a hyphenated words" do
114 | expect(contents).to match / The M65-A<\/span> aircraft weights 10 tons.<\/p>/
115 | expect(contents).to_not match / The M65<\/span>-A aircraft weights 10 tons.<\/p>/
116 | end
117 |
118 | it "test jt_emdash" do
119 | expect(contents).to match / And yet, when the car was finally delivered—<\/span>nearly three months after it was ordered—<\/span>she decided she no longer wanted it, leaving the dealer with an oddly equipped car that would be difficult to sell.<\/p>/
120 | expect(contents).to_not match / And yet, when the car was finally delivered—nearly three months after it was ordered—she decided she no longer wanted it, leaving the dealer with an oddly equipped car that would be difficult to sell.<\/p>/
121 | end
122 |
123 | it "test jt_emdash with space" do
124 | expect(contents).to match / The flight Vancouver —<\/span> Toronto is around 4 hours and 30 minutes.<\/p>/
125 | expect(contents).to_not match / The flight Vancouver — Toronto is around 4 hours and 30 minutes.<\/p>/
126 | end
127 | end
128 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "simplecov"
2 | SimpleCov.start
3 |
4 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
5 | require "jekyll/typogrify"
6 | require "jekyll"
7 |
8 | Jekyll.logger.log_level = :error
9 |
10 | RSpec.configure do |config|
11 | config.run_all_when_everything_filtered = true
12 | config.filter_run :focus
13 | config.order = "random"
14 |
15 | SOURCE_DIR = File.expand_path("../fixtures", __FILE__)
16 | DEST_DIR = File.expand_path("../dest", __FILE__)
17 |
18 | def source_dir(*files)
19 | File.join(SOURCE_DIR, *files)
20 | end
21 |
22 | def dest_dir(*files)
23 | File.join(DEST_DIR, *files)
24 | end
25 | end
26 |
--------------------------------------------------------------------------------