2
# -*- coding: utf-8 -*-
4
# Copyright (C) 2009 Daniel Fett
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
# Author: Daniel Fett advancedcaching@fragcom.de
27
class ParseError(Exception):
28
def __init__(self, errormsg, token = None):
36
class RunError(Exception):
37
def __init__(self, errormsg):
51
def __init__(self, core, pointprovider, userpointprovider, dataroot):
56
self.pointprovider = pointprovider
59
def write_settings(self, settings):
60
self.settings = settings
63
print "$ The command line interface is not fully implemented yet, feel"
64
print "$ free to contribute at git://github.com/webhamster/advancedcaching.git"
68
except ParseError as e:
70
print "# Parse Error at token '%s': " % sys.argv[self.nt - 1]
72
print "# Parse Error after Token '%s':" % sys.argv[e.token]
75
print "# Execution Error at token '%s': " % sys.argv[self.nt - 1]
79
def check_caches_retrieved(self):
80
if self.caches == None:
81
self.caches = self.pointprovider.get_all()
82
print "* retrieved all caches (%d) from database" % len(self.caches)
85
def parse_input (self):
86
while self.has_next():
87
if sys.argv[self.nt] == 'set':
89
if sys.argv[self.nt] == 'import':
91
elif sys.argv[self.nt] == 'sql':
93
elif sys.argv[self.nt] == 'filter':
95
elif sys.argv[self.nt] == 'do':
98
raise ParseError("Expected 'import', 'sql', 'filter' or 'do'", self.nt - 1)
103
if not self.has_next():
104
raise ParseError("Expected some options.")
105
while self.has_next():
106
token = sys.argv[self.nt]
108
if token == '--pass' or token == '--password':
109
password = self.parse_string()
110
self.set_password(password)
111
elif token == '--user' or token == '--username':
112
username = self.parse_string()
113
self.set_username(username)
115
raise ParseError("I don't understand '%s'" % token)
116
print "* Finished setting options. Exiting."
119
def parse_import(self):
121
if not self.has_next():
122
raise ParseError("Expected import actions.")
124
token = sys.argv[self.nt]
127
coord1 = self.parse_coord()
128
coord2 = self.parse_coord()
129
self.import_points(coord1, coord2)
130
elif token == '--around':
131
coord1 = self.parse_coord()
132
radius = self.parse_int()
133
self.import_points(coord1, radius)
141
if not self.has_next():
142
print "Table structure for geocaches:"
143
info = self.pointprovider.get_table_info()
145
print "\t".join([str(x) for x in row])
146
print "Example SQL-Query:"
147
print "SELECT * FROM geocaches WHERE type = 'multi' AND name LIKE 'GC1X%' AND found = 0 ORDER BY title DESC LIMIT 5"
148
raise ParseError("Expected sql string.")
149
text = self.parse_string()
150
self.caches = self.pointprovider.get_by_query(text)
153
def parse_filter(self):
154
self.check_caches_retrieved()
156
if not self.has_next():
157
raise ParseError("Expected filter options.")
158
while self.has_next():
159
token = sys.argv[self.nt]
162
coord1 = self.parse_coord()
163
coord2 = self.parse_coord()
164
self.add_filter_in(coord1, coord2)
165
elif token == '--around':
166
coord1 = self.parse_coord()
167
radius = self.parse_int()
168
self.add_filter_in(coord1, radius)
169
elif token == '--found' or token == '-f':
170
self.add_filter_found(True)
171
elif token == '--not-found' or token == '-F':
172
self.add_filter_found(False)
173
elif token == '-w' or token == '--was-downloaded':
174
self.add_filter_has_details(True)
175
elif token == '-s' or token == '--size':
176
op = self.parse_minmax()
177
size = self.parse_size()
178
self.add_filter_size(op, size)
179
elif token == '-d' or token == '--difficulty':
180
op = self.parse_minmax()
181
diff = self.parse_decimal()
182
self.add_filter_difficulty(op, diff)
183
elif token == '-t' or token == '--terrain':
184
op = self.parse_minmax()
185
terr = self.parse_decimal()
186
self.add_filter_terrain(op, terr)
187
elif token == '-T' or token == '--type':
188
types = self.parse_types()
189
self.add_filter_types(types)
190
elif token == '-o' or token == '--owner':
191
owner = self.parse_string()
192
self.add_filter_owner(owner)
193
elif token == '-n' or token == '--name':
194
name = self.parse_string()
195
self.add_filter_name (name)
196
elif token == '-i' or token == '--id':
197
id = self.parse_string()
198
self.add_filter_id (id)
199
elif token == '--new':
200
self.caches = self.new_caches
206
def parse_actions(self):
207
self.check_caches_retrieved()
209
if not self.has_next():
210
raise ParseError("Expected actions.")
211
while self.has_next():
212
token = sys.argv[self.nt]
214
if token == '--print':
216
elif token == '--fetch-details':
217
self.action_fetch_details()
218
elif token == '--export-html':
219
folder = self.parse_string()
220
self.action_export_html(folder)
221
elif token == '--export-gpx':
222
folder = self.parse_string()
223
self.action_export_gpx(folder)
224
elif token == '--export-single-gpx':
225
filename = self.parse_string()
226
self.action_export_single_gpx(filename)
227
elif token == '--draw-map':
228
zoom = self.parse_integer()
229
filename = self.parse_string()
230
self.action_draw_map(zoom, filename)
231
elif token == '--draw-maps':
232
zoom = self.parse_integer()
233
folder = self.parse_string()
234
self.action_draw_maps(zoom, folder)
235
elif token == '--command':
236
cmd = self.parse_string()
237
self.action_command(cmd)
239
raise ParseError("Unknown export action: %s" % token)
242
# if we have 5 tokens
243
# then 1..4 are valid tokens (0 is command)
245
# so we have a next token, if nt < len(tokens)-1
246
return (self.nt < len(sys.argv))
248
def parse_coord(self):
249
if not self.has_next():
250
raise ParseError("Expected Coordinate but there was none.", self.nt-1)
251
text = sys.argv[self.nt]
253
if text.startswith('q:'):
256
c = self.core.get_coord_by_name(query)
257
except Exception as e:
261
c = geo.try_parse_coordinate(text)
262
except Exception as e:
267
def parse_string(self):
268
if not self.has_next():
269
raise ParseError("Expected some Input, found nothing", self.nt-1)
270
text = sys.argv[self.nt]
275
if not self.has_next():
276
raise ParseError("Expected a number, found nothing.", self.nt-1)
277
text = sys.argv[self.nt]
281
def parse_size(self):
282
if not self.has_next():
283
raise ParseError("Expected a size (1..4/micro/small/regular/huge/other), found nothing.", self.nt-1)
284
text = sys.argv[self.nt].lower()
286
if text in ['1', '2', '3', '4', '5']:
288
elif text == 'micro':
290
elif text == 'small':
292
elif text in ['normal', 'regular']:
294
elif text in ['huge', 'big']:
296
elif text == 'other':
299
raise ParseError('Unknown size: %s' % text)
301
def parse_types(self):
302
if not self.has_next():
303
raise ParseError("Expected geocache type, found not even an electronic sausage.", self.nt-1)
304
text = sys.argv[self.nt].lower()
307
types = text.split(',')
310
if i in geocaching.GeocacheCoordinate.TYPES:
313
raise ParseError("Unknown Type: %s (expected one of: %s)" % (i, ', '.join(geocaching.GeocacheCoordinate.TYPES)))
317
def parse_operator(self):
318
text = sys.argv[self.nt]
328
def parse_decimal(self):
329
if not self.has_next():
330
raise ParseError("Expected a number", self.nt - 1)
331
text = sys.argv[self.nt]
333
return 10 * float(text)
335
raise ParseError("Could not parse '%s' as a valid number." % text)
337
def set_username(self, string):
338
self.settings['options_username'] = string
339
self.core.on_config_changed(self.settings)
341
def set_password(self, string):
342
self.settings['options_password'] = string
343
self.core.on_config_changed(self.settings)
345
def import_points(self, c1, c2):
346
if isinstance(c2, geo.Coordinate):
347
print "* Downloading Caches between %s and %s" % (c1, c2)
348
self.caches, self.new_caches = self.core.on_download((c1, c2))
350
# try to calculate some points northwest and southeast to the
351
# given point with approximately correct distances
352
new_c1 = c1.transform(-45, c2 * 1000 * math.sqrt(2))
353
new_c2 = c1.transform(-45 + 180, c2 * 1000 * math.sqrt(2))
354
print "* Downloading Caches in %d km distance to %s" % (c2, c1)
355
print "* Approximation: Caches between %s and %s" % (new_c1, new_c2)
356
self.caches, self.new_caches = self.core.on_download((new_c1, new_c2))
359
def add_filter_in(self, coord1, coord2):
360
if isinstance(coord2, geo.Coordinate):
361
self.caches = filter(lambda x: self.filter_in(coord1, coord2, x), self.caches)
363
self.caches = filter(lambda x: self.filter_in_radius(coord1, coord2, x), self.caches)
364
print "* filter in radius/coordinates: %d left" % len(self.caches)
366
def filter_in(self, c1, c2, check):
367
return (check.lat > min(c1.lat, c2.lat)
368
and check.lat < max(c1.lat, c2.lat)
369
and check.lon > min(c1.lon, c2.lon)
370
and check.lon < max(c1.lon, c2.lon))
373
def filter_in_radius(self, coord1, radius, check):
374
return check.distance_to(coord1) <= radius * 1000
376
def add_filter_found(self, found):
377
self.caches = filter(lambda x: x.found == found, self.caches)
378
print "* filter width found: %d left" % len(self.caches)
380
def add_filter_has_details(self, has_details):
381
self.caches = filter(lambda x: x.was_downloaded() == has_details, self.caches)
382
print "* filter with 'has details': %d left" % len(self.caches)
384
def add_filter_size(self, op, size):
386
self.caches = filter(lambda x: x.size == size, self.caches)
388
self.caches = filter(lambda x: x.size >= size, self.caches)
390
self.caches = filter(lambda x: x.size <= size, self.caches)
392
raise RunError("What Happen? Somebody set us up the geocache.")
393
print "* filter with size: %d left" % len(self.caches)
395
def add_filter_difficulty(self, op, diff):
397
self.caches = filter(lambda x: x.diff == diff, self.caches)
399
self.caches = filter(lambda x: x.diff >= diff, self.caches)
401
self.caches = filter(lambda x: x.diff <= diff, self.caches)
403
raise RunError("What Happen? Somebody set us up the geocache.")
404
print "* filter with difficulty: %d left" % len(self.caches)
406
def add_filter_terrain(self, op, terr):
408
self.caches = filter(lambda x: x.terr == terr, self.caches)
410
self.caches = filter(lambda x: x.terr >= terr, self.caches)
412
self.caches = filter(lambda x: x.terr <= terr, self.caches)
414
raise RunError("What Happen? Somebody set us up the geocache.")
415
print "* filter with terrain: %d left" % len(self.caches)
417
def add_filter_types(self, types):
418
self.caches = filter(lambda x: x.type in types, self.caches)
419
print "* filter with types: %d left" % len(self.caches)
421
def add_filter_owner(self, owner):
422
self.caches = filter(lambda x: owner.lower() in x.owner.lower(), self.caches)
423
print "* filter with owner: %d left" % len(self.caches)
425
def add_filter_name (self, name):
426
self.caches = filter(lambda x: name.lower() in x.title.lower(), self.caches)
427
print "* filter with name: %d left" % len(self.caches)
429
def add_filter_id (self, idstring):
430
self.caches = filter(lambda x: idstring.lower() in x.name.lower(), self.caches)
431
print "* filter with id: %d left" % len(self.caches)
433
def action_print (self):
434
for c in self.caches:
435
print "%s\t%s (%s)" % (c.name, c.title, c.type)
437
def action_fetch_details(self):
439
for c in self.caches:
440
print "* (%d of %d)\tDownloading '%s'" % (i, len(self.caches), c.title)
441
self.core.on_download_cache(c)
444
def action_export_html(self, folder):
446
for c in self.caches:
447
print "* (%d of %d)\tExporting '%s'" % (i, len(self.caches), c.title)
448
self.core.on_export_cache(c, folder)
451
def action_export_gpx(self):
454
def action_command(self, commandline):
456
if len(self.caches) == 0:
457
print "* Not running command (no geocaches left)"
459
list = " -- ".join(["%s (%s)" % (a.title, a.type) for a in self.caches])
460
if not isinstance(list, str):
461
list = unicodedata.normalize('NFKD', list).encode('ascii','ignore')
462
os.system(commandline % ('"%s"' % list.encode('string-escape')))
464
def set_download_progress(self, some, thing):
467
def hide_progress(self):
470
def show_error(self, message):
471
raise RunError(message)