contestify 1.2.1 → 2.0.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.
- data/README.md +28 -10
- data/bin/contestify +1 -5
- data/contestify.gemspec +2 -1
- data/lib/contestify/contest.rb +9 -44
- data/lib/contestify/judges/coci/README.md +14 -0
- data/lib/contestify/judges/coci/coci.rb +35 -0
- data/lib/contestify/judges/coci/configuration.rb +97 -0
- data/lib/contestify/judges.rb +1 -0
- data/lib/contestify/util.rb +12 -0
- data/lib/contestify/version.rb +1 -1
- data/lib/contestify.rb +2 -0
- metadata +21 -7
- data/lib/contestify/configuration.rb +0 -94
data/README.md
CHANGED
@@ -1,28 +1,46 @@
|
|
1
1
|
Contestify
|
2
|
-
|
2
|
+
=====
|
3
3
|
|
4
|
-
This is a simple ruby gem to automate the process of setting up a programming contest
|
4
|
+
This is a simple ruby gem to automate the process of setting up a programming contest with [DOMJudge](http://domjudge.sourceforge.net/). You can choose from several judges and the problems will be uploaded without too much work, because, let's be honest, DOMJudge is a great online judge, but it sucks at UI.
|
5
5
|
|
6
|
-
|
6
|
+
Install
|
7
7
|
---
|
8
8
|
|
9
|
+
Installing Contestify couldn't be easier. Just type
|
10
|
+
|
11
|
+
gem install contestify
|
12
|
+
|
13
|
+
Then, you can run `contestify check` to check the OS dependencies. Most Unix systems _should_ have the dependencies installed.
|
14
|
+
|
15
|
+
Usage & Help
|
16
|
+
---
|
17
|
+
|
18
|
+
You can run the `contestify` command with no arguments to see the available judges. Each available judge will ask for different arguments. If you need help with a specific judge, you can type `contestify help <judge>` and it will give you some help about it.
|
19
|
+
|
9
20
|
```
|
10
|
-
contestify
|
21
|
+
contestify help coci
|
11
22
|
```
|
12
23
|
|
13
|
-
|
24
|
+
Provide the requested data for each judge and it should run smoothly. It is important to notice that Contestify **DOES NOT** store **ANY** data. It asks for passwords just to be able to upload (and sometimes fetch) the problems. If you still doubt it, you can check the code for yourself.
|
25
|
+
|
26
|
+
|
27
|
+
Notes
|
28
|
+
---
|
29
|
+
|
30
|
+
* Contestify **will not** create a new contest. Just add problems to the current one. So you'll need to have an active contest to run this.
|
14
31
|
|
15
|
-
It is assumed that the
|
32
|
+
* It is assumed that the **Admin username** is `admin`.
|
16
33
|
|
17
|
-
|
34
|
+
Fast Example
|
35
|
+
----
|
18
36
|
|
19
37
|
```
|
20
|
-
contestify http://hsin.hr/coci/contest1_testdata.zip http://juez.factorcomun.org/jury/problem.php p4ssw0rd
|
38
|
+
contestify coci http://hsin.hr/coci/contest1_testdata.zip http://juez.factorcomun.org/jury/problem.php p4ssw0rd
|
21
39
|
```
|
22
40
|
|
23
41
|
Contributions
|
24
42
|
---
|
25
43
|
|
26
|
-
If you find any bug or have any addition, please let us know. You can use the [issue tracker](https://github.com/nhocki/contestify/issues) for that.
|
44
|
+
If you find any bug or have any addition, please let us know. You can use the [issue tracker](https://github.com/nhocki/contestify/issues) for that.
|
27
45
|
|
28
|
-
|
46
|
+
We would also love to have more and more judges, so if you want to write one, just submit a pull request with it.
|
data/bin/contestify
CHANGED
@@ -2,12 +2,8 @@
|
|
2
2
|
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
3
3
|
require 'contestify'
|
4
4
|
|
5
|
-
problems_url = ARGV[0]
|
6
|
-
judge_url = ARGV[1]
|
7
|
-
judge_password = ARGV[2]
|
8
|
-
|
9
5
|
begin
|
10
|
-
Contestify::Contest.
|
6
|
+
Contestify::Contest.start
|
11
7
|
rescue Exception => e
|
12
8
|
puts e.message
|
13
9
|
end
|
data/contestify.gemspec
CHANGED
data/lib/contestify/contest.rb
CHANGED
@@ -1,53 +1,18 @@
|
|
1
1
|
module Contestify
|
2
|
-
class Contest
|
2
|
+
class Contest < Thor
|
3
3
|
include Contestify::Util
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
if [@problems_url, @judge_url, @judge_password].any? {|attribute| attribute.nil? or attribute.empty?}
|
11
|
-
raise Exception.new(Contestify::HELP_MESSAGE)
|
12
|
-
end
|
5
|
+
desc "coci PROBLEMS_URL JUDGE_UPLOAD_URL JUDGE_PASSWORD", "Setups a contest based on a COCI problemset"
|
6
|
+
def coci(problems_url, judge_url, judge_password)
|
7
|
+
contest = Contestify::Judge::Coci.new problems_url
|
8
|
+
Contestify::Uploader.upload!(judge_url, judge_password, contest.problems_paths)
|
9
|
+
clean_dir! contest.working_root_path
|
13
10
|
end
|
14
11
|
|
15
|
-
|
12
|
+
desc "check", "Checks that the user has the required OS software installed"
|
13
|
+
def check
|
16
14
|
check_dependencies
|
17
|
-
|
18
|
-
unzip_problems
|
19
|
-
@base_dir = Dir.pwd
|
20
|
-
problems_path = Contestify::Configuration.configure!(@base_dir)
|
21
|
-
Contestify::Uploader.upload!(@judge_url, @judge_password, problems_path)
|
22
|
-
clean_dir!
|
23
|
-
end
|
24
|
-
|
25
|
-
private ######################################################################## PRIVATE
|
26
|
-
|
27
|
-
def clean_dir!
|
28
|
-
puts red "Deleting created files & folders (#{@base_dir})"
|
29
|
-
FileUtils.rm_rf @base_dir
|
30
|
-
end
|
31
|
-
|
32
|
-
def check_dependencies
|
33
|
-
system?(:zip) and
|
34
|
-
system?(:curl) and
|
35
|
-
system?(:unzip) and
|
36
|
-
system?(:dos2unix)
|
37
|
-
end
|
38
|
-
|
39
|
-
def get_problems
|
40
|
-
FileUtils.mkdir_p File.join(Dir.pwd, "contestify")
|
41
|
-
Dir.chdir File.join(Dir.pwd, "contestify")
|
42
|
-
puts green "=> Fetching problems from #{@problems_url}"
|
43
|
-
curl_output = `curl #{@problems_url} > #{File.join(Dir.pwd, "problems.zip")}`
|
44
|
-
raise Exception.new(red Contestify::CURL_PROBLEM) unless $?.success?
|
45
|
-
end
|
46
|
-
|
47
|
-
def unzip_problems
|
48
|
-
puts green "=> Unzipping problems"
|
49
|
-
unzip_output = `unzip #{File.join(Dir.pwd, "problems.zip")}`
|
50
|
-
raise Exception.new(red Contestify::UNZIP_PROBLEM) unless $?.success?
|
15
|
+
puts green "Everything looks ok. You may now use Contestify."
|
51
16
|
end
|
52
17
|
end
|
53
18
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Contestify::Coci
|
2
|
+
===
|
3
|
+
|
4
|
+
This strategy will upload the problems from a given URL to the server. Usage:
|
5
|
+
|
6
|
+
contestify coci problem_url judge_upload_url judge_admin_password
|
7
|
+
|
8
|
+
Note that the Judge URL is the actual **upload** URL, which is normally `http://you-dom-judge.com/jury/problem.php`.
|
9
|
+
|
10
|
+
Here's an example of usage with _real_ data:
|
11
|
+
|
12
|
+
contestify http://hsin.hr/coci/contest1_testdata.zip http://domjudge.factorcomun.org/jury/problem.php p4ssw0rd
|
13
|
+
|
14
|
+
This will get the .zip file from the `http://hsin.hr/coci/contest1_testdata.zip` and add the problems to the DOM Judge on the server.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'contestify/judges/coci/configuration'
|
2
|
+
|
3
|
+
module Contestify
|
4
|
+
module Judge
|
5
|
+
class Coci
|
6
|
+
attr_accessor :problems_url, :working_root_path, :problems_paths
|
7
|
+
|
8
|
+
def initialize(problems_url)
|
9
|
+
@problems_url = problems_url
|
10
|
+
get_problems
|
11
|
+
unzip_problems
|
12
|
+
@working_root_path = Dir.pwd
|
13
|
+
@problems_paths = Contestify::Coci::Configuration.configure!(@working_root_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
private ################################################ PRIVATE
|
17
|
+
|
18
|
+
def get_problems
|
19
|
+
working_path = File.join(Dir.pwd, "contestify")
|
20
|
+
FileUtils.mkdir_p working_path
|
21
|
+
Dir.chdir working_path
|
22
|
+
puts green "=> Fetching problems from #{@problems_url}"
|
23
|
+
curl_output = `curl #{@problems_url} > #{File.join(Dir.pwd, "problems.zip")}`
|
24
|
+
raise Exception.new(red Contestify::CURL_PROBLEM) unless $?.success?
|
25
|
+
end
|
26
|
+
|
27
|
+
def unzip_problems
|
28
|
+
puts green "=> Unzipping problems"
|
29
|
+
unzip_output = `unzip #{File.join(Dir.pwd, "problems.zip")}`
|
30
|
+
raise Exception.new(red Contestify::UNZIP_PROBLEM) unless $?.success?
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Contestify
|
2
|
+
module Coci
|
3
|
+
# <tt>Coci::Configuration</tt> holds all the logic to configure a COCI based
|
4
|
+
# contest. This class **must** implement the `self.configure!` method.
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
# <tt>Contestify::Configuration.configure!</tt> is *the only* method you
|
8
|
+
# should call from the contest interface. All the other methods should be
|
9
|
+
# called from here. This is the only method needed for future strategies
|
10
|
+
# for other judges.
|
11
|
+
#
|
12
|
+
# This should be called in a directory structure such that you have a
|
13
|
+
# `base_folder` with one folder for each problem you want to upload to the
|
14
|
+
# server. Each of these problems folders should have all the input/output
|
15
|
+
# files you want to use as test cases.
|
16
|
+
#
|
17
|
+
# This method **must** return the absolute paths of the problem folders in
|
18
|
+
# an array. This return value will be used in Contestify::Uploader.upload!
|
19
|
+
#
|
20
|
+
# ==== Parameters
|
21
|
+
# base_folder<String>:: The base folder where all the problems folders are
|
22
|
+
# stored.
|
23
|
+
#
|
24
|
+
def self.configure!(base_folder)
|
25
|
+
puts green "=> Configuring problems (#{base_folder})"
|
26
|
+
problem_index = 0
|
27
|
+
Dir.glob("*").select { |f| File.directory?(f) }.map do |dir|
|
28
|
+
Dir.chdir File.join(base_folder, dir)
|
29
|
+
dirid = Dir.pwd.split('/').last[0...8]
|
30
|
+
puts green "==> #{dirid.upcase}"
|
31
|
+
rename_data_files
|
32
|
+
add_problem_config(dirid, problem_index)
|
33
|
+
problem_index += 1
|
34
|
+
puts green "==> All the work for #{dirid.upcase} is done"
|
35
|
+
# Return the absolute path for this problem
|
36
|
+
Dir.pwd
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#########################################################################
|
41
|
+
private #################################################################
|
42
|
+
|
43
|
+
# <tt>Coci::Configuration.rename_data_files</tt> is in charge of
|
44
|
+
# giving each input/output file a standard name that DOMJudge understands.
|
45
|
+
#
|
46
|
+
# This method should be overriden in future strategies.
|
47
|
+
#
|
48
|
+
# After this method is called, every different input/output file **must**
|
49
|
+
# have the following structure:
|
50
|
+
#
|
51
|
+
# `problemid`.`number`.in
|
52
|
+
# `problemid`.`number`.out
|
53
|
+
#
|
54
|
+
# where:
|
55
|
+
#
|
56
|
+
# `problemid` is at most 8 letters long and it's the id for the judge and
|
57
|
+
# `number` is the test case number. The .in and .out will determine the
|
58
|
+
# input and compare file the judge will run/diff.
|
59
|
+
def self.rename_data_files
|
60
|
+
puts blue "===> Renaming input/output files"
|
61
|
+
Dir.glob("*").select { |f| f =~ /\.in.[0-9]+/ }.each do |f|
|
62
|
+
parts = f.split(".")
|
63
|
+
new_name = [parts[0], parts[2], parts[1]].join(".")
|
64
|
+
File.rename f, new_name
|
65
|
+
|
66
|
+
f.gsub!(".in", ".out")
|
67
|
+
parts = f.split(".")
|
68
|
+
new_name = [parts[0], parts[2], parts[1]].join(".")
|
69
|
+
File.rename f, new_name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# <tt>Coci::Configuration.add_problem_config</tt> will add the
|
75
|
+
# configuration file needed for DOMJudge to understand the problem.
|
76
|
+
#
|
77
|
+
# ==== Parameters
|
78
|
+
# probid<String>:: The problem id for DOMJudge. This **must** be at most
|
79
|
+
# 8 letters long.
|
80
|
+
#
|
81
|
+
# problem_index<Integer>:: This is the problem number. This is only used
|
82
|
+
# to choose the problem ballon color.
|
83
|
+
#
|
84
|
+
def self.add_problem_config(probid, problem_index)
|
85
|
+
puts blue "===> Adding DOM Judge configuration #{probid}"
|
86
|
+
file_content = <<-TEXT
|
87
|
+
probid = #{probid}
|
88
|
+
name = #{probid}
|
89
|
+
allow_submit = true
|
90
|
+
color = #{Contestify::PROBLEM_COLORS[problem_index]}
|
91
|
+
timelimit = 1
|
92
|
+
TEXT
|
93
|
+
File.open(File.join(Dir.pwd, "domjudge-problem.ini"), 'w') {|f| f.write(file_content) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'contestify/judges/coci/coci'
|
data/lib/contestify/util.rb
CHANGED
@@ -4,5 +4,17 @@ module Contestify
|
|
4
4
|
`which #{command.to_s}`
|
5
5
|
raise Exception.new(red "`#{command}` is not installed. Please install it first") unless $?.success?
|
6
6
|
end
|
7
|
+
|
8
|
+
def clean_dir!(directory)
|
9
|
+
puts red "Deleting created files & folders (#{directory})"
|
10
|
+
FileUtils.rm_rf directory
|
11
|
+
end
|
12
|
+
|
13
|
+
def check_dependencies
|
14
|
+
system?(:zip) and
|
15
|
+
system?(:curl) and
|
16
|
+
system?(:unzip) and
|
17
|
+
system?(:dos2unix)
|
18
|
+
end
|
7
19
|
end
|
8
20
|
end
|
data/lib/contestify/version.rb
CHANGED
data/lib/contestify.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'thor'
|
2
3
|
require "contestify/version"
|
3
4
|
require "contestify/messages"
|
4
5
|
require "contestify/colorize"
|
@@ -6,5 +7,6 @@ require "contestify/util"
|
|
6
7
|
require "contestify/contest"
|
7
8
|
require "contestify/configuration"
|
8
9
|
require "contestify/uploader"
|
10
|
+
require "contestify/judges"
|
9
11
|
|
10
12
|
include Contestify::Colorize
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contestify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-12-
|
12
|
+
date: 2011-12-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: &70364193982720 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.14.6
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70364193982720
|
14
25
|
- !ruby/object:Gem::Dependency
|
15
26
|
name: rake
|
16
|
-
requirement: &
|
27
|
+
requirement: &70364193981160 !ruby/object:Gem::Requirement
|
17
28
|
none: false
|
18
29
|
requirements:
|
19
30
|
- - ~>
|
@@ -21,7 +32,7 @@ dependencies:
|
|
21
32
|
version: 0.9.2
|
22
33
|
type: :development
|
23
34
|
prerelease: false
|
24
|
-
version_requirements: *
|
35
|
+
version_requirements: *70364193981160
|
25
36
|
description: Gem to prepare internal programming contests taking problems from the
|
26
37
|
COCI contests.
|
27
38
|
email:
|
@@ -40,8 +51,11 @@ files:
|
|
40
51
|
- contestify.gemspec
|
41
52
|
- lib/contestify.rb
|
42
53
|
- lib/contestify/colorize.rb
|
43
|
-
- lib/contestify/configuration.rb
|
44
54
|
- lib/contestify/contest.rb
|
55
|
+
- lib/contestify/judges.rb
|
56
|
+
- lib/contestify/judges/coci/README.md
|
57
|
+
- lib/contestify/judges/coci/coci.rb
|
58
|
+
- lib/contestify/judges/coci/configuration.rb
|
45
59
|
- lib/contestify/messages.rb
|
46
60
|
- lib/contestify/uploader.rb
|
47
61
|
- lib/contestify/util.rb
|
@@ -60,7 +74,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
74
|
version: '0'
|
61
75
|
segments:
|
62
76
|
- 0
|
63
|
-
hash:
|
77
|
+
hash: -3999630116395939116
|
64
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
79
|
none: false
|
66
80
|
requirements:
|
@@ -69,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
83
|
version: '0'
|
70
84
|
segments:
|
71
85
|
- 0
|
72
|
-
hash:
|
86
|
+
hash: -3999630116395939116
|
73
87
|
requirements: []
|
74
88
|
rubyforge_project:
|
75
89
|
rubygems_version: 1.8.10
|
@@ -1,94 +0,0 @@
|
|
1
|
-
module Contestify
|
2
|
-
# <tt>Contestify::Configuration</tt> is in charge of renaming files and
|
3
|
-
# adding DOMJudge specific configuration files to each problem.
|
4
|
-
class Configuration
|
5
|
-
|
6
|
-
# <tt>Contestify::Configuration.configure!</tt> is *the only* method you
|
7
|
-
# should call from the contest interface. All the other methods should be
|
8
|
-
# called from here. This is the only method needed for future strategies
|
9
|
-
# for other judges.
|
10
|
-
#
|
11
|
-
# This should be called in a directory structure such that you have a
|
12
|
-
# `base_folder` with one folder for each problem you want to upload to the
|
13
|
-
# server. Each of these problems folders should have all the input/output
|
14
|
-
# files you want to use as test cases.
|
15
|
-
#
|
16
|
-
# This method **must** return the absolute paths of the problem folders in
|
17
|
-
# an array. This return value will be used in Contestify::Uploader.upload!
|
18
|
-
#
|
19
|
-
# ==== Parameters
|
20
|
-
# base_folder<String>:: The base folder where all the problems folders are
|
21
|
-
# stored.
|
22
|
-
#
|
23
|
-
def self.configure!(base_folder)
|
24
|
-
puts green "=> Configuring problems"
|
25
|
-
problem_index = 0
|
26
|
-
Dir.glob("*").select { |f| File.directory?(f) }.map do |dir|
|
27
|
-
Dir.chdir File.join(base_folder, dir)
|
28
|
-
dirid = Dir.pwd.split('/').last[0...8]
|
29
|
-
puts green "==> #{dirid.upcase}"
|
30
|
-
rename_data_files
|
31
|
-
add_problem_config(dirid, problem_index)
|
32
|
-
problem_index += 1
|
33
|
-
puts green "==> All the work for #{dirid.upcase} is done"
|
34
|
-
Dir.pwd # Return the absolute path for this problem
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# <tt>Contestify::Configuration.rename_data_files</tt> is in charge of
|
39
|
-
# giving each input/output file a standard name that DOMJudge understands.
|
40
|
-
#
|
41
|
-
# This method should be overriden in future strategies.
|
42
|
-
#
|
43
|
-
# After this method is called, every different input/output file **must**
|
44
|
-
# have the following structure:
|
45
|
-
#
|
46
|
-
# `problemid`.`number`.in
|
47
|
-
# `problemid`.`number`.out
|
48
|
-
#
|
49
|
-
# where:
|
50
|
-
#
|
51
|
-
# `problemid` is at most 8 letters long and it's the id for the judge and
|
52
|
-
# `number` is the test case number. The .in and .out will determine the
|
53
|
-
# input and compare file the judge will run/diff.
|
54
|
-
def self.rename_data_files
|
55
|
-
puts blue "===> Renaming input/output files"
|
56
|
-
Dir.glob("*").select { |f| f =~ /\.in.[0-9]+/ }.each do |f|
|
57
|
-
# puts red " Renaming #{f}..."
|
58
|
-
parts = f.split(".")
|
59
|
-
new_name = [parts[0], parts[2], parts[1]].join(".")
|
60
|
-
# puts blue " " + new_name
|
61
|
-
File.rename f, new_name
|
62
|
-
|
63
|
-
f.gsub!(".in", ".out")
|
64
|
-
# puts red " Renaming #{f}..."
|
65
|
-
parts = f.split(".")
|
66
|
-
new_name = [parts[0], parts[2], parts[1]].join(".")
|
67
|
-
# puts blue " " + new_name
|
68
|
-
File.rename f, new_name
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# <tt>Contestify::Configuration.add_problem_config</tt> will add the
|
73
|
-
# configuration file needed for DOMJudge to understand the problem.
|
74
|
-
#
|
75
|
-
# ==== Parameters
|
76
|
-
# probid<String>:: The problem id for DOMJudge. This **must** be at most
|
77
|
-
# 8 letters long.
|
78
|
-
#
|
79
|
-
# problem_index<Integer>:: This is the problem number. This is only used
|
80
|
-
# to choose the problem ballon color.
|
81
|
-
|
82
|
-
def self.add_problem_config(probid, problem_index)
|
83
|
-
puts blue "===> Adding DOM Judge configuration #{probid}"
|
84
|
-
file_content = <<-TEXT
|
85
|
-
probid = #{probid}
|
86
|
-
name = #{probid}
|
87
|
-
allow_submit = true
|
88
|
-
color = #{Contestify::PROBLEM_COLORS[problem_index]}
|
89
|
-
timelimit = 1
|
90
|
-
TEXT
|
91
|
-
File.open(File.join(Dir.pwd, "domjudge-problem.ini"), 'w') {|f| f.write(file_content) }
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|