├── lib ├── phlex-pdf.rb └── phlex │ ├── pdf │ ├── version.rb │ └── rails.rb │ └── pdf.rb ├── .rspec ├── CHANGELOG.md ├── sig └── phlex │ └── pdf.rbs ├── bin ├── setup └── console ├── .gitignore ├── Rakefile ├── Gemfile ├── spec ├── spec_helper.rb └── phlex │ └── pdf_spec.rb ├── .github └── workflows │ └── main.yml ├── LICENSE.txt ├── Gemfile.lock ├── phlex-pdf.gemspec ├── README.md └── CODE_OF_CONDUCT.md /lib/phlex-pdf.rb: -------------------------------------------------------------------------------- 1 | require "phlex/pdf" -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | 3 | ## [0.1.0] - 2024-02-23 4 | 5 | - Initial release 6 | -------------------------------------------------------------------------------- /lib/phlex/pdf/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Phlex 4 | class PDF 5 | VERSION = "0.1.2" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /sig/phlex/pdf.rbs: -------------------------------------------------------------------------------- 1 | module Phlex 2 | class PDF 3 | VERSION: String 4 | # See the writing guide of rbs: https://github.com/ruby/rbs#guides 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | # rspec failure tracking 11 | .rspec_status 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in phlex-pdf.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.0" 9 | 10 | gem "rspec", "~> 3.0" 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "phlex/pdf" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | require "irb" 11 | IRB.start(__FILE__) 12 | -------------------------------------------------------------------------------- /lib/phlex/pdf/rails.rb: -------------------------------------------------------------------------------- 1 | module Phlex::PDF::Rails 2 | module Helpers 3 | # Sends a PDF to the browser via Rails controllers. 4 | def send_pdf(pdf, disposition: "inline", type: "application/pdf", **kwargs) 5 | send_data( 6 | pdf.to_pdf, 7 | disposition: disposition, 8 | type: type, 9 | **kwargs 10 | ) 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "phlex/pdf" 4 | require "pdf/inspector" 5 | 6 | RSpec.configure do |config| 7 | # Enable flags like --only-failures and --next-failure 8 | config.example_status_persistence_file_path = ".rspec_status" 9 | 10 | # Disable RSpec exposing methods globally on `Module` and `main` 11 | config.disable_monkey_patching! 12 | 13 | config.expect_with :rspec do |c| 14 | c.syntax = :expect 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | name: Ruby ${{ matrix.ruby }} 14 | strategy: 15 | matrix: 16 | ruby: 17 | - '3.3.0' 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Set up Ruby 22 | uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: ${{ matrix.ruby }} 25 | bundler-cache: true 26 | - name: Run the default task 27 | run: bundle exec rake 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Brad Gessler 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 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | phlex-pdf (0.1.2) 5 | matrix (~> 0.4) 6 | prawn (~> 2.0) 7 | zeitwerk (~> 2.0) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | Ascii85 (1.1.0) 13 | afm (0.2.2) 14 | diff-lcs (1.5.1) 15 | hashery (2.1.2) 16 | matrix (0.4.2) 17 | pdf-core (0.9.0) 18 | pdf-inspector (1.3.0) 19 | pdf-reader (>= 1.0, < 3.0.a) 20 | pdf-reader (2.12.0) 21 | Ascii85 (~> 1.0) 22 | afm (~> 0.2.1) 23 | hashery (~> 2.0) 24 | ruby-rc4 25 | ttfunk 26 | prawn (2.4.0) 27 | pdf-core (~> 0.9.0) 28 | ttfunk (~> 1.7) 29 | rake (13.1.0) 30 | rspec (3.13.0) 31 | rspec-core (~> 3.13.0) 32 | rspec-expectations (~> 3.13.0) 33 | rspec-mocks (~> 3.13.0) 34 | rspec-core (3.13.0) 35 | rspec-support (~> 3.13.0) 36 | rspec-expectations (3.13.0) 37 | diff-lcs (>= 1.2.0, < 2.0) 38 | rspec-support (~> 3.13.0) 39 | rspec-mocks (3.13.0) 40 | diff-lcs (>= 1.2.0, < 2.0) 41 | rspec-support (~> 3.13.0) 42 | rspec-support (3.13.0) 43 | ruby-rc4 (0.1.5) 44 | ttfunk (1.7.0) 45 | zeitwerk (2.6.13) 46 | 47 | PLATFORMS 48 | arm64-darwin-23 49 | ruby 50 | 51 | DEPENDENCIES 52 | pdf-inspector (~> 1.0) 53 | phlex-pdf! 54 | rake (~> 13.0) 55 | rspec (~> 3.0) 56 | 57 | BUNDLED WITH 58 | 2.5.4 59 | -------------------------------------------------------------------------------- /phlex-pdf.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/phlex/pdf/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "phlex-pdf" 7 | spec.version = Phlex::PDF::VERSION 8 | spec.authors = ["Brad Gessler"] 9 | spec.email = ["bradgessler@gmail.com"] 10 | 11 | spec.summary = "Build PDFs out of components" 12 | spec.description = "A thin wrapper above Prawn that makes it easier to build compentized PDFs" 13 | spec.homepage = "https://github.com/phlex-ruby/phlex-pdf" 14 | spec.license = "MIT" 15 | spec.required_ruby_version = ">= 2.6.0" 16 | 17 | spec.metadata["allowed_push_host"] = "https://rubygems.org/" 18 | 19 | spec.metadata["homepage_uri"] = spec.homepage 20 | spec.metadata["source_code_uri"] = spec.homepage 21 | spec.metadata["changelog_uri"] = spec.homepage 22 | 23 | # Specify which files should be added to the gem when it is released. 24 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 25 | spec.files = Dir.chdir(__dir__) do 26 | `git ls-files -z`.split("\x0").reject do |f| 27 | (File.expand_path(f) == __FILE__) || 28 | f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) 29 | end 30 | end 31 | spec.bindir = "exe" 32 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 33 | spec.require_paths = ["lib"] 34 | 35 | # Uncomment to register a new dependency of your gem 36 | spec.add_dependency "prawn", "~> 2.0" 37 | spec.add_dependency "matrix", "~> 0.4" 38 | spec.add_dependency "zeitwerk", "~> 2.0" 39 | 40 | spec.add_development_dependency "pdf-inspector", "~> 1.0" 41 | end 42 | -------------------------------------------------------------------------------- /lib/phlex/pdf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "pdf/version" 4 | require "prawn" 5 | require "matrix" 6 | require "zeitwerk" 7 | 8 | module Phlex 9 | class PDF 10 | Loader = Zeitwerk::Loader.for_gem_extension(Phlex).tap do |loader| 11 | loader.ignore "#{__dir__}/generators" 12 | loader.inflector.inflect("pdf" => "PDF") 13 | loader.setup 14 | end 15 | 16 | include Prawn::View 17 | 18 | def document = @document 19 | 20 | def call(document, &block) 21 | @document = document 22 | around_template do 23 | if block_given? 24 | view_template do 25 | yield_content(&block) 26 | end 27 | else 28 | view_template 29 | end 30 | end 31 | end 32 | 33 | # @abstract Override this method to hook in around a template render. You can do things before and after calling `super` to render the template. You should always call `super` so that callbacks can be added at different layers of the inheritance tree. 34 | # @return [nil] 35 | def around_template 36 | before_template 37 | yield 38 | after_template 39 | nil 40 | end 41 | 42 | # @abstract Override this method to hook in right before a template is rendered. Please remember to call `super` so that callbacks can be added at different layers of the inheritance tree. 43 | # @return [nil] 44 | def before_template 45 | nil 46 | end 47 | 48 | # @abstract Override this method to hook in right after a template is rendered. Please remember to call `super` so that callbacks can be added at different layers of the inheritance tree. 49 | # @return [nil] 50 | def after_template 51 | nil 52 | end 53 | 54 | def to_pdf(...) 55 | self.class.blank.tap{ |doc| call doc }.render(...) 56 | end 57 | 58 | def yield_content(&block) 59 | return unless block_given? 60 | 61 | if block.arity.zero? 62 | # This handles lambdas and -> 63 | yield 64 | else 65 | # This handles Proc and proc 66 | yield self 67 | end 68 | end 69 | 70 | def render(renderable, &block) 71 | case renderable 72 | when Phlex::PDF 73 | renderable.call(@document, &block) 74 | when String 75 | @document.text renderable 76 | when Class 77 | render(renderable.new, &block) if renderable < Phlex::PDF 78 | when Proc, Method 79 | yield_content(&renderable) 80 | when Enumerable 81 | renderable.each { |r| render(r, &block) } 82 | else 83 | raise ArgumentError, "You can't render a #{renderable.inspect}." 84 | end 85 | 86 | nil 87 | end 88 | 89 | class << self 90 | # Components should override this method to define their view. 91 | def blank = Prawn::Document.new(skip_page_creation: true) 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/phlex/pdf_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "phlex/pdf" 4 | 5 | class PDFComponent < Phlex::PDF 6 | end 7 | 8 | class PDFPage < PDFComponent 9 | def before_template = start_new_page 10 | end 11 | 12 | class WarningComponent < PDFComponent 13 | def initialize(message) 14 | @message = message 15 | end 16 | 17 | def view_template 18 | text @message, color: "FF0000" 19 | end 20 | end 21 | 22 | class HeaderComponent < PDFComponent 23 | def view_template 24 | text "Header", size: 24 25 | end 26 | end 27 | 28 | class FooterComponent < PDFComponent 29 | def view_template 30 | text "Page #{document.page_number}" 31 | end 32 | end 33 | 34 | class BodyComponent < PDFComponent 35 | def view_template 36 | render "Body" 37 | yield if block_given? 38 | end 39 | 40 | def greet(name) 41 | text "Hello #{name}", color: "0000FF" 42 | end 43 | end 44 | 45 | class MyPage < PDFPage 46 | def initialize(title, subtitle: ) 47 | @title = title 48 | @subtitle = subtitle 49 | end 50 | 51 | def view_template 52 | render HeaderComponent.new 53 | 54 | text @title 55 | text @subtitle 56 | 57 | render [ 58 | WarningComponent.new("Danger!"), 59 | WarningComponent.new("Don't Panic!") 60 | ] 61 | 62 | render Proc.new { text "Rendered from Proc.new", color: "00FF00" } 63 | render proc { text "Rendered from proc", color: "00FFFF" } 64 | render lambda { text "Rendered from lambda", color: "FFFF00" } 65 | render -> { text "Rendered from ->", color: "99FF00" } 66 | render method(:calm) 67 | 68 | render BodyComponent do |body| 69 | body.greet "Brad" 70 | end 71 | render FooterComponent.new 72 | end 73 | 74 | def calm 75 | text "Rendered from Method", color: "F0F0F0" 76 | end 77 | end 78 | 79 | class PDFDocument < PDFComponent 80 | def view_template 81 | render [ 82 | MyPage.new("Hi", subtitle: "There"), 83 | MyPage.new("Friendly", subtitle: "Pal"), 84 | MyPage.new("Whats", subtitle: "Up") 85 | ] 86 | end 87 | end 88 | 89 | RSpec.describe Phlex::PDF do 90 | let(:pages) do 91 | PDF::Inspector::Page.analyze(PDFDocument.new.to_pdf).pages 92 | end 93 | 94 | it "generates a PDF" do 95 | expect(pages.size).to eq(3) 96 | expect(pages[0][:strings]).to include("Hi", "There") 97 | expect(pages[1][:strings]).to include("Friendly", "Pal") 98 | expect(pages[2][:strings]).to include("Whats", "Up") 99 | 100 | pages.each do |page| 101 | expect(page[:strings]).to include("Header", "Rendered from Proc.new", "Rendered from proc", "Rendered from lambda", "Rendered from ->", "Rendered from Method", "Rendered from lambda", "Rendered from ->", "Rendered from Method", "Hello Brad", "Danger!", "Don't Panic!") 102 | end 103 | end 104 | end 105 | 106 | RSpec.describe Phlex::PDF::Loader do 107 | it "resolves modules" do 108 | expect { Phlex::PDF::Rails::Helpers }.not_to raise_error 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phlex::PDF 2 | 3 | Phlex PDF lets you compose PDF files with components in pure Ruby. It's a thin layer that sits on the shoulder of giants, [Prawn](https://github.com/prawnpdf/prawn), that encourages a component-first approach to building PDF documents. 4 | 5 | ## Installation 6 | 7 | Install the gem and add to the application's Gemfile by executing: 8 | 9 | $ bundle add phlex-pdf 10 | 11 | If bundler is not being used to manage dependencies, install the gem by executing: 12 | 13 | $ gem install phlex-pdf 14 | 15 | ## Usage 16 | 17 | `Phlex::PDF` is a thin wrapper around `Prawn::View`, so you'll want to become familiar with [PrawnPDF](http://prawnpdf.org/), particularly the [PrawnPDF Manual](https://prawnpdf.org/manual.pdf). 18 | 19 | ```ruby 20 | require "phlex/pdf" 21 | 22 | # Base component. You'll put methods here that are shared across all components. 23 | class PDFComponent < Phlex::PDF 24 | end 25 | 26 | # Page has a `before_template` method that created a new page. Without a page 27 | # nothing will render and an error would occur. 28 | class PDFPage < PDFComponent 29 | # Creates a new page 30 | def before_template = create_new_page 31 | 32 | def after_template 33 | text "Page #{document.page_number}" 34 | end 35 | end 36 | 37 | # Example component inherits from PDFComponent. Note that it does NOT create any 38 | # new pages. 39 | class BoxComponent < PDFComponent 40 | def view_template 41 | text "I'm a box" 42 | yield 43 | end 44 | end 45 | 46 | # The final PDF that's rendered should be a subclass of PDFPage. Components 47 | # can be rendered within a PDFPage. 48 | class MyPage < PDFPage 49 | def initialize(title:) 50 | @title = title 51 | end 52 | 53 | def view_template 54 | text @title 55 | 56 | render BoxComponent.new do 57 | text "Look! I'm a box inside a box!" 58 | end 59 | end 60 | end 61 | 62 | # Render the PDF to a string. 63 | MyPage.new(title: "This is a PDF!").to_pdf 64 | ``` 65 | 66 | ### Rails integration 67 | 68 | To stream PDFs from Rails controllers, the `send_pdf` helper that wraps `send_data` and renders the PDF is available. 69 | 70 | ```ruby 71 | class MyController < ApplicationController 72 | include Phlex::PDF::Rails::Helpers 73 | 74 | def show 75 | send_pdf MyPage.new(title: "This is a PDF!") 76 | end 77 | end 78 | ``` 79 | 80 | ## Development 81 | 82 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 83 | 84 | 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`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). 85 | 86 | ## Contributing 87 | 88 | Bug reports and pull requests are welcome on GitHub at https://github.com/phlex-ruby/phlex-pdf. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/phlex-ruby/phlex-pdf/blob/main/CODE_OF_CONDUCT.md). 89 | 90 | ## License 91 | 92 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 93 | 94 | ## Code of Conduct 95 | 96 | Everyone interacting in the Phlex::PDF project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/phlex-ruby/phlex-pdf/blob/main/CODE_OF_CONDUCT.md). 97 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | * Trolling, insulting or derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders 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, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at bradgessler@gmail.com. All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 78 | 79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | [homepage]: https://www.contributor-covenant.org 82 | 83 | For answers to common questions about this code of conduct, see the FAQ at 84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 85 | --------------------------------------------------------------------------------