at_coder_friends 0.5.2 → 0.6.0
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/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +1 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/config/default.yml +3 -0
- data/docs/CONFIGURATION.md +74 -9
- data/lib/at_coder_friends.rb +10 -5
- data/lib/at_coder_friends/cli.rb +2 -5
- data/lib/at_coder_friends/context.rb +4 -0
- data/lib/at_coder_friends/emitter.rb +2 -2
- data/lib/at_coder_friends/generator/cxx_builtin.rb +191 -0
- data/lib/at_coder_friends/generator/main.rb +53 -0
- data/lib/at_coder_friends/generator/ruby_builtin.rb +128 -0
- data/lib/at_coder_friends/parser/binary.rb +39 -0
- data/lib/at_coder_friends/parser/constraints.rb +36 -0
- data/lib/at_coder_friends/parser/{format_parser.rb → input_format.rb} +42 -30
- data/lib/at_coder_friends/parser/interactive.rb +29 -0
- data/lib/at_coder_friends/parser/main.rb +6 -3
- data/lib/at_coder_friends/parser/sample_data.rb +24 -0
- data/lib/at_coder_friends/parser/section_wrapper.rb +49 -0
- data/lib/at_coder_friends/parser/{page_parser.rb → sections.rb} +44 -50
- data/lib/at_coder_friends/problem.rb +40 -24
- data/lib/at_coder_friends/scraping/agent.rb +1 -5
- data/lib/at_coder_friends/scraping/authentication.rb +2 -2
- data/lib/at_coder_friends/scraping/session.rb +1 -1
- data/lib/at_coder_friends/scraping/tasks.rb +2 -6
- data/lib/at_coder_friends/test_runner/base.rb +36 -31
- data/lib/at_coder_friends/test_runner/judge.rb +2 -6
- data/lib/at_coder_friends/test_runner/sample.rb +8 -6
- data/lib/at_coder_friends/version.rb +1 -1
- data/tasks/regression/check_diff.rake +29 -0
- data/tasks/regression/check_parse.rake +56 -0
- data/tasks/regression/regression.rb +67 -0
- data/tasks/regression/section_list.rake +41 -0
- data/tasks/regression/setup.rake +48 -0
- data/templates/cxx_builtin_default.cxx +26 -0
- data/templates/cxx_builtin_interactive.cxx +61 -0
- data/templates/ruby_builtin_default.rb +5 -0
- data/templates/ruby_builtin_interactive.rb +32 -0
- metadata +21 -8
- data/lib/at_coder_friends/cxx_generator.rb +0 -169
- data/lib/at_coder_friends/parser/constraints_parser.rb +0 -26
- data/lib/at_coder_friends/ruby_generator.rb +0 -97
- data/tasks/regression.rake +0 -163
| @@ -1,45 +1,61 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module AtCoderFriends
         | 
| 4 | 
            -
               | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 4 | 
            +
              # holds problem information
         | 
| 5 | 
            +
              class Problem
         | 
| 6 | 
            +
                SECTION_IN_FMT = 'INPUT_FORMAT'
         | 
| 7 | 
            +
                SECTION_OUT_FMT = 'OUTPUT_FORMAT'
         | 
| 8 | 
            +
                SECTION_IO_FMT = 'INOUT_FORMAT'
         | 
| 9 | 
            +
                SECTION_CONSTRAINTS = 'CONSTRAINTS'
         | 
| 10 | 
            +
                SECTION_IN_SMP = 'INPUT_SAMPLE_%<no>s'
         | 
| 11 | 
            +
                SECTION_IN_SMP_PAT = /^INPUT_SAMPLE_(?<no>\d+)$/.freeze
         | 
| 12 | 
            +
                SECTION_OUT_SMP = 'OUTPUT_SAMPLE_%<no>s'
         | 
| 13 | 
            +
                SECTION_OUT_SMP_PAT = /^OUTPUT_SAMPLE_(?<no>\d+)$/.freeze
         | 
| 14 | 
            +
                SECTION_IO_SMP = 'INOUT_SAMPLE'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                SampleData = Struct.new(:no, :ext, :txt)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                InputFormat = Struct.new(:container, :item, :names, :size) do
         | 
| 19 | 
            +
                  def initialize(container, item, names, size = [])
         | 
| 20 | 
            +
                    super(container, item, names, size)
         | 
| 21 | 
            +
                  end
         | 
| 7 22 | 
             
                end
         | 
| 8 | 
            -
              end
         | 
| 9 23 |  | 
| 10 | 
            -
             | 
| 11 | 
            -
                def initialize(container, item, names, size = [])
         | 
| 12 | 
            -
                  super(container, item, names, size)
         | 
| 13 | 
            -
                end
         | 
| 14 | 
            -
              end
         | 
| 24 | 
            +
                Constraint = Struct.new(:name, :type, :value)
         | 
| 15 25 |  | 
| 16 | 
            -
             | 
| 26 | 
            +
                Options = Struct.new(:interactive, :binary_values)
         | 
| 17 27 |  | 
| 18 | 
            -
             | 
| 28 | 
            +
                SourceCode = Struct.new(:ext, :txt)
         | 
| 19 29 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
                attr_reader :q, :smps, :srcs
         | 
| 23 | 
            -
                attr_accessor :page, :desc, :fmt, :defs, :constraints
         | 
| 30 | 
            +
                attr_reader :q, :samples, :sources, :options
         | 
| 31 | 
            +
                attr_accessor :page, :sections, :formats, :constraints
         | 
| 24 32 |  | 
| 25 | 
            -
                def initialize(q)
         | 
| 33 | 
            +
                def initialize(q, page = Mechanize::Page.new)
         | 
| 26 34 | 
             
                  @q = q
         | 
| 27 | 
            -
                  @page =  | 
| 28 | 
            -
                  @ | 
| 29 | 
            -
                  @ | 
| 30 | 
            -
                  @ | 
| 31 | 
            -
                  @defs = []
         | 
| 35 | 
            +
                  @page = page
         | 
| 36 | 
            +
                  @sections = {}
         | 
| 37 | 
            +
                  @samples = []
         | 
| 38 | 
            +
                  @formats = []
         | 
| 32 39 | 
             
                  @constraints = []
         | 
| 33 | 
            -
                  @ | 
| 40 | 
            +
                  @options = Options.new
         | 
| 41 | 
            +
                  @sources = []
         | 
| 34 42 | 
             
                  yield self if block_given?
         | 
| 35 43 | 
             
                end
         | 
| 36 44 |  | 
| 45 | 
            +
                def url
         | 
| 46 | 
            +
                  @url ||= page.uri.to_s
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def page_body
         | 
| 50 | 
            +
                  @page_body ||= page.body.force_encoding('utf-8')
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 37 53 | 
             
                def add_smp(no, ext, txt)
         | 
| 38 | 
            -
                  @ | 
| 54 | 
            +
                  @samples << SampleData.new(no, ext, txt)
         | 
| 39 55 | 
             
                end
         | 
| 40 56 |  | 
| 41 57 | 
             
                def add_src(ext, txt)
         | 
| 42 | 
            -
                  @ | 
| 58 | 
            +
                  @sources << SourceCode.new(ext, txt)
         | 
| 43 59 | 
             
                end
         | 
| 44 60 | 
             
              end
         | 
| 45 61 | 
             
            end
         | 
| @@ -30,10 +30,6 @@ module AtCoderFriends | |
| 30 30 | 
             
                    @contest ||= contest_name(ctx.path)
         | 
| 31 31 | 
             
                  end
         | 
| 32 32 |  | 
| 33 | 
            -
                  def config
         | 
| 34 | 
            -
                    ctx.config
         | 
| 35 | 
            -
                  end
         | 
| 36 | 
            -
             | 
| 37 33 | 
             
                  def common_url(path)
         | 
| 38 34 | 
             
                    File.join(BASE_URL, path)
         | 
| 39 35 | 
             
                  end
         | 
| @@ -43,7 +39,7 @@ module AtCoderFriends | |
| 43 39 | 
             
                  end
         | 
| 44 40 |  | 
| 45 41 | 
             
                  def lang_id(ext)
         | 
| 46 | 
            -
                    config.dig('ext_settings', ext, 'submit_lang') || (
         | 
| 42 | 
            +
                    ctx.config.dig('ext_settings', ext, 'submit_lang') || (
         | 
| 47 43 | 
             
                      msg = <<~MSG
         | 
| 48 44 | 
             
                        submit_lang for .#{ext} is not specified.
         | 
| 49 45 | 
             
                        Available languages:
         | 
| @@ -38,13 +38,13 @@ module AtCoderFriends | |
| 38 38 | 
             
                  end
         | 
| 39 39 |  | 
| 40 40 | 
             
                  def read_auth
         | 
| 41 | 
            -
                    user = config['user'].to_s
         | 
| 41 | 
            +
                    user = ctx.config['user'].to_s
         | 
| 42 42 | 
             
                    if user.empty?
         | 
| 43 43 | 
             
                      print('Enter username:')
         | 
| 44 44 | 
             
                      user = STDIN.gets.chomp
         | 
| 45 45 | 
             
                    end
         | 
| 46 46 |  | 
| 47 | 
            -
                    pass = config['password'].to_s
         | 
| 47 | 
            +
                    pass = ctx.config['password'].to_s
         | 
| 48 48 | 
             
                    if pass.empty?
         | 
| 49 49 | 
             
                      print("Enter password for #{user}:")
         | 
| 50 50 | 
             
                      pass = STDIN.noecho(&:gets).chomp
         | 
| @@ -1,7 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require 'English'
         | 
| 4 | 
            -
             | 
| 5 3 | 
             
            module AtCoderFriends
         | 
| 6 4 | 
             
              module Scraping
         | 
| 7 5 | 
             
                # fetch problems from tasks page
         | 
| @@ -29,10 +27,8 @@ module AtCoderFriends | |
| 29 27 | 
             
                  def fetch_problem(q, url)
         | 
| 30 28 | 
             
                    puts "fetch problem from #{url} ..."
         | 
| 31 29 | 
             
                    page = fetch_with_auth(url)
         | 
| 32 | 
            -
                     | 
| 33 | 
            -
             | 
| 34 | 
            -
                      pbm.page = page
         | 
| 35 | 
            -
                    end
         | 
| 30 | 
            +
                    page.search('br').each { |br| br.replace("\n") }
         | 
| 31 | 
            +
                    Problem.new(q, page)
         | 
| 36 32 | 
             
                  end
         | 
| 37 33 | 
             
                end
         | 
| 38 34 | 
             
              end
         | 
| @@ -8,25 +8,26 @@ module AtCoderFriends | |
| 8 8 | 
             
                # run tests for the specified program.
         | 
| 9 9 | 
             
                class Base
         | 
| 10 10 | 
             
                  include PathUtil
         | 
| 11 | 
            +
                  STATUS_STR = {
         | 
| 12 | 
            +
                    OK: '<< OK >>'.green,
         | 
| 13 | 
            +
                    WA: '!!!!! WA !!!!!'.red,
         | 
| 14 | 
            +
                    RE: '!!!!! RE !!!!!'.red,
         | 
| 15 | 
            +
                    NO_EXP: ''
         | 
| 16 | 
            +
                  }.freeze
         | 
| 11 17 |  | 
| 12 18 | 
             
                  attr_reader :ctx, :path, :dir, :prg, :base, :ext, :q
         | 
| 13 19 |  | 
| 14 20 | 
             
                  def initialize(ctx)
         | 
| 15 21 | 
             
                    @ctx = ctx
         | 
| 16 22 | 
             
                    @path, @dir, @prg, @base, @ext, @q = split_prg_path(ctx.path)
         | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
                  def config
         | 
| 20 | 
            -
                    ctx.config
         | 
| 23 | 
            +
                    @detail = true
         | 
| 21 24 | 
             
                  end
         | 
| 22 25 |  | 
| 23 26 | 
             
                  def test_cmd
         | 
| 24 27 | 
             
                    @test_cmd ||= begin
         | 
| 25 | 
            -
                      cmds = config.dig('ext_settings', ext, 'test_cmd')
         | 
| 28 | 
            +
                      cmds = ctx.config.dig('ext_settings', ext, 'test_cmd')
         | 
| 26 29 | 
             
                      cmd = cmds && (cmds[which_os.to_s] || cmds['default'])
         | 
| 27 | 
            -
                       | 
| 28 | 
            -
             | 
| 29 | 
            -
                      cmd.gsub('{dir}', dir).gsub('{base}', base)
         | 
| 30 | 
            +
                      cmd&.gsub('{dir}', dir)&.gsub('{base}', base)
         | 
| 30 31 | 
             
                    end
         | 
| 31 32 | 
             
                  end
         | 
| 32 33 |  | 
| @@ -39,18 +40,25 @@ module AtCoderFriends | |
| 39 40 | 
             
                  end
         | 
| 40 41 |  | 
| 41 42 | 
             
                  def run_test(id, infile, outfile, expfile)
         | 
| 42 | 
            -
                    return false unless File.exist?(infile) && File.exist?(expfile)
         | 
| 43 | 
            -
             | 
| 44 43 | 
             
                    puts "==== #{id} ===="
         | 
| 44 | 
            +
                    return false unless check_file(infile, outfile)
         | 
| 45 45 |  | 
| 46 | 
            -
                    makedirs_unless(File.dirname(outfile))
         | 
| 47 46 | 
             
                    is_success = send(test_mtd, infile, outfile)
         | 
| 48 | 
            -
                     | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
                     | 
| 47 | 
            +
                    input = File.read(infile)
         | 
| 48 | 
            +
                    result = File.read(outfile)
         | 
| 49 | 
            +
                    expected = File.exist?(expfile) && File.read(expfile)
         | 
| 50 | 
            +
                    status = check_status(is_success, result, expected)
         | 
| 51 | 
            +
                    print detail_str(input, result, expected) if @detail
         | 
| 52 | 
            +
                    puts STATUS_STR[status]
         | 
| 53 | 
            +
                    status == :OK
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def check_file(infile, outfile)
         | 
| 57 | 
            +
                    unless File.exist?(infile)
         | 
| 58 | 
            +
                      puts "#{File.basename(infile)} not found."
         | 
| 59 | 
            +
                      return false
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                    makedirs_unless(File.dirname(outfile))
         | 
| 54 62 | 
             
                    true
         | 
| 55 63 | 
             
                  end
         | 
| 56 64 |  | 
| @@ -76,9 +84,16 @@ module AtCoderFriends | |
| 76 84 | 
             
                    [true, res['Stdout']]
         | 
| 77 85 | 
             
                  end
         | 
| 78 86 |  | 
| 79 | 
            -
                  def  | 
| 80 | 
            -
                     | 
| 81 | 
            -
             | 
| 87 | 
            +
                  def check_status(is_success, result, expected)
         | 
| 88 | 
            +
                    if !is_success
         | 
| 89 | 
            +
                      :RE
         | 
| 90 | 
            +
                    elsif !expected
         | 
| 91 | 
            +
                      :NO_EXP
         | 
| 92 | 
            +
                    elsif result != expected
         | 
| 93 | 
            +
                      :WA
         | 
| 94 | 
            +
                    else
         | 
| 95 | 
            +
                      :OK
         | 
| 96 | 
            +
                    end
         | 
| 82 97 | 
             
                  end
         | 
| 83 98 |  | 
| 84 99 | 
             
                  def detail_str(input, result, expected)
         | 
| @@ -86,22 +101,12 @@ module AtCoderFriends | |
| 86 101 | 
             
                    ret += "-- input --\n"
         | 
| 87 102 | 
             
                    ret += input
         | 
| 88 103 | 
             
                    ret += "-- expected --\n"
         | 
| 89 | 
            -
                    ret += expected
         | 
| 104 | 
            +
                    ret += expected || "(no expected value)\n"
         | 
| 90 105 | 
             
                    ret += "-- result --\n"
         | 
| 91 106 | 
             
                    ret += result
         | 
| 92 107 | 
             
                    ret
         | 
| 93 108 | 
             
                  end
         | 
| 94 109 |  | 
| 95 | 
            -
                  def result_str(is_success, result, expected)
         | 
| 96 | 
            -
                    if !is_success
         | 
| 97 | 
            -
                      '!!!!! RE !!!!!'.red
         | 
| 98 | 
            -
                    elsif result != expected
         | 
| 99 | 
            -
                      '!!!!! WA !!!!!'.red
         | 
| 100 | 
            -
                    else
         | 
| 101 | 
            -
                      '<< OK >>'.green
         | 
| 102 | 
            -
                    end
         | 
| 103 | 
            -
                  end
         | 
| 104 | 
            -
             | 
| 105 110 | 
             
                  def which_os
         | 
| 106 111 | 
             
                    @which_os ||= begin
         | 
| 107 112 | 
             
                      case RbConfig::CONFIG['host_os']
         | 
| @@ -16,10 +16,11 @@ module AtCoderFriends | |
| 16 16 |  | 
| 17 17 | 
             
                  def judge_all
         | 
| 18 18 | 
             
                    puts "***** judge_all #{prg} (#{test_loc}) *****"
         | 
| 19 | 
            -
                    Dir["#{data_dir}/#{q}/in/*.txt"].sort. | 
| 19 | 
            +
                    results = Dir["#{data_dir}/#{q}/in/*.txt"].sort.map do |infile|
         | 
| 20 20 | 
             
                      id = File.basename(infile, '.txt')
         | 
| 21 21 | 
             
                      judge(id, false)
         | 
| 22 22 | 
             
                    end
         | 
| 23 | 
            +
                    !results.empty? && results.all?
         | 
| 23 24 | 
             
                  end
         | 
| 24 25 |  | 
| 25 26 | 
             
                  def judge_one(id)
         | 
| @@ -34,11 +35,6 @@ module AtCoderFriends | |
| 34 35 | 
             
                    expfile = "#{data_dir}/#{q}/out/#{id}.txt"
         | 
| 35 36 | 
             
                    run_test(id, infile, outfile, expfile)
         | 
| 36 37 | 
             
                  end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                  def show_result(is_success, input, result, expected)
         | 
| 39 | 
            -
                    print detail_str(input, result, expected) if @detail
         | 
| 40 | 
            -
                    puts result_str(is_success, result, expected)
         | 
| 41 | 
            -
                  end
         | 
| 42 38 | 
             
                end
         | 
| 43 39 | 
             
              end
         | 
| 44 40 | 
             
            end
         | 
| @@ -15,18 +15,20 @@ module AtCoderFriends | |
| 15 15 |  | 
| 16 16 | 
             
                  def test_all
         | 
| 17 17 | 
             
                    puts "***** test_all #{prg} (#{test_loc}) *****"
         | 
| 18 | 
            -
                     | 
| 19 | 
            -
                       | 
| 18 | 
            +
                    results = Dir["#{data_dir}/#{q}_*.in"].sort.map do |infile|
         | 
| 19 | 
            +
                      id = File.basename(infile, '.in').sub(/[^_]+_/, '')
         | 
| 20 | 
            +
                      test(id)
         | 
| 20 21 | 
             
                    end
         | 
| 22 | 
            +
                    !results.empty? && results.all?
         | 
| 21 23 | 
             
                  end
         | 
| 22 24 |  | 
| 23 | 
            -
                  def test_one( | 
| 25 | 
            +
                  def test_one(id)
         | 
| 24 26 | 
             
                    puts "***** test_one #{prg} (#{test_loc}) *****"
         | 
| 25 | 
            -
                    test( | 
| 27 | 
            +
                    test(id)
         | 
| 26 28 | 
             
                  end
         | 
| 27 29 |  | 
| 28 | 
            -
                  def test( | 
| 29 | 
            -
                    id = format('%<q>s_%< | 
| 30 | 
            +
                  def test(id)
         | 
| 31 | 
            +
                    id = format('%<q>s_%<id>s', q: q, id: id)
         | 
| 30 32 | 
             
                    files = %w[in out exp].map { |ext| "#{data_dir}/#{id}.#{ext}" }
         | 
| 31 33 | 
             
                    run_test(id, *files)
         | 
| 32 34 | 
             
                  end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'regression'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module AtCoderFriends
         | 
| 6 | 
            +
              # tasks for regression
         | 
| 7 | 
            +
              module Regression
         | 
| 8 | 
            +
                module_function
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def check_diff
         | 
| 11 | 
            +
                  emit_dir = format(EMIT_DIR_FMT, now: Time.now.strftime('%Y%m%d%H%M%S'))
         | 
| 12 | 
            +
                  rmdir_force(emit_dir)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  local_pbm_list.each do |contest, q, url|
         | 
| 15 | 
            +
                    pbm = scraping_agent(emit_dir, contest).fetch_problem(q, url)
         | 
| 16 | 
            +
                    pipeline(pbm)
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  system("diff -r #{EMIT_ORG_DIR} #{emit_dir}")
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            namespace :regression do
         | 
| 25 | 
            +
              desc 'run regression check'
         | 
| 26 | 
            +
              task :check_diff do
         | 
| 27 | 
            +
                AtCoderFriends::Regression.check_diff
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'regression'
         | 
| 4 | 
            +
            require 'at_coder_friends'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module AtCoderFriends
         | 
| 7 | 
            +
              # tasks for regression
         | 
| 8 | 
            +
              module Regression
         | 
| 9 | 
            +
                module_function
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def check_parse(arg)
         | 
| 12 | 
            +
                  arg ||= 'fmt,smp,int'
         | 
| 13 | 
            +
                  list = local_pbm_list.map do |contest, q, url|
         | 
| 14 | 
            +
                    pbm = scraping_agent(REGRESSION_HOME, contest).fetch_problem(q, url)
         | 
| 15 | 
            +
                    Parser::Main.process(pbm)
         | 
| 16 | 
            +
                    tbl = {
         | 
| 17 | 
            +
                      'fmt' => !fmt?(pbm),
         | 
| 18 | 
            +
                      'smp' => pbm.samples.all? { |smp| smp.txt.empty? },
         | 
| 19 | 
            +
                      'int' => pbm.options.interactive,
         | 
| 20 | 
            +
                      'bin' => pbm.options.binary_values
         | 
| 21 | 
            +
                    }
         | 
| 22 | 
            +
                    [contest, q, tbl.values_at(*arg.split(','))]
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                  report(list)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def fmt?(pbm)
         | 
| 28 | 
            +
                  [Problem::SECTION_IN_FMT, Problem::SECTION_IO_FMT]
         | 
| 29 | 
            +
                    .any? { |key| pbm.sections[key]&.code_block&.size&.positive? }
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def report(list)
         | 
| 33 | 
            +
                  list
         | 
| 34 | 
            +
                    .select { |_, _, flags| flags.any? }
         | 
| 35 | 
            +
                    .map { |c, q, flags| [c, q, flags.map { |f| f_to_s(f) }] }
         | 
| 36 | 
            +
                    .sort
         | 
| 37 | 
            +
                    .each { |args| puts args.flatten.join("\t") }
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def f_to_s(f)
         | 
| 41 | 
            +
                  if f.is_a?(Array)
         | 
| 42 | 
            +
                    f
         | 
| 43 | 
            +
                  else
         | 
| 44 | 
            +
                    f ? '◯' : '-'
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            namespace :regression do
         | 
| 51 | 
            +
              desc 'checks page parse result'
         | 
| 52 | 
            +
              task :check_parse, ['flags'] do |_, args|
         | 
| 53 | 
            +
                flags = args[:flags]
         | 
| 54 | 
            +
                AtCoderFriends::Regression.check_parse flags
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'net/http'
         | 
| 4 | 
            +
            require 'uri'
         | 
| 5 | 
            +
            require 'json'
         | 
| 6 | 
            +
            require 'csv'
         | 
| 7 | 
            +
            require 'mechanize'
         | 
| 8 | 
            +
            require 'at_coder_friends'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module AtCoderFriends
         | 
| 11 | 
            +
              # tasks for regression
         | 
| 12 | 
            +
              module Regression
         | 
| 13 | 
            +
                module_function
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                CONTEST_LIST_URL = 'https://kenkoooo.com/atcoder/resources/contests.json'
         | 
| 16 | 
            +
                REGRESSION_HOME =
         | 
| 17 | 
            +
                  File.expand_path(File.join(__dir__, '..', '..', 'regression'))
         | 
| 18 | 
            +
                PAGES_DIR = File.join(REGRESSION_HOME, 'pages')
         | 
| 19 | 
            +
                EMIT_ORG_DIR = File.join(REGRESSION_HOME, 'emit_org')
         | 
| 20 | 
            +
                EMIT_DIR_FMT = File.join(REGRESSION_HOME, 'emit_%<now>s')
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def contest_id_list
         | 
| 23 | 
            +
                  uri = URI.parse(CONTEST_LIST_URL)
         | 
| 24 | 
            +
                  json = Net::HTTP.get(uri)
         | 
| 25 | 
            +
                  contests = JSON.parse(json)
         | 
| 26 | 
            +
                  puts "Total #{contests.size} contests"
         | 
| 27 | 
            +
                  contests.map { |h| h['id'] }
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def local_pbm_list
         | 
| 31 | 
            +
                  Dir.glob(PAGES_DIR + '/**/*.html').map do |pbm_path|
         | 
| 32 | 
            +
                    contest = File.basename(File.dirname(pbm_path))
         | 
| 33 | 
            +
                    q = File.basename(pbm_path, '.html')
         | 
| 34 | 
            +
                    url = "file://#{pbm_path}"
         | 
| 35 | 
            +
                    [contest, q, url]
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def pbm_list_from_file(file)
         | 
| 40 | 
            +
                  dat = File.join(REGRESSION_HOME, file)
         | 
| 41 | 
            +
                  CSV.read(dat, col_sep: "\t", headers: false).map do |contest, q|
         | 
| 42 | 
            +
                    pbm_path = File.join(PAGES_DIR, contest, "#{q}.html")
         | 
| 43 | 
            +
                    url = "file://#{pbm_path}"
         | 
| 44 | 
            +
                    [contest, q, url]
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def scraping_agent(root, contest)
         | 
| 49 | 
            +
                  @ctx = Context.new({}, File.join(root, contest))
         | 
| 50 | 
            +
                  @ctx.scraping_agent
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def agent
         | 
| 54 | 
            +
                  @agent ||= Mechanize.new
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def pipeline(pbm)
         | 
| 58 | 
            +
                  Parser::Main.process(pbm)
         | 
| 59 | 
            +
                  @ctx.generator.process(pbm)
         | 
| 60 | 
            +
                  @ctx.emitter.emit(pbm)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def rmdir_force(dir)
         | 
| 64 | 
            +
                  FileUtils.rm_r(dir) if Dir.exist?(dir)
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
            end
         |