cease 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +25 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +26 -0
- data/README.md +134 -0
- data/Rakefile +10 -0
- data/bin/cease +5 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/cease.gemspec +34 -0
- data/lib/cease/cli.rb +53 -0
- data/lib/cease/eviction/chunk.rb +81 -0
- data/lib/cease/eviction/command/date_time.rb +136 -0
- data/lib/cease/eviction/comment.rb +107 -0
- data/lib/cease/eviction/context.rb +100 -0
- data/lib/cease/eviction/scope.rb +83 -0
- data/lib/cease/eviction/statement.rb +39 -0
- data/lib/cease/examiner.rb +30 -0
- data/lib/cease/git.rb +21 -0
- data/lib/cease/report.rb +68 -0
- data/lib/cease/summary.rb +33 -0
- data/lib/cease/version.rb +3 -0
- data/lib/cease.rb +11 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cdd44d4a984dd4c0f68b3e3708dcf8e1e7d5ab85fda130a67a41fa5942473231
|
4
|
+
data.tar.gz: 0ca8399c3a97ad5de4c278c9d318556074a23ab5576b652d4d312f2197a4a008
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 87d2e9b44d400ec3696ccd509dfd7a55c6c4a47b6b1fb7635e9eedc6e76888aa62e51a6644324e686f920a16a7b454edc814fa3db6fca4676bb1a698ebc7c9e3
|
7
|
+
data.tar.gz: 325ce0face6fd0751041e911ec7e3d12ce8a5960a461cc85b5d55fe1463926141e252274cf99349b045c3d2d04641b9d1111f3906991f715c50cb830ed1df783
|
@@ -0,0 +1,25 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
|
13
|
+
strategy:
|
14
|
+
matrix:
|
15
|
+
ruby: [2.6, 2.7, "3.0", "3.1", jruby-9.3]
|
16
|
+
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v2
|
19
|
+
- name: Setup Ruby
|
20
|
+
uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: ${{ matrix.ruby }}
|
23
|
+
bundler-cache: true
|
24
|
+
- name: Run specs
|
25
|
+
run: bundle exec rspec
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at sung@dustybit.software. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [https://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: https://contributor-covenant.org
|
74
|
+
[version]: https://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2021 Dusty Bit Software, Inc.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name Solidus nor the names of its contributors may be used to
|
13
|
+
endorse or promote products derived from this software without specific
|
14
|
+
prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
17
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
18
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
20
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
21
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
22
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# Cease
|
2
|
+
|
3
|
+

|
4
|
+
|
5
|
+
`cease` is a tool that scans for Ruby code marked as EOL.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install via rubygems:
|
10
|
+
```sh
|
11
|
+
gem install cease
|
12
|
+
```
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
Run it:
|
17
|
+
```sh
|
18
|
+
cease [directory_or_source_file]*
|
19
|
+
```
|
20
|
+
|
21
|
+
## Example
|
22
|
+
### Basic usage
|
23
|
+
Given a source file called `example.rb` that contains the following code:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# [cease] at 12pm on 1/1/1999
|
27
|
+
class RemoveMeLater
|
28
|
+
def foo
|
29
|
+
puts 'bar'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
# [/cease]
|
33
|
+
```
|
34
|
+
|
35
|
+
You should see the output:
|
36
|
+
```sh
|
37
|
+
$ cease example.rb
|
38
|
+
Scanning 1 source(s)...
|
39
|
+
|
40
|
+
(example.rb)
|
41
|
+
|
42
|
+
[3, 9]: Overdue by roughly 23 years
|
43
|
+
class RemoveMeLater
|
44
|
+
def foo
|
45
|
+
puts 'bar'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
Total of 1 evictions(s) found.
|
51
|
+
```
|
52
|
+
|
53
|
+
### Options
|
54
|
+
Cease supports both 12 and 24 hour clocks:
|
55
|
+
```ruby
|
56
|
+
# [cease] at 13:00 on 1/1/1999
|
57
|
+
class RemoveMeLater
|
58
|
+
...
|
59
|
+
# [/cease]
|
60
|
+
```
|
61
|
+
|
62
|
+
Multiple commands per source:
|
63
|
+
```ruby
|
64
|
+
# [cease] at 12pm on 1/1/1999
|
65
|
+
class RemoveMeLater
|
66
|
+
def foo
|
67
|
+
puts 'bar'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
# [/cease]
|
71
|
+
|
72
|
+
# [cease] at 1pm on 3/3/3333
|
73
|
+
class RemoveMeWayLater
|
74
|
+
def foo
|
75
|
+
puts 'bar'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
# [/cease]
|
79
|
+
```
|
80
|
+
|
81
|
+
If a date isn't provided, Cease attempts to guess the date based on the git commit:
|
82
|
+
```ruby
|
83
|
+
# [cease] at 13:00 # The date will be based on the commit timestamp of this comment.
|
84
|
+
class RemoveMeLater
|
85
|
+
...
|
86
|
+
# [/cease]
|
87
|
+
```
|
88
|
+
|
89
|
+
You can provide an optional timezone (defaults to UTC):
|
90
|
+
```ruby
|
91
|
+
# [cease] at 1pm on 1/1/1999 { timezone: 'PST' }
|
92
|
+
class RemoveMeLater
|
93
|
+
...
|
94
|
+
# [/cease]
|
95
|
+
```
|
96
|
+
|
97
|
+
**NOTE: Do not nest commands! This will not work:**
|
98
|
+
```ruby
|
99
|
+
# [cease] at 1pm
|
100
|
+
class RemoveMe
|
101
|
+
# [cease] at 3pm
|
102
|
+
def initialize
|
103
|
+
end
|
104
|
+
# [/cease]
|
105
|
+
end
|
106
|
+
# [/cease]
|
107
|
+
```
|
108
|
+
|
109
|
+
## Development
|
110
|
+
|
111
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
112
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
113
|
+
prompt that will allow you to experiment.
|
114
|
+
|
115
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
116
|
+
release a new version, update the version number in `version.rb`, and then run
|
117
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
118
|
+
git commits and tags, and push the `.gem` file to
|
119
|
+
[rubygems.org](https://rubygems.org).
|
120
|
+
|
121
|
+
## Contributing
|
122
|
+
|
123
|
+
Bug reports and pull requests are welcome on GitHub at
|
124
|
+
https://github.com/DustyBitSoftware/cease. This project is intended to be a safe,
|
125
|
+
welcoming space for collaboration, and contributors are expected to adhere to
|
126
|
+
the [code of
|
127
|
+
conduct](https://github.com/nohmar/cease/blob/master/CODE_OF_CONDUCT.md).
|
128
|
+
|
129
|
+
|
130
|
+
## Code of Conduct
|
131
|
+
|
132
|
+
Everyone interacting in the Cease project's codebases, issue trackers, chat
|
133
|
+
rooms and mailing lists is expected to follow the [code of
|
134
|
+
conduct](https://github.com/nohmar/cease/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/cease
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/cease.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'lib/cease/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "cease"
|
5
|
+
spec.version = Cease::VERSION
|
6
|
+
spec.authors = ["Sung Noh"]
|
7
|
+
spec.email = ["sung@dustybit.software"]
|
8
|
+
|
9
|
+
spec.summary = 'Evict unused code at some time in the future'
|
10
|
+
spec.description = 'Cease is a tool that detects blocks of code ' \
|
11
|
+
'to be removed at a specified time.'
|
12
|
+
spec.homepage = 'https://github.com/DustyBitSoftware/cease'
|
13
|
+
spec.license = 'BSD-3-Clause'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = "bin"
|
26
|
+
spec.executables = spec.files.grep(%r{^bin/}).map { |path| File.basename(path) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_runtime_dependency 'parser', '~> 3.0.0'
|
30
|
+
spec.add_runtime_dependency 'git', '~> 1.10.0'
|
31
|
+
spec.add_runtime_dependency 'rainbow', '~> 3.0.0'
|
32
|
+
spec.add_runtime_dependency 'dotiw', '~> 5.3.2'
|
33
|
+
spec.add_runtime_dependency 'tzinfo', '~> 2.0.4'
|
34
|
+
end
|
data/lib/cease/cli.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
require_relative 'report'
|
4
|
+
|
5
|
+
module Cease
|
6
|
+
class CLI
|
7
|
+
def initialize(argv: ARGV)
|
8
|
+
@argv = argv
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
report.execute
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :argv
|
18
|
+
|
19
|
+
def report
|
20
|
+
Report.new(sources: sources)
|
21
|
+
end
|
22
|
+
|
23
|
+
def sources
|
24
|
+
entries.each_with_object([]) do |given_path, paths|
|
25
|
+
next unless given_path.exist?
|
26
|
+
next if hidden_entry?(given_path)
|
27
|
+
|
28
|
+
relevant_paths = []
|
29
|
+
|
30
|
+
# Peform a depth-first search for a given Pathname.
|
31
|
+
given_path.find do |path|
|
32
|
+
relevant_paths << path if ruby_file?(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
paths.concat(relevant_paths)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def entries
|
40
|
+
return Pathname.new('.').entries if argv.empty?
|
41
|
+
argv.map { |arg| Pathname.new(arg) }
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def hidden_entry?(path)
|
46
|
+
path.basename.to_s.start_with? '.'
|
47
|
+
end
|
48
|
+
|
49
|
+
def ruby_file?(path)
|
50
|
+
path.extname == '.rb'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Cease
|
4
|
+
module Eviction
|
5
|
+
class Chunk
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
# @params ast [Parser::AST::Node]
|
9
|
+
# @params statement [Cease::Eviction::Statement]
|
10
|
+
def initialize(ast:, statement:)
|
11
|
+
@ast = ast
|
12
|
+
@statement = statement
|
13
|
+
@closest_parent = nil
|
14
|
+
@children = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Array<Parser::AST::Node>]
|
18
|
+
def extract
|
19
|
+
return ast if only_child?
|
20
|
+
|
21
|
+
find_closest_parent(ast)
|
22
|
+
find_children(closest_parent)
|
23
|
+
children
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :ast, :closest_parent, :statement, :children
|
29
|
+
|
30
|
+
delegate [:open_comment, :close_comment] => :statement
|
31
|
+
|
32
|
+
# @note A depth-first search to find the closest non-comment parent to
|
33
|
+
# the open comment. The closest parent is found outside the boundaries
|
34
|
+
# of the Eviction statement.
|
35
|
+
def find_closest_parent(ast)
|
36
|
+
@closest_parent = ast
|
37
|
+
|
38
|
+
return unless open_comment > ast
|
39
|
+
|
40
|
+
filter_children(ast) do |child|
|
41
|
+
# Skip the child if it begins past the closing comment.
|
42
|
+
next if close_comment < child
|
43
|
+
|
44
|
+
# Skip the child if the expression begins and ends on the same line
|
45
|
+
# e.g. send, array, symbol
|
46
|
+
next if open_comment > child && close_comment > child
|
47
|
+
|
48
|
+
find_closest_parent(child)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_children(ast)
|
53
|
+
return children unless ast
|
54
|
+
|
55
|
+
filter_children(ast) do |child|
|
56
|
+
if open_comment > child && !close_comment.nested_in?(child)
|
57
|
+
@children << child
|
58
|
+
next # We got what we need. Don't go digging for other children.
|
59
|
+
end
|
60
|
+
|
61
|
+
find_children(child)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def filter_children(ast)
|
66
|
+
return unless block_given?
|
67
|
+
|
68
|
+
ast.children.grep(Parser::AST::Node).each do |child|
|
69
|
+
next unless child.respond_to? :loc
|
70
|
+
next unless child.loc.expression
|
71
|
+
|
72
|
+
yield(child)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def only_child?
|
77
|
+
ast.children.count < 2
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require_relative '../../git'
|
5
|
+
|
6
|
+
module Cease
|
7
|
+
module Eviction
|
8
|
+
module Command
|
9
|
+
class DateTime
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
class BadOptionsError < StandardError; end
|
13
|
+
|
14
|
+
DEFAULT_TIMEZONE = 'UTC'.freeze
|
15
|
+
TIMEZONE_NAME_MAPPING = {
|
16
|
+
"UTC" => "Etc/UTC",
|
17
|
+
"PST" => "America/Los_Angeles",
|
18
|
+
"MTZ" => "America/Denver",
|
19
|
+
"CST" => "America/Chicago",
|
20
|
+
"EST" => "America/New_York"
|
21
|
+
}.freeze
|
22
|
+
TIMEZONE_KEY = "timezone".freeze
|
23
|
+
|
24
|
+
# @params comment [Cease::Eviction::Comment]
|
25
|
+
# @params date_time [String]
|
26
|
+
# @params options [String]
|
27
|
+
def initialize(comment, date_time, options)
|
28
|
+
@comment = comment
|
29
|
+
@time, @date = parse(date_time)
|
30
|
+
@options = options
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [TZ::DateTimeWithOffset, nil]
|
34
|
+
def parsed_in_timezone
|
35
|
+
return unless valid?
|
36
|
+
tz.local_datetime(*local_datetime_args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def valid?
|
40
|
+
return false unless valid_time?
|
41
|
+
return false if date && !valid_date?
|
42
|
+
return false if timezone_name && !valid_timezone?
|
43
|
+
true
|
44
|
+
rescue
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def guess?
|
49
|
+
return true unless parsed_date
|
50
|
+
!!parsed_date
|
51
|
+
rescue
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def tz
|
56
|
+
::TZInfo::Timezone.get(
|
57
|
+
TIMEZONE_NAME_MAPPING[timezone_name] ||
|
58
|
+
TIMEZONE_NAME_MAPPING[DEFAULT_TIMEZONE]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
delegate [:year, :month, :day, :hour, :minute] => :best_guess_date
|
65
|
+
|
66
|
+
attr_reader :comment, :options, :time, :date
|
67
|
+
|
68
|
+
def parse(date_time)
|
69
|
+
split = date_time.split(' ')
|
70
|
+
results = []
|
71
|
+
|
72
|
+
loop do
|
73
|
+
break if split.length == 0
|
74
|
+
results << split.shift(2)
|
75
|
+
end
|
76
|
+
|
77
|
+
results
|
78
|
+
.partition { |result| result.include?("at") }
|
79
|
+
.map { |result| _identifer, value = result.flatten; value }
|
80
|
+
end
|
81
|
+
|
82
|
+
def parsed_options
|
83
|
+
@parsed_options ||= YAML.safe_load(options || '')
|
84
|
+
rescue Psych::SyntaxError
|
85
|
+
raise BadOptionsError
|
86
|
+
end
|
87
|
+
|
88
|
+
def timezone_name
|
89
|
+
return unless parsed_options
|
90
|
+
parsed_options[TIMEZONE_KEY]
|
91
|
+
end
|
92
|
+
|
93
|
+
def valid_time?
|
94
|
+
return false unless time
|
95
|
+
parsed_time
|
96
|
+
end
|
97
|
+
|
98
|
+
def valid_date?
|
99
|
+
return false unless date
|
100
|
+
parsed_date
|
101
|
+
end
|
102
|
+
|
103
|
+
def valid_timezone?
|
104
|
+
return true if TIMEZONE_NAME_MAPPING.keys.include?(timezone_name)
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
def best_guess_date
|
109
|
+
@best_guess_date ||= begin
|
110
|
+
parsed_date ||
|
111
|
+
(comment.last_commit_date &&
|
112
|
+
::DateTime.parse(comment.last_commit_date.to_s)) ||
|
113
|
+
::DateTime.now
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def local_datetime_args
|
118
|
+
[year, month, day, hour, minute]
|
119
|
+
end
|
120
|
+
|
121
|
+
def parsed_date
|
122
|
+
return unless date
|
123
|
+
|
124
|
+
::DateTime.strptime(
|
125
|
+
"#{date} #{parsed_time.hour}:#{parsed_time.minute}",
|
126
|
+
"%m/%d/%Y %H:%M"
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def parsed_time
|
131
|
+
@parsed_time ||= ::DateTime.parse(time)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'dotiw'
|
3
|
+
|
4
|
+
require_relative 'command/date_time'
|
5
|
+
|
6
|
+
module Cease
|
7
|
+
module Eviction
|
8
|
+
class Comment
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
include Comparable
|
12
|
+
include DOTIW::Methods
|
13
|
+
|
14
|
+
# @examples
|
15
|
+
# [cease] at 14:00
|
16
|
+
# [cease] at 1pm on 01/01/2044
|
17
|
+
# [cease] at 2:30pm on 12/01/2021 {timezone: 'PST'}
|
18
|
+
OPEN_COMMENT_REGEX = /
|
19
|
+
\[cease\]\s # prefix
|
20
|
+
(.+?) # non-greedy date and time
|
21
|
+
(:?\s*) # optional seperator
|
22
|
+
(\{.*?\})? # optional options
|
23
|
+
$ # extend non-greedy matchers to end of line
|
24
|
+
/x.freeze
|
25
|
+
CLOSE_COMMENT_REGEX = /\[\/cease\]/.freeze
|
26
|
+
|
27
|
+
def self.close_comment?(comment)
|
28
|
+
return false unless comment
|
29
|
+
!!(comment.text =~ CLOSE_COMMENT_REGEX)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @params comment [Parser::Source::Comment]
|
33
|
+
# @params source [Pathname]
|
34
|
+
def initialize(comment:, source: nil)
|
35
|
+
@comment = comment
|
36
|
+
@date_time, @seperator, @options = scanned_comment
|
37
|
+
@source = source
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse
|
41
|
+
scanned_comment
|
42
|
+
end
|
43
|
+
|
44
|
+
def date_time
|
45
|
+
Command::DateTime.new(self, @date_time, @options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def close_comment?
|
49
|
+
self.class.close_comment?(comment)
|
50
|
+
end
|
51
|
+
|
52
|
+
def last_commit_date
|
53
|
+
return unless source
|
54
|
+
|
55
|
+
line = loc.line
|
56
|
+
search = "-L #{line},#{line}:#{source.to_s}"
|
57
|
+
Git.log.object(search)&.first&.date
|
58
|
+
end
|
59
|
+
|
60
|
+
def past_due_description
|
61
|
+
return unless overdue?
|
62
|
+
|
63
|
+
dotiw = distance_of_time_in_words(
|
64
|
+
date_time.tz.to_local(DateTime.now),
|
65
|
+
date_time.parsed_in_timezone,
|
66
|
+
highest_measures: 1
|
67
|
+
)
|
68
|
+
|
69
|
+
"Overdue by roughly #{dotiw}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def overdue?
|
73
|
+
return false if close_comment?
|
74
|
+
return false unless date_time.valid?
|
75
|
+
|
76
|
+
date_time.tz.to_local(DateTime.now) >= date_time.parsed_in_timezone
|
77
|
+
end
|
78
|
+
|
79
|
+
def <=>(other)
|
80
|
+
other.loc.expression.begin_pos <=> loc.expression.begin_pos
|
81
|
+
end
|
82
|
+
|
83
|
+
def nested_in?(other)
|
84
|
+
other.loc.expression.end_pos > loc.expression.begin_pos
|
85
|
+
end
|
86
|
+
|
87
|
+
def valid?
|
88
|
+
return false unless comment
|
89
|
+
return true if close_comment?
|
90
|
+
parse.any?
|
91
|
+
end
|
92
|
+
|
93
|
+
attr_reader :comment, :options, :source
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
delegate loc: :comment
|
98
|
+
|
99
|
+
def scanned_comment
|
100
|
+
return [] unless comment
|
101
|
+
|
102
|
+
@scanned_comment ||=
|
103
|
+
comment.text.scan(OPEN_COMMENT_REGEX).flatten
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
|
3
|
+
require_relative 'chunk'
|
4
|
+
require_relative 'comment'
|
5
|
+
require_relative 'scope'
|
6
|
+
require_relative 'statement'
|
7
|
+
|
8
|
+
module Cease
|
9
|
+
module Eviction
|
10
|
+
class Context
|
11
|
+
class << self
|
12
|
+
# @params source [Pathname]
|
13
|
+
#
|
14
|
+
# @return [Array<Cease::Eviction>]
|
15
|
+
def from_source(source:)
|
16
|
+
source_to_chunks(source) do |chunk, statement, comments|
|
17
|
+
new(comments: comments, statement: statement, chunk: chunk)
|
18
|
+
end.compact
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def parsed_source(source)
|
24
|
+
buffer = Parser::Source::Buffer.new(source.to_s, 1)
|
25
|
+
buffer.read
|
26
|
+
|
27
|
+
Parser::CurrentRuby.new.parse_with_comments(buffer)
|
28
|
+
end
|
29
|
+
|
30
|
+
def closest_close_comment(comments, index)
|
31
|
+
# Find the next closest close command.
|
32
|
+
# Nested evictions break things.
|
33
|
+
comments[index..-1].find do |comment|
|
34
|
+
Comment.close_comment?(comment)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def source_to_chunks(source)
|
39
|
+
ast, comments = parsed_source(source)
|
40
|
+
|
41
|
+
comments.each_with_index.map do |comment, index|
|
42
|
+
next if Comment.close_comment?(comment)
|
43
|
+
|
44
|
+
statement = Statement.from_comments(
|
45
|
+
comment,
|
46
|
+
closest_close_comment(comments, index + 1),
|
47
|
+
source
|
48
|
+
)
|
49
|
+
|
50
|
+
next unless statement.valid?
|
51
|
+
|
52
|
+
chunk = Chunk.new(ast: ast, statement: statement)
|
53
|
+
yield(chunk, statement, comments) if block_given?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(chunk:, comments:, statement:)
|
59
|
+
@chunk = chunk
|
60
|
+
@comments = comments
|
61
|
+
@statement = statement
|
62
|
+
end
|
63
|
+
|
64
|
+
def description
|
65
|
+
indent = ' ' * 2
|
66
|
+
lines_output = lines.inspect
|
67
|
+
alignment = ' ' * (lines_output.length - indent.length)
|
68
|
+
result = ''
|
69
|
+
|
70
|
+
header = "#{indent}#{Rainbow(lines_output).blue}: "\
|
71
|
+
"#{Rainbow(statement.open_comment.past_due_description).indianred}\n"
|
72
|
+
|
73
|
+
scope.format.each do |line|
|
74
|
+
result << "#{alignment}#{line}\n"
|
75
|
+
end
|
76
|
+
|
77
|
+
"#{header}#{Rainbow(result).wheat}\n"
|
78
|
+
end
|
79
|
+
|
80
|
+
def lines
|
81
|
+
return [] unless statement.valid?
|
82
|
+
statement.lines
|
83
|
+
end
|
84
|
+
|
85
|
+
def overdue?
|
86
|
+
return false unless statement.valid?
|
87
|
+
statement.open_comment.overdue?
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader :comments, :statement, :chunk
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def scope
|
95
|
+
@scope ||=
|
96
|
+
Scope.new(chunk: chunk, comments: comments, statement: statement)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'rainbow'
|
4
|
+
|
5
|
+
module Cease
|
6
|
+
module Eviction
|
7
|
+
class Scope
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
# @params chunk [Cease::Eviction::Chunk]
|
11
|
+
# @params comments [Array<Parser::Source::Comment>]
|
12
|
+
# @params statement [Cease::Statement>]
|
13
|
+
def initialize(chunk:, comments:, statement:)
|
14
|
+
@chunk = chunk
|
15
|
+
@comments = comments
|
16
|
+
@statement = statement
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array<String>]
|
20
|
+
def format
|
21
|
+
length = formatted_lines.length
|
22
|
+
|
23
|
+
case length
|
24
|
+
when (0..25) then formatted_lines
|
25
|
+
else
|
26
|
+
[
|
27
|
+
formatted_lines[0..10],
|
28
|
+
formatted_lines[11] << "\n",
|
29
|
+
Rainbow("...#{line_count - 12} line(s) truncated.").yellow
|
30
|
+
].flatten
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :chunk, :comments, :statement
|
37
|
+
|
38
|
+
delegate [:open_comment, :close_comment] => :statement
|
39
|
+
|
40
|
+
# @return [Array<Parser::AST::Node, Parser::Source::Comment>]
|
41
|
+
def chunk_with_comments
|
42
|
+
extracted_chunk = chunk.extract
|
43
|
+
sorted_comments = comments.sort_by { |comment| comment.loc.line }
|
44
|
+
|
45
|
+
sorted_scope = extracted_chunk.each_with_object([]) do |ast, results|
|
46
|
+
until sorted_comments.first.loc.line > ast.loc.line do
|
47
|
+
comment = sorted_comments.shift
|
48
|
+
|
49
|
+
results << comment if comment.loc.line > open_comment.comment.loc.line
|
50
|
+
end
|
51
|
+
|
52
|
+
results << ast
|
53
|
+
end
|
54
|
+
|
55
|
+
# Concatenate any leftover comments outside AST nodes.
|
56
|
+
sorted_comments.each do |comment|
|
57
|
+
sorted_scope << comment if comment.loc.line < close_comment.comment.loc.line
|
58
|
+
end
|
59
|
+
|
60
|
+
sorted_scope
|
61
|
+
end
|
62
|
+
|
63
|
+
# @note Align indented lines relative to the expression's column.
|
64
|
+
def formatted_lines
|
65
|
+
@formatted_lines ||= chunk_with_comments.map do |content|
|
66
|
+
column = content.loc.column
|
67
|
+
|
68
|
+
# TODO: Find single line breaks between lines.
|
69
|
+
content.loc.expression.source
|
70
|
+
.split("\n")
|
71
|
+
.map do |result|
|
72
|
+
next result[column...] if result.start_with?(' ')
|
73
|
+
result
|
74
|
+
end
|
75
|
+
end.flatten
|
76
|
+
end
|
77
|
+
|
78
|
+
def line_count
|
79
|
+
statement.lines.inject(&:-).abs
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative 'comment'
|
2
|
+
|
3
|
+
module Cease
|
4
|
+
module Eviction
|
5
|
+
class Statement
|
6
|
+
def self.from_comments(*comments)
|
7
|
+
open_comment, close_comment, source = comments
|
8
|
+
|
9
|
+
new(
|
10
|
+
open_comment: open_comment,
|
11
|
+
close_comment: close_comment,
|
12
|
+
source: source
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param open_comment [Parser::Source::Comment]
|
17
|
+
# @param close_comment [Parser::Source::Comment]
|
18
|
+
# @params source [Pathname]
|
19
|
+
def initialize(open_comment:, close_comment:, source:)
|
20
|
+
@open_comment = Comment.new(comment: open_comment, source: source)
|
21
|
+
@close_comment = Comment.new(comment: close_comment, source: source)
|
22
|
+
end
|
23
|
+
|
24
|
+
def lines
|
25
|
+
return [] unless valid?
|
26
|
+
|
27
|
+
[open_comment, close_comment].map do |eviction_comment|
|
28
|
+
eviction_comment.loc&.line
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid?
|
33
|
+
[open_comment, close_comment].all?(&:valid?)
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :open_comment, :close_comment
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'summary'
|
2
|
+
require_relative 'eviction/context'
|
3
|
+
|
4
|
+
module Cease
|
5
|
+
class Examiner
|
6
|
+
# @param source [Pathname]
|
7
|
+
def initialize(source:)
|
8
|
+
@source = source
|
9
|
+
end
|
10
|
+
|
11
|
+
def evictions
|
12
|
+
@evictions ||= Eviction::Context.from_source(source: source)
|
13
|
+
end
|
14
|
+
|
15
|
+
def summarize
|
16
|
+
return unless summarizable?
|
17
|
+
Summary.new(examiner: self).summarize
|
18
|
+
end
|
19
|
+
|
20
|
+
def summarizable?
|
21
|
+
overdue_evictions.any?
|
22
|
+
end
|
23
|
+
|
24
|
+
def overdue_evictions
|
25
|
+
evictions.select(&:overdue?)
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :source
|
29
|
+
end
|
30
|
+
end
|
data/lib/cease/git.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module Cease
|
4
|
+
class Git
|
5
|
+
def self.log
|
6
|
+
new.log
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(pwd: Pathname.pwd.to_s)
|
10
|
+
@pwd = pwd
|
11
|
+
end
|
12
|
+
|
13
|
+
def log
|
14
|
+
@log ||= ::Git.open(pwd).log
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :pwd
|
20
|
+
end
|
21
|
+
end
|
data/lib/cease/report.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative 'examiner'
|
2
|
+
|
3
|
+
module Cease
|
4
|
+
class Report
|
5
|
+
SUCCESS_EXIT_CODE = 0
|
6
|
+
ERROR_EXIT_CODE = 1
|
7
|
+
|
8
|
+
def initialize(sources:)
|
9
|
+
@sources = sources
|
10
|
+
@examiners = []
|
11
|
+
@total_eviction_count = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
print_header
|
16
|
+
|
17
|
+
sources.each do |source|
|
18
|
+
add_examiner(Examiner.new(source: source))
|
19
|
+
end
|
20
|
+
|
21
|
+
print_results
|
22
|
+
print_footer if reportable?
|
23
|
+
|
24
|
+
result_code
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_accessor :total_eviction_count
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :examiners, :sources
|
32
|
+
|
33
|
+
def add_examiner(examiner)
|
34
|
+
self.total_eviction_count += examiner.overdue_evictions.length
|
35
|
+
examiners << examiner
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_results
|
39
|
+
summarizable_examiners.each(&:summarize)
|
40
|
+
end
|
41
|
+
|
42
|
+
def print_header
|
43
|
+
puts "\nScanning #{sources.length} source(s)...\n\n"
|
44
|
+
end
|
45
|
+
|
46
|
+
def print_footer
|
47
|
+
puts Rainbow(
|
48
|
+
Rainbow"\nTotal of #{total_eviction_count} evictions(s) found.\n"
|
49
|
+
).green
|
50
|
+
end
|
51
|
+
|
52
|
+
def result_code
|
53
|
+
if reportable?
|
54
|
+
ERROR_EXIT_CODE
|
55
|
+
else
|
56
|
+
SUCCESS_EXIT_CODE
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def reportable?
|
61
|
+
summarizable_examiners.any?
|
62
|
+
end
|
63
|
+
|
64
|
+
def summarizable_examiners
|
65
|
+
examiners.select(&:summarizable?)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'rainbow'
|
4
|
+
|
5
|
+
module Cease
|
6
|
+
class Summary
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
delegate [
|
10
|
+
:source,
|
11
|
+
:overdue_evictions
|
12
|
+
] => :examiner
|
13
|
+
|
14
|
+
def initialize(examiner:)
|
15
|
+
@examiner = examiner
|
16
|
+
end
|
17
|
+
|
18
|
+
def summarize
|
19
|
+
return if overdue_evictions.none?
|
20
|
+
|
21
|
+
puts Rainbow("(#{source_name})").underline.bright
|
22
|
+
puts "\n#{overdue_evictions.map(&:description).join}"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :examiner
|
28
|
+
|
29
|
+
def source_name
|
30
|
+
source.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/cease.rb
ADDED
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cease
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sung Noh
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-01-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: git
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.10.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.10.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rainbow
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.0.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: dotiw
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 5.3.2
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 5.3.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: tzinfo
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.0.4
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.0.4
|
83
|
+
description: Cease is a tool that detects blocks of code to be removed at a specified
|
84
|
+
time.
|
85
|
+
email:
|
86
|
+
- sung@dustybit.software
|
87
|
+
executables:
|
88
|
+
- cease
|
89
|
+
- console
|
90
|
+
- setup
|
91
|
+
extensions: []
|
92
|
+
extra_rdoc_files: []
|
93
|
+
files:
|
94
|
+
- ".github/workflows/ci.yml"
|
95
|
+
- ".gitignore"
|
96
|
+
- ".rspec"
|
97
|
+
- CODE_OF_CONDUCT.md
|
98
|
+
- Gemfile
|
99
|
+
- LICENSE.txt
|
100
|
+
- README.md
|
101
|
+
- Rakefile
|
102
|
+
- bin/cease
|
103
|
+
- bin/console
|
104
|
+
- bin/setup
|
105
|
+
- cease.gemspec
|
106
|
+
- lib/cease.rb
|
107
|
+
- lib/cease/cli.rb
|
108
|
+
- lib/cease/eviction/chunk.rb
|
109
|
+
- lib/cease/eviction/command/date_time.rb
|
110
|
+
- lib/cease/eviction/comment.rb
|
111
|
+
- lib/cease/eviction/context.rb
|
112
|
+
- lib/cease/eviction/scope.rb
|
113
|
+
- lib/cease/eviction/statement.rb
|
114
|
+
- lib/cease/examiner.rb
|
115
|
+
- lib/cease/git.rb
|
116
|
+
- lib/cease/report.rb
|
117
|
+
- lib/cease/summary.rb
|
118
|
+
- lib/cease/version.rb
|
119
|
+
homepage: https://github.com/DustyBitSoftware/cease
|
120
|
+
licenses:
|
121
|
+
- BSD-3-Clause
|
122
|
+
metadata:
|
123
|
+
homepage_uri: https://github.com/DustyBitSoftware/cease
|
124
|
+
source_code_uri: https://github.com/DustyBitSoftware/cease
|
125
|
+
changelog_uri: https://github.com/DustyBitSoftware/cease/CHANGELOG.md
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 2.3.0
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubygems_version: 3.2.32
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: Evict unused code at some time in the future
|
145
|
+
test_files: []
|