mirror of
				https://gitea.invidious.io/iv-org/shard-ameba.git
				synced 2024-08-15 00:53:29 +00:00 
			
		
		
		
	Merge pull request #125 from crystal-ameba/config-globs
Ability to configure globs and globally excluded paths
This commit is contained in:
		
						commit
						6a913be980
					
				
					 4 changed files with 193 additions and 48 deletions
				
			
		
							
								
								
									
										46
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										46
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
					@ -24,8 +24,9 @@
 | 
				
			||||||
  * [Docker](#docker)
 | 
					  * [Docker](#docker)
 | 
				
			||||||
  * [From sources](#from-sources)
 | 
					  * [From sources](#from-sources)
 | 
				
			||||||
- [Configuration](#configuration)
 | 
					- [Configuration](#configuration)
 | 
				
			||||||
  * [Only/Except](#onlyexcept)
 | 
					  * [Sources](#sources)
 | 
				
			||||||
  * [Explanation](#explanation)
 | 
					  * [Rules](#rules)
 | 
				
			||||||
 | 
					  * [Explain issues](#explain-issues)
 | 
				
			||||||
  * [Inline disabling](#inline-disabling)
 | 
					  * [Inline disabling](#inline-disabling)
 | 
				
			||||||
- [Editors & integrations](#editors--integrations)
 | 
					- [Editors & integrations](#editors--integrations)
 | 
				
			||||||
- [Credits & inspirations](#credits--inspirations)
 | 
					- [Credits & inspirations](#credits--inspirations)
 | 
				
			||||||
| 
						 | 
					@ -150,7 +151,37 @@ It allows to configure rule properties, disable specific rules and exclude sourc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Generate new file by running `ameba --gen-config`.
 | 
					Generate new file by running `ameba --gen-config`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Only/Except
 | 
					### Sources
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**List of sources to run Ameba on can be configured globally via:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `Globs` section - an array of wildcards (or paths) to include to the
 | 
				
			||||||
 | 
					  inspection. Defaults to `%w(**/*.cr !lib)`, meaning it includes all project
 | 
				
			||||||
 | 
					  files with `*.cr` extension except those which exist in `lib` folder.
 | 
				
			||||||
 | 
					- `Excluded` section - an array of wildcards (or paths) to exclude from the
 | 
				
			||||||
 | 
					  source list defined by `Globs`. Defaults to an empty array.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In this example we define default globs and exclude `src/compiler` folder:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``` yaml
 | 
				
			||||||
 | 
					Globs:
 | 
				
			||||||
 | 
					  - **/*.cr
 | 
				
			||||||
 | 
					  - !lib
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					Excluded:
 | 
				
			||||||
 | 
					  - src/compiler
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Specific sources can be excluded at rule level**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``` yaml
 | 
				
			||||||
 | 
					Style/RedundantBegin:
 | 
				
			||||||
 | 
					  Excluded:
 | 
				
			||||||
 | 
					  - src/server/processor.cr
 | 
				
			||||||
 | 
					  - src/server/api.cr
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Rules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
One or more rules, or a one or more group of rules can be included or excluded
 | 
					One or more rules, or a one or more group of rules can be included or excluded
 | 
				
			||||||
via command line arguments:
 | 
					via command line arguments:
 | 
				
			||||||
| 
						 | 
					@ -162,7 +193,14 @@ $ ameba --except Lint/Syntax # runs all rules except Lint/Syntax
 | 
				
			||||||
$ ameba --except Style,Lint  # runs all rules except rules in Style and Lint groups
 | 
					$ ameba --except Style,Lint  # runs all rules except rules in Style and Lint groups
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Explanation
 | 
					Or through the configuration file:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``` yaml
 | 
				
			||||||
 | 
					Style/RedundantBegin:
 | 
				
			||||||
 | 
					  Enabled: false
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Explain issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Ameba allows you to dig deeper into an issue, by showing you details about the issue
 | 
					Ameba allows you to dig deeper into an issue, by showing you details about the issue
 | 
				
			||||||
and the reasoning by it being reported.
 | 
					and the reasoning by it being reported.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,70 @@ module Ameba
 | 
				
			||||||
      Config::AVAILABLE_FORMATTERS.should_not be_nil
 | 
					      Config::AVAILABLE_FORMATTERS.should_not be_nil
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe ".new" do
 | 
				
			||||||
 | 
					      it "loads default globs when config is empty" do
 | 
				
			||||||
 | 
					        yml = YAML.parse "{}"
 | 
				
			||||||
 | 
					        config = Config.new(yml)
 | 
				
			||||||
 | 
					        config.globs.should eq Config::DEFAULT_GLOBS
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "initializes globs as string" do
 | 
				
			||||||
 | 
					        yml = YAML.parse <<-CONFIG
 | 
				
			||||||
 | 
					          ---
 | 
				
			||||||
 | 
					          Globs: src/*.cr
 | 
				
			||||||
 | 
					          CONFIG
 | 
				
			||||||
 | 
					        config = Config.new(yml)
 | 
				
			||||||
 | 
					        config.globs.should eq %w(src/*.cr)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "initializes globs as array" do
 | 
				
			||||||
 | 
					        yml = YAML.parse <<-CONFIG
 | 
				
			||||||
 | 
					          ---
 | 
				
			||||||
 | 
					          Globs:
 | 
				
			||||||
 | 
					           - "src/*.cr"
 | 
				
			||||||
 | 
					           - "!spec"
 | 
				
			||||||
 | 
					          CONFIG
 | 
				
			||||||
 | 
					        config = Config.new(yml)
 | 
				
			||||||
 | 
					        config.globs.should eq %w(src/*.cr !spec)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "raises if Globs has a wrong type" do
 | 
				
			||||||
 | 
					        yml = YAML.parse <<-CONFIG
 | 
				
			||||||
 | 
					          ---
 | 
				
			||||||
 | 
					          Globs: 100
 | 
				
			||||||
 | 
					          CONFIG
 | 
				
			||||||
 | 
					        expect_raises(Exception, "incorrect 'Globs' section in a config file") { Config.new(yml) }
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "initializes excluded as string" do
 | 
				
			||||||
 | 
					        yml = YAML.parse <<-CONFIG
 | 
				
			||||||
 | 
					          ---
 | 
				
			||||||
 | 
					          Excluded: spec
 | 
				
			||||||
 | 
					          CONFIG
 | 
				
			||||||
 | 
					        config = Config.new(yml)
 | 
				
			||||||
 | 
					        config.excluded.should eq %w(spec)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "initializes excluded as array" do
 | 
				
			||||||
 | 
					        yml = YAML.parse <<-CONFIG
 | 
				
			||||||
 | 
					          ---
 | 
				
			||||||
 | 
					          Excluded:
 | 
				
			||||||
 | 
					           - spec
 | 
				
			||||||
 | 
					           - lib/*.cr
 | 
				
			||||||
 | 
					          CONFIG
 | 
				
			||||||
 | 
					        config = Config.new(yml)
 | 
				
			||||||
 | 
					        config.excluded.should eq %w(spec lib/*.cr)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "raises if Excluded has a wrong type" do
 | 
				
			||||||
 | 
					        yml = YAML.parse <<-CONFIG
 | 
				
			||||||
 | 
					          ---
 | 
				
			||||||
 | 
					          Excluded: true
 | 
				
			||||||
 | 
					          CONFIG
 | 
				
			||||||
 | 
					        expect_raises(Exception, "incorrect 'Excluded' section in a config file") { Config.new(yml) }
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    describe ".load" do
 | 
					    describe ".load" do
 | 
				
			||||||
      it "loads custom config" do
 | 
					      it "loads custom config" do
 | 
				
			||||||
        config = Config.load config_sample
 | 
					        config = Config.load config_sample
 | 
				
			||||||
| 
						 | 
					@ -28,7 +92,7 @@ module Ameba
 | 
				
			||||||
      config = Config.load config_sample
 | 
					      config = Config.load config_sample
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it "holds source globs" do
 | 
					      it "holds source globs" do
 | 
				
			||||||
        config.globs.should contain "spec/ameba/config_spec.cr"
 | 
					        config.globs.should eq Config::DEFAULT_GLOBS
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it "allows to set globs" do
 | 
					      it "allows to set globs" do
 | 
				
			||||||
| 
						 | 
					@ -37,12 +101,36 @@ module Ameba
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe "#excluded, #excluded=" do
 | 
				
			||||||
 | 
					      config = Config.load config_sample
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "defaults to empty array" do
 | 
				
			||||||
 | 
					        config.excluded.should be_empty
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "allows to set excluded" do
 | 
				
			||||||
 | 
					        config.excluded = ["spec"]
 | 
				
			||||||
 | 
					        config.excluded.should eq ["spec"]
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    describe "#sources" do
 | 
					    describe "#sources" do
 | 
				
			||||||
      config = Config.load config_sample
 | 
					      config = Config.load config_sample
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it "returns list of sources" do
 | 
					      it "returns list of sources" do
 | 
				
			||||||
        config.sources.size.should be > 0
 | 
					        config.sources.size.should be > 0
 | 
				
			||||||
        config.sources.first.should be_a Source
 | 
					        config.sources.first.should be_a Source
 | 
				
			||||||
 | 
					        config.sources.any? { |s| s.fullpath == __FILE__ }.should be_true
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns a list of sources mathing globs" do
 | 
				
			||||||
 | 
					        config.globs = %w(**/config_spec.cr)
 | 
				
			||||||
 | 
					        config.sources.size.should eq(1)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns a lisf of sources excluding 'Excluded'" do
 | 
				
			||||||
 | 
					        config.excluded = %w(**/config_spec.cr)
 | 
				
			||||||
 | 
					        config.sources.any? { |s| s.fullpath == __FILE__ }.should be_false
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ module Ameba::Cli
 | 
				
			||||||
  def run(args = ARGV)
 | 
					  def run(args = ARGV)
 | 
				
			||||||
    opts = parse_args args
 | 
					    opts = parse_args args
 | 
				
			||||||
    config = Config.load opts.config, opts.colors?
 | 
					    config = Config.load opts.config, opts.colors?
 | 
				
			||||||
    config.globs = opts.globs
 | 
					    config.globs = opts.globs.not_nil! if opts.globs
 | 
				
			||||||
    config.severity = opts.fail_level.not_nil! if opts.fail_level
 | 
					    config.severity = opts.fail_level.not_nil! if opts.fail_level
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    configure_formatter(config, opts)
 | 
					    configure_formatter(config, opts)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,43 +25,16 @@ class Ameba::Config
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  PATH = ".ameba.yml"
 | 
					  PATH = ".ameba.yml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DEFAULT_GLOBS = %w(
 | 
				
			||||||
 | 
					    **/*.cr
 | 
				
			||||||
 | 
					    !lib
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setter formatter : Formatter::BaseFormatter?
 | 
					  setter formatter : Formatter::BaseFormatter?
 | 
				
			||||||
  setter globs : Array(String)?
 | 
					 | 
				
			||||||
  getter rules : Array(Rule::Base)
 | 
					  getter rules : Array(Rule::Base)
 | 
				
			||||||
  property severity = Severity::Convention
 | 
					  property severity = Severity::Convention
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @rule_groups : Hash(String, Array(Rule::Base))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # Creates a new instance of `Ameba::Config` based on YAML parameters.
 | 
					 | 
				
			||||||
  #
 | 
					 | 
				
			||||||
  # `Config.load` uses this constructor to instantiate new config by YAML file.
 | 
					 | 
				
			||||||
  protected def initialize(@config : YAML::Any)
 | 
					 | 
				
			||||||
    @rules = Rule.rules.map &.new(config).as(Rule::Base)
 | 
					 | 
				
			||||||
    @rule_groups = @rules.group_by &.group
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if @config.as_h? && (name = @config["Formatter"]?.try &.["Name"]?)
 | 
					 | 
				
			||||||
      self.formatter = name.to_s
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # Loads YAML configuration file by `path`.
 | 
					 | 
				
			||||||
  #
 | 
					 | 
				
			||||||
  # ```
 | 
					 | 
				
			||||||
  # config = Ameba::Config.load
 | 
					 | 
				
			||||||
  # ```
 | 
					 | 
				
			||||||
  #
 | 
					 | 
				
			||||||
  def self.load(path = PATH, colors = true)
 | 
					 | 
				
			||||||
    Colorize.enabled = colors
 | 
					 | 
				
			||||||
    content = File.exists?(path) ? File.read path : ""
 | 
					 | 
				
			||||||
    Config.new YAML.parse(content)
 | 
					 | 
				
			||||||
  rescue e
 | 
					 | 
				
			||||||
    raise "Config file is invalid: #{e.message}"
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def self.formatter_names
 | 
					 | 
				
			||||||
    AVAILABLE_FORMATTERS.keys.join("|")
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # Returns a list of paths (with wildcards) to files.
 | 
					  # Returns a list of paths (with wildcards) to files.
 | 
				
			||||||
  # Represents a list of sources to be inspected.
 | 
					  # Represents a list of sources to be inspected.
 | 
				
			||||||
  # If globs are not set, it will return default list of files.
 | 
					  # If globs are not set, it will return default list of files.
 | 
				
			||||||
| 
						 | 
					@ -71,22 +44,61 @@ class Ameba::Config
 | 
				
			||||||
  # config.globs = ["**/*.cr"]
 | 
					  # config.globs = ["**/*.cr"]
 | 
				
			||||||
  # config.globs
 | 
					  # config.globs
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
 | 
					  property globs : Array(String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Represents a list of paths to exclude from globs.
 | 
				
			||||||
 | 
					  # Can have wildcards.
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  def globs
 | 
					  # ```
 | 
				
			||||||
    @globs ||= default_files
 | 
					  # config = Ameba::Config.load
 | 
				
			||||||
 | 
					  # config.excluded = ["spec", "src/server/*.cr"]
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  property excluded : Array(String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @rule_groups : Hash(String, Array(Rule::Base))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Creates a new instance of `Ameba::Config` based on YAML parameters.
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # `Config.load` uses this constructor to instantiate new config by YAML file.
 | 
				
			||||||
 | 
					  protected def initialize(config : YAML::Any)
 | 
				
			||||||
 | 
					    @rules = Rule.rules.map &.new(config).as(Rule::Base)
 | 
				
			||||||
 | 
					    @rule_groups = @rules.group_by &.group
 | 
				
			||||||
 | 
					    @excluded = load_array_section(config, "Excluded")
 | 
				
			||||||
 | 
					    @globs = load_array_section(config, "Globs", DEFAULT_GLOBS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.formatter = load_formatter_name(config)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Returns a list of sources.
 | 
					  # Loads YAML configuration file by `path`.
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  # config = Ameba::Config.load
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  def self.load(path = PATH, colors = true)
 | 
				
			||||||
 | 
					    Colorize.enabled = colors
 | 
				
			||||||
 | 
					    content = File.exists?(path) ? File.read path : "{}"
 | 
				
			||||||
 | 
					    Config.new YAML.parse(content)
 | 
				
			||||||
 | 
					  rescue e
 | 
				
			||||||
 | 
					    raise "Config file is invalid: #{e.message}"
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def self.formatter_names
 | 
				
			||||||
 | 
					    AVAILABLE_FORMATTERS.keys.join("|")
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Returns a list of sources matching globs and excluded sections.
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  # config = Ameba::Config.load
 | 
					  # config = Ameba::Config.load
 | 
				
			||||||
  # config.sources # => list of default sources
 | 
					  # config.sources # => list of default sources
 | 
				
			||||||
  # config.globs = ["**/*.cr"]
 | 
					  # config.globs = ["**/*.cr"]
 | 
				
			||||||
 | 
					  # config.excluded = ["spec"]
 | 
				
			||||||
  # config.sources # => list of sources pointing to files found by the wildcards
 | 
					  # config.sources # => list of sources pointing to files found by the wildcards
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  def sources
 | 
					  def sources
 | 
				
			||||||
    find_files_by_globs(globs)
 | 
					    (find_files_by_globs(globs) - find_files_by_globs(excluded))
 | 
				
			||||||
      .map { |path| Source.new File.read(path), path }
 | 
					      .map { |path| Source.new File.read(path), path }
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -100,7 +112,7 @@ class Ameba::Config
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  def formatter
 | 
					  def formatter
 | 
				
			||||||
    @formatter ||= default_formatter
 | 
					    @formatter ||= Formatter::DotFormatter.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Sets formatter by name.
 | 
					  # Sets formatter by name.
 | 
				
			||||||
| 
						 | 
					@ -158,12 +170,19 @@ class Ameba::Config
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private def default_files
 | 
					  private def load_formatter_name(config)
 | 
				
			||||||
    Dir["**/*.cr"].reject(&.starts_with? "lib/")
 | 
					    name = config["Formatter"]?.try &.["Name"]?
 | 
				
			||||||
 | 
					    name ? name.to_s : nil
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private def default_formatter
 | 
					  private def load_array_section(config, section_name, default = [] of String)
 | 
				
			||||||
    Formatter::DotFormatter.new
 | 
					    case value = config[section_name]?
 | 
				
			||||||
 | 
					    when .nil?  then default
 | 
				
			||||||
 | 
					    when .as_s? then [value.to_s]
 | 
				
			||||||
 | 
					    when .as_a? then value.as_a.map(&.as_s)
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      raise "incorrect '#{section_name}' section in a config files"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # :nodoc:
 | 
					  # :nodoc:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue