1# -*- coding: utf-8 -*- 2 3# Copyright(C) 2012 Romain Bignon 4# 5# This file is part of weboob. 6# 7# weboob is free software: you can redistribute it and/or modify 8# it under the terms of the GNU Lesser General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# weboob is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU Lesser General Public License for more details. 16# 17# You should have received a copy of the GNU Lesser General Public License 18# along with weboob. If not, see <http://www.gnu.org/licenses/>. 19 20from __future__ import print_function 21 22from weboob.capabilities.housing import (CapHousing, Query, POSTS_TYPES, 23 ADVERT_TYPES, HOUSE_TYPES) 24from weboob.capabilities.base import empty 25from weboob.tools.application.repl import ReplApplication, defaultcount 26from weboob.tools.application.formatters.iformatter import IFormatter, PrettyFormatter 27from weboob.tools.config.yamlconfig import YamlConfig 28 29 30__all__ = ['Flatboob'] 31 32 33class HousingFormatter(IFormatter): 34 MANDATORY_FIELDS = ('id', 'title', 'cost', 'currency', 'area', 'date', 'text') 35 36 def format_obj(self, obj, alias): 37 result = u'%s%s%s\n' % (self.BOLD, obj.title, self.NC) 38 result += 'ID: %s\n' % obj.fullid 39 40 if hasattr(obj, 'url') and obj.url: 41 result += 'URL: %s\n' % obj.url 42 43 result += 'Cost: %s%s %s\n' % (obj.cost, obj.currency, obj.utilities) 44 area = u'%.2fm²' % (obj.area) if obj.area else u'%s' % obj.area 45 result += u'Area: %s\n' % area 46 if hasattr(obj, 'price_per_meter') and not empty(obj.price_per_meter): 47 result += u'Price per square meter: %.2f %s/m²\n' % (obj.price_per_meter, obj.currency) 48 if hasattr(obj, 'rooms') and not empty(obj.rooms): 49 result += u'Rooms: %d\n' % (obj.rooms) 50 if hasattr(obj, 'bedrooms') and not empty(obj.bedrooms): 51 result += u'Bedrooms: %d\n' % (obj.bedrooms) 52 if obj.date: 53 result += 'Date: %s\n' % obj.date.strftime('%Y-%m-%d') 54 result += 'Phone: %s\n' % obj.phone 55 if hasattr(obj, 'location') and obj.location: 56 result += 'Location: %s\n' % obj.location 57 if hasattr(obj, 'station') and obj.station: 58 result += 'Station: %s\n' % obj.station 59 60 if hasattr(obj, 'photos') and obj.photos: 61 result += '\n%sPhotos%s\n' % (self.BOLD, self.NC) 62 for photo in obj.photos: 63 result += ' * %s\n' % photo.url 64 65 result += '\n%sDescription%s\n' % (self.BOLD, self.NC) 66 result += obj.text 67 68 if hasattr(obj, 'details') and obj.details: 69 result += '\n\n%sDetails%s\n' % (self.BOLD, self.NC) 70 for key, value in obj.details.items(): 71 result += ' %s: %s\n' % (key, value) 72 73 return result 74 75 76class HousingListFormatter(PrettyFormatter): 77 MANDATORY_FIELDS = ('id', 'title', 'cost', 'text') 78 79 def get_title(self, obj): 80 return '%s%s %s - %s' % (obj.cost, obj.currency, obj.utilities, obj.title) 81 82 def get_description(self, obj): 83 result = u'' 84 if hasattr(obj, 'date') and obj.date: 85 result += '%s - ' % obj.date.strftime('%Y-%m-%d') 86 result += obj.text 87 return result 88 89 90class Flatboob(ReplApplication): 91 APPNAME = 'flatboob' 92 VERSION = '2.0' 93 COPYRIGHT = 'Copyright(C) 2012-YEAR Romain Bignon' 94 DESCRIPTION = "Console application to search for housing." 95 SHORT_DESCRIPTION = "search for housing" 96 CAPS = CapHousing 97 CONFIG = {'queries': {}} 98 EXTRA_FORMATTERS = {'housing_list': HousingListFormatter, 99 'housing': HousingFormatter, 100 } 101 COMMANDS_FORMATTERS = {'search': 'housing_list', 102 'info': 'housing', 103 'load': 'housing_list' 104 } 105 106 def main(self, argv): 107 self.load_config(klass=YamlConfig) 108 return ReplApplication.main(self, argv) 109 110 @defaultcount(10) 111 def do_search(self, line): 112 """ 113 search 114 115 Search for housing. Parameters are interactively asked. 116 """ 117 pattern = 'notempty' 118 query = Query() 119 query.cities = [] 120 while pattern: 121 if len(query.cities) > 0: 122 print('\n%sSelected cities:%s %s' % (self.BOLD, self.NC, ', '.join([c.name for c in query.cities]))) 123 pattern = self.ask('Enter a city pattern (or empty to stop)', default='') 124 if not pattern: 125 break 126 127 cities = [] 128 for city in self.weboob.do('search_city', pattern): 129 cities.append(city) 130 131 if len(cities) == 0: 132 print(' Not found!') 133 continue 134 if len(cities) == 1: 135 if city in query.cities: 136 query.cities.remove(city) 137 else: 138 query.cities.append(city) 139 continue 140 141 r = 'notempty' 142 while r != '': 143 for i, city in enumerate(cities): 144 print(' %s%2d)%s [%s] %s (%s)' % (self.BOLD, i+1, self.NC, 'x' if city in query.cities else ' ', city.name, city.backend)) 145 r = self.ask('Select cities (or empty to stop)', regexp='(\d+|)', default='') 146 if not r.isdigit(): 147 continue 148 r = int(r) 149 if r <= 0 or r > len(cities): 150 continue 151 city = cities[r-1] 152 if city in query.cities: 153 query.cities.remove(city) 154 else: 155 query.cities.append(city) 156 157 r = 'notempty' 158 while r != '': 159 for i, good in enumerate(HOUSE_TYPES, 1): 160 print(' %s%2d)%s [%s] %s' % (self.BOLD, 161 i, 162 self.NC, 163 'x' if good in query.house_types else ' ', good)) 164 r = self.ask('Select type of house (or empty to stop)', regexp='(\d+|)', default='') 165 if not r.isdigit(): 166 continue 167 r = int(r) 168 if r <= 0 or r > len(HOUSE_TYPES): 169 continue 170 value = list(HOUSE_TYPES)[r - 1] 171 if value in query.house_types: 172 query.house_types.remove(value) 173 else: 174 query.house_types.append(value) 175 176 _type = None 177 posts_types = sorted(POSTS_TYPES) 178 while _type not in range(len(posts_types)): 179 for i, t in enumerate(posts_types): 180 print(' %s%2d)%s %s' % (self.BOLD, 181 i, 182 self.NC, 183 t)) 184 _type = self.ask_int('Type of query') 185 186 query.type = posts_types[_type] 187 188 r = 'notempty' 189 while r != '': 190 for i, good in enumerate(ADVERT_TYPES, 1): 191 print(' %s%2d)%s [%s] %s' % (self.BOLD, 192 i, 193 self.NC, 194 'x' if good in query.advert_types else ' ', good)) 195 r = self.ask('Select type of posts (or empty to stop)', regexp='(\d+|)', default='') 196 if not r.isdigit(): 197 continue 198 r = int(r) 199 if r <= 0 or r > len(ADVERT_TYPES): 200 continue 201 value = list(ADVERT_TYPES)[r - 1] 202 if value in query.advert_types: 203 query.advert_types.remove(value) 204 else: 205 query.advert_types.append(value) 206 207 query.area_min = self.ask_int('Enter min area') 208 query.area_max = self.ask_int('Enter max area') 209 query.cost_min = self.ask_int('Enter min cost') 210 query.cost_max = self.ask_int('Enter max cost') 211 query.nb_rooms = self.ask_int('Enter number of rooms') 212 save_query = self.ask('Save query (y/n)', default='n') 213 if save_query.upper() == 'Y': 214 name = '' 215 while not name: 216 name = self.ask('Query name') 217 218 self.config.set('queries', name, query) 219 self.config.save() 220 self.complete_search(query) 221 222 def complete_search(self, query): 223 self.change_path([u'housings']) 224 self.start_format() 225 for housing in self.do('search_housings', query): 226 self.cached_format(housing) 227 228 def ask_int(self, txt): 229 r = self.ask(txt, default='', regexp='(\d+|)') 230 if r: 231 return int(r) 232 return None 233 234 @defaultcount(10) 235 def do_load(self, query_name): 236 """ 237 load [query name] 238 without query name : list loadable queries 239 with query name laod query 240 """ 241 queries = self.config.get('queries') 242 if not queries: 243 print('There is no saved queries', file=self.stderr) 244 return 2 245 246 if not query_name: 247 for name in queries.keys(): 248 print(' %s* %s %s' % (self.BOLD, 249 self.NC, 250 name)) 251 query_name = self.ask('Which one') 252 253 if query_name in queries: 254 self.complete_search(queries.get(query_name)) 255 else: 256 print('Unknown query', file=self.stderr) 257 return 2 258 259 def complete_info(self, text, line, *ignored): 260 args = line.split(' ') 261 if len(args) == 2: 262 return self._complete_object() 263 264 def do_info(self, _id): 265 """ 266 info ID 267 268 Get information about a housing. 269 """ 270 if not _id: 271 print('This command takes an argument: %s' % self.get_command_help('info', short=True), file=self.stderr) 272 return 2 273 274 housing = self.get_object(_id, 'get_housing') 275 if not housing: 276 print('Housing not found: %s' % _id, file=self.stderr) 277 return 3 278 279 self.start_format() 280 self.format(housing) 281