Skip to content

Commit

Permalink
Speed
Browse files Browse the repository at this point in the history
  • Loading branch information
kddnewton committed Oct 27, 2023
1 parent e328ecb commit aaf5d29
Show file tree
Hide file tree
Showing 31 changed files with 541 additions and 573 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ gemspec
group :rubocop do
gem "rubocop-shopify", require: false
end

gem "prism", github: "ruby/prism"
9 changes: 8 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
GIT
remote: https://github.com/ruby/prism
revision: 4ff8fe2f0f72ebdb52706b4898ccf20083fc92a3
specs:
prism (0.15.1)

PATH
remote: .
specs:
smart_todo (1.6.0)
rexml
prism

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -52,6 +58,7 @@ PLATFORMS
DEPENDENCIES
bundler (>= 1.17)
minitest (~> 5.0)
prism!
rake (>= 10.0)
rubocop-shopify
smart_todo!
Expand Down
16 changes: 16 additions & 0 deletions bin/profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/setup"
require "smart_todo"

class NullDispatcher < SmartTodo::Dispatchers::Base
class << self
def validate_options!(_); end
end
def dispatch
end
end
exit SmartTodo::CLI.new(NullDispatcher).run
11 changes: 4 additions & 7 deletions lib/smart_todo.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
# frozen_string_literal: true

require "prism"
require "smart_todo/version"
require "smart_todo/events"

module SmartTodo
autoload :SlackClient, "smart_todo/slack_client"
autoload :CLI, "smart_todo/cli"
autoload :Todo, "smart_todo/todo"
autoload :CommentParser, "smart_todo/comment_parser"

module Parser
autoload :CommentParser, "smart_todo/parser/comment_parser"
autoload :TodoNode, "smart_todo/parser/todo_node"
autoload :MetadataParser, "smart_todo/parser/metadata_parser"
end

module Events
class Events
autoload :Date, "smart_todo/events/date"
autoload :GemBump, "smart_todo/events/gem_bump"
autoload :GemRelease, "smart_todo/events/gem_release"
Expand Down
68 changes: 53 additions & 15 deletions lib/smart_todo/cli.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
# frozen_string_literal: true

require "optionparser"
require "etc"

module SmartTodo
# This class is the entrypoint of the SmartTodo library and is responsible
# to retrieve the command line options as well as iterating over each files/directories
# to run the +CommentParser+ on.
class CLI
def initialize
def initialize(dispatcher = nil)
@options = {}
@errors = []
@dispatcher = dispatcher
end

# @param args [Array<String>]
Expand All @@ -19,15 +21,22 @@ def run(args = ARGV)

paths << "." if paths.empty?

comment_parser = CommentParser.new
paths.each do |path|
normalize_path(path).each do |file|
parse_file(file)

$stdout.print(".")
$stdout.flush
normalize_path(path).each do |filepath|
comment_parser.parse_file(filepath)

# Don't print anything out in CI, as we want to go as fast as possible
# and flushing stdout on every file can get quite costly.
unless ENV["CI"]
$stdout.print(".")
$stdout.flush
end
end
end

process_dispatches(process_todos(comment_parser.todos))

if @errors.empty?
0
else
Expand Down Expand Up @@ -79,25 +88,54 @@ def normalize_path(path)
end
end

# @param file [String] a path to a file
def parse_file(file)
Parser::CommentParser.new(File.read(file, encoding: "UTF-8")).parse.each do |todo_node|
def process_todos(todos)
events = Events.new
dispatches = []

todos.each do |todo|
event_message = nil
event_met = todo_node.metadata.events.find do |event|
event_message = Events.public_send(event.method_name, *event.arguments)
event_met = todo.events.find do |event|
event_message = events.public_send(event.method_name, *event.arguments)
rescue => e
message = "Error while parsing #{file} on event `#{event.method_name}` with arguments #{event.arguments}: " \
message = "Error while parsing #{todo.filepath} on event `#{event.method_name}` " \
"with arguments #{event.arguments.map(&:inspect)}: " \
"#{e.message}"

@errors << message

nil
end

@errors.concat(todo_node.metadata.errors)

dispatcher.new(event_message, todo_node, file, @options).dispatch if event_met
@errors.concat(todo.errors)
dispatches << [event_message, todo] if event_met
end

dispatches
end

def process_dispatches(dispatches)
queue = Queue.new
dispatches.each { |dispatch| queue << dispatch }

thread_count = Etc.nprocessors
thread_count.times { queue << nil }

threads =
thread_count.times.map do
Thread.new do
Thread.current.abort_on_exception = true

loop do
dispatch = queue.pop
break if dispatch.nil?

(event_message, todo) = dispatch
dispatcher.new(event_message, todo, todo.filepath, @options).dispatch
end
end
end

threads.each(&:join)
end
end
end
49 changes: 49 additions & 0 deletions lib/smart_todo/comment_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

module SmartTodo
class CommentParser
attr_reader :todos

def initialize
@todos = []
end

def parse(source, filepath = "-e")
parse_comments(Prism.parse_inline_comments(source, filepath), filepath)
end

def parse_file(filepath)
parse_comments(Prism.parse_file_inline_comments(filepath), filepath)
end

class << self
def parse(source)
parser = new
parser.parse(source)
parser.todos
end
end

private

def parse_comments(comments, filepath)
current_todo = nil

comments.each do |comment|
source = comment.location.slice

if source.match?(/^#\sTODO\(/)
todos << current_todo if current_todo
current_todo = Todo.new(source, filepath)
elsif current_todo && (indent = source[/^#(\s*)/, 1].length) && (indent - current_todo.indent == 2)
current_todo << "#{source[(indent + 1)..]}\n"
else
todos << current_todo if current_todo
current_todo = nil
end
end

todos << current_todo if current_todo
end
end
end
2 changes: 1 addition & 1 deletion lib/smart_todo/dispatchers/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def initialize(event_message, todo_node, file, options)
@todo_node = todo_node
@options = options
@file = file
@assignees = @todo_node.metadata.assignees
@assignees = @todo_node.assignees
end

# This method gets called when a TODO reminder is expired and needs to be delivered.
Expand Down
56 changes: 47 additions & 9 deletions lib/smart_todo/events.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# frozen_string_literal: true

gem("bundler")
require "bundler"
require "net/http"
require "time"
require "json"

module SmartTodo
# This module contains all the methods accessible for SmartTodo comments.
# It is meant to be reopened by the host application in order to define
Expand All @@ -10,23 +16,29 @@ module SmartTodo
#
# @example Adding a custom event
# module SmartTodo
# module Events
# class Events
# def trello_card_close(card)
# ...
# end
# end
# end
#
# TODO(on: trello_card_close(381), to: 'john@example.com')
module Events
extend self
class Events
def initialize
@now = nil
@spec_set = nil
@rubygems_client = nil
@github_client = nil
@installed_ruby_version = nil
end

# Check if the +date+ is in the past
#
# @param date [String] a correctly formatted date
# @return [false, String]
def date(date)
Date.met?(date)
Date.met?(date, now)
end

# Check if a new version of +gem_name+ was released with the +requirements+ expected
Expand All @@ -35,7 +47,7 @@ def date(date)
# @param requirements [Array<String>] a list of version specifiers
# @return [false, String]
def gem_release(gem_name, *requirements)
GemRelease.new(gem_name, requirements).met?
GemRelease.new(gem_name, requirements, rubygems_client).met?
end

# Check if +gem_name+ was bumped to the +requirements+ expected
Expand All @@ -44,7 +56,7 @@ def gem_release(gem_name, *requirements)
# @param requirements [Array<String>] a list of version specifiers
# @return [false, String]
def gem_bump(gem_name, *requirements)
GemBump.new(gem_name, requirements).met?
GemBump.new(gem_name, requirements, spec_set).met?
end

# Check if the issue +issue_number+ is closed
Expand All @@ -54,7 +66,7 @@ def gem_bump(gem_name, *requirements)
# @param issue_number [String, Integer]
# @return [false, String]
def issue_close(organization, repo, issue_number)
IssueClose.new(organization, repo, issue_number, type: "issues").met?
IssueClose.new(organization, repo, issue_number, github_client, type: "issues").met?
end

# Check if the pull request +pr_number+ is closed
Expand All @@ -64,15 +76,41 @@ def issue_close(organization, repo, issue_number)
# @param pr_number [String, Integer]
# @return [false, String]
def pull_request_close(organization, repo, pr_number)
IssueClose.new(organization, repo, pr_number, type: "pulls").met?
IssueClose.new(organization, repo, pr_number, github_client, type: "pulls").met?
end

# Check if the installed ruby version meets requirements.
#
# @param requirements [Array<String>] a list of version specifiers
# @return [false, String]
def ruby_version(*requirements)
RubyVersion.new(requirements).met?
RubyVersion.new(requirements, installed_ruby_version).met?
end

private

def now
@now ||= Time.now
end

def spec_set
@spec_set ||= Bundler.load.specs
end

def rubygems_client
@rubygems_client ||= Net::HTTP.new("rubygems.org", Net::HTTP.https_default_port).tap do |client|
client.use_ssl = true
end
end

def github_client
@github_client ||= Net::HTTP.new("api.github.com", Net::HTTP.https_default_port).tap do |client|
client.use_ssl = true
end
end

def installed_ruby_version
@installed_ruby_version ||= Gem::Version.new(RUBY_VERSION)
end
end
end
6 changes: 3 additions & 3 deletions lib/smart_todo/events/date.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
require "time"

module SmartTodo
module Events
class Events
# An event that check if the passed date is passed
class Date
class << self
# @param on_date [String] a string parsable by Time.parse
# @return [String, false]
def met?(on_date)
if Time.now >= Time.parse(on_date)
def met?(on_date, now = Time.now)
if now >= Time.parse(on_date)
message(on_date)
else
false
Expand Down
Loading

0 comments on commit aaf5d29

Please sign in to comment.