Class: Gergich::Draft

Inherits:
Object
  • Object
show all
Defined in:
lib/gergich.rb

Constant Summary collapse

SEVERITY_MAP =
{
  "info" => 0,
  "warn" => -1,
  "error" => -2
}.freeze
POSITION_KEYS =
%w[end_character end_line start_character start_line].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(commit = Commit.new) ⇒ Draft

Returns a new instance of Draft.



369
370
371
# File 'lib/gergich.rb', line 369

def initialize(commit = Commit.new)
  @commit = commit
end

Instance Attribute Details

#commitObject (readonly)

Returns the value of attribute commit.



367
368
369
# File 'lib/gergich.rb', line 367

def commit
  @commit
end

Instance Method Details

#add_comment(path, position, message, severity) ⇒ Object

Public: add an inline comment to the draft

path - the relative file path, e.g. “app/models/user.rb” position - either a Fixnum (line number) or a Hash (range). If a

Hash, must have the following Fixnum properties:
  * start_line
  * start_character
  * end_line
  * end_character

message - the text of the comment severity - “info”|“warn”|“error” - this will automatically prefix

the comment (e.g. "[ERROR] message here"), and the most
severe comment will be used to determine the overall
Code-Review score (0, -1, or -2 respectively)

Raises:



456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/gergich.rb', line 456

def add_comment(path, position, message, severity)
  stripped_path = path.strip

  raise GergichError, "invalid position `#{position}`" unless valid_position?(position)

  position = position.to_json if position.is_a?(Hash)
  raise GergichError, "invalid severity `#{severity}`" unless SEVERITY_MAP.key?(severity)
  raise GergichError, "no message specified" unless message.is_a?(String) && !message.empty?

  db.execute "INSERT INTO comments (path, position, message, severity) VALUES (?, ?, ?, ?)",
             [stripped_path, position, message, severity]
end

#add_label(name, score) ⇒ Object

Public: add a label to the draft

name - the label name, e.g. “Code-Review” score - the score, e.g. “-1”

You can set add the same label multiple times, but the lowest score for a given label will be used. This also applies to the inferred “Code-Review” score from comments; if it is non-zero, it will trump a higher score set here.

Raises:



425
426
427
428
429
430
431
432
# File 'lib/gergich.rb', line 425

def add_label(name, score)
  score = score.to_i
  raise GergichError, "invalid score" if score < -2 || score > 1
  raise GergichError, "can't set #{name}" if %w[Verified].include?(name)

  db.execute "INSERT INTO labels (name, score) VALUES (?, ?)",
             [name, score]
end

#add_message(message) ⇒ Object

Public: add something to the cover message

These messages will appear after the “-1” (or whatever)



437
438
439
# File 'lib/gergich.rb', line 437

def add_message(message)
  db.execute "INSERT INTO messages (message) VALUES (?)", [message]
end

#all_commentsObject



491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/gergich.rb', line 491

def all_comments
  @all_comments ||= begin
    comments = {}

    sql = "SELECT path, position, message, severity FROM comments"
    db.execute(sql).each do |row|
      inline = changed_files.include?(row["path"])
      comments[row["path"]] ||= FileReview.new(row["path"], inline)
      comments[row["path"]].add_comment(row["position"],
                                        row["message"],
                                        row["severity"])
    end

    comments.values
  end
end

#changed_filesObject



520
521
522
# File 'lib/gergich.rb', line 520

def changed_files
  @changed_files ||= commit.files + ["/COMMIT_MSG"]
end

#cover_message_partsObject



560
561
562
563
564
# File 'lib/gergich.rb', line 560

def cover_message_parts
  parts = messages
  parts << orphaned_message unless other_comments.empty?
  parts
end

#create_db_schema!Object



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/gergich.rb', line 394

def create_db_schema!
  db.execute <<-SQL
    CREATE TABLE comments (
      path VARCHAR,
      position VARCHAR,
      message VARCHAR,
      severity VARCHAR
    );
  SQL
  db.execute <<-SQL
    CREATE TABLE labels (
      name VARCHAR,
      score INTEGER
    );
  SQL
  db.execute <<-SQL
    CREATE TABLE messages (
      message VARCHAR
    );
  SQL
end

#dbObject



379
380
381
382
383
384
385
386
387
388
# File 'lib/gergich.rb', line 379

def db
  @db ||= begin
    require "sqlite3"
    db_exists = File.exist?(db_file)
    db = SQLite3::Database.new(db_file)
    db.results_as_hash = true
    create_db_schema! unless db_exists
    db
  end
end

#db_fileObject



373
374
375
376
377
# File 'lib/gergich.rb', line 373

def db_file
  @db_file ||= File.expand_path(
    "#{ENV.fetch('GERGICH_DB_PATH', '/tmp')}/#{GERGICH_USER}-#{commit.revision_id}.sqlite3"
  )
end

#infoObject



524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/gergich.rb', line 524

def info
  @info ||= begin
    comments = inline_comments.map { |file| [file.path, file.to_a] }.to_h

    {
      comments: comments,
      cover_message_parts: cover_message_parts,
      total_comments: all_comments.map(&:count).inject(&:+),
      score: labels[GERGICH_REVIEW_LABEL],
      labels: labels
    }
  end
end

#inline_commentsObject



508
509
510
# File 'lib/gergich.rb', line 508

def inline_comments
  all_comments.select(&:inline)
end

#labelsObject



479
480
481
482
483
484
485
486
487
488
489
# File 'lib/gergich.rb', line 479

def labels
  @labels ||= begin
    labels = { GERGICH_REVIEW_LABEL => 0 }
    db.execute("SELECT name, MIN(score) AS score FROM labels GROUP BY name").each do |row|
      labels[row["name"]] = row["score"]
    end
    score = min_comment_score
    labels[GERGICH_REVIEW_LABEL] = score if score < [0, labels[GERGICH_REVIEW_LABEL]].min
    labels
  end
end

#messagesObject



538
539
540
# File 'lib/gergich.rb', line 538

def messages
  db.execute("SELECT message FROM messages").map { |row| row["message"] }
end

#min_comment_scoreObject



516
517
518
# File 'lib/gergich.rb', line 516

def min_comment_score
  all_comments.inject(0) { |acc, elem| [acc, elem.min_score].min }
end

#orphaned_messageObject



542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
# File 'lib/gergich.rb', line 542

def orphaned_message
  messages = ["NOTE: I couldn't create inline comments for everything. " \
              "Although this isn't technically part of your commit, you " \
              "should still check it out (i.e. side effects or auto-" \
              "generated from stuff you *did* change):"]

  other_comments.each do |file|
    file.comments.each do |position, comments|
      comments.each do |comment|
        line = position.is_a?(Integer) ? position : position["start_line"]
        messages << "#{file.path}:#{line}: #{comment}"
      end
    end
  end

  messages.join("\n\n")
end

#other_commentsObject



512
513
514
# File 'lib/gergich.rb', line 512

def other_comments
  all_comments.reject(&:inline)
end

#reset!Object



390
391
392
# File 'lib/gergich.rb', line 390

def reset!
  FileUtils.rm_f(db_file)
end

#valid_position?(position) ⇒ Boolean

Returns:

  • (Boolean)


470
471
472
473
474
475
476
477
# File 'lib/gergich.rb', line 470

def valid_position?(position)
  (
    position.is_a?(Integer) && position >= 0
  ) || (
    position.is_a?(Hash) && position.keys.map(&:to_s).sort == POSITION_KEYS &&
    position.values.all? { |v| v.is_a?(Integer) && v >= 0 }
  )
end