├── .github
└── FUNDING.yml
├── .gitignore
├── .rspec
├── .ruby-version
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── lib
└── rake
│ ├── notes.rb
│ └── notes
│ ├── rake_task.rb
│ ├── source_annotation_extractor.rb
│ └── version.rb
├── rake-notes.gemspec
└── spec
├── rake
└── notes
│ └── source_annotation_extractor_spec.rb
└── spec_helper.rb
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # # These are supported funding model platforms
2 | #
3 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | # patreon: # Replace with a single Patreon username
5 | # open_collective: # Replace with a single Open Collective username
6 | # ko_fi: # Replace with a single Ko-fi username
7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | # liberapay: # Replace with a single Liberapay username
10 | # issuehunt: # Replace with a single IssueHunt username
11 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | # polar: # Replace with a single Polar username
13 | buy_me_a_coffee: "benjaminoakes"
14 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | InstalledFiles
7 | _yardoc
8 | coverage
9 | doc/
10 | lib/bundler/man
11 | pkg
12 | rdoc
13 | spec/reports
14 | *.swp
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | .tmp
19 | vendor/bundle
20 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format documentation
3 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.3.2
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in rake-notes.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | rake-notes (1.0.0)
5 | colored
6 | rake
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | colored (1.2)
12 | diff-lcs (1.5.1)
13 | rake (13.2.1)
14 | rspec (3.13.0)
15 | rspec-core (~> 3.13.0)
16 | rspec-expectations (~> 3.13.0)
17 | rspec-mocks (~> 3.13.0)
18 | rspec-core (3.13.0)
19 | rspec-support (~> 3.13.0)
20 | rspec-expectations (3.13.0)
21 | diff-lcs (>= 1.2.0, < 2.0)
22 | rspec-support (~> 3.13.0)
23 | rspec-mocks (3.13.1)
24 | diff-lcs (>= 1.2.0, < 2.0)
25 | rspec-support (~> 3.13.0)
26 | rspec-support (3.13.1)
27 |
28 | PLATFORMS
29 | ruby
30 | x86_64-linux
31 |
32 | DEPENDENCIES
33 | rake-notes!
34 | rspec
35 |
36 | BUNDLED WITH
37 | 2.5.9
38 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Fabio Rehm
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rake-notes
2 |
3 | `rake notes` task for non-Rails' projects (heavily based on Rails' one ;)
4 |
5 | ## Installation
6 |
7 | Add this line to your application's Gemfile:
8 |
9 | gem 'rake-notes'
10 |
11 | And then execute:
12 |
13 | $ bundle
14 |
15 | Or install it yourself as:
16 |
17 | $ gem install rake-notes
18 |
19 | And add this line to your project's Rakefile:
20 |
21 | require 'rake/notes/rake_task'
22 |
23 |
24 | ## Acknowledgement
25 |
26 | Special thanks to everyone that contributed to the original
27 | [Rails' code](https://github.com/rails/rails/blob/master/railties/lib/rails/source_annotation_extractor.rb)
28 |
29 |
30 | ## Contributing
31 |
32 | 1. Fork it
33 | 2. Create your feature branch (`git checkout -b my-new-feature`)
34 | 3. Commit your changes (`git commit -am 'Add some feature'`)
35 | 4. Push to the branch (`git push origin my-new-feature`)
36 | 5. Create new Pull Request
37 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
3 | require 'rspec/core/rake_task'
4 | RSpec::Core::RakeTask.new(:test)
5 |
6 | task :default => [:test]
7 |
--------------------------------------------------------------------------------
/lib/rake/notes.rb:
--------------------------------------------------------------------------------
1 | require 'rake/notes/version'
2 |
3 | # Nothing to see here, see rake_task.rb
4 |
--------------------------------------------------------------------------------
/lib/rake/notes/rake_task.rb:
--------------------------------------------------------------------------------
1 | require 'rake'
2 | require 'rake/tasklib'
3 |
4 | require 'rake/notes/source_annotation_extractor'
5 |
6 | module Rake
7 | module Notes
8 | class RakeTask < ::Rake::TaskLib
9 | include ::Rake::DSL if defined?(::Rake::DSL)
10 |
11 | def initialize(*args)
12 | yield self if block_given?
13 |
14 | desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)"
15 | task :notes do
16 | SourceAnnotationExtractor.enumerate "OPTIMIZE|FIXME|TODO", :tag => true
17 | end
18 |
19 | namespace :notes do
20 | ["OPTIMIZE", "FIXME", "TODO"].each do |annotation|
21 | desc "Enumerate all #{annotation} annotations"
22 | task annotation.downcase.intern do
23 | SourceAnnotationExtractor.enumerate annotation
24 | end
25 | end
26 |
27 | desc "Enumerate a custom annotation, specify with ANNOTATION=CUSTOM"
28 | task :custom do
29 | SourceAnnotationExtractor.enumerate ENV['ANNOTATION']
30 | end
31 | end
32 | end
33 | end
34 | end
35 | end
36 |
37 | Rake::Notes::RakeTask.new
38 |
--------------------------------------------------------------------------------
/lib/rake/notes/source_annotation_extractor.rb:
--------------------------------------------------------------------------------
1 | require 'colored'
2 | require 'yaml'
3 |
4 | module Rake
5 | module Notes
6 | # From:
7 | # https://github.com/rails/rails/blob/master/railties/lib/rails/source_annotation_extractor.rb
8 | #
9 | # Implements the logic behind the rake tasks for annotations like
10 | #
11 | # rake notes
12 | # rake notes:optimize
13 | #
14 | # and friends. See rake -T notes.
15 | #
16 | # Annotation objects are triplets :line, :tag, :text that
17 | # represent the line where the annotation lives, its tag, and its text. Note
18 | # the filename is not stored.
19 | #
20 | # Annotations are looked for in comments and modulus whitespace they have to
21 | # start with the tag optionally followed by a colon. Everything up to the end
22 | # of the line (or closing ERB comment tag) is considered to be their text.
23 | class SourceAnnotationExtractor
24 | RUBYFILES = %w( Vagrantfile Rakefile Puppetfile Gemfile )
25 | # `gem install --standalone` puts gems into $project/bundle and it really slows
26 | # down the rake task
27 | DEFAULT_IGNORE_DIRS = { 'ignore' => [ './bundle', './vendor' ] }
28 |
29 | def self.ignore_dirs
30 | @ignore_dirs ||= begin
31 | config = DEFAULT_IGNORE_DIRS
32 | config = config.merge(YAML.safe_load(File.read('./.rake-notes.yml'))) if File.exist?('./.rake-notes.yml')
33 | config['ignore'] || []
34 | end
35 | end
36 |
37 | class Annotation < Struct.new(:line, :tag, :text)
38 | COLORS = {
39 | 'OPTIMIZE' => 'cyan',
40 | 'FIXME' => 'red',
41 | 'TODO' => 'yellow'
42 | }
43 |
44 | # Returns a representation of the annotation that looks like this:
45 | #
46 | # [126] [TODO] This algorithm is simple and clearly correct, make it faster.
47 | #
48 | # If +options+ has a flag :tag the tag is shown as in the example above.
49 | # Otherwise the string contains just line and text.
50 | def to_s(options={})
51 | colored_tag = COLORS[tag.to_s].nil? ? tag : tag.send(COLORS[tag.to_s])
52 | s = "[#{line.to_s.rjust(options[:indent]).green}] "
53 | s << "[#{colored_tag}] " if options[:tag]
54 | s << text
55 | end
56 | end
57 |
58 | # Prints all annotations with tag +tag+ under the current directory. Only
59 | # known file types are taken into account. The +options+ hash is passed
60 | # to each annotation's +to_s+.
61 | #
62 | # This class method is the single entry point for the rake tasks.
63 | def self.enumerate(tag, options={})
64 | extractor = new(tag)
65 | extractor.display(extractor.find, options)
66 | end
67 |
68 | attr_reader :tag
69 |
70 | def initialize(tag)
71 | @tag = tag
72 | end
73 |
74 | # Returns a hash that maps filenames to arrays with their annotations.
75 | def find
76 | find_in('.')
77 | end
78 |
79 | # Returns a hash that maps filenames under +dir+ (recursively) to arrays
80 | # with their annotations. Only files with annotations are included, and only
81 | # known file types are taken into account.
82 | def find_in(dir)
83 | results = {}
84 | Dir.glob("#{dir}/*") do |item|
85 | next if File.basename(item)[0] == ?.
86 |
87 | if File.directory?(item)
88 | next if SourceAnnotationExtractor.ignore_dirs.include?(item)
89 | results.update(find_in(item))
90 | elsif item =~ /\.(builder|rb|coffee|rake|pp|ya?ml|gemspec|feature)$/ || RUBYFILES.include?(File.basename(item))
91 | results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
92 | elsif item =~ /\.(css|scss|js|ts|tsx)$/
93 | results.update(extract_annotations_from(item, /\/\/\s*(#{tag}):?\s*(.*)$/))
94 | elsif item =~ /\.erb$/
95 | results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/))
96 | elsif item =~ /\.haml$/
97 | results.update(extract_annotations_from(item, /-\s*#\s*(#{tag}):?\s*(.*)$/))
98 | elsif item =~ /\.slim$/
99 | results.update(extract_annotations_from(item, /\/\s*\s*(#{tag}):?\s*(.*)$/))
100 | elsif item =~ /\.md$/
101 | results.update(extract_annotations_from(item, /\*\*\s*(#{tag})\**:?\s*(.*)\**$/))
102 | end
103 | end
104 |
105 | results
106 | end
107 |
108 | # If +file+ is the filename of a file that contains annotations this method returns
109 | # a hash with a single entry that maps +file+ to an array of its annotations.
110 | # Otherwise it returns an empty hash.
111 | def extract_annotations_from(file, pattern)
112 | lineno = 0
113 | result = File.readlines(file).inject([]) do |list, line|
114 | lineno += 1
115 | next list unless line =~ pattern
116 | list << Annotation.new(lineno, $1, $2)
117 | end
118 | result.empty? ? {} : { file => result }
119 | end
120 |
121 | # Prints the mapping from filenames to annotations in +results+ ordered by filename.
122 | # The +options+ hash is passed to each annotation's +to_s+.
123 | def display(results, options={})
124 | options[:indent] = results.map { |f, a| a.map(&:line) }.flatten.max.to_s.size
125 | out = options.delete(:out) || $stdout
126 | results.keys.sort.each do |file|
127 | out.puts "#{file[2..-1]}:"
128 | results[file].each do |note|
129 | out.puts " * #{note.to_s(options)}"
130 | end
131 | out.puts
132 | end
133 | end
134 | end
135 | end
136 | end
137 |
--------------------------------------------------------------------------------
/lib/rake/notes/version.rb:
--------------------------------------------------------------------------------
1 | module Rake
2 | module Notes
3 | VERSION = "1.0.0"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rake-notes.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'rake/notes/version'
5 |
6 | Gem::Specification.new do |gem|
7 | gem.name = "rake-notes"
8 | gem.version = Rake::Notes::VERSION
9 | gem.authors = ["Fabio Rehm"]
10 | gem.email = ["fgrehm@gmail.com"]
11 | gem.description = "rake notes task for non-Rails' projects"
12 | gem.summary = "rake notes task for non-Rails' projects"
13 | gem.homepage = "https://github.com/fgrehm/rake-notes"
14 |
15 | gem.files = `git ls-files`.split($/)
16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18 | gem.require_paths = ["lib"]
19 |
20 | gem.add_dependency 'rake'
21 | gem.add_dependency 'colored'
22 |
23 | gem.add_development_dependency 'rspec'
24 | end
25 |
--------------------------------------------------------------------------------
/spec/rake/notes/source_annotation_extractor_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | require 'rake/notes/source_annotation_extractor'
4 |
5 | describe Rake::Notes::SourceAnnotationExtractor do
6 | before do
7 | @current_path = Dir.pwd
8 | Dir.mkdir(fixture_path) unless Dir.exist?(fixture_path)
9 | Dir.chdir(fixture_path)
10 | end
11 |
12 | after do
13 | Dir.chdir(@current_path)
14 | end
15 |
16 | context 'extracting notes based on file type' do
17 | subject do
18 | subj = StringIO.new
19 | described_class.enumerate('TODO', :out => subj)
20 | subj.string
21 | end
22 |
23 | before(:all) do
24 | fixture_file "index.html.erb", "<% # TODO: note in erb %>"
25 | fixture_file "index.html.haml", "- # TODO: note in haml"
26 | fixture_file "index.html.slim", "/ TODO: note in slim"
27 | fixture_file "application.js.coffee", "# TODO: note in coffee"
28 | fixture_file "application.js", "// TODO: note in js"
29 | fixture_file "application.css", "// TODO: note in css"
30 | fixture_file "application.css.scss", "// TODO: note in scss"
31 | fixture_file "component.tsx", "// TODO: note in tsx"
32 | fixture_file "application.ts", "// TODO note in ts"
33 | fixture_file "application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby"
34 | fixture_file "task.rake", "# TODO: note in rake"
35 | fixture_file "init.pp", "# TODO: note in puppet"
36 | fixture_file "config.yml", "# TODO: note in yml"
37 | fixture_file "config.yaml", "# TODO: note in yaml"
38 | fixture_file "gem.gemspec", "# TODO: note in gemspec"
39 | fixture_file "Vagrantfile", "# TODO: note in vagrantfile"
40 | fixture_file "Rakefile", "# TODO: note in rakefile"
41 | fixture_file "Puppetfile", "# TODO: note in puppetfile"
42 | fixture_file "Gemfile", "# TODO: note in gemfile"
43 | fixture_file "feature.feature", "# TODO: note in cucumber feature"
44 | fixture_file "README.md", "**TODO**: note in markdown version one"
45 | fixture_file "CONTRIBUTING.md", "**TODO: note in markdown version two**"
46 | end
47 |
48 | it { should match(/note in erb/) }
49 | it { should match(/note in haml/) }
50 | it { should match(/note in slim/) }
51 | it { should match(/note in ruby/) }
52 | it { should match(/note in coffee/) }
53 | it { should match(/note in js/) }
54 | it { should match(/note in css/) }
55 | it { should match(/note in scss/) }
56 | it { should match(/note in tsx/) }
57 | it { should match(/note in ts/) }
58 | it { should match(/note in rake/) }
59 | it { should match(/note in puppet/) }
60 | it { should match(/note in yml/) }
61 | it { should match(/note in yaml/) }
62 | it { should match(/note in gemspec/) }
63 | it { should match(/note in vagrantfile/) }
64 | it { should match(/note in rakefile/) }
65 | it { should match(/note in puppetfile/) }
66 | it { should match(/note in gemfile/) }
67 | it { should match(/note in cucumber feature/) }
68 | it { should match(/note in markdown version one/) }
69 | it { should match(/note in markdown version two/) }
70 | end
71 |
72 | def fixture_file(path, contents)
73 | FileUtils.mkdir_p File.dirname("#{fixture_path}/#{path}")
74 | File.open("#{fixture_path}/#{path}", 'w') do |f|
75 | f.puts contents
76 | end
77 | end
78 |
79 | def fixture_path
80 | "#{Dir.pwd}/.tmp"
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'bundler/setup'
2 | Bundler.require(:default)
3 |
4 | RSpec.configure do |config|
5 | config.treat_symbols_as_metadata_keys_with_true_values = true
6 | config.run_all_when_everything_filtered = true
7 | config.filter_run :focus
8 |
9 | # Run specs in random order to surface order dependencies. If you find an
10 | # order dependency and want to debug it, you can fix the order by providing
11 | # the seed, which is printed after each run.
12 | # --seed 1234
13 | config.order = 'random'
14 | end
15 |
--------------------------------------------------------------------------------