--- /dev/null
+all:
+ python2.5 setup.py build
+clean:
+ python2.5 setup.py clean --all
+install:
+ python2.5 setup.py install --root $(DESTDIR)
--- /dev/null
+pystan for Debian
+-----------------
+
+<possible notes regarding this package - if none, delete this file>
+
+ --
+ <vincent.lark@gmail.com> Mon, 16 Mar 2009 07:58:38 +0100
--- /dev/null
+pystan (0.1-1) unstable; urgency=low
+
+ * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP>
+
+ -- Vincent Lark <vincent.lark@gmail.com> Mon, 16 Mar 2009 07:58:38 +0100
+
--- /dev/null
+Source: pystan
+Section: user/other
+Priority: optional
+Maintainer: Vincent Lark <vincent.lark@gmail.com>
+Build-Depends: debhelper (>= 5), python2.5-dev
+Standards-Version: 3.7.2
+
+Package: pystan
+Architecture: all
+Depends: python2.5, python2.5-hildon, python2.5-gtk2
+Description: View STAN schedules
+ View Stan (nancy, france) bus schedules
+XB-Maemo-Icon-26:
+ iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+g
+ vaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1gURDQoYya0JlwAAAU9J
+ REFUSMftlL1KA0EUhb/NZl/ggnHQxsJUxt5CUucVJCCkDfgyKdIGG5/A0s5HEBtJ
+ EdDAQGBgmw0YJmMzgXXYza5CtNkDW9zZw5z7c+ZCgwb/Ai3i9sVl/Bq8RIs4LRK1
+ gJDsKvJyNXmJMuYTsMoY1zpgozaABdYArQNPZQ1kfyGU7SpqVwxzAMwABWhgpIwp
+ 4vWBB+AUWAI3ypjnfEXtPU4bLKx9vErTeCeiRSYF+fTn1j5dp2myE9EiU+DSi3wX
+ ymeqRQAmZ3EcA5E/fgO6BULT8zhOcrwXoJdrXRa2Lgps2y2odAUcBUIXQdz78YyC
+ SldAp8b7+bXrIv91qjZBietqCc2DjbAt4b2WxJkyZljVujlwp0U0cPxuLcAIuC+4
+ dKxFlsDJarvdAGP/b6hFnDImYs+uG3hbO2AB3Jbsur63tQM+fFx3bzZocEB8AdV2
+ gJBZgKTwAAAAAElFTkSuQmCC
+
--- /dev/null
+This package was debianized by
+ <vincent.lark@gmail.com> on
+Mon, 16 Mar 2009 07:58:38 +0100.
+
+It was downloaded from <fill in http/ftp site>
+
+Upstream Author: <put author(s) name and email here>
+
+Copyright: <put the year(s) of the copyright, and the names of the
+ copyright holder(s) here>
+
+License:
+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this package; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+On Debian systems, the complete text of the GNU General
+Public License can be found in `/usr/share/common-licenses/GPL'.
+
+The Debian packaging is (C) 2009,
+ <vincent.lark@gmail.com> and
+is licensed under the GPL, see above.
+
+
+# Please also look if there are files or directories which have a
+# different copyright/license attached and list them here.
--- /dev/null
+usr/bin
+usr/sbin
--- /dev/null
+pystan_0.1-1_all.deb user/other optional
--- /dev/null
+Package: pystan
+Version: 0.1-1
+Section: user/other
+Priority: optional
+Architecture: all
+Depends: python2.5, python2.5-hildon, python2.5-gtk2
+Installed-Size: 140
+Maintainer: Vincent Lark <vincent.lark@gmail.com>
+Description: View STAN schedules
+ View Stan (nancy, france) bus schedules
+Maemo-Icon-26:
+ iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+g
+ vaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1gURDQoYya0JlwAAAU9J
+ REFUSMftlL1KA0EUhb/NZl/ggnHQxsJUxt5CUucVJCCkDfgyKdIGG5/A0s5HEBtJ
+ EdDAQGBgmw0YJmMzgXXYza5CtNkDW9zZw5z7c+ZCgwb/Ai3i9sVl/Bq8RIs4LRK1
+ gJDsKvJyNXmJMuYTsMoY1zpgozaABdYArQNPZQ1kfyGU7SpqVwxzAMwABWhgpIwp
+ 4vWBB+AUWAI3ypjnfEXtPU4bLKx9vErTeCeiRSYF+fTn1j5dp2myE9EiU+DSi3wX
+ ymeqRQAmZ3EcA5E/fgO6BULT8zhOcrwXoJdrXRa2Lgps2y2odAUcBUIXQdz78YyC
+ SldAp8b7+bXrIv91qjZBietqCc2DjbAt4b2WxJkyZljVujlwp0U0cPxuLcAIuC+4
+ dKxFlsDJarvdAGP/b6hFnDImYs+uG3hbO2AB3Jbsur63tQM+fFx3bzZocEB8AdV2
+ gJBZgKTwAAAAAElFTkSuQmCC
--- /dev/null
+f466787e3d793b02fcd02a845e2bd0ad usr/share/applications/hildon/pystan.desktop
+047cc0470ebd6f123bffcc95a2b1decb usr/share/doc/pystan/changelog.Debian
+1573ad5a1f2e997723daec8e3ba6a9eb usr/share/pixmaps/pystan_icon_26x26.png
+8c5ed46684f553197b9b34ae3e12352b usr/lib/python2.5/site-packages/pystan/pystan.py
+7e06be438b2120dd372435d8af7cb599 usr/lib/python2.5/site-packages/pystan/lib/timetable.py
+80d225b8f76996af038148d01df8ec7b usr/lib/python2.5/site-packages/pystan/lib/stan.pyc
+4717daecc957dacc7f4c535746345c47 usr/lib/python2.5/site-packages/pystan/lib/timetable_parser.pyc
+2cbfdb2885c9704e22d50bcebacd0815 usr/lib/python2.5/site-packages/pystan/lib/gui.pyc
+3ffa8db925d680e6d9de7794fa389949 usr/lib/python2.5/site-packages/pystan/lib/gui.py
+fb461ddfb6169b759ac33f69ee529d83 usr/lib/python2.5/site-packages/pystan/lib/stan.py
+d41d8cd98f00b204e9800998ecf8427e usr/lib/python2.5/site-packages/pystan/lib/__init__.py
+4910bdeea33e5438204b26925a520da0 usr/lib/python2.5/site-packages/pystan/lib/timetable_parser.py
+dd0b4563e07a766fa929a68a9367431e usr/lib/python2.5/site-packages/pystan/lib/timetable.pyc
+3b7e76444d1dbfb32dab30fd83be0e57 usr/lib/python2.5/site-packages/pystan/lib/__init__.pyc
+e1c78567dd054fa5b9a28c4fb0072d52 usr/lib/python2.5/site-packages/pystan/pystan.pyc
+68b329da9893e34099c7d8ad5cb9c940 usr/lib/python2.5/site-packages/pystan/__init__.py
+166934514c8c29a2d8926fd0d41a4d43 usr/lib/python2.5/site-packages/pystan/__init__.pyc
--- /dev/null
+# -*- coding: UTF-8 -*-
+
+import gtk
+import hildon
+import webbrowser
+
+from stan import STAN
+from timetable import StanTimetable
+
+class StanGUI(hildon.Program):
+ #
+ def __init__(self):
+ hildon.Program.__init__(self)
+ self.stan = STAN()
+ self.window = hildon.Window()
+ self.window.set_title('pySTAN')
+ self.window.connect("destroy", gtk.main_quit)
+ self.add_window(self.window)
+
+ self.tabs = self.main_tabs()
+ self.window.add(self.tabs)
+ #
+ self.last_station = False
+
+ def main_tabs(self):
+ tabs = gtk.Notebook()
+ tabs.set_tab_pos(gtk.POS_TOP)
+ tabs.append_page(self.tab_lines(), gtk.Label('Par lignes'))
+ #tabs.append_page(self.tab_stations(), gtk.Label('Par arrets'))
+ #tabs.append_page(self.tab_search(), gtk.Label('Recherche'))
+ return tabs
+ #
+ def tab_search(self):
+ tab_search = gtk.Frame(None) #'Recherche de trajet')
+
+ from_location = gtk.Entry()
+ from_location_label = gtk.Label(u"Départ")
+ from_city = gtk.Entry()
+ from_city_label = gtk.Label(u"Ã ")
+
+ to_location = gtk.Entry()
+ to_location_label = gtk.Label(u"Arrivée")
+ to_city = gtk.Entry()
+ to_city_label = gtk.Label(u"Ã ")
+
+ datetime_select = gtk.Calendar()
+ datetime_select.display_options(gtk.CALENDAR_SHOW_DAY_NAMES | gtk.CALENDAR_WEEK_START_MONDAY)
+
+ restriction = gtk.Combo()
+ restriction.set_popdown_strings([u"partir après", u"arriver avant"])
+
+ vbox = gtk.VBox()
+ vbox.add(from_location_label)
+ vbox.add(from_location)
+ vbox.add(from_city)
+ vbox.add(to_location_label)
+ vbox.add(to_location)
+ vbox.add(to_city)
+
+ vbox.add(datetime_select)
+ vbox.add(restriction)
+
+ tab_search.add(vbox)
+
+ return tab_search
+
+ def tab_lines(self):
+ tab_lines = tab_lines = gtk.Frame(None) #'Recherche par lignes')
+
+ list_lines = gtk.combo_box_new_text()
+ list_lines.connect('changed', self.show_line_timetable)
+
+ for line in self.stan.lines():
+ list_lines.append_text( '-'.join(line['numbers']) + "\t" + line['name'] )
+
+ vbox = gtk.VBox()
+
+ alignment_list = gtk.Alignment(xalign=0.0, yalign=0.0, xscale=1.0, yscale=0.10)
+ alignment_list.add(list_lines)
+ vbox.add(alignment_list)
+
+ self.timetable = StanTimetable()
+ self.timetable.create()
+ self.timetable.set_activated_callback(self.timetable_row_clicked)
+
+ alignment_tt = gtk.Alignment(xalign=0.0, yalign=0.0, xscale=1.0, yscale=0.90)
+ alignment_tt.add(self.timetable)
+ vbox.add(alignment_tt)
+
+ tab_lines.add(vbox)
+ return tab_lines
+
+ def show_line_timetable(self, selection):
+ self.timetable.load( self.stan.lines()[selection.get_active()], '08/03/2009', '12' )
+
+ def timetable_row_clicked(self, station_name):
+ if self.last_station != station_name:
+ if self.last_station != False:
+ self.tabs.remove(self.tab_map)
+ self.last_station = station_name
+ self.tab_map = self.tab_geomap(station=station_name)
+ self.tabs.append_page(self.tab_map, gtk.Label(station_name))
+ self.tabs.show_all()
+ self.tabs.set_current_page(1)
+
+ def tab_stations(self):
+ tab_stations = gtk.Frame(None) #'Recherche par arrets')
+
+ list_stations = gtk.Combo()
+ list_stations.set_popdown_strings([])
+
+ tab_stations.add(list_stations)
+
+ return tab_stations
+
+ def tab_geomap(self, station):
+ tab_map = gtk.Frame(None)
+ map_button = gtk.Button()
+ #map_label = gtk.Label(u"Link to map")
+ map_button.connect('clicked', self.open_map, 'http://www.google.fr')
+
+ tab_map.add(map_button)
+ return tab_map
+
+ def open_map(self, data, url):
+ webbrowser.open(url)
+
+ def run(self):
+ self.window.show_all()
+ gtk.main()
+
--- /dev/null
+# -*- coding: UTF-8 -*-
+
+import urllib2
+from urllib2 import URLError
+
+
+from timetable_parser import StanTimetableParser
+
+class STAN:
+
+ RSS_URL = 'http://www.reseau-stan.com/rss'
+
+ def lines(self):
+
+ return [
+ { 'id': 158,
+ 'name': u"Nancy République / Champigneulles",
+ 'numbers': ["321"]
+ },
+ { 'id': 144,
+ 'name': u"Nancy République / Bouxières-aux-Dames",
+ 'numbers': ["322"]
+ },
+ { 'id': 146,
+ 'name': u"Nancy République / Frouard Le Nid",
+ 'numbers': ["323"]
+ },
+ { 'id': 148,
+ 'name': u"Nancy République / Pompey Les Vannes",
+ 'numbers': ["324"]
+ },
+ { 'id': 150,
+ 'name': u"Nancy République / Pompey Fonds de Lavaux",
+ 'numbers': ["325"]
+ },
+ { 'id': 145,
+ 'name': u"Vandoeuvre Vélodrome / Chaligny",
+ 'numbers': ["511"]
+ },
+ { 'id': 147,
+ 'name': u"Vandoeuvre Vélodrome / Pont Saint-Vincent",
+ 'numbers': ["512"]
+ },
+ { 'id': 149,
+ 'name': u"Vandoeuvre Vélodrome / Messein",
+ 'numbers': ["514"]
+ },
+ { 'id': 151,
+ 'name': u"Nancy Place Carnot / Chaligny",
+ 'numbers': ["521"]
+ },
+ { 'id': 153,
+ 'name': u"Nancy Place Carnot / Pont Saint-Vincent",
+ 'numbers': ["522"]
+ },
+ { 'id': 155,
+ 'name': u"Nancy Place Carnot / Bainville-sur-Madon",
+ 'numbers': ["523"]
+ },
+ { 'id': 152,
+ 'name': u"Nancy République / Saint-Nicolas Le Nid",
+ 'numbers': ["621"]
+ },
+ { 'id': 157,
+ 'name': u"Nancy République / Saint-Nicolas République",
+ 'numbers': ["622"]
+ },
+ { 'id': 156,
+ 'name': u"Nancy République / Dombasle Stand",
+ 'numbers': ["623"]
+ },
+ { 'id': 154,
+ 'name': u"Nancy République / Dombasle Maroc",
+ 'numbers': ["624"]
+ },
+ { 'id': 126,
+ 'name': u"Vandoeuvre Parc des Expositions / Nancy République",
+ 'numbers': ["129"]
+ },
+ { 'id': 133,
+ 'name': u"Ligne 80",
+ 'numbers': ["80"]
+ },
+ { 'id': 138,
+ 'name': u"Vandoeuvre CHU Brabois / Essey Mouzimpré",
+ 'numbers': ["Tram 1"]
+ },
+ { 'id': 141,
+ 'name': u"Vandoeuvre CHU Brabois / Centre d\'accueil - Villers les Essarts",
+ 'numbers': ["Ptit Stan Vilers"]
+ },
+ { 'id': 143,
+ 'name': u"Vandoeuvre CHU Brabois / Vandoeuvre Nations - Médecine préventive ",
+ 'numbers': ["Ptit Stan Vandoeuvre"]
+ },
+ { 'id': 140,
+ 'name': u"Laxou Sapinière / Laxou Provinces",
+ 'numbers': ["Ptit Stan Laxou"]
+ },
+ { 'id': 142,
+ 'name': u"Nancy Carnot",
+ 'numbers': ["Ptit Stan Colline"]
+ },
+ { 'id': 134,
+ 'name': u"Malzéville Sadi Carnot - Nancy Cimetière du Sud",
+ 'numbers': ["Minibus"]
+ },
+ { 'id': 159,
+ 'name': u"StanU",
+ 'numbers': ["110"]
+ },
+ { 'id': 112,
+ 'name': u"Maxéville Champ le Boeuf - Nancy Haut du Lièvre / Jarville Sion",
+ 'numbers': ["111", "112"]
+ },
+ { 'id': 96,
+ 'name': u"Houdemont / Vandoeuvre Charmois",
+ 'numbers': ["114"]
+ },
+ { 'id': 98,
+ 'name': u"Ludres / Villers Lycée Stanislas",
+ 'numbers': ["115"]
+ },
+ { 'id': 101,
+ 'name': u"Fléville Fleurychamp - Heillecourt / Villers Lycée Stanislas",
+ 'numbers': ["116"]
+ },
+ { 'id': 103,
+ 'name': u"Nancy Beauregard / Nancy République",
+ 'numbers': ["121"]
+ },
+ { 'id': 135,
+ 'name': u"Villers Clairlieu / Nancy République",
+ 'numbers': ["122", "126"]
+ },
+ { 'id': 106,
+ 'name': u"Vandoeuvre Nations / Nancy République",
+ 'numbers': ["123"]
+ },
+ { 'id': 109,
+ 'name': u"Laxou Mouzon / Nancy République",
+ 'numbers': ["124"]
+ },
+ { 'id': 118,
+ 'name': u"Laxou Provinces / Seichamps",
+ 'numbers': ["130"]
+ },
+ { 'id': 95,
+ 'name': u"Maxéville Mairie / Jarville Sion - Heillecourt - Fléville De la Noue",
+ 'numbers': ["131", "132", "133"]
+ },
+ { 'id': 97,
+ 'name': u"Villers Lycée Stanislas / Maxéville Lafayette - Malzéville Savlons",
+ 'numbers': ["134", "135"]
+ },
+ { 'id': 100,
+ 'name': u"Nancy Cours Léopold / Laneuveville - La Madeleine",
+ 'numbers': ["136", "137"]
+ },
+ { 'id': 102,
+ 'name': u"Laxou Champ le Boeuf / Vandoeurvre Roberval",
+ 'numbers': ["138"]
+ },
+ { 'id': 104,
+ 'name': u"Nancy Cours Léopold / Ludres",
+ 'numbers': ["139"]
+ },
+ { 'id': 114,
+ 'name': u"Malzéville / Saulxures Forêt - Art sur Meurthe",
+ 'numbers': ["141", "142"]
+ },
+ { 'id': 108,
+ 'name': u"Malzéville Pixérécourt / Saulxures",
+ 'numbers': ["161"]
+ },
+ { 'id': 116,
+ 'name': u"Essey C.E.S / Gérard Barrois Stade Marcel Picot",
+ 'numbers': ["162"]
+ },
+ { 'id': 107,
+ 'name': u"Dommartemont / Jarville Sion",
+ 'numbers': ["163"]
+ },
+ { 'id': 110,
+ 'name': u"Essey Mouzimpré / Seichamps - Pulnoy (Lignes accessibles PMR)",
+ 'numbers': ["171", "172"]
+ }
+ ]
+
+
+ def stations(self):
+ '''
+ Extract from http://www.reseau-stan.com/horaires_arret/index7f96.html?rub_code=28
+ and ...... lieux publics
+ '''
+ return {
+ }
+
+
+ def search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
+ cached_result = self.cached_search(from_location, from_city, to_location, to_city, datetime, restrict)
+ if cached_search is not None:
+ return cached_result
+
+ '''
+ POST to http://www.reseau-stan.com/ri/index.asp
+ from_location: keywordsDep
+ from_city: comDep
+ to_location: keywordsArr
+ to_city: comArr
+ date: laDate (11/03/2009)
+ hour: lHeure (13 = 13h)
+ minute: laMinute
+ restrict: typeDate (68 = partir apres, 65 = arriver avant)
+ '''
+ pass
+
+
+ def cached_search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
+ cache_id = '-'.join([ from_location, from_city, to_location, to_city, datetime, restrict ])
+ pass
+
+ def load_timetable(self, lineid, date, hour):
+ parser = StanTimetableParser()
+ timetable_raw_html_file = None
+ try:
+ timetable_raw_html_file = self.load_timetable_remote(lineid, date, hour)
+ except URLError, e:
+ print e
+
+ parser.feed(timetable_raw_html_file)
+ parser.close()
+ return parser.result
+
+ def load_timetable_remote(self, lineid, date, hour):
+ url = 'http://www.reseau-stan.com/horaires_ligne/index.asp?rub_code=6&thm_id=7&gpl_id=0&lign_id=%s' % lineid
+ print url
+ response = urllib2.urlopen(url)
+ return response.read()
--- /dev/null
+# -*- coding: UTF-8 -*-
+
+import gtk
+import sys
+
+from stan import STAN
+
+class StanTimetable(gtk.VBox):
+
+ def create(self):
+ self.current_timetable = False
+ self.navigation = False
+
+ def set_activated_callback(self, new_callback):
+ self.cell_activated = new_callback
+
+ def cell_activated(self, data):
+ print data
+
+ def __cell_activated(self, timetable_view=None, cell_position=None, column_view=None):
+ if len(cell_position) == 1:
+ data = self.timetable_struct[ cell_position[0] ][0]
+ self.cell_activated(data)
+
+ def navigation_buttons(self):
+ box = gtk.HBox()
+
+ next = gtk.Button()
+ prev = gtk.Button()
+
+ box.add(prev)
+ box.add(next)
+ return box
+
+ def load(self, lineobj, date, time):
+
+ if (self.current_timetable):
+ self.remove(self.current_timetable)
+
+ self.timetable_struct = STAN().load_timetable(lineobj['id'], date, time)
+
+ self.timetable_navigation = self.timetable_struct['navigation']
+ self.timetable_struct = self.timetable_struct['timetable']
+
+ self.timetable_store = gtk.ListStore(*([str] * len(self.timetable_struct[0])))
+ for line in self.timetable_struct:
+ print line
+
+ self.timetable_store.append( line )
+ self.timetable_view = gtk.TreeView(self.timetable_store)
+
+ self.timetable_view.connect("row-activated", self.__cell_activated)
+
+ #
+
+ column_name = gtk.TreeViewColumn(u"Name")
+ self.timetable_view.append_column(column_name)
+ cell = gtk.CellRendererText()
+ column_name.pack_start(cell, True)
+ column_name.add_attribute(cell, 'text', 0)
+
+ nb_curses_columns = len(self.timetable_struct[0])-1
+ #nb_curses_columns -= 2
+
+ for i in range(nb_curses_columns):
+ column = gtk.TreeViewColumn(None)
+ self.timetable_view.append_column(column)
+ cell = gtk.CellRendererText()
+ cell.set_property('xalign', 0.5)
+ column.pack_start(cell, True)
+ column.add_attribute(cell, 'text', i+1)
+
+ self.timetable_view.set_search_column(0)
+
+ self.current_timetable = gtk.ScrolledWindow()
+
+ self.current_timetable.add(self.timetable_view)
+
+ if self.navigation != False:
+ self.remove(self.navigation)
+ self.navigation = self.navigation_buttons()
+ self.add(self.navigation)
+
+ self.add(self.current_timetable)
+
+ self.show_all()
+
+
+
+ pass
--- /dev/null
+
+import re
+
+from HTMLParser import HTMLParser
+
+class StanTimetableParser(HTMLParser):
+
+ def __init__(self):
+ HTMLParser.__init__(self)
+
+ self.result = {
+ 'navigation': {},
+ 'timetable': []
+ }
+
+ self.current_tt_line = None
+ self.state = None
+ self.TT_CAPTURING_TIMETABLE = 'TT_CAPTURING_TIMETABLE'
+ self.TT_CAPTURING_NAVIGATION = 'TT_CAPTURING_NAVIGATION'
+ self.TT_STOP_HOUR = 'TT_STOP_HOUR'
+ self.TT_STOP_NAME = 'TT_STOP_NAME'
+ self.TT_NAVIG_PREV = 'TT_NAVIG_PREV'
+ self.TT_NAVIG_NEXT = 'TT_NAVIG_NEXT'
+ self.TT_NAVIG_PREV_LINK = 'TT_NAVIG_PREV_LINK'
+ self.TT_NAVIG_NEXT_LINK = 'TT_NAVIG_NEXT_LINK'
+
+ def handle_starttag(self, tag, attrs):
+ self.last_tag = tag
+
+ attributes = {}
+ for attr in attrs:
+ attributes[attr[0]] = attr[1]
+
+ if tag == 'div' and attributes.has_key('class') and attributes['class'] == 'goatResultTop':
+ self.state = self.TT_CAPTURING_NAVIGATION
+
+ elif tag == 'table' and attributes.has_key('id') and attributes['id'] == 'linehour':
+ self.state = self.TT_CAPTURING_TIMETABLE
+
+ elif self.state == self.TT_CAPTURING_TIMETABLE:
+ if tag == 'tr':
+ self.current_tt_line = []
+ elif tag == 'strong':
+ self.state = self.TT_STOP_NAME
+ elif tag == 'td' and attributes.has_key('class') and 'hour' in attributes['class']:
+ self.state = self.TT_STOP_HOUR
+
+ elif self.state == self.TT_CAPTURING_NAVIGATION:
+ if tag == 'div' and attributes.has_key('class') and attributes['class'] == 'linehourPrev':
+ self.state = self.TT_NAVIG_PREV
+ elif tag == 'div' and attributes.has_key('class') and attributes['class'] == 'linehourNext':
+ self.state = self.TT_NAVIG_NEXT
+
+ elif self.state == self.TT_NAVIG_PREV and tag == 'a':
+ self.result['navigation']['prev'] = attributes['href']
+ self.state = self.TT_CAPTURING_NAVIGATION
+
+ elif self.state == self.TT_NAVIG_NEXT and tag == 'a':
+ self.result['navigation']['next'] = attributes['href']
+ self.state = self.TT_CAPTURING_NAVIGATION
+
+
+
+ def handle_data(self, data):
+ if self.state == self.TT_STOP_HOUR:
+ self.current_tt_line.append(data)
+ self.state = self.TT_CAPTURING_TIMETABLE
+
+ elif self.state == self.TT_STOP_NAME:
+ # remove in-parenthesis
+ data = re.compile('^[^\(]+').match(data).group()
+ self.current_tt_line.append(data)
+ self.state = self.TT_CAPTURING_TIMETABLE
+
+
+
+ def handle_endtag(self, tag):
+ if tag == 'tr' and self.state == self.TT_CAPTURING_TIMETABLE and self.current_tt_line is not None and len(self.current_tt_line) > 0:
+ self.result['timetable'].append(self.current_tt_line)
+
+ elif tag == 'table' and self.state == self.TT_CAPTURING_TIMETABLE:
+ self.state = None
--- /dev/null
+#!/usr/bin/env python2.5
+
+from lib.gui import StanGUI
+
+app = StanGUI()
+app.run()
--- /dev/null
+[Desktop Entry]
+Version=1.0.0
+Encoding=UTF-8
+Name=Maemo Stan
+Exec=/usr/bin/maemostan
+Icon=maemostan_icon_26x26
+Type=Application
--- /dev/null
+pystan (0.1-1) unstable; urgency=low
+
+ * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP>
+
+ -- Vincent Lark <vincent.lark@gmail.com> Mon, 16 Mar 2009 07:58:38 +0100
+
--- /dev/null
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+
+
+
+CFLAGS = -Wall -g
+
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+ CFLAGS += -O0
+else
+ CFLAGS += -O2
+endif
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # Add here commands to configure the package.
+
+ touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp
+ dh_testdir
+
+ # Add here commands to compile the package.
+ $(MAKE)
+ #docbook-to-man debian/pystan.sgml > pystan.1
+
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+
+ # Add here commands to clean up after the build process.
+ -$(MAKE) clean
+
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/pystan.
+ $(MAKE) DESTDIR=$(CURDIR)/debian/pystan install
+
+
+# Build architecture-independent files here.
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs
+ dh_fixperms
+ dh_installdeb
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+# Build architecture-dependent files here.
+binary-arch: build install
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
--- /dev/null
+
+import os
+
+from distutils.core import setup
+
+setup( name='pyStan',
+ version='0.1.0',
+ package_dir = {'': 'src'},
+ packages = ['pystan', 'pystan.lib'],
+ data_files = [
+ ('share/pixmaps', ['src/pystan_icon_26x26.png']),
+ ('share/applications/hildon', ['src/pystan.desktop']),
+ ]
+)
--- /dev/null
+[Desktop Entry]
+Version=1.0.0
+Encoding=UTF-8
+Name=Maemo Stan
+Exec=/usr/bin/maemostan
+Icon=maemostan_icon_26x26
+Type=Application
--- /dev/null
+# -*- coding: UTF-8 -*-
+
+import gtk
+import hildon
+import webbrowser
+
+import sys, time
+
+from stan import STAN
+from timetable import StanTimetable
+
+class StanGUI(hildon.Program):
+ #
+ def __init__(self):
+ hildon.Program.__init__(self)
+ self.stan = STAN()
+ self.window = hildon.Window()
+ self.window.set_title('pySTAN')
+ self.window.connect("destroy", gtk.main_quit)
+ self.add_window(self.window)
+
+ self.tabs = self.main_tabs()
+ self.window.add(self.tabs)
+ #
+ self.last_station = False
+
+ def main_tabs(self):
+ tabs = gtk.Notebook()
+ tabs.set_tab_pos(gtk.POS_TOP)
+ tabs.append_page(self.tab_lines(), gtk.Label('Par lignes'))
+ #tabs.append_page(self.tab_stations(), gtk.Label('Par arrets'))
+ #tabs.append_page(self.tab_search(), gtk.Label('Recherche'))
+ return tabs
+ #
+ def tab_search(self):
+ tab_search = gtk.Frame(None) #'Recherche de trajet')
+
+ from_location = gtk.Entry()
+ from_location_label = gtk.Label(u"Départ")
+ from_city = gtk.Entry()
+ from_city_label = gtk.Label(u"Ã ")
+
+ to_location = gtk.Entry()
+ to_location_label = gtk.Label(u"Arrivée")
+ to_city = gtk.Entry()
+ to_city_label = gtk.Label(u"Ã ")
+
+ datetime_select = gtk.Calendar()
+ datetime_select.display_options(gtk.CALENDAR_SHOW_DAY_NAMES | gtk.CALENDAR_WEEK_START_MONDAY)
+
+ restriction = gtk.Combo()
+ restriction.set_popdown_strings([u"partir après", u"arriver avant"])
+
+ vbox = gtk.VBox()
+ vbox.add(from_location_label)
+ vbox.add(from_location)
+ vbox.add(from_city)
+ vbox.add(to_location_label)
+ vbox.add(to_location)
+ vbox.add(to_city)
+
+ vbox.add(datetime_select)
+ vbox.add(restriction)
+
+ tab_search.add(vbox)
+
+ return tab_search
+
+ # special callback for drawing line numbers in combo box
+ def line_number_cell_cb(self, treeviewcolumn, cellrenderer, modele, iter):
+ for line in self.stan.lines():
+ cell_line_name = modele.get_value(iter, 1)
+ cell_line_id = modele.get_value(iter, 2)
+
+ if line['id'] == cell_line_id:
+ text = '-'.join(line['numbers'])
+ cellrenderer.set_property('text', text)
+
+ if len(text) <= 10:
+ cellrenderer.set_property('scale', 0.8)
+ else:
+ cellrenderer.set_property('scale', 0.6)
+
+ if not line.has_key('fg-color'):
+ #print "ERROR:\tline %s do not have foreground color" % line['id']
+ pass
+ else:
+ cellrenderer.set_property('foreground-gdk', gtk.gdk.color_parse(line['fg-color']))
+
+ if not line.has_key('bg-color'):
+ #print "ERROR:\tline %s do not have background color" % line['id']
+ pass
+ else:
+ cellrenderer.set_property('background-gdk', gtk.gdk.color_parse(line['bg-color']))
+ return
+
+ def set_previous_button_cb(self, callback):
+ self._previous_button_cb = callback
+
+ def _previous_button_cb(self, data):
+ pass
+
+ def set_active_lineid(self, lineid):
+ pass
+
+ def tab_lines(self):
+ tab_lines = gtk.Frame(None) #'Recherche par lignes')
+
+ #lines_navigation = gtk.Table(rows=2, columns=2)
+ lines_navigation = gtk.HBox()
+
+ #previous_button = gtk.Button(u"<")
+ #previous_button.connect('clicked', self._previous_button_cb)
+ #alignment_previous_button = gtk.Alignment(xalign=0, xscale=0.02)
+ #alignment_previous_button.add(previous_button)
+ #lines_navigation.attach(previous_button, 0, 1, 0, 1, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK, xpadding=1, ypadding=0)
+ #lines_navigation.pack_start(previous_button)
+
+ lines_list = gtk.ComboBox()
+ lines_model = gtk.ListStore(str, str, int)
+ lines_item_number = gtk.CellRendererText()
+ lines_item_number.set_property('scale', 0.8)
+ lines_item_name = gtk.CellRendererText()
+ lines_item_name.set_property('scale', 0.8)
+
+ lines_list.pack_start(lines_item_number, True)
+ lines_list.pack_start(lines_item_name, True)
+
+ lines_list.add_attribute(lines_item_number, 'text', 0)
+ lines_list.add_attribute(lines_item_name, 'text', 1)
+
+ lines_list.set_cell_data_func(lines_item_number, self.line_number_cell_cb)
+
+ for line in self.stan.lines():
+ lines_model.append([ '' , line['name'], line['id'] ])
+
+ lines_list.connect('changed', self.show_line_timetable)
+ lines_list.set_model(lines_model)
+
+ alignment_lines_list = gtk.Alignment(yscale=0.1)
+ alignment_lines_list.add(lines_list)
+ lines_navigation.pack_end(alignment_lines_list)
+ #lines_navigation.attach(lines_list, 1, 2, 0, 1, xoptions=gtk.SHRINK, yoptions=gtk.FILL)
+
+ vbox = gtk.VBox()
+
+ alignment_lines_navigation = gtk.Alignment(xscale=1.0, yscale=0.1)
+ alignment_lines_navigation.add(lines_navigation)
+
+ vbox.pack_start(alignment_lines_navigation, False)
+
+ self.timetable = StanTimetable()
+ self.timetable.create()
+ self.timetable.set_activated_callback(self.timetable_row_clicked)
+
+ #alignment_tt = gtk.Alignment(yscale=0.9)
+ #alignment_tt.add(self.timetable)
+ vbox.pack_start(self.timetable, True, True)
+
+ #lines_navigation.attach(self.timetable, 0, 2, 1, 2, xoptions=gtk.EXPAND, yoptions=gtk.EXPAND, xpadding=1, ypadding=0)
+ #lines_navigation.pack_start(self.timetable)
+
+ tab_lines.add(vbox)
+ return tab_lines
+
+ def show_line_timetable(self, selection):
+ self.timetable.load( self.stan.lines()[selection.get_active()], time.strftime("%d/%m/%Y"), time.strftime("%H"), 0 )
+
+ def timetable_row_clicked(self, station):
+ if self.last_station != station['name']:
+ if self.last_station != False:
+ self.tabs.remove(self.tab_map)
+ self.last_station = station['name']
+ self.tab_map = self.tab_geomap(station)
+ self.tabs.append_page(self.tab_map, gtk.Label(station['name']))
+ self.tabs.show_all()
+ self.tabs.set_current_page(1)
+
+ def tab_stations(self):
+ tab_stations = gtk.Frame(None) #'Recherche par arrets')
+
+ list_stations = gtk.Combo()
+ list_stations.set_popdown_strings([])
+
+ tab_stations.add(list_stations)
+
+ return tab_stations
+
+ def tab_geomap(self, station):
+ tab_map = gtk.Frame(None)
+ map_button = gtk.Button()
+ #map_label = gtk.Label(u"Link to map")
+ map_button.connect('clicked', self.open_map, station['href'])
+
+ tab_map.add(map_button)
+ return tab_map
+
+ def open_map(self, data, url):
+ webbrowser.open(url)
+
+ def run(self):
+ self.window.show_all()
+ gtk.main()
+
--- /dev/null
+# -*- coding: UTF-8 -*-
+
+import os, sys, time, re
+import urllib, urllib2, urlparse, cgi
+from urllib2 import URLError
+
+
+from timetable_parser import StanTimetableParser
+
+class STAN:
+
+ RSS_URL = 'http://www.reseau-stan.com/rss'
+
+ get_params = [ 'rub_code', 'gpl_id', 'thm_id', 'lineid', 'date', 'hour', 'direction' ]
+
+ def lines(self):
+
+ return [
+ { 'id': 144,
+ 'name': u"Nancy République / Bouxières-aux-Dames",
+ 'numbers': ["322"],
+ 'fg-color': '#fff',
+ 'bg-color': '#6ecbf1'
+ },
+ { 'id': 145,
+ 'name': u"Vandoeuvre Vᅢᄅlodrome / Chaligny",
+ 'numbers': ["511"],
+ 'fg-color': '#fff',
+ 'bg-color': '#a3b909'
+ },
+ { 'id': 146,
+ 'name': u"Nancy République / Frouard Le Nid",
+ 'numbers': ["323"],
+ 'fg-color': '#fff',
+ 'bg-color': '#f5a3c8'
+ },
+ { 'id': 147,
+ 'name': u"Vandoeuvre Vélodrome / Pont Saint-Vincent",
+ 'numbers': ["512"],
+ 'fg-color': '#fff',
+ 'bg-color': '#c00266'
+ },
+ { 'id': 148,
+ 'name': u"Nancy République / Pompey Les Vannes",
+ 'numbers': ["324"],
+ 'fg-color': '#fff',
+ 'bg-color': '#d80b8c'
+ },
+ { 'id': 149,
+ 'name': u"Vandoeuvre Vélodrome / Messein",
+ 'numbers': ["514"],
+ 'fg-color': '#fff',
+ 'bg-color': '#006d2c'
+ },
+ { 'id': 150,
+ 'name': u"Nancy République / Pompey Fonds de Lavaux",
+ 'numbers': ["325"],
+ 'fg-color': '#fff',
+ 'bg-color': '#b4d8be'
+ },
+ { 'id': 151,
+ 'name': u"Nancy Place Carnot / Chaligny",
+ 'numbers': ["521"],
+ 'fg-color': '#a3b909',
+ 'bg-color': '#fff'
+ },
+ { 'id': 152,
+ 'name': u"Nancy République / Saint-Nicolas Le Nid",
+ 'numbers': ["621"],
+ 'fg-color': '#fff',
+ 'bg-color': '#0b0a4b'
+ },
+ { 'id': 153,
+ 'name': u"Nancy Place Carnot / Pont Saint-Vincent",
+ 'numbers': ["522"],
+ 'fg-color': '#c00266',
+ 'bg-color': '#fff'
+ },
+ { 'id': 154,
+ 'name': u"Nancy République / Dombasle Maroc",
+ 'numbers': ["624"],
+ 'fg-color': '#fff',
+ 'bg-color': '#fb1518'
+ },
+ { 'id': 155,
+ 'name': u"Nancy Place Carnot / Bainville-sur-Madon",
+ 'numbers': ["523"],
+ 'fg-color': '#81d0e3',
+ 'bg-color': '#fff'
+ },
+ { 'id': 157,
+ 'name': u"Nancy République / Saint-Nicolas République",
+ 'numbers': ["622"],
+ 'fg-color': '#fff',
+ 'bg-color': '#0d97a8'
+ },
+ { 'id': 156,
+ 'name': u"Nancy République / Dombasle Stand",
+ 'numbers': ["623"],
+ 'fg-color': '#fff',
+ 'bg-color': '#fe6612'
+ },
+ { 'id': 158,
+ 'name': u"Nancy République / Champigneulles",
+ 'numbers': ["321"],
+ 'fg-color': '#fff',
+ 'bg-color': '#d3b6d1'
+ },
+ { 'id': 126,
+ 'name': u"Vandoeuvre Parc des Expositions / Nancy République",
+ 'numbers': ["129"],
+ 'fg-color': '#fff',
+ 'bg-color': '#92bbad'
+ },
+ { 'id': 133,
+ 'name': u"Ligne 80",
+ 'numbers': ["80"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 138,
+ 'name': u"Vandoeuvre CHU Brabois / Essey Mouzimpré",
+ 'numbers': ["Tram 1"],
+ 'fg-color': '#fff',
+ 'bg-color': '#ff0000'
+ },
+ { 'id': 141,
+ 'name': u"Vandoeuvre CHU Brabois / Centre d\'accueil - Villers les Essarts",
+ 'numbers': ["Ptit Stan Vilers"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 143,
+ 'name': u"Vandoeuvre CHU Brabois / Vandoeuvre Nations - Médecine préventive ",
+ 'numbers': ["Ptit Stan Vandoeuvre"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 140,
+ 'name': u"Laxou Sapinière / Laxou Provinces",
+ 'numbers': ["Ptit Stan Laxou"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 142,
+ 'name': u"Nancy Carnot",
+ 'numbers': ["Ptit Stan Colline"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 134,
+ 'name': u"Malzéville Sadi Carnot - Nancy Cimetière du Sud",
+ 'numbers': ["Minibus"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 159,
+ 'name': u"StanU",
+ 'numbers': ["110"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 112,
+ 'name': u"Maxéville Champ le Boeuf - Nancy Haut du Lièvre / Jarville Sion",
+ 'numbers': ["111", "112"],
+ 'fg-color': '#fff',
+ 'bg-color': '#71228d'
+ },
+ { 'id': 96,
+ 'name': u"Houdemont / Vandoeuvre Charmois",
+ 'numbers': ["114"],
+ 'fg-color': '#000',
+ 'bg-color': '#b6a6d3'
+ },
+ { 'id': 98,
+ 'name': u"Ludres / Villers Lycée Stanislas",
+ 'numbers': ["115"],
+ 'fg-color': '#fff',
+ 'bg-color': '#705aac'
+ },
+ { 'id': 101,
+ 'name': u"Fléville Fleurychamp - Heillecourt / Villers Lycée Stanislas",
+ 'numbers': ["116"],
+ 'fg-color': '#fff',
+ 'bg-color': '#755eac'
+ },
+ { 'id': 103,
+ 'name': u"Nancy Beauregard / Nancy République",
+ 'numbers': ["121"],
+ 'fg-color': '#fff',
+ 'bg-color': '#339d2f'
+ },
+ { 'id': 135,
+ 'name': u"Villers Clairlieu / Nancy République",
+ 'numbers': ["122", "126"],
+ 'fg-color': '#000',
+ 'bg-color': '#9cca1f'
+ },
+ { 'id': 106,
+ 'name': u"Vandoeuvre Nations / Nancy République",
+ 'numbers': ["123"],
+ 'fg-color': '#000',
+ 'bg-color': '#a9d72f'
+ },
+ { 'id': 109,
+ 'name': u"Laxou Mouzon / Nancy République",
+ 'numbers': ["124"],
+ 'fg-color': '#fff',
+ 'bg-color': '#39a63b'
+ },
+
+ { 'id': 118,
+ 'name': u"Laxou Provinces / Seichamps",
+ 'numbers': ["130"],
+ 'fg-color': '#fff',
+ 'bg-color': '#048a96'
+ },
+ { 'id': 95,
+ 'name': u"Maxéville Mairie / Jarville Sion - Heillecourt - Fléville De la Noue",
+ 'numbers': ["131", "132", "133"],
+ 'fg-color': '#000',
+ 'bg-color': '#82cee6'
+ },
+ { 'id': 97,
+ 'name': u"Villers Lycée Stanislas / Maxéville Lafayette - Malzéville Savlons",
+ 'numbers': ["134", "135"],
+ 'fg-color': '#fff',
+ 'bg-color': '#1693cb'
+ },
+ { 'id': 100,
+ 'name': u"Nancy Cours Léopold / Laneuveville - La Madeleine",
+ 'numbers': ["136", "137"],
+ 'fg-color': '#fff',
+ 'bg-color': '#6f88c1'
+ },
+ { 'id': 102,
+ 'name': u"Laxou Champ le Boeuf / Vandoeurvre Roberval",
+ 'numbers': ["138"],
+ 'fg-color': '#fff',
+ 'bg-color': '#123e97'
+ },
+ { 'id': 104,
+ 'name': u"Nancy Cours Léopold / Ludres",
+ 'numbers': ["139"],
+ 'fg-color': '#fff',
+ 'bg-color': '#6f87c7'
+ },
+ { 'id': 114,
+ 'name': u"Malzéville / Saulxures Forêt - Art sur Meurthe",
+ 'numbers': ["141", "142"],
+ 'fg-color': '#fff',
+ 'bg-color': '#ff750e'
+ },
+ { 'id': 108,
+ 'name': u"Malzéville Pixérécourt / Saulxures",
+ 'numbers': ["161"],
+ 'fg-color': '#000',
+ 'bg-color': '#ffff33'
+ },
+ { 'id': 116,
+ 'name': u"Essey C.E.S / Gérard Barrois Stade Marcel Picot",
+ 'numbers': ["162"],
+ 'fg-color': '#000',
+ 'bg-color': '#f59638'
+ },
+ { 'id': 107,
+ 'name': u"Dommartemont / Jarville Sion",
+ 'numbers': ["163"],
+ 'fg-color': '#000',
+ 'bg-color': '#fad956'
+ },
+ { 'id': 110,
+ 'name': u"Essey Mouzimpré / Seichamps - Pulnoy (Lignes accessibles PMR)",
+ 'numbers': ["171", "172"],
+ 'bg-color': '#f38ab3'
+ }
+ ]
+
+ def schedules_cache_dirname(self):
+ pystan_schedules = os.path.expanduser('~/.pystan/cache/schedules')
+ if not os.path.isdir(pystan_schedules):
+ #os.makedirs('~/.pystan/cache/schedules')
+ pass
+ return pystan_schedules
+
+ def filter_params(self, params):
+ only_wanted = {}
+ for key in params:
+ if key in self.get_params:
+ only_wanted[key] = params[key]
+ return only_wanted
+
+ def schedule_cache_filename(self, params):
+ params['date'] = self.schedules_cache_dateformat(params['date'])
+ params = [ str(key)+"="+str(params[key]) for key in iter(params)]
+ params.sort()
+ filename = '_'.join(params)
+ filename = filename.replace('/', '-')
+ return "%s/%s" % (self.schedules_cache_dirname(), filename)
+
+ def schedules_cache_dateformat(self, date):
+ date = time.strptime(date, "%d/%m/%Y")
+ day_n = int(time.strftime("%w", date))
+ # saturday / sunday
+ if day_n == 0 or day_n == 6:
+ date = time.strftime("%a/%m/%Y", date)
+ # week
+ else:
+ date = time.strftime("week/%m/%Y", date)
+ return date
+
+
+ def default_timetable_params(self, params):
+ for param, default in { 'rub_code':6, 'thm_id':6, 'gpl_id':0 }.items(): #{'sens':1, 'date':'01/01/01', 'hour':'12', 'index':'1'}:
+ if not params.has_key(param) or params[param] is None:
+ params[param] = default
+ return params
+
+ def search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
+ cached_result = self.cached_search(from_location, from_city, to_location, to_city, datetime, restrict)
+ if cached_search is not None:
+ return cached_result
+
+ '''
+ POST to http://www.reseau-stan.com/ri/index.asp
+ from_location: keywordsDep
+ from_city: comDep
+ to_location: keywordsArr
+ to_city: comArr
+ date: laDate (11/03/2009)
+ hour: lHeure (13 = 13h)
+ minute: laMinute
+ restrict: typeDate (68 = partir apres, 65 = arriver avant)
+ '''
+ pass
+
+ def cached_search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
+ cache_id = '-'.join([ from_location, from_city, to_location, to_city, datetime, restrict ])
+ pass
+
+ def load_timetable(self, lineid, params=None, bypass=False):
+ params = self.default_timetable_params(params)
+ params['lineid'] = lineid
+ params = self.filter_params(params)
+
+ timetable_raw_html_file = None
+
+ if timetable_raw_html_file is None:
+ timetable_raw_html_file = self.load_timetable_local(params, bypass)
+
+ if timetable_raw_html_file is None:
+ timetable_raw_html_file = self.load_timetable_remote(params)
+
+ if timetable_raw_html_file is None:
+ print "WARNING:\tNo cache file and no internet connection"
+
+ if timetable_raw_html_file:
+ parser = StanTimetableParser()
+ parser.feed(timetable_raw_html_file)
+ parser.close()
+ parsed = parser.result
+
+ if parsed['navigation'].has_key('next'):
+ index_next = urlparse.urlsplit(parsed['navigation']['next'])[3]
+ index_next = cgi.parse_qs(index_next)
+ parsed['navigation']['next'] = index_next['index'][0]
+
+ if parsed['navigation'].has_key('prev'):
+ index_prev = urlparse.urlsplit(parsed['navigation']['prev'])[3]
+ index_prev = cgi.parse_qs(index_prev)
+ parsed['navigation']['prev'] = index_prev['index'][0]
+
+ if len(parsed['stations']) > 0:
+ parsed['stations'] = self.extract_stations_infos(parsed['stations'])
+ pass
+
+ return parsed
+
+ else:
+ return timetable_raw_html_file
+
+ def load_timetable_remote(self, params):
+ url = 'http://www.reseau-stan.com/horaires_ligne/index.asp'
+
+ get_params = params.copy()
+ get_params['lign_id'] = get_params['lineid']; del(get_params['lineid'])
+
+ if get_params.has_key('direction') and int(get_params['direction']) > 0:
+ get_params['sens'] = 2
+ del(get_params['direction'])
+ else:
+ get_params['sens'] = 1
+
+ if get_params.has_key('hour'):
+ get_params['index'] = get_params['hour']
+ del(get_params['hour'])
+
+ req = urllib2.Request(url, urllib.urlencode(get_params))
+
+ print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
+
+ file_data = None
+
+ try:
+ response = urllib2.urlopen(req)
+ file_data = response.read()
+ except URLError, e:
+ print "WARNING:\t%s" % e
+
+ if file_data is not None:
+ self.save_timetable_local(file_data, params)
+ return file_data
+
+ def load_timetable_local(self, params, bypass=False):
+ file = self.schedule_cache_filename(params)
+ file_data = None
+ max_age = 999L
+
+ print "GET FROM:\t%s" % file
+
+ if not os.path.isfile(file):
+ #print "WARNING: %s does not exists" % file
+ file_data = None
+
+ elif bypass or os.stat(file).st_mtime <= int(time.time()) + max_age:
+ response = open(file)
+ try:
+ file_data = response.read()
+ finally:
+ response.close()
+ else:
+ print "WARNING:\t%s exists but is too old" % file
+ file_data = False
+
+ return file_data
+
+ def save_timetable_local(self, file_data, params):
+
+ file_data = re.compile('(<div id="goatResult">.*)<div id="footer">', re.DOTALL).findall(file_data)
+
+ if len(file_data) != 1:
+ print "ERROR:\tCannot store a valid html section (%s occurences found)" % len(file_data)
+ return False
+ else:
+ file_data = file_data[0]
+
+ file = self.schedule_cache_filename(params)
+ print "WRITING:\t%s" % file
+ file_dest = open(file, 'w')
+ file_dest.write(file_data)
+ file_dest.close()
+ return True
+
+ def extract_stations_infos(self, stations_urls):
+ stations_ids = []
+ for station_url in stations_urls:
+ station_id = urlparse.urlsplit(station_url)[3]
+ station_id = cgi.parse_qs(station_id)
+
+ if not station_id.has_key('pa_id1') or len(station_id['pa_id1']) != 1:
+ print "ERROR:\tCannot extract pa_id1 from url %s " % station_url
+ continue
+
+ station_id = station_id['pa_id1'][0]
+
+ url = 'http://www.reseau-stan.com/ri/cartegoogle.asp'
+
+ if False:
+ req = urllib2.Request(url, urllib.urlencode({ 'pa_iad1':station_id }))
+ print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
+
+ file_data = None
+ try:
+ response = urllib2.urlopen(req)
+ file_data = response.read()
+ except URLError, e:
+ print "WARNING: %s" % e
+
+ stations_ids.append({
+ 'id': station_id,
+ 'href': "http://www.reseau-stan.com/ri/cartegoogle.asp?%s" % urllib.urlencode({ 'pa_iad1':station_id })
+ })
+
+ return stations_ids
+
--- /dev/null
+# -*- coding: UTF-8 -*-
+
+import gtk
+import sys
+
+from stan import STAN
+
+class StanTimetable(gtk.VBox):
+
+ def create(self):
+ self.current_timetable = False
+ self.navigation = False
+ self.navigation_pages = []
+
+ def set_activated_callback(self, new_callback):
+ self.cell_activated = new_callback
+
+ def cell_activated(self, data):
+ print data
+
+ def __cell_activated(self, timetable_view=None, cell_position=None, column_view=None):
+ if len(cell_position) == 1:
+ station = self.timetable_stations[ cell_position[0] ]
+ station['name'] = self.timetable_struct[ cell_position[0] ][0]
+ self.cell_activated(station)
+
+ def navigation_buttons(self):
+ box = gtk.HBox()
+
+ current = self.navigation_pages[len(self.navigation_pages)-1]
+
+ #previous_page = None
+ #if len(self.navigation_pages) > 0:
+ # previous_page = gtk.Button(u"<")
+ # previous_page.connect('clicked', self.load_by_params, self.navigation_pages[len(self.navigation_pages)-1])
+
+ if self.timetable_navigation.has_key('next'):
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_GO_FORWARD, 48)
+ next = gtk.Button()
+ next.add(image)
+ index_next = self.timetable_navigation['next']
+ next_nav = current.copy(); next_nav.update({ 'hour':int(index_next) })
+ next.connect('clicked', self.load_by_params, next_nav)
+ box.pack_end(next,False,False)
+
+ if self.timetable_navigation.has_key('prev'):
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_GO_BACK, 48)
+ index_prev = self.timetable_navigation['prev']
+ prev = gtk.Button()
+ prev.add(image)
+ prev_nav = current.copy(); prev_nav.update({'hour':int(index_prev) })
+ prev.connect('clicked', self.load_by_params, prev_nav)
+ box.pack_start(prev,False,False)
+
+ invert = gtk.Button()
+ invert_nav = current.copy(); invert_nav.update({'direction':int(not invert_nav['direction'])})
+ invert.connect('clicked', self.load_by_params, invert_nav)
+ image = gtk.Image()
+ if current['direction']:
+ image.set_from_stock(gtk.STOCK_SORT_DESCENDING, 48)
+ else:
+ image.set_from_stock(gtk.STOCK_SORT_ASCENDING, 48)
+ invert.add(image)
+ box.add(invert)
+
+ alignment_box_navigation = gtk.Alignment(xscale=1.0,yscale=0.1)
+ alignment_box_navigation.add(box)
+ return alignment_box_navigation
+
+ def load_by_params(self, widget, params):
+ if params.has_key('pop') and params['pop'] and len(self.navigation_pages) > 0:
+ self.navigation_pages.pop(len(self.navigation_pages)-1)
+
+ self.load(params['lineobj'], params['date'], params['hour'], params['direction'], params['bypass'])
+
+ def load(self, lineobj, date, hour, direction, bypass=False):
+
+ params = {'lineobj':lineobj, 'date':date, 'hour':hour, 'direction':direction, 'bypass':True }
+
+ if (self.current_timetable):
+ self.remove(self.current_timetable)
+
+ if (self.navigation):
+ self.remove(self.navigation)
+
+ self.timetable_struct = STAN().load_timetable(lineobj['id'], params, bypass)
+
+ if self.timetable_struct is None or (self.timetable_struct.has_key('timetable') and len(self.timetable_struct['timetable']) == 0):
+ self.current_timetable = gtk.VBox()
+ self.current_timetable.add(gtk.Label(u"No timetable available for line \n%s" % lineobj['name']))
+
+ if len(self.navigation_pages) > 0:
+ go_previous_page = gtk.Button(u"Get back")
+ back_params = self.navigation_pages[len(self.navigation_pages)-1]
+ back_params['pop'] = True
+ go_previous_page.connect('clicked', self.load_by_params, back_params)
+ self.current_timetable.add(go_previous_page)
+
+ elif self.timetable_struct == False:
+ self.current_timetable = gtk.VBox()
+ self.current_timetable.add(gtk.Label(u"No recent timetable available for line \n%s" % lineobj['name']))
+ bypass_button = gtk.Button(u"See old local timetable")
+ bypass_button.connect("clicked", self.load_by_params, params)
+ self.current_timetable.add(bypass_button)
+
+ elif len(self.timetable_struct['timetable']) > 0:
+ self.navigation_pages.append(params)
+
+ self.timetable_navigation = self.timetable_struct['navigation']
+ self.timetable_stations = self.timetable_struct['stations']
+ self.timetable_struct = self.timetable_struct['timetable']
+
+ #self.timetable_store = gtk.ListStore(([str] * len(self.timetable_struct[0])))
+ # beurk
+
+ if len(self.timetable_struct[0]) < 7:
+ for k,v in enumerate(self.timetable_struct):
+ self.timetable_struct[k].append('-')
+
+ self.timetable_store = gtk.ListStore(str, str, str, str, str, str, str)
+ for k,line in enumerate(self.timetable_struct):
+ self.timetable_store.append( line )
+
+ self.timetable_view = gtk.TreeView(self.timetable_store)
+
+ self.timetable_view.connect("row-activated", self.__cell_activated)
+
+ column_name = gtk.TreeViewColumn(u"Name")
+ self.timetable_view.append_column(column_name)
+ cell = gtk.CellRendererText()
+ cell.set_property('scale', 0.8)
+ column_name.pack_start(cell, True)
+ column_name.add_attribute(cell, 'text', 0)
+
+ nb_curses_columns = len(self.timetable_struct[0])-1
+
+ for i in range(nb_curses_columns):
+ column = gtk.TreeViewColumn(u"Schedules")
+ self.timetable_view.append_column(column)
+ cell = gtk.CellRendererText()
+ cell.set_property('xalign', 0.5)
+ cell.set_property('scale', 0.8)
+ cell.set_property('xpad', 9)
+ column.pack_end(cell, True)
+ column.add_attribute(cell, 'text', i+1)
+
+ #self.timetable_view.set_property('search_column', 0)
+ #self.timetable_view.set_property('enable-search', True)
+ #self.timetable_view.set_property('headers-clickable', True)
+
+ self.current_timetable = gtk.ScrolledWindow()
+ self.current_timetable.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+
+ self.current_timetable.add_with_viewport(self.timetable_view)
+
+ if self.navigation:
+ self.remove(self.navigation)
+ self.navigation = self.navigation_buttons()
+ self.add(self.navigation)
+
+ self.add(self.current_timetable)
+ self.show_all()
--- /dev/null
+
+import re
+
+from HTMLParser import HTMLParser
+
+class StanTimetableParser(HTMLParser):
+
+ def __init__(self):
+ HTMLParser.__init__(self)
+
+ self.result = {
+ 'navigation': {},
+ 'timetable': [],
+ 'stations': []
+ }
+
+ self.current_tt_line = None
+ self.state = None
+ self.TT_CAPTURING_TIMETABLE = 'TT_CAPTURING_TIMETABLE'
+ self.TT_CAPTURING_NAVIGATION = 'TT_CAPTURING_NAVIGATION'
+ self.TT_STOP_HOUR = 'TT_STOP_HOUR'
+ self.TT_STOP_NAME = 'TT_STOP_NAME'
+ self.TT_NAVIG_PREV = 'TT_NAVIG_PREV'
+ self.TT_NAVIG_NEXT = 'TT_NAVIG_NEXT'
+ self.TT_NAVIG_PREV_LINK = 'TT_NAVIG_PREV_LINK'
+ self.TT_NAVIG_NEXT_LINK = 'TT_NAVIG_NEXT_LINK'
+
+ def handle_starttag(self, tag, attrs):
+
+ attributes = {}
+ for attr in attrs:
+ attributes[attr[0]] = attr[1]
+
+ if tag == 'div' and attributes.has_key('class') and attributes['class'] == 'goatResultTop':
+ self.state = self.TT_CAPTURING_NAVIGATION
+
+ elif tag == 'table' and attributes.has_key('id') and attributes['id'] == 'linehour':
+ self.state = self.TT_CAPTURING_TIMETABLE
+
+ elif self.state == self.TT_CAPTURING_TIMETABLE:
+ if tag == 'tr':
+ self.current_tt_line = []
+ elif tag == 'strong':
+ self.state = self.TT_STOP_NAME
+ elif tag == 'a' and self.last_tag[0] == 'td' and 'plan' in self.last_tag[1]['class']:
+ self.result['stations'].append(attributes['href'])
+ #self.current_tt_line.append(attributes['href'])
+ elif tag == 'td' and attributes.has_key('class') and 'hour' in attributes['class']:
+ self.state = self.TT_STOP_HOUR
+
+ elif self.state == self.TT_CAPTURING_NAVIGATION:
+ if tag == 'div' and attributes.has_key('class') and attributes['class'] == 'linehourPrev':
+ self.state = self.TT_NAVIG_PREV
+ elif tag == 'div' and attributes.has_key('class') and attributes['class'] == 'linehourNext':
+ self.state = self.TT_NAVIG_NEXT
+
+ elif self.state == self.TT_NAVIG_PREV and tag == 'a':
+ self.result['navigation']['prev'] = attributes['href']
+ self.state = self.TT_CAPTURING_NAVIGATION
+
+ elif self.state == self.TT_NAVIG_NEXT and tag == 'a':
+ self.result['navigation']['next'] = attributes['href']
+ self.state = self.TT_CAPTURING_NAVIGATION
+
+ self.last_tag = [ tag, attributes ]
+ pass
+
+
+
+ def handle_data(self, data):
+ if self.state == self.TT_STOP_HOUR:
+ self.current_tt_line.append(data)
+ self.state = self.TT_CAPTURING_TIMETABLE
+
+ elif self.state == self.TT_STOP_NAME:
+ # remove in-parenthesis
+ data = re.compile('^[^\(]+').match(data).group()
+
+ # slice doubles
+ data = data.replace(" - ", "\n")
+ self.current_tt_line.append(data)
+ self.state = self.TT_CAPTURING_TIMETABLE
+
+
+
+ def handle_endtag(self, tag):
+ if tag == 'tr' and self.state == self.TT_CAPTURING_TIMETABLE and self.current_tt_line is not None and len(self.current_tt_line) > 0:
+ self.result['timetable'].append(self.current_tt_line)
+
+ elif tag == 'table' and self.state == self.TT_CAPTURING_TIMETABLE:
+ self.state = None
--- /dev/null
+#!/usr/bin/env python2.5
+
+import sys
+import pygtk
+pygtk.require('2.0')
+
+from lib.gui import StanGUI
+
+app = StanGUI()
+app.run()
--- /dev/null
+This is TeX, Version 3.141592 (Web2C 7.5.4) (format=tex 2009.1.22) 24 MAR 2009 18:51
+**&tex --help
+(/scratchbox/devkits/doctools/share/texmf-dist/tex/latex/tools/q.tex
+! Interruption.
+l.2
+ %% This is file `q.tex',
+? Q
+OK, entering \batchmode...
+ File ignored
+)
+! Emergency stop.
+<*> &tex --help
+
+*** (job aborted, no legal \end found)
+
+No pages of output.
--- /dev/null
+This is TeX, Version 3.141592 (Web2C 7.5.4) (format=tex 2009.1.22) 24 MAR 2009 18:51
+**&tex
+
+*
+! Interruption.
+<*>
+
+? exit
+Type <return> to proceed, S to scroll future error messages,
+R to run without stopping, Q to run quietly,
+I to insert something,
+H for help, X to quit.
+? X
+No pages of output.