Skip to content

Commit 933e175

Browse files
author
jorges
committed
GCJ Command Line v1.0-beta4, released 2011-06-03
[Feature]: Source files specified in the configuration files and using the -a option in gcj_submit_solution.py can now be glob patterns. [Feature]: In the tools/ directory there is an update_hashbangs.py script to change the hashbang line of a bulk of python files using a single command. [Fix]: --base_dir option was not working in gcj_submit_solution.py. [Fix]: Default value for --base_dir was not following symlinks properly. [Fix]: User status script was crashing when the user is not logged in or did not participate in the contest. [Fix]: Configuration file reading was crashing when spaces were inserted before the opening brace. A fix was made to clean the configuration file before attempting to parse it.
1 parent 2aac83b commit 933e175

12 files changed

+212
-36
lines changed

CHANGES

+21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
-------------------------------------------------------------------------------
2+
GCJ Command Line v1.0-beta4, released 2011-06-03
3+
4+
[Feature]: Source files specified in the configuration files and using the -a
5+
option in gcj_submit_solution.py can now be glob patterns.
6+
7+
[Feature]: In the tools/ directory there is an update_hashbangs.py script to
8+
change the hashbang line of a bulk of python files using a single
9+
command.
10+
11+
[Fix]: --base_dir option was not working in gcj_submit_solution.py.
12+
13+
[Fix]: Default value for --base_dir was not following symlinks properly.
14+
15+
[Fix]: User status script was crashing when the user is not logged in or did
16+
not participate in the contest.
17+
18+
[Fix]: Configuration file reading was crashing when spaces were inserted before
19+
the opening brace. A fix was made to clean the configuration file before
20+
attempting to parse it.
21+
122
-------------------------------------------------------------------------------
223
GCJ Command Line v1.0-beta3, released 2011-05-12
324

README

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
GCJ Commandline submit tool, v1.0-beta3
1+
GCJ Commandline submit tool, v1.0-beta4
22
Copyright 2010 Google Inc. All Rights Reserved.
33

44
Download and submit tool for the Google Code Jam
@@ -127,8 +127,8 @@ which will download the input file and store it in source/A-small-0.in. Now
127127
it is time to run your solution and generate the output file.
128128

129129
NOTE: This script downloads an input file for the specified problem and starts
130-
its timer without asking a confirmation. You should double-check the parameters
131-
before running it or you might start a timer for another problem.
130+
its timer without asking for confirmation. You should double-check the
131+
parameters before running it or you might start a timer for another problem.
132132

133133
NOTE: Downloading an input file twice in the same attempt will just redownload
134134
the same input file if its timer has *NOT* expired. Otherwise, it will download
@@ -155,9 +155,9 @@ to submit two extra files called library.cpp and template.txt you can execute:
155155
$ ./gcj_submit_solution.py -a library.cpp -a template.txt A small 0
156156

157157
which will submit the file source/A-small-0.out as the answer and
158-
source/A-small-0.cpp and library.cpp as the source files. If you add a
159-
directory using the -a option, the program will compress it into a zip file
160-
before submitting it.
158+
source/A-small-0.cpp, library.cpp and template.txt as the source files. If you
159+
add a directory using the -a option, the program will compress it into a zip
160+
file before submitting it.
161161

162162
After submitting the file, the Code Jam server will answer with a message
163163
string. The most important values are:
@@ -176,7 +176,7 @@ string. The most important values are:
176176

177177
During the contest you can check your status by using the gcj_get_status.py
178178
script, which will show you your current rank, number of points, time at which
179-
input has been solved, number of wrong attempts per input and remaining time for
179+
each input was solved, number of wrong attempts per input and remaining time for
180180
current attempts.
181181

182182
To run this script you should execute:

__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/python2
1+
#!/usr/bin/env python2
22
# -*- coding: utf-8 -*-
33
#
44
# Copyright 2011 Google Inc.

gcj_clear_contest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def main():
4747
parser.add_option('--base_dir', action='store', dest='base_dir',
4848
help=('Base directory used to parametrize configuration '
4949
'file paths'))
50-
parser.set_defaults(base_dir=os.path.dirname(__file__))
50+
parser.set_defaults(base_dir=os.path.dirname(os.path.realpath(__file__)))
5151
options, args = parser.parse_args()
5252

5353
# Store the script location in a runtime constant, so it can be used by

gcj_download_input.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def main():
6767
help=('Base directory used to parametrize configuration '
6868
'file paths'))
6969
parser.set_defaults(renew_cookie=False, force=False,
70-
base_dir=os.path.dirname(__file__))
70+
base_dir=os.path.dirname(os.path.realpath(__file__)))
7171
options, args = parser.parse_args()
7272

7373
# Store the script location in a runtime constant, so it can be used by

gcj_get_status.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ def main():
9494
parser.add_option('--base_dir', action='store', dest='base_dir',
9595
help=('Base directory used to parametrize configuration '
9696
'file paths'))
97-
parser.set_defaults(renew_cookie=False, base_dir=os.path.dirname(__file__))
97+
parser.set_defaults(renew_cookie=False,
98+
base_dir=os.path.dirname(os.path.realpath(__file__)))
9899
options, args = parser.parse_args()
99100

100101
# Store the script location in a runtime constant, so it can be used by

gcj_init_contest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def main():
4646
parser.add_option('--base_dir', action='store', dest='base_dir',
4747
help=('Base directory used to parametrize configuration '
4848
'file paths'))
49-
parser.set_defaults(base_dir=os.path.dirname(__file__))
49+
parser.set_defaults(base_dir=os.path.dirname(os.path.realpath(__file__)))
5050
options, args = parser.parse_args()
5151

5252
# Store the script location in a runtime constant, so it can be used by

gcj_renew_login.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def main():
4646
parser.add_option('--base_dir', action='store', dest='base_dir',
4747
help=('Base directory used to parametrize configuration '
4848
'file paths'))
49-
parser.set_defaults(base_dir=os.path.dirname(__file__))
49+
parser.set_defaults(base_dir=os.path.dirname(os.path.realpath(__file__)))
5050
options, args = parser.parse_args()
5151

5252
# Store the script location in a runtime constant, so it can be used by

gcj_submit_solution.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def main():
9393
parser.set_defaults(renew_login=False, force=False, gzip_content=True,
9494
zip_sources=False, ignore_zip=False,
9595
ignore_def_source=False,
96-
base_dir=os.path.dirname(__file__))
96+
base_dir=os.path.dirname(os.path.realpath(__file__)))
9797
options, args = parser.parse_args()
9898

9999
# Store the script location in a runtime constant, so it can be used by

lib/output_submitter.py

+28-22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020

2121

22+
import glob
2223
import httplib
2324
import json
2425
import os
@@ -51,14 +52,16 @@ def __init__(self, host, cookie, middleware_token, contest_id, problem_id):
5152
self.contest_id = contest_id
5253
self.problem_id = problem_id
5354

54-
def _PrepareSourceFiles(self, source_names):
55+
def _PrepareSourceFiles(self, source_patterns):
5556
"""Zip all source directories into files and return them.
5657
5758
The returned list will contain (filename, file_data) tuples, where file_data
5859
will be None for files that have not been read yet.
5960
6061
Args:
61-
source_names: List with all the files to include with the solution.
62+
source_patterns: List with all the file patterns to include with the
63+
solution. These file patterns will be expanded using python's glob
64+
module.
6265
6366
Returns:
6467
A (source_files, ignored_zips) tuple, where source_files is a list of
@@ -70,23 +73,25 @@ def _PrepareSourceFiles(self, source_names):
7073
source_files = []
7174
ignored_zips = set()
7275

73-
# Process each source specified by the user.
74-
for source in source_names:
75-
# Check if the source is a directory or a file.
76-
if os.path.isdir(source):
77-
# Create a zip file in memory for the directory, add it to the source
78-
# files and update the ignored_zips set.
79-
sys.stdout.write('Compressing directory "{0}"...\n'.format(source))
80-
zipped_contents, newly_ignored_zips = (
81-
zip_utils.MakeZipFileInMemory([source], ignore_exts=['.zip']))
82-
ignored_zips.update(newly_ignored_zips)
83-
flat_source_name = source.replace('\\', '_').replace('/', '_')
84-
zip_filename = '{1}_{0}.zip'.format(random.randrange(0, 2**31 - 1),
85-
flat_source_name)
86-
source_files.append((zip_filename, zipped_contents))
87-
else:
88-
# Add files directly to the prepared sources.
89-
source_files.append((source, None))
76+
# Process each source pattern specified by the user, expanding them using
77+
# the python's glob module.
78+
for source_pattern in source_patterns:
79+
for source in glob.iglob(source_pattern):
80+
# Check if the source is a directory or a file.
81+
if os.path.isdir(source):
82+
# Create a zip file in memory for the directory, add it to the source
83+
# files and update the ignored_zips set.
84+
sys.stdout.write('Compressing directory "{0}"...\n'.format(source))
85+
zipped_contents, newly_ignored_zips = (
86+
zip_utils.MakeZipFileInMemory([source], ignore_exts=['.zip']))
87+
ignored_zips.update(newly_ignored_zips)
88+
flat_source_name = source.replace('\\', '_').replace('/', '_')
89+
zip_filename = '{1}_{0}.zip'.format(random.randrange(0, 2**31 - 1),
90+
flat_source_name)
91+
source_files.append((zip_filename, zipped_contents))
92+
else:
93+
# Add files directly to the prepared sources.
94+
source_files.append((source, None))
9095

9196
# Return all generated sets.
9297
return source_files, ignored_zips
@@ -123,15 +128,16 @@ def _ParseResult(self, response_data, input_public):
123128
'cannot submit solution. Check that the host, '
124129
'user and contest id are valid: {0}.\n'.format(e))
125130

126-
def Submit(self, input_id, output_name, source_names, input_public,
131+
def Submit(self, input_id, output_name, source_patterns, input_public,
127132
gzip_body=True, zip_sources=False, add_ignored_zips=False):
128133
"""Submit the specified output and sources file to the problem.
129134
130135
Args:
131136
input_id: Identifier of the output to submit ('0' for the small output,
132137
'1' for the large output).
133138
output_name: Name of the file with the output data.
134-
source_names: Names of the source files to be included with the output.
139+
source_patterns: Name patterns of the source files to be included with the
140+
output. These patterns will be expanded using Python's glob module.
135141
input_public: Boolean indicating whether the answer is public or not.
136142
gzip_body: Boolean indicating whether the body has to be gzipped or not.
137143
zip_sources: Boolean indicating whether all sources should be put inside a
@@ -150,7 +156,7 @@ def Submit(self, input_id, output_name, source_names, input_public,
150156
# Prepare the source files (zipping all directories). After this,
151157
# source_files will only contain text files and zip files specified directly
152158
# or by compressing a directory.
153-
source_files, ignored_zips = self._PrepareSourceFiles(set(source_names))
159+
source_files, ignored_zips = self._PrepareSourceFiles(set(source_patterns))
154160

155161
# Check if the user requested to zip source files.
156162
if zip_sources:

tools/__init__.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python2
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright 2011 Google Inc.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+

tools/update_hashbangs.py

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright 2011 Google Inc.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
"""This file implements the a tool to change the hashbang line of a bulk of
19+
python files."""
20+
21+
22+
23+
import optparse
24+
import os
25+
import sys
26+
27+
28+
def ChangeFileHashbang(filename, new_hashbang_line):
29+
"""Change the specified file hashbang line.
30+
31+
This function assumes that the specified filename is a python file.
32+
33+
Args:
34+
filename: String with the name of the file to modify.
35+
new_hashbang_line: String with the new hashbang line to put in the file.
36+
"""
37+
# Open the file and read all lines from it.
38+
with open(filename, 'rt') as file_obj:
39+
file_lines = file_obj.readlines()
40+
41+
# Substitute the first line if it's an existing hashbang. Otherwise add the
42+
# new hashbang at the top of the file.
43+
complete_hashbang_line = '#!{0}\n'.format(new_hashbang_line.strip())
44+
if len(file_lines) > 0 and file_lines[0].startswith('#!'):
45+
file_lines[0] = complete_hashbang_line
46+
else:
47+
file_lines[0:1] = [complete_hashbang_line]
48+
49+
# Write the processed lines to the file.
50+
with open(filename, 'wt') as file_obj:
51+
file_obj.write(''.join(file_lines))
52+
53+
54+
def ProcessFile(target, new_hashbang_line):
55+
"""Change the hashbang line of the specified target if it's a python file.
56+
57+
Args:
58+
target: String with the file to modify.
59+
new_hashbang_line: String with the new hashbang line to put in the target.
60+
"""
61+
# Only process a target if it's a python file.
62+
if os.path.isfile(target):
63+
extension = os.path.splitext(target)[1]
64+
if extension == '.py':
65+
print 'Adding hashbang to {0}.'.format(target)
66+
ChangeFileHashbang(target, new_hashbang_line)
67+
else:
68+
print 'Ignoring {0}, it\'s not a python file.'.format(target)
69+
elif not os.path.isdir(target):
70+
print 'Ignoring {0}, it\'s not a file.'.format(target)
71+
72+
73+
def ProcessTarget(target, new_hashbang_line, recursive):
74+
"""Process a target, which might be a directory or a regular file.
75+
76+
Args:
77+
target: Name of the directory or file to process.
78+
new_hashbang_line: String with the new hashbang line to put in the target.
79+
recursive: Flag indicating whether directories should be processed
80+
recursively or not.
81+
"""
82+
# Process directories recursively or not depending on the recursive flag, if
83+
# the recursive flag is False then only directly inside the target are
84+
# processed. Target files are processed directly.
85+
target_files = []
86+
if os.path.isdir(target):
87+
if recursive:
88+
for dirpath, dirnames, filenames in os.walk(target):
89+
target_files.extend(os.path.join(dirpath, filename)
90+
for filename in filenames)
91+
else:
92+
target_files.extend(os.path.join(target, filename)
93+
for filename in os.listdir(target))
94+
else:
95+
target_files.append(target)
96+
97+
# Process each target file independently.
98+
for filename in target_files:
99+
ProcessFile(filename, new_hashbang_line)
100+
101+
102+
def main():
103+
"""Main function for the update hashbang script.
104+
105+
This script receives two positional arguments or: the new hashbang to assign
106+
to all python files and a list of one or more directories with all the files
107+
to modify.
108+
"""
109+
# Create an option parser and use it to parse the supplied arguments.
110+
program_version = 'GCJ hashbang tool'
111+
parser = optparse.OptionParser(usage='%prog [options]',
112+
version=program_version)
113+
parser.add_option('-r', action='store_true', dest='recursive',
114+
help=('Process all directories recursively'))
115+
parser.set_defaults(recursive=False)
116+
options, args = parser.parse_args()
117+
118+
# Check that the number of arguments is valid.
119+
if len(args) < 2:
120+
sys.stderr.write('need at least two positional arguments\n')
121+
sys.exit(1)
122+
123+
# Modify all targets applying the new hashbang line.
124+
new_hashbang_line = args[0]
125+
for target in args[1:]:
126+
ProcessTarget(target, new_hashbang_line, options.recursive)
127+
128+
129+
if __name__ == '__main__':
130+
main()

0 commit comments

Comments
 (0)