├── .gitignore ├── .travis.yml ├── ChangeLog ├── LICENSE ├── MANIFEST.in ├── README.md ├── pyhipku ├── __init__.py ├── decode.py ├── dictionary.py ├── encode.py └── test │ ├── test_current_version.py │ └── test_hipku.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # User specified 2 | #################### 3 | 4 | # virtualenv folder 5 | venv/ 6 | 7 | # pycharm 8 | .idea/ 9 | 10 | # Belows are from https://github.com/github/gitignore/blob/master/Python.gitignore 11 | #################### 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | env/ 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .coverage 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | 61 | # Sphinx documentation 62 | docs/_build/ 63 | 64 | # PyBuilder 65 | target/ 66 | 67 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.6" 5 | - "2.7" 6 | - "3.3" 7 | - "3.4" 8 | 9 | before_install: 10 | - pip install coveralls 11 | 12 | script: 13 | - nosetests -v --with-coverage --cover-package=pyhipku 14 | 15 | after_success: 16 | - coveralls 17 | 18 | notifications: 19 | email: false 20 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2015.07.23 v0.2.2 2 | - no more wild import. 3 | - fix the import in encode.py and decode.py. 4 | - fix typo when raise an error for illegal haiku. 5 | 6 | 2015.03.13 v0.2.1 7 | - Better exception handle. 8 | 9 | 2015.03.04 v0.2.0 10 | - Add python 3.x support, #3 11 | 12 | 2015.02.25 v0.1.0 13 | - Initial release, you can encode and decode your IP as haiku now. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 lord63 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include pyhipku/test/test_hipku.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyHipku 2 | 3 | [![Latest Version][1]][2] 4 | [![Build Status][3]][4] 5 | [![Coverage Status][5]][6] 6 | 7 | A tiny python library to encode IPv6 and IPv4 addressed as haiku. 8 | This a python port of [hipku][7](javascript). 9 | 10 | ## Install 11 | 12 | $ pip install pyhipku 13 | 14 | ## Usage 15 | 16 | Encode the IP address to haiku 17 | 18 | >>> from pyhipku import encode 19 | >>> print(encode('127.0.0.1')) 20 | The hungry white ape 21 | aches in the ancient canyon. 22 | Autumn colors crunch. 23 | 24 | Decode haiku to IP address 25 | 26 | >>> from pyhipku import decode 27 | >>> decode('The hungry white ape\naches in the ancient canyon.\nAutumn colors crunch.\n') 28 | '127.0.0.1' 29 | 30 | ## Run the tests 31 | 32 | $ pip install nose coveralls 33 | $ nosetests -v --with-coverage --cover-package=pyhipku 34 | 35 | ## About the website 36 | 37 | The source code for the demo is [here][]. 38 | 39 | ## License 40 | 41 | MIT 42 | 43 | 44 | [1]: http://img.shields.io/pypi/v/pyhipku.svg 45 | [2]: https://pypi.python.org/pypi/pyhipku 46 | [3]: https://travis-ci.org/lord63/pyhipku.svg 47 | [4]: https://travis-ci.org/lord63/pyhipku 48 | [5]: https://coveralls.io/repos/lord63/pyhipku/badge.svg 49 | [6]: https://coveralls.io/r/lord63/pyhipku 50 | [7]: https://github.com/gabemart/hipku 51 | [here]: https://github.com/lord63/pyhipku_web 52 | -------------------------------------------------------------------------------- /pyhipku/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | hipku 6 | ~~~~~ 7 | 8 | Encode any IP address as a haiku. 9 | 10 | :copyright: (c) 2015 by lord63. 11 | :license: MIT, see LICENSE for more details. 12 | """ 13 | 14 | from __future__ import absolute_import 15 | 16 | from .encode import encode 17 | from .decode import decode 18 | 19 | 20 | __all__ = ["encode", "decode"] 21 | __title__ = "hipku" 22 | __version__ = "0.2.2" 23 | __author__ = "lord63" 24 | __license__ = "MIT" 25 | __copyright__ = "Copyright 2015 lord63" 26 | -------------------------------------------------------------------------------- /pyhipku/decode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import absolute_import, division 5 | 6 | from .dictionary import (adjectives, nouns, verbs, animal_adjectives, 7 | animal_colors, animal_nouns, animal_verbs, 8 | nature_adjectives, nature_nouns, plant_nouns, 9 | plant_verbs) 10 | 11 | 12 | def decode(haiku): 13 | """Decode haiku as IP address""" 14 | word_array = split_haiku(haiku) 15 | is_ipv6 = haiku_is_ipv6(word_array) 16 | factor_array = get_factors(word_array, is_ipv6) 17 | octet_array = get_octets(factor_array, is_ipv6) 18 | ip_string = get_ip_string(octet_array, is_ipv6) 19 | return ip_string 20 | 21 | 22 | def split_haiku(haiku): 23 | """Split haiku, remove the period and new_line""" 24 | word_array = haiku.lower().replace('.', '').split() 25 | return word_array 26 | 27 | 28 | def haiku_is_ipv6(word_array): 29 | """Weather haiku is converted from IPv6 or not, return True if it is""" 30 | if word_array[0] == 'the' and len(word_array) in [12, 13, 14]: 31 | is_ipv6 = False 32 | elif len(word_array) == 17: 33 | is_ipv6 = True 34 | else: 35 | raise ValueError("Illegal haiku") 36 | return is_ipv6 37 | 38 | 39 | def get_key(is_ipv6): 40 | """Return an array of dictionaries representing the correct word 41 | order for the haiku""" 42 | if is_ipv6: 43 | key = [adjectives, nouns, adjectives, nouns, verbs, adjectives, 44 | adjectives, adjectives, adjectives, adjectives, nouns, 45 | adjectives, nouns, verbs, adjectives, nouns] 46 | else: 47 | key = [animal_adjectives, animal_colors, animal_nouns, animal_verbs, 48 | nature_adjectives, nature_nouns, plant_nouns, plant_verbs] 49 | return key 50 | 51 | 52 | def get_factors(word_array, is_ipv6): 53 | """Return an array of factors and remainders for each encoded word""" 54 | key = get_key(is_ipv6) 55 | # Remove the useless words, make sure there is a one-to-one match 56 | # between word_array and key. 57 | if is_ipv6: 58 | word_array.remove('and') 59 | else: 60 | word_array.remove('the') 61 | word_array.remove('in') 62 | word_array.remove('the') 63 | # Plant_nouns may have 1~3 words, join them into one. 64 | word_array.insert(6, ' '.join(word_array[6:-1])) 65 | del word_array[7:-1] 66 | factor_array = [] 67 | for i in range(len(key)): 68 | try: 69 | factor_array.append(key[i].index(word_array[i])) 70 | except ValueError as e: 71 | raise ValueError("Check the invalid word: {0}".format(e.args[0])) 72 | return factor_array 73 | 74 | 75 | def get_octets(factor_array, is_ipv6): 76 | """Return an array of octets for each pair of factor and remainder""" 77 | if is_ipv6: 78 | multiplier = 256 79 | else: 80 | multiplier = 16 81 | octet_array = [] 82 | for i in range(0, len(factor_array), 2): 83 | result = factor_array[i]*multiplier + factor_array[i+1] 84 | if is_ipv6: 85 | origin = hex(result)[2:] 86 | else: 87 | origin = str(result) 88 | octet_array.append(origin) 89 | return octet_array 90 | 91 | 92 | def get_ip_string(octct_array, is_ipv6): 93 | """Get the ip string, yeah""" 94 | if is_ipv6: 95 | separator = ':' 96 | else: 97 | separator = '.' 98 | ip_string = separator.join(octct_array) 99 | return ip_string 100 | -------------------------------------------------------------------------------- /pyhipku/dictionary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # IPv4 dictionaries. 5 | 6 | animal_adjectives = [ 7 | 'agile', 8 | 'bashful', 9 | 'clever', 10 | 'clumsy', 11 | 'drowsy', 12 | 'fearful', 13 | 'graceful', 14 | 'hungry', 15 | 'lonely', 16 | 'morose', 17 | 'placid', 18 | 'ruthless', 19 | 'silent', 20 | 'thoughtful', 21 | 'vapid', 22 | 'weary'] 23 | 24 | animal_colors = [ 25 | 'beige', 26 | 'black', 27 | 'blue', 28 | 'bright', 29 | 'bronze', 30 | 'brown', 31 | 'dark', 32 | 'drab', 33 | 'green', 34 | 'gold', 35 | 'grey', 36 | 'jade', 37 | 'pale', 38 | 'pink', 39 | 'red', 40 | 'white'] 41 | 42 | animal_nouns = [ 43 | 'ape', 44 | 'bear', 45 | 'crow', 46 | 'dove', 47 | 'frog', 48 | 'goat', 49 | 'hawk', 50 | 'lamb', 51 | 'mouse', 52 | 'newt', 53 | 'owl', 54 | 'pig', 55 | 'rat', 56 | 'snake', 57 | 'toad', 58 | 'wolf'] 59 | 60 | animal_verbs = [ 61 | 'aches', 62 | 'basks', 63 | 'cries', 64 | 'dives', 65 | 'eats', 66 | 'fights', 67 | 'groans', 68 | 'hunts', 69 | 'jumps', 70 | 'lies', 71 | 'prowls', 72 | 'runs', 73 | 'sleeps', 74 | 'thrives', 75 | 'wakes', 76 | 'yawns'] 77 | 78 | nature_adjectives = [ 79 | 'ancient', 80 | 'barren', 81 | 'blazing', 82 | 'crowded', 83 | 'distant', 84 | 'empty', 85 | 'foggy', 86 | 'fragrant', 87 | 'frozen', 88 | 'moonlit', 89 | 'peaceful', 90 | 'quiet', 91 | 'rugged', 92 | 'serene', 93 | 'sunlit', 94 | 'wind-swept'] 95 | 96 | nature_nouns = [ 97 | 'canyon', 98 | 'clearing', 99 | 'desert', 100 | 'foothills', 101 | 'forest', 102 | 'grasslands', 103 | 'jungle', 104 | 'meadow', 105 | 'mountains', 106 | 'prairie', 107 | 'river', 108 | 'rockpool', 109 | 'sand-dune', 110 | 'tundra', 111 | 'valley', 112 | 'wetlands'] 113 | 114 | plant_nouns = [ 115 | 'autumn colors', 116 | 'cherry blossoms', 117 | 'chrysanthemums', 118 | 'crabapple blooms', 119 | 'dry palm fronds', 120 | 'fat horse chestnuts', 121 | 'forget-me-nots', 122 | 'jasmine petals', 123 | 'lotus flowers', 124 | 'ripe blackberries', 125 | 'the maple seeds', 126 | 'the pine needles', 127 | 'tiger lillies', 128 | 'water lillies', 129 | 'willow branches', 130 | 'yellowwood leaves'] 131 | 132 | plant_verbs = [ 133 | 'blow', 134 | 'crunch', 135 | 'dance', 136 | 'drift', 137 | 'drop', 138 | 'fall', 139 | 'grow', 140 | 'pile', 141 | 'rest', 142 | 'roll', 143 | 'show', 144 | 'spin', 145 | 'stir', 146 | 'sway', 147 | 'turn', 148 | 'twist'] 149 | 150 | # IPv6 dictionaries. 151 | 152 | adjectives = [ 153 | 'ace', 154 | 'apt', 155 | 'arched', 156 | 'ash', 157 | 'bad', 158 | 'bare', 159 | 'beige', 160 | 'big', 161 | 'black', 162 | 'bland', 163 | 'bleak', 164 | 'blond', 165 | 'blue', 166 | 'blunt', 167 | 'blush', 168 | 'bold', 169 | 'bone', 170 | 'both', 171 | 'bound', 172 | 'brash', 173 | 'brass', 174 | 'brave', 175 | 'brief', 176 | 'brisk', 177 | 'broad', 178 | 'bronze', 179 | 'brushed', 180 | 'burned', 181 | 'calm', 182 | 'ceil', 183 | 'chaste', 184 | 'cheap', 185 | 'chilled', 186 | 'clean', 187 | 'coarse', 188 | 'cold', 189 | 'cool', 190 | 'corn', 191 | 'crass', 192 | 'crazed', 193 | 'cream', 194 | 'crisp', 195 | 'crude', 196 | 'cruel', 197 | 'cursed', 198 | 'cute', 199 | 'daft', 200 | 'damp', 201 | 'dark', 202 | 'dead', 203 | 'deaf', 204 | 'dear', 205 | 'deep', 206 | 'dense', 207 | 'dim', 208 | 'drab', 209 | 'dry', 210 | 'dull', 211 | 'faint', 212 | 'fair', 213 | 'fake', 214 | 'false', 215 | 'famed', 216 | 'far', 217 | 'fast', 218 | 'fat', 219 | 'fierce', 220 | 'fine', 221 | 'firm', 222 | 'flat', 223 | 'flawed', 224 | 'fond', 225 | 'foul', 226 | 'frail', 227 | 'free', 228 | 'fresh', 229 | 'full', 230 | 'fun', 231 | 'glum', 232 | 'good', 233 | 'grave', 234 | 'gray', 235 | 'great', 236 | 'green', 237 | 'grey', 238 | 'grim', 239 | 'gruff', 240 | 'hard', 241 | 'harsh', 242 | 'high', 243 | 'hoarse', 244 | 'hot', 245 | 'huge', 246 | 'hurt', 247 | 'ill', 248 | 'jade', 249 | 'jet', 250 | 'jinxed', 251 | 'keen', 252 | 'kind', 253 | 'lame', 254 | 'lank', 255 | 'large', 256 | 'last', 257 | 'late', 258 | 'lean', 259 | 'lewd', 260 | 'light', 261 | 'limp', 262 | 'live', 263 | 'loath', 264 | 'lone', 265 | 'long', 266 | 'loose', 267 | 'lost', 268 | 'louche', 269 | 'loud', 270 | 'low', 271 | 'lush', 272 | 'mad', 273 | 'male', 274 | 'masked', 275 | 'mean', 276 | 'meek', 277 | 'mild', 278 | 'mint', 279 | 'moist', 280 | 'mute', 281 | 'near', 282 | 'neat', 283 | 'new', 284 | 'nice', 285 | 'nude', 286 | 'numb', 287 | 'odd', 288 | 'old', 289 | 'pained', 290 | 'pale', 291 | 'peach', 292 | 'pear', 293 | 'peeved', 294 | 'pink', 295 | 'piqued', 296 | 'plain', 297 | 'plum', 298 | 'plump', 299 | 'plush', 300 | 'poor', 301 | 'posed', 302 | 'posh', 303 | 'prim', 304 | 'prime', 305 | 'prompt', 306 | 'prone', 307 | 'proud', 308 | 'prune', 309 | 'puce', 310 | 'pure', 311 | 'quaint', 312 | 'quartz', 313 | 'quick', 314 | 'rare', 315 | 'raw', 316 | 'real', 317 | 'red', 318 | 'rich', 319 | 'ripe', 320 | 'rough', 321 | 'rude', 322 | 'rushed', 323 | 'rust', 324 | 'sad', 325 | 'safe', 326 | 'sage', 327 | 'sane', 328 | 'scortched', 329 | 'shaped', 330 | 'sharp', 331 | 'sheared', 332 | 'short', 333 | 'shrewd', 334 | 'shrill', 335 | 'shrunk', 336 | 'shy', 337 | 'sick', 338 | 'skilled', 339 | 'slain', 340 | 'slick', 341 | 'slight', 342 | 'slim', 343 | 'slow', 344 | 'small', 345 | 'smart', 346 | 'smooth', 347 | 'smug', 348 | 'snide', 349 | 'snug', 350 | 'soft', 351 | 'sore', 352 | 'sought', 353 | 'sour', 354 | 'spare', 355 | 'sparse', 356 | 'spent', 357 | 'spoilt', 358 | 'spry', 359 | 'squat', 360 | 'staid', 361 | 'stale', 362 | 'stary', 363 | 'staunch', 364 | 'steep', 365 | 'stiff', 366 | 'strange', 367 | 'straw', 368 | 'stretched', 369 | 'strict', 370 | 'striped', 371 | 'strong', 372 | 'suave', 373 | 'sure', 374 | 'svelte', 375 | 'swank', 376 | 'sweet', 377 | 'swift', 378 | 'tall', 379 | 'tame', 380 | 'tan', 381 | 'tart', 382 | 'taut', 383 | 'teal', 384 | 'terse', 385 | 'thick', 386 | 'thin', 387 | 'tight', 388 | 'tiny', 389 | 'tired', 390 | 'toothed', 391 | 'torn', 392 | 'tough', 393 | 'trim', 394 | 'trussed', 395 | 'twin', 396 | 'used', 397 | 'vague', 398 | 'vain', 399 | 'vast', 400 | 'veiled', 401 | 'vexed', 402 | 'vile', 403 | 'warm', 404 | 'weak', 405 | 'webbed', 406 | 'wrong', 407 | 'wry', 408 | 'young'] 409 | 410 | nouns = [ 411 | 'ants', 412 | 'apes', 413 | 'asps', 414 | 'balls', 415 | 'barb', 416 | 'barbs', 417 | 'bass', 418 | 'bats', 419 | 'beads', 420 | 'beaks', 421 | 'bears', 422 | 'bees', 423 | 'bells', 424 | 'belts', 425 | 'birds', 426 | 'blades', 427 | 'blobs', 428 | 'blooms', 429 | 'boars', 430 | 'boats', 431 | 'bolts', 432 | 'books', 433 | 'bowls', 434 | 'boys', 435 | 'bream', 436 | 'brides', 437 | 'broods', 438 | 'brooms', 439 | 'brutes', 440 | 'bucks', 441 | 'bulbs', 442 | 'bulls', 443 | 'busks', 444 | 'cakes', 445 | 'calfs', 446 | 'calves', 447 | 'cats', 448 | 'char', 449 | 'chests', 450 | 'choirs', 451 | 'clams', 452 | 'clans', 453 | 'clouds', 454 | 'clowns', 455 | 'cod', 456 | 'coins', 457 | 'colts', 458 | 'cones', 459 | 'cords', 460 | 'cows', 461 | 'crabs', 462 | 'cranes', 463 | 'crows', 464 | 'cults', 465 | 'czars', 466 | 'darts', 467 | 'dates', 468 | 'deer', 469 | 'dholes', 470 | 'dice', 471 | 'discs', 472 | 'does', 473 | 'dogs', 474 | 'doors', 475 | 'dopes', 476 | 'doves', 477 | 'dreams', 478 | 'drones', 479 | 'ducks', 480 | 'dunes', 481 | 'dwarves', 482 | 'eels', 483 | 'eggs', 484 | 'elk', 485 | 'elks', 486 | 'elms', 487 | 'elves', 488 | 'ewes', 489 | 'eyes', 490 | 'faces', 491 | 'facts', 492 | 'fawns', 493 | 'feet', 494 | 'ferns', 495 | 'fish', 496 | 'fists', 497 | 'flames', 498 | 'fleas', 499 | 'flocks', 500 | 'flutes', 501 | 'foals', 502 | 'foes', 503 | 'fools', 504 | 'fowl', 505 | 'frogs', 506 | 'fruits', 507 | 'gangs', 508 | 'gar', 509 | 'geese', 510 | 'gems', 511 | 'germs', 512 | 'ghosts', 513 | 'gnomes', 514 | 'goats', 515 | 'grapes', 516 | 'grooms', 517 | 'grouse', 518 | 'grubs', 519 | 'guards', 520 | 'gulls', 521 | 'hands', 522 | 'hares', 523 | 'hawks', 524 | 'heads', 525 | 'hearts', 526 | 'hens', 527 | 'herbs', 528 | 'hills', 529 | 'hogs', 530 | 'holes', 531 | 'hordes', 532 | 'ide', 533 | 'jars', 534 | 'jays', 535 | 'kids', 536 | 'kings', 537 | 'kites', 538 | 'lads', 539 | 'lakes', 540 | 'lambs', 541 | 'larks', 542 | 'lice', 543 | 'lights', 544 | 'limbs', 545 | 'looms', 546 | 'loons', 547 | 'mares', 548 | 'masks', 549 | 'mice', 550 | 'mimes', 551 | 'minks', 552 | 'mists', 553 | 'mites', 554 | 'mobs', 555 | 'molds', 556 | 'moles', 557 | 'moons', 558 | 'moths', 559 | 'newts', 560 | 'nymphs', 561 | 'orbs', 562 | 'orcs', 563 | 'owls', 564 | 'pearls', 565 | 'pears', 566 | 'peas', 567 | 'perch', 568 | 'pigs', 569 | 'pikes', 570 | 'pines', 571 | 'plains', 572 | 'plants', 573 | 'plums', 574 | 'pools', 575 | 'prawns', 576 | 'prunes', 577 | 'pugs', 578 | 'punks', 579 | 'quail', 580 | 'quails', 581 | 'queens', 582 | 'quills', 583 | 'rafts', 584 | 'rains', 585 | 'rams', 586 | 'rats', 587 | 'rays', 588 | 'ribs', 589 | 'rocks', 590 | 'rooks', 591 | 'ruffs', 592 | 'runes', 593 | 'sands', 594 | 'seals', 595 | 'seas', 596 | 'seeds', 597 | 'serfs', 598 | 'shards', 599 | 'sharks', 600 | 'sheep', 601 | 'shells', 602 | 'ships', 603 | 'shoals', 604 | 'shrews', 605 | 'shrimp', 606 | 'skate', 607 | 'skies', 608 | 'skunks', 609 | 'sloths', 610 | 'slugs', 611 | 'smew', 612 | 'smiles', 613 | 'snails', 614 | 'snakes', 615 | 'snipes', 616 | 'sole', 617 | 'songs', 618 | 'spades', 619 | 'sprats', 620 | 'sprouts', 621 | 'squabs', 622 | 'squads', 623 | 'squares', 624 | 'squid', 625 | 'stars', 626 | 'stoats', 627 | 'stones', 628 | 'storks', 629 | 'strays', 630 | 'suns', 631 | 'swans', 632 | 'swarms', 633 | 'swells', 634 | 'swifts', 635 | 'tars', 636 | 'teams', 637 | 'teeth', 638 | 'terns', 639 | 'thorns', 640 | 'threads', 641 | 'thrones', 642 | 'ticks', 643 | 'toads', 644 | 'tools', 645 | 'trees', 646 | 'tribes', 647 | 'trolls', 648 | 'trout', 649 | 'tunes', 650 | 'tusks', 651 | 'veins', 652 | 'verbs', 653 | 'vines', 654 | 'voles', 655 | 'wasps', 656 | 'waves', 657 | 'wells', 658 | 'whales', 659 | 'whelks', 660 | 'whiffs', 661 | 'winds', 662 | 'wolves', 663 | 'worms', 664 | 'wraiths', 665 | 'wrens', 666 | 'yaks'] 667 | 668 | verbs = [ 669 | 'aid', 670 | 'arm', 671 | 'awe', 672 | 'axe', 673 | 'bag', 674 | 'bait', 675 | 'bare', 676 | 'bash', 677 | 'bathe', 678 | 'beat', 679 | 'bid', 680 | 'bilk', 681 | 'blame', 682 | 'bleach', 683 | 'bleed', 684 | 'bless', 685 | 'bluff', 686 | 'blur', 687 | 'boast', 688 | 'boost', 689 | 'boot', 690 | 'bore', 691 | 'botch', 692 | 'breed', 693 | 'brew', 694 | 'bribe', 695 | 'brief', 696 | 'brine', 697 | 'broil', 698 | 'browse', 699 | 'bruise', 700 | 'build', 701 | 'burn', 702 | 'burst', 703 | 'call', 704 | 'calm', 705 | 'carve', 706 | 'chafe', 707 | 'chant', 708 | 'charge', 709 | 'chart', 710 | 'cheat', 711 | 'check', 712 | 'cheer', 713 | 'chill', 714 | 'choke', 715 | 'chomp', 716 | 'choose', 717 | 'churn', 718 | 'cite', 719 | 'clamp', 720 | 'clap', 721 | 'clasp', 722 | 'claw', 723 | 'clean', 724 | 'cleanse', 725 | 'clip', 726 | 'cloack', 727 | 'clone', 728 | 'clutch', 729 | 'coax', 730 | 'crack', 731 | 'crave', 732 | 'crunch', 733 | 'cry', 734 | 'cull', 735 | 'cure', 736 | 'curse', 737 | 'cuss', 738 | 'dare', 739 | 'daze', 740 | 'dent', 741 | 'dig', 742 | 'ding', 743 | 'doubt', 744 | 'dowse', 745 | 'drag', 746 | 'drain', 747 | 'drape', 748 | 'draw', 749 | 'dread', 750 | 'dredge', 751 | 'drill', 752 | 'drink', 753 | 'drip', 754 | 'drive', 755 | 'drop', 756 | 'drown', 757 | 'dry', 758 | 'dump', 759 | 'eat', 760 | 'etch', 761 | 'face', 762 | 'fail', 763 | 'fault', 764 | 'fear', 765 | 'feed', 766 | 'feel', 767 | 'fetch', 768 | 'fight', 769 | 'find', 770 | 'fix', 771 | 'flap', 772 | 'flay', 773 | 'flee', 774 | 'fling', 775 | 'flip', 776 | 'float', 777 | 'foil', 778 | 'forge', 779 | 'free', 780 | 'freeze', 781 | 'frisk', 782 | 'gain', 783 | 'glimpse', 784 | 'gnaw', 785 | 'goad', 786 | 'gouge', 787 | 'grab', 788 | 'grasp', 789 | 'graze', 790 | 'grieve', 791 | 'grip', 792 | 'groom', 793 | 'guard', 794 | 'guards', 795 | 'guide', 796 | 'gulp', 797 | 'gush', 798 | 'halt', 799 | 'harm', 800 | 'hate', 801 | 'haul', 802 | 'haunt', 803 | 'have', 804 | 'heal', 805 | 'hear', 806 | 'help', 807 | 'herd', 808 | 'hex', 809 | 'hire', 810 | 'hit', 811 | 'hoist', 812 | 'hound', 813 | 'hug', 814 | 'hurl', 815 | 'irk', 816 | 'jab', 817 | 'jeer', 818 | 'join', 819 | 'jolt', 820 | 'keep', 821 | 'kick', 822 | 'kill', 823 | 'kiss', 824 | 'lash', 825 | 'leash', 826 | 'leave', 827 | 'lift', 828 | 'like', 829 | 'love', 830 | 'lugg', 831 | 'lure', 832 | 'maim', 833 | 'make', 834 | 'mask', 835 | 'meet', 836 | 'melt', 837 | 'mend', 838 | 'miss', 839 | 'mould', 840 | 'move', 841 | 'nab', 842 | 'name', 843 | 'need', 844 | 'oust', 845 | 'paint', 846 | 'paw', 847 | 'pay', 848 | 'peck', 849 | 'peeve', 850 | 'pelt', 851 | 'please', 852 | 'pluck', 853 | 'poach', 854 | 'poll', 855 | 'praise', 856 | 'prick', 857 | 'print', 858 | 'probe', 859 | 'prod', 860 | 'prompt', 861 | 'punch', 862 | 'quash', 863 | 'quell', 864 | 'quote', 865 | 'raid', 866 | 'raise', 867 | 'raze', 868 | 'ride', 869 | 'roast', 870 | 'rouse', 871 | 'rule', 872 | 'scald', 873 | 'scalp', 874 | 'scar', 875 | 'scathe', 876 | 'score', 877 | 'scorn', 878 | 'scour', 879 | 'scuff', 880 | 'sear', 881 | 'see', 882 | 'seek', 883 | 'seize', 884 | 'send', 885 | 'sense', 886 | 'serve', 887 | 'shake', 888 | 'shear', 889 | 'shift', 890 | 'shoot', 891 | 'shun', 892 | 'slap', 893 | 'slay', 894 | 'slice', 895 | 'smack', 896 | 'smash', 897 | 'smell', 898 | 'smite', 899 | 'snare', 900 | 'snatch', 901 | 'sniff', 902 | 'snub', 903 | 'soak', 904 | 'spare', 905 | 'splash', 906 | 'split', 907 | 'spook', 908 | 'spray', 909 | 'squash', 910 | 'squeeze', 911 | 'stab', 912 | 'stain', 913 | 'starve', 914 | 'steal', 915 | 'steer', 916 | 'sting', 917 | 'strike', 918 | 'stun', 919 | 'tag', 920 | 'tame', 921 | 'taste', 922 | 'taunt', 923 | 'teach', 924 | 'tend'] 925 | -------------------------------------------------------------------------------- /pyhipku/encode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import absolute_import, division 5 | import socket 6 | 7 | from .dictionary import (adjectives, nouns, verbs, animal_adjectives, 8 | animal_colors, animal_nouns, animal_verbs, 9 | nature_adjectives, nature_nouns, plant_nouns, 10 | plant_verbs) 11 | 12 | 13 | def encode(ip): 14 | """Encode IP address as haiku""" 15 | is_ipv6 = ip_is_ipv6(ip) 16 | decimal_octect_array = split_ip(ip, is_ipv6) 17 | factord_octet_array = factor_octets(decimal_octect_array, is_ipv6) 18 | encoded_word_array = encode_words(factord_octet_array, is_ipv6) 19 | haiku_text = write_haiku(encoded_word_array, is_ipv6) 20 | return haiku_text 21 | 22 | 23 | def ip_is_ipv6(ip): 24 | """IP address is IPv6 or not, return True if it is""" 25 | if ip.find(':') != -1: 26 | return True 27 | elif ip.find('.') != -1: 28 | return False 29 | else: 30 | raise ValueError("Formatting error in IP address input. " 31 | "Contains neither ':' or '.'") 32 | 33 | 34 | def split_ip(ip, is_ipv6): 35 | """Split IP address and convert to integer""" 36 | if is_ipv6: 37 | separator = ':' 38 | else: 39 | separator = '.' 40 | # Remove new_line and space characters. 41 | ip = ''.join(ip.split()) 42 | # Validate the IP address. 43 | try: 44 | if is_ipv6: 45 | socket.inet_pton(socket.AF_INET6, ip) 46 | else: 47 | socket.inet_pton(socket.AF_INET, ip) 48 | except (OSError, socket.error): 49 | raise ValueError("Illegal IP address.") 50 | octet_array = ip.split(separator) 51 | # Replace missing octect with 0 if IPv6 address is in abbreviated format. 52 | if len(octet_array) < 8 and is_ipv6: 53 | octet_missing_num = 8 - len(octet_array) 54 | octet_array = pad_octets(octet_array, octet_missing_num) 55 | decimal_octect_array = [] 56 | if is_ipv6: 57 | for i in range(len(octet_array)): 58 | decimal_octect_array.append(int(octet_array[i], 16)) 59 | else: 60 | decimal_octect_array = [int(num) for num in octet_array] 61 | return decimal_octect_array 62 | 63 | 64 | def pad_octets(octet_array, octet_missing_num): 65 | """Pad appropriate number of 0 octets if IPv6 is abbreviated""" 66 | padded_octect = '0' 67 | length = len(octet_array) 68 | # If the first or last octect is blank, zero them. 69 | if octet_array[0] == '': 70 | octet_array[0] = padded_octect 71 | if octet_array[length - 1] == '': 72 | octet_array[length - 1] = padded_octect 73 | # Check the rest of the array for blank octets and pad as needed. 74 | for i in range(length): 75 | if octet_array[i] == '': 76 | octet_array[i] = padded_octect 77 | for j in range(octet_missing_num): 78 | octet_array.insert(i, padded_octect) 79 | return octet_array 80 | 81 | 82 | def factor_octets(octet_array, is_ipv6): 83 | """Convert each decimal octet into a factor of the 84 | divisor (16 or 256) and a remainder""" 85 | if is_ipv6: 86 | divisor = 256 87 | else: 88 | divisor = 16 89 | factord_octet_array = [] 90 | for i in range(len(octet_array)): 91 | factord_octet_array.extend([octet_array[i] // divisor, 92 | octet_array[i] % divisor]) 93 | return factord_octet_array 94 | 95 | 96 | def encode_words(factor_array, is_ipv6): 97 | """Get a word array from the dictionary according to the factor_array""" 98 | key = get_key(is_ipv6) 99 | encoded_word_array = [] 100 | for i in range(len(factor_array)): 101 | encoded_word_array.append(key[i][factor_array[i]]) 102 | return encoded_word_array 103 | 104 | 105 | def get_key(is_ipv6): 106 | """Return an array of dictionaries representing the correct word 107 | order for the haiku""" 108 | if is_ipv6: 109 | key = [adjectives, nouns, adjectives, nouns, verbs, adjectives, 110 | adjectives, adjectives, adjectives, adjectives, nouns, 111 | adjectives, nouns, verbs, adjectives, nouns] 112 | else: 113 | key = [animal_adjectives, animal_colors, animal_nouns, animal_verbs, 114 | nature_adjectives, nature_nouns, plant_nouns, plant_verbs] 115 | return key 116 | 117 | 118 | def write_haiku(word_array, is_ipv6): 119 | """Return the beautiful haiku""" 120 | # String to place in schema to show word slot. 121 | octct = 'OCTET' 122 | schema = get_schema(is_ipv6, octct) 123 | 124 | # Replace each instance of 'octet' in the schema with a word from 125 | # the encoded word array. 126 | for i in range(len(word_array)): 127 | for j in range(len(schema)): 128 | if schema[j] == octct: 129 | schema[j] = word_array[i] 130 | break 131 | # Capitalize appropriate words. 132 | schema = capitalize_haiku(schema) 133 | haiku = ''.join(schema) 134 | return haiku 135 | 136 | 137 | def get_schema(is_ipv6, octet): 138 | """Get the template with word slots""" 139 | new_line = '\n' 140 | period = '.' 141 | space = ' ' 142 | non_words = [new_line, period, space] 143 | if is_ipv6: 144 | schema = [octet, octet, 'and', octet, octet, new_line, octet, octet, 145 | octet, octet, octet, octet, octet, period, new_line, octet, 146 | octet, octet, octet, octet, period, new_line] 147 | else: 148 | schema = ['The', octet, octet, octet, new_line, octet, 'in the', 149 | octet, octet, period, new_line, octet, octet, period, 150 | new_line] 151 | space_num = 0 152 | # Add spaces before words except the first word. 153 | for i in range(1, len(schema)): 154 | i = i + space_num 155 | insert_space = True 156 | # If the current entry is a non_word, don't add a space. 157 | if schema[i] in non_words: 158 | insert_space = False 159 | # If the previous entry is a new_line, don't add a space. 160 | if schema[i-1] == new_line: 161 | insert_space = False 162 | if insert_space: 163 | schema.insert(i, space) 164 | space_num = space_num + 1 165 | return schema 166 | 167 | 168 | def capitalize_haiku(haiku_array): 169 | """Capitalize appropriate words in haiku""" 170 | period = '.' 171 | # Always capitalize the first word. 172 | haiku_array[0] = haiku_array[0].capitalize() 173 | for i in range(1, len(haiku_array)): 174 | if haiku_array[i] == period and i+2 < len(haiku_array): 175 | # If the current entry is a period then the next entry will 176 | # be a new_line or a space, so check two positions after and 177 | # capitalize that entry, so long as it's a word. 178 | haiku_array[i+2] = haiku_array[i+2].capitalize() 179 | return haiku_array 180 | -------------------------------------------------------------------------------- /pyhipku/test/test_current_version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Test that a series of encoded test IPs match haiku for the current 6 | set of dictionaries and schema, and that a series of decoded test 7 | haiku match IPs for the current dictionaries and schema. These tests 8 | must be updated whenever the dictionaries or schema are changed. 9 | """ 10 | 11 | from __future__ import absolute_import 12 | import unittest 13 | 14 | from pyhipku import encode 15 | from pyhipku import decode 16 | 17 | 18 | class PyhipkuTestCase(unittest.TestCase): 19 | def setUp(self): 20 | self.ipv4_pairs = [ 21 | ['0.0.0.0', 'The agile beige ape\naches in the ancient canyon.\n' 22 | 'Autumn colors blow.\n'], 23 | ['127.0.0.1', 'The hungry white ape\naches in the ancient canyon' 24 | '.\nAutumn colors crunch.\n'], 25 | ['82.158.98.2', 'The fearful blue newt\nwakes in the foggy desert' 26 | '.\nAutumn colors dance.\n'], 27 | ['255.255.255.255', 'The weary white wolf\nyawns in the ' 28 | 'wind-swept wetlands.\nYellowwood leaves twist.\n'] 29 | ] 30 | self.ipv6_pairs = [ 31 | ['0:0:0:0:0:0:0:0', 'Ace ants and ace ants\naid ace ace ace ace ' 32 | 'ace ants.\nAce ants aid ace ants.\n'], 33 | ['2c8f:27aa:61fd:56ec:7ebe:d03a:1f50:475f', 'Cursed mobs and ' 34 | 'crazed queens\nfeel wrong gruff tired moist slow sprats.\nFaint ' 35 | 'bulls dread fond fruits.\n'], 36 | ['ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'Young yaks and ' 37 | 'young yaks\ntend young young young young young yaks.\nYoung ' 38 | 'yaks tend young yaks.\n'] 39 | ] 40 | 41 | def test_ipv4_encode(self): 42 | for i in range(len(self.ipv4_pairs)): 43 | self.assertEqual(encode(self.ipv4_pairs[i][0]), 44 | self.ipv4_pairs[i][1]) 45 | 46 | def test_ipv6_encode(self): 47 | for i in range(len(self.ipv6_pairs)): 48 | self.assertEqual(encode(self.ipv6_pairs[i][0]), 49 | self.ipv6_pairs[i][1]) 50 | 51 | def test_ipv4_decode(self): 52 | for i in range(len(self.ipv4_pairs)): 53 | self.assertEqual(decode(self.ipv4_pairs[i][1]), 54 | self.ipv4_pairs[i][0]) 55 | 56 | def test_ipv6_decode(self): 57 | for i in range(len(self.ipv6_pairs)): 58 | self.assertEqual(decode(self.ipv6_pairs[i][1]), 59 | self.ipv6_pairs[i][0]) 60 | 61 | def test_encode_exception_handle(self): 62 | self.assertRaises(ValueError, encode, 'Hello, world') 63 | self.assertRaises(ValueError, encode, '127.0.0.2560') 64 | self.assertRaises(ValueError, encode, '127.0.0') 65 | self.assertRaises(ValueError, encode, '0::0:zzzz') 66 | self.assertRaises(ValueError, encode, '0:0:0:0:0:0:0') 67 | 68 | def test_decode_exception_handle(self): 69 | self.assertRaises(ValueError, decode, 'Hello, world.') 70 | self.assertRaises(ValueError, decode, 71 | ('The hungry white ape\naches in the ancient canyon.' 72 | '\nAutumn colors foo.\n')) 73 | -------------------------------------------------------------------------------- /pyhipku/test/test_hipku.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Test that when you take an IP address, encode it, and then decode it 6 | again, it matches the original address. These tests are applicable to 7 | all versions of Hipku, regardless of changes to the dictionaries or schema. 8 | """ 9 | 10 | from __future__ import absolute_import 11 | 12 | from pyhipku import encode 13 | from pyhipku import decode 14 | 15 | 16 | def test_ipv4(): 17 | assert decode(encode('0.0.0.0')) == '0.0.0.0' 18 | assert decode(encode('82.158.98.2')) == '82.158.98.2' 19 | assert decode(encode('255.255.255.255')) == '255.255.255.255' 20 | 21 | 22 | def test_ipv6(): 23 | assert decode(encode('0:0:0:0:0:0:0:0')) == '0:0:0:0:0:0:0:0' 24 | assert (decode(encode('2c8f:27aa:61fd:56ec:7ebe:d03a:1f50:475f')) == 25 | '2c8f:27aa:61fd:56ec:7ebe:d03a:1f50:475f') 26 | assert (decode(encode('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')) == 27 | 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff') 28 | 29 | 30 | def test_abbreviated_ipv6(): 31 | assert decode(encode('::0')) == '0:0:0:0:0:0:0:0' 32 | assert decode(encode('0::')) == '0:0:0:0:0:0:0:0' 33 | assert decode(encode('0::0')) == '0:0:0:0:0:0:0:0' 34 | assert decode(encode('0:0::0:0')) == '0:0:0:0:0:0:0:0' 35 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup 5 | 6 | import pyhipku 7 | 8 | 9 | try: 10 | import pypandoc 11 | long_description = pypandoc.convert('README.md','rst') 12 | except (IOError, ImportError): 13 | with open('README.md') as f: 14 | long_description = f.read() 15 | 16 | 17 | setup( 18 | name='pyhipku', 19 | version=pyhipku.__version__, 20 | url='http://github.com/lord63/pyhipku/', 21 | license='MIT', 22 | author='lord63', 23 | author_email='lord63.j@gmail.com', 24 | description='Encode any IP address as a haiku', 25 | long_description=long_description, 26 | packages=['pyhipku'], 27 | include_package_data=True, 28 | keywords='ip haiku', 29 | classifiers=[ 30 | 'Development Status :: 4 - Beta', 31 | 'Operating System :: POSIX', 32 | 'Operating System :: POSIX :: Linux', 33 | 'License :: OSI Approved :: MIT License', 34 | 'Programming Language :: Python :: 2', 35 | 'Programming Language :: Python :: 2.6', 36 | 'Programming Language :: Python :: 2.7', 37 | 'Programming Language :: Python :: 3', 38 | 'Programming Language :: Python :: 3.3', 39 | 'Programming Language :: Python :: 3.4', 40 | 'Topic :: Software Development :: Libraries :: Python Modules' 41 | ] 42 | ) 43 | --------------------------------------------------------------------------------