diff --git a/.gitignore b/.gitignore index c3c1634..a22d658 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,9 @@ *# *.pyc *.o +.idea ./bin/ build/ cache/ target/ -puzzles \ No newline at end of file +puzzles diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 0000000..d452e20 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,3 @@ +Neale Pickett +Patrick Avery +Shannon Steinfadt diff --git a/answer_words.txt b/answer_words.txt new file mode 100644 index 0000000..55d9e06 --- /dev/null +++ b/answer_words.txt @@ -0,0 +1,4096 @@ +abalones +abashed +abattoir +abbreviating +abduct +abetters +abettors +abhorrent +ablution +ablutions +aboriginal +aborted +aboveboard +abrades +absence +absorbents +abstention +abstraction +abuser +academically +acceptably +accepting +accompanists +accumulative +accuracy +accurate +accusers +acetate +acidifying +acidity +acolytes +acoustic +acquires +actualized +acuity +adagios +addicting +adherent +adjacently +adjectivally +adjoined +adjuration +adjustable +administrates +admiringly +admonish +admonishments +adores +adorns +adulterers +advance +advancing +advert +advisory +aerosol +aerospace +affability +affable +afflict +affordable +affront +afterbirths +agates +agglomerates +aggravations +aggressiveness +agnostic +agrarian +aiding +ailments +airfoils +airily +airless +aisles +albatrosses +alcoholism +alderwoman +alerting +aliasing +aligns +allayed +allays +alleyway +allocated +allocates +allocation +allotments +allowances +allures +alluring +allusion +aloofness +alternately +always +amalgam +amalgamations +amanuenses +ambassadorship +ambiguity +ambulance +amenity +amiably +amoeba +amoebae +amorality +amounting +amphitheater +amplified +anaconda +anatomy +ancestors +android +anesthesia +anesthetic +angina +angular +animal +animate +animating +animations +anions +annealing +announcements +annoyingly +annoys +annuals +answering +antagonize +antecedent +anthropomorphic +anticlimaxes +antique +antiwar +apathetically +aphelion +aphrodisiacs +apostrophe +apostrophes +appeases +appellation +appendage +appendages +appetizingly +applaud +applauded +applesauce +appliances +applicators +appointing +appraises +apprise +approbations +arboretums +archaeologists +archeologist +archest +archivists +arcing +ardors +arguing +argyles +armadas +arraigned +arraignment +arrangers +arrowhead +arsenals +artichokes +artificially +artistically +ascensions +ashamed +assault +assaulter +assaults +assertiveness +assign +assignations +assignments +assizes +assumes +assuming +astounds +astral +astrological +atrocity +attacked +attained +attempted +attendances +attentiveness +attitudinizing +auctioneers +auctioning +audiences +audits +augurs +aureole +auspiciously +authorizes +authors +automaton +aviaries +aviator +avionics +avoidable +avoirdupois +avowed +awaiting +awaken +awarded +aweigh +awfuller +babble +babysit +bacchanal +backpackers +backpacking +backpedaling +backstage +badness +bagpipe +balanced +baleen +balloon +ballyhooing +baluster +bamboozles +banishment +bankruptcies +banned +bantam +banters +banyans +baptismal +baptisteries +baptized +barbered +bargains +barnstormed +baroness +barraged +barrelled +barrelling +barricaded +barroom +baseless +baselines +bassists +bathing +batsman +beaching +beacons +beaker +beamed +beaning +beatified +beatitude +beautifier +becoming +bedazzled +bedfellow +bedrocks +bedtime +beetles +befouled +beginnings +begrudge +begrudges +beguiled +beholds +beleaguers +belligerently +bellow +bellwether +bellyached +belong +belting +bemoans +benefit +benign +bequeathed +bequeathing +besieged +besieging +besmirches +bestir +bethinking +betokening +bewilders +bewitched +bicameral +bigamist +biggest +binders +binged +birdied +bitchiest +biting +bittersweets +bivouac +bivouacking +blackball +blacked +blackmailed +blackness +bladder +blamed +blaming +blancmange +blaring +blasphemer +blasphemes +bleakest +blessings +blinked +blistered +blocks +blondest +blooding +bloodshot +blotchier +blower +boarders +boasts +boaters +bobbed +bobbing +bobolink +bobsledding +boggle +boldface +bologna +bolting +bondage +bondsman +bonging +bonitoes +boogie +bookkeeping +bookstore +boomeranged +boondoggled +boosters +boosting +bootee +booths +bootlegged +botanical +botanist +bottomed +bouillabaisse +boulevards +bountiful +bouquet +bracelet +bracts +braining +brandies +brandishes +brashest +brawled +brazenly +breach +breaded +breadfruits +breadths +breadwinner +breakage +breastbones +breaststroke +breezier +breviaries +bricklayers +bridal +bridals +brides +bright +brilliant +brilliantly +briquettes +brisket +bristles +bristly +brittleness +brittlest +broadcasting +broadened +broadest +broadness +broadswords +brochure +brochures +broncos +brontosauruses +brownout +browser +bruises +brunettes +brushed +bucketfuls +buckler +bucktooth +buffalo +buffing +buildings +buildup +bulldozes +bullet +bulletined +bulrushes +buncombe +bunion +buoyed +burglarize +burnoose +burros +burrows +busheling +bushwhacking +busybodies +busying +butcher +butchers +buzzards +cablegram +cables +cadaverous +cadger +calcium +calculated +calligrapher +callused +calmed +calved +camber +camellia +cameramen +camisole +camouflaged +camper +cancan +cancans +cancelled +cancers +candles +cannabis +canneries +cantankerously +canteens +cantered +canters +canticles +capering +capons +capriciously +capsize +capsizing +capstan +captain +captivating +caramel +carcass +cardiogram +caressing +caribou +caricaturing +carnivals +caroler +carousels +carpeting +carport +cartwheel +carves +caseload +caseworker +caseworkers +cassava +cassino +castigated +castigators +castings +catalysts +catcalls +catechized +caterers +catheter +cattails +cattle +catwalks +caudal +caught +cavaliers +caveman +caving +cavorting +ceases +ceasing +celebrant +celebrated +celebrates +celerity +celesta +celestial +cellulars +cellulite +censure +censuring +centenarian +centenaries +centers +centralized +cerebellum +ceremonial +ceremony +cesarian +chaining +chairmanship +championing +channelled +channelling +chantey +chapel +chaplaincy +chapped +charitable +charity +charming +charted +chartered +chases +chastened +chastised +chatters +chattier +chauffeured +cheapskate +checkered +cheekbones +cheerleaders +cheerless +cheesecloth +cheesiest +cheroot +chessboard +chewing +chickpea +chiding +chiefer +chiefest +childhoods +chilliness +chillings +chilly +chisel +chisels +chlorinated +choking +choleric +choruses +chronically +chronology +chrysalises +churchyards +churls +cicadas +cinctures +cinematographer +ciphering +circulation +circulations +circulatory +circumcising +circumflexes +circumscribes +circumventing +citizenship +civics +civilians +civilized +civvies +clamminess +clandestinely +clapboarding +clapboards +clapper +clarinetists +clarity +classify +clauses +cleanings +clematises +clergies +clergymen +clerked +cleverness +climactic +climaxes +clippers +clippings +clitorises +cloakroom +closest +cloudy +clutter +coalesce +coarse +coasted +coasting +coasts +cobble +cobblers +cobbling +cockiest +cocooning +codding +coeducational +coeval +coffeecake +coffin +cognac +cohabiting +coined +colder +coldest +collapsible +collectible +collectibles +collectivize +collects +colleges +collie +collocates +colluded +colonizer +coloraturas +column +combed +combustible +comedies +comforted +comity +commandeered +commandeers +commencement +commiserations +commissioner +commutative +compartmentalizes +compassed +compassing +compel +competition +competitively +compiles +completeness +complexes +complicated +component +composer +composite +composition +compound +compounding +comprehensible +compresses +compromising +comptrollers +computes +conceal +concentrating +concentrically +conceptualizations +conciliates +conciser +conclusively +concussions +condemning +condenser +condescends +condoled +condor +conduce +conducing +conducive +conduction +confabs +confederations +confessions +confidante +confinement +confiscate +conflicted +confronting +congestion +conglomeration +congratulate +congratulations +congress +congresses +congressmen +conjecture +conjoint +conjugated +conjurors +conked +conking +connecter +conquers +consciously +consecrates +consequences +considerately +consigning +consignment +consist +consisted +consolidates +conspicuously +conspired +constituted +constrictions +consummated +consumptive +containing +contaminates +contemplative +contest +contiguous +continuation +contort +controversies +contusions +conurbation +convenes +convenient +convergence +converter +convivial +convolutions +convoys +convulses +cooked +coolly +coopers +coordinator +coppery +coquetted +coquetting +corals +cording +cornea +cornrows +cornstalks +coronet +corralled +correspondent +cosigns +cosmetologists +cosmically +cosmologist +costars +cotter +cotton +cottonmouth +cottons +coughs +councillor +counterpoints +counterrevolutionaries +counting +courtliness +courtly +coverlets +cowardliness +cowhide +cowing +crackup +craftily +crashes +credential +credibility +creditable +creels +creeper +cribbing +crimsoning +crinkles +crinoline +crisscrossed +crisscrosses +crofts +crooners +crosschecked +crowns +cruddiest +crudely +cruelly +cruets +crumpet +crunches +crunchiest +cryogenics +crystal +crystalized +crystallize +cuckoos +cultivated +cultural +culvert +cupping +curiously +curliest +currant +curriculums +curtailed +curtailments +cushions +cussing +cyberpunk +cyclamen +cymbals +cytoplasm +dabble +daemons +daffiest +dahlias +daintiest +damaging +dampest +damply +danker +dankest +dapples +darken +darling +dashes +datelined +daubers +daydreamed +daydreaming +deactivating +deadbolt +deaden +dearness +deathtraps +debater +debaters +debauches +debilitate +debits +debonair +debtor +debtors +decadently +decadents +decays +deceitfully +deceitfulness +deceiving +decelerate +decimate +decked +decoding +decolonize +decomposed +deconstruction +decontaminated +decontaminating +decoration +decreasing +deducing +deductible +deducting +deescalating +defaced +defected +defendants +deferments +deflect +defogger +degraded +dehumidifier +dehydrates +dejecting +delegates +deletes +delinquently +deluges +delusions +delusive +delves +delving +demagog +demagogue +demarcate +demarcated +demeans +demesne +demijohn +demise +demoed +demonstrators +demoralize +demoralized +demurring +demurs +dendrite +denied +denizen +denizens +denominates +denominator +denser +dentist +dentists +deodorizes +dependability +dependant +depict +depleted +depleting +deported +deposing +deprecating +depriving +deputies +derail +deregulates +derive +dermatology +dervish +descants +descriptively +desecrating +desert +desire +desolately +despatches +desperate +destination +desultory +detain +detergent +determiners +dethrones +detoxification +devaluation +development +developments +develops +deviate +devised +devours +devout +devoutest +dhotis +diagnosed +dialogues +diddling +diereses +digestive +digitized +digressed +digressing +dilation +dilatory +dillydallies +dillydally +dimensional +diminished +dimwit +diplomacy +diplomata +dippers +dipsticks +directions +directorship +directory +dirigibles +disabusing +disadvantaging +disallowing +disappointingly +disapprovingly +disarms +disassembles +disbarred +disburse +discern +disclosing +discoing +disconnect +disconnections +discontentedly +discontinuations +discontinues +discording +discotheque +discount +discourages +discoursed +discus +discussions +disembowels +disgruntled +dishcloths +disheartens +dishevelled +dishonoring +disillusioning +disinclined +disliking +disorganize +disparaged +dispassionately +dispenses +displayable +disposes +disproven +disruptive +dissected +disservice +dissociated +distastefully +distastes +distillations +distilleries +distillers +distinguishing +distracted +distributions +distributive +diverging +dividend +diviners +divisibility +divisors +divorcing +dizzies +docile +docility +dockets +doctrine +dogfights +dogies +dolefully +domesticity +domicile +domineered +dominion +donate +dormitory +dorsal +dossiers +doubtless +douches +doughtiest +downhill +downplays +downscale +downsize +dozing +drachmas +draftiest +dragonfly +dramatizing +drawbridges +drawstrings +dreaded +dreadful +dreadlocks +dreamlike +dreamy +dreary +drenches +dressage +dressiest +drifted +drivelled +driven +drizzles +drowse +drowsily +drowsiness +drummed +drumming +drumstick +duelling +dumbfound +dunner +duodena +duplicity +during +duskiest +dustbin +dustman +dwarfism +dwelled +dwindled +dynamite +dynamos +earned +earnestness +earthwork +easily +easterlies +eavesdroppers +economized +editorship +effete +efficacious +effusions +eggplants +elaborate +elapse +elating +elbowed +electoral +elicited +elides +elites +ellipse +elongations +emaciating +emancipates +embalmers +embalms +embarking +embitters +emboldening +embossed +embrace +embryos +eminently +emoted +emotions +emphases +emphatic +emphatically +empire +empires +employment +emulsifies +emulsify +enamor +encapsulate +encapsulation +encase +encoded +encoder +encompass +encouragements +encrusted +endeavor +endures +energies +enfeebled +enfolding +enforcers +engages +engender +engenders +engraver +engulfing +enlargement +ennobles +enquiring +enquiry +ensconcing +enshrines +ensnares +entail +entails +entangling +entertainment +enthralls +enthusiastically +enticed +enticements +entire +entombed +entraps +entrusted +enumeration +enunciates +enunciation +envelops +envisions +epaulettes +ephemeral +epidemiology +epilogues +epoxied +equality +equalizing +equestrian +equilateral +equivocates +erasing +erection +eroticism +errors +eruptions +escapees +escorting +espouses +essaying +essayist +essentials +established +establishes +estates +ethically +ethics +ethnologists +eulogizes +euthanasia +evaporating +evened +evener +everlasting +evocation +evolves +exactest +exceed +excepting +excepts +excerpt +excessively +excitement +exclaims +exclusives +excoriate +excretions +exculpate +excuses +executable +executors +exhausted +exhilarated +exhumations +exigent +exiles +existent +existentially +existing +exoduses +exonerating +expanding +expects +expediences +expedites +expending +experiments +expiry +explanatory +exploring +expressed +expressing +expressiveness +expressly +expunge +extemporized +extension +extinguished +extrapolates +extrapolation +extricates +extroversion +extrovert +exults +eyeful +eyelets +facsimiled +factory +factual +faecal +fagged +fainted +fairness +faithfuls +faithlessness +fallibility +fallowed +falsest +falterings +famines +famished +fanatical +fancying +fantasy +farmhands +farmland +farsighted +farting +fastenings +fathoms +feasibility +feasting +featherweights +felicity +feline +fermentation +ferrets +fertilization +fertilizer +fervidly +festoons +fetiches +fetter +feuding +feverish +fibber +fiendish +fiercely +fifteens +figurehead +figures +filching +fillies +filliped +filtering +finalizing +finders +finessed +finesses +fingerprint +finishing +finking +firestorm +firmer +fishbowl +fishier +fishiest +fishnets +fishtailing +fisticuffs +fitted +flagellate +flakiness +flamencos +flames +flanneling +flapjacks +flashier +flatfoot +flatness +fleecing +flicks +flimsiness +flings +flintlocks +flippantly +florid +flotillas +flounces +flourish +fluffing +fluoroscope +fluxing +flyleaves +flyovers +foetuses +fogbound +fogging +foghorns +follow +fondles +fondue +footballers +footbridges +footstep +forborne +forebears +foreboding +forehand +foreshortened +foreskin +forestalls +foresting +foreswore +foretasting +foreword +forklifts +forsworn +fortifies +fortissimo +fortitude +fortuitously +forward +fossil +fossilizes +foundations +foxtrotting +fracas +fractiously +fractures +frailest +frailty +frantic +frappes +fraternally +fraternization +fraudulently +freebooters +freewheeled +freewill +freighters +frenetically +frequenting +freshman +freshmen +friendship +friers +fringes +frisking +frittering +frizzed +frolicked +frontispiece +froths +fruitiest +frustrates +fulminations +functioning +fundamentally +funerals +furling +furloughing +furnish +furnished +furors +furriest +furrows +furtherance +fustier +gabardine +gadded +gaffed +gaggle +gainsays +galena +gallantly +gallery +gallivanted +gallivanting +gallstones +gamesmanship +gamest +gangrenes +garbanzo +gardening +garishly +garners +garnish +garnished +gashes +gather +gauchos +gaudiest +gearshifts +gearwheels +geishas +gelling +geneses +genetic +genetically +geneticist +genteel +gentian +gentlefolk +genuineness +geodesics +geostationary +gesticulated +gesture +gestures +gherkin +ghostwriting +gibberish +gibbon +gibing +giblet +giddily +gigabyte +giggles +gigolo +girdled +glamored +glamour +glassier +glazing +gleaned +glimmering +glimpse +glistened +globetrotters +globule +glories +glossary +glower +gluttonous +glycerine +gnashed +goalies +gobbler +godsends +goldbricking +golfed +gondoliers +gonged +gonorrhea +goslings +gossamer +gouges +goulash +governance +gowning +gracefully +grandee +grannies +grapnel +grateful +gratified +gratis +graying +greases +gregarious +grenadiers +greyhounds +grilled +grislier +gristlier +grossest +grottos +grouses +gruelings +grunted +guardrail +guessed +guesstimate +guidance +guided +guileful +gulled +gulling +gummed +gunboat +gunpowder +gushiest +gutters +guzzling +gymnastic +gynecology +haberdashery +habituates +hackneying +hackneys +haemophilia +haggled +hairbreadth +hairless +hairpiece +halberd +halftimes +hallelujahs +hallmarking +hamlets +hammed +hammered +hammers +handbags +handballs +handcraft +handcuffing +handedness +handicapper +handily +handing +handkerchiefs +handlebars +handling +handpicking +handshake +handstand +hangars +hanged +hangings +hankerings +harassing +hardcover +hardiest +harelip +harems +harmfully +harmonizes +harrowed +harvest +hashed +hastened +hatchbacks +haversacks +hawing +hayseed +headers +headland +headsets +headwinds +healthfully +hearkened +hearses +heartened +heartens +hearth +heaters +heaven +heavens +heckler +hedgerows +hedonistic +heehawed +heiress +heliotropes +helium +hellebore +hellion +helpmates +heppest +herbicide +hereditary +heretical +heroins +heroism +hibernating +hierarchical +hierarchy +highballs +highbrows +highjack +highjackers +hindquarter +hindrance +hippie +hirsute +historians +hitched +hitchhike +hitchhiker +hitchhikers +hitting +hoagie +hoarded +hobbling +hobgoblin +hobnailing +hockey +holdout +holdouts +holdover +hollowing +holography +homeland +homeopathy +homesickness +homogeneity +homogeneously +homogenization +homosexuals +honest +honeymooners +honorariums +hoodwink +hoofing +hoorayed +horticulturist +hosannas +hoteliers +householder +housemaid +housewares +hubcap +huffily +humanizer +humerus +humiliate +hungriest +hurrying +hurtle +hussies +hutches +hutzpa +hydraulic +hydrogenates +hydrology +hydroplane +hydroplanes +hyenas +hygienic +hyperventilated +hyphenations +hyphened +hypochondriac +hypochondriacs +hypoglycemia +hypothesize +hysterectomy +hysterical +hysterically +idealist +identically +identifying +idolatry +igniting +ignorant +illumined +illusive +illustrated +illustrator +imagining +imbalance +imbedded +imbibes +imitates +immediacy +immigration +immortally +impaired +impairment +impaneling +impanels +impediment +imperatively +imperiling +imperils +imperishable +impertinence +impinged +impingement +implacability +implant +imposingly +impound +imprimaturs +imprisonments +improperly +imputed +inaccessibility +inaccurately +inaction +inadequacy +inamorata +inaugural +inbred +inbreeds +incarcerations +incinerators +incisiveness +incite +incomes +inconstancy +incredulously +incriminate +incrusted +incurably +indecipherable +indentations +indestructible +indicatives +indicator +indictment +indigestion +indignant +indiscreetly +indisposition +indorsed +inducing +inductions +industrial +industrialism +inebriated +ineffectiveness +ineptitude +inessential +inessentials +inexorably +inextricable +infamous +inferior +infertility +inflames +inflatable +inflate +infomercial +informed +infringe +ingraining +ingratiatingly +inhabit +inhalation +inhalations +inheritors +initiates +injection +inkblots +inmates +inoperative +inputting +inquest +insecurities +insensitivity +insentient +instability +instances +instills +institutionalize +institutionalized +instrumentalist +insularity +insureds +intake +integrates +integrating +intelligence +intend +intensifiers +intercepted +interface +interfered +interjects +interleaved +interluding +internal +internalize +internationalism +interpret +interrogation +intersection +interviewees +interweaving +intriguingly +introduce +introverted +intrudes +inundate +inveighs +inveigle +inventive +invents +investment +invests +invitational +invoice +invoke +ionizing +ipecacs +irking +irrefutable +irregardless +irregular +irrelevancy +irresolute +irrespective +irretrievably +islands +isolated +isolating +italicizing +itemized +iterator +itinerary +jabbered +jackrabbits +jading +jamboree +jangle +jauntily +jaunting +jawbones +jazzier +jazziest +jeopardizes +jerkiest +jitney +jokers +jonquil +joshing +jounced +jounces +journalism +jovial +joyfully +joyriders +judgmental +juggler +juicers +juicing +junkie +junkier +justest +justify +juxtaposing +kamikaze +kangaroos +katydid +kayaking +keening +kenneling +kerosine +kettledrum +khakis +kibitzers +kickbacks +kicked +kickers +kickstand +kickstands +kielbasas +killdeers +killers +kilobyte +kindliness +kinematics +kisses +kitchen +kleptomania +kneads +knitters +knitwear +knobbier +knotted +knuckle +kookaburras +kopecks +laboratories +ladles +lallygagging +lamentably +lamming +lancers +landladies +landowner +languages +languishes +lapsed +laryngitis +lassies +lateraling +latterly +laudable +launderer +laundering +lavishest +lawless +lazies +league +learned +leased +leases +leaven +lectern +lecturing +leeches +leerier +leeriest +legatos +leggins +legislator +legislatures +leonine +lethally +letters +levitates +lexicographer +liability +liable +libeling +liberalization +liberally +libertine +librettos +licensed +licentiate +lieutenancy +lifespan +lightning +likeliest +likenesses +limelight +limousines +limpets +linage +linearly +linefeed +lingeringly +liniments +linnets +lionized +lisped +literals +livens +livestock +loader +loading +lobbed +lobbying +localizes +locket +locking +locust +lodges +logarithm +logbook +loggers +logistics +lollygags +longed +longingly +looked +looming +loonies +lordly +loudspeaker +loveable +loveliness +lowers +loyalty +lubricator +lucidness +lugubriously +lumberjacks +luminously +lunched +luncheon +lunges +luring +lusted +luxuriantly +luxury +lyceum +macing +macintoshes +macroscopic +mademoiselle +madras +madrigals +maggots +magnanimous +magnesia +magnetize +magnified +magnolia +magnolias +maharaja +maharajas +maharanee +maiming +majestic +making +maladjusted +malfunctions +mallows +manhandled +maniacal +manicured +manning +mantlepieces +marcher +margin +marginally +marigold +marihuana +marinade +marketed +marketer +marmalade +marquess +marquis +martini +martinis +marveling +marvels +masculines +mashed +masochism +masochists +massiveness +material +materialism +matures +maturities +matzot +maximal +mayors +mealier +mealtime +mealtimes +meandered +meanly +measurably +meatballs +mediaeval +mediation +meeting +mellower +meltdowns +member +members +membrane +menaces +menagerie +menopause +menorahs +meretricious +merganser +merged +merges +mermaids +merrymakers +metallurgical +metatarsals +meteorologist +methadon +method +mewing +miasma +microwaving +midmost +midterm +midyears +miffing +mightiest +migraines +migration +milepost +militia +milkman +milkshake +mimosas +mincing +mingled +minibike +minicomputers +minimalists +mining +minstrel +minstrels +misadventures +misanthropes +misapply +misconceive +misconstrue +miscounted +miscued +misdealt +misdiagnosis +misdoings +misfires +misfitted +misguiding +mishap +misidentified +misinterpretation +mislays +misogynist +misogynists +misprints +misrepresent +misrepresentations +missal +mistaken +mistiness +mistreating +misuses +mixture +mizzen +moccasin +mockingly +modernism +modernity +modernize +modulates +moisturizes +molder +moldier +molecular +molluscs +mollycoddles +mommas +monetary +moneymakers +mongoose +monicker +moniker +monkeyed +monkeys +monoliths +monolog +monopolizes +monosyllabic +monosyllables +monotony +moochers +mooned +moonshine +moonshines +moralizes +moratoriums +mordants +mortgagees +morticing +mortifying +moseys +mosques +mothballed +mothering +motherland +motivate +motivates +motivating +motocross +motorcar +motorists +motorized +motormouth +mountaineer +mountainside +mountebanks +mounted +mourned +mournful +mousses +mouthing +moving +muckiest +mucking +mudslingers +muezzin +muffin +muftis +muggers +muggiest +mulligatawny +multiplexors +multiplication +multiprocessing +multitude +multivariate +mumblers +munching +muralist +murders +murkiest +mushiness +mushing +musicology +muskmelon +mustered +mutability +muteness +muting +muzzled +myrtles +napalmed +narcissuses +narrating +nasalizing +national +nattily +naturalize +naturalizes +naturally +natures +navigate +necessaries +necessity +neckerchiefs +necklace +needles +nefarious +negligently +negligs +neighboring +nephew +nerves +neurotic +neuter +newscasters +newspapers +newsreel +nicknames +nicknaming +niftier +nightclubbing +nightingale +nightmare +nightshirt +nimblest +ninety +nobility +nobleness +nobler +noiselessly +noising +nominations +nonagenarians +nonempty +nonevents +nonexistence +nonobjective +nonpayment +nonrenewable +nonwhites +normalize +normalizing +northeast +northeastern +northeastward +notwithstanding +nuggets +nullify +numerating +numeric +nursed +nutmeats +nutted +objection +obscene +obscurest +observable +observing +obsessive +obstacle +obstruct +obtrudes +occlude +occluded +occluding +occupational +occurrences +oceanographic +oddity +odious +odometers +offenders +offends +offensiveness +offices +officials +officiate +offing +offsetting +offshoots +oilfields +oleanders +omelette +omnibus +omnivores +onlooker +operetta +oppresses +orbital +ordaining +orderliness +orientating +ornaments +ornate +ornerier +ornithologists +orphaning +orthodontics +orthogonal +oscillating +oscillators +osteoporosis +ounces +ousted +outbidding +outdistance +outfitter +outgoes +outlays +outnumbered +outnumbers +outplacement +outputted +outrageous +outriders +outselling +outshining +outsiders +outstretched +outvotes +outwitted +overacts +overate +overawes +overbearing +overcharged +overcome +overcompensation +overdose +overdoses +overexposed +overexposes +overexposing +overexposure +overextending +overheating +overpays +overpower +overpowered +overseer +oversees +overshoe +oversimplifications +overstay +overtaxes +overture +overweening +overwhelmingly +overworking +ownership +pachyderm +pacifier +packet +padding +padres +pageant +paintbrush +paintwork +pajamas +paleness +palest +palindromes +palladium +pallbearers +pallets +palmiest +palsying +pancake +pancreatic +pandemics +pandering +panelled +panoplies +panoply +pantheons +panthers +pantomimed +pantsuits +papergirls +parable +parachuting +paradoxically +paragraphing +parakeets +parallelisms +paralytic +parameter +parameters +paramilitary +paranoid +paranormal +paraphrasing +paraplegia +parboiling +pardoning +parented +parked +parkway +parlays +parquetry +parries +parrying +partake +particularity +particularization +particularizes +particularizing +partisanship +partnered +partook +passageway +passageways +passel +password +pasteurization +patents +paternal +patinae +patriarchy +patricides +patriot +patriotically +patronize +patronizingly +paunches +peaceably +peccaries +peccary +pectorals +peculiarly +pedagogical +peddler +peddlers +pedlars +peerless +pellets +pendulous +peninsula +penlites +pennies +pensioner +pensioning +pentagon +penthouse +penury +peppercorn +peppermint +pepperonis +peppier +perceivable +peregrinations +perennially +perfecter +perfectionists +perforation +performer +perfunctory +perilous +periodical +periodicals +peripatetic +peritoneum +perking +peroxided +perpetuate +perplexities +perseveres +personage +personify +persons +persuasively +perter +pervaded +petiole +pettifogged +petunia +petunias +phalanx +phalluses +pharmacopeia +philanderer +philter +phoebe +phonetics +phonic +photocopied +photographic +photography +phraseology +physicked +piazze +piccalilli +pickaxes +picketing +pickled +piddling +piffle +piggish +piggishness +piggyback +pilferer +pilling +pillory +pinnacle +pinochle +pinprick +pintos +pipped +piquing +pirouetted +pisses +pistachio +pistil +pistils +pistons +pitched +piteous +pitiable +pitiless +pitons +pivotal +pixels +pizzerias +placeholder +placentas +placer +placidly +plagiarism +plagiarizing +plague +plainclothesman +plainest +plaintiff +plaits +playgoer +playhouse +playpen +pleads +pleasing +pleasure +plenipotentiaries +plenitudes +plexus +pliancy +plinths +plodding +plowman +plowshare +pluming +plunderers +plunking +plural +plutocracies +plutocratic +plutocrats +plutonium +plying +pockmarked +poetically +poised +pokeys +police +poliomyelitis +politeness +pollinate +polliwog +pollute +pollywogs +polyhedra +polythene +pommel +poniards +pooched +poodle +portables +portage +portaged +portent +portly +portmanteaus +portray +portrays +positioned +positioning +possession +possessions +possessively +postdoc +postmarking +postmodern +postpaid +postscript +posture +postures +potboilers +potholders +potpies +potshots +pouches +poultices +powered +powerhouse +powwow +powwowing +practicability +practicably +practical +pranced +pranksters +preachier +precluded +precludes +preconceived +preconceives +predicates +predication +preexists +preferable +prefixed +prejudices +preliminary +preludes +prematurely +premeditating +premiere +premising +preordaining +prepackage +prepositions +prepossessed +prepossessing +preppie +prequels +presage +presaged +presages +prescience +presences +presentations +presents +presidents +pressing +presume +presupposes +preterit +preterits +prettified +prettifies +prevalence +prevaricated +prevaricating +preventing +prided +primly +primmer +printer +prisms +privater +privatization +privatized +privileges +prizefighting +proclivities +proclivity +procrastination +procreates +proctoring +procured +procuring +prodding +producing +professorships +profiling +profiteered +profiteering +profusely +profusions +programing +programmer +programmers +progression +prohibitory +projectile +proliferating +prolongations +promiscuous +promptest +prompts +pronouncements +proofs +propagandists +propagandized +propellent +propeller +propelling +propensity +properly +prophesied +prophylactics +propounds +prorates +proscriptions +proselytes +proselytizing +prosperous +protectiveness +protruding +protuberances +providence +provisional +provocatively +psyched +psychobabble +psychologists +psychopaths +psychotics +ptomaine +publicity +puddled +puffins +pullout +pulpits +pulsars +pulsate +pumice +pumpkin +pupils +pureed +purism +puritanical +purplest +pyromaniacs +quadraphonic +quadruplicating +quahogs +qualification +queenlier +queerer +questionable +questioned +questions +quicker +quicklime +quiescence +quietus +quilted +quintessences +quintuple +quirking +quitter +rabbited +raccoon +racial +racism +racketeered +radiotelephone +radium +raffles +raggedest +raggediest +ragtags +raining +rainmakers +raised +ramrodded +rancid +rancorous +ransom +ranting +rapport +rasher +raspberry +rations +rattlesnake +rattletraps +rattlings +razors +reachable +reaches +reactions +readied +reaffirms +reappoints +reason +reasons +reassemble +reassembles +reassured +rebind +rebuke +rebukes +recapitulating +recede +receipt +receives +recently +recess +recessions +recheck +rechecks +recipe +reciprocating +recoil +recoils +recompensed +recompenses +reconcile +reconciles +reconsidering +reconvening +recorder +recording +recount +recovering +recriminated +recriminating +recruiters +recruitment +recursion +redcoat +reddened +redeemed +redoes +redouble +redoubles +redounds +redrawn +redraws +reduction +redwood +reeducation +reefing +reelect +reenforced +reenlist +reexamine +refines +refocussed +refrigerates +refrigerating +refuelled +refurbish +refuse +refutation +regales +regency +regenerates +regimenting +regularized +rehabbed +rehashed +rehashes +rehired +reimburses +reimposed +reiterating +relabelling +relapsed +release +relentlessness +reliance +relocation +reluctant +remade +remand +remarks +remedied +remedies +remiss +remodel +remodels +remotely +remounting +removal +remunerating +renders +rendition +renegade +renegaded +renews +rental +rented +reoccupying +repairable +repeal +repeatedly +repellants +repellents +rephrasing +replacing +replaying +replete +replying +reposes +reprehends +represent +representation +repressed +reprieve +reprieving +reprise +reprises +reproofing +reptiles +repulsing +repulsiveness +requested +requirement +reread +resale +reschedule +reschedules +rescission +resentfully +resents +reserves +reside +residents +residue +resigns +resinous +resolves +resonates +restatement +restaurateur +restaurateurs +resulted +resumed +resurrecting +resuscitate +retards +retirements +retrain +retread +retried +retrievable +retrieving +retrogression +retrorocket +retrospectives +returns +revels +revere +reverencing +reversals +reversing +reviling +revise +revisiting +revivify +revocable +revolt +revolutionize +reworded +rewording +rework +rheumatic +ricocheting +riders +ridiculous +rifling +rightful +rigidly +rigors +riposted +ripping +ripsaw +ritzier +rivaling +rivalling +rivers +roadblocked +roadkill +rocking +roguery +rolled +romanticizes +rooter +rootless +rosier +rostra +rotates +rotational +rotundas +rotundity +roughhouse +roughing +roughnecked +roughs +roughshod +rounder +rouses +roustabouts +routed +rowdier +rowdies +rowdiest +royalty +rubber +rubbished +rubbishing +rubbishy +rubicund +rubier +rudimentary +ruggeder +ruggedest +rumbas +rumors +rundowns +rushes +russet +rustics +rustier +rustiest +rusting +rustle +rustproofs +saboteur +sachems +sacked +sacrilege +sadistically +saffrons +sailors +salads +salaries +salesmanship +salesmen +salines +salmonella +saloons +saltcellar +saltines +saluting +salvage +sanctimonious +sanctions +sandblaster +sandstone +sanserif +sarcastically +sartorial +sassafras +satinwood +satirizing +satisfies +sauted +savoriest +sawing +scalloped +scalpel +scalpers +scandalously +scanners +scapegoated +scapulae +scarecrow +scariest +scarify +scatted +scatterbrain +scavengers +schemes +scheming +scherzos +schoolbook +schoolboy +schoolmate +schoolroom +schoolrooms +scolloping +scorch +scorcher +scoreless +scoutmaster +scowls +scrabbled +scraggly +scrappiest +screwdrivers +screwy +scrimps +scrimshaw +scrimshawing +scrimshaws +scrolled +scrounging +scrupulously +scrutinized +seabeds +seaboard +seaman +seaming +seamless +seamstress +searching +seascapes +seasonally +seaweed +seaworthy +seclude +secluded +secularization +seductively +seedier +seediness +seeped +seesawed +segment +segmenting +seizure +semaphored +semiautomatic +semifinalists +semifinals +seminar +senses +sensibly +sensitize +sensually +sepals +separation +seraphic +serial +serialize +seriousness +serpent +served +service +serviceman +servos +sessions +settle +sevenths +seventieths +severely +sexagenarian +sexiest +shackle +shadowbox +shadowboxed +shakes +shakeups +shallowest +shallows +shambling +shammies +shards +sharking +shaven +shears +sheathe +shebangs +sheered +sheerest +shellac +sheltering +shelved +sheriffs +shillelagh +shilling +shimmy +shingled +shinier +shinning +shirking +shirks +shirttails +shittier +shittiest +shivers +shleps +shocks +shoddier +shoddily +shoehorned +shoehorning +shoeing +shoelace +shoeshine +shooters +shooting +shoplifter +shopped +shored +shoring +shortsightedness +should +shovelfuls +shovelled +showboated +shrewish +shucked +shuffles +shutterbug +shuttlecock +shyster +sickly +sideshows +siestas +signal +signifying +signposts +silencer +silences +silent +silliness +silverfishes +silvery +simplification +sinful +singletons +sinking +sirocco +sitars +sixteenth +sizable +skewering +skimpy +skirts +skylines +slaloming +slammed +slashed +slaughtering +slavish +sleazy +sledge +sledgehammer +sledges +sleepyhead +sleeted +sleeve +slenderest +slenderizes +slicer +slimmed +slinkiest +slipcover +sluing +slumbering +slumping +slurring +smacking +smallest +smelly +smidgen +smites +smoggiest +smokiest +smuttier +snickers +snider +sniped +snipes +snivelled +snored +snorers +snowballed +snowdrift +snowdrop +snowflake +snowmobile +snuffed +soaping +soapstone +sobered +sociability +socialite +sociologist +sociology +softeners +sojourn +soldierly +solely +solemn +solicitor +solicitous +solider +soliloquies +solitaire +somewhere +sophisticates +sophisticating +sorceress +sordidly +sorrow +soulfully +sourer +southbound +southwestern +spacesuits +spadeful +spades +spandex +spanking +speaking +specialization +spectator +spectroscopic +speculated +speculating +spellbinder +spelled +spellings +spendthrift +spermicides +spiking +spills +spillways +spinach +spindlier +spinier +spiniest +spinster +splashdowns +spleen +splice +splines +spoiled +spoilt +spokespeople +spokesperson +sponges +spooked +spoonfuls +sportscast +sportsmanship +spotter +sprawls +sprayed +sprightly +spring +sprinting +sprucer +sprucest +spryer +spryness +spurring +spurted +spurts +sputters +squash +squashy +squeezers +squint +stabilizer +stabled +stables +stagings +staircase +staircases +stalkings +stallions +stamping +stanch +stands +staphylococcus +stapler +starchiest +stared +starlings +stated +statehood +stateroom +statesmanlike +statue +statute +stauncher +staunchly +stayed +steeples +stemming +stencil +steppingstones +stepsisters +sterilized +sterner +stewarded +stewardess +stickpins +stiffen +stiffeners +stilettoes +stilettos +stilled +stimulated +stinkers +stockier +stodgiest +stomach +stomping +stoneware +stoniest +stooping +stoppage +storehouses +storey +straddled +straightforward +straightforwardly +straitened +strange +strangers +strangleholds +strapping +stratagems +strategic +strategist +streakiest +streetlights +strenuous +strews +stridden +strikeout +stringiest +stripe +strongboxes +structure +strudel +struggled +studies +sturdiest +stutterer +stylist +subbasement +subclass +subcompact +subdivided +subjugate +subjunctives +subleasing +submarines +submissive +subornation +subplot +subpoena +subscribed +substations +subsumed +subterranean +subtitled +subtle +subtractions +suburbanites +suburbans +suburbs +successions +succinct +suffering +suffixed +suffocate +suffocation +suffragettes +suicides +suitcases +suites +sulphured +sultrier +summations +sunglasses +sunroof +suntan +superabundances +superber +supercharged +supercharger +superhighways +superpowers +superscripts +supervision +supplant +suppositories +suppository +surfaced +surfacing +surges +surgical +surmised +surmises +surmounting +surplice +surprises +surrendered +surrogate +surround +survived +suspends +suspense +suspension +suspensions +swains +swapped +swarming +swarthiest +swashes +swatted +sweatshop +swellings +swelter +swelters +swerve +swiftest +swinger +switchbacks +switchboards +swords +sycophant +syllabify +syllabus +symbioses +symbolically +symmetrical +symptoms +synagogs +synched +syndromes +synopses +synthesizer +synthetic +syphilis +syphoning +tabloids +tackles +tackling +tadpoles +tailspin +tallness +tameable +tangential +tangos +tanned +tapioca +tariffs +tarnish +tarots +tarpons +tastelessly +tatters +tattles +tattletales +tattoo +taxonomic +taxying +teabag +technicality +technological +technology +teleconference +telegraphy +telemetries +telepathically +telepathy +telephony +telephoto +telethon +televise +televised +telexes +temperas +tempestuous +template +templates +temporary +temporized +temporizes +tempted +tenable +tenacious +tenancies +tenancy +tenoning +tenser +tensing +tentacles +tenuous +terminally +termly +terrorizes +testier +testiness +tetrahedrons +textbook +thankless +theatrical +theologies +theorems +theoretic +therapists +thereabouts +therein +therewith +thesis +thickened +thimbleful +thinker +thinning +thirtieth +thistle +thoraces +thorny +thoughtful +thoughtfully +thraldom +thread +threes +throes +throne +thronging +throttles +thunder +thwarted +tiddlywinks +tidying +tieing +tightened +tightropes +timbering +timekeeper +timetable +timetabled +timing +tinkle +tinselled +tinsmiths +tipper +tippler +tireless +tiring +titillating +titled +toadstool +toasts +toddler +toddling +toffees +toileted +tollgate +tomahawked +tomatoes +tombstone +topics +topographies +topped +topsides +torpedoes +totter +tougher +toughly +towheaded +townsmen +towpaths +toxemia +tracheas +tracing +tractors +trademarking +tradesmen +trammed +trampling +trampoline +trampolines +tranquilizer +tranquillizing +transaction +transcript +transferable +transfers +transformed +translation +translators +transpired +transponder +trapezoids +trappable +treacheries +treatise +treaty +trekking +trembles +tremulous +trenches +trepidation +triage +tribalism +tribunals +trickery +trickling +trimming +trinity +trisect +triter +trivial +trochee +troubadours +troublemaker +trounced +troupes +trouser +troweled +truancy +truffle +trusting +tuberculous +tuberous +tubular +tufted +tufting +tumbrils +tunnies +turnabout +turnpikes +turquoises +turtledoves +turtlenecks +tussle +tussocks +tutorial +twaddles +tweaking +tweediest +twelfth +twenties +twerps +twigging +twisted +twittered +twitting +typecasts +tyrannical +tyrannosauruses +tzarina +ubiquity +ugliness +umbilici +umbilicus +umpiring +umpteen +unanimous +unaware +unbeliever +unbend +unbolts +unbounded +unbreakable +unbuckle +unburden +unbutton +uncensored +uncharacteristic +uncleaner +unclothe +unclothed +unconvincingly +uncooperative +uncork +uncouth +uncover +uncritical +undefined +underachieved +underachieving +underacts +undercurrents +underdogs +underexposes +underfeed +underlines +underlining +underpay +undersecretary +underside +understandable +undulated +uneasily +uneconomic +unenthusiastic +unexpected +unexpectedly +unfetter +unfilled +unfurled +unfurling +ungainlier +unguents +unhealthier +unhelpful +unhinge +unified +uniforming +uninitialized +uninstaller +unjustly +unlatched +unlatching +unleashes +unleashing +unmake +unmasked +unmentionables +unmodified +unobserved +unopposed +unpaid +unpleasantness +unpredictability +unprepared +unprompted +unprotected +unprovoked +unquenchable +unreadier +unreasonableness +unrecognizable +unreliability +unremarkable +unrewarding +unruliness +unscrambling +unseeing +unshaven +unsightliness +unskilled +unsnapping +unsnaps +unsubscribing +unsuccessful +untaught +untiringly +unused +unveil +upfront +upholstered +uprising +uproariously +uranium +urbaner +urbanized +urbanizing +uselessness +usurpation +usurpers +uterine +utters +uvulars +vacuity +vacuums +vagrant +vainglorious +valiantly +validates +valleys +vantages +vaporizing +variations +vatting +vaulter +vaulters +vaunts +vegetarianism +vendor +veneer +venial +ventured +veracity +verbalize +verbalized +verbalizing +verily +vermin +vexatious +victimizing +videodisc +viewed +viewings +vigilante +vilified +villainous +villein +vintages +violate +virtually +visaed +visage +viscountess +visioning +visual +vitiating +viticulture +vocalization +vocations +vocative +vociferates +voiding +voracious +vouchsafing +waiver +waiving +walked +wallboard +wallflowers +walrus +wampum +warbled +warehousing +warmly +warned +warping +warrants +warrens +warthogs +washable +washbowl +washcloths +wasters +wasting +watchfulness +waterfall +wattled +waveform +waxwings +weakens +weaned +webbed +wedding +weeder +weediest +weeper +weepiest +weighed +weirdness +welders +wellsprings +westerners +wetted +wheelbarrows +whetted +whiled +whiling +whimsical +whiniest +whining +whinnying +whipping +whirligig +whirlwinds +whispers +whistling +whitecaps +whitening +whoever +wholesalers +whorls +wickers +wickets +widths +wifelier +wiggles +wildfowl +wiling +willfully +windjammer +windowsills +windscreen +winged +wingless +wingtip +winterier +wiseacres +wisecrack +wisecracking +wishes +wispier +wispiest +withering +withers +withstanding +wizards +wobbled +woefuller +wolfhound +wondrous +wonted +woodier +woodpile +woofer +woofing +woolen +woolies +workings +workmanlike +wormier +worryings +worshipers +worshipping +worthy +wounded +wreathes +wrenched +wrested +wrestler +wrinkling +writer +wrongheadedly +yachted +yammer +yammered +yawing +yearlings +yelled +yessing +yipping +yodelers +yummiest +zealously +zephyr +zeroing +zestfully +zigzags +zircons +zodiac +zoological diff --git a/devel-server.py b/devel-server.py index 410e109..0f15dfd 100755 --- a/devel-server.py +++ b/devel-server.py @@ -1,5 +1,6 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 +import cgi import glob import http.server import mistune @@ -7,8 +8,19 @@ import os import pathlib import puzzles import socketserver +import sys +import traceback + +try: + from http.server import HTTPStatus +except ImportError: + class HTTPStatus: + NOT_FOUND = 404 + OK = 200 + +# XXX: This will eventually cause a problem. Do something more clever here. +seed = 1 -HTTPStatus = http.server.HTTPStatus def page(title, body): return """ @@ -24,6 +36,7 @@ def page(title, body): """.format(title, body) + def mdpage(body): try: title, _ = body.split('\n', 1) @@ -37,7 +50,21 @@ def mdpage(body): class ThreadingServer(socketserver.ThreadingMixIn, http.server.HTTPServer): pass -class MothHandler(http.server.CGIHTTPRequestHandler): + +class MothHandler(http.server.SimpleHTTPRequestHandler): + def handle_one_request(self): + try: + super().handle_one_request() + except: + tbtype, value, tb = sys.exc_info() + tblist = traceback.format_tb(tb, None) + traceback.format_exception_only(tbtype, value) + page = ("# Traceback (most recent call last)\n" + + " " + + " ".join(tblist[:-1]) + + tblist[-1]) + self.serve_md(page) + + def do_GET(self): if self.path == "/": self.serve_front() @@ -75,45 +102,76 @@ you are a fool. body = [] path = self.path.rstrip('/') parts = path.split("/") + #raise ValueError(parts) if len(parts) < 3: # List all categories body.append("# Puzzle Categories") for i in glob.glob(os.path.join("puzzles", "*", "")): body.append("* [{}](/{})".format(i, i)) - elif len(parts) == 3: + self.serve_md('\n'.join(body)) + return + + fpath = os.path.join("puzzles", parts[2]) + cat = puzzles.Category(fpath, seed) + if len(parts) == 3: # List all point values in a category body.append("# Puzzles in category `{}`".format(parts[2])) - puzz = [] - for i in glob.glob(os.path.join("puzzles", parts[2], "*.moth")): - base = os.path.basename(i) - root, _ = os.path.splitext(base) - points = int(root) - puzz.append(points) - for puzzle in sorted(puzz): - body.append("* [puzzles/{cat}/{points}](/puzzles/{cat}/{points}/)".format(cat=parts[2], points=puzzle)) - elif len(parts) == 4: + for points in cat.pointvals: + body.append("* [puzzles/{cat}/{points}](/puzzles/{cat}/{points}/)".format(cat=parts[2], points=points)) + self.serve_md('\n'.join(body)) + return + + pzl = cat.puzzle(int(parts[3])) + if len(parts) == 4: body.append("# {} puzzle {}".format(parts[2], parts[3])) - with open("puzzles/{}/{}.moth".format(parts[2], parts[3])) as f: - p = puzzles.Puzzle(f) - body.append("* Author: `{}`".format(p.fields.get("author"))) - body.append("* Summary: `{}`".format(p.fields.get("summary"))) + body.append("* Author: `{}`".format(pzl['author'])) + body.append("* Summary: `{}`".format(pzl['summary'])) body.append('') body.append("## Body") - body.append(p.body) - body.append("## Answers:") - for a in p.answers: + body.append(pzl.body) + body.append("## Answers") + for a in pzl['answer']: body.append("* `{}`".format(a)) body.append("") + body.append("## Files") + for pzl_file in pzl['files']: + body.append("* [puzzles/{cat}/{points}/{filename}]({filename})" + .format(cat=parts[2], points=pzl.points, filename=pzl_file)) + self.serve_md('\n'.join(body)) + return + elif len(parts) == 5: + try: + self.serve_puzzle_file(pzl['files'][parts[4]]) + except KeyError: + self.send_error(HTTPStatus.NOT_FOUND, "File not found") + return else: body.append("# Not Implemented Yet") - self.serve_md('\n'.join(body)) + self.serve_md('\n'.join(body)) + + CHUNK_SIZE = 4096 + def serve_puzzle_file(self, file): + """Serve a PuzzleFile object.""" + self.send_response(HTTPStatus.OK) + self.send_header("Content-type", "application/octet-stream") + self.send_header('Content-Disposition', 'attachment; filename="{}"'.format(file.name)) + if file.path is not None: + fs = os.stat(file.path) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + + # We're using application/octet stream, so we can send the raw bytes. + self.end_headers() + chunk = file.handle.read(self.CHUNK_SIZE) + while chunk: + self.wfile.write(chunk) + chunk = file.handle.read(self.CHUNK_SIZE) def serve_file(self): if self.path.endswith(".md"): self.serve_md() else: super().do_GET() - + def serve_md(self, text=None): fspathstr = self.translate_path(self.path) fspath = pathlib.Path(fspathstr) @@ -125,8 +183,9 @@ you are a fool. return None content = mdpage(text) - self.send_response(http.server.HTTPStatus.OK) - self.send_header("Content-type", "text/html; encoding=utf-8") + self.send_response(HTTPStatus.OK) + + self.send_header("Content-type", "text/html; charset=utf-8") self.send_header("Content-Length", len(content)) try: fs = fspath.stat() @@ -136,9 +195,10 @@ you are a fool. self.end_headers() self.wfile.write(content.encode('utf-8')) -def run(address=('', 8080)): + +def run(address=('localhost', 8080)): httpd = ThreadingServer(address, MothHandler) - print("=== Listening on http://{}:{}/".format(address[0] or "localhost", address[1])) + print("=== Listening on http://{}:{}/".format(address[0], address[1])) httpd.serve_forever() if __name__ == '__main__': diff --git a/mistune.py b/mistune.py index c0f976d..a81c4c1 100644 --- a/mistune.py +++ b/mistune.py @@ -335,7 +335,7 @@ class BlockLexer(object): rest = len(item) if i != length - 1 and rest: - _next = item[rest-1] == '\n' + _next = item[rest - 1] == '\n' if not loose: loose = _next diff --git a/puzzles.py b/puzzles.py index e2c79f5..df18c0c 100644 --- a/puzzles.py +++ b/puzzles.py @@ -1,13 +1,14 @@ #!/usr/bin/python3 -import hmac -import base64 import argparse +from collections import defaultdict, namedtuple import glob -import json -import os +import hashlib +from importlib.machinery import SourceFileLoader import mistune +import os import random +import tempfile messageChars = b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -17,13 +18,129 @@ def djb2hash(buf): h = ((h * 33) + c) & 0xffffffff return h -class Puzzle: - def __init__(self, stream): - self.message = bytes(random.choice(messageChars) for i in range(20)) - self.fields = {} - self.answers = [] - self.hashes = [] +# We use a named tuple rather than a full class, because any random name generation has +# to be done with Puzzle's random number generator, and it's cleaner to not pass that around. +PuzzleFile = namedtuple('PuzzleFile', ['path', 'handle', 'name', 'visible']) +PuzzleFile.__doc__ = """A file associated with a puzzle. + path: The path to the original input file. May be None (when this is created from a file handle + and there is no original input. + handle: A File-like object set to read the file from. You should be able to read straight + from it without having to seek to the beginning of the file. + name: The name of the output file. + visible: A boolean indicating whether this file should visible to the user. If False, + the file is still expected to be accessible, but it's path must be known + (or figured out) to retrieve it.""" + +class Puzzle: + + KNOWN_KEYS = [ + 'answer', + 'author', + 'file', + 'hidden', + 'name' + 'resource', + 'summary' + ] + REQUIRED_KEYS = [ + 'author', + 'answer', + ] + SINGULAR_KEYS = [ + 'name' + ] + + # Get a big list of clean words for our answer file. + ANSWER_WORDS = [w.strip() for w in open(os.path.join(os.path.dirname(__file__), + 'answer_words.txt'))] + + def __init__(self, category_seed, path=None, points=None): + """A MOTH Puzzle. + :param category_seed: A byte string to use as a seed for random numbers for this puzzle. + It is combined with the puzzle points. + :param path: An optional path to a puzzle directory. The point value for the puzzle is taken + from the puzzle directories name (it must be an integer greater than zero). + Within this directory, we expect: + (optional) A puzzle.moth file in RFC2822 format. The puzzle will get its attributes + from the headers, and the body will be the puzzle description in + Markdown format. + (optional) A puzzle.py file. This is expected to have a callable called make + that takes a single positional argument (this puzzle object). + This callable can then do whatever it needs to with this object. + :param points: The point value of the puzzle. Mutually exclusive with path. + If neither of the above are given, the point value for the puzzle will have to + be set at instantiation. + + For puzzle attributes, this class acts like a dictionary that in most cases assigns + always returns a list. Certain keys, however behave differently: + - Keys in Puzzle.SINGULAR_KEYS can only have one value, and writing to these overwrites + that value. + - The keys 'hidden', 'file', and 'resource' all create a new PuzzleFile object that + gets added under the 'files' key. + - The 'answer' also adds a new hash under the the 'hash' key. + """ + + super().__init__() + + if (points is None and path is None) or (points is not None and path is not None): + raise ValueError("Either points or path must be set, but not both.") + + self._dict = defaultdict(lambda: []) + if os.path.isdir(path): + self._puzzle_dir = path + else: + self._puzzle_dir = None + self.message = bytes(random.choice(messageChars) for i in range(20)) + self.body = '' + + # This defaults to a dict, not a list like most things + self._dict['files'] = {} + + # A list of temporary files we've created that will need to be deleted. + self._temp_files = [] + if path is not None: + if not os.path.isdir(path): + raise ValueError("No such directory: {}".format(path)) + + pathname = os.path.split(path)[-1] + try: + self.points = int(pathname) + except ValueError: + raise ValueError("Directory name must be a point value: {}".format(path)) + elif points is not None: + self.points = points + + self._seed = category_seed * self.points + self.rand = random.Random(self._seed) + + if path is not None: + files = os.listdir(path) + + if 'puzzle.moth' in files: + self._read_config(open(os.path.join(path, 'puzzle.moth'))) + + if 'puzzle.py' in files: + # Good Lord this is dangerous as fuck. + loader = SourceFileLoader('puzzle_mod', os.path.join(path, 'puzzle.py')) + puzzle_mod = loader.load_module() + if hasattr(puzzle_mod, 'make'): + self.body = '# `puzzle.body` was not set by the `make` function' + puzzle_mod.make(self) + else: + self.body = '# `puzzle.py` does not define a `make` function' + + def cleanup(self): + """Cleanup any outstanding temporary files.""" + for path in self._temp_files: + if os.path.exists(path): + try: + os.unlink(path) + except OSError: + pass + + def _read_config(self, stream): + """Read a configuration file (ISO 2822)""" body = [] header = True for line in stream: @@ -33,40 +150,139 @@ class Puzzle: header = False continue key, val = line.split(':', 1) - key = key.lower() val = val.strip() - self._add_field(key, val) + self[key] = val else: body.append(line) self.body = ''.join(body) - def _add_field(self, key, val): + def random_hash(self): + """Create a random hash from our number generator suitable for use as a filename.""" + return hashlib.sha1(str(self.rand.random()).encode('ascii')).digest() + + def _puzzle_file(self, path, name, visible=True): + """Make a puzzle file instance for the given file. To add files as you would in the config + file (to 'file', 'hidden', or 'resource', simply assign to that keyword in the object: + puzzle['file'] = 'some_file.txt' + puzzle['hidden'] = 'some_hidden_file.txt' + puzzle['resource'] = 'some_file_in_the_category_resource_directory_omg_long_name.txt' + :param path: The path to the file + :param name: The name of the file. If set to None, the published file will have + a random hash as a name and have visible set to False. + :return: + """ + + # Make sure it actually exists. + if not os.path.exists(path): + raise ValueError("Included file {} does not exist.".format(path)) + + file = open(path, 'rb') + + return PuzzleFile(path=path, handle=file, name=name, visible=visible) + + def make_temp_file(self, name=None, visible=True): + """Get a file object for adding dynamically generated data to the puzzle. When you're + done with this file, flush it, but don't close it. + :param name: The name of the file for links within the puzzle. If this is None, a name + will be generated for you. + :param visible: Whether or not the file will be visible to the user. + :return: A file object for writing + """ + + if name is None: + name = self.random_hash() + + file = tempfile.NamedTemporaryFile(mode='w+b', delete=False) + file_read = open(file.name, 'rb') + + self._dict['files'][name] = PuzzleFile(path=file.name, handle=file_read, + name=name, visible=visible) + + return file + + def make_handle_file(self, handle, name, visible=True): + """Add a file to the puzzle from a file handle. + :param handle: A file object or equivalent. + :param name: The name of the file in the final puzzle. + :param visible: Whether or not it's visible. + :return: None + """ + + def __setitem__(self, key, value): + """Set a value for this puzzle, as if it were set in the config file. Most values default + being added to a list. Files (regardless of type) go in a dict under ['files']. Keys + in Puzzle.SINGULAR_KEYS are single values that get overwritten with subsequent assignments. + Only keys in Puzzle.KNOWN_KEYS are accepted. + :param key: + :param value: + :return: + """ + + key = key.lower() + + if key in ('file', 'resource', 'hidden') and self._puzzle_dir is None: + raise KeyError("Cannot set a puzzle file for single file puzzles.") + if key == 'answer': - h = djb2hash(val.encode('utf8')) - self.answers.append(val) - self.hashes.append(h) + # Handle adding answers to the puzzle + self._dict['hash'].append(djb2hash(value.encode('utf8'))) + self._dict['answer'].append(value) + elif key == 'file': + # Handle adding files to the puzzle + path = os.path.join(self._puzzle_dir, 'files', value) + self._dict['files'][value] = self._puzzle_file(path, value) + elif key == 'resource': + # Handle adding category files to the puzzle + path = os.path.join(self._puzzle_dir, '../res', value) + self._dict['files'].append(self._puzzle_file(path, value)) + elif key == 'hidden': + # Handle adding secret, 'hidden' files to the puzzle. + path = os.path.join(self._puzzle_dir, 'files', value) + name = self.random_hash() + self._dict['files'].append(self._puzzle_file(path, name, visible=False)) + elif key in self.SINGULAR_KEYS: + # These keys can only have one value + self._dict[key] = value + elif key in self.KNOWN_KEYS: + self._dict[key].append(value) else: - self.fields[key] = val + raise KeyError("Invalid Attribute: {}".format(key)) + + def __getitem__(self, item): + return self._dict[item.lower()] + + def make_answer(self, word_count, sep=' '): + """Generate and return a new answer. It's automatically added to the puzzle answer list. + :param int word_count: The number of words to include in the answer. + :param str|bytes sep: The word separator. + :returns: The answer string + """ + + answer = sep.join(self.rand.sample(self.ANSWER_WORDS, word_count)) + self['answer'] = answer + + return answer def htmlify(self): + """Format and return the markdown for the puzzle body.""" return mistune.markdown(self.body) def publish(self): obj = { - 'author': self.fields['author'], - 'hashes': self.hashes, + 'author': self['author'], + 'hashes': self['hashes'], 'body': self.htmlify(), } return obj def secrets(self): obj = { - 'answers': self.answers, - 'summary': self.fields['summary'], + 'answers': self['answers'], + 'summary': self['summary'], } return obj - -if __name__ == '__main__': + +if __name__ == '__main__': parser = argparse.ArgumentParser(description='Build a puzzle category') parser.add_argument('puzzledir', nargs='+', help='Directory of puzzle source') args = parser.parse_args() @@ -78,10 +294,29 @@ if __name__ == '__main__': filename = os.path.basename(puzzlePath) points, ext = os.path.splitext(filename) points = int(points) - puzzle = Puzzle(open(puzzlePath)) + puzzle = Puzzle(puzzlePath, "test") puzzles[points] = puzzle for points in sorted(puzzles): puzzle = puzzles[points] print(puzzle.secrets()) + +class Category: + def __init__(self, path, seed): + self.path = path + self.seed = seed + self.pointvals = [] + for fpath in glob.glob(os.path.join(path, "[0-9]*")): + pn = os.path.basename(fpath) + points = int(pn) + self.pointvals.append(points) + self.pointvals.sort() + + def puzzle(self, points): + path = os.path.join(self.path, str(points)) + return Puzzle(self.seed, path=path) + + def puzzles(self): + for points in self.pointvals: + yield self.puzzle(points) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3bf77b8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,8 @@ +[flake8] +# flake8 is an automated code formatting pedant. +# Use it, please. +# +# python3 -m flake8 . +# +ignore = E501 +exclude = .git \ No newline at end of file