<commit_before># frozen_string_literal: true
# rubocop:disable Metrics/ClassLength
require 'set'
class Project < ActiveRecord::Base
  STATUS_CHOICE = %w(? Met Unmet).freeze
  STATUS_CHOICE_NA = (STATUS_CHOICE + %w(N/A)).freeze
  MIN_SHOULD_LENGTH = 5
  MAX_TEXT_LENGTH = 8192 # Arbitrary maximum to reduce abuse
  MAX_SHORT_STRING_LENGTH = 254 # Arbitrary maximum to reduce abuse

  # The "Criteria" hash is loaded during application initialization
  # from a YAML file.

  ALL_CRITERIA = Criteria.keys.to_set.freeze
  ALL_CRITERIA_STATUS = ALL_CRITERIA.map do |criterion|
    "#{criterion}_status".to_sym
  end.to_set.freeze
  ALL_CRITERIA_JUSTIFICATION = ALL_CRITERIA.map do |criterion|
    "#{criterion}_justification".to_sym
  end.to_set.freeze
  PROJECT_OTHER_FIELDS = Set.new(
    [:name, :description, :project_homepage_url, :repo_url, :cpe,
     :license, :general_comments,
     :user_id]).freeze
  PROJECT_PERMITTED_FIELDS = (PROJECT_OTHER_FIELDS +
    ALL_CRITERIA_STATUS + ALL_CRITERIA_JUSTIFICATION).to_set.freeze
  PROJECT_PERMITTED_FIELDS_ARRAY = PROJECT_PERMITTED_FIELDS.to_a.freeze

  # A project is associated with a user
  belongs_to :user
  delegate :name, to: :user, prefix: true

  default_scope { order(:created_at) }

  # Record information about a project.
  # We'll also record previous versions of information:
  has_paper_trail

  # For these fields we'll have just simple validation rules.
  # We'll rely on Rails' HTML escaping system to counter XSS.
  validates :name, length: { maximum: MAX_SHORT_STRING_LENGTH }
  validates :description, length: { maximum: MAX_TEXT_LENGTH }
  validates :license, length: { maximum: MAX_SHORT_STRING_LENGTH }

  # We'll do automated analysis on these URLs, which means we will *download*
  # from URLs provided by untrusted users.  Thus we'll add additional
  # URL restrictions to counter tricks like http://ACCOUNT:PASSWORD@host...
  # and http://something/?arbitrary_parameters

  validates :repo_url, url: true, length: { maximum: MAX_SHORT_STRING_LENGTH },
                       uniqueness: { allow_blank: true }
  validates :project_homepage_url,
            url: true,
            length: { maximum: MAX_SHORT_STRING_LENGTH }
  validate :need_a_base_url

  validates :cpe,
            length: { maximum: MAX_SHORT_STRING_LENGTH },
            format: { with: /\A(cpe:.*)?\Z/, message: 'Must begin with cpe:' }

  validates :user_id, presence: true

  # Validate all of the criteria-related inputs
  ALL_CRITERIA.each do |criterion|
    # validates column, allow_blank: true, length: { maximum: 25 }
    status = "#{criterion}_status".to_sym
    if Criteria[criterion.to_s]['na_allowed']
      validates status, inclusion: { in: STATUS_CHOICE_NA }
    else
      validates status, inclusion: { in: STATUS_CHOICE }
    end
    justification = "#{criterion}_justification".to_sym
    validates justification, length: { maximum: MAX_TEXT_LENGTH }
  end

  # TODO: Remove these Criteria queries from the project model

  # Is this criterion in the category MUST, SHOULD, or SUGGESTED?
  def self.criterion_category(criterion)
    (Criteria[criterion.to_s])[:category]
  end

  # Is na allowed?
  def self.na_allowed?(criterion)
    (Criteria[criterion.to_s])[:na_allowed]
  end

  # Is a URL required in the justification to be enough with met?
  def self.met_url_required?(criterion)
    (Criteria[criterion.to_s])[:met_url_required]
  end

  # TODO: Should be normal method.
  # Return badge level of the given project.
  # If every non-future criterion is enough, it's passing.
  # Otherwise, if there are no '?' statuses, it's failing.
  # Otherwise, it's in_progress.
  # We have to disable Style/Next; rubocop gets confused here.
  # rubocop:disable Metrics/MethodLength, Style/Next
  def self.badge_level(project)
    badge_level = 'passing'
    ALL_CRITERIA.each do |criterion|
      status = project["#{criterion}_status"]
      if status == '?'
        badge_level = 'in_progress'
        break
      end
      justification = project["#{criterion}_justification"]
      unless enough_criterion? status, justification,
                               Criteria[criterion.to_s][:category],
                               Criteria[criterion.to_s][:met_url_required]
        badge_level = 'failing'
      end
    end
    badge_level
  end
  # rubocop:enable Metrics/MethodLength, Style/Next

  def self.to_percentage(portion, total)
    if portion == total
      100
    elsif portion == 0
      0
    else
      ((portion * 100.0) / total).round
    end
  end

  # TODO: Should be normal method.
  def self.badge_percentage(project)
    met = ALL_CRITERIA.count do |criterion|
      status = project["#{criterion}_status"]<commit_msg>To determine badge level, ignore '?' values in future criteria<commit_after># frozen_string_literal: true
# rubocop:disable Metrics/ClassLength
require 'set'
class Project < ActiveRecord::Base
  STATUS_CHOICE = %w(? Met Unmet).freeze
  STATUS_CHOICE_NA = (STATUS_CHOICE + %w(N/A)).freeze
  MIN_SHOULD_LENGTH = 5
  MAX_TEXT_LENGTH = 8192 # Arbitrary maximum to reduce abuse
  MAX_SHORT_STRING_LENGTH = 254 # Arbitrary maximum to reduce abuse

  # The "Criteria" hash is loaded during application initialization
  # from a YAML file.

  ALL_CRITERIA = Criteria.keys.to_set.freeze
  ALL_ACTIVE_CRITERIA = ALL_CRITERIA.select do |criterion|
    Criteria[criterion] != 'FUTURE'
  end.to_set.freeze
  ALL_CRITERIA_STATUS = ALL_CRITERIA.map do |criterion|
    "#{criterion}_status".to_sym
  end.to_set.freeze
  ALL_CRITERIA_JUSTIFICATION = ALL_CRITERIA.map do |criterion|
    "#{criterion}_justification".to_sym
  end.to_set.freeze
  PROJECT_OTHER_FIELDS = Set.new(
    [:name, :description, :project_homepage_url, :repo_url, :cpe,
     :license, :general_comments,
     :user_id]).freeze
  PROJECT_PERMITTED_FIELDS = (PROJECT_OTHER_FIELDS +
    ALL_CRITERIA_STATUS + ALL_CRITERIA_JUSTIFICATION).to_set.freeze
  PROJECT_PERMITTED_FIELDS_ARRAY = PROJECT_PERMITTED_FIELDS.to_a.freeze

  # A project is associated with a user
  belongs_to :user
  delegate :name, to: :user, prefix: true

  default_scope { order(:created_at) }

  # Record information about a project.
  # We'll also record previous versions of information:
  has_paper_trail

  # For these fields we'll have just simple validation rules.
  # We'll rely on Rails' HTML escaping system to counter XSS.
  validates :name, length: { maximum: MAX_SHORT_STRING_LENGTH }
  validates :description, length: { maximum: MAX_TEXT_LENGTH }
  validates :license, length: { maximum: MAX_SHORT_STRING_LENGTH }

  # We'll do automated analysis on these URLs, which means we will *download*
  # from URLs provided by untrusted users.  Thus we'll add additional
  # URL restrictions to counter tricks like http://ACCOUNT:PASSWORD@host...
  # and http://something/?arbitrary_parameters

  validates :repo_url, url: true, length: { maximum: MAX_SHORT_STRING_LENGTH },
                       uniqueness: { allow_blank: true }
  validates :project_homepage_url,
            url: true,
            length: { maximum: MAX_SHORT_STRING_LENGTH }
  validate :need_a_base_url

  validates :cpe,
            length: { maximum: MAX_SHORT_STRING_LENGTH },
            format: { with: /\A(cpe:.*)?\Z/, message: 'Must begin with cpe:' }

  validates :user_id, presence: true

  # Validate all of the criteria-related inputs
  ALL_CRITERIA.each do |criterion|
    # validates column, allow_blank: true, length: { maximum: 25 }
    status = "#{criterion}_status".to_sym
    if Criteria[criterion.to_s]['na_allowed']
      validates status, inclusion: { in: STATUS_CHOICE_NA }
    else
      validates status, inclusion: { in: STATUS_CHOICE }
    end
    justification = "#{criterion}_justification".to_sym
    validates justification, length: { maximum: MAX_TEXT_LENGTH }
  end

  # TODO: Remove these Criteria queries from the project model

  # Is this criterion in the category MUST, SHOULD, or SUGGESTED?
  def self.criterion_category(criterion)
    (Criteria[criterion.to_s])[:category]
  end

  # Is na allowed?
  def self.na_allowed?(criterion)
    (Criteria[criterion.to_s])[:na_allowed]
  end

  # Is a URL required in the justification to be enough with met?
  def self.met_url_required?(criterion)
    (Criteria[criterion.to_s])[:met_url_required]
  end

  # TODO: Should be normal method.
  # Return badge level of the given project.
  # If every non-future criterion is enough, it's passing.
  # Otherwise, if there are no '?' statuses, it's failing.
  # Otherwise, it's in_progress.
  # We have to disable Style/Next; rubocop gets confused here.
  # rubocop:disable Metrics/MethodLength, Style/Next
  def self.badge_level(project)
    badge_level = 'passing'
    ALL_ACTIVE_CRITERIA.each do |criterion|
      status = project["#{criterion}_status"]
      if status == '?'
        badge_level = 'in_progress'
        break
      end
      justification = project["#{criterion}_justification"]
      unless enough_criterion? status, justification,
                               Criteria[criterion.to_s][:category],
                               Criteria[criterion.to_s][:met_url_required]
        badge_level = 'failing'
      end
    end
    badge_level
  end
  # rubocop:enable Metrics/MethodLength, Style/Next

  def self.to_percentage(portion, total)
    if portion == total
      100
    elsif portion == 0
      0
    else
      ((portion * 100.0) / total).round
    end
  end

  # TODO: Should be normal method.
  def self.badge_percentage(project)
    met = ALL_CRITERIA.count do |criterion|
      status = project["#{criterion}_status"]
{'fraction_non_alphanumeric': 0.09156324955678381, 'fraction_numerical': 0.0039628741266033995, 'mean_word_length': 3.584130019120459, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6775114', 'n_tokens_mistral': 3170, 'n_tokens_neox': 3014, 'n_words': 808}