todoloo 0.0.0 → 0.0.2
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 +4 -4
- data/CHANGELOG +3 -0
- data/exe/todoloo +4 -0
- data/lib/todoloo/cli.rb +27 -0
- data/lib/todoloo/error_handler.rb +40 -0
- data/lib/todoloo/file_scanner.rb +50 -0
- data/lib/todoloo/helpers.rb +24 -0
- data/lib/todoloo/parser.rb +159 -0
- data/lib/todoloo/task.rb +14 -0
- data/lib/todoloo/task_list/folded_string.rb +13 -0
- data/lib/todoloo/task_list.rb +74 -0
- data/lib/todoloo/transformer.rb +28 -0
- data/lib/todoloo/version.rb +1 -1
- data/lib/todoloo.rb +21 -0
- metadata +100 -7
- data/.ruby-version +0 -1
- data/Makefile +0 -8
- data/sig/todoloo.rbs +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e10a05abef8c425b5e2e9347d1bccc9e592f5e7a03a5608f84aa5495cfdf45e
|
4
|
+
data.tar.gz: 3e0febaa61ee4a872d045d8df00f9b49e17716ae3384b52a1aad8e0efbd4ec68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aefc739a86f6120a9383f3edec5b84433498cdcfe747fa40fd72654fc61ab4925137be92402c2303e37db9ead9b632837171a58d9c1142fdc80fa664deb43387
|
7
|
+
data.tar.gz: b0862355aa8fdb6bbfc4c405f8b0f83fd84a489f930c9aca3728bbae1b9888481bde8f34c1d3ed9632934068c685d84302e3f9df995dfea209b3573ddcef3033
|
data/CHANGELOG
ADDED
data/exe/todoloo
ADDED
data/lib/todoloo/cli.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
class Todoloo::CLI < Thor
|
4
|
+
package_name "Todoloo"
|
5
|
+
|
6
|
+
desc "scan", "Scans all files that match the given globs and outputs a tasks.yml"
|
7
|
+
method_option :exclude, type: :array, aliases: "-e", desc: "List of path globs to exclude"
|
8
|
+
def scan
|
9
|
+
Todoloo::FileScanner
|
10
|
+
.new("**/*.rb", excludes: options[:exclude] || [], trace: true)
|
11
|
+
.scan
|
12
|
+
.write("tasks.yml")
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "io", "Reads input from stdio and writes to stdout"
|
16
|
+
def io
|
17
|
+
Todoloo::TaskList.new.add(
|
18
|
+
Todoloo::Parser
|
19
|
+
.new
|
20
|
+
.parse_and_transform($stdin.read)
|
21
|
+
).write($stdout)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.exit_on_failure?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Todoloo
|
2
|
+
class ErrorHandler
|
3
|
+
def initialize(kind)
|
4
|
+
@handler = new_handler_proc(kind)
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(error, original_exception: nil)
|
8
|
+
# TODO(errors): Handle `original_exception`
|
9
|
+
@handler.call(error)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def new_handler_proc(kind)
|
15
|
+
case kind
|
16
|
+
when :raise
|
17
|
+
proc { |e| raise e }
|
18
|
+
when :stderr, :log, :trace
|
19
|
+
new_handler($stderr)
|
20
|
+
when String
|
21
|
+
File.open(kind, "w")
|
22
|
+
else
|
23
|
+
if kind.respond_to?(:write)
|
24
|
+
proc do |e|
|
25
|
+
if e.is_a?(Error)
|
26
|
+
kind.puts(e.message)
|
27
|
+
kind.puts(e.backtrace) if e.backtrace && !e.backtrace.empty?
|
28
|
+
else
|
29
|
+
kind.puts(e)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
elsif kind.respond_to?(:call)
|
33
|
+
kind
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Unsupported type for ErrorHandler: #{kind}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "concurrent-ruby"
|
2
|
+
require "parallel"
|
3
|
+
|
4
|
+
# File scanner for finding todos
|
5
|
+
class Todoloo::FileScanner
|
6
|
+
def initialize(pattern, excludes: [], trace: nil)
|
7
|
+
@parsers = Concurrent::Hash.new
|
8
|
+
|
9
|
+
@pattern = pattern
|
10
|
+
|
11
|
+
@excludes = excludes
|
12
|
+
|
13
|
+
@trace_output = case trace
|
14
|
+
when nil, false
|
15
|
+
nil
|
16
|
+
when true
|
17
|
+
$stderr
|
18
|
+
when String
|
19
|
+
File.open(trace, "w")
|
20
|
+
else
|
21
|
+
raise ArgumentError, "Invalid value #{trace} for argument trace"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Todoloo::TaskList]
|
26
|
+
def scan
|
27
|
+
Todoloo::TaskList.new.add(scan_files)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def parser
|
33
|
+
(@parsers[Parallel.worker_number] ||= Todoloo::Parser.new)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Array<Array<Task>>]
|
37
|
+
def scan_files
|
38
|
+
Parallel.map(Todoloo::Helpers.glob_paths(@pattern, excludes: @excludes)) do |path|
|
39
|
+
trace { "* Scan #{path}" }
|
40
|
+
|
41
|
+
parser.parse_and_transform(File.read(path), path: path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def trace(&block)
|
46
|
+
return unless @trace_output
|
47
|
+
|
48
|
+
@trace_output.puts(block.call)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Todoloo
|
2
|
+
module Helpers
|
3
|
+
extend self
|
4
|
+
# Returns an `Enumerator` over the paths that match `pattern`
|
5
|
+
#
|
6
|
+
# TODO We need to optimize how we traverse the file-system so that we can short-circuit the excludes instead
|
7
|
+
# of only filtering after finding them.
|
8
|
+
#
|
9
|
+
# @pattern [String]
|
10
|
+
# The file paths to glob
|
11
|
+
#
|
12
|
+
# @excludes [Array<String>]
|
13
|
+
# Optional list of file globs to exclude from the enumeration
|
14
|
+
#
|
15
|
+
# @return [Enumerator<String>]
|
16
|
+
def glob_paths(pattern, excludes: [])
|
17
|
+
Enumerator.new do |y|
|
18
|
+
Dir[pattern].lazy.each do |path|
|
19
|
+
y << path unless excludes.any? { |e| File.fnmatch(e, path) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Todoloo
|
2
|
+
# Read https://kschiess.github.io/parslet/get-started.html
|
3
|
+
class Parser < Parslet::Parser
|
4
|
+
TASK_TYPES = %w[TODO NOTE FIXME HBD HACK XXX]
|
5
|
+
root :file
|
6
|
+
|
7
|
+
# Returns a list of tasks
|
8
|
+
def parse_and_transform(text, path: "")
|
9
|
+
tree = parse(text)
|
10
|
+
|
11
|
+
pp tree if Todoloo.debug?
|
12
|
+
|
13
|
+
Transformer.new.apply(tree).map do |hash|
|
14
|
+
line, column = hash.fetch(:type).line_and_column
|
15
|
+
|
16
|
+
Task.from_hash(hash.merge(path: path, line: line, column: column))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def safe_parse(text)
|
21
|
+
parse(text)
|
22
|
+
rescue Parslet::ParseFailed => e
|
23
|
+
puts e.parse_failure_cause.ascii_tree
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Extension to automatically define `?` predicate rules
|
28
|
+
def self.rule(name, *args, **kwargs, &block)
|
29
|
+
if name.to_s.end_with?("?")
|
30
|
+
super
|
31
|
+
else
|
32
|
+
super # Define the original rule
|
33
|
+
super("#{name}?") { public_send(name).maybe }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Single char rules
|
38
|
+
rule(:lparen) { str("(") }
|
39
|
+
rule(:rparen) { str(")") }
|
40
|
+
|
41
|
+
rule(:eof) { any.absent? }
|
42
|
+
|
43
|
+
rule(:eol) { str("\n") }
|
44
|
+
|
45
|
+
rule(:space) { match(/[ \t]/) }
|
46
|
+
|
47
|
+
rule(:code_text) { match(/[^#\n]/) }
|
48
|
+
|
49
|
+
# FIXME: Cannot handle JS comments yet
|
50
|
+
rule(:comment_char) { str("#") } # || str("//") }
|
51
|
+
|
52
|
+
# Can read all the way without honoring anything special,
|
53
|
+
# since its use will only be when the comment has already started
|
54
|
+
rule(:comment_text) { match(/[^\n]/) }
|
55
|
+
|
56
|
+
# Parts
|
57
|
+
rule(:spacing) { space.repeat(1) }
|
58
|
+
|
59
|
+
rule(:code) { code_text.repeat(1).as(:code) }
|
60
|
+
|
61
|
+
rule(:line) { (comment | code >> comment | code).as(:line) }
|
62
|
+
|
63
|
+
rule(:file) { (line >> eol | eol | line >> eof).repeat.as(:file) }
|
64
|
+
|
65
|
+
rule(:comment) { (comment_start >> (task.as(:task) | line).maybe).as(:comment) }
|
66
|
+
|
67
|
+
rule(:comment_start) do
|
68
|
+
comment_char.capture(:comment_start) >> spacing?
|
69
|
+
end
|
70
|
+
|
71
|
+
# examples
|
72
|
+
# TODO(topic): My task
|
73
|
+
# TODO: My task
|
74
|
+
# TODO: My task
|
75
|
+
rule(:task) { task_type.as(:type).capture(:type) >> topics.repeat(0, 1).as(:topics) >> task_separator? >> spacing? >> description.repeat(0, 1).as(:description) }
|
76
|
+
|
77
|
+
rule(:description) { multiline_description | single_line_description }
|
78
|
+
|
79
|
+
rule(:description_text) { comment_text.repeat(1) }
|
80
|
+
|
81
|
+
rule(:single_line_description) { description_text.as(:text) }
|
82
|
+
|
83
|
+
rule(:multiline_description) do
|
84
|
+
(description_text >> comment_continuation.repeat(1)).as(:text)
|
85
|
+
end
|
86
|
+
|
87
|
+
MIN_INDENTATION_BEYOND_TYPE_START = 1
|
88
|
+
def comment_continuation
|
89
|
+
dynamic do |_source, context|
|
90
|
+
parser = match('[\n]')
|
91
|
+
|
92
|
+
comment_start = column_offset_of_capture(:comment_start, context)
|
93
|
+
|
94
|
+
# Match any non comment content before the comment starts
|
95
|
+
parser = ignore_code_text(parser, comment_start)
|
96
|
+
|
97
|
+
# Consume the characther that the comment opened with, like a `#` or `//`
|
98
|
+
parser >>= str(context.captures[:comment_start]).ignore
|
99
|
+
|
100
|
+
parser >>= (spacing? >> task_type).absent?
|
101
|
+
|
102
|
+
required_indentation = column_offset_of_capture(:type, context) - comment_start - context.captures[:comment_start].length + MIN_INDENTATION_BEYOND_TYPE_START
|
103
|
+
|
104
|
+
parser = indentation(parser, required_indentation)
|
105
|
+
|
106
|
+
# parser >>= task_type.absent?
|
107
|
+
|
108
|
+
parser >>= description_text
|
109
|
+
parser
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Calculates zero-based column offset for the given capture.
|
114
|
+
# Use only within dynamic blocks
|
115
|
+
def column_offset_of_capture(name, context)
|
116
|
+
case name
|
117
|
+
when :type
|
118
|
+
# FIXME: Not sure yet why this is even necessary...
|
119
|
+
_, col_index = context.captures[:type][:type].line_and_column
|
120
|
+
else
|
121
|
+
_, col_index = context.captures[name].line_and_column
|
122
|
+
end
|
123
|
+
|
124
|
+
col_index - 1
|
125
|
+
end
|
126
|
+
|
127
|
+
def ignore_code_text(parser, count)
|
128
|
+
if count.positive?
|
129
|
+
parser >> code_text.repeat(count, count).ignore
|
130
|
+
else
|
131
|
+
parser
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def indentation(parser, count)
|
136
|
+
return parser unless count.positive?
|
137
|
+
|
138
|
+
parser >> space.repeat(count, count)
|
139
|
+
end
|
140
|
+
|
141
|
+
rule(:task_type) do
|
142
|
+
TASK_TYPES
|
143
|
+
.map { |t| str(t) }
|
144
|
+
.reduce do |result, partial_matcher|
|
145
|
+
result | partial_matcher
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
rule(:topic) { match(/[^),]/).repeat(1).as(:topic) }
|
150
|
+
|
151
|
+
rule(:topic_rest) { (str(",") >> spacing.maybe >> topic).repeat(0, 1) }
|
152
|
+
|
153
|
+
# (topic1, topic2)
|
154
|
+
rule(:topics) { lparen >> topic >> topic_rest >> rparen }
|
155
|
+
|
156
|
+
rule(:task_separator) { str(":") }
|
157
|
+
rule(:task_separator?) { task_separator.maybe }
|
158
|
+
end
|
159
|
+
end
|
data/lib/todoloo/task.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Todoloo
|
2
|
+
class Task < Struct.new(:type, :topics, :description, :path, :line, :column)
|
3
|
+
def self.from_hash(hash)
|
4
|
+
new(
|
5
|
+
hash.fetch(:type),
|
6
|
+
hash.fetch(:topics),
|
7
|
+
hash.fetch(:description),
|
8
|
+
hash.fetch(:path),
|
9
|
+
hash.fetch(:line),
|
10
|
+
hash.fetch(:column)
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "psych"
|
2
|
+
# A string that is written to YAML as a folded string by default
|
3
|
+
class Todoloo::TaskList::FoldedString
|
4
|
+
def initialize(string)
|
5
|
+
@string = string
|
6
|
+
end
|
7
|
+
|
8
|
+
def encode_with(coder)
|
9
|
+
coder.style = Psych::Nodes::Scalar::LITERAL
|
10
|
+
coder.scalar = @string
|
11
|
+
coder.tag = nil
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Todoloo
|
4
|
+
class TaskList
|
5
|
+
def initialize
|
6
|
+
@tasks = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(task)
|
10
|
+
if task.is_a?(Array)
|
11
|
+
task.each { |t| add(t) }
|
12
|
+
else
|
13
|
+
raise ArgumentError, "Task type must be Todoloo::Task: #{task.inspect}" unless task.is_a?(Todoloo::Task)
|
14
|
+
@tasks << task
|
15
|
+
end
|
16
|
+
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
# Structure:
|
21
|
+
# TOPIC:
|
22
|
+
# TYPE:
|
23
|
+
# - description:
|
24
|
+
# path:
|
25
|
+
def write(path, error: :raise)
|
26
|
+
output = YAML.dump(
|
27
|
+
converted_tasks(
|
28
|
+
error_handler: ErrorHandler.new(error)
|
29
|
+
)
|
30
|
+
)
|
31
|
+
|
32
|
+
if path.is_a?(String)
|
33
|
+
File.write(path, output)
|
34
|
+
else
|
35
|
+
path.write(output)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def topics_for_task(task)
|
42
|
+
if task.topics.empty?
|
43
|
+
[""]
|
44
|
+
else
|
45
|
+
task.topics
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def converted_tasks(error_handler)
|
50
|
+
output = {}
|
51
|
+
|
52
|
+
@tasks.each do |task|
|
53
|
+
output_task = {
|
54
|
+
"description" => FoldedString.new(task.description.to_s),
|
55
|
+
"path" => "#{task.path}:#{task.line}:#{task.column}"
|
56
|
+
}
|
57
|
+
|
58
|
+
topics_for_task(task).each do |topic|
|
59
|
+
by_type = output[topic.to_s] ||= {}
|
60
|
+
tasks = by_type[task.type.to_s.downcase] ||= []
|
61
|
+
tasks << output_task.dup
|
62
|
+
end
|
63
|
+
rescue Error => e
|
64
|
+
if task.respond_to?(:line) && task.respond_to?(:column)
|
65
|
+
error_handler.call("#{path}:#{task.line}:#{task.column}: Error: #{e}", original_exception: e)
|
66
|
+
else
|
67
|
+
error_handler.call("#{path}:??:??:Error: #{e}", original_exception: e)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
output
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Todoloo
|
2
|
+
class Transformer < Parslet::Transform
|
3
|
+
rule(file: subtree(:x)) { x.compact }
|
4
|
+
|
5
|
+
rule(lines: subtree(:x)) { x }
|
6
|
+
|
7
|
+
rule(line: {code: subtree(:x)}) { nil }
|
8
|
+
|
9
|
+
rule(line: {code: subtree(:x), comment: subtree(:y)}) { y }
|
10
|
+
|
11
|
+
# Discard lines with only code and comments without tasks
|
12
|
+
rule(line: {code: subtree(:x), comment: simple(:y)}) { nil }
|
13
|
+
|
14
|
+
rule(line: {comment: subtree(:x)}) { x }
|
15
|
+
|
16
|
+
rule(line: {comment: simple(:x)}) { nil }
|
17
|
+
|
18
|
+
rule(type: simple(:type), topics: sequence(:topics), description: sequence(:description)) do
|
19
|
+
{type: type, topics: topics, description: description.empty? ? "" : (raise "must be empty")}
|
20
|
+
end
|
21
|
+
|
22
|
+
rule(topic: simple(:x)) { x }
|
23
|
+
|
24
|
+
rule(task: subtree(:task)) { task }
|
25
|
+
|
26
|
+
rule([{text: simple(:x)}]) { x }
|
27
|
+
end
|
28
|
+
end
|
data/lib/todoloo/version.rb
CHANGED
data/lib/todoloo.rb
CHANGED
@@ -1,7 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "todoloo/version"
|
4
|
+
|
5
|
+
require "parslet"
|
6
|
+
|
7
|
+
require "zeitwerk"
|
8
|
+
|
9
|
+
loader = Zeitwerk::Loader.for_gem
|
10
|
+
|
11
|
+
loader.inflector.inflect "cli" => "CLI"
|
12
|
+
|
13
|
+
loader.setup # ready!
|
14
|
+
|
4
15
|
module Todoloo
|
5
16
|
class Error < StandardError; end
|
6
17
|
# Your code goes here...
|
18
|
+
|
19
|
+
class << self
|
20
|
+
attr_accessor :debug
|
21
|
+
|
22
|
+
def debug?
|
23
|
+
@debug
|
24
|
+
end
|
25
|
+
end
|
7
26
|
end
|
27
|
+
|
28
|
+
Todoloo.debug = false
|
metadata
CHANGED
@@ -1,31 +1,124 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: todoloo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Coetzee
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
12
|
-
dependencies:
|
11
|
+
date: 2024-07-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: concurrent-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.3.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.3.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: parallel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.25.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.25.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: parslet
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.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: 2.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: psych
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 5.1.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.1.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thor
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.3.1
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.3.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: zeitwerk
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
13
97
|
description: A tool for managing and saying bye to todos
|
14
98
|
email:
|
15
99
|
- chriscz93@gmail.com
|
16
|
-
executables:
|
100
|
+
executables:
|
101
|
+
- todoloo
|
17
102
|
extensions: []
|
18
103
|
extra_rdoc_files: []
|
19
104
|
files:
|
20
|
-
-
|
105
|
+
- CHANGELOG
|
21
106
|
- CODE_OF_CONDUCT.md
|
22
107
|
- LICENSE.txt
|
23
|
-
- Makefile
|
24
108
|
- README.md
|
25
109
|
- Rakefile
|
110
|
+
- exe/todoloo
|
26
111
|
- lib/todoloo.rb
|
112
|
+
- lib/todoloo/cli.rb
|
113
|
+
- lib/todoloo/error_handler.rb
|
114
|
+
- lib/todoloo/file_scanner.rb
|
115
|
+
- lib/todoloo/helpers.rb
|
116
|
+
- lib/todoloo/parser.rb
|
117
|
+
- lib/todoloo/task.rb
|
118
|
+
- lib/todoloo/task_list.rb
|
119
|
+
- lib/todoloo/task_list/folded_string.rb
|
120
|
+
- lib/todoloo/transformer.rb
|
27
121
|
- lib/todoloo/version.rb
|
28
|
-
- sig/todoloo.rbs
|
29
122
|
homepage: https://github.com/chriscz/todoloo
|
30
123
|
licenses:
|
31
124
|
- MIT
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
3.2.2
|
data/Makefile
DELETED
data/sig/todoloo.rbs
DELETED