{"id":19206787,"url":"https://github.com/hausgold/countless","last_synced_at":"2026-03-11T21:02:09.082Z","repository":{"id":56858427,"uuid":"446825802","full_name":"hausgold/countless","owner":"hausgold","description":"Code statistics/annotations helpers","archived":false,"fork":false,"pushed_at":"2026-03-09T05:11:56.000Z","size":148,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":9,"default_branch":"master","last_synced_at":"2026-03-09T09:57:01.962Z","etag":null,"topics":["annotations","cloc","gem","oss","rake","rake-task","ruby","ruby-gem","statistics"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/countless","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hausgold.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-01-11T13:10:36.000Z","updated_at":"2026-03-09T05:11:57.000Z","dependencies_parsed_at":"2025-02-25T10:22:07.507Z","dependency_job_id":"8913a9ce-27e1-4293-8e85-7555bbb80682","html_url":"https://github.com/hausgold/countless","commit_stats":{"total_commits":8,"total_committers":2,"mean_commits":4.0,"dds":0.125,"last_synced_commit":"c60f8b414f5a8a213f34c5d7030cc6df3fb4c79c"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/hausgold/countless","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hausgold%2Fcountless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hausgold%2Fcountless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hausgold%2Fcountless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hausgold%2Fcountless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hausgold","download_url":"https://codeload.github.com/hausgold/countless/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hausgold%2Fcountless/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30400654,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-11T18:46:22.935Z","status":"ssl_error","status_checked_at":"2026-03-11T18:46:17.045Z","response_time":84,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["annotations","cloc","gem","oss","rake","rake-task","ruby","ruby-gem","statistics"],"created_at":"2024-11-09T13:17:00.340Z","updated_at":"2026-03-11T21:02:09.076Z","avatar_url":"https://github.com/hausgold.png","language":"Ruby","readme":"![Countless](doc/assets/project.svg)\n\n[![Continuous Integration](https://github.com/hausgold/countless/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/hausgold/countless/actions/workflows/test.yml)\n[![Gem Version](https://badge.fury.io/rb/countless.svg)](https://badge.fury.io/rb/countless)\n[![Test Coverage](https://automate-api.hausgold.de/v1/coverage_reports/countless/coverage.svg)](https://knowledge.hausgold.de/coverage)\n[![Test Ratio](https://automate-api.hausgold.de/v1/coverage_reports/countless/ratio.svg)](https://knowledge.hausgold.de/coverage)\n[![API docs](https://automate-api.hausgold.de/v1/coverage_reports/countless/documentation.svg)](https://www.rubydoc.info/gems/countless)\n\nThis is a reusable and widely configurable collection of\n[Rake](https://ruby.github.io/rake/) tasks and utilities for code statistics\nand annotations. The Rake task names and outputs are based on the Rails tasks.\nFor code statistics (lines of code, comments) the\n[cloc](https://github.com/AlDanial/cloc) utility is used, which is\nbattle-proven, popular and good maintained. A bundled version of it is shipped\nwith the gem package.\n\n- [Installation](#installation)\n- [Requirements](#requirements)\n- [Usage](#usage)\n  - [Additional Configuration](#additional-configuration)\n- [Development](#development)\n- [Code of Conduct](#code-of-conduct)\n- [Contributing](#contributing)\n- [Releasing](#releasing)\n\n## Installation\n\nAdd this line to your gemspec/Gemfile:\n\n```ruby\n# Within a gem/library use:\nspec.add_runtime_dependency 'countless'\n\n# In an application use:\ngem 'countless'\n```\n\nAnd then execute:\n\n```bash\n$ bundle\n```\n\n## Requirements\n\n* [Ruby](https://www.ruby-lang.org/) (\u003e=3.2, tested on CRuby/MRI only, may work\n  with other implementations as well)\n* [Perl](https://www.perl.org/) (\u003e= 5.10, for the\n  [cloc](https://github.com/AlDanial/cloc) utility)\n\n## Usage\n\nYou can configure the Countless gem in several ways, but the most common\nusecase is to install its Rake tasks and configure it in order to work\nproperly. Here comes a self descriptive example (within a Rakefile):\n\n```ruby\n# Add the annotations and statistics tasks\nrequire 'countless/rake_tasks'\n```\n\nAfterwards the following Rake tasks are available to you:\n\n* **stats**: Report code statistics (KLOCs, etc) (run via `bundle exec rake stats`)\n  ```\n  +------------------+-------+-----+----------+---------+---------+-----+-------+\n  | Name             | Lines | LOC | Comments | Classes | Methods | M/C | LOC/M |\n  +------------------+-------+-----+----------+---------+---------+-----+-------+\n  | Extensions       |    83 |  40 |       33 |       0 |       4 |   0 |    10 |\n  | Top-levels       |   934 | 503 |      331 |       5 |      36 |   7 |    13 |\n  | Extensions specs |    36 |  28 |        1 |       0 |       4 |   0 |     7 |\n  | Top-levels specs |   323 | 260 |        4 |       0 |      39 |   0 |     6 |\n  +------------------+-------+-----+----------+---------+---------+-----+-------+\n  | Total            |  1376 | 831 |      369 |       5 |      83 |  16 |    10 |\n  +------------------+-------+-----+----------+---------+---------+-----+-------+\n    Code LOC: 543     Test LOC: 288     Code to Test Ratio: 1:0.5\n  ```\n* **notes**: Enumerate all annotations (run via `bundle exec rake notes`)\n  ```\n  Rakefile:\n    * [ 7] This is just for testing purposes\n\n  lib/countless/rake_tasks.rb:\n    * [ 3] This is just for testing purposes here. Keep it exactly like that.\n\n  spec/fixtures/files/test/test_spec.rb:\n    * [29] Do something\n  ```\n  * **notes:optimize**, **notes:fixme**, **notes:todo**, **notes:testme**,\n    **notes:deprecateme** (by default, see `config.annotation_tags`, to\n    configure more defaults)\n  * **notes:custom**: Show notes for custom annotation (run via `bundle exec\n    rake notes:custom ANNOTATION='NOTE'`)\n\n### Additional Configuration\n\n```ruby\n# All the configured values here represent the Gem defaults.\nCountless.configure do |config|\n  # The base/root path of the project to work on. This path is used as a #\n  # prefix to all relative path/file configurations. By default we check for a\n  # Rake invocation (Rakefile location), a Rails invocation (project root) or\n  # fallback the the current working directory of the process.\n  config.base_path = Dir.pwd\n\n  # The path to the cloc (https://github.com/AlDanial/cloc) utility. The gem\n  # comes with a bundled version of the utility, ready to be used. But you\n  # can also change the used binary path in order to use a different version\n  # which you manually provisioned.\n  config.cloc_path = File.expand_path('../../bin/cloc', __dir__)\n\n  # We allow to configure additional file extensions to consider for\n  # statistics calculation. They will be included in the default list. This\n  # way you can easily extend the list.\n  config.additional_stats_file_extensions = []\n\n  # All the file extensions to consider for statistics calculation\n  config.stats_file_extensions = %w[\n    rb js jsx ts tsx css scss coffee rake erb haml h c cpp rs\n  ] + config.additional_stats_file_extensions\n\n  # We allow to configure additional application object types. They will be\n  # included in the default list. This way you can easily extend the list.\n  config.additional_stats_app_object_types = []\n\n  # Configure the application (in the root +app+ directory) object types,\n  # they will be added as regular directories as well as their testing\n  # counter parts (minitest/RSpec)\n  config.stats_app_object_types = %w[\n    channels consumers controllers dashboards decorators fields helpers jobs\n    mailboxes mailers models policies serializers services uploaders\n    validators value_objects views\n  ] + config.additional_stats_app_object_types\n\n  # We allow to configure additional statistics directories. They will be\n  # included in the default list. This way you can easily extend the list.\n  config.additional_stats_directories = []\n\n  # A list of custom base directories in an application / gem\n  config.stats_base_directories = [\n    { name: 'JavaScripts', dir: 'app/assets/javascripts' },\n    { name: 'Stylesheets', dir: 'app/assets/stylesheets' },\n    { name: 'JavaScript', dir: 'app/javascript' },\n    { name: 'API', dir: 'app/api' },\n    { name: 'API tests', dir: 'test/api', test: true },\n    { name: 'API specs', dir: 'spec/api', test: true },\n    { name: 'APIs', dir: 'app/apis' },\n    { name: 'API tests', dir: 'test/apis', test: true },\n    { name: 'API specs', dir: 'spec/apis', test: true },\n    { name: 'Libraries', dir: 'app/lib' },\n    { name: 'Library tests', dir: 'test/lib', test: true },\n    { name: 'Library specs', dir: 'spec/lib', test: true },\n    { name: 'Libraries', dir: 'lib' },\n    { name: 'Library tests', dir: 'test/lib', test: true },\n    { name: 'Library specs', dir: 'spec/lib', test: true }\n  ] + config.additional_stats_directories\n\n  # We allow to configure additional detailed statistics patterns. They will\n  # be included in the default list. This way you can easily extend the list.\n  config.additional_detailed_stats_patterns = {}\n\n  # All the detailed statistics (class/method and tests/examples) patterns\n  # which will be used for parsing the source files to gather the metrics\n  config.detailed_stats_patterns = {\n    ruby: {\n      extensions: %w[rb rake],\n      class: /^\\s*class\\s+[_A-Z]/, # regular Ruby classes\n      method: Regexp.union(\n        [\n          /^\\s*def\\s+[_a-z]/, # regular Ruby methods\n          /^\\s*def test_/, # minitest\n          /^\\s*x?it(\\s+|\\()['\"_a-z]/ # RSpec\n        ]\n      )\n    },\n    javascript: {\n      extensions: %w[js jsx ts tsx],\n      class: /^\\s*class\\s+[_A-Z]/,\n      method: Regexp.union(\n        [\n          /function(\\s+[_a-zA-Z][\\da-zA-Z]*)?\\s*\\(/, # regular method\n          /^\\s*x?it(\\s+|\\()['\"_a-z]/, # jsspec, jasmine, jest\n          /^\\s*test(\\s+|\\()['\"_a-z]/, # jest\n          /^\\s*QUnit.test(\\s+|\\()['\"_a-z]/ # qunit\n        ]\n      )\n    },\n    coffee: {\n      extensions: %w[coffee],\n      class: /^\\s*class\\s+[_A-Z]/,\n      method: /[-=]\u003e/\n    },\n    rust: {\n      extensions: %(rs),\n      class: /^\\s*struct\\s+[_A-Z]/,\n      method: Regexp.union(\n        [\n          /^\\s*fn\\s+[_a-z]/, # regular Rust methods\n          /#\\[test\\]/ # methods with test config\n        ]\n      )\n    },\n    c_cpp: {\n      extensions: %(h c cpp),\n      class: /^\\s*(struct|class)\\s+[_a-z]/i,\n      method: /^\\s*\\w.* \\w.*\\(.*\\)\\s*{/m\n    }\n  }.deep_merge(config.additional_detailed_stats_patterns)\n\n  # We allow to configure additional annotation directories. They will be\n  # included in the default list. This way you can easily extend the list.\n  config.additional_annotations_directories = []\n\n  # Configure the directories which should be checked for annotations\n  config.annotations_directories = %w[\n    app config db src lib test tests spec doc docs\n  ] + config.additional_annotations_directories\n\n  # We allow to configure additional annotation files/patterns. They will be\n  # included in the default list. This way you can easily extend the list.\n  config.additional_annotations_files = []\n\n  # Configure the files/patterns which should be checked for annotations\n  config.annotations_files = %w[\n    Appraisals CHANGELOG.md CODE_OF_CONDUCT.md config.ru docker-compose.yml\n    Dockerfile Envfile Gemfile *.gemspec Makefile Rakefile README.md\n  ] + config.additional_annotations_files\n\n  # We allow to configure additional annotation tags. They will be included\n  # in the default list. This way you can easily extend the list.\n  config.additional_annotation_tags = []\n\n  # Configure the annotation tags which will be search\n  config.annotation_tags = %w[\n    OPTIMIZE FIXME TODO TESTME DEPRECATEME\n  ] + config.additional_annotation_tags\n\n  # We allow to configure additional annotation patterns. They will be\n  # included in the default list. This way you can easily extend the list.\n  config.additional_annotation_patterns = {}\n\n  # Configure all known file extensions of annotations files\n  config.annotation_patterns = {\n    hashtag: {\n      files: %w[Appraisals Dockerfile Envfile Gemfile Rakefile\n                Makefile Appraisals],\n      extensions: %w[builder md ru rb rake yml yaml ruby gemspec toml],\n      regex: -\u003e(tag) { /#\\s*(#{tag}):?\\s*(.*)$/ }\n    },\n    double_slash: {\n      extensions: %w[css js jsx ts tsx rust c h],\n      regex: -\u003e(tag) { %r{//\\s*(#{tag}):?\\s*(.*)$} }\n    },\n    erb: {\n      extensions: %w[erb],\n      regex: -\u003e(tag) { /\u003c%\\s*#\\s*(#{tag}):?\\s*(.*?)\\s*%\u003e/ }\n    },\n    haml: {\n      extensions: %w[haml],\n      regex: -\u003e(tag) { /-#\\s*(#{tag}):?\\s*(.*)$/ }\n    }\n  }.deep_merge(config.additional_annotation_patterns)\nend\n```\n\n## Development\n\nAfter checking out the repo, run `make install` to install dependencies. Then,\nrun `make test` to run the tests. You can also run `make shell-irb` for an\ninteractive prompt that will allow you to experiment.\n\n## Code of Conduct\n\nEveryone interacting in the project codebase, issue tracker, chat\nrooms and mailing lists is expected to follow the [code of\nconduct](./CODE_OF_CONDUCT.md).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at\nhttps://github.com/hausgold/countless. Make sure that every pull request adds\na bullet point to the [changelog](./CHANGELOG.md) file with a reference to the\nactual pull request.\n\n## Releasing\n\nThe release process of this Gem is fully automated. You just need to open the\nGithub Actions [Release\nWorkflow](https://github.com/hausgold/countless/actions/workflows/release.yml)\nand trigger a new run via the **Run workflow** button. Insert the new version\nnumber (check the [changelog](./CHANGELOG.md) first for the latest release) and\nyou're done.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhausgold%2Fcountless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhausgold%2Fcountless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhausgold%2Fcountless/lists"}