annotate 0.2.0/bin/dl_pdg_metadata.py @ 0:9e8b1c747a6a draft default tip

planemo upload
author galaxytrakr
date Fri, 29 May 2026 13:32:17 +0000
parents
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
1 #!/usr/bin/env python3
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
2
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
3 # Kranti Konganti
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
4
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
5 import argparse
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
6 import inspect
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
7 import logging
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
8 import os
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
9 import re
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
10 import shutil
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
11 import tempfile
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
12 from html.parser import HTMLParser
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
13 from urllib.request import urlopen
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
14
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
15 # Set logging format.
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
16 logging.basicConfig(
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
17 format="\n" + "=" * 55 + "\n%(asctime)s - %(levelname)s\n" + "=" * 55 + "\n%(message)s\n",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
18 level=logging.DEBUG,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
19 )
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
20
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
21
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
22 # Multiple inheritence for pretty printing of help text.
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
23 class MultiArgFormatClasses(argparse.RawTextHelpFormatter, argparse.ArgumentDefaultsHelpFormatter):
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
24 pass
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
25
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
26
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
27 # HTMLParser override class to get PDG release and latest Cluster .tsv file
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
28 class NCBIPathogensHTMLParser(HTMLParser):
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
29 def __init__(self, *, convert_charrefs: bool = ...) -> None:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
30 super().__init__(convert_charrefs=convert_charrefs)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
31 self.reset()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
32 self.href_data = list()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
33
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
34 def handle_data(self, data):
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
35 self.href_data.append(data)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
36
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
37
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
38 def dl_pdg(**kwargs) -> None:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
39 """
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
40 Method to save the PDG metadata file and
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
41 return the latest PDG release.
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
42 """
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
43 db_path, url, regex, suffix, overwrite, release = [kwargs[k] for k in kwargs.keys()]
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
44
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
45 if (db_path or url) == None:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
46 logging.error("Please provide absolute UNIX path\n" + "to store the result DB flat files.")
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
47 exit(1)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
48
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
49 if re.match(r"^PDG\d+\.\d+$", release):
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
50 url = re.sub("latest_snps", release.strip(), url)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
51
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
52 html_parser = NCBIPathogensHTMLParser()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
53 logging.info(f"Finding latest NCBI PDG release at:\n{url}")
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
54
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
55 with urlopen(url) as response:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
56 with tempfile.NamedTemporaryFile(delete=False) as tmp_html_file:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
57 shutil.copyfileobj(response, tmp_html_file)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
58
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
59 with open(tmp_html_file.name, "r") as html:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
60 html_parser.feed("".join(html.readlines()))
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
61
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
62 pdg_filename = re.search(regex, "".join(html_parser.href_data)).group(0)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
63 pdg_release = pdg_filename.rstrip(suffix)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
64 pdg_metadata_url = "/".join([url, pdg_filename])
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
65 pdg_release = pdg_filename.rstrip(suffix)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
66 dest_dir = os.path.join(db_path, pdg_release)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
67
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
68 logging.info(f"Found NCBI PDG file:\n{pdg_metadata_url}")
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
69
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
70 if (
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
71 not overwrite
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
72 and re.match(r".+?\.metadata\.tsv$", pdg_filename)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
73 and os.path.exists(dest_dir)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
74 ):
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
75 logging.error(f"DB path\n{dest_dir}\nalready exists. Please use -f to overwrite.")
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
76 exit(1)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
77 elif overwrite and not re.match(r".+?\.reference_target\.cluster_list\.tsv$", pdg_filename):
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
78 shutil.rmtree(dest_dir, ignore_errors=True) if os.path.exists(dest_dir) else None
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
79 os.makedirs(dest_dir)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
80 elif (
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
81 not overwrite
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
82 and re.match(r".+?\.metadata\.tsv$", pdg_filename)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
83 and not os.path.exists(dest_dir)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
84 ):
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
85 os.makedirs(dest_dir)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
86
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
87 tsv_at = os.path.join(dest_dir, pdg_filename)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
88 logging.info(f"Saving to:\n{tsv_at}")
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
89
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
90 with urlopen(pdg_metadata_url) as response:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
91 with open(tsv_at, "w") as tsv:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
92 tsv.writelines(response.read().decode("utf-8"))
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
93
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
94 html.close()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
95 tmp_html_file.close()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
96 os.unlink(tmp_html_file.name)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
97 tsv.close()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
98 response.close()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
99
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
100 return tsv_at, dest_dir
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
101
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
102
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
103 def main() -> None:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
104 """
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
105 This script is part of the `cronology_db` Nextflow workflow and is only
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
106 tested on POSIX sytems.
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
107 It:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
108 1. Downloads the latest NCBI Pathogens Release metadata file, which
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
109 looks like PDGXXXXXXXXXX.2504.metadata.csv and also the SNP cluster
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
110 information file which looks like PDGXXXXXXXXXX.2504.reference_target.cluster_list.tsv
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
111 2. Generates a new metadata file with only required information such as
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
112 computed_serotype, isolates GenBank or RefSeq downloadable genome FASTA
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
113 URL.
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
114 """
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
115
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
116 prog_name = os.path.basename(inspect.stack()[0].filename)
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
117
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
118 parser = argparse.ArgumentParser(
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
119 prog=prog_name, description=main.__doc__, formatter_class=MultiArgFormatClasses
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
120 )
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
121
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
122 required = parser.add_argument_group("required arguments")
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
123
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
124 parser.add_argument(
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
125 "-db",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
126 dest="db_path",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
127 default=os.getcwd(),
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
128 required=False,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
129 help="Absolute UNIX path to a path where all results files are\nstored.",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
130 )
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
131 parser.add_argument(
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
132 "-f",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
133 dest="overwrite_db",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
134 default=False,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
135 required=False,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
136 action="store_true",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
137 help="Force overwrite a PDG release directory at DB path\nmentioned with -db.",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
138 )
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
139 parser.add_argument(
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
140 "-org",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
141 dest="organism",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
142 default="Cronobacter",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
143 required=False,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
144 help="The organism to create the DB flat files\nfor.",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
145 )
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
146 required.add_argument(
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
147 "-rel",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
148 dest="release",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
149 default=False,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
150 required=False,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
151 help="If you get a 404 error, try mentioning the actual release identifier.\n"
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
152 + "Ex: For Cronobacter, you can get the release identifier by going to:\n"
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
153 + " https://ftp.ncbi.nlm.nih.gov/pathogen/Results/Cronobacter\n"
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
154 + "Ex: If you want metadata beloginging to release PDG000000002.2507, then you\n"
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
155 + " would use this command-line option as:\n -rel PDG000000002.2507",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
156 )
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
157
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
158 args = parser.parse_args()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
159 db_path = args.db_path
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
160 org = args.organism
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
161 overwrite = args.overwrite_db
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
162 release = args.release
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
163 ncbi_pathogens_loc = "/".join(
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
164 ["https://ftp.ncbi.nlm.nih.gov/pathogen/Results", org, "latest_snps"]
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
165 )
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
166
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
167 if not db_path:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
168 db_path = os.getcwd()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
169
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
170 # Save metadata
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
171 file, dest_dir = dl_pdg(
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
172 db_path=db_path,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
173 url="/".join([ncbi_pathogens_loc, "Metadata"]),
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
174 regex=re.compile(r"PDG\d+\.\d+\.metadata\.tsv"),
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
175 suffix=".metadata.tsv",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
176 overwrite=overwrite,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
177 release=release,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
178 )
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
179
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
180 # Save cluster to target mapping
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
181 dl_pdg(
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
182 db_path=db_path,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
183 url="/".join([ncbi_pathogens_loc, "Clusters"]),
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
184 regex=re.compile(r"PDG\d+\.\d+\.reference_target\.cluster_list\.tsv"),
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
185 suffix="reference_target\.cluster_list\.tsv",
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
186 overwrite=overwrite,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
187 release=release,
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
188 )
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
189
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
190 # Create accs.txt for dataformat to fetch required ACC fields
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
191 accs_file = os.path.join(dest_dir, "accs_all.txt")
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
192 with open(file, "r") as pdg_metadata_fh:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
193 with open(accs_file, "w") as accs_fh:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
194 for line in pdg_metadata_fh.readlines():
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
195 if re.match(r"^#", line) or line in ["\n", "\n\r", "\r"]:
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
196 continue
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
197 cols = line.strip().split("\t")
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
198 asm_acc = cols[9]
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
199 accs_fh.write(f"{asm_acc}\n") if (asm_acc != "NULL") else None
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
200 accs_fh.close()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
201 pdg_metadata_fh.close()
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
202
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
203 logging.info("Finished writing accessions for dataformat tool.")
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
204
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
205
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
206 if __name__ == "__main__":
9e8b1c747a6a planemo upload
galaxytrakr
parents:
diff changeset
207 main()