├── .gitignore ├── .travis.yml ├── AUTHORS ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs └── src ├── cmd.rs ├── format.rs ├── geometry.rs ├── gtfs.rs ├── main.rs ├── main.rs.in └── tfl ├── client.rs ├── line.rs └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | cache 3 | gtfs 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | 7 | notifications: 8 | email: 9 | on_success: never 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | TFL GTFS is thanks to the work of: 2 | 3 | Tom Burdick 4 | Samuel Pro 5 | Arnau Siches 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aho-corasick" 3 | version = "0.5.3" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "0.6.4" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "ansi_term" 19 | version = "0.7.5" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | 22 | [[package]] 23 | name = "ansi_term" 24 | version = "0.11.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | dependencies = [ 27 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 28 | ] 29 | 30 | [[package]] 31 | name = "aster" 32 | version = "0.22.1" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | dependencies = [ 35 | "syntex_syntax 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "atty" 40 | version = "0.2.9" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | dependencies = [ 43 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 46 | ] 47 | 48 | [[package]] 49 | name = "bitflags" 50 | version = "0.5.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | 53 | [[package]] 54 | name = "bitflags" 55 | version = "0.7.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | 58 | [[package]] 59 | name = "bitflags" 60 | version = "0.9.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | 63 | [[package]] 64 | name = "bitflags" 65 | version = "1.0.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | 68 | [[package]] 69 | name = "byteorder" 70 | version = "0.5.3" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | 73 | [[package]] 74 | name = "cfg-if" 75 | version = "0.1.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | 78 | [[package]] 79 | name = "clap" 80 | version = "2.31.2" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | dependencies = [ 83 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "atty 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 90 | ] 91 | 92 | [[package]] 93 | name = "clippy" 94 | version = "0.0.195" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | dependencies = [ 97 | "clippy_lints 0.0.195 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 100 | ] 101 | 102 | [[package]] 103 | name = "clippy_lints" 104 | version = "0.0.195" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | dependencies = [ 107 | "if_chain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", 109 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 110 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 111 | "pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 112 | "quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "regex-syntax 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "serde_derive 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 120 | ] 121 | 122 | [[package]] 123 | name = "cookie" 124 | version = "0.2.5" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | dependencies = [ 127 | "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 131 | ] 132 | 133 | [[package]] 134 | name = "csv" 135 | version = "0.14.7" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | dependencies = [ 138 | "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 140 | ] 141 | 142 | [[package]] 143 | name = "either" 144 | version = "1.5.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | 147 | [[package]] 148 | name = "env_logger" 149 | version = "0.3.5" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | dependencies = [ 152 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", 154 | ] 155 | 156 | [[package]] 157 | name = "fuchsia-zircon" 158 | version = "0.3.3" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | dependencies = [ 161 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 163 | ] 164 | 165 | [[package]] 166 | name = "fuchsia-zircon-sys" 167 | version = "0.3.3" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | 170 | [[package]] 171 | name = "gcc" 172 | version = "0.3.54" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | 175 | [[package]] 176 | name = "gdi32-sys" 177 | version = "0.2.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | dependencies = [ 180 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 182 | ] 183 | 184 | [[package]] 185 | name = "getopts" 186 | version = "0.2.17" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | 189 | [[package]] 190 | name = "hpack" 191 | version = "0.2.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | dependencies = [ 194 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 195 | ] 196 | 197 | [[package]] 198 | name = "httparse" 199 | version = "1.2.4" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | 202 | [[package]] 203 | name = "hyper" 204 | version = "0.9.18" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | dependencies = [ 207 | "cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 208 | "httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 210 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 211 | "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 212 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 213 | "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 214 | "openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 215 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 216 | "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 217 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 218 | "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 219 | "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 220 | "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 221 | "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 222 | ] 223 | 224 | [[package]] 225 | name = "idna" 226 | version = "0.1.4" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | dependencies = [ 229 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 231 | "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 232 | ] 233 | 234 | [[package]] 235 | name = "if_chain" 236 | version = "0.1.2" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | 239 | [[package]] 240 | name = "itertools" 241 | version = "0.7.8" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | dependencies = [ 244 | "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 245 | ] 246 | 247 | [[package]] 248 | name = "itoa" 249 | version = "0.1.1" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | 252 | [[package]] 253 | name = "kernel32-sys" 254 | version = "0.2.2" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | dependencies = [ 257 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 259 | ] 260 | 261 | [[package]] 262 | name = "language-tags" 263 | version = "0.2.2" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | 266 | [[package]] 267 | name = "lazy_static" 268 | version = "0.2.11" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | 271 | [[package]] 272 | name = "lazy_static" 273 | version = "1.0.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | 276 | [[package]] 277 | name = "libc" 278 | version = "0.2.40" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | 281 | [[package]] 282 | name = "libressl-pnacl-sys" 283 | version = "2.1.6" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | dependencies = [ 286 | "pnacl-build-helper 1.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 287 | ] 288 | 289 | [[package]] 290 | name = "log" 291 | version = "0.3.9" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | dependencies = [ 294 | "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 295 | ] 296 | 297 | [[package]] 298 | name = "log" 299 | version = "0.4.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | dependencies = [ 302 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 303 | ] 304 | 305 | [[package]] 306 | name = "matches" 307 | version = "0.1.6" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | 310 | [[package]] 311 | name = "memchr" 312 | version = "0.1.11" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | dependencies = [ 315 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 316 | ] 317 | 318 | [[package]] 319 | name = "memchr" 320 | version = "2.0.1" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | dependencies = [ 323 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 324 | ] 325 | 326 | [[package]] 327 | name = "mime" 328 | version = "0.2.6" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | dependencies = [ 331 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 332 | ] 333 | 334 | [[package]] 335 | name = "num-traits" 336 | version = "0.1.43" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | dependencies = [ 339 | "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 340 | ] 341 | 342 | [[package]] 343 | name = "num-traits" 344 | version = "0.2.2" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | 347 | [[package]] 348 | name = "num_cpus" 349 | version = "1.8.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | dependencies = [ 352 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 353 | ] 354 | 355 | [[package]] 356 | name = "openssl" 357 | version = "0.7.14" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | dependencies = [ 360 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 361 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 362 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 363 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 364 | "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", 365 | "openssl-sys-extras 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 366 | ] 367 | 368 | [[package]] 369 | name = "openssl-sys" 370 | version = "0.7.17" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | dependencies = [ 373 | "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 374 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 375 | "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 376 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 377 | "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 378 | ] 379 | 380 | [[package]] 381 | name = "openssl-sys-extras" 382 | version = "0.7.14" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | dependencies = [ 385 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 386 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 387 | "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", 388 | ] 389 | 390 | [[package]] 391 | name = "openssl-verify" 392 | version = "0.1.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | dependencies = [ 395 | "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 396 | ] 397 | 398 | [[package]] 399 | name = "percent-encoding" 400 | version = "1.0.1" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | 403 | [[package]] 404 | name = "pkg-config" 405 | version = "0.3.9" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | 408 | [[package]] 409 | name = "pnacl-build-helper" 410 | version = "1.4.11" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | dependencies = [ 413 | "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 414 | "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 415 | ] 416 | 417 | [[package]] 418 | name = "proc-macro2" 419 | version = "0.3.6" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | dependencies = [ 422 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 423 | ] 424 | 425 | [[package]] 426 | name = "pulldown-cmark" 427 | version = "0.1.2" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | dependencies = [ 430 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 431 | "getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 432 | ] 433 | 434 | [[package]] 435 | name = "quasi" 436 | version = "0.16.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | dependencies = [ 439 | "syntex_errors 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 440 | "syntex_syntax 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 441 | ] 442 | 443 | [[package]] 444 | name = "quasi_codegen" 445 | version = "0.16.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | dependencies = [ 448 | "aster 0.22.1 (registry+https://github.com/rust-lang/crates.io-index)", 449 | "syntex 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 450 | "syntex_errors 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 451 | "syntex_syntax 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 452 | ] 453 | 454 | [[package]] 455 | name = "quasi_macros" 456 | version = "0.16.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | dependencies = [ 459 | "quasi_codegen 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", 460 | ] 461 | 462 | [[package]] 463 | name = "quine-mc_cluskey" 464 | version = "0.2.4" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | 467 | [[package]] 468 | name = "quote" 469 | version = "0.5.2" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | dependencies = [ 472 | "proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 473 | ] 474 | 475 | [[package]] 476 | name = "rand" 477 | version = "0.3.22" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | dependencies = [ 480 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 481 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 482 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 483 | ] 484 | 485 | [[package]] 486 | name = "rand" 487 | version = "0.4.2" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | dependencies = [ 490 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 491 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 492 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 493 | ] 494 | 495 | [[package]] 496 | name = "redox_syscall" 497 | version = "0.1.37" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | 500 | [[package]] 501 | name = "redox_termios" 502 | version = "0.1.1" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | dependencies = [ 505 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 506 | ] 507 | 508 | [[package]] 509 | name = "regex" 510 | version = "0.1.80" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | dependencies = [ 513 | "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 514 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 515 | "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 516 | "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 517 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 518 | ] 519 | 520 | [[package]] 521 | name = "regex" 522 | version = "0.2.10" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | dependencies = [ 525 | "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 526 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 527 | "regex-syntax 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 528 | "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 529 | "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 530 | ] 531 | 532 | [[package]] 533 | name = "regex-syntax" 534 | version = "0.3.9" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | 537 | [[package]] 538 | name = "regex-syntax" 539 | version = "0.5.5" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | dependencies = [ 542 | "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 543 | ] 544 | 545 | [[package]] 546 | name = "remove_dir_all" 547 | version = "0.5.1" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | dependencies = [ 550 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 551 | ] 552 | 553 | [[package]] 554 | name = "rust-crypto" 555 | version = "0.2.36" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | dependencies = [ 558 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 559 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 560 | "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", 561 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 562 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 563 | ] 564 | 565 | [[package]] 566 | name = "rustc-serialize" 567 | version = "0.3.24" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | 570 | [[package]] 571 | name = "same-file" 572 | version = "0.1.3" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | dependencies = [ 575 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 576 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 577 | ] 578 | 579 | [[package]] 580 | name = "scoped_threadpool" 581 | version = "0.1.9" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | 584 | [[package]] 585 | name = "semver" 586 | version = "0.9.0" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | dependencies = [ 589 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 590 | ] 591 | 592 | [[package]] 593 | name = "semver-parser" 594 | version = "0.7.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | 597 | [[package]] 598 | name = "serde" 599 | version = "0.7.15" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | 602 | [[package]] 603 | name = "serde" 604 | version = "1.0.42" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | 607 | [[package]] 608 | name = "serde_codegen" 609 | version = "0.7.15" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | dependencies = [ 612 | "aster 0.22.1 (registry+https://github.com/rust-lang/crates.io-index)", 613 | "quasi 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", 614 | "quasi_codegen 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", 615 | "quasi_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", 616 | "serde_codegen_internals 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 617 | "syntex 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 618 | "syntex_syntax 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 619 | ] 620 | 621 | [[package]] 622 | name = "serde_codegen_internals" 623 | version = "0.4.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | dependencies = [ 626 | "syntex_errors 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 627 | "syntex_syntax 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 628 | ] 629 | 630 | [[package]] 631 | name = "serde_derive" 632 | version = "1.0.42" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | dependencies = [ 635 | "proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 636 | "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 637 | "serde_derive_internals 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", 638 | "syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", 639 | ] 640 | 641 | [[package]] 642 | name = "serde_derive_internals" 643 | version = "0.23.1" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | dependencies = [ 646 | "proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 647 | "syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", 648 | ] 649 | 650 | [[package]] 651 | name = "serde_json" 652 | version = "0.7.4" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | dependencies = [ 655 | "itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 656 | "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 657 | "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 658 | ] 659 | 660 | [[package]] 661 | name = "serde_macros" 662 | version = "0.7.15" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | dependencies = [ 665 | "serde_codegen 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 666 | ] 667 | 668 | [[package]] 669 | name = "solicit" 670 | version = "0.4.4" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | dependencies = [ 673 | "hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 674 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 675 | ] 676 | 677 | [[package]] 678 | name = "strsim" 679 | version = "0.7.0" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | 682 | [[package]] 683 | name = "syn" 684 | version = "0.13.1" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | dependencies = [ 687 | "proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 688 | "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 689 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 690 | ] 691 | 692 | [[package]] 693 | name = "syntex" 694 | version = "0.39.0" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | dependencies = [ 697 | "syntex_errors 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 698 | "syntex_syntax 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 699 | ] 700 | 701 | [[package]] 702 | name = "syntex_errors" 703 | version = "0.39.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | dependencies = [ 706 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 707 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 708 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 709 | "syntex_pos 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 710 | "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 711 | "unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 712 | ] 713 | 714 | [[package]] 715 | name = "syntex_pos" 716 | version = "0.39.0" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | dependencies = [ 719 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 720 | ] 721 | 722 | [[package]] 723 | name = "syntex_syntax" 724 | version = "0.39.0" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | dependencies = [ 727 | "bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 728 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 729 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 730 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 731 | "syntex_errors 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 732 | "syntex_pos 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 733 | "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 734 | "unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 735 | ] 736 | 737 | [[package]] 738 | name = "tempdir" 739 | version = "0.3.7" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | dependencies = [ 742 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 743 | "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 744 | ] 745 | 746 | [[package]] 747 | name = "term" 748 | version = "0.4.6" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | dependencies = [ 751 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 752 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 753 | ] 754 | 755 | [[package]] 756 | name = "termion" 757 | version = "1.5.1" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | dependencies = [ 760 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 761 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 762 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 763 | ] 764 | 765 | [[package]] 766 | name = "textwrap" 767 | version = "0.9.0" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | dependencies = [ 770 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 771 | ] 772 | 773 | [[package]] 774 | name = "tflgtfs" 775 | version = "0.1.0" 776 | dependencies = [ 777 | "ansi_term 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", 778 | "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)", 779 | "clippy 0.0.195 (registry+https://github.com/rust-lang/crates.io-index)", 780 | "csv 0.14.7 (registry+https://github.com/rust-lang/crates.io-index)", 781 | "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 782 | "hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)", 783 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 784 | "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", 785 | "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 786 | "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 787 | "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 788 | "serde_codegen 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 789 | "serde_json 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", 790 | "serde_macros 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 791 | "syntex 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", 792 | ] 793 | 794 | [[package]] 795 | name = "thread-id" 796 | version = "2.0.0" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | dependencies = [ 799 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 800 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 801 | ] 802 | 803 | [[package]] 804 | name = "thread_local" 805 | version = "0.2.7" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | dependencies = [ 808 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 809 | ] 810 | 811 | [[package]] 812 | name = "thread_local" 813 | version = "0.3.5" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | dependencies = [ 816 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 817 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 818 | ] 819 | 820 | [[package]] 821 | name = "time" 822 | version = "0.1.39" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | dependencies = [ 825 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 826 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 827 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 828 | ] 829 | 830 | [[package]] 831 | name = "toml" 832 | version = "0.4.6" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | dependencies = [ 835 | "serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)", 836 | ] 837 | 838 | [[package]] 839 | name = "traitobject" 840 | version = "0.0.1" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | 843 | [[package]] 844 | name = "typeable" 845 | version = "0.1.2" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | 848 | [[package]] 849 | name = "ucd-util" 850 | version = "0.1.1" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | 853 | [[package]] 854 | name = "unicase" 855 | version = "1.4.2" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | dependencies = [ 858 | "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 859 | ] 860 | 861 | [[package]] 862 | name = "unicode-bidi" 863 | version = "0.3.4" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | dependencies = [ 866 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 867 | ] 868 | 869 | [[package]] 870 | name = "unicode-normalization" 871 | version = "0.1.5" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | 874 | [[package]] 875 | name = "unicode-width" 876 | version = "0.1.4" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | 879 | [[package]] 880 | name = "unicode-xid" 881 | version = "0.0.3" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | 884 | [[package]] 885 | name = "unicode-xid" 886 | version = "0.1.0" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | 889 | [[package]] 890 | name = "unreachable" 891 | version = "1.0.0" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | dependencies = [ 894 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 895 | ] 896 | 897 | [[package]] 898 | name = "url" 899 | version = "1.7.0" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | dependencies = [ 902 | "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 903 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 904 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 905 | ] 906 | 907 | [[package]] 908 | name = "user32-sys" 909 | version = "0.2.0" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | dependencies = [ 912 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 913 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 914 | ] 915 | 916 | [[package]] 917 | name = "utf8-ranges" 918 | version = "0.1.3" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | 921 | [[package]] 922 | name = "utf8-ranges" 923 | version = "1.0.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | 926 | [[package]] 927 | name = "vec_map" 928 | version = "0.8.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | 931 | [[package]] 932 | name = "version_check" 933 | version = "0.1.3" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | 936 | [[package]] 937 | name = "void" 938 | version = "1.0.2" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | 941 | [[package]] 942 | name = "walkdir" 943 | version = "1.0.7" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | dependencies = [ 946 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 947 | "same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 948 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 949 | ] 950 | 951 | [[package]] 952 | name = "winapi" 953 | version = "0.2.8" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | 956 | [[package]] 957 | name = "winapi" 958 | version = "0.3.4" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | dependencies = [ 961 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 962 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 963 | ] 964 | 965 | [[package]] 966 | name = "winapi-build" 967 | version = "0.1.1" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | 970 | [[package]] 971 | name = "winapi-i686-pc-windows-gnu" 972 | version = "0.4.0" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | 975 | [[package]] 976 | name = "winapi-x86_64-pc-windows-gnu" 977 | version = "0.4.0" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | 980 | [metadata] 981 | "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" 982 | "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" 983 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 984 | "checksum ansi_term 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "30275ad0ad84ec1c06dde3b3f7d23c6006b7d76d61a85e7060b426b747eff70d" 985 | "checksum aster 0.22.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2e21e476ef6522e1762461ddbd1eba4d049c9a86619ccd03226b09f3d0741e" 986 | "checksum atty 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6609a866dd1a1b2d0ee1362195bf3e4f6438abb2d80120b83b1e1f4fb6476dd0" 987 | "checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" 988 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 989 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 990 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 991 | "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" 992 | "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" 993 | "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" 994 | "checksum clippy 0.0.195 (registry+https://github.com/rust-lang/crates.io-index)" = "1d48c23f9c2f4cb3ac08fb972e9a892778f7829c7ae611106887b42cf1749ad2" 995 | "checksum clippy_lints 0.0.195 (registry+https://github.com/rust-lang/crates.io-index)" = "f490c98750106b5034c6b625b83b69539e42537783cb802220153f53f9a83ffa" 996 | "checksum cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0e3d6405328b6edb412158b3b7710e2634e23f3614b9bb1c412df7952489a626" 997 | "checksum csv 0.14.7 (registry+https://github.com/rust-lang/crates.io-index)" = "266c1815d7ca63a5bd86284043faf91e8c95e943e55ce05dc0ae08e952de18bc" 998 | "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" 999 | "checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" 1000 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 1001 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 1002 | "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" 1003 | "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" 1004 | "checksum getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "b900c08c1939860ce8b54dc6a89e26e00c04c380fd0e09796799bd7f12861e05" 1005 | "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" 1006 | "checksum httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f407128745b78abc95c0ffbe4e5d37427fdc0d45470710cfef8c44522a2e37" 1007 | "checksum hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9bf64f730d6ee4b0528a5f0a316363da9d8104318731509d4ccc86248f82b3" 1008 | "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" 1009 | "checksum if_chain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "61bb90bdd39e3af69b0172dfc6130f6cd6332bf040fbb9bdd4401d37adbd48b8" 1010 | "checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450" 1011 | "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" 1012 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 1013 | "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 1014 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 1015 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 1016 | "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" 1017 | "checksum libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cbc058951ab6a3ef35ca16462d7642c4867e6403520811f28537a4e2f2db3e71" 1018 | "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 1019 | "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" 1020 | "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" 1021 | "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" 1022 | "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" 1023 | "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" 1024 | "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" 1025 | "checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364" 1026 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 1027 | "checksum openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "c4117b6244aac42ed0150a6019b4d953d28247c5dd6ae6f46ae469b5f2318733" 1028 | "checksum openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)" = "89c47ee94c352eea9ddaf8e364be7f978a3bb6d66d73176572484238dd5a5c3f" 1029 | "checksum openssl-sys-extras 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "11c5e1dba7d3d03d80f045bf0d60111dc69213b67651e7c889527a3badabb9fa" 1030 | "checksum openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed86cce894f6b0ed4572e21eb34026f1dc8869cb9ee3869029131bc8c3feb2d" 1031 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 1032 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 1033 | "checksum pnacl-build-helper 1.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dfbe13ee77c06fb633d71c72438bd983286bb3521863a753ade8e951c7efb090" 1034 | "checksum proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "49b6a521dc81b643e9a51e0d1cf05df46d5a2f3c0280ea72bcb68276ba64a118" 1035 | "checksum pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdf85cda6cadfae5428a54661d431330b312bc767ddbc57adbedc24da66e32" 1036 | "checksum quasi 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "314e56e9e59af71a5b1f09fab15e8e66ab2ccb786688f8d2e04d98b8d7cbc161" 1037 | "checksum quasi_codegen 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a3856abd5ec12f873eeac0837cce65ac33814ed4acba287a9e806620763d4b7" 1038 | "checksum quasi_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b530b7ff57b6a38e4d53909c64657f40e0eef6a8fea1f0943d76160c277c9c5" 1039 | "checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" 1040 | "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" 1041 | "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" 1042 | "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" 1043 | "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" 1044 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 1045 | "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" 1046 | "checksum regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "aec3f58d903a7d2a9dc2bf0e41a746f4530e0cab6b615494e058f67a3ef947fb" 1047 | "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" 1048 | "checksum regex-syntax 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bd90079345f4a4c3409214734ae220fd773c6f2e8a543d07370c6c1c369cfbfb" 1049 | "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" 1050 | "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" 1051 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 1052 | "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" 1053 | "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 1054 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1055 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1056 | "checksum serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1b0e0732aa8ec4267f61815a396a942ba3525062e3bd5520aa8419927cfc0a92" 1057 | "checksum serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)" = "a73973861352c932ed1365ce22b32467ce260ac4c8db11cf750ce56334ff2dcf" 1058 | "checksum serde_codegen 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "973836af70870533bc6a332488ded6aef80a5ff507b663e8b4e1ef44580ea8fd" 1059 | "checksum serde_codegen_internals 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eed6f11ba7400225025b44c77b4eca1655958aac6c586c5ec9c76fd5597ef849" 1060 | "checksum serde_derive 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b392c5a0cebb98121454531c50e60e2ffe0fbeb1a44da277da2d681d08d7dc0b" 1061 | "checksum serde_derive_internals 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9d30c4596450fd7bbda79ef15559683f9a79ac0193ea819db90000d7e1cae794" 1062 | "checksum serde_json 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b22e8a0554f31cb0f501e027de07b253553b308124f61c57598b9678dba35c0b" 1063 | "checksum serde_macros 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "10aa279b5b061a3e827639cc15e563be096b7323c9c811e10a4b18ba4607eaf8" 1064 | "checksum solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "172382bac9424588d7840732b250faeeef88942e37b6e35317dce98cafdd75b2" 1065 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 1066 | "checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59" 1067 | "checksum syntex 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8cdab48fb1d177756cac40894a2f12ee8e37beadb0d220855343093af7411cbd" 1068 | "checksum syntex_errors 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b2b48e32b90ebbd428cfbcb768fa2b798b6869100bd5980f433eb9c0aab14d0" 1069 | "checksum syntex_pos 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e5253631b0329f46ebcae63c2db016d4c8171eaa940610cc894f45617ebe92" 1070 | "checksum syntex_syntax 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0834c549cf3abdf728c0696fe097fd158951bfbb105d8cc06f6d0ceb7fa432f0" 1071 | "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" 1072 | "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" 1073 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 1074 | "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" 1075 | "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" 1076 | "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" 1077 | "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" 1078 | "checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" 1079 | "checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9" 1080 | "checksum traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "07eaeb7689bb7fca7ce15628319635758eda769fed481ecfe6686ddef2600616" 1081 | "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" 1082 | "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" 1083 | "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" 1084 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1085 | "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" 1086 | "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" 1087 | "checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" 1088 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 1089 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 1090 | "checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7" 1091 | "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" 1092 | "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" 1093 | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" 1094 | "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" 1095 | "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" 1096 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1097 | "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" 1098 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1099 | "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" 1100 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1101 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1102 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1103 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tflgtfs" 3 | version = "0.1.0" 4 | authors = ["Tom Burdick "] 5 | build = "build.rs" 6 | 7 | [features] 8 | default = ["serde_codegen"] 9 | nightly = ["serde_macros", "clippy"] 10 | 11 | [build-dependencies] 12 | serde_codegen = { version = "0.7", optional = true } 13 | syntex = "0.39" 14 | 15 | [dependencies] 16 | ansi_term = "0.7" 17 | clap = "2.3" 18 | csv = "0.14" 19 | env_logger = "0.3" 20 | hyper = "0.9" 21 | log = "0.3" 22 | rand = "0.3" 23 | rust-crypto = "^0.2" 24 | scoped_threadpool = "0.1" 25 | serde = "0.7" 26 | serde_json = "0.7" 27 | serde_macros = { version = "0.7", optional = true } 28 | clippy = { version = "*", optional = true } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transport For London GTFS Exporter 2 | 3 | This simple Rust CLI allows you to fetch data from the 4 | [Tfl Unified API][tfl-api] and transform it to [GTFS][gtfs]. 5 | 6 | [![Build Status](https://travis-ci.org/CommuteStream/tflgtfs.svg?branch=master)](https://travis-ci.org/CommuteStream/tflgtfs) 7 | [![Clippy Linting Result](https://clippy.bashy.io/github/CommuteStream/tflgtfs/master/badge.svg)](https://clippy.bashy.io/github/CommuteStream/tflgtfs/master/log) 8 | 9 | 10 | ## Install 11 | 12 | Clone [the repository][tfl-cli] and compile: 13 | 14 | ```sh 15 | cargo build --release 16 | ``` 17 | 18 | You will find the binary in `./target/release/`. 19 | 20 | ### OSX Users 21 | 22 | *WARNING*: If you compile under OSX 10.11 you might need to specify the 23 | OpenSSL include path. For example, having OpenSSL installed via Homebrew, 24 | the command is: 25 | 26 | ```sh 27 | OPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include cargo build --release 28 | ``` 29 | 30 | ### Arch Linux Users 31 | *WARNING*: OpenSSL versions `> 1.0` do not work. You'll need to install 32 | the [`openssl-1.0`](https://www.archlinux.org/packages/extra/x86_64/openssl-1.0/) package in the `extra` repository and compile with the following command ([thanks to @Yamakaky](https://github.com/sfackler/rust-openssl/issues/631#issuecomment-315404620)): 33 | 34 | ```sh 35 | OPENSSL_INCLUDE_DIR=/usr/include/openssl-1.0 OPENSSL_LIB_DIR=/usr/lib/openssl-1.0 cargo build --release 36 | ``` 37 | 38 | 39 | ## Usage 40 | 41 | Check the help `./target/release/tflgtfs help` for details. 42 | 43 | In short, you can fetch Tfl lines with the `fetch-lines` command and transform 44 | the cached values with the `transform gtfs` command. 45 | 46 | You can do it in one shot via: 47 | 48 | ```sh 49 | ./target/release/tflgtfs fetch-lines --format gtfs 50 | ``` 51 | 52 | You will find the resulting GTFS files inside `./gtfs`. 53 | 54 | 55 | ## Development 56 | 57 | When developing on nightly build it using the following command to actually 58 | benefit from linting and Serde macro: 59 | 60 | ``` 61 | cargo build --features nightly --no-default-features 62 | ``` 63 | 64 | 65 | ## License 66 | 67 | See [License](./LICENSE). 68 | 69 | 70 | [tfl-cli]: https://github.com/CommuteStream/tflgtfs/ 71 | [tfl-api]: https://api.tfl.gov.uk/ 72 | [gtfs]: https://developers.google.com/transit/gtfs/ 73 | [cargo-clippy]: https://crates.io/crates/cargo-clippy 74 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "serde_macros"))] 2 | mod inner { 3 | extern crate syntex; 4 | extern crate serde_codegen; 5 | 6 | use std::env; 7 | use std::path::Path; 8 | 9 | pub fn main() { 10 | let out_dir = env::var_os("OUT_DIR").unwrap(); 11 | 12 | let src = Path::new("src/main.rs.in"); 13 | let dst = Path::new(&out_dir).join("main.rs"); 14 | 15 | let mut registry = syntex::Registry::new(); 16 | 17 | serde_codegen::register(&mut registry); 18 | registry.expand("", &src, &dst).unwrap(); 19 | } 20 | } 21 | 22 | #[cfg(feature = "serde_macros")] 23 | mod inner { 24 | pub fn main() {} 25 | } 26 | 27 | fn main() { 28 | inner::main(); 29 | } 30 | -------------------------------------------------------------------------------- /src/cmd.rs: -------------------------------------------------------------------------------- 1 | use ansi_term::Colour::{Green, Red, White, Blue}; 2 | use rand::distributions::{IndependentSample, Range}; 3 | use rand; 4 | use scoped_threadpool::Pool; 5 | use std::collections::HashSet; 6 | use std::process; 7 | use std::sync::Arc; 8 | 9 | use format::{OutputFormat}; 10 | use gtfs::{write_gtfs, route_section_id}; 11 | use tfl::line::{Line}; 12 | use tfl::client::{Client, DataSource}; 13 | 14 | 15 | pub fn fetch_lines(format: OutputFormat, thread_number: u32, sample_size: Option) { 16 | let lines = load_lines(DataSource::API, thread_number, sample_size); 17 | 18 | match format { 19 | OutputFormat::GTFS => transform_gtfs(lines), 20 | _ => process::exit(0), 21 | } 22 | } 23 | 24 | pub fn transform(format: OutputFormat, thread_number: u32, sample_size: Option) { 25 | let lines = load_lines(DataSource::Cache, thread_number, sample_size); 26 | 27 | match format { 28 | OutputFormat::GTFS => transform_gtfs(lines), 29 | _ => process::exit(0), 30 | } 31 | } 32 | 33 | fn sample(xs: Vec, size: usize) -> Vec { 34 | let len = xs.len(); 35 | 36 | if size > len { return xs } 37 | 38 | let between = Range::new(0usize, len - size); 39 | let mut rng = rand::thread_rng(); 40 | let seed = between.ind_sample(&mut rng); 41 | let lower = seed; 42 | let upper = seed + size; 43 | 44 | println!("{}: {}..{}", Green.bold().paint("Sample window"), lower, upper); 45 | 46 | xs[lower .. upper].to_vec() 47 | } 48 | 49 | #[test] 50 | fn sample_test() { 51 | assert_eq!(sample(vec![0; 100], 200).len(), 100); 52 | assert_eq!(sample(vec![0; 100], 10).len(), 10); 53 | } 54 | 55 | fn load_lines(data_source: DataSource, thread_number: u32, sample_size: Option) -> Vec { 56 | let mut pool = Pool::new(thread_number); 57 | let client = Arc::new(Client::new()); 58 | 59 | let mut lines = match data_source { 60 | DataSource::Cache => client.get_cached_lines(), 61 | DataSource::API => client.get_lines(), 62 | }; 63 | 64 | if let Some(n) = sample_size { 65 | lines = sample(lines, n); 66 | } 67 | 68 | pool.scoped(|scope| { 69 | for line in &mut lines { 70 | let client = client.clone(); 71 | scope.execute(move || { 72 | line.inbound_sequence = client.get_sequence(&line.id, "inbound"); 73 | line.outbound_sequence = client.get_sequence(&line.id, "outbound"); 74 | line.stops = Some(client.get_stops(&line.id)); 75 | for route_section in &mut line.route_sections { 76 | println!("{} Timetable", Green.bold().paint("Getting")); 77 | println!("\tLine: {}", Blue.bold().paint(line.name.clone())); 78 | println!("\tRoute Section: {} ...", White.bold().paint(route_section.name.clone())); 79 | route_section.timetable = client.get_timetable(&line.id, &route_section.originator, &route_section.destination); 80 | } 81 | }); 82 | } 83 | }); 84 | 85 | lines 86 | } 87 | 88 | fn transform_gtfs(lines: Vec) { 89 | let mut line_count = 0; 90 | let mut line_ids: HashSet = HashSet::new(); 91 | let mut route_section_count = 0; 92 | let mut route_section_ids: HashSet = HashSet::new(); 93 | let mut schedule_names: HashSet = HashSet::new(); 94 | 95 | for line in &lines { 96 | let is_duplicated = if line_ids.contains(&line.id) { 97 | Red.paint("yes") 98 | } else { 99 | Green.paint("no") 100 | }; 101 | 102 | println!("{}; Duplicate: {}", line, is_duplicated); 103 | 104 | for route_section in &line.route_sections { 105 | let has_timetable = match route_section.timetable { 106 | Some(ref timetable) => { 107 | let names = timetable.schedule_names(); 108 | schedule_names = schedule_names.union(&names).cloned().collect::>(); 109 | names.is_empty() 110 | }, 111 | None => false, 112 | }; 113 | 114 | let id = route_section_id(&line, &route_section); 115 | println!(" {}, Has Timetable: {}, Duplicate: {}", id, has_timetable, route_section_ids.contains(&id)); 116 | route_section_ids.insert(id.clone()); 117 | route_section_count += 1; 118 | } 119 | line_count += 1; 120 | line_ids.insert(line.id.clone()); 121 | } 122 | 123 | if lines.is_empty() { 124 | println!("No lines found in the cache, try fetching some data first"); 125 | process::exit(0); 126 | } 127 | 128 | println!("Duplicate Lines: {}, Duplicate Route Sections: {}", line_count - line_ids.len(), route_section_count-route_section_ids.len()); 129 | 130 | println!("Schedule Names:"); 131 | for schedule_name in &schedule_names { 132 | println!("\t{}", schedule_name); 133 | } 134 | 135 | // Generate CSV files from fetched data 136 | write_gtfs(&lines); 137 | } 138 | -------------------------------------------------------------------------------- /src/format.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | #[derive(Debug)] 4 | pub enum OutputFormat { 5 | GTFS, 6 | JSON, 7 | None, 8 | } 9 | 10 | impl FromStr for OutputFormat { 11 | type Err = &'static str; 12 | 13 | fn from_str(value: &str) -> Result { 14 | match value { 15 | "gtfs" => Ok(OutputFormat::GTFS), 16 | "json" => Ok(OutputFormat::JSON), 17 | "none" => Ok(OutputFormat::None), 18 | _ => Err("Unexpected output format") 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/geometry.rs: -------------------------------------------------------------------------------- 1 | use serde_json; 2 | use std::collections::{HashSet, HashMap}; 3 | use std::f64::consts::PI; 4 | use std::fmt; 5 | 6 | 7 | const PRECISION: f64 = 10000.0; 8 | 9 | /// Point containing latitude and longitude values as integers. 10 | /// Integers are used here due to Rust itself not providing some basic 11 | /// floating point functionality at the moment. All due to the undefined 12 | /// behavior revolving around `NaN` float values. For example, what is the boolean 13 | /// result of `0.0 > NaN` ? Its undefined, so therefore > is not implemented by 14 | /// Rust Proper! Perhaps they'll fix this massive inconvienence in the future. 15 | /// TODO use floats 16 | #[derive(PartialEq, Eq, Clone, Debug, Hash, Copy)] 17 | pub struct Point { 18 | lat: i64, 19 | lon: i64, 20 | } 21 | 22 | /// Degress to Radians 23 | fn deg2rad(deg: f64) -> f64 { 24 | ((2.0 * PI) / 180.0) * deg 25 | } 26 | 27 | impl fmt::Display for Point { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | write!(f, "({}, {})", self.lat(), self.lon()) 30 | } 31 | } 32 | 33 | impl Point { 34 | /// New integer stored Point from floating point lat/lon coordinates 35 | pub fn new(lat: f64, lon: f64) -> Point { 36 | Point { 37 | lat: (lat * PRECISION).floor() as i64, 38 | lon: (lon * PRECISION).floor() as i64, 39 | } 40 | } 41 | 42 | /// Latitude value 43 | pub fn lat(&self) -> f64 { 44 | (self.lat as f64) / PRECISION 45 | } 46 | 47 | /// Longitude value 48 | pub fn lon(&self) -> f64 { 49 | (self.lon as f64) / PRECISION 50 | } 51 | 52 | /// Spheroid distance calculation given earth coordinates as lat/lon values. 53 | /// Returns the distance in meters. 54 | pub fn geo_distance(&self, p: &Point) -> f64 { 55 | let r = 6371000.0; // metres 56 | let lat1 = p.lat(); 57 | let lon1 = p.lon(); 58 | let lat2 = self.lat(); 59 | let lon2 = self.lon(); 60 | let sig1 = deg2rad(lat1); 61 | let sig2 = deg2rad(lat2); 62 | let deltasig = deg2rad(lat2 - lat1); 63 | let deltalambda = deg2rad(lon2 - lon1); 64 | let a = (deltasig / 2.0).sin() * (deltasig / 2.0).sin() + 65 | sig1.cos() * sig2.cos() * 66 | (deltalambda / 2.0).sin() * (deltalambda / 2.0).sin(); 67 | let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt()); 68 | r * c 69 | } 70 | 71 | } 72 | 73 | /// Path 74 | pub type Path = Vec; 75 | 76 | /// Maintains a sparse routing graph 77 | #[derive(Default)] 78 | pub struct RouteGraph { 79 | vertices: HashSet, 80 | edges: HashMap>, 81 | paths: HashMap<(Point, Point), Path>, 82 | } 83 | 84 | /// Convert the TFL lineStrings attribute to a simple flat vectory of paths. 85 | /// lineStrings in TFL data is a JSON array of string values, containing 86 | /// either an array of points or an array of arrays of points, we handle both. 87 | pub fn linestrings_to_paths(line_strings: &[String]) -> Vec { 88 | let mut paths : Vec = Vec::new(); 89 | for line_string in line_strings { 90 | match serde_json::from_str::>(&line_string) { 91 | Ok(raw_path) => paths.push(raw_path.iter().map(|&(lon, lat)| Point::new(lat, lon)).collect()), 92 | Err(err) => { 93 | match serde_json::from_str::>>(&line_string) { 94 | Ok(raw_paths) => { 95 | for raw_path in raw_paths { 96 | paths.push(raw_path.iter().map(|&(lon, lat)| Point::new(lat, lon)).collect()); 97 | } 98 | }, 99 | Err(err2) => println!("Errors decoding line string, single line {}, multi line {}", err, err2), 100 | } 101 | }, 102 | } 103 | } 104 | paths 105 | } 106 | 107 | impl RouteGraph { 108 | /// New Route Graph 109 | pub fn new() -> RouteGraph { 110 | RouteGraph { 111 | vertices: HashSet::new(), 112 | edges: HashMap::new(), 113 | paths: HashMap::new(), 114 | } 115 | } 116 | 117 | /// Add many paths 118 | pub fn add_paths(&mut self, paths: &[Path]) { 119 | for path in paths { 120 | self.add_path(path); 121 | } 122 | } 123 | 124 | /// Add single path to the graph 125 | pub fn add_path(&mut self, path: &Path) { 126 | // add points 127 | let first = path.first().unwrap(); 128 | let last = path.last().unwrap(); 129 | self.vertices.insert(*first); 130 | self.vertices.insert(*last); 131 | 132 | // add bidirectional edges 133 | self.edges.entry(*first).or_insert_with(|| vec![*last]).push(*last); 134 | self.edges.entry(*last).or_insert_with(|| vec![*first]).push(*first); 135 | 136 | // add paths 137 | self.paths.insert((*first, *last), path.clone()); 138 | self.paths.insert((*last, *first), path.iter().rev().cloned().collect()); 139 | assert!(self.paths.contains_key(&(first.clone(), last.clone()))); 140 | assert!(self.paths.contains_key(&(last.clone(), first.clone()))); 141 | } 142 | 143 | /// Find the closest actual point (vertex) in our graph since they are not 144 | /// going to be exact matches 145 | fn closest_point(&self, pt: &Point) -> (Point, f64) { 146 | let far_pt = Point::new(pt.lat() + 90.0, pt.lon() + 90.0); 147 | let far_dist = far_pt.geo_distance(pt); 148 | let (min_pt, min_dist) = self.edges.keys().fold((&far_pt, far_dist), |(min_pt, min_dist), vert| { 149 | let vert_dist = vert.geo_distance(pt); 150 | if vert_dist < min_dist { 151 | (vert, vert_dist) 152 | } else { 153 | (min_pt, min_dist) 154 | } 155 | }); 156 | 157 | (*min_pt, min_dist) 158 | } 159 | 160 | /// Find a path between two points if one exists 161 | pub fn path(&self, p0: Point, p1: Point) -> Option { 162 | let (start, start_dist) = self.closest_point(&p0); 163 | if start_dist > 2000.0 { 164 | println!("start point {} to closest {} distance is {} > 2000", p0, start, start_dist); 165 | return None; 166 | } 167 | let (end, end_dist) = self.closest_point(&p1); 168 | if end_dist > 2000.0 { 169 | println!("end point {} to closest {} distance is {} > 2000", p1, end, end_dist); 170 | return None; 171 | } 172 | let visited: HashSet = HashSet::new(); 173 | self.find_path(start, end, &visited) 174 | } 175 | 176 | // Recursively search the path graph from p0 to p1, when a path is found 177 | // the callstack will inherently build up a single Path containing all the 178 | // points between the two. 179 | // Visited is a simple HashSet marking points we've already visited to avoid 180 | // infinitely recursing in circles. 181 | fn find_path(&self, p0: Point, p1: Point, visited: &HashSet) -> Option { 182 | match self.edges.get(&p0) { 183 | Some(to_vertices) => { 184 | if visited.contains(&p0) { 185 | None 186 | } else { 187 | for next in to_vertices { 188 | if *next == p1 { 189 | return Some(self.paths.get(&(p0, *next)).unwrap().clone()); 190 | } 191 | 192 | let mut visited0 = visited.clone(); 193 | visited0.insert(*next); 194 | 195 | if let Some(path) = self.find_path(*next, p1, &visited0) { 196 | return Some(vec![&(self.paths.get(&(p0, *next)).unwrap())[..], &path[..]].concat()); 197 | } 198 | } 199 | 200 | None 201 | } 202 | }, 203 | None => None, 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/gtfs.rs: -------------------------------------------------------------------------------- 1 | use csv; 2 | 3 | use crypto::digest::Digest; 4 | use crypto::md5::Md5; 5 | use std::collections::{HashSet, HashMap}; 6 | use std::fs::File; 7 | use std::fs; 8 | use std::path::Path; 9 | 10 | use tfl::line::{Line, TimeTable, RouteSection, Schedule, KnownJourney, StationInterval}; 11 | use geometry::{linestrings_to_paths, RouteGraph, Point}; 12 | 13 | struct Route<'a> { 14 | line: &'a Line, 15 | inbound_graph: RouteGraph, 16 | outbound_graph: RouteGraph, 17 | } 18 | 19 | impl<'a> Route<'a> { 20 | fn new(line: &'a Line) -> Route { 21 | let inbound_paths = match line.inbound_sequence.as_ref() { 22 | Some(ref seq) => linestrings_to_paths(&seq.line_strings), 23 | None => vec![], 24 | }; 25 | let outbound_paths = match line.outbound_sequence.as_ref() { 26 | Some(ref seq) => linestrings_to_paths(&seq.line_strings), 27 | None => vec![], 28 | }; 29 | let mut inbound_graph = RouteGraph::new(); 30 | let mut outbound_graph = RouteGraph::new(); 31 | 32 | inbound_graph.add_paths(&inbound_paths); 33 | outbound_graph.add_paths(&outbound_paths); 34 | 35 | Route { 36 | line: line, 37 | inbound_graph: inbound_graph, 38 | outbound_graph: outbound_graph, 39 | } 40 | } 41 | } 42 | 43 | fn route_type(line: &Line) -> &'static str { 44 | match &line.mode_name[..] { 45 | "dlr" | "tram" => "0", 46 | "tube" | "overground" => "1", 47 | "national-rail" | "tflrail" => "2", 48 | "bus" => "3", 49 | "river-tour" | "river-bus" => "4", 50 | "cable-car" => "5", 51 | _ => { 52 | println!("Missing line mode_name match: {}", line.mode_name); 53 | "" 54 | }, 55 | } 56 | } 57 | 58 | fn write_agency(gtfs_path: &str) { 59 | let fname = format!("{}/{}", gtfs_path, "/agency.txt"); 60 | let fpath = Path::new(&fname); 61 | let mut wtr = csv::Writer::from_file(fpath).unwrap(); 62 | let records = vec![ 63 | ("agency_id","agency_name","agency_url","agency_timezone"), 64 | ("tfl","Transport For London","https://tfl.gov.uk","Europe/London") 65 | ]; 66 | for record in records { 67 | wtr.encode(record).unwrap(); 68 | } 69 | } 70 | 71 | fn write_routes(gtfs_path: &str, routes: &Vec) { 72 | let fname = format!("{}/{}", gtfs_path, "/routes.txt"); 73 | let fpath = Path::new(&fname); 74 | let mut wtr = csv::Writer::from_file(fpath).unwrap(); 75 | wtr.encode(("route_id", "agency_id", "route_color", "route_short_name", "route_long_name", "route_type")).unwrap(); 76 | for route in routes { 77 | let line = &route.line; 78 | let line_color = line.color(); 79 | wtr.encode((&line.id, "tfl", &line_color, &line.name, "", route_type(&line))).unwrap(); 80 | } 81 | } 82 | 83 | fn write_stops(gtfs_path: &str, routes: &[Route]) -> HashMap { 84 | let fname = format!("{}/{}", gtfs_path, "/stops.txt"); 85 | let fpath = Path::new(&fname); 86 | let mut wtr = csv::Writer::from_file(fpath).unwrap(); 87 | let mut written_stops = HashMap::::new(); 88 | wtr.encode(("stop_id", "stop_name", "stop_lat", "stop_lon")).unwrap(); 89 | 90 | for route in routes { 91 | let stops = route.line.stops.as_ref().unwrap(); 92 | 93 | for stop in stops { 94 | if !written_stops.contains_key(&stop.naptan_id) { 95 | wtr.encode((stop.naptan_id.clone(), stop.common_name.clone(), stop.lat, stop.lon)).unwrap(); 96 | written_stops.insert(stop.naptan_id.clone(), (stop.lat, stop.lon)); 97 | 98 | for child in &stop.children { 99 | if !written_stops.contains_key(&child.naptan_id) { 100 | wtr.encode((child.naptan_id.clone(), child.common_name.clone(), stop.lat, stop.lon)).unwrap(); 101 | written_stops.insert(child.naptan_id.clone(), (stop.lat, stop.lon)); 102 | } 103 | } 104 | } 105 | } 106 | 107 | for section in &route.line.route_sections { 108 | if let Some(ref timetable) = section.timetable { 109 | for station in &timetable.stations { 110 | if !written_stops.contains_key(&station.id) { 111 | wtr.encode((station.id.clone(), station.name.clone(), station.lat, station.lon)).unwrap(); 112 | written_stops.insert(station.id.clone(), (station.lat, station.lon)); 113 | } 114 | } 115 | 116 | for stop in &timetable.stops { 117 | if !written_stops.contains_key(&stop.id) { 118 | wtr.encode((stop.id.clone(), stop.name.clone(), stop.lat, stop.lon)).unwrap(); 119 | written_stops.insert(stop.id.clone(), (stop.lat, stop.lon)); 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | written_stops 127 | } 128 | 129 | fn write_calendar(gtfs_path: &str) { 130 | let fname = format!("{}/{}", gtfs_path, "/calendar.txt"); 131 | let fpath = Path::new(&fname); 132 | let mut wtr = csv::Writer::from_file(fpath).unwrap(); 133 | let start_date = "20151031"; 134 | let end_date = "20161031"; 135 | let records = vec![ 136 | ("service_id", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", "start_date", "end_date"), 137 | ("School Monday", "1", "0", "0", "0", "0", "0", "0", &start_date, &end_date), 138 | ("Sunday Night/Monday Morning", "1", "0", "0", "0", "0", "0", "1", &start_date, &end_date), 139 | ("School Monday, Tuesday, Thursday & Friday", "1", "1", "0", "1", "1", "0", "0", &start_date, &end_date), 140 | ("Tuesday", "0", "1", "0", "0", "0", "0", "0", &start_date, &end_date), 141 | ("Monday - Thursday", "1", "1", "1", "1", "0", "0", "0", &start_date, &end_date), 142 | ("Saturday", "0", "0", "0", "0", "0", "1", "0", &start_date, &end_date), 143 | ("Saturday and Sunday", "0", "0", "0", "0", "0", "1","1", &start_date, &end_date), 144 | ("Sunday", "0", "0", "0", "0", "0", "0", "1", &start_date, &end_date), 145 | ("School Tuesday", "0", "1", "0", "0", "0", "0", "0", &start_date, &end_date), 146 | ("Saturday Night/Sunday Morning", "0", "0", "0", "0", "0", "1", "1", &start_date, &end_date), 147 | ("Mo-Fr Night/Tu-Sat Morning", "1", "1", "1", "1","1", "1", "0", &start_date, &end_date), 148 | ("Monday to Thursday", "1", "1", "1", "1", "0", "0", "0", &start_date, &end_date), 149 | ("Mo-Th Nights/Tu-Fr Morning", "1", "1", "1", "1", "1", "0", "0", &start_date, &end_date), 150 | ("Saturday (also Good Friday)", "0", "0", "0", "0", "0", "1", "0", &start_date, &end_date), 151 | ("Mon-Th Schooldays", "1", "1", "1", "1", "0", "0", "0", &start_date, &end_date), 152 | ("Saturdays and Public Holidays", "0", "0", "0", "0", "0", "1", "0", &start_date, &end_date), 153 | ("Friday Night/Saturday Morning", "0", "0", "0", "0", "1", "1", "0", &start_date, &end_date), 154 | ("Friday", "0", "0", "0", "0", "1", "0", "0", &start_date, &end_date), 155 | ("Thursdays", "0", "0", "0", "1", "0", "0", "0", &start_date, &end_date), 156 | ("Sunday night/Monday morning - Thursday night/Friday morning", "1", "1", "1", "1", "1", "0", "1", &start_date, &end_date), 157 | ("School Thursday", "0", "0", "0", "1", "0", "0", "0", &start_date, &end_date), 158 | ("School Friday", "0", "0", "0", "0", "1", "0", "0", &start_date, &end_date), 159 | ("Daily", "1", "1", "1", "1", "1", "1", "1", &start_date, &end_date), 160 | ("Tuesday, Wednesday & Thursday", "0", "1", "1", "1", "0", "0", "0", &start_date, &end_date), 161 | ("Mon-Fri Schooldays", "1", "1", "1", "1", "1", "0", "0", &start_date, &end_date), 162 | ("Wednesday", "0", "0", "1", "0", "0", "0", "0", &start_date, &end_date), 163 | ("Monday, Tuesday and Thursday", "1", "1", "0", "1", "0", "0", "0", &start_date, &end_date), 164 | ("Wednesdays", "0", "0", "1", "0", "0", "0", "0", &start_date, &end_date), 165 | ("Monday to Friday", "1", "1", "1", "1", "1", "0", "0", &start_date, &end_date), 166 | ("Monday", "1", "0", "0", "0", "0", "0", "0", &start_date, &end_date), 167 | ("Sunday and other Public Holidays", "0", "0", "0", "0", "0", "0", "1", &start_date, &end_date), 168 | ("School Wednesday", "0", "0", "1", "0", "0", "0", "0", &start_date, &end_date), 169 | ("Monday - Friday", "1", "1", "1", "1", "1", "0", "0", &start_date, &end_date), 170 | ]; 171 | 172 | for record in records { 173 | wtr.encode(record).unwrap(); 174 | } 175 | } 176 | 177 | fn trip_id(line: &Line, section: &RouteSection, schedule: &Schedule, journey: &KnownJourney) -> String { 178 | let tfmt = time_offset_fmt(journey, 0.0); 179 | let input = line.id.to_string() + §ion.originator + §ion.destination + &schedule.name + &tfmt; 180 | let mut hasher = Md5::new(); 181 | 182 | hasher.input_str(&input); 183 | 184 | hasher.result_str() 185 | } 186 | 187 | fn write_route_section_trips(wtr: &mut csv::Writer, shape_id: &str, line: &Line, section: &RouteSection) { 188 | let mut written_trips : HashSet = HashSet::new(); 189 | let direction = match §ion.direction[..] { 190 | "inbound" => "1".to_owned(), 191 | "outbound" => "0".to_owned(), 192 | _ => "".to_owned(), 193 | }; 194 | 195 | if let Some(timetable) = section.timetable.as_ref() { 196 | let first: Option<&TimeTable> = timetable.first_timetable(); 197 | 198 | if let Some(ref x) = first { 199 | for schedule in &x.schedules { 200 | for journey in &schedule.known_journeys { 201 | let id = trip_id(line, section, schedule, journey); 202 | 203 | if !written_trips.contains(&id) { 204 | written_trips.insert(id.clone()); 205 | wtr.encode((&line.id, &schedule.name, &id, &direction, &shape_id)).unwrap(); 206 | } 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | pub fn route_section_id(line: &Line, section: &RouteSection) -> String { 214 | line.id.clone() + " " + §ion.originator + " to " + §ion.destination 215 | } 216 | 217 | fn write_trips(gtfs_path: &str, routes: &[Route]) { 218 | let fname = format!("{}/{}", gtfs_path, "/trips.txt"); 219 | let fpath = Path::new(&fname); 220 | let mut wtr = csv::Writer::from_file(fpath).unwrap(); 221 | wtr.encode(("route_id", "service_id", "trip_id", "direction", "shape_id")).unwrap(); 222 | for route in routes { 223 | let mut written_route_sections = HashSet::::new(); 224 | let route_sections = &route.line.route_sections; 225 | for route_section in route_sections { 226 | let id = route_section_id(route.line, route_section); 227 | 228 | if !written_route_sections.contains(&id) { 229 | write_route_section_trips(&mut wtr, &id, route.line, route_section); 230 | written_route_sections.insert(id); 231 | } 232 | } 233 | } 234 | } 235 | 236 | fn time_offset_fmt(journey: &KnownJourney, offset: f64) -> String { 237 | let dep_hour : u64 = journey.hour.parse().unwrap(); 238 | let dep_minute : u64 = journey.minute.parse().unwrap(); 239 | let rounded_offset : u64 = offset.floor() as u64; 240 | let minute_offset : u64 = dep_minute + rounded_offset; 241 | let hour : u64 = dep_hour + minute_offset / 60; 242 | let minute : u64 = minute_offset % 60; 243 | format!("{:02}:{:02}:00", hour, minute) 244 | } 245 | 246 | fn write_journey_stop_times(wtr: &mut csv::Writer, line: &Line, section: &RouteSection, schedule: &Schedule, journey: &KnownJourney, interval: &StationInterval) { 247 | let mut stop_seq = 1; 248 | let trip_id = trip_id(line, section, schedule, journey); 249 | let dep_time = time_offset_fmt(journey, 0.0); 250 | wtr.encode((&trip_id, §ion.originator, stop_seq, &dep_time, &dep_time)).unwrap(); 251 | for stop in &interval.intervals { 252 | stop_seq += 1; 253 | let dep_time = time_offset_fmt(journey, stop.time_to_arrival); 254 | wtr.encode((&trip_id, &stop.stop_id, stop_seq, &dep_time, &dep_time)).unwrap(); 255 | } 256 | } 257 | 258 | fn intervals(station_intervals: &[StationInterval]) -> HashMap { 259 | station_intervals.iter().map(|x| (x.id, x)).collect() 260 | } 261 | 262 | fn write_route_section_stop_times(wtr: &mut csv::Writer, line: &Line, section: &RouteSection) { 263 | if let Some(timetable) = section.timetable.as_ref() { 264 | let mut written_trips : HashSet = HashSet::new(); 265 | let record: Option<&TimeTable> = timetable.first_timetable(); 266 | 267 | if let Some(ref datum) = record { 268 | let intervals = intervals(&datum.station_intervals); 269 | 270 | for schedule in &datum.schedules { 271 | for journey in &schedule.known_journeys { 272 | let log_exception = || { println!("Error, Could not find interval for schedule!!!!"); }; 273 | 274 | intervals.get(&journey.interval_id) 275 | .map_or_else(log_exception, |interval| { 276 | let id = trip_id(line, section, schedule, journey); 277 | 278 | if !written_trips.contains(&id) { 279 | written_trips.insert(id.clone()); 280 | write_journey_stop_times(wtr, line, section, schedule, journey, interval); 281 | } 282 | }); 283 | } 284 | } 285 | } 286 | } 287 | } 288 | 289 | fn write_stop_times(gtfs_path: &str, routes: &[Route]) { 290 | let fname = format!("{}/{}", gtfs_path, "/stop_times.txt"); 291 | let fpath = Path::new(&fname); 292 | let mut wtr = csv::Writer::from_file(fpath).unwrap(); 293 | wtr.encode(("trip_id", "stop_id", "stop_sequence", "arrival_time", "departure_time")).unwrap(); 294 | for route in routes { 295 | let mut written_route_sections = HashSet::::new(); 296 | let route_sections = &route.line.route_sections; 297 | for route_section in route_sections { 298 | let id = route_section_id(route.line, route_section); 299 | 300 | if !written_route_sections.contains(&id) { 301 | write_route_section_stop_times(&mut wtr, route.line, route_section); 302 | written_route_sections.insert(id); 303 | } 304 | } 305 | } 306 | } 307 | 308 | fn write_shape_path(wtr: &mut csv::Writer, shape_id: &str, path: &[Point]) { 309 | for (seq, item) in path.iter().enumerate() { 310 | wtr.encode((shape_id, item.lat(), item.lon(), seq)).unwrap(); 311 | } 312 | } 313 | 314 | fn write_shape(wtr: &mut csv::Writer, shape_id: &str, _route: &Route, section: &RouteSection, stops: &HashMap, graph: &RouteGraph) { 315 | if let Some(&(start_lat, start_lon)) = stops.get(§ion.originator) { 316 | let start_pt = Point::new(start_lat, start_lon); 317 | 318 | if let Some(&(end_lat, end_lon)) = stops.get(§ion.destination) { 319 | let end_pt = Point::new(end_lat, end_lon); 320 | match graph.path(start_pt, end_pt) { 321 | Some(path) => write_shape_path(wtr, shape_id, &path), 322 | None => { 323 | println!("could not find shape for {}!!!", shape_id); 324 | }, 325 | } 326 | } 327 | } 328 | } 329 | 330 | fn write_shapes(gtfs_path: &str, routes: &[Route], stops: &HashMap) { 331 | let fname = format!("{}/{}", gtfs_path, "/shapes.txt"); 332 | let fpath = Path::new(&fname); 333 | let mut wtr = csv::Writer::from_file(fpath).unwrap(); 334 | wtr.encode(("shape_id", "shape_pt_lat", "shape_pt_lon", "shape_pt_sequence")).unwrap(); 335 | for route in routes { 336 | let mut written_shapes = HashSet::::new(); 337 | let route_sections = &route.line.route_sections; 338 | for route_section in route_sections { 339 | let shape_id = route_section_id(route.line, route_section); 340 | 341 | if !written_shapes.contains(&shape_id) { 342 | let graph = match &route_section.direction[..] { 343 | "inbound" => Some(&route.inbound_graph), 344 | "outbound" => Some(&route.outbound_graph), 345 | _ => None, 346 | }; 347 | 348 | if let Some(graph) = graph { 349 | write_shape(&mut wtr, &shape_id, route, route_section, stops, graph); 350 | written_shapes.insert(shape_id); 351 | } 352 | } 353 | } 354 | } 355 | } 356 | 357 | pub fn write_gtfs(lines: &[Line]) { 358 | let routes = lines.iter().map(|line| Route::new(line)).collect(); 359 | let gtfs_path : &Path = Path::new("./gtfs"); 360 | let gtfs_path_str = gtfs_path.to_str().unwrap(); 361 | let _ = fs::create_dir(gtfs_path_str); 362 | write_agency(gtfs_path_str); 363 | write_routes(gtfs_path_str, &routes); 364 | let all_stops = write_stops(gtfs_path_str, &routes); 365 | write_calendar(gtfs_path_str); 366 | write_trips(gtfs_path_str, &routes); 367 | write_stop_times(gtfs_path_str, &routes); 368 | write_shapes(gtfs_path_str, &routes, &all_stops); 369 | } 370 | 371 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "serde_macros", feature(custom_derive, plugin))] 2 | #![cfg_attr(feature = "serde_macros", plugin(serde_macros))] 3 | 4 | #![cfg_attr(feature="clippy", feature(plugin))] 5 | #![cfg_attr(feature="clippy", plugin(clippy))] 6 | 7 | #[macro_use] extern crate clap; 8 | extern crate ansi_term; 9 | extern crate crypto; 10 | extern crate csv; 11 | extern crate env_logger; 12 | extern crate hyper; 13 | extern crate rand; 14 | extern crate scoped_threadpool; 15 | extern crate serde; 16 | extern crate serde_json; 17 | 18 | #[cfg(feature = "serde_macros")] 19 | include!("main.rs.in"); 20 | 21 | #[cfg(not(feature = "serde_macros"))] 22 | include!(concat!(env!("OUT_DIR"), "/main.rs")); 23 | -------------------------------------------------------------------------------- /src/main.rs.in: -------------------------------------------------------------------------------- 1 | mod cmd; 2 | mod format; 3 | mod geometry; 4 | mod gtfs; 5 | mod tfl; 6 | 7 | use clap::{Arg, App, SubCommand}; 8 | use format::{OutputFormat}; 9 | 10 | fn arg_format<'a, 'b>() -> Arg<'a, 'b> { 11 | Arg::with_name("format") 12 | .help("Output format") 13 | .possible_values(&["gtfs"]) 14 | .long("format") 15 | .value_name("format") 16 | } 17 | 18 | fn main() { 19 | env_logger::init().unwrap(); 20 | 21 | let matches = App::new("tfl") 22 | .version(env!("CARGO_PKG_VERSION")) 23 | .about("Tfl consumer") 24 | .subcommand(SubCommand::with_name("fetch-lines") 25 | .about("Fetch lines from Tfl") 26 | .arg(arg_format()) 27 | .arg(Arg::with_name("threads") 28 | .help("Number of threads. Defaults to 5") 29 | .long("threads") 30 | .value_name("number")) 31 | .arg(Arg::with_name("sample") 32 | .help("Take a sample of the given size") 33 | .long("sample") 34 | .value_name("size"))) 35 | .subcommand(SubCommand::with_name("transform") 36 | .about("Transform cached data to the given format") 37 | .arg(arg_format() 38 | .index(1) 39 | .required(true)) 40 | .arg(Arg::with_name("threads") 41 | .help("Number of threads. Defaults to 5") 42 | .long("threads") 43 | .value_name("number")) 44 | .arg(Arg::with_name("sample") 45 | .help("Take a sample of the given size") 46 | .long("sample") 47 | .value_name("size"))) 48 | .get_matches(); 49 | 50 | if let Some(ref matches) = matches.subcommand_matches("fetch-lines") { 51 | let format = value_t!(matches, "format", OutputFormat).unwrap_or(OutputFormat::None); 52 | let thread_number = value_t!(matches, "threads", u32).unwrap_or(5); 53 | let sample_size = value_t!(matches, "sample", usize).ok(); 54 | cmd::fetch_lines(format, thread_number, sample_size); 55 | } 56 | 57 | if let Some(ref matches) = matches.subcommand_matches("transform") { 58 | let format = value_t!(matches, "format", OutputFormat).unwrap_or_else(|e| e.exit()); 59 | let thread_number = value_t!(matches, "threads", u32).unwrap_or(5); 60 | let sample_size = value_t!(matches, "sample", usize).ok(); 61 | cmd::transform(format, thread_number, sample_size); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/tfl/client.rs: -------------------------------------------------------------------------------- 1 | use ansi_term::Colour::Red; 2 | use hyper::header::{Accept, qitem}; 3 | use hyper::mime::{Mime, TopLevel, SubLevel}; 4 | use hyper; 5 | use serde_json; 6 | use std::fs; 7 | use std::io::{Read, Write}; 8 | use std::path::Path; 9 | use std::sync::Arc; 10 | 11 | use tfl::line::{Line, TimeTableResponse, Sequence, Stop}; 12 | 13 | pub enum DataSource { 14 | API, 15 | Cache 16 | } 17 | 18 | #[derive(Clone, Default)] 19 | pub struct Client { 20 | client: Arc, 21 | app_id: String, 22 | app_key: String, 23 | cache_dir: String, 24 | } 25 | 26 | impl Client { 27 | pub fn new() -> Client { 28 | let cache_path: &Path = Path::new("./cache"); 29 | let _ = fs::create_dir(cache_path); 30 | 31 | Client { 32 | client : Arc::new(hyper::Client::new()), 33 | app_id : String::new(), 34 | app_key : String::new(), 35 | cache_dir : String::from("./cache"), 36 | } 37 | } 38 | 39 | fn get(&self, endpoint : &str) -> String { 40 | match self.cache_get(endpoint) { 41 | Some(body) => body, 42 | None => self.remote_get(endpoint) 43 | } 44 | } 45 | 46 | fn remote_get(&self, endpoint : &str) -> String { 47 | let req_uri = format!("https://api.tfl.gov.uk{}?app_id={}&app_key={}", endpoint, self.app_id, self.app_key); 48 | let mut body = String::new(); 49 | let mut resp = self.client.get(&req_uri) 50 | .header(Accept(vec![ 51 | qitem(Mime(TopLevel::Application, 52 | SubLevel::Ext("json".to_owned()), vec![])), 53 | ])) 54 | .send().unwrap(); 55 | resp.read_to_string(&mut body).unwrap(); 56 | self.cache_put(endpoint, body) 57 | } 58 | 59 | fn cache_fname(&self, endpoint : &str) -> String { 60 | let fname = String::from(endpoint); 61 | let fname0 = fname.replace("/", "_"); 62 | self.cache_dir.clone() + "/" + &fname0 63 | } 64 | 65 | fn cache_put(&self, endpoint : &str, body : String) -> String { 66 | let mut f = fs::File::create(self.cache_fname(endpoint)).unwrap(); 67 | f.write_all(body.as_bytes()).unwrap(); 68 | body 69 | } 70 | 71 | fn cache_get(&self, endpoint : &str) -> Option { 72 | let mut body = String::new(); 73 | match fs::File::open(self.cache_fname(endpoint)) { 74 | Ok(ref mut f) => { 75 | f.read_to_string(&mut body).unwrap(); 76 | Some(body) 77 | }, 78 | Err(_) => None, 79 | } 80 | } 81 | 82 | pub fn get_cached_lines(&self) -> Vec { 83 | let body = self.cache_get("/line/route"); 84 | match body { 85 | Some(x) => serde_json::from_str(&x).unwrap(), 86 | None => vec![] 87 | } 88 | } 89 | 90 | pub fn get_lines(&self) -> Vec { 91 | let body = self.get("/line/route"); 92 | serde_json::from_str(&body).unwrap() 93 | } 94 | 95 | pub fn get_timetable(&self, line_id : &str, originator: &str, destination : &str) -> Option { 96 | let req_uri = format!("/line/{}/timetable/{}/to/{}", line_id, originator, destination); 97 | let body = self.get(&req_uri); 98 | match serde_json::from_str::(&body) { 99 | Ok(ttresp) => Some(ttresp.clone()), 100 | Err(err) => { 101 | println!("{}: {}", Red.bold().paint("Error decoding timetable"), err); 102 | None 103 | }, 104 | } 105 | } 106 | 107 | pub fn get_stops(&self, line_id : &str) -> Vec { 108 | let req_uri = format!("/line/{}/stoppoints", line_id); 109 | let body = self.get(&req_uri); 110 | match serde_json::from_str::>(&body) { 111 | Ok(stops) => stops, 112 | Err(err) => { 113 | println!("{}: {}", Red.bold().paint("Error decoding stops"), err); 114 | Vec::::new() 115 | } 116 | } 117 | } 118 | 119 | pub fn get_sequence(&self, line_id : &str, direction : &str) -> Option { 120 | let req_uri = format!("/line/{}/route/sequence/{}", line_id, direction); 121 | let body = self.get(&req_uri); 122 | match serde_json::from_str::(&body) { 123 | Ok(seq) => Some(seq), 124 | Err(err) => { 125 | println!("{}: {}", Red.bold().paint("Error decoding sequence"), err); 126 | None 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/tfl/line.rs: -------------------------------------------------------------------------------- 1 | use ansi_term::Colour::{Green, Blue, Red}; 2 | use std::fmt; 3 | use std::collections::HashSet; 4 | 5 | #[derive(Clone, Debug, Deserialize)] 6 | pub struct Line { 7 | pub id: String, 8 | pub name: String, 9 | #[serde(rename="modeName")] 10 | pub mode_name: String, 11 | #[serde(rename="routeSections")] 12 | pub route_sections: Vec, 13 | pub stops: Option>, 14 | pub inbound_sequence: Option, 15 | pub outbound_sequence: Option, 16 | } 17 | 18 | /// Default color string, use null so the importer can choose 19 | const DEFAULT_COLOR : &'static str = ""; 20 | 21 | impl Line { 22 | /// Tube Color 23 | fn tube_color(&self) -> &str { 24 | match &self.name[..] { 25 | "Bakerloo" => "894E24", 26 | "Central" => "DC241F", 27 | "Circle" => "FFCE00", 28 | "District" => "007229", 29 | "Hammersmith & City" => "D799AF", 30 | "Jubilee" => "6A7278", 31 | "Metropolitan" => "751056", 32 | "Northern" => "000", 33 | "Piccadilly" => "0019A8", 34 | "Victoria" => "00A0E2", 35 | "Waterloo & City" => "76D0BD", 36 | _ => { 37 | println!("Missing tube color for {}", self.name); 38 | DEFAULT_COLOR 39 | }, 40 | } 41 | } 42 | 43 | /// Tram Color 44 | fn tram_color(&self) -> &str { 45 | match &self.name[..] { 46 | "Tram 1" | "Tram 2" => "C6D834", 47 | "Tram 3" => "79C23F", 48 | "Tram 4" => "336B14", 49 | _ => { 50 | println!("Missing tram color for {}", self.name); 51 | DEFAULT_COLOR 52 | }, 53 | } 54 | } 55 | 56 | /// National Rail Color 57 | fn national_rail_color(&self) -> &str { 58 | match &self.name[..] { 59 | "South West Trains" => "F11815", 60 | "Southeastern" => "0071BF", 61 | "Southern" => "00A74B", 62 | "Great Northern" => "00A6E2", 63 | "Arriva Trains Wales" => "00B9B4", 64 | "c2c" => "F0188C", 65 | "Chiltern Railways" => "B389C1", 66 | "Cross Country" => "A03467", 67 | "East Midlands Trains" => "E16C16", 68 | "First Great Western" => "2D2B94", 69 | "First Hull Trains" => "1B903F", 70 | "First TransPennine Express" => "F265A0", 71 | "Gatwick Express" => "231F20", 72 | "Grand Central" => "3F3F40", 73 | "Greater Anglia" => "8B8FA5", 74 | "Heathrow Connect" => "F6858D", 75 | "Heathrow Express" => "55C4BF", 76 | "Island Line" => "F8B174", 77 | "London Midland" => "8BC831", 78 | "Merseyrail" => "FEC95F", 79 | "Northern Rail" => "0569A8", 80 | "ScotRail" => "96A3A9", 81 | "Thameslink" => "DA4290", 82 | "Virgin Trains" => "A8652C", 83 | "Virgin Trains East Coast" => "9C0101", 84 | _ => { 85 | println!("Missing national rail color for {}", self.name); 86 | DEFAULT_COLOR 87 | }, 88 | } 89 | } 90 | 91 | /// River Bus Color 92 | fn river_bus_color(&self) -> &str { 93 | match &self.name[..] { 94 | "RB1" => "2D3039", 95 | "RB2" => "0072BC", 96 | "RB4" => "61C29D", 97 | "RB5" => "BA6830", 98 | "RB6" => "DF64B0", 99 | "Woolwich Ferry" => "F7931D", 100 | _ => { 101 | println!("Missing rail color for {}", self.name); 102 | DEFAULT_COLOR 103 | }, 104 | } 105 | } 106 | 107 | fn cable_car_color(&self) -> &str { 108 | match &self.name[..] { 109 | "Emirates Air Line" => "E51937", 110 | _ => { 111 | println!("Missing rail color for {}", self.name); 112 | DEFAULT_COLOR 113 | }, 114 | } 115 | } 116 | 117 | /// The Line's Color based on the TFL colors on tfl.gov.uk 118 | pub fn color(&self) -> &str { 119 | match &self.mode_name[..] { 120 | "dlr" => "00AFAD", 121 | "overground" => "E86A10", 122 | "tflrail" => "0019A8", 123 | "tube" => self.tube_color(), 124 | "tram" => self.tram_color(), 125 | "national-rail" => self.national_rail_color(), 126 | "river-bus" | "river-ferry" => self.river_bus_color(), 127 | "cable-car" => self.cable_car_color(), 128 | _ => DEFAULT_COLOR, 129 | } 130 | } 131 | } 132 | 133 | impl fmt::Display for Line { 134 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 135 | let id: &str = &self.id; 136 | write!(f, "{} {}", Green.bold().paint("Line"), Blue.bold().paint(id)) 137 | } 138 | } 139 | 140 | #[derive(Clone, Debug, Deserialize)] 141 | pub struct Stop { 142 | #[serde(rename="naptanId")] 143 | pub naptan_id: String, 144 | #[serde(rename="commonName")] 145 | pub common_name: String, 146 | pub lat: f64, 147 | pub lon: f64, 148 | pub children: Vec, 149 | } 150 | 151 | #[derive(Clone, Debug, Deserialize)] 152 | pub struct RouteSection { 153 | pub name: String, 154 | pub direction: String, 155 | pub originator: String, 156 | pub destination: String, 157 | pub timetable: Option, 158 | } 159 | 160 | #[derive(Clone, Debug, Deserialize)] 161 | pub struct Interval { 162 | #[serde(rename="stopId")] 163 | pub stop_id: String, 164 | #[serde(rename="timeToArrival")] 165 | pub time_to_arrival: f64, 166 | } 167 | 168 | #[derive(Clone, Debug, Deserialize)] 169 | pub struct StationInterval { 170 | pub id: i64, 171 | pub intervals: Vec 172 | } 173 | 174 | #[derive(Clone, Debug, Deserialize)] 175 | pub struct KnownJourney { 176 | #[serde(rename="intervalId")] 177 | pub interval_id: i64, 178 | pub hour: String, 179 | pub minute: String, 180 | } 181 | 182 | #[derive(Clone, Debug, Deserialize)] 183 | pub struct Schedule { 184 | pub name: String, 185 | #[serde(rename="knownJourneys")] 186 | pub known_journeys: Vec, 187 | } 188 | 189 | #[derive(Clone, Debug, Deserialize)] 190 | pub struct TimeTable { 191 | #[serde(rename="stationIntervals")] 192 | pub station_intervals: Vec, 193 | pub schedules: Vec, 194 | } 195 | 196 | #[derive(Clone, Debug, Deserialize)] 197 | pub struct RoutesTimeTables { 198 | pub routes: Vec, 199 | } 200 | 201 | #[derive(Clone, Debug, Deserialize)] 202 | pub struct Station { 203 | pub id: String, 204 | pub name: String, 205 | pub lat: f64, 206 | pub lon: f64, 207 | } 208 | 209 | #[derive(Clone, Debug, Deserialize)] 210 | pub struct TimeTableResponse { 211 | pub stations: Vec, 212 | pub stops: Vec, 213 | pub timetable: RoutesTimeTables, 214 | #[serde(rename="statusErrorMessage")] 215 | pub status_error_message: Option, 216 | #[serde(rename="lineId")] 217 | line_id: String, 218 | } 219 | 220 | #[derive(Clone, Debug, Deserialize)] 221 | pub struct Sequence { 222 | #[serde(rename="lineStrings")] 223 | pub line_strings: Vec, 224 | } 225 | 226 | impl TimeTableResponse { 227 | pub fn first_timetable(&self) -> Option<&TimeTable> { 228 | if let Some(ref message) = self.status_error_message { 229 | println!("{} (line {}): {}", Red.bold().paint("Error"), Blue.bold().paint(self.line_id.clone()), message); 230 | None 231 | } else { 232 | Some(&self.timetable.routes[0]) 233 | } 234 | } 235 | 236 | pub fn schedule_names(&self) -> HashSet { 237 | if let Some(record) = *&self.first_timetable() { 238 | return record.schedules.iter() 239 | .map(|x| x.name.clone()) 240 | .collect(); 241 | } 242 | 243 | HashSet::new() 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/tfl/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod line; 3 | --------------------------------------------------------------------------------