Countless

Continuous Integration Gem Version Test Coverage Test Ratio API docs

This is a reusable and widely configurable collection of Rake tasks and utilities for code statistics and annotations. The Rake task names and outputs are based on the Rails tasks. For code statistics (lines of code, comments) the cloc utility is used, which is battle-proven, popular and good maintained. A bundled version of it is shipped with the gem package.

Installation

Add this line to your gemspec/Gemfile:

# Within a gem/library use:
spec.add_runtime_dependency 'countless'

# In an application use:
gem 'countless'

And then execute:

$ bundle

Requirements

  • Ruby (>=2.5, tested on CRuby/MRI only, may work with other implementations as well)
  • Perl (>= 5.10, for the cloc utility)

Usage

You can configure the Countless gem in serveral ways, but the most common usecase is to install its Rake tasks and configure it in order to work properly. Here comes a self descriptive example (within a Rakefile):

# Add the annotations and statistics tasks
require 'countless/rake_tasks'

Afterwards the following Rake tasks are available to you:

  • stats: Report code statistics (KLOCs, etc) (run via bundle exec rake stats) +------------------+-------+-----+----------+---------+---------+-----+-------+ | Name | Lines | LOC | Comments | Classes | Methods | M/C | LOC/M | +------------------+-------+-----+----------+---------+---------+-----+-------+ | Extensions | 83 | 40 | 33 | 0 | 4 | 0 | 10 | | Top-levels | 934 | 503 | 331 | 5 | 36 | 7 | 13 | | Extensions specs | 36 | 28 | 1 | 0 | 4 | 0 | 7 | | Top-levels specs | 323 | 260 | 4 | 0 | 39 | 0 | 6 | +------------------+-------+-----+----------+---------+---------+-----+-------+ | Total | 1376 | 831 | 369 | 5 | 83 | 16 | 10 | +------------------+-------+-----+----------+---------+---------+-----+-------+ Code LOC: 543 Test LOC: 288 Code to Test Ratio: 1:0.5
  • notes: Enumerate all annotations (run via bundle exec rake notes) ``` Rakefile: * [ 7] This is just for testing purposes

lib/countless/rake_tasks.rb: * [ 3] This is just for testing purposes here. Keep it exactly like that.

spec/fixtures/files/test/test_spec.rb: * [29] Do something

  * **notes:optimize**, **notes:fixme**, **notes:todo**, **notes:testme**,
    **notes:deprecateme** (by default, see `config.annotation_tags`, to
    configure more defaults)
  * **notes:custom**: Show notes for custom annotation (run via `bundle exec
    rake notes:custom ANNOTATION='NOTE'`)

### Addtional Configuration

```ruby
# All the configured values here represent the Gem defaults.
Countless.configure do |conf|
  # The base/root path of the project to work on. This path is used as a #
  # prefix to all relative path/file configurations. By default we check for a
  # Rake invokation (Rakefile location), a Rails invokation (project root) or
  # fallback the the current working directory of the process.
  config.base_path = Dir.pwd

  # The path to the cloc (https://github.com/AlDanial/cloc) utility. The gem
  # comes with a bundled version of the utility, ready to be used. But you
  # can also change the used binary path in order to use a different version
  # which you manually provisioned.
  config.cloc_path = File.expand_path('../../bin/cloc', __dir__)

  # We allow to configure additional file extensions to consider for
  # statistics calculation. They will be included in the default list. This
  # way you can easily extend the list.
  config.additional_stats_file_extensions = []

  # All the file extensions to consider for statistics calculation
  config.stats_file_extensions = %w[
    rb js jsx ts tsx css scss coffee rake erb haml h c cpp rs
  ] + config.additional_stats_file_extensions

  # We allow to configure additional application object types. They will be
  # included in the default list. This way you can easily extend the list.
  config.additional_stats_app_object_types = []

  # Configure the application (in the root +app+ directory) object types,
  # they will be added as regular directories as well as their testing
  # counter parts (minitest/RSpec)
  config.stats_app_object_types = %w[
    channels consumers controllers dashboards decorators fields helpers jobs
    mailboxes mailers models policies serializers services uploaders
    validators value_objects views
  ] + config.additional_stats_app_object_types

  # We allow to configure additional statistics directories. They will be
  # included in the default list. This way you can easily extend the list.
  config.additional_stats_directories = []

  # A list of custom base directories in an application / gem
  config.stats_base_directories = [
    { name: 'JavaScripts', dir: 'app/assets/javascripts' },
    { name: 'Stylesheets', dir: 'app/assets/stylesheets' },
    { name: 'JavaScript', dir: 'app/javascript' },
    { name: 'API', dir: 'app/api' },
    { name: 'API tests', dir: 'test/api', test: true },
    { name: 'API specs', dir: 'spec/api', test: true },
    { name: 'APIs', dir: 'app/apis' },
    { name: 'API tests', dir: 'test/apis', test: true },
    { name: 'API specs', dir: 'spec/apis', test: true },
    { name: 'Libraries', dir: 'app/lib' },
    { name: 'Library tests', dir: 'test/lib', test: true },
    { name: 'Library specs', dir: 'spec/lib', test: true },
    { name: 'Libraries', dir: 'lib' },
    { name: 'Library tests', dir: 'test/lib', test: true },
    { name: 'Library specs', dir: 'spec/lib', test: true }
  ] + config.additional_stats_directories

  # We allow to configure additional detailed statistics patterns. They will
  # be included in the default list. This way you can easily extend the list.
  config.additional_detailed_stats_patterns = {}

  # All the detailed statistics (class/method and tests/examples) patterns
  # which will be used for parsing the source files to gather the metrics
  config.detailed_stats_patterns = {
    ruby: {
      extensions: %w[rb rake],
      class: /^\s*class\s+[_A-Z]/, # regular Ruby classes
      method: Regexp.union(
        [
          /^\s*def\s+[_a-z]/, # regular Ruby methods
          /^\s*def test_/, # minitest
          /^\s*x?it(\s+|\()['"_a-z]/ # RSpec
        ]
      )
    },
    javascript: {
      extensions: %w[js jsx ts tsx],
      class: /^\s*class\s+[_A-Z]/,
      method: Regexp.union(
        [
          /function(\s+[_a-zA-Z][\da-zA-Z]*)?\s*\(/, # regular method
          /^\s*x?it(\s+|\()['"_a-z]/, # jsspec, jasmine, jest
          /^\s*test(\s+|\()['"_a-z]/, # jest
          /^\s*QUnit.test(\s+|\()['"_a-z]/ # qunit
        ]
      )
    },
    coffee: {
      extensions: %w[coffee],
      class: /^\s*class\s+[_A-Z]/,
      method: /[-=]>/
    },
    rust: {
      extensions: %(rs),
      class: /^\s*struct\s+[_A-Z]/,
      method: Regexp.union(
        [
          /^\s*fn\s+[_a-z]/, # regular Rust methods
          /#\[test\]/ # methods with test config
        ]
      )
    },
    c_cpp: {
      extensions: %(h c cpp),
      class: /^\s*(struct|class)\s+[_a-z]/i,
      method: /^\s*\w.* \w.*\(.*\)\s*{/m
    }
  }.deep_merge(config.additional_detailed_stats_patterns)

  # We allow to configure additional annotation directories. They will be
  # included in the default list. This way you can easily extend the list.
  config.additional_annotations_directories = []

  # Configure the directories which should be checked for annotations
  config.annotations_directories = %w[
    app config db src lib test tests spec doc docs
  ] + config.additional_annotations_directories

  # We allow to configure additional annotation files/patterns. They will be
  # included in the default list. This way you can easily extend the list.
  config.additional_annotations_files = []

  # Configure the files/patterns which should be checked for annotations
  config.annotations_files = %w[
    Appraisals CHANGELOG.md CODE_OF_CONDUCT.md config.ru docker-compose.yml
    Dockerfile Envfile Gemfile *.gemspec Makefile Rakefile README.md
  ] + config.additional_annotations_files

  # We allow to configure additional annotation tags. They will be included
  # in the default list. This way you can easily extend the list.
  config.additional_annotation_tags = []

  # Configure the annotation tags which will be search
  config.annotation_tags = %w[
    OPTIMIZE FIXME TODO TESTME DEPRECATEME
  ] + config.additional_annotation_tags

  # We allow to configure additional annotation patterns. They will be
  # included in the default list. This way you can easily extend the list.
  config.additional_annotation_patterns = {}

  # Configure all known file extensions of annotations files
  config.annotation_patterns = {
    hashtag: {
      files: %w[Appraisals Dockerfile Envfile Gemfile Rakefile
                Makefile Appraisals],
      extensions: %w[builder md ru rb rake yml yaml ruby gemspec toml],
      regex: ->(tag) { /#\s*(#{tag}):?\s*(.*)$/ }
    },
    double_slash: {
      extensions: %w[css js jsx ts tsx rust c h],
      regex: ->(tag) { %r{//\s*(#{tag}):?\s*(.*)$} }
    },
    erb: {
      extensions: %w[erb],
      regex: ->(tag) { /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ }
    },
    haml: {
      extensions: %w[haml],
      regex: ->(tag) { /-#\s*(#{tag}):?\s*(.*)$/ }
    }
  }.deep_merge(config.additional_annotation_patterns)
end

Development

After checking out the repo, run make install to install dependencies. Then, run make test to run the tests. You can also run make shell-irb for an interactive prompt that will allow you to experiment.

To release a new version, update the version number in version.rb, and then run make release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/hausgold/countless.