diff --git a/README.md b/README.md index e666c2c..3676ca5 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Devel: ![](https://github.com/dirtbags/moth/workflows/Mothd%20Docker%20build/badge.svg?branch=devel) ![](https://github.com/dirtbags/moth/workflows/moth-devel%20Docker%20build/badge.svg?branch=devel) -This is a set of thingies to run our Monarch-Of-The-Hill contest, -which in the past has been called +Monarch Of The Hill (MOTH) is a puzzle server. +We (the authors) have used it for instructional and contest events called "Tracer FIRE", "Project 2", "HACK", @@ -23,152 +23,38 @@ and "Cyber Fire Foundry". Information about these events is at http://dirtbags.net/contest/ -This software serves up puzzles in a manner similar to Jeopardy. -It also tracks scores, -and comes with a JavaScript-based scoreboard to display team rankings. +A few things make MOTH different than other Capture The Flag server projects: + +* Once any team opens a puzzle, all teams can work on it (high fives to DC949/Orange County for this idea) +* No penalties for wrong answers +* No time-based point deductions (if you're faster, you get to answer more puzzles) +* No internal notion of ranking or score: it only stores an event log, and scoreboards parse it however they want +* All puzzles must be compiled to static content before it can be served up +* The server does very little: most functionality is in client-side JavaScript + +You can read more about why we made these decisions in [philosophy](doc/philosophy.md). -Running a Development Server -============================ - -To use example puzzles - - docker run --rm -it -p 8080:8080 dirtbags/moth-devel - -or, to use your own puzzles - - docker run --rm -it -p 8080:8080 -v /path/to/puzzles:/puzzles:ro dirtbags/moth-devel - -And point a browser to http://localhost:8080/ (or whatever host is running the server). - -The development server includes a number of Python libraries that we have found useful in writing puzzles. - -When you're ready to create your own puzzles, -read [the devel server documentation](doc/devel-server.md). - -Click the `[mb]` link by a puzzle category to compile and download a mothball that the production server can read. +Documentation +========== +* [Development](doc/development.md): The development server lets you create and test categories, and compile mothballs. +* [Getting Started](doc/getting-started.md): This guide will get you started with a production server. +* [Administration](doc/administration.md): How to set hours, and change setup. Running a Production Server =========================== - docker run --rm -it -p 8080:8080 -v /path/to/moth/state:/state -v /path/to/moth/balls:/mothballs:ro dirtbags/moth + docker run --rm -it -p 8080:8080 -v /path/to/moth/state:/state -v /path/to/moth/mothballs:/mothballs:ro dirtbags/moth You can be more fine-grained about directories, if you like. Inside the container, you need the following paths: * `/state` (rw) Where state is stored. Read [the overview](doc/overview.md) to learn what's what in here. * `/mothballs` (ro) Mothballs (puzzle bundles) as provided by the development server. -* `/resources` (ro) Overrides for built-in HTML/CSS resources. +* `/theme` (ro) Overrides for the built-in theme. - - - -Getting Started Developing -------------------------------- - -If you don't have a `puzzles` directory, -you can copy the example puzzles as a starting point: - - $ cp -r example-puzzles puzzles - -Then launch the development server: - - $ python3 devel/devel-server.py - -Point a web browser at http://localhost:8080/ -and start hacking on things in your `puzzles` directory. - -More on how the devel sever works in -[the devel server documentation](doc/devel-server.md) - - -Running A Production Server -==================== - -Run `dirtbags/moth` (Docker) or `mothd` (native). - -`mothd` assumes you're running a contest out of `/moth`. -For Docker, you'll need to bind-mount your actual directories -(`state`, `mothballs`, and optionally `resources`) into -`/moth/`. - -You can override any path with an option, -run `mothd -help` for usage. - - -State Directory -=============== - - -Pausing scoring -------------------- - -Create the file `state/disabled` -to pause scoring, -and remove it to resume. -You can use the Unix `touch` command to create the file: - - touch state/disabled - -When scoring is paused, -participants can still submit answers, -and the system will tell them whether the answer is correct. -As soon as you unpause, -all correctly-submitted answers will be scored. - - -Resetting an instance -------------------- - -Remove the file `state/initialized`, -and the server will zap everything. - - -Setting up custom team IDs -------------------- - -The file `state/teamids.txt` has all the team IDs, -one per line. -This defaults to all 4-digit natural numbers. -You can edit it to be whatever strings you like. - -We sometimes to set `teamids.txt` to a bunch of random 8-digit hex values: - - for i in $(seq 50); do od -x /dev/urandom | awk '{print $2 $3; exit;}'; done - -Remember that team IDs are essentially passwords. - - -Enabling offline/PWA mode -------------------- - -If the file `state/export_manifest` is found, the server will expose the -endpoint `/current_manifest.json?id=`. This endpoint will return -a list of all files, including static theme content and JSON and content -for currently-unlocked puzzles. This is used by the native PWA -implementation and `Cache` button on the index page to cache all of the -content necessary to display currently-open puzzles while offline. -Grading will be unavailable while offline. Some puzzles may not function -as expected while offline. A valid team ID must be provided. - -Mothball Directory -================== - -Installing puzzle categories -------------------- - -The development server will provide you with a `.mb` (mothball) file, -when you click the `[mb]` link next to a category. - -Just drop that file into the `mothballs` directory, -and the server will pick it up. - -If you remove a mothball, -the category will vanish, -but points scored in that category won't! - Contributing to MOTH ================== diff --git a/cmd/mothd/server_test.go b/cmd/mothd/server_test.go index ffc481f..2c5f732 100644 --- a/cmd/mothd/server_test.go +++ b/cmd/mothd/server_test.go @@ -24,7 +24,7 @@ func NewTestServer() *MothServer { afero.WriteFile(theme.Fs, "/index.html", []byte("index.html"), 0644) go theme.Maintain(TestMaintenanceInterval) - return NewMothServer(Configuration{Devel: true}, theme, state, puzzles) + return NewMothServer(Configuration{}, theme, state, puzzles) } func TestServer(t *testing.T) { @@ -47,7 +47,7 @@ func TestServer(t *testing.T) { es := handler.ExportState() if es.Config.Devel { - t.Error("Marked as development server") + t.Error("Marked as development server", es.Config) } if len(es.Puzzles) != 1 { t.Error("Puzzle categories wrong length") diff --git a/cmd/mothd/state.go b/cmd/mothd/state.go index 0f00d18..156e49c 100644 --- a/cmd/mothd/state.go +++ b/cmd/mothd/state.go @@ -17,7 +17,11 @@ import ( // DistinguishableChars are visually unambiguous glyphs. // People with mediocre handwriting could write these down unambiguously, // and they can be entered without holding down shift. -const DistinguishableChars = "234678abcdefhikmnpqrtwxyz=" +const DistinguishableChars = "34678abcdefhikmnpqrtwxy=" + +// RFC3339Space is a time layout which replaces 'T' with a space. +// This is also a valid RFC3339 format. +const RFC3339Space = "2006-01-02 15:04:05Z07:00" // State defines the current state of a MOTH instance. // We use the filesystem for synchronization between threads. @@ -50,11 +54,11 @@ func NewState(fs afero.Fs) *State { // updateEnabled checks a few things to see if this state directory is "enabled". func (s *State) updateEnabled() { nextEnabled := true - why := "`state/enabled` present, `state/hours` missing" + why := "`state/enabled` present, `state/hours.txt` missing" - if untilFile, err := s.Open("hours"); err == nil { + if untilFile, err := s.Open("hours.txt"); err == nil { defer untilFile.Close() - why = "`state/hours` present" + why = "`state/hours.txt` present" scanner := bufio.NewScanner(untilFile) for scanner.Scan() { @@ -74,10 +78,13 @@ func (s *State) updateEnabled() { case '#': continue default: - log.Println("Misformatted line in hours file") + log.Println("Misformatted line in hours.txt file") } line = strings.TrimSpace(line) until, err := time.Parse(time.RFC3339, line) + if err != nil { + until, err = time.Parse(RFC3339Space, line) + } if err != nil { log.Println("Suspended: Unparseable until date:", line) continue @@ -283,7 +290,7 @@ func (s *State) maybeInitialize() { // Remove any extant control and state files s.Remove("enabled") - s.Remove("hours") + s.Remove("hours.txt") s.Remove("points.log") s.Remove("messages.html") s.Remove("mothd.log") @@ -327,8 +334,8 @@ func (s *State) maybeInitialize() { f.Close() } - if f, err := s.Create("hours"); err == nil { - fmt.Fprintln(f, "# hours: when the contest is enabled") + if f, err := s.Create("hours.txt"); err == nil { + fmt.Fprintln(f, "# hours.txt: when the contest is enabled") fmt.Fprintln(f, "#") fmt.Fprintln(f, "# Enable: + timestamp") fmt.Fprintln(f, "# Disable: - timestamp") diff --git a/cmd/mothd/state_test.go b/cmd/mothd/state_test.go index 9e024de..cca8150 100644 --- a/cmd/mothd/state_test.go +++ b/cmd/mothd/state_test.go @@ -34,7 +34,7 @@ func TestState(t *testing.T) { mustExist("initialized") mustExist("enabled") - mustExist("hours") + mustExist("hours.txt") teamIDsBuf, err := afero.ReadFile(s.Fs, "teamids.txt") if err != nil { @@ -133,7 +133,7 @@ func TestStateDisabled(t *testing.T) { t.Error("Brand new state is disabled") } - hoursFile, err := s.Create("hours") + hoursFile, err := s.Create("hours.txt") if err != nil { t.Error(err) } @@ -146,7 +146,7 @@ func TestStateDisabled(t *testing.T) { t.Error("Disabling 1970-01-01") } - fmt.Fprintln(hoursFile, "+ 1970-01-01T01:01:01+05:00") + fmt.Fprintln(hoursFile, "+ 1970-01-01 01:01:01+05:00") hoursFile.Sync() s.refresh() if !s.Enabled { @@ -175,12 +175,12 @@ func TestStateDisabled(t *testing.T) { t.Error("Disabling 1980-01-01") } - if err := s.Remove("hours"); err != nil { + if err := s.Remove("hours.txt"); err != nil { t.Error(err) } s.refresh() if !s.Enabled { - t.Error("Removing `hours` disabled event") + t.Error("Removing `hours.txt` disabled event") } if err := s.Remove("enabled"); err != nil { diff --git a/devel/answer_words.txt b/devel/answer_words.txt deleted file mode 100644 index 7adc1a8..0000000 --- a/devel/answer_words.txt +++ /dev/null @@ -1,7713 +0,0 @@ -about -search -other -information -which -their -there -contact -business -online -first -would -services -these -click -service -price -people -state -email -health -world -products -music -should -product -system -policy -number -please -available -copyright -support -message -after -software -video -where -rights -public -books -school -through -links -review -years -order -privacy -items -company -group -under -general -research -university -january -reviews -program -games -management -could -great -united -hotel -international -center -store -travel -comments -development -report -member -details -terms -before -hotels -right -because -local -those -using -results -office -education -national -design -posted -internet -address -community -within -states -phone -shipping -reserved -subject -between -forum -family -based -black -check -special -prices -website -index -being -women -today -technology -south -project -pages -version -section -found -sports -house -related -security -county -american -photo -members -power -while -network -computer -systems -three -total -place -following -download -without -access -think -north -resources -current -posts -media -control -water -history -pictures -personal -since -including -guide -directory -board -location -change -white -small -rating -government -children -during -return -students -shopping -account -times -sites -level -digital -profile -previous -events -hours -image -department -title -description -insurance -another -shall -property -class -still -money -quality -every -listing -content -country -private -little -visit -tools -reply -customer -december -compare -movies -include -college -value -article -provide -source -author -different -press -learn -around -print -course -canada -process -stock -training -credit -point -science -categories -advanced -sales -english -estate -conditions -select -windows -photos -thread -category -large -gallery -table -register -however -october -november -market -library -really -action -start -series -model -features -industry -human -provided -required -second -accessories -movie -forums -march -september -better -questions -yahoo -going -medical -friend -server -study -application -staff -articles -feedback -again -looking -issues -april -never -users -complete -street -topic -comment -financial -things -working -against -standard -person -below -mobile -party -payment -equipment -login -student -programs -offers -legal -above -recent -stores -problem -memory -performance -social -august -quote -language -story -options -experience -rates -create -young -america -important -field -paper -single -activities -example -girls -additional -password -latest -something -question -changes -night -texas -poker -status -browse -issue -range -building -seller -court -february -always -result -audio -light -write -offer -groups -given -files -event -release -analysis -request -china -making -picture -needs -possible -might -professional -month -major -areas -future -space -committee -cards -problems -london -washington -meeting -become -interest -child -enter -california -share -similar -garden -schools -million -added -reference -companies -listed -learning -energy -delivery -popular -stories -computers -journal -reports -welcome -central -images -president -notice -original -radio -until -color -council -includes -track -australia -discussion -archive -others -entertainment -agreement -format -least -society -months -safety -friends -trade -edition -messages -marketing -further -updated -association -having -provides -david -already -green -studies -close -common -drive -specific -several -living -collection -called -short -display -limited -powered -solutions -means -director -daily -beach -natural -whether -electronics -period -planning -database -official -weather -average -technical -window -france -region -island -record -direct -microsoft -conference -environment -records -district -calendar -costs -style -front -statement -update -parts -downloads -early -miles -sound -resource -present -applications -either -document -works -material -written -federal -hosting -rules -final -adult -tickets -thing -centre -requirements -cheap -finance -minutes -third -gifts -europe -reading -topics -individual -cover -usually -together -videos -percent -function -getting -global -economic -player -projects -lyrics -often -subscribe -submit -germany -amount -watch -included -though -thanks -everything -deals -various -words -linux -production -commercial -james -weight -heart -advertising -received -choose -treatment -newsletter -archives -points -knowledge -magazine -error -camera -currently -construction -registered -clear -receive -domain -methods -chapter -makes -protection -policies -beauty -manager -india -position -taken -listings -models -michael -known -cases -engineering -florida -simple -quick -wireless -license -friday -whole -annual -published -later -basic -shows -corporate -google -church -method -purchase -customers -active -response -practice -hardware -figure -materials -holiday -enough -designed -along -among -death -writing -speed -countries -brand -discount -higher -effects -created -remember -standards -yellow -political -increase -advertise -kingdom -environmental -thought -stuff -french -storage -japan -doing -loans -shoes -entry -nature -orders -availability -africa -summary -growth -notes -agency -monday -european -activity -although -western -income -force -employment -overall -river -commission -package -contents -players -engine -album -regional -supplies -started -administration -institute -views -plans -double -build -screen -exchange -types -sponsored -lines -electronic -continue -across -benefits -needed -season -apply -someone -anything -printer -condition -effective -believe -organization -effect -asked -sunday -selection -casino -volume -cross -anyone -mortgage -silver -corporation -inside -solution -mature -rather -weeks -addition -supply -nothing -certain -executive -running -lower -necessary -union -jewelry -according -clothing -particular -names -robert -homepage -skills -islands -advice -career -military -rental -decision -leave -british -teens -woman -facilities -sellers -middle -cable -opportunities -taking -values -division -coming -tuesday -object -lesbian -appropriate -machine -length -actually -score -statistics -client -returns -capital -follow -sample -investment -shown -saturday -christmas -england -culture -flash -george -choice -starting -registration -thursday -courses -consumer -airport -foreign -artist -outside -furniture -levels -channel -letter -phones -ideas -wednesday -structure -summer -allow -degree -contract -button -releases -homes -super -matter -custom -virginia -almost -located -multiple -asian -distribution -editor -industrial -cause -potential -focus -featured -rooms -female -responsible -communications -associated -thomas -primary -cancer -numbers -reason -browser -spring -foundation -answer -voice -friendly -schedule -documents -communication -purpose -feature -comes -police -everyone -independent -approach -cameras -brown -physical -operating -medicine -ratings -chicago -forms -glass -happy -smith -wanted -developed -thank -unique -survey -prior -telephone -sport -ready -animal -sources -mexico -population -regular -secure -navigation -operations -therefore -simply -evidence -station -christian -round -paypal -favorite -understand -option -master -valley -recently -probably -rentals -built -publications -blood -worldwide -improve -connection -publisher -larger -networks -earth -parents -nokia -impact -transfer -introduction -kitchen -strong -carolina -wedding -properties -hospital -ground -overview -accommodation -owners -disease -excellent -italy -perfect -opportunity -classic -basis -command -cities -william -express -award -distance -peter -assessment -ensure -involved -extra -especially -interface -partners -budget -rated -guides -success -maximum -operation -existing -quite -selected -amazon -patients -restaurants -beautiful -warning -locations -horse -forward -flowers -stars -significant -lists -technologies -owner -retail -animals -useful -directly -manufacturer -providing -housing -takes -bring -catalog -searches -trying -mother -authority -considered -traffic -programme -joined -input -strategy -agent -valid -modern -senior -ireland -teaching -grand -testing -trial -charge -units -instead -canadian -normal -wrote -enterprise -ships -entire -educational -leading -metal -positive -fitness -chinese -opinion -football -abstract -output -funds -greater -likely -develop -employees -artists -alternative -processing -responsibility -resolution -guest -seems -publication -relations -trust -contains -session -multi -photography -republic -components -vacation -century -academic -assistance -completed -graphics -indian -expected -grade -dating -pacific -mountain -organizations -filter -mailing -vehicle -longer -consider -northern -behind -panel -floor -german -buying -match -proposed -default -require -outdoor -morning -otherwise -allows -protein -plant -reported -transportation -politics -partner -disclaimer -authors -boards -faculty -parties -membership -mission -string -sense -modified -released -stage -internal -goods -recommended -unless -richard -detailed -japanese -approved -background -target -except -character -maintenance -ability -maybe -functions -moving -brands -places -pretty -trademarks -phentermine -spain -southern -yourself -winter -battery -youth -pressure -submitted -boston -keywords -medium -television -interested -break -purposes -throughout -dance -itself -defined -papers -playing -awards -studio -reader -virtual -device -established -answers -remote -programming -external -apple -regarding -instructions -offered -theory -enjoy -remove -surface -minimum -visual -variety -teachers -martin -manual -block -subjects -agents -increased -repair -civil -steel -understanding -songs -fixed -wrong -beginning -hands -associates -finally -updates -desktop -classes -paris -sector -capacity -requires -jersey -fully -father -electric -instruments -quotes -officer -driver -businesses -respect -unknown -specified -restaurant -worth -procedures -teacher -relationship -workers -georgia -peace -traditional -campus -showing -creative -coast -benefit -progress -funding -devices -grant -agree -fiction -sometimes -watches -careers -beyond -families -museum -themselves -transport -interesting -blogs -evaluation -accepted -former -implementation -complex -galleries -references -presented -agencies -literature -respective -parent -spanish -michigan -columbia -setting -scale -stand -economy -highest -helpful -monthly -critical -frame -musical -definition -secretary -angeles -networking -australian -employee -chief -gives -bottom -magazines -packages -detail -francisco -changed -heard -begin -individuals -colorado -royal -clean -switch -russian -largest -african -titles -relevant -guidelines -justice -connect -bible -basket -applied -weekly -installation -described -demand -suite -vegas -square -chris -attention -advance -auction -difference -allowed -correct -charles -nation -selling -piece -sheet -seven -older -illinois -regulations -elements -species -cells -module -resort -facility -random -pricing -certificate -minister -motion -looks -fashion -directions -visitors -documentation -monitor -trading -forest -calls -whose -coverage -couple -giving -chance -vision -ending -clients -actions -listen -discuss -accept -automotive -naked -successful -communities -clinical -situation -sciences -markets -lowest -highly -publishing -appear -emergency -developing -lives -currency -leather -determine -temperature -announcements -patient -actual -historical -stone -commerce -ringtones -perhaps -persons -difficult -scientific -satellite -tests -village -accounts -amateur -particularly -factors -coffee -settings -buyer -cultural -steve -easily -poster -functional -closed -holidays -zealand -balance -monitoring -graduate -replies -architecture -initial -label -thinking -scott -recommend -canon -league -waste -minute -provider -optional -dictionary -accounting -manufacturing -sections -chair -fishing -effort -phase -fields -fantasy -letters -motor -professor -context -install -shirt -apparel -generally -continued -crime -count -breast -techniques -johnson -quickly -dollars -websites -religion -claim -driving -permission -surgery -patch -measures -generation -kansas -chemical -doctor -reduce -brought -himself -component -enable -exercise -santa -guarantee -leader -diamond -israel -processes -servers -alone -meetings -seconds -jones -arizona -keyword -interests -flight -congress -username -produced -italian -paperback -classifieds -supported -pocket -saint -freedom -argument -competition -creating -drugs -joint -premium -providers -fresh -characters -attorney -upgrade -factor -growing -thousands -stream -apartments -hearing -eastern -auctions -therapy -entries -dates -generated -signed -upper -administrative -serious -prime -samsung -limit -began -louis -steps -errors -shops -efforts -informed -thoughts -creek -worked -quantity -urban -practices -sorted -reporting -essential -myself -tours -platform -affiliate -labor -immediately -admin -nursing -defense -machines -designated -heavy -covered -recovery -integrated -configuration -merchant -comprehensive -expert -universal -protect -solid -presentation -languages -became -orange -compliance -vehicles -prevent -theme -campaign -marine -improvement -guitar -finding -pennsylvania -examples -saying -spirit -claims -challenge -motorola -acceptance -strategies -affairs -touch -intended -towards -goals -election -suggest -branch -charges -serve -affiliates -reasons -magic -mount -smart -talking -latin -multimedia -avoid -certified -manage -corner -computing -oregon -element -birth -virus -abuse -interactive -requests -separate -quarter -procedure -leadership -tables -define -racing -religious -facts -breakfast -column -plants -faith -chain -developer -identify -avenue -missing -approximately -domestic -sitemap -recommendations -moved -houston -reach -comparison -mental -viewed -moment -extended -sequence -attack -sorry -centers -opening -damage -reserve -recipes -gamma -plastic -produce -placed -truth -counter -failure -follows -weekend -dollar -ontario -automatically -minnesota -films -bridge -native -williams -movement -printing -baseball -owned -approval -draft -chart -played -contacts -jesus -readers -clubs -jackson -equal -adventure -matching -offering -shirts -profit -leaders -posters -institutions -assistant -variable -advertisement -expect -parking -headlines -yesterday -compared -determined -wholesale -workshop -russia -codes -kinds -extension -seattle -statements -golden -completely -teams -lighting -senate -forces -funny -brother -turned -portable -tried -electrical -applicable -returned -pattern -named -theatre -laser -earlier -manufacturers -sponsor -classical -warranty -dedicated -indiana -direction -harry -basketball -objects -delete -evening -assembly -nuclear -taxes -mouse -signal -criminal -issued -brain -sexual -wisconsin -powerful -dream -obtained -false -flower -personnel -passed -supplied -identified -falls -opinions -promote -stated -stats -hawaii -professionals -appears -carry -decided -covers -advantage -hello -designs -maintain -tourism -priority -newsletters -adults -clips -savings -graphic -payments -estimated -binding -brief -ended -winning -eight -anonymous -straight -script -served -wants -miscellaneous -prepared -dining -alert -integration -atlanta -dakota -interview -framework -installed -queen -credits -clearly -handle -sweet -criteria -pubmed -massachusetts -diego -associate -truck -behavior -enlarge -frequently -revenue -measure -changing -votes -looked -discussions -festival -laboratory -ocean -flights -experts -signs -depth -whatever -logged -laptop -vintage -train -exactly -explore -maryland -concept -nearly -eligible -checkout -reality -forgot -handling -origin -gaming -feeds -billion -destination -scotland -faster -intelligence -dallas -bought -nations -route -followed -specifications -broken -tripadvisor -frank -alaska -battle -residential -anime -speak -decisions -industries -protocol -query -partnership -editorial -expression -equity -provisions -speech -principles -suggestions -rural -shared -sounds -replacement -strategic -judge -economics -bytes -forced -compatible -fight -apartment -height -speaker -filed -netherlands -obtain -consulting -recreation -offices -designer -remain -managed -failed -marriage -korea -banks -participants -secret -kelly -leads -negative -austin -favorites -toronto -theater -springs -missouri -andrew -perform -healthy -translation -estimates -assets -injury -joseph -ministry -drivers -lawyer -figures -married -protected -proposal -sharing -philadelphia -portal -waiting -birthday -gratis -banking -officials -brian -toward -slightly -assist -conduct -contained -lingerie -legislation -calling -parameters -serving -profiles -miami -comics -matters -houses -postal -relationships -tennessee -controls -breaking -combined -ultimate -wales -representative -frequency -introduced -minor -finish -departments -residents -noted -displayed -reduced -physics -spent -performed -extreme -samples -davis -daniel -reviewed -forecast -removed -helps -singles -administrator -cycle -amounts -contain -accuracy -sleep -pharmacy -brazil -creation -static -scene -hunter -addresses -crystal -famous -writer -chairman -violence -oklahoma -speakers -drink -academy -dynamic -gender -permanent -agriculture -cleaning -constitutes -portfolio -practical -delivered -collectibles -infrastructure -exclusive -concerns -colour -vendor -originally -intel -utilities -philosophy -regulation -officers -reduction -referred -supports -nutrition -recording -regions -junior -rings -meaning -secondary -wonderful -ladies -henry -ticket -announced -guess -agreed -prevention -soccer -import -posting -presence -instant -mentioned -automatic -healthcare -viewing -maintained -increasing -majority -connected -christ -directors -aspects -austria -ahead -participation -scheme -utility -preview -manner -matrix -containing -combination -devel -amendment -despite -strength -guaranteed -turkey -libraries -proper -distributed -degrees -singapore -enterprises -delta -seeking -inches -phoenix -convention -shares -principal -daughter -standing -comfort -colors -cisco -ordering -alpha -appeal -cruise -bonus -certification -previously -bookmark -buildings -specials -disney -household -batteries -adobe -smoking -becomes -drives -alabama -improved -trees -achieve -positions -dress -subscription -dealer -contemporary -nearby -carried -happen -exposure -panasonic -permalink -signature -gambling -refer -miller -provision -outdoors -clothes -caused -luxury -babes -frames -certainly -indeed -newspaper -circuit -layer -printed -removal -easier -liability -trademark -printers -adding -kentucky -mostly -taylor -trackback -prints -spend -factory -interior -revised -americans -optical -promotion -relative -amazing -clock -identity -suites -conversion -feeling -hidden -reasonable -victoria -serial -relief -revision -broadband -influence -ratio -importance -planet -webmaster -copies -recipe -permit -seeing -proof -tennis -prescription -bedroom -empty -instance -licensed -orlando -specifically -bureau -maine -represent -conservation -ideal -specs -recorded -pieces -finished -parks -dinner -lawyers -sydney -stress -cream -trends -discover -patterns -boxes -louisiana -hills -javascript -fourth -advisor -marketplace -aware -wilson -shape -evolution -irish -certificates -objectives -stations -suggested -remains -greatest -firms -concerned -operator -structures -generic -encyclopedia -usage -charts -continuing -mixed -census -interracial -competitive -exist -wheel -transit -suppliers -compact -poetry -lights -tracking -angel -keeping -preparation -attempt -receiving -matches -accordance -width -noise -engines -forget -array -discussed -accurate -stephen -elizabeth -climate -reservations -playstation -alcohol -greek -instruction -managing -annotation -sister -differences -walking -explain -smaller -newest -establish -happened -expressed -extent -sharp -lesbians -paragraph -mathematics -compensation -export -managers -aircraft -modules -sweden -conflict -conducted -versions -employer -occur -percentage -knows -mississippi -describe -concern -backup -requested -citizens -connecticut -heritage -personals -immediate -holding -trouble -spread -coach -kevin -agricultural -expand -supporting -audience -assigned -jordan -collections -participate -specialist -affect -virgin -experienced -investigation -raised -institution -directed -dealers -searching -sporting -helping -affected -totally -plate -expenses -indicate -blonde -proceedings -favourite -transmission -anderson -characteristics -organic -experiences -albums -cheats -extremely -verzeichnis -contracts -guests -hosted -diseases -concerning -developers -equivalent -chemistry -neighborhood -nevada -thailand -variables -agenda -anyway -continues -tracks -advisory -curriculum -logic -template -prince -circle -grants -anywhere -psychology -responses -atlantic -circumstances -edward -investor -identification -leaving -wildlife -appliances -elementary -cooking -speaking -sponsors -unlimited -respond -sizes -plain -entered -launch -checking -costa -belgium -printable -guidance -trail -enforcement -symbol -crafts -highway -buddy -hardcover -observed -setup -booking -glossary -fiscal -celebrity -styles -denver -filled -channels -ericsson -appendix -notify -blues -chocolate -portion -scope -hampshire -supplier -cables -cotton -bluetooth -controlled -requirement -authorities -biology -dental -killed -border -ancient -debate -representatives -starts -pregnancy -causes -arkansas -biography -leisure -attractions -learned -transactions -notebook -explorer -historic -attached -opened -husband -disabled -authorized -crazy -upcoming -britain -concert -retirement -scores -financing -efficiency -comedy -adopted -efficient -weblog -linear -commitment -specialty -bears -carrier -edited -constant -mouth -jewish -meter -linked -portland -interviews -concepts -reflect -deliver -wonder -lessons -fruit -begins -qualified -reform -alerts -treated -discovery -mysql -classified -relating -assume -confidence -alliance -confirm -neither -lewis -howard -offline -leaves -engineer -lifestyle -consistent -replace -clearance -connections -inventory -converter -organisation -checks -reached -becoming -safari -objective -indicated -sugar -stick -securities -allen -relation -enabled -genre -slide -montana -volunteer -tested -democratic -enhance -switzerland -exact -bound -parameter -adapter -processor -formal -dimensions -contribute -hockey -storm -micro -colleges -laptops -showed -challenges -editors -threads -supreme -brothers -recognition -presents -submission -dolls -estimate -encourage -regulatory -inspection -consumers -cancel -limits -territory -transaction -manchester -weapons -paint -delay -pilot -outlet -contributions -continuous -czech -resulting -cambridge -initiative -novel -execution -disability -increases -ultra -winner -idaho -contractor -episode -examination -potter -plays -bulletin -indicates -modify -oxford -truly -epinions -painting -committed -extensive -affordable -universe -candidate -databases -patent -outstanding -eating -perspective -planned -watching -lodge -messenger -mirror -tournament -consideration -discounts -sterling -sessions -kernel -stocks -buyers -journals -catalogue -jennifer -antonio -charged -broad -taiwan -chosen -greece -swiss -sarah -clark -labour -terminal -publishers -nights -behalf -caribbean -liquid -nebraska -salary -reservation -foods -gourmet -guard -properly -orleans -saving -remaining -empire -resume -twenty -newly -raise -prepare -avatar -depending -illegal -expansion -hundreds -lincoln -helped -premier -tomorrow -purchased -decide -consent -drama -visiting -performing -downtown -keyboard -contest -collected -bands -suitable -absolutely -millions -lunch -audit -chamber -guinea -findings -muscle -featuring -implement -clicking -scheduled -polls -typical -tower -yours -calculator -significantly -chicken -temporary -attend -shower -sending -jason -tonight -sufficient -holdem -shell -province -catholic -awareness -vancouver -governor -seemed -contribution -measurement -swimming -spyware -formula -constitution -packaging -solar -catch -pakistan -reliable -consultation -northwest -doubt -finder -unable -periods -classroom -tasks -democracy -attacks -wallpaper -merchandise -const -resistance -doors -symptoms -resorts -biggest -memorial -visitor -forth -insert -baltimore -gateway -alumni -drawing -candidates -charlotte -ordered -biological -fighting -transition -happens -preferences -romance -instrument -bruce -split -themes -powers -heaven -pregnant -twice -classification -focused -egypt -physician -hollywood -bargain -wikipedia -cellular -norway -vermont -asking -blocks -normally -spiritual -hunting -diabetes -shift -bodies -photographs -cutting -simon -writers -marks -flexible -loved -favourites -mapping -numerous -relatively -birds -satisfaction -represents -indexed -pittsburgh -superior -preferred -saved -paying -cartoon -shots -intellectual -moore -granted -choices -carbon -spending -comfortable -magnetic -interaction -listening -effectively -registry -crisis -outlook -massive -denmark -employed -bright -treat -header -poverty -formed -piano -sheets -patrick -experimental -puerto -revolution -consolidation -displays -plasma -allowing -earnings -mystery -landscape -dependent -mechanical -journey -delaware -bidding -consultants -risks -banner -applicant -charter -barbara -cooperation -counties -acquisition -ports -implemented -directories -recognized -dreams -blogger -notification -licensing -stands -teach -occurred -textbooks -rapid -hairy -diversity -cleveland -reverse -deposit -seminar -investments -latina -wheels -specify -accessibility -dutch -sensitive -templates -formats -depends -boots -holds -router -concrete -editing -poland -folder -womens -completion -upload -pulse -universities -technique -contractors -voting -courts -notices -subscriptions -calculate -detroit -alexander -broadcast -converted -metro -toshiba -anniversary -improvements -strip -specification -pearl -accident -accessible -accessory -resident -possibly -airline -typically -representation -regard -exists -arrangements -smooth -conferences -uniprotkb -strike -consumption -birmingham -flashing -narrow -afternoon -threat -surveys -sitting -putting -consultant -controller -ownership -committees -legislative -researchers -vietnam -trailer -castle -gardens -missed -malaysia -unsubscribe -antique -labels -willing -molecular -acting -heads -stored -logos -residence -attorneys -antiques -density -hundred -operators -strange -sustainable -philippines -statistical -mention -innovation -employers -parallel -honda -amended -operate -bills -bathroom -stable -opera -definitions -doctors -lesson -cinema -asset -elections -drinking -reaction -blank -enhanced -entitled -severe -generate -stainless -newspapers -hospitals -deluxe -humor -monitors -exception -lived -duration -successfully -indonesia -pursuant -fabric -visits -primarily -tight -domains -capabilities -contrast -recommendation -flying -recruitment -berlin -organized -siemens -adoption -improving -expensive -meant -capture -pounds -buffalo -organisations -plane -explained -programmes -desire -expertise -mechanism -camping -jewellery -meets -welfare -caught -eventually -marked -driven -measured -medline -bottle -agreements -considering -innovative -marshall -massage -rubber -conclusion -closing -tampa -thousand -legend -grace -susan -adams -python -monster -villa -columns -disorders -collaboration -hamilton -detection -cookies -inner -formation -tutorial -engineers -entity -cruises -holder -proposals -moderator -tutorials -settlement -portugal -lawrence -roman -duties -valuable -collectables -ethics -forever -dragon -captain -fantastic -imagine -brings -heating -governments -purchasing -scripts -stereo -appointed -taste -dealing -commit -operational -airlines -liberal -livecam -trips -sides -turns -corresponding -descriptions -cache -jacket -determination -animation -oracle -matthew -lease -productions -aviation -hobbies -proud -excess -disaster -console -commands -telecommunications -instructor -giant -achieved -injuries -shipped -seats -approaches -alarm -voltage -anthony -nintendo -usual -loading -stamps -appeared -franklin -angle -vinyl -highlights -mining -designers -melbourne -ongoing -worst -imaging -betting -scientists -liberty -wyoming -blackjack -argentina -convert -possibility -analyst -commissioner -dangerous -garage -exciting -reliability -thongs -unfortunately -respectively -volunteers -attachment -ringtone -finland -morgan -derived -pleasure -honor -oriented -eagle -desktops -pants -columbus -nurse -prayer -appointment -workshops -hurricane -quiet -postage -producer -represented -mortgages -responsibilities -cheese -comic -carefully -productivity -investors -crown -underground -diagnosis -maker -crack -principle -picks -vacations -semester -calculated -fetish -applies -casinos -appearance -smoke -apache -filters -incorporated -craft -notebooks -apart -fellow -blind -lounge -algorithm -coins -gross -strongly -valentine -hilton -proteins -horror -familiar -capable -douglas -debian -involving -investing -christopher -admission -epson -elected -carrying -victory -madison -terrorism -editions -mainly -ethnic -parliament -actor -finds -situations -fifth -allocated -citizen -vertical -corrections -structural -municipal -describes -prize -occurs -absolute -disabilities -consists -anytime -substance -prohibited -addressed -soldiers -guardian -lecture -simulation -layout -initiatives -concentration -classics -interpretation -horses -dirty -wayne -donate -taught -bankruptcy -worker -optimization -alive -temple -substances -prove -discovered -wings -breaks -genetic -restrictions -participating -waters -promise -exhibition -prefer -ridge -cabinet -modem -harris -bringing -evaluate -tiffany -tropical -collect -composition -toyota -streets -nationwide -vector -definitely -shaved -turning -buffer -purple -existence -commentary -larry -limousines -developments -immigration -destinations -mutual -pipeline -necessarily -syntax -attribute -prison -skill -chairs -everyday -apparently -surrounding -mountains -moves -popularity -inquiry -ethernet -checked -exhibit -throw -trend -sierra -visible -desert -postposted -oldest -rhode -coordinator -obviously -mercury -steven -handbook -navigate -worse -summit -victims -spaces -fundamental -burning -escape -coupons -somewhat -receiver -substantial -progressive -cialis -boats -glance -scottish -championship -arcade -richmond -sacramento -impossible -russell -tells -obvious -fiber -depression -graph -covering -platinum -judgment -bedrooms -talks -filing -foster -modeling -passing -awarded -testimonials -trials -tissue -memorabilia -clinton -masters -bonds -cartridge -alberta -explanation -commons -cincinnati -subsection -fraud -electricity -permitted -spectrum -arrival -pottery -emphasis -roger -aspect -workplace -awesome -mexican -confirmed -counts -priced -wallpapers -crash -desired -inter -closer -assumes -heights -shadow -riding -infection -firefox -expense -grove -eligibility -venture -clinic -korean -healing -princess -entering -packet -spray -studios -involvement -buttons -placement -observations -vbulletin -funded -thompson -winners -extend -roads -subsequent -dublin -rolling -motorcycle -disclosure -establishment -memories -nelson -arrived -creates -faces -tourist -mayor -murder -adequate -senator -yield -presentations -grades -cartoons -digest -lodging -hence -entirely -replaced -radar -rescue -undergraduate -losses -combat -reducing -stopped -occupation -lakes -donations -associations -citysearch -closely -radiation -diary -seriously -kings -shooting -flags -baker -launched -elsewhere -pollution -conservative -guestbook -shock -effectiveness -walls -abroad -ebony -drawn -arthur -visited -walker -demonstrate -atmosphere -suggests -beast -operated -experiment -targets -overseas -purchases -dodge -counsel -federation -pizza -invited -yards -assignment -chemicals -gordon -farmers -queries -ukraine -absence -nearest -cluster -vendors -whereas -serves -woods -surprise -partial -shoppers -everybody -couples -nashville -ranking -jokes -simpson -twiki -sublime -counseling -palace -acceptable -satisfied -measurements -verify -globe -trusted -copper -milwaukee -medication -warehouse -shareware -dicke -kerry -receipt -supposed -ordinary -nobody -ghost -violation -configure -stability -applying -southwest -pride -institutional -expectations -independence -knowing -reporter -metabolism -keith -champion -cloudy -linda -personally -chile -plenty -sentence -throat -ignore -maria -uniform -excellence -wealth -somewhere -vacuum -dancing -attributes -recognize -brass -writes -plaza -outcomes -survival -quest -publish -screening -thumbnail -trans -jonathan -whenever -lifetime -pioneer -booty -forgotten -acrobat -plates -acres -venue -athletic -thermal -essays -behaviour -vital -telling -fairly -coastal -config -charity -intelligent -edinburgh -excel -modes -obligation -campbell -stupid -harbor -hungary -traveler -segment -realize -regardless -enemy -puzzle -rising -aluminum -wells -wishlist -opens -insight -restricted -republican -secrets -lucky -latter -merchants -thick -trailers -repeat -syndrome -philips -attendance -penalty -glasses -enables -iraqi -builder -vista -jessica -chips -terry -flood -arguments -amsterdam -arena -adventures -pupils -stewart -announcement -outcome -appreciate -expanded -casual -grown -polish -lovely -extras -centres -jerry -clause -smile -lands -troops -indoor -bulgaria -armed -broker -charger -regularly -believed -cooling -trucks -mechanisms -divorce -laura -shopper -tokyo -partly -nikon -customize -tradition -candy -pills -tiger -donald -folks -sensor -exposed -telecom -angels -deputy -indicators -sealed -emissions -physicians -loaded -complaint -scenes -experiments -afghanistan -boost -spanking -scholarship -governance -founded -supplements -chronic -icons -moral -catering -finger -keeps -pound -locate -camcorder -trained -implementing -roses -ourselves -bread -tobacco -wooden -motors -tough -roberts -incident -gonna -dynamics -conversation -decrease -chest -pension -billy -revenues -emerging -worship -capability -craig -herself -producing -churches -precision -damages -reserves -contributed -solve -shorts -reproduction -minority -diverse -ingredients -johnny -franchise -recorder -complaints -facing -nancy -promotions -tones -passion -rehabilitation -maintaining -sight -defence -patches -refund -towns -environments -trembl -divided -reception -emails -cyprus -correctly -insider -seminars -consequences -makers -hearts -geography -appearing -integrity -worry -discrimination -carter -legacy -pleased -danger -vitamin -widely -processed -phrase -genuine -raising -implications -functionality -paradise -hybrid -reads -roles -intermediate -emotional -glory -platforms -bigger -billing -diesel -versus -combine -overnight -geographic -exceed -saudi -fault -preliminary -districts -introduce -promotional -chevrolet -babies -karen -compiled -romantic -revealed -specialists -generator -albert -examine -jimmy -graham -suspension -bristol -margaret -compaq -correction -slowly -authentication -communicate -rugby -supplement -showtimes -portions -infant -promoting -sectors -samuel -fluid -grounds -regards -machinery -bandwidth -unlike -equation -baskets -probability -dimension -wright -barry -proven -schedules -admissions -cached -warren -studied -reviewer -involves -quarterly -profits -devil -grass -comply -marie -florist -illustrated -cherry -continental -alternate -deutsch -achievement -limitations -kenya -webcam -funeral -nutten -earrings -enjoyed -automated -chapters -charlie -quebec -passenger -convenient -dennis -francis -sized -manga -noticed -socket -silent -literary -signals -orientation -theft -childhood -swing -symbols -humans -analog -facial -choosing -talent -dated -flexibility -seeker -wisdom -shoot -boundary -packard -offset -payday -philip -elite -holders -believes -swedish -poems -deadline -jurisdiction -robot -displaying -witness -collins -equipped -stages -encouraged -winds -powder -broadway -acquired -assess -cartridges -stones -entrance -gnome -roots -declaration -losing -attempts -gadgets -noble -glasgow -automation -impacts -gospel -advantages -shore -loves -induced -knight -preparing -loose -recipient -linking -extensions -appeals -earned -illness -islamic -athletics -southeast -alternatives -pending -parker -determining -lebanon -personalized -kennedy -conditioning -teenage -triple -cooper -vincent -secured -unusual -answered -partnerships -destruction -slots -increasingly -migration -disorder -routine -toolbar -basically -rocks -conventional -titans -applicants -wearing -sought -genes -mounted -habitat -firewall -median -scanner -herein -occupational -animated -judicial -adjustment -integer -treatments -bachelor -attitude -camcorders -engaged -falling -basics -montreal -carpet -struct -lenses -binary -genetics -attended -difficulty -collective -coalition -dropped -enrollment -walter -besides -producers -collector -hosts -interfaces -advertisers -moments -atlas -strings -representing -observation -feels -torture -deleted -mitchell -restoration -convenience -returning -ralph -opposition -container -defendant -warner -confirmation -embedded -inkjet -supervisor -wizard -corps -actors -liver -peripherals -liable -brochure -morris -bestsellers -petition -eminem -recall -antenna -picked -assumed -departure -minneapolis -belief -killing -bikini -memphis -shoulder -decor -lookup -texts -harvard -brokers -diameter -ottawa -podcast -seasons -interactions -refine -bidder -singer -evans -herald -literacy -fails -aging -intervention -plugin -attraction -diving -invite -modification -alice -latinas -suppose -customized -involve -moderate -terror -younger -thirty -opposite -understood -rapidly -dealtime -intro -mercedes -assurance -clerk -happening -mills -outline -amendments -tramadol -holland -receives -jeans -metropolitan -compilation -verification -fonts -refers -favor -veterans -sigma -attractive -xhtml -occasion -recordings -jefferson -victim -demands -sleeping -careful -gardening -obligations -arrive -orchestra -sunset -tracked -moreover -minimal -polyphonic -lottery -framed -aside -outsourcing -licence -adjustable -allocation -michelle -essay -discipline -demonstrated -dialogue -identifying -alphabetical -camps -declared -dispatched -aaron -handheld -trace -disposal -florists -packs -installing -switches -romania -voluntary -consult -greatly -blogging -cycling -midnight -commonly -photographer -inform -turkish -messaging -pentium -quantum -murray -intent -largely -pleasant -announce -constructed -additions -requiring -spoke -arrow -engagement -sampling -rough -weird -refinance -inspired -holes -weddings -blade -suddenly -oxygen -cookie -meals -canyon -meters -merely -calendars -arrangement -conclusions -passes -bibliography -pointer -compatibility -stretch -durham -furthermore -permits -cooperative -muslim -sleeve -netscape -cleaner -cricket -feeding -stroke -township -rankings -measuring -robin -robinson -jacksonville -strap -headquarters -sharon -crowd -transfers -olympic -transformation -remained -attachments -entities -customs -administrators -personality -rainbow -roulette -decline -gloves -israeli -medicare -skiing -cloud -facilitate -subscriber -valve -hewlett -explains -proceed -flickr -feelings -knife -jamaica -priorities -shelf -bookstore -timing -liked -parenting -adopt -denied -fotos -incredible -britney -freeware -donation -outer -deaths -rivers -commonwealth -pharmaceutical -manhattan -tales -katrina -workforce -islam -nodes -thumbs -seeds -cited -targeted -organizational -skype -realized -twelve -founder -decade -gamecube -dispute -portuguese -tired -titten -adverse -everywhere -excerpt -steam -discharge -drinks -voices -acute -halloween -climbing -stood -perfume -carol -honest -albany -hazardous -restore -stack -methodology -somebody -housewares -reputation -resistant -democrats -recycling -curve -creator -amber -qualifications -museums -coding -slideshow -tracker -variation -passage -transferred -trunk -hiking -pierre -jelsoft -headset -photograph -oakland -colombia -waves -camel -distributor -lamps -underlying -wrestling -suicide -archived -photoshop -arabia -gathering -projection -juice -chase -mathematical -logical -sauce -extract -specialized -diagnostic -panama -indianapolis -payable -corporations -courtesy -criticism -automobile -confidential -statutory -accommodations -athens -northeast -downloaded -judges -retired -remarks -detected -decades -paintings -walked -arising -nissan -bracelet -juvenile -injection -yorkshire -populations -protective -afraid -acoustic -railway -cassette -initially -indicator -pointed -causing -mistake -norton -locked -eliminate -fusion -mineral -sunglasses -steering -beads -fortune -preference -canvas -threshold -parish -claimed -screens -cemetery -planner -croatia -flows -stadium -venezuela -exploration -fewer -sequences -coupon -nurses -proxy -astronomy -lanka -edwards -contests -translate -announces -costume -tagged -berkeley -voted -killer -bikes -gates -adjusted -bishop -pulled -shaped -compression -seasonal -establishing -farmer -counters -constitutional -perfectly -slave -instantly -cultures -norfolk -coaching -examined -encoding -litigation -submissions -heroes -painted -lycos -zdnet -broadcasting -horizontal -artwork -cosmetic -resulted -portrait -terrorist -informational -ethical -carriers -ecommerce -mobility -floral -builders -struggle -schemes -suffering -neutral -fisher -spears -prospective -bedding -ultimately -joining -heading -equally -artificial -bearing -spectacular -coordination -connector -combo -seniors -worlds -guilty -affiliated -activation -naturally -haven -tablet -subscribers -charm -violent -mitsubishi -underwear -basin -potentially -ranch -constraints -crossing -inclusive -dimensional -cottage -drunk -considerable -crimes -resolved -mozilla -toner -latex -branches -anymore -delhi -holdings -alien -locator -selecting -processors -pantyhose -broke -nepal -zimbabwe -difficulties -complexity -constantly -browsing -resolve -barcelona -presidential -documentary -territories -melissa -moscow -thesis -nylon -palestinian -discs -rocky -bargains -frequent -nigeria -ceiling -pixels -ensuring -hispanic -legislature -hospitality -anybody -procurement -diamonds -fleet -untitled -bunch -totals -marriott -singing -theoretical -afford -exercises -starring -referral -surveillance -optimal -distinct -protocols -highlight -substitute -inclusion -hopefully -brilliant -turner -sucking -cents -reuters -spoken -omega -evaluated -stayed -civic -assignments -manuals -termination -watched -saver -thereof -grill -households -redeem -rogers -grain -authentic -regime -wanna -wishes -montgomery -architectural -louisville -depend -differ -macintosh -movements -ranging -monica -repairs -breath -amenities -virtually -candle -hanging -colored -authorization -verified -formerly -projector -situated -comparative -seeks -herbal -loving -strictly -routing -stanley -psychological -surprised -retailer -vitamins -elegant -gains -renewal -genealogy -opposed -deemed -scoring -expenditure -brooklyn -liverpool -sisters -critics -connectivity -spots -algorithms -hacker -madrid -similarly -margin -solely -salon -collaborative -norman -excluding -turbo -headed -voters -madonna -commander -murphy -thinks -thats -suggestion -soldier -phillips -aimed -justin -interval -mirrors -spotlight -tricks -reset -brush -investigate -expansys -panels -repeated -assault -connecting -spare -logistics -kodak -tongue -bowling -danish -monkey -proportion -filename -skirt -florence -invest -honey -analyses -drawings -significance -scenario -lovers -atomic -approx -symposium -arabic -gauge -essentials -junction -protecting -faced -rachel -solving -transmitted -weekends -screenshots -produces -intensive -chains -kingston -sixth -engage -deviant -switching -quoted -adapters -correspondence -farms -imports -supervision -cheat -bronze -expenditures -sandy -separation -testimony -suspect -celebrities -macro -sender -mandatory -boundaries -crucial -syndication -celebration -adjacent -filtering -tuition -spouse -exotic -viewer -signup -threats -luxembourg -puzzles -reaching -damaged -receptor -laugh -surgical -destroy -citation -pitch -autos -premises -perry -proved -offensive -imperial -dozen -benjamin -deployment -teeth -cloth -studying -colleagues -stamp -lotus -salmon -olympus -separated -cargo -directive -salem -starter -upgrades -likes -butter -pepper -weapon -luggage -burden -tapes -zones -races -stylish -maple -grocery -offshore -governing -retailers -depot -kenneth -blend -harrison -julie -occasionally -attending -emission -finest -realty -janet -recruiting -apparent -instructional -phpbb -autumn -traveling -probe -permissions -biotechnology -toilet -ranked -jackets -routes -packed -excited -outreach -helen -mounting -recover -lopez -balanced -prescribed -catherine -timely -talked -debug -delayed -chuck -reproduced -explicit -calculation -villas -ebook -consolidated -exclude -peeing -occasions -brooks -equations -newton -exceptional -anxiety -bingo -whilst -spatial -respondents -ceramic -prompt -precious -minds -annually -considerations -scanners -xanax -fingers -sunny -ebooks -delivers -queensland -necklace -musicians -leeds -composite -unavailable -cedar -arranged -theaters -advocacy -raleigh -essentially -designing -threaded -qualify -blair -hopes -assessments -mason -diagram -burns -pumps -footwear -beijing -peoples -victor -mario -attach -licenses -utils -removing -advised -brunswick -spider -ranges -pairs -sensitivity -trails -preservation -hudson -isolated -calgary -interim -assisted -divine -streaming -approve -chose -compound -intensity -technological -syndicate -abortion -dialog -venues -blast -wellness -calcium -newport -antivirus -addressing -discounted -indians -shield -harvest -membrane -prague -previews -bangladesh -constitute -locally -concluded -pickup -desperate -mothers -nascar -iceland -demonstration -governmental -manufactured -candles -graduation -sailing -variations -sacred -addiction -morocco -chrome -tommy -springfield -refused -brake -exterior -greeting -ecology -oliver -congo -botswana -delays -synthesis -olive -undefined -unemployment -cyber -verizon -scored -enhancement -newcastle -clone -dicks -velocity -lambda -relay -composed -tears -performances -oasis -baseline -angry -societies -silicon -brazilian -identical -petroleum -compete -norwegian -lover -belong -honolulu -beatles -retention -exchanges -rolls -thomson -barnes -soundtrack -wondering -malta -daddy -ferry -rabbit -profession -seating -separately -physiology -collecting -exports -omaha -participant -scholarships -recreational -dominican -electron -loads -friendship -heather -passport -motel -unions -treasury -warrant -solaris -frozen -occupied -royalty -scales -rally -observer -sunshine -strain -ceremony -somehow -arrested -expanding -provincial -investigations -yamaha -medications -hebrew -gained -rochester -dying -laundry -stuck -solomon -placing -stops -homework -adjust -assessed -advertiser -enabling -encryption -filling -downloadable -sophisticated -imposed -silence -focuses -soviet -possession -laboratories -treaty -vocal -trainer -organ -stronger -volumes -advances -vegetables -lemon -toxic -thumbnails -darkness -bizrate -vienna -implied -stanford -stockings -respondent -packing -statute -rejected -satisfy -destroyed -shelter -chapel -gamespot -manufacture -layers -wordpress -guided -vulnerability -accountability -celebrate -accredited -appliance -compressed -bahamas -powell -mixture -bench -rider -scheduling -radius -perspectives -mortality -logging -hampton -christians -borders -therapeutic -butts -bobby -impressive -sheep -accordingly -architect -railroad -lectures -challenging -wines -nursery -harder -microwave -cheapest -accidents -travesti -relocation -stuart -contributors -salvador -salad -monroe -tender -violations -temperatures -paste -clouds -competitions -discretion -tanzania -preserve -unsigned -staying -cosmetics -easter -theories -repository -praise -jeremy -venice -concentrations -estonia -christianity -veteran -streams -landing -signing -executed -katie -negotiations -realistic -showcase -integral -relax -namibia -generating -christina -congressional -synopsis -hardly -prairie -reunion -composer -sword -absent -photographic -sells -ecuador -hoping -accessed -spirits -modifications -coral -pixel -float -colin -imported -paths -bubble -acquire -contrary -millennium -tribune -vessel -acids -focusing -viruses -cheaper -admitted -dairy -admit -fancy -equality -samoa -achieving -stickers -fisheries -exceptions -reactions -leasing -lauren -beliefs -macromedia -companion -squad -analyze -ashley -scroll -relate -divisions -wages -additionally -suffer -forests -fellowship -invalid -concerts -martial -males -victorian -retain -colours -execute -tunnel -genres -cambodia -patents -copyrights -chaos -lithuania -mastercard -wheat -chronicles -obtaining -beaver -updating -distribute -readings -decorative -kijiji -confused -compiler -enlargement -eagles -bases -accused -campaigns -unity -conjunction -bride -defines -airports -instances -indigenous -begun -brunette -packets -anchor -socks -validation -parade -corruption -trigger -incentives -cholesterol -gathered -essex -slovenia -notified -differential -beaches -folders -dramatic -surfaces -terrible -routers -pendant -dresses -baptist -scientist -starsmerchant -hiring -clocks -arthritis -females -wallace -nevertheless -reflects -taxation -fever -cuisine -surely -practitioners -transcript -myspace -theorem -inflation -stylus -compounds -drums -contracting -arnold -structured -reasonably -chicks -cattle -radical -graduates -rover -recommends -controlling -treasure -reload -distributors -flame -levitra -tanks -assuming -monetary -elderly -arlington -particles -floating -extraordinary -indicating -bolivia -spell -hottest -stevens -coordinate -kuwait -exclusively -emily -alleged -limitation -widescreen -compile -webster -struck -illustration -plymouth -warnings -construct -inquiries -bridal -annex -inspiration -tribal -curious -affecting -freight -rebate -meetup -eclipse -sudan -downloading -shuttle -aggregate -stunning -cycles -affects -forecasts -detect -actively -ampland -complicated -fastest -butler -shopzilla -injured -decorating -payroll -cookbook -expressions -courier -uploaded -shakespeare -hints -collapse -americas -connectors -unlikely -conflicts -techno -beverage -tribute -wired -elvis -immune -latvia -travelers -forestry -barriers -rarely -infected -offerings -martha -genesis -barrier -argue -incorrect -trains -metals -bicycle -furnishings -letting -arise -guatemala -celtic -thereby -jamie -particle -perception -minerals -advise -humidity -bottles -boxing -bangkok -renaissance -pathology -ordinance -hughes -photographers -infections -jeffrey -chess -operates -brisbane -configured -survive -oscar -festivals -menus -possibilities -reveal -canal -amino -contributing -herbs -clinics -manitoba -analytical -missions -watson -lying -costumes -strict -saddam -circulation -drill -offense -bryan -protest -assumption -jerusalem -hobby -tries -transexuales -invention -nickname -technician -inline -executives -enquiries -washing -staffing -cognitive -exploring -trick -enquiry -closure -timber -intense -playlist -registrar -showers -supporters -ruling -steady -statutes -withdrawal -myers -drops -predicted -wider -saskatchewan -cancellation -plugins -enrolled -sensors -screw -ministers -publicly -hourly -blame -geneva -freebsd -veterinary -prostores -reseller -handed -suffered -intake -informal -relevance -incentive -butterfly -tucson -mechanics -heavily -swingers -fifty -headers -mistakes -numerical -uncle -defining -counting -reflection -accompanied -assure -invitation -devoted -princeton -jacob -sodium -randy -spirituality -hormone -meanwhile -proprietary -timothy -childrens -brick -naval -thumbzilla -medieval -porcelain -bridges -pichunter -captured -thehun -decent -casting -dayton -translated -shortly -cameron -columnists -carlos -donna -andreas -warrior -diploma -cabin -innocent -scanning -consensus -valium -copying -delivering -cordless -patricia -eddie -uganda -fired -journalism -trivia -adidas -perth -grammar -intention -syria -disagree -klein -harvey -tires -undertaken -hazard -retro -statewide -semiconductor -gregory -episodes -boolean -circular -anger -mainland -illustrations -suits -chances -interact -happiness -substantially -bizarre -glenn -auckland -olympics -fruits -identifier -ribbon -calculations -conducting -startup -suzuki -trinidad -kissing -handy -exempt -crops -reduces -accomplished -calculators -geometry -impression -slovakia -guild -correlation -gorgeous -capitol -dishes -barbados -chrysler -nervous -refuse -extends -fragrance -mcdonald -replica -plumbing -brussels -tribe -neighbors -trades -superb -transparent -trinity -charleston -handled -legends -champions -floors -selections -projectors -inappropriate -exhaust -comparing -shanghai -speaks -burton -vocational -davidson -copied -scotia -farming -gibson -pharmacies -roller -introducing -batch -organize -appreciated -alter -nicole -latino -ghana -edges -mixing -handles -skilled -fitted -albuquerque -harmony -distinguished -asthma -projected -assumptions -shareholders -twins -developmental -regulated -triangle -amend -anticipated -oriental -reward -windsor -zambia -completing -hydrogen -webshots -sprint -comparable -chick -advocate -confusion -copyrighted -inputs -warranties -genome -escorts -documented -thong -medal -paperbacks -coaches -vessels -harbour -walks -keyboards -knives -vulnerable -arrange -artistic -honors -booth -indie -reflected -unified -bones -breed -detector -ignored -polar -fallen -precise -sussex -respiratory -notifications -msgid -transexual -mainstream -invoice -evaluating -subcommittee -gather -maternity -backed -alfred -colonial -carey -motels -forming -embassy -journalists -danny -rebecca -slight -proceeds -indirect -amongst -foundations -msgstr -arrest -volleyball -adipex -horizon -deeply -toolbox -marina -liabilities -prizes -bosnia -browsers -decreased -patio -tolerance -surfing -creativity -lloyd -describing -optics -pursue -lightning -overcome -quotations -inspector -attract -brighton -beans -bookmarks -ellis -disable -snake -succeed -leonard -lending -reminder -searched -behavioral -riverside -bathrooms -plains -raymond -insights -abilities -initiated -sullivan -midwest -karaoke -lonely -nonprofit -lancaster -suspended -hereby -observe -julia -containers -attitudes -berry -collar -simultaneously -racial -integrate -bermuda -amanda -sociology -mobiles -screenshot -exhibitions -kelkoo -confident -retrieved -exhibits -officially -consortium -terrace -bacteria -replied -seafood -novels -recipients -ought -delicious -traditions -safely -finite -kidney -periodically -fixes -sends -durable -mazda -allied -throws -moisture -hungarian -roster -referring -symantec -spencer -wichita -nasdaq -uruguay -transform -timer -tablets -tuning -gotten -educators -tyler -futures -vegetable -verse -highs -humanities -independently -wanting -custody -scratch -launches -alignment -masturbating -henderson -britannica -ellen -competitors -rocket -bullet -towers -racks -nasty -visibility -latitude -consciousness -tumor -deposits -beverly -mistress -encounter -trustees -watts -duncan -reprints -bernard -resolutions -accessing -forty -tubes -attempted -midlands -priest -floyd -ronald -analysts -queue -trance -locale -nicholas -bundle -hammer -invasion -witnesses -runner -administered -notion -skins -mailed -fujitsu -spelling -arctic -exams -rewards -beneath -strengthen -defend -frederick -medicaid -infrared -seventh -welsh -belly -aggressive -advertisements -quarters -stolen -soonest -haiti -disturbed -determines -sculpture -naturals -motivation -lenders -pharmacology -fitting -fixtures -bloggers -agrees -passengers -quantities -petersburg -consistently -powerpoint -surplus -elder -sonic -obituaries -cheers -punishment -appreciation -subsequently -belarus -zoning -gravity -providence -thumb -restriction -incorporate -backgrounds -treasurer -guitars -essence -flooring -lightweight -ethiopia -mighty -athletes -humanity -transcription -holmes -complications -scholars -scripting -remembered -galaxy -chester -snapshot -caring -synthetic -segments -testament -dominant -twist -specifics -itunes -stomach -partially -buried -newbie -minimize -darwin -ranks -wilderness -debut -generations -tournaments -bradley -anatomy -sponsorship -headphones -fraction -proceeding -defects -volkswagen -uncertainty -breakdown -milton -marker -reconstruction -subsidiary -strengths -clarity -sandra -adelaide -encouraging -furnished -monaco -settled -folding -emirates -terrorists -airfare -comparisons -beneficial -distributions -vaccine -belize -viewpicture -promised -volvo -penny -robust -bookings -threatened -minolta -republicans -discusses -porter -jungle -responded -abstracts -ivory -alpine -prediction -pharmaceuticals -andale -fabulous -remix -alias -thesaurus -individually -battlefield -literally -newer -ecological -spice -implies -cooler -appraisal -consisting -maritime -periodic -submitting -overhead -ascii -prospect -shipment -breeding -citations -geographical -donor -mozambique -tension -trash -shapes -manor -envelope -diane -homeland -disclaimers -championships -excluded -andrea -breeds -rapids -disco -sheffield -bailey -endif -finishing -emotions -wellington -incoming -prospects -lexmark -cleaners -bulgarian -eternal -cashiers -aboriginal -remarkable -rotation -preventing -productive -boulevard -eugene -metric -compliant -minus -penalties -bennett -imagination -hotmail -refurbished -joshua -armenia -varied -grande -closest -activated -actress -conferencing -assign -armstrong -politicians -trackbacks -accommodate -tigers -aurora -slides -milan -premiere -lender -villages -shade -chorus -christine -rhythm -digit -argued -dietary -symphony -clarke -sudden -accepting -precipitation -marilyn -lions -findlaw -pools -lyric -claire -isolation -speeds -sustained -matched -approximate -carroll -rational -programmer -fighters -chambers -greetings -inherited -warming -incomplete -vocals -chronicle -fountain -chubby -grave -legitimate -biographies -burner -investigator -plaintiff -finnish -gentle -prisoners -deeper -muslims -mediterranean -nightlife -footage -howto -worthy -reveals -architects -saints -entrepreneur -carries -freelance -excessive -devon -screensaver -helena -saves -regarded -valuation -unexpected -cigarette -characteristic -marion -lobby -egyptian -tunisia -metallica -outlined -consequently -headline -treating -punch -appointments -gotta -cowboy -narrative -bahrain -enormous -karma -consist -betty -queens -academics -quantitative -lucas -screensavers -subdivision -tribes -defeat -clicks -distinction -honduras -naughty -hazards -insured -harper -livestock -mardi -exemption -tenant -sustainability -cabinets -tattoo -shake -algebra -shadows -holly -formatting -silly -nutritional -mercy -hartford -freely -marcus -sunrise -wrapping -nicaragua -weblogs -timeline -belongs -readily -affiliation -fence -nudist -infinite -diana -ensures -relatives -lindsay -legally -shame -satisfactory -revolutionary -bracelets -civilian -telephony -fatal -remedy -realtors -breathing -briefly -thickness -adjustments -graphical -genius -discussing -aerospace -fighter -meaningful -flesh -retreat -adapted -barely -wherever -estates -democrat -borough -maintains -failing -shortcuts -retained -voyeurweb -pamela -andrews -marble -extending -jesse -specifies -logitech -surrey -briefing -belkin -accreditation -blackberry -highland -meditation -modular -microphone -macedonia -combining -brandon -instrumental -giants -organizing -balloon -moderators -winston -solved -kazakhstan -hawaiian -standings -partition -invisible -gratuit -consoles -qatar -magnet -translations -porsche -cayman -jaguar -sheer -commodity -posing -kilometers -thanksgiving -hopkins -urgent -guarantees -infants -gothic -cylinder -witch -indication -congratulations -cohen -puppy -kathy -graphs -surround -cigarettes -revenge -expires -enemies -controllers -consultancy -finances -accepts -enjoying -conventions -patrol -smell -italiano -coordinates -carnival -roughly -sticker -promises -responding -physically -divide -stakeholders -hydrocodone -consecutive -cornell -satin -deserve -attempting -mailto -promo -representations -worried -tunes -garbage -competing -combines -bradford -phrases -peninsula -chelsea -boring -reynolds -accurately -speeches -reaches -schema -considers -catalogs -ministries -vacancies -quizzes -parliamentary -prefix -lucia -savannah -barrel -typing -nerve -planets -deficit -boulder -pointing -renew -coupled -myanmar -metadata -harold -circuits -floppy -texture -handbags -somerset -incurred -acknowledge -thoroughly -antigua -nottingham -thunder -caution -identifies -questionnaire -qualification -locks -modelling -namely -miniature -euros -interstate -pirates -aerial -consequence -rebel -systematic -perceived -origins -hired -makeup -textile -madagascar -nathan -tobago -presenting -troubleshooting -uzbekistan -indexes -centuries -magnitude -richardson -hindu -fragrances -vocabulary -licking -earthquake -fundraising -markers -weights -albania -geological -assessing -lasting -wicked -introduces -kills -roommate -webcams -pushed -webmasters -computational -acdbentity -participated -handhelds -answering -impressed -slope -reggae -failures -conspiracy -surname -theology -nails -evident -whats -rides -rehab -saturn -organizer -allergy -twisted -combinations -preceding -merit -enzyme -cumulative -zshops -planes -edmonton -tackle -disks -condo -pokemon -amplifier -ambien -arbitrary -prominent -retrieve -lexington -vernon -worldcat -titanium -fairy -builds -contacted -shaft -recorders -occasional -leslie -casio -deutsche -postings -innovations -kitty -postcards -drain -monte -fires -algeria -blessed -reviewing -cardiff -cornwall -favors -potato -panic -explicitly -sticks -leone -transsexual -citizenship -excuse -reforms -basement -onion -strand -sandwich -lawsuit -informative -girlfriend -bloomberg -cheque -hierarchy -influenced -banners -reject -abandoned -circles -italic -beats -merry -scuba -complement -passive -mauritius -valued -checklist -requesting -courage -verde -lauderdale -scenarios -gazette -hitachi -extraction -batman -elevation -hearings -coleman -utilization -beverages -calibration -efficiently -anaheim -textbook -dried -entertaining -prerequisite -luther -frontier -settle -stopping -refugees -knights -hypothesis -palmer -medicines -derby -peaceful -altered -pontiac -regression -doctrine -scenic -trainers -enhancements -renewable -intersection -passwords -sewing -consistency -collectors -conclude -recognised -munich -celebs -propose -azerbaijan -lighter -astrology -advisors -pavilion -tactics -trusts -occurring -supplemental -travelling -talented -annie -pillow -induction -derek -precisely -shorter -harley -spreading -provinces -relying -finals -paraguay -steal -parcel -refined -fifteen -widespread -incidence -fears -predict -boutique -acrylic -rolled -tuner -incidents -peterson -shannon -toddler -enhancing -flavor -alike -homeless -horrible -hungry -metallic -blocked -interference -warriors -palestine -listprice -cadillac -atmospheric -malawi -sagem -knowledgestorm -curtis -parental -referenced -strikes -lesser -publicity -marathon -proposition -pressing -gasoline -dressed -scout -belfast -dealt -niagara -warcraft -charms -catalyst -trader -bucks -allowance -denial -designation -thrown -prepaid -raises -duplicate -electro -criterion -badge -wrist -civilization -analyzed -vietnamese -heath -tremendous -ballot -lexus -varying -remedies -validity -trustee -weighted -angola -performs -plastics -realm -corrected -jenny -helmet -salaries -postcard -elephant -yemen -encountered -tsunami -scholar -nickel -internationally -surrounded -buses -expedia -geology -creatures -coating -commented -wallet -cleared -smilies -accomplish -boating -drainage -shakira -corners -broader -vegetarian -rouge -yeast -newfoundland -clearing -investigated -ambassador -coated -intend -stephanie -contacting -vegetation -findarticles -louise -kenny -specially -routines -hitting -yukon -beings -aquatic -reliance -habits -striking -infectious -podcasts -singh -gilbert -ferrari -continuity -brook -outputs -phenomenon -ensemble -insulin -assured -biblical -conscious -accent -mysimon -eleven -wives -ambient -utilize -mileage -prostate -adaptor -auburn -unlock -hyundai -pledge -vampire -angela -relates -nitrogen -xerox -merger -softball -referrals -differently -firewire -nextel -framing -organised -musician -blocking -rwanda -sorts -integrating -vsnet -limiting -dispatch -revisions -papua -restored -armor -riders -chargers -remark -dozens -varies -reasoning -rendered -picking -charitable -guards -annotated -convinced -openings -burlington -replacing -researcher -watershed -councils -occupations -acknowledged -kruger -pockets -granny -equilibrium -viral -inquire -pipes -characterized -laden -aruba -cottages -realtor -merge -privilege -edgar -develops -qualifying -chassis -dubai -estimation -pushing -fleece -pediatric -pierce -allan -dressing -techrepublic -sperm -filme -craps -frost -institutes -sally -yacht -tracy -prefers -drilling -brochures -breach -whale -traveller -appropriations -suspected -tomatoes -benchmark -beginners -instructors -highlighted -bedford -stationery -mustang -unauthorized -clusters -antibody -competent -momentum -wiring -pastor -calvin -shark -contributor -demonstrates -phases -grateful -emerald -gradually -laughing -grows -cliff -desirable -tract -ballet -journalist -abraham -bumper -afterwards -webpage -religions -garlic -hostels -shine -senegal -explosion -banned -wendy -briefs -signatures -diffs -mumbai -ozone -disciplines -daughters -conversations -radios -tariff -nvidia -opponent -pasta -simplified -muscles -serum -wrapped -swift -motherboard -runtime -inbox -focal -bibliographic -distant -champagne -decimal -deviation -superintendent -propecia -samba -hostel -housewives -employ -mongolia -penguin -magical -influences -inspections -irrigation -miracle -manually -reprint -hydraulic -centered -robertson -yearly -penetration -wound -belle -conviction -omissions -writings -hamburg -retrieval -qualities -cindy -fathers -charging -marvel -lined -prototype -importantly -petite -apparatus -terrain -explaining -strips -gossip -rangers -nomination -empirical -rotary -dependence -discrete -beginner -boxed -sexuality -polyester -cubic -commitments -suggesting -sapphire -kinase -skirts -remainder -crawford -labeled -privileges -televisions -specializing -marking -commodities -serbia -sheriff -griffin -declined -guyana -spies -neighbor -motorcycles -elect -highways -thinkpad -concentrate -intimate -reproductive -preston -deadly -bunny -chevy -molecules -rounds -longest -refrigerator -tions -intervals -sentences -dentists -exclusion -workstation -holocaust -flyer -dosage -receivers -customise -disposition -variance -navigator -investigators -cameroon -baking -marijuana -adaptive -computed -needle -baths -cathedral -brakes -nirvana -fairfield -invision -sticky -destiny -generous -madness -emacs -climb -blowing -fascinating -landscapes -heated -lafayette -jackie -computation -cardiovascular -sparc -cardiac -salvation -dover -adrian -predictions -accompanying -vatican -brutal -learners -selective -arbitration -configuring -token -editorials -sacrifice -seekers -removable -convergence -yields -gibraltar -suited -numeric -anthropology -skating -kinda -aberdeen -emperor -malpractice -dylan -belts -blacks -educated -rebates -reporters -burke -proudly -necessity -rendering -inserted -pulling -basename -obesity -curves -suburban -touring -clara -vertex -hepatitis -nationally -tomato -andorra -waterproof -expired -travels -flush -waiver -specialties -hayes -humanitarian -invitations -functioning -delight -survivor -garcia -cingular -economies -alexandria -bacterial -moses -counted -undertake -declare -continuously -johns -valves -impaired -achievements -donors -jewel -teddy -convertible -teaches -ventures -bufing -stranger -tragedy -julian -dryer -painful -velvet -tribunal -ruled -pensions -prayers -funky -secretariat -nowhere -paragraphs -joins -adolescent -nominations -wesley -lately -cancelled -scary -mattress -mpegs -brunei -likewise -banana -introductory -slovak -cakes -reservoir -occurrence -mixer -remind -worcester -sbjct -demographic -charming -tooth -disciplinary -annoying -respected -stays -disclose -affair -drove -washer -upset -restrict -springer -beside -mines -portraits -rebound -logan -mentor -interpreted -evaluations -fought -baghdad -elimination -metres -hypothetical -immigrants -complimentary -helicopter -pencil -freeze -performer -titled -commissions -sphere -powerseller -ratios -concord -graduated -endorsed -surprising -walnut -lance -ladder -italia -unnecessary -dramatically -liberia -sherman -maximize -hansen -senators -workout -yugoslavia -bleeding -characterization -colon -likelihood -lanes -purse -fundamentals -contamination -endangered -compromise -masturbation -optimize -stating -caroline -expiration -namespace -align -peripheral -bless -engaging -negotiation -crest -opponents -triumph -nominated -confidentiality -electoral -changelog -welding -deferred -alternatively -alloy -condos -plots -polished -gently -greensboro -tulsa -locking -casey -controversial -draws -fridge -blanket -bloom -simpsons -elliott -recovered -fraser -justify -upgrading -blades -loops -surge -frontpage -trauma -tahoe -advert -possess -demanding -defensive -flashers -subaru -forbidden -vanilla -programmers -monitored -installations -deutschland -picnic -souls -arrivals -spank -practitioner -motivated -smithsonian -hollow -vault -securely -examining -fioricet -groove -revelation -pursuit -delegation -wires -dictionaries -mails -backing -greenhouse -sleeps -blake -transparency -travis -endless -figured -orbit -currencies -niger -bacon -survivors -positioning -heater -colony -cannon -circus -promoted -forbes -moldova -descending -paxil -spine -trout -enclosed -temporarily -cooked -thriller -transmit -apnic -fatty -gerald -pressed -frequencies -scanned -reflections -hunger -mariah -municipality -joyce -detective -surgeon -cement -experiencing -fireplace -endorsement -planners -disputes -textiles -missile -intranet -closes -psychiatry -persistent -deborah -marco -assists -summaries -gabriel -auditor -aquarium -violin -prophet -bracket -looksmart -isaac -oxide -magnificent -colleague -naples -promptly -modems -adaptation -harmful -paintball -prozac -sexually -enclosure -dividend -newark -glucose -phantom -playback -supervisors -westminster -turtle -distances -absorption -treasures -warned -neural -fossil -hometown -badly -transcripts -apollo -disappointed -persian -continually -communist -collectible -handmade -greene -entrepreneurs -robots -grenada -creations -scoop -acquisitions -earning -mailman -sanyo -nested -biodiversity -excitement -somalia -movers -verbal -blink -presently -carlo -workflow -mysterious -novelty -bryant -tiles -voyuer -librarian -subsidiaries -switched -stockholm -tamil -garmin -fuzzy -indonesian -grams -therapist -richards -budgets -toolkit -promising -relaxation -render -carmen -thereafter -hardwood -erotica -temporal -forge -commissioners -dense -brave -forwarding -awful -nightmare -airplane -reductions -southampton -istanbul -impose -organisms -telescope -viewers -asbestos -portsmouth -meyer -enters -savage -advancement -harassment -willow -resumes -throwing -existed -generators -wagon -barbie -favour -knock -generates -potatoes -thorough -replication -inexpensive -receptors -peers -roland -optimum -interventions -quilt -huntington -creature -mounts -syracuse -internship -refresh -aluminium -snowboard -beastality -webcast -michel -evanescence -subtle -coordinated -notre -shipments -maldives -stripes -firmware -antarctica -shepherd -canberra -cradle -chancellor -mambo -flour -controversy -legendary -sympathy -choir -avoiding -beautifully -blond -expects -jumping -fabrics -antibodies -polymer -hygiene -poultry -virtue -burst -examinations -surgeons -bouquet -immunology -promotes -mandate -wiley -departmental -corpus -johnston -terminology -gentleman -fibre -reproduce -convicted -shades -indices -roommates -adware -threatening -spokesman -zoloft -activists -frankfurt -prisoner -daisy -halifax -encourages -ultram -cursor -assembled -earliest -donated -stuffed -restructuring -insects -terminals -crude -morrison -maiden -simulations -sufficiently -examines -viking -myrtle -bored -cleanup -conditional -crossword -bother -budapest -conceptual -knitting -attacked -bhutan -liechtenstein -mating -compute -redhead -arrives -translator -automobiles -tractor -allah -continent -unwrap -fares -longitude -resist -challenged -telecharger -hoped -safer -insertion -instrumentation -wagner -constraint -groundwater -touched -strengthening -cologne -wishing -ranger -smallest -insulation -newman -marsh -ricky -scared -theta -infringement -subjective -monsters -asylum -lightbox -robbie -stake -cocktail -outlets -swaziland -varieties -arbor -mediawiki -configurations -poison diff --git a/devel/devel-server.py b/devel/devel-server.py deleted file mode 100755 index 36bbab4..0000000 --- a/devel/devel-server.py +++ /dev/null @@ -1,296 +0,0 @@ -#!/usr/bin/python3 - -import cgitb -import html -import cgi -import http.server -import io -import json -import mimetypes -import moth -import logging -import os -import pathlib -import random -import shutil -import socketserver -import sys -import traceback -import mothballer -import parse -import urllib.parse -import posixpath -from http import HTTPStatus - - -sys.dont_write_bytecode = True # Don't write .pyc files - - -class MothServer(socketserver.ForkingMixIn, http.server.HTTPServer): - def __init__(self, server_address, RequestHandlerClass): - super().__init__(server_address, RequestHandlerClass) - self.args = {} - - -class MothRequestHandler(http.server.SimpleHTTPRequestHandler): - endpoints = [] - - def __init__(self, request, client_address, server): - self.directory = str(server.args["theme_dir"]) - try: - super().__init__(request, client_address, server, directory=server.args["theme_dir"]) - except TypeError: - super().__init__(request, client_address, server) - - # Why isn't this the default?! - def guess_type(self, path): - mtype, encoding = mimetypes.guess_type(path) - if encoding: - return "%s; encoding=%s" % (mtype, encoding) - else: - return mtype - - # Backport from Python 3.7 - def translate_path(self, path): - # I guess we just hope that some other thread doesn't call getcwd - getcwd = os.getcwd - os.getcwd = lambda: self.directory - ret = super().translate_path(path) - os.getcwd = getcwd - return ret - - - def get_puzzle(self): - category = self.req.get("cat") - points = int(self.req.get("points")) - catpath = str(self.server.args["puzzles_dir"].joinpath(category)) - cat = moth.Category(catpath, self.seed) - puzzle = cat.puzzle(points) - return puzzle - - - def send_json(self, obj): - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(json.dumps(obj).encode("utf-8")) - - - def handle_register(self): - # Everybody eats when they come to my house - ret = { - "status": "success", - "data": { - "short": "You win", - "description": "Welcome to the development server, you wily hacker you" - } - } - self.send_json(ret) - endpoints.append(('/{seed}/register', handle_register)) - - - def handle_answer(self): - for f in ("cat", "points", "answer"): - self.req[f] = self.fields.getfirst(f) - puzzle = self.get_puzzle() - ret = { - "status": "success", - "data": { - "short": "", - "description": "%r was not in list of answers" % self.req.get("answer") - }, - } - - if self.req.get("answer") in puzzle.answers: - ret["data"]["description"] = "Answer is correct" - self.send_json(ret) - endpoints.append(('/{seed}/answer', handle_answer)) - - - def puzzlelist(self): - puzzles = {} - for p in self.server.args["puzzles_dir"].glob("*"): - if not p.is_dir() or p.match(".*"): - continue - catName = p.parts[-1] - cat = moth.Category(str(p), self.seed) - puzzles[catName] = [[i, str(i)] for i in cat.pointvals()] - puzzles[catName].append([0, ""]) - if len(puzzles) <= 1: - logging.warning("No directories found matching {}/*".format(self.server.args["puzzles_dir"])) - - return puzzles - - - # XXX: Remove this (redundant) when we've upgraded the bundled theme (probably v3.6 and beyond) - def handle_puzzlelist(self): - self.send_json(self.puzzlelist()) - endpoints.append(('/{seed}/puzzles.json', handle_puzzlelist)) - - - def handle_state(self): - resp = { - "Config": { - "Devel": True, - }, - "Puzzles": self.puzzlelist(), - "Messages": "

[MOTH Development Server] Participant broadcast messages would go here.

", - } - self.send_json(resp) - endpoints.append(('/{seed}/state', handle_state)) - - - def handle_puzzle(self): - puzzle = self.get_puzzle() - - obj = puzzle.package() - obj["answers"] = puzzle.answers - obj["hint"] = puzzle.hint - obj["summary"] = puzzle.summary - obj["logs"] = puzzle.logs - obj["format"] = puzzle._source_format - - self.send_json(obj) - endpoints.append(('/{seed}/content/{cat}/{points}/puzzle.json', handle_puzzle)) - - - def handle_puzzlefile(self): - puzzle = self.get_puzzle() - - try: - file = puzzle.files[self.req["filename"]] - except KeyError: - self.send_error( - HTTPStatus.NOT_FOUND, - "File Not Found: %s" % self.req["filename"], - ) - return - - self.send_response(200) - self.send_header("Content-Type", mimetypes.guess_type(file.name)) - self.end_headers() - shutil.copyfileobj(file.stream, self.wfile) - endpoints.append(("/{seed}/content/{cat}/{points}/{filename}", handle_puzzlefile)) - - - def handle_mothballer(self): - category = self.req.get("cat") - - try: - catdir = self.server.args["puzzles_dir"].joinpath(category) - mb = mothballer.package(category, str(catdir), self.seed) - except Exception as ex: - logging.exception(ex) - self.send_response(500) - self.send_header("Content-Type", "text/html; charset=\"utf-8\"") - self.end_headers() - self.wfile.write(cgitb.html(sys.exc_info()).encode("utf-8")) - return - - self.send_response(200) - self.send_header("Content-Type", "application/octet_stream") - self.end_headers() - shutil.copyfileobj(mb, self.wfile) - endpoints.append(("/{seed}/mothballer/{cat}.mb", handle_mothballer)) - - - def handle_index(self): - seed = random.getrandbits(32) - self.send_response(307) - self.send_header("Location", "%s/" % seed) - self.send_header("Content-Type", "text/html") - self.end_headers() - self.wfile.write(b"Your browser was supposed to redirect you to here." % seed) - endpoints.append((r"/", handle_index)) - - - def handle_theme_file(self): - self.path = "/" + self.req.get("path", "") - super().do_GET() - endpoints.append(("/{seed}/", handle_theme_file)) - endpoints.append(("/{seed}/{path}", handle_theme_file)) - - - def do_GET(self): - self.fields = cgi.FieldStorage( - fp=self.rfile, - headers=self.headers, - environ={ - "REQUEST_METHOD": self.command, - "CONTENT_TYPE": self.headers["Content-Type"], - }, - ) - - url = urllib.parse.urlparse(self.path) - for pattern, function in self.endpoints: - result = parse.parse(pattern, url.path) - if result: - self.req = result.named - seed = self.req.get("seed", "random") - if seed == "random": - self.seed = random.getrandbits(32) - else: - self.seed = int(seed) - return function(self) - super().do_GET() - - def do_POST(self): - self.do_GET() - - def do_HEAD(self): - self.send_error( - HTTPStatus.NOT_IMPLEMENTED, - "Unsupported method (%r)" % self.command, - ) - -# I don't fully understand why you can't do this inside the class definition. -MothRequestHandler.extensions_map[".mjs"] = "application/ecmascript" - - -if __name__ == '__main__': - import argparse - - parser = argparse.ArgumentParser(description="MOTH puzzle development server") - parser.add_argument( - '--puzzles', default='puzzles', - help="Directory containing your puzzles" - ) - parser.add_argument( - '--theme', default='theme', - help="Directory containing theme files") - parser.add_argument( - '--bind', default=":8080", - help="Bind to ip:port" - ) - parser.add_argument( - '--base', default="", - help="Base URL to this server, for reverse proxy setup" - ) - parser.add_argument( - "-v", "--verbose", - action="count", - default=1, # Leave at 1, for now, to maintain current default behavior - help="Include more verbose logging. Use multiple flags to increase level", - ) - args = parser.parse_args() - parts = args.bind.split(":") - addr = parts[0] - port = int(parts[1]) - if args.verbose >= 2: - log_level = logging.DEBUG - elif args.verbose == 1: - log_level = logging.INFO - else: - log_level = logging.WARNING - - logging.basicConfig(level=log_level) - - mimetypes.add_type("application/javascript", ".mjs") - - server = MothServer((addr, port), MothRequestHandler) - server.args["base_url"] = args.base - server.args["puzzles_dir"] = pathlib.Path(args.puzzles) - server.args["theme_dir"] = args.theme - - logging.info("Listening on %s:%d", addr, port) - server.serve_forever() diff --git a/devel/mistune.py b/devel/mistune.py deleted file mode 100644 index a81c4c1..0000000 --- a/devel/mistune.py +++ /dev/null @@ -1,1190 +0,0 @@ -# coding: utf-8 -"""mistune - ~~~~~~~ - - The fastest markdown parser in pure Python with renderer feature. - - Copyright (c) 2014 - 2015, Hsiaoming Yang - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the creator nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS - BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. -""" - -import re -import inspect - -__version__ = '0.7.3' -__author__ = 'Hsiaoming Yang ' -__all__ = [ - 'BlockGrammar', 'BlockLexer', - 'InlineGrammar', 'InlineLexer', - 'Renderer', 'Markdown', - 'markdown', 'escape', -] - - -_key_pattern = re.compile(r'\s+') -_nonalpha_pattern = re.compile(r'\W') -_escape_pattern = re.compile(r'&(?!#?\w+;)') -_newline_pattern = re.compile(r'\r\n|\r') -_block_quote_leading_pattern = re.compile(r'^ *> ?', flags=re.M) -_block_code_leading_pattern = re.compile(r'^ {4}', re.M) -_inline_tags = [ - 'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data', - 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u', 'mark', - 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr', 'ins', 'del', - 'img', 'font', -] -_pre_tags = ['pre', 'script', 'style'] -_valid_end = r'(?!:/|[^\w\s@]*@)\b' -_valid_attr = r'''\s*[a-zA-Z\-](?:\=(?:"[^"]*"|'[^']*'|\d+))*''' -_block_tag = r'(?!(?:%s)\b)\w+%s' % ('|'.join(_inline_tags), _valid_end) -_scheme_blacklist = ('javascript:', 'vbscript:') - - -def _pure_pattern(regex): - pattern = regex.pattern - if pattern.startswith('^'): - pattern = pattern[1:] - return pattern - - -def _keyify(key): - return _key_pattern.sub(' ', key.lower()) - - -def escape(text, quote=False, smart_amp=True): - """Replace special characters "&", "<" and ">" to HTML-safe sequences. - - The original cgi.escape will always escape "&", but you can control - this one for a smart escape amp. - - :param quote: if set to True, " and ' will be escaped. - :param smart_amp: if set to False, & will always be escaped. - """ - if smart_amp: - text = _escape_pattern.sub('&', text) - else: - text = text.replace('&', '&') - text = text.replace('<', '<') - text = text.replace('>', '>') - if quote: - text = text.replace('"', '"') - text = text.replace("'", ''') - return text - - -def escape_link(url): - """Remove dangerous URL schemes like javascript: and escape afterwards.""" - lower_url = url.lower().strip('\x00\x1a \n\r\t') - for scheme in _scheme_blacklist: - if lower_url.startswith(scheme): - return '' - return escape(url, quote=True, smart_amp=False) - - -def preprocessing(text, tab=4): - text = _newline_pattern.sub('\n', text) - text = text.expandtabs(tab) - text = text.replace('\u00a0', ' ') - text = text.replace('\u2424', '\n') - pattern = re.compile(r'^ +$', re.M) - return pattern.sub('', text) - - -class BlockGrammar(object): - """Grammars for block level tokens.""" - - def_links = re.compile( - r'^ *\[([^^\]]+)\]: *' # [key]: - r']+)>?' # or link - r'(?: +["(]([^\n]+)[")])? *(?:\n+|$)' - ) - def_footnotes = re.compile( - r'^\[\^([^\]]+)\]: *(' - r'[^\n]*(?:\n+|$)' # [^key]: - r'(?: {1,}[^\n]*(?:\n+|$))*' - r')' - ) - - newline = re.compile(r'^\n+') - block_code = re.compile(r'^( {4}[^\n]+\n*)+') - fences = re.compile( - r'^ *(`{3,}|~{3,}) *(\S+)? *\n' # ```lang - r'([\s\S]+?)\s*' - r'\1 *(?:\n+|$)' # ``` - ) - hrule = re.compile(r'^ {0,3}[-*_](?: *[-*_]){2,} *(?:\n+|$)') - heading = re.compile(r'^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)') - lheading = re.compile(r'^([^\n]+)\n *(=|-)+ *(?:\n+|$)') - block_quote = re.compile(r'^( *>[^\n]+(\n[^\n]+)*\n*)+') - list_block = re.compile( - r'^( *)([*+-]|\d+\.) [\s\S]+?' - r'(?:' - r'\n+(?=\1?(?:[-*_] *){3,}(?:\n+|$))' # hrule - r'|\n+(?=%s)' # def links - r'|\n+(?=%s)' # def footnotes - r'|\n{2,}' - r'(?! )' - r'(?!\1(?:[*+-]|\d+\.) )\n*' - r'|' - r'\s*$)' % ( - _pure_pattern(def_links), - _pure_pattern(def_footnotes), - ) - ) - list_item = re.compile( - r'^(( *)(?:[*+-]|\d+\.) [^\n]*' - r'(?:\n(?!\2(?:[*+-]|\d+\.) )[^\n]*)*)', - flags=re.M - ) - list_bullet = re.compile(r'^ *(?:[*+-]|\d+\.) +') - paragraph = re.compile( - r'^((?:[^\n]+\n?(?!' - r'%s|%s|%s|%s|%s|%s|%s|%s|%s' - r'))+)\n*' % ( - _pure_pattern(fences).replace(r'\1', r'\2'), - _pure_pattern(list_block).replace(r'\1', r'\3'), - _pure_pattern(hrule), - _pure_pattern(heading), - _pure_pattern(lheading), - _pure_pattern(block_quote), - _pure_pattern(def_links), - _pure_pattern(def_footnotes), - '<' + _block_tag, - ) - ) - block_html = re.compile( - r'^ *(?:%s|%s|%s) *(?:\n{2,}|\s*$)' % ( - r'', - r'<(%s)((?:%s)*?)>([\s\S]*?)<\/\1>' % (_block_tag, _valid_attr), - r'<%s(?:%s)*?\s*\/?>' % (_block_tag, _valid_attr), - ) - ) - table = re.compile( - r'^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*' - ) - nptable = re.compile( - r'^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*' - ) - text = re.compile(r'^[^\n]+') - - -class BlockLexer(object): - """Block level lexer for block grammars.""" - grammar_class = BlockGrammar - - default_rules = [ - 'newline', 'hrule', 'block_code', 'fences', 'heading', - 'nptable', 'lheading', 'block_quote', - 'list_block', 'block_html', 'def_links', - 'def_footnotes', 'table', 'paragraph', 'text' - ] - - list_rules = ( - 'newline', 'block_code', 'fences', 'lheading', 'hrule', - 'block_quote', 'list_block', 'block_html', 'text', - ) - - footnote_rules = ( - 'newline', 'block_code', 'fences', 'heading', - 'nptable', 'lheading', 'hrule', 'block_quote', - 'list_block', 'block_html', 'table', 'paragraph', 'text' - ) - - def __init__(self, rules=None, **kwargs): - self.tokens = [] - self.def_links = {} - self.def_footnotes = {} - - if not rules: - rules = self.grammar_class() - - self.rules = rules - - def __call__(self, text, rules=None): - return self.parse(text, rules) - - def parse(self, text, rules=None): - text = text.rstrip('\n') - - if not rules: - rules = self.default_rules - - def manipulate(text): - for key in rules: - rule = getattr(self.rules, key) - m = rule.match(text) - if not m: - continue - getattr(self, 'parse_%s' % key)(m) - return m - return False # pragma: no cover - - while text: - m = manipulate(text) - if m is not False: - text = text[len(m.group(0)):] - continue - if text: # pragma: no cover - raise RuntimeError('Infinite loop at: %s' % text) - return self.tokens - - def parse_newline(self, m): - length = len(m.group(0)) - if length > 1: - self.tokens.append({'type': 'newline'}) - - def parse_block_code(self, m): - # clean leading whitespace - code = _block_code_leading_pattern.sub('', m.group(0)) - self.tokens.append({ - 'type': 'code', - 'lang': None, - 'text': code, - }) - - def parse_fences(self, m): - self.tokens.append({ - 'type': 'code', - 'lang': m.group(2), - 'text': m.group(3), - }) - - def parse_heading(self, m): - self.tokens.append({ - 'type': 'heading', - 'level': len(m.group(1)), - 'text': m.group(2), - }) - - def parse_lheading(self, m): - """Parse setext heading.""" - self.tokens.append({ - 'type': 'heading', - 'level': 1 if m.group(2) == '=' else 2, - 'text': m.group(1), - }) - - def parse_hrule(self, m): - self.tokens.append({'type': 'hrule'}) - - def parse_list_block(self, m): - bull = m.group(2) - self.tokens.append({ - 'type': 'list_start', - 'ordered': '.' in bull, - }) - cap = m.group(0) - self._process_list_item(cap, bull) - self.tokens.append({'type': 'list_end'}) - - def _process_list_item(self, cap, bull): - cap = self.rules.list_item.findall(cap) - - _next = False - length = len(cap) - - for i in range(length): - item = cap[i][0] - - # remove the bullet - space = len(item) - item = self.rules.list_bullet.sub('', item) - - # outdent - if '\n ' in item: - space = space - len(item) - pattern = re.compile(r'^ {1,%d}' % space, flags=re.M) - item = pattern.sub('', item) - - # determine whether item is loose or not - loose = _next - if not loose and re.search(r'\n\n(?!\s*$)', item): - loose = True - - rest = len(item) - if i != length - 1 and rest: - _next = item[rest - 1] == '\n' - if not loose: - loose = _next - - if loose: - t = 'loose_item_start' - else: - t = 'list_item_start' - - self.tokens.append({'type': t}) - # recurse - self.parse(item, self.list_rules) - self.tokens.append({'type': 'list_item_end'}) - - def parse_block_quote(self, m): - self.tokens.append({'type': 'block_quote_start'}) - # clean leading > - cap = _block_quote_leading_pattern.sub('', m.group(0)) - self.parse(cap) - self.tokens.append({'type': 'block_quote_end'}) - - def parse_def_links(self, m): - key = _keyify(m.group(1)) - self.def_links[key] = { - 'link': m.group(2), - 'title': m.group(3), - } - - def parse_def_footnotes(self, m): - key = _keyify(m.group(1)) - if key in self.def_footnotes: - # footnote is already defined - return - - self.def_footnotes[key] = 0 - - self.tokens.append({ - 'type': 'footnote_start', - 'key': key, - }) - - text = m.group(2) - - if '\n' in text: - lines = text.split('\n') - whitespace = None - for line in lines[1:]: - space = len(line) - len(line.lstrip()) - if space and (not whitespace or space < whitespace): - whitespace = space - newlines = [lines[0]] - for line in lines[1:]: - newlines.append(line[whitespace:]) - text = '\n'.join(newlines) - - self.parse(text, self.footnote_rules) - - self.tokens.append({ - 'type': 'footnote_end', - 'key': key, - }) - - def parse_table(self, m): - item = self._process_table(m) - - cells = re.sub(r'(?: *\| *)?\n$', '', m.group(3)) - cells = cells.split('\n') - for i, v in enumerate(cells): - v = re.sub(r'^ *\| *| *\| *$', '', v) - cells[i] = re.split(r' *\| *', v) - - item['cells'] = cells - self.tokens.append(item) - - def parse_nptable(self, m): - item = self._process_table(m) - - cells = re.sub(r'\n$', '', m.group(3)) - cells = cells.split('\n') - for i, v in enumerate(cells): - cells[i] = re.split(r' *\| *', v) - - item['cells'] = cells - self.tokens.append(item) - - def _process_table(self, m): - header = re.sub(r'^ *| *\| *$', '', m.group(1)) - header = re.split(r' *\| *', header) - align = re.sub(r' *|\| *$', '', m.group(2)) - align = re.split(r' *\| *', align) - - for i, v in enumerate(align): - if re.search(r'^ *-+: *$', v): - align[i] = 'right' - elif re.search(r'^ *:-+: *$', v): - align[i] = 'center' - elif re.search(r'^ *:-+ *$', v): - align[i] = 'left' - else: - align[i] = None - - item = { - 'type': 'table', - 'header': header, - 'align': align, - } - return item - - def parse_block_html(self, m): - tag = m.group(1) - if not tag: - text = m.group(0) - self.tokens.append({ - 'type': 'close_html', - 'text': text - }) - else: - attr = m.group(2) - text = m.group(3) - self.tokens.append({ - 'type': 'open_html', - 'tag': tag, - 'extra': attr, - 'text': text - }) - - def parse_paragraph(self, m): - text = m.group(1).rstrip('\n') - self.tokens.append({'type': 'paragraph', 'text': text}) - - def parse_text(self, m): - text = m.group(0) - self.tokens.append({'type': 'text', 'text': text}) - - -class InlineGrammar(object): - """Grammars for inline level tokens.""" - - escape = re.compile(r'^\\([\\`*{}\[\]()#+\-.!_>~|])') # \* \+ \! .... - inline_html = re.compile( - r'^(?:%s|%s|%s)' % ( - r'', - r'<(\w+%s)((?:%s)*?)\s*>([\s\S]*?)<\/\1>' % (_valid_end, _valid_attr), - r'<\w+%s(?:%s)*?\s*\/?>' % (_valid_end, _valid_attr), - ) - ) - autolink = re.compile(r'^<([^ >]+(@|:)[^ >]+)>') - link = re.compile( - r'^!?\[(' - r'(?:\[[^^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*' - r')\]\(' - r'''\s*(<)?([\s\S]*?)(?(2)>)(?:\s+['"]([\s\S]*?)['"])?\s*''' - r'\)' - ) - reflink = re.compile( - r'^!?\[(' - r'(?:\[[^^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*' - r')\]\s*\[([^^\]]*)\]' - ) - nolink = re.compile(r'^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]') - url = re.compile(r'''^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])''') - double_emphasis = re.compile( - r'^_{2}([\s\S]+?)_{2}(?!_)' # __word__ - r'|' - r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word** - ) - emphasis = re.compile( - r'^\b_((?:__|[^_])+?)_\b' # _word_ - r'|' - r'^\*((?:\*\*|[^\*])+?)\*(?!\*)' # *word* - ) - code = re.compile(r'^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)') # `code` - linebreak = re.compile(r'^ {2,}\n(?!\s*$)') - strikethrough = re.compile(r'^~~(?=\S)([\s\S]*?\S)~~') # ~~word~~ - footnote = re.compile(r'^\[\^([^\]]+)\]') - text = re.compile(r'^[\s\S]+?(?=[\\%s' % (tag, extra, text, tag) - else: - html = m.group(0) - return self.renderer.inline_html(html) - - def output_footnote(self, m): - key = _keyify(m.group(1)) - if key not in self.footnotes: - return None - if self.footnotes[key]: - return None - self.footnote_index += 1 - self.footnotes[key] = self.footnote_index - return self.renderer.footnote_ref(key, self.footnote_index) - - def output_link(self, m): - return self._process_link(m, m.group(3), m.group(4)) - - def output_reflink(self, m): - key = _keyify(m.group(2) or m.group(1)) - if key not in self.links: - return None - ret = self.links[key] - return self._process_link(m, ret['link'], ret['title']) - - def output_nolink(self, m): - key = _keyify(m.group(1)) - if key not in self.links: - return None - ret = self.links[key] - return self._process_link(m, ret['link'], ret['title']) - - def _process_link(self, m, link, title=None): - line = m.group(0) - text = m.group(1) - if line[0] == '!': - return self.renderer.image(link, title, text) - - self._in_link = True - text = self.output(text) - self._in_link = False - return self.renderer.link(link, title, text) - - def output_double_emphasis(self, m): - text = m.group(2) or m.group(1) - text = self.output(text) - return self.renderer.double_emphasis(text) - - def output_emphasis(self, m): - text = m.group(2) or m.group(1) - text = self.output(text) - return self.renderer.emphasis(text) - - def output_code(self, m): - text = m.group(2) - return self.renderer.codespan(text) - - def output_linebreak(self, m): - return self.renderer.linebreak() - - def output_strikethrough(self, m): - text = self.output(m.group(1)) - return self.renderer.strikethrough(text) - - def output_text(self, m): - text = m.group(0) - return self.renderer.text(text) - - -class Renderer(object): - """The default HTML renderer for rendering Markdown. - """ - - def __init__(self, **kwargs): - self.options = kwargs - - def placeholder(self): - """Returns the default, empty output value for the renderer. - - All renderer methods use the '+=' operator to append to this value. - Default is a string so rendering HTML can build up a result string with - the rendered Markdown. - - Can be overridden by Renderer subclasses to be types like an empty - list, allowing the renderer to create a tree-like structure to - represent the document (which can then be reprocessed later into a - separate format like docx or pdf). - """ - return '' - - def block_code(self, code, lang=None): - """Rendering block level code. ``pre > code``. - - :param code: text content of the code block. - :param lang: language of the given code. - """ - code = code.rstrip('\n') - if not lang: - code = escape(code, smart_amp=False) - return '
%s\n
\n' % code - code = escape(code, quote=True, smart_amp=False) - return '
%s\n
\n' % (lang, code) - - def block_quote(self, text): - """Rendering
with the given text. - - :param text: text content of the blockquote. - """ - return '
%s\n
\n' % text.rstrip('\n') - - def block_html(self, html): - """Rendering block level pure html content. - - :param html: text content of the html snippet. - """ - if self.options.get('skip_style') and \ - html.lower().startswith('`` ``

``. - - :param text: rendered text content for the header. - :param level: a number for the header level, for example: 1. - :param raw: raw text content of the header. - """ - return '%s\n' % (level, text, level) - - def hrule(self): - """Rendering method for ``
`` tag.""" - if self.options.get('use_xhtml'): - return '
\n' - return '
\n' - - def list(self, body, ordered=True): - """Rendering list tags like ``
    `` and ``
      ``. - - :param body: body contents of the list. - :param ordered: whether this list is ordered or not. - """ - tag = 'ul' - if ordered: - tag = 'ol' - return '<%s>\n%s\n' % (tag, body, tag) - - def list_item(self, text): - """Rendering list item snippet. Like ``
    1. ``.""" - return '
    2. %s
    3. \n' % text - - def paragraph(self, text): - """Rendering paragraph tags. Like ``

      ``.""" - return '

      %s

      \n' % text.strip(' ') - - def table(self, header, body): - """Rendering table element. Wrap header and body in it. - - :param header: header part of the table. - :param body: body part of the table. - """ - return ( - '\n%s\n' - '\n%s\n
      \n' - ) % (header, body) - - def table_row(self, content): - """Rendering a table row. Like ````. - - :param content: content of current table row. - """ - return '\n%s\n' % content - - def table_cell(self, content, **flags): - """Rendering a table cell. Like ```` ````. - - :param content: content of current table cell. - :param header: whether this is header or not. - :param align: align of current table cell. - """ - if flags['header']: - tag = 'th' - else: - tag = 'td' - align = flags['align'] - if not align: - return '<%s>%s\n' % (tag, content, tag) - return '<%s style="text-align:%s">%s\n' % ( - tag, align, content, tag - ) - - def double_emphasis(self, text): - """Rendering **strong** text. - - :param text: text content for emphasis. - """ - return '%s' % text - - def emphasis(self, text): - """Rendering *emphasis* text. - - :param text: text content for emphasis. - """ - return '%s' % text - - def codespan(self, text): - """Rendering inline `code` text. - - :param text: text content for inline code. - """ - text = escape(text.rstrip(), smart_amp=False) - return '%s' % text - - def linebreak(self): - """Rendering line break like ``
      ``.""" - if self.options.get('use_xhtml'): - return '
      \n' - return '
      \n' - - def strikethrough(self, text): - """Rendering ~~strikethrough~~ text. - - :param text: text content for strikethrough. - """ - return '%s' % text - - def text(self, text): - """Rendering unformatted text. - - :param text: text content. - """ - return escape(text) - - def escape(self, text): - """Rendering escape sequence. - - :param text: text content. - """ - return escape(text) - - def autolink(self, link, is_email=False): - """Rendering a given link or email address. - - :param link: link content or email address. - :param is_email: whether this is an email or not. - """ - text = link = escape(link) - if is_email: - link = 'mailto:%s' % link - return '%s' % (link, text) - - def link(self, link, title, text): - """Rendering a given link with content and title. - - :param link: href link for ```` tag. - :param title: title content for `title` attribute. - :param text: text content for description. - """ - link = escape_link(link) - if not title: - return '%s' % (link, text) - title = escape(title, quote=True) - return '%s' % (link, title, text) - - def image(self, src, title, text): - """Rendering a image with title and text. - - :param src: source link of the image. - :param title: title text of the image. - :param text: alt text of the image. - """ - src = escape_link(src) - text = escape(text, quote=True) - if title: - title = escape(title, quote=True) - html = '%s' % html - return '%s>' % html - - def inline_html(self, html): - """Rendering span level pure html content. - - :param html: text content of the html snippet. - """ - if self.options.get('escape'): - return escape(html) - return html - - def newline(self): - """Rendering newline element.""" - return '' - - def footnote_ref(self, key, index): - """Rendering the ref anchor of a footnote. - - :param key: identity key for the footnote. - :param index: the index count of current footnote. - """ - html = ( - '' - '%d' - ) % (escape(key), escape(key), index) - return html - - def footnote_item(self, key, text): - """Rendering a footnote item. - - :param key: identity key for the footnote. - :param text: text content of the footnote. - """ - back = ( - '' - ) % escape(key) - text = text.rstrip() - if text.endswith('

      '): - text = re.sub(r'<\/p>$', r'%s

      ' % back, text) - else: - text = '%s

      %s

      ' % (text, back) - html = '
    4. %s
    5. \n' % (escape(key), text) - return html - - def footnotes(self, text): - """Wrapper for all footnotes. - - :param text: contents of all footnotes. - """ - html = '
      \n%s
        %s
      \n
      \n' - return html % (self.hrule(), text) - - -class Markdown(object): - """The Markdown parser. - - :param renderer: An instance of ``Renderer``. - :param inline: An inline lexer class or instance. - :param block: A block lexer class or instance. - """ - def __init__(self, renderer=None, inline=None, block=None, **kwargs): - if not renderer: - renderer = Renderer(**kwargs) - else: - kwargs.update(renderer.options) - - self.renderer = renderer - - if inline and inspect.isclass(inline): - inline = inline(renderer, **kwargs) - if block and inspect.isclass(block): - block = block(**kwargs) - - if inline: - self.inline = inline - else: - self.inline = InlineLexer(renderer, **kwargs) - - self.block = block or BlockLexer(BlockGrammar()) - self.footnotes = [] - self.tokens = [] - - # detect if it should parse text in block html - self._parse_block_html = kwargs.get('parse_block_html') - - def __call__(self, text): - return self.parse(text) - - def render(self, text): - """Render the Markdown text. - - :param text: markdown formatted text content. - """ - return self.parse(text) - - def parse(self, text): - out = self.output(preprocessing(text)) - - keys = self.block.def_footnotes - - # reset block - self.block.def_links = {} - self.block.def_footnotes = {} - - # reset inline - self.inline.links = {} - self.inline.footnotes = {} - - if not self.footnotes: - return out - - footnotes = filter(lambda o: keys.get(o['key']), self.footnotes) - self.footnotes = sorted( - footnotes, key=lambda o: keys.get(o['key']), reverse=True - ) - - body = self.renderer.placeholder() - while self.footnotes: - note = self.footnotes.pop() - body += self.renderer.footnote_item( - note['key'], note['text'] - ) - - out += self.renderer.footnotes(body) - return out - - def pop(self): - if not self.tokens: - return None - self.token = self.tokens.pop() - return self.token - - def peek(self): - if self.tokens: - return self.tokens[-1] - return None # pragma: no cover - - def output(self, text, rules=None): - self.tokens = self.block(text, rules) - self.tokens.reverse() - - self.inline.setup(self.block.def_links, self.block.def_footnotes) - - out = self.renderer.placeholder() - while self.pop(): - out += self.tok() - return out - - def tok(self): - t = self.token['type'] - - # sepcial cases - if t.endswith('_start'): - t = t[:-6] - - return getattr(self, 'output_%s' % t)() - - def tok_text(self): - text = self.token['text'] - while self.peek()['type'] == 'text': - text += '\n' + self.pop()['text'] - return self.inline(text) - - def output_newline(self): - return self.renderer.newline() - - def output_hrule(self): - return self.renderer.hrule() - - def output_heading(self): - return self.renderer.header( - self.inline(self.token['text']), - self.token['level'], - self.token['text'], - ) - - def output_code(self): - return self.renderer.block_code( - self.token['text'], self.token['lang'] - ) - - def output_table(self): - aligns = self.token['align'] - aligns_length = len(aligns) - cell = self.renderer.placeholder() - - # header part - header = self.renderer.placeholder() - for i, value in enumerate(self.token['header']): - align = aligns[i] if i < aligns_length else None - flags = {'header': True, 'align': align} - cell += self.renderer.table_cell(self.inline(value), **flags) - - header += self.renderer.table_row(cell) - - # body part - body = self.renderer.placeholder() - for i, row in enumerate(self.token['cells']): - cell = self.renderer.placeholder() - for j, value in enumerate(row): - align = aligns[j] if j < aligns_length else None - flags = {'header': False, 'align': align} - cell += self.renderer.table_cell(self.inline(value), **flags) - body += self.renderer.table_row(cell) - - return self.renderer.table(header, body) - - def output_block_quote(self): - body = self.renderer.placeholder() - while self.pop()['type'] != 'block_quote_end': - body += self.tok() - return self.renderer.block_quote(body) - - def output_list(self): - ordered = self.token['ordered'] - body = self.renderer.placeholder() - while self.pop()['type'] != 'list_end': - body += self.tok() - return self.renderer.list(body, ordered) - - def output_list_item(self): - body = self.renderer.placeholder() - while self.pop()['type'] != 'list_item_end': - if self.token['type'] == 'text': - body += self.tok_text() - else: - body += self.tok() - - return self.renderer.list_item(body) - - def output_loose_item(self): - body = self.renderer.placeholder() - while self.pop()['type'] != 'list_item_end': - body += self.tok() - return self.renderer.list_item(body) - - def output_footnote(self): - self.inline._in_footnote = True - body = self.renderer.placeholder() - key = self.token['key'] - while self.pop()['type'] != 'footnote_end': - body += self.tok() - self.footnotes.append({'key': key, 'text': body}) - self.inline._in_footnote = False - return self.renderer.placeholder() - - def output_close_html(self): - text = self.token['text'] - return self.renderer.block_html(text) - - def output_open_html(self): - text = self.token['text'] - tag = self.token['tag'] - if self._parse_block_html and tag not in _pre_tags: - text = self.inline(text, rules=self.inline.inline_html_rules) - extra = self.token.get('extra') or '' - html = '<%s%s>%s' % (tag, extra, text, tag) - return self.renderer.block_html(html) - - def output_paragraph(self): - return self.renderer.paragraph(self.inline(self.token['text'])) - - def output_text(self): - return self.renderer.paragraph(self.tok_text()) - - -def markdown(text, escape=True, **kwargs): - """Render markdown formatted text to html. - - :param text: markdown formatted text content. - :param escape: if set to False, all html tags will not be escaped. - :param use_xhtml: output with xhtml tags. - :param hard_wrap: if set to True, it will use the GFM line breaks feature. - :param parse_block_html: parse text only in block level html. - :param parse_inline_html: parse text only in inline level html. - """ - return Markdown(escape=escape, **kwargs)(text) diff --git a/devel/moth.py b/devel/moth.py deleted file mode 100644 index 7bcf8f5..0000000 --- a/devel/moth.py +++ /dev/null @@ -1,508 +0,0 @@ -#!/usr/bin/python3 - -import argparse -import contextlib -import copy -import glob -import hashlib -import html -import io -import importlib.machinery -import logging -import mistune -import os -import random -import string -import sys -import tempfile -import shlex -import yaml - -messageChars = b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' - -LOGGER = logging.getLogger(__name__) - -def sha256hash(str): - return hashlib.sha256(str.encode("utf-8")).hexdigest() - -@contextlib.contextmanager -def pushd(newdir): - newdir = str(newdir) - curdir = os.getcwd() - LOGGER.debug("Attempting to chdir from %s to %s" % (curdir, newdir)) - os.chdir(newdir) - - # Force a copy of the old path, instead of just a reference - old_path = list(sys.path) - old_modules = copy.copy(sys.modules) - sys.path.append(newdir) - - try: - yield - finally: - # Restore the old path - to_remove = [] - for module in sys.modules: - if module not in old_modules: - to_remove.append(module) - - for module in to_remove: - del(sys.modules[module]) - - sys.path = old_path - LOGGER.debug("Changing directory back from %s to %s" % (newdir, curdir)) - os.chdir(curdir) - - -def loadmod(name, path): - abspath = os.path.abspath(path) - loader = importlib.machinery.SourceFileLoader(name, abspath) - return loader.load_module() - - -# 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'))] - -class PuzzleFile: - """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.""" - - def __init__(self, stream, name, visible=True): - self.stream = stream - self.name = name - self.visible = visible - -class PuzzleSuccess(dict): - """Puzzle success objectives - - :param acceptable: Learning outcome from acceptable knowledge of the subject matter - :param mastery: Learning outcome from mastery of the subject matter - """ - - valid_fields = ["acceptable", "mastery"] - - def __init__(self, **kwargs): - super(PuzzleSuccess, self).__init__() - for key in self.valid_fields: - self[key] = None - for key, value in kwargs.items(): - if key in self.valid_fields: - self[key] = value - - def __getattr__(self, attr): - if attr in self.valid_fields: - return self[attr] - raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, attr)) - - def __setattr__(self, attr, value): - if attr in self.valid_fields: - self[attr] = value - else: - raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, attr)) - - -class Puzzle: - def __init__(self, category_seed, points): - """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 points: The point value of the puzzle. - """ - - super().__init__() - - self._source_format = "py" - - self.points = points - self.summary = None - self.authors = [] - self.answers = [] - self.xAnchors = {"begin", "end"} - self.scripts = [] - self.pattern = None - self.hint = None - self.files = {} - self.body = io.StringIO() - - # NIST NICE objective content - self.objective = None # Text describing the expected learning outcome from solving this puzzle, *why* are you solving this puzzle - self.success = PuzzleSuccess() # Text describing criteria for different levels of success, e.g. {"Acceptable": "Did OK", "Mastery": "Did even better"} - self.solution = None # Text describing how to solve the puzzle - self.ksas = [] # A list of references to related NICE KSAs (e.g. K0058, . . .) - - self.logs = [] - self.randseed = category_seed * self.points - self.rand = random.Random(self.randseed) - - def log(self, *vals): - """Add a new log message to this puzzle.""" - msg = ' '.join(str(v) for v in vals) - self.logs.append(msg) - - def read_stream(self, stream): - header = True - line = "" - if stream.read(3) == "---": - header = "yaml" - self._source_format = "yaml" - else: - header = "moth" - self._source_format = "moth" - - stream.seek(0) - - if header == "yaml": - LOGGER.info("Puzzle is YAML-formatted") - self.read_yaml_header(stream) - elif header == "moth": - LOGGER.info("Puzzle is MOTH-formatted") - self.read_moth_header(stream) - - for line in stream: - self.body.write(line) - - def read_yaml_header(self, stream): - contents = "" - header = False - for line in stream: - if line.strip() == "---" and header: # Handle last line - break - elif line.strip() == "---": # Handle first line - header = True - continue - else: - contents += line - - config = yaml.safe_load(contents) - for key, value in config.items(): - key = key.lower() - self.handle_header_key(key, value) - - def read_moth_header(self, stream): - for line in stream: - line = line.strip() - if not line: - break - - key, val = line.split(':', 1) - key = key.lower() - val = val.strip() - self.handle_header_key(key, val) - - def handle_header_key(self, key, val): - LOGGER.debug("Handling key: %s, value: %s", key, val) - if key == 'author': - self.authors.append(val) - elif key == 'authors': - if not isinstance(val, list): - raise ValueError("Authors must be a list, got %s, instead" & (type(val),)) - self.authors = list(val) - elif key == 'summary': - self.summary = val - elif key == 'answer': - if not isinstance(val, str): - raise ValueError("Answers must be strings, got %s, instead" % (type(val),)) - self.answers.append(val) - elif key == 'x-answer-pattern': - a = val.strip("*") - assert "*" not in a, "Patterns may only have * at the beginning and end" - assert "?" not in a, "Patterns do not currently support ? characters" - assert "[" not in a, "Patterns do not currently support character ranges" - self.answers.append(a) - if val.startswith("*"): - self.xAnchors.discard("begin") - if val.endswith("*"): - self.xAnchors.discard("end") - elif key == "answers": - for answer in val: - if not isinstance(answer, str): - raise ValueError("Answers must be strings, got %s, instead" % (type(answer),)) - self.answers.append(answer) - elif key == 'pattern': - self.pattern = val - elif key == 'hint': - self.hint = val - elif key == 'name': - pass - elif key == 'file': - parts = shlex.split(val) - name = parts[0] - hidden = False - LOGGER.debug("Attempting to open %s", os.path.abspath(name)) - stream = open(name, 'rb') - try: - name = parts[1] - hidden = (parts[2].lower() == "hidden") - except IndexError: - pass - self.files[name] = PuzzleFile(stream, name, not hidden) - - elif key == 'files' and isinstance(val, dict): - for filename, options in val.items(): - if not options: - options = {} - source = options.get("source", filename) - hidden = options.get("hidden", False) - - stream = open(source, "rb") - self.files[filename] = PuzzleFile(stream, filename, not hidden) - - elif key == 'files' and isinstance(val, list): - for filename in val: - stream = open(filename, "rb") - self.files[filename] = PuzzleFile(stream, filename) - - elif key == 'script': - stream = open(val, 'rb') - self.add_script_stream(stream, val) - - elif key == "scripts" and isinstance(val, list): - for script in val: - stream = open(script, "rb") - self.add_script_stream(stream, script) - - elif key == "objective": - self.objective = val - elif key == "success": - # Force success dictionary keys to be lower-case - self.success = dict((x.lower(), y) for x,y in val.items()) - elif key == "success.acceptable": - self.success.acceptable = val - elif key == "success.mastery": - self.success.mastery = val - elif key == "solution": - self.solution = val - elif key == "ksas": - if not isinstance(val, list): - raise ValueError("KSAs must be a list, got %s, instead" & (type(val),)) - self.ksas = val - elif key == "ksa": - self.ksas.append(val) - else: - raise ValueError("Unrecognized header field: {}".format(key)) - - - def read_directory(self, path): - try: - puzzle_mod = loadmod("puzzle", os.path.join(path, "puzzle.py")) - except FileNotFoundError: - puzzle_mod = None - - with pushd(path): - if puzzle_mod: - puzzle_mod.make(self) - elif os.path.exists('puzzle.moth'): - with open('puzzle.moth') as f: - self.read_stream(f) - else: - self.authors = ["boggarts"] - self.body.write("This puzzle is broken! It has no puzzle.py or puzzle.moth.") - - def random_hash(self): - """Create a file basename (no extension) with our number generator.""" - return ''.join(self.rand.choice(string.ascii_lowercase) for i in range(8)) - - 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 - """ - - stream = tempfile.TemporaryFile() - self.add_stream(stream, name, visible) - return stream - - def add_script_stream(self, stream, name): - # Make sure this shows up in the header block of the HTML output. - self.files[name] = PuzzleFile(stream, name, visible=False) - self.scripts.append(name) - - def add_stream(self, stream, name=None, visible=True): - if name is None: - name = self.random_hash() - self.files[name] = PuzzleFile(stream, name, visible) - - def create_stream(self, name=None, visible=True): - stream = io.BytesIO() - self.add_stream(stream, name, visible) - return stream - - def add_file(self, filename, visible=True): - fd = open(filename, 'rb') - name = os.path.basename(filename) - self.add_stream(fd, name=name, visible=visible) - - def randword(self): - """Return a randomly-chosen word""" - - return self.rand.choice(ANSWER_WORDS) - - def make_answer(self, word_count=4, 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 - """ - - words = [self.randword() for i in range(word_count)] - answer = sep.join(words) - self.answers.append(answer) - return answer - - hexdump_stdch = stdch = ( - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - ' !"#$%&\'()*+,-./' - '0123456789:;<=>?' - '@ABCDEFGHIJKLMNO' - 'PQRSTUVWXYZ[\]^_' - '`abcdefghijklmno' - 'pqrstuvwxyz{|}~Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - ) - - def hexdump(self, buf, charset=hexdump_stdch, gap=('ļæ½', 'āŒ·')): - hexes, chars = [], [] - out = [] - - for b in buf: - if len(chars) == 16: - out.append((hexes, chars)) - hexes, chars = [], [] - - if b is None: - h, c = gap - else: - h = '{:02x}'.format(b) - c = charset[b] - chars.append(c) - hexes.append(h) - - out.append((hexes, chars)) - - offset = 0 - elided = False - lastchars = None - self.body.write('
      ')
      -        for hexes, chars in out:
      -            if chars == lastchars:
      -                offset += len(chars)
      -                if not elided:
      -                    self.body.write('*\n')
      -                    elided = True
      -                continue
      -            lastchars = chars[:]
      -            elided = False
      -
      -            pad = 16 - len(chars)
      -            hexes += ['  '] * pad
      -
      -            self.body.write('{:08x}  '.format(offset))
      -            self.body.write(' '.join(hexes[:8]))
      -            self.body.write('  ')
      -            self.body.write(' '.join(hexes[8:]))
      -            self.body.write('  |')
      -            self.body.write(html.escape(''.join(chars)))
      -            self.body.write('|\n')
      -            offset += len(chars)
      -        self.body.write('{:08x}\n'.format(offset))
      -        self.body.write('
      ') - - def get_authors(self): - if len(self.authors) > 0: - return self.authors - elif hasattr(self, "author"): - return [self.author] - else: - return [] - - def get_body(self): - return self.body.getvalue() - - def html_body(self): - """Format and return the markdown for the puzzle body.""" - return mistune.markdown(self.get_body(), escape=False) - - def package(self, answers=False): - """Return a dict packaging of the puzzle.""" - - files = sorted([fn for fn,f in self.files.items() if f.visible]) - hidden = [fn for fn,f in self.files.items() if not f.visible] - return { - 'authors': self.get_authors(), - 'hashes': self.hashes(), - 'files': files, - 'hidden': hidden, - 'scripts': self.scripts, - 'pattern': self.pattern, - 'body': self.html_body(), - 'objective': self.objective, - 'success': self.success, - 'solution': self.solution, - 'ksas': self.ksas, - 'xAnchors': list(self.xAnchors), - } - - def hashes(self): - "Return a list of answer hashes" - - return [sha256hash(a) for a in self.answers] - - -class Category: - def __init__(self, path, seed): - self.path = path - self.seed = seed - self.catmod = None - - try: - self.catmod = loadmod('category', os.path.join(path, 'category.py')) - except FileNotFoundError: - self.catmod = None - - def pointvals(self): - if self.catmod: - with pushd(self.path): - pointvals = self.catmod.pointvals() - else: - pointvals = [] - for fpath in glob.glob(os.path.join(self.path, "[0-9]*")): - pn = os.path.basename(fpath) - points = int(pn) - pointvals.append(points) - return sorted(pointvals) - - def puzzle(self, points): - puzzle = Puzzle(self.seed, points) - path = os.path.join(self.path, str(points)) - if self.catmod: - with pushd(self.path): - self.catmod.make(points, puzzle) - else: - puzzle.read_directory(path) - return puzzle - - def __iter__(self): - for points in self.pointvals(): - yield self.puzzle(points) diff --git a/devel/mothballer.py b/devel/mothballer.py deleted file mode 100755 index c428d51..0000000 --- a/devel/mothballer.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import binascii -import datetime -import hashlib -import io -import json -import logging -import moth -import os -import platform -import shutil -import tempfile -import zipfile -import random - -SEEDFN = "SEED" - - -def write_kv_pairs(ziphandle, filename, kv): - """ Write out a sorted map to file - :param ziphandle: a zipfile object - :param filename: The filename to write within the zipfile object - :param kv: the map to write out - :return: - """ - filehandle = io.StringIO() - for key in sorted(kv.keys()): - if isinstance(kv[key], list): - for val in kv[key]: - filehandle.write("%s %s\n" % (key, val)) - else: - filehandle.write("%s %s\n" % (key, kv[key])) - filehandle.seek(0) - ziphandle.writestr(filename, filehandle.read()) - - -def escape(s): - return s.replace('&', '&').replace('<', '<').replace('>', '>') - - -def build_category(categorydir, outdir): - category_seed = random.getrandbits(32) - - categoryname = os.path.basename(categorydir.strip(os.sep)) - zipfilename = os.path.join(outdir, "%s.mb" % categoryname) - logging.info("Building {} from {}".format(zipfilename, categorydir)) - - if os.path.exists(zipfilename): - # open and gather some state - existing = zipfile.ZipFile(zipfilename, 'r') - try: - category_seed = int(existing.open(SEEDFN).read().strip()) - except Exception: - pass - existing.close() - logging.debug("Using PRNG seed {}".format(category_seed)) - - zipfileraw = tempfile.NamedTemporaryFile(delete=False) - mothball = package(categoryname, categorydir, category_seed) - shutil.copyfileobj(mothball, zipfileraw) - zipfileraw.close() - shutil.move(zipfileraw.name, zipfilename) - -def write_metadata(ziphandle, category): - metadata = {"platform": {}, "moth": {}, "category": {}} - - try: - with open("../VERSION", "r") as infile: - version = infile.read().strip() - metadata["moth"]["version"] = version - except IOError: - pass - - metadata["category"]["build_time"] = datetime.datetime.now().strftime("%c") - metadata["category"]["type"] = "catmod" if category.catmod is not None else "traditional" - metadata["platform"]["arch"] = platform.machine() - metadata["platform"]["os"] = platform.system() - metadata["platform"]["version"] = platform.platform() - metadata["platform"]["python_version"] = platform.python_version() - - ziphandle.writestr("meta.json", json.dumps(metadata)) - -# Returns a file-like object containing the contents of the new zip file -def package(categoryname, categorydir, seed): - zfraw = io.BytesIO() - zf = zipfile.ZipFile(zfraw, 'x') - zf.writestr("category_seed.txt", str(seed)) - - cat = moth.Category(categorydir, seed) - answers = {} - summary = {} - puzzles = [] - for puzzle in cat: - logging.info("Processing point value {}".format(puzzle.points)) - - puzzles.append(puzzle.points) - answers[puzzle.points] = puzzle.answers - summary[puzzle.points] = puzzle.summary - - puzzledir = os.path.join("content", str(puzzle.points)) - for fn, f in puzzle.files.items(): - payload = f.stream.read() - zf.writestr(os.path.join(puzzledir, fn), payload) - - obj = puzzle.package() - zf.writestr(os.path.join(puzzledir, 'puzzle.json'), json.dumps(obj)) - - zf.writestr("puzzles.txt", "\n".join(str(p) for p in puzzles) + "\n") - write_kv_pairs(zf, 'answers.txt', answers) - write_kv_pairs(zf, 'summaries.txt', summary) - write_metadata(zf, cat) - - # clean up - zf.close() - zfraw.seek(0) - return zfraw - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Build a category package') - parser.add_argument('outdir', help='Output directory') - parser.add_argument('categorydirs', nargs='+', help='Directory of category source') - args = parser.parse_args() - - logging.basicConfig(level=logging.DEBUG) - - outdir = os.path.abspath(args.outdir) - for categorydir in args.categorydirs: - categorydir = os.path.abspath(categorydir) - build_category(categorydir, outdir) diff --git a/devel/mothd.service b/devel/mothd.service deleted file mode 100644 index 664afcb..0000000 --- a/devel/mothd.service +++ /dev/null @@ -1,18 +0,0 @@ -# To install: -# sudo cp mothd.service /etc/systemd/system/moth.service -# sudo systemctl enable mothd -# sudo systemctl start mothd - -[Unit] -Description=Monarch Of The Hill server -After=network.target auditd.service - -[Service] -WorkingDirectory=/srv/moth -User=www-data -ExecStart=/srv/moth/mothd -KillMode=process -Restart=on-failure - -[Install] -WantedBy=multi-user.target diff --git a/devel/parse.py b/devel/parse.py deleted file mode 100644 index 80ca637..0000000 --- a/devel/parse.py +++ /dev/null @@ -1,1335 +0,0 @@ -# -*- encoding: utf-8 -*- -r'''Parse strings using a specification based on the Python format() syntax. - - ``parse()`` is the opposite of ``format()`` - -The module is set up to only export ``parse()``, ``search()``, ``findall()``, -and ``with_pattern()`` when ``import \*`` is used: - ->>> from parse import * - -From there it's a simple thing to parse a string: - ->>> parse("It's {}, I love it!", "It's spam, I love it!") - ->>> _[0] -'spam' - -Or to search a string for some pattern: - ->>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n') - - -Or find all the occurrences of some pattern in a string: - ->>> ''.join(r.fixed[0] for r in findall(">{}<", "

      the bold text

      ")) -'the bold text' - -If you're going to use the same pattern to match lots of strings you can -compile it once: - ->>> from parse import compile ->>> p = compile("It's {}, I love it!") ->>> print(p) - ->>> p.parse("It's spam, I love it!") - - -("compile" is not exported for ``import *`` usage as it would override the -built-in ``compile()`` function) - -The default behaviour is to match strings case insensitively. You may match with -case by specifying `case_sensitive=True`: - ->>> parse('SPAM', 'spam', case_sensitive=True) is None -True - - -Format Syntax -------------- - -A basic version of the `Format String Syntax`_ is supported with anonymous -(fixed-position), named and formatted fields:: - - {[field name]:[format spec]} - -Field names must be a valid Python identifiers, including dotted names; -element indexes imply dictionaries (see below for example). - -Numbered fields are also not supported: the result of parsing will include -the parsed fields in the order they are parsed. - -The conversion of fields to types other than strings is done based on the -type in the format specification, which mirrors the ``format()`` behaviour. -There are no "!" field conversions like ``format()`` has. - -Some simple parse() format string examples: - ->>> parse("Bring me a {}", "Bring me a shrubbery") - ->>> r = parse("The {} who say {}", "The knights who say Ni!") ->>> print(r) - ->>> print(r.fixed) -('knights', 'Ni!') ->>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade") ->>> print(r) - ->>> print(r.named) -{'item': 'hand grenade'} ->>> print(r['item']) -hand grenade ->>> 'item' in r -True - -Note that `in` only works if you have named fields. Dotted names and indexes -are possible though the application must make additional sense of the result: - ->>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!") ->>> print(r) - ->>> print(r.named) -{'food.type': 'spam'} ->>> print(r['food.type']) -spam ->>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!") ->>> print(r) - ->>> print(r['quest']) -{'name': 'to seek the holy grail!'} ->>> print(r['quest']['name']) -to seek the holy grail! - -If the text you're matching has braces in it you can match those by including -a double-brace ``{{`` or ``}}`` in your format string, just like format() does. - - -Format Specification --------------------- - -Most often a straight format-less ``{}`` will suffice where a more complex -format specification might have been used. - -Most of `format()`'s `Format Specification Mini-Language`_ is supported: - - [[fill]align][0][width][.precision][type] - -The differences between `parse()` and `format()` are: - -- The align operators will cause spaces (or specified fill character) to be - stripped from the parsed value. The width is not enforced; it just indicates - there may be whitespace or "0"s to strip. -- Numeric parsing will automatically handle a "0b", "0o" or "0x" prefix. - That is, the "#" format character is handled automatically by d, b, o - and x formats. For "d" any will be accepted, but for the others the correct - prefix must be present if at all. -- Numeric sign is handled automatically. -- The thousands separator is handled automatically if the "n" type is used. -- The types supported are a slightly different mix to the format() types. Some - format() types come directly over: "d", "n", "%", "f", "e", "b", "o" and "x". - In addition some regular expression character group types "D", "w", "W", "s" - and "S" are also available. -- The "e" and "g" types are case-insensitive so there is not need for - the "E" or "G" types. - -===== =========================================== ======== -Type Characters Matched Output -===== =========================================== ======== -l Letters (ASCII) str -w Letters, numbers and underscore str -W Not letters, numbers and underscore str -s Whitespace str -S Non-whitespace str -d Digits (effectively integer numbers) int -D Non-digit str -n Numbers with thousands separators (, or .) int -% Percentage (converted to value/100.0) float -f Fixed-point numbers float -F Decimal numbers Decimal -e Floating-point numbers with exponent float - e.g. 1.1e-10, NAN (all case insensitive) -g General number format (either d, f or e) float -b Binary numbers int -o Octal numbers int -x Hexadecimal numbers (lower and upper case) int -ti ISO 8601 format date/time datetime - e.g. 1972-01-20T10:21:36Z ("T" and "Z" - optional) -te RFC2822 e-mail format date/time datetime - e.g. Mon, 20 Jan 1972 10:21:36 +1000 -tg Global (day/month) format date/time datetime - e.g. 20/1/1972 10:21:36 AM +1:00 -ta US (month/day) format date/time datetime - e.g. 1/20/1972 10:21:36 PM +10:30 -tc ctime() format date/time datetime - e.g. Sun Sep 16 01:03:52 1973 -th HTTP log format date/time datetime - e.g. 21/Nov/2011:00:07:11 +0000 -ts Linux system log format date/time datetime - e.g. Nov 9 03:37:44 -tt Time time - e.g. 10:21:36 PM -5:30 -===== =========================================== ======== - -Some examples of typed parsing with ``None`` returned if the typing -does not match: - ->>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...') - ->>> parse('Our {:d} {:w} are...', 'Our three weapons are...') ->>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM') - - -And messing about with alignment: - ->>> parse('with {:>} herring', 'with a herring') - ->>> parse('spam {:^} spam', 'spam lovely spam') - - -Note that the "center" alignment does not test to make sure the value is -centered - it just strips leading and trailing whitespace. - -Width and precision may be used to restrict the size of matched text -from the input. Width specifies a minimum size and precision specifies -a maximum. For example: - ->>> parse('{:.2}{:.2}', 'look') # specifying precision - ->>> parse('{:4}{:4}', 'look at that') # specifying width - ->>> parse('{:4}{:.4}', 'look at that') # specifying both - ->>> parse('{:2d}{:2d}', '0440') # parsing two contiguous numbers - - -Some notes for the date and time types: - -- the presence of the time part is optional (including ISO 8601, starting - at the "T"). A full datetime object will always be returned; the time - will be set to 00:00:00. You may also specify a time without seconds. -- when a seconds amount is present in the input fractions will be parsed - to give microseconds. -- except in ISO 8601 the day and month digits may be 0-padded. -- the date separator for the tg and ta formats may be "-" or "/". -- named months (abbreviations or full names) may be used in the ta and tg - formats in place of numeric months. -- as per RFC 2822 the e-mail format may omit the day (and comma), and the - seconds but nothing else. -- hours greater than 12 will be happily accepted. -- the AM/PM are optional, and if PM is found then 12 hours will be added - to the datetime object's hours amount - even if the hour is greater - than 12 (for consistency.) -- in ISO 8601 the "Z" (UTC) timezone part may be a numeric offset -- timezones are specified as "+HH:MM" or "-HH:MM". The hour may be one or two - digits (0-padded is OK.) Also, the ":" is optional. -- the timezone is optional in all except the e-mail format (it defaults to - UTC.) -- named timezones are not handled yet. - -Note: attempting to match too many datetime fields in a single parse() will -currently result in a resource allocation issue. A TooManyFields exception -will be raised in this instance. The current limit is about 15. It is hoped -that this limit will be removed one day. - -.. _`Format String Syntax`: - http://docs.python.org/library/string.html#format-string-syntax -.. _`Format Specification Mini-Language`: - http://docs.python.org/library/string.html#format-specification-mini-language - - -Result and Match Objects ------------------------- - -The result of a ``parse()`` and ``search()`` operation is either ``None`` (no match), a -``Result`` instance or a ``Match`` instance if ``evaluate_result`` is False. - -The ``Result`` instance has three attributes: - -fixed - A tuple of the fixed-position, anonymous fields extracted from the input. -named - A dictionary of the named fields extracted from the input. -spans - A dictionary mapping the names and fixed position indices matched to a - 2-tuple slice range of where the match occurred in the input. - The span does not include any stripped padding (alignment or width). - -The ``Match`` instance has one method: - -evaluate_result() - Generates and returns a ``Result`` instance for this ``Match`` object. - - - -Custom Type Conversions ------------------------ - -If you wish to have matched fields automatically converted to your own type you -may pass in a dictionary of type conversion information to ``parse()`` and -``compile()``. - -The converter will be passed the field string matched. Whatever it returns -will be substituted in the ``Result`` instance for that field. - -Your custom type conversions may override the builtin types if you supply one -with the same identifier. - ->>> def shouty(string): -... return string.upper() -... ->>> parse('{:shouty} world', 'hello world', dict(shouty=shouty)) - - -If the type converter has the optional ``pattern`` attribute, it is used as -regular expression for better pattern matching (instead of the default one). - ->>> def parse_number(text): -... return int(text) ->>> parse_number.pattern = r'\d+' ->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) - ->>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number)) ->>> assert _ is None, "MISMATCH" - -You can also use the ``with_pattern(pattern)`` decorator to add this -information to a type converter function: - ->>> from parse import with_pattern ->>> @with_pattern(r'\d+') -... def parse_number(text): -... return int(text) ->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) - - -A more complete example of a custom type might be: - ->>> yesno_mapping = { -... "yes": True, "no": False, -... "on": True, "off": False, -... "true": True, "false": False, -... } ->>> @with_pattern(r"|".join(yesno_mapping)) -... def parse_yesno(text): -... return yesno_mapping[text.lower()] - - -If the type converter ``pattern`` uses regex-grouping (with parenthesis), -you should indicate this by using the optional ``regex_group_count`` parameter -in the ``with_pattern()`` decorator: - ->>> @with_pattern(r'((\d+))', regex_group_count=2) -... def parse_number2(text): -... return int(text) ->>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2)) - - -Otherwise, this may cause parsing problems with unnamed/fixed parameters. - - -Potential Gotchas ------------------ - -`parse()` will always match the shortest text necessary (from left to right) -to fulfil the parse pattern, so for example: - ->>> pattern = '{dir1}/{dir2}' ->>> data = 'root/parent/subdir' ->>> sorted(parse(pattern, data).named.items()) -[('dir1', 'root'), ('dir2', 'parent/subdir')] - -So, even though `{'dir1': 'root/parent', 'dir2': 'subdir'}` would also fit -the pattern, the actual match represents the shortest successful match for -`dir1`. - ----- - -**Version history (in brief)**: - -- 1.11.0 Implement `__contains__` for Result instances. -- 1.10.0 Introduce a "letters" matcher, since "w" matches numbers - also. -- 1.9.1 Fix deprecation warnings around backslashes in regex strings - (thanks Mickaƫl Schoentgen). Also fix some documentation formatting - issues. -- 1.9.0 We now honor precision and width specifiers when parsing numbers - and strings, allowing parsing of concatenated elements of fixed width - (thanks Julia Signell) -- 1.8.4 Add LICENSE file at request of packagers. - Correct handling of AM/PM to follow most common interpretation. - Correct parsing of hexadecimal that looks like a binary prefix. - Add ability to parse case sensitively. - Add parsing of numbers to Decimal with "F" (thanks John Vandenberg) -- 1.8.3 Add regex_group_count to with_pattern() decorator to support - user-defined types that contain brackets/parenthesis (thanks Jens Engel) -- 1.8.2 add documentation for including braces in format string -- 1.8.1 ensure bare hexadecimal digits are not matched -- 1.8.0 support manual control over result evaluation (thanks Timo Furrer) -- 1.7.0 parse dict fields (thanks Mark Visser) and adapted to allow - more than 100 re groups in Python 3.5+ (thanks David King) -- 1.6.6 parse Linux system log dates (thanks Alex Cowan) -- 1.6.5 handle precision in float format (thanks Levi Kilcher) -- 1.6.4 handle pipe "|" characters in parse string (thanks Martijn Pieters) -- 1.6.3 handle repeated instances of named fields, fix bug in PM time - overflow -- 1.6.2 fix logging to use local, not root logger (thanks Necku) -- 1.6.1 be more flexible regarding matched ISO datetimes and timezones in - general, fix bug in timezones without ":" and improve docs -- 1.6.0 add support for optional ``pattern`` attribute in user-defined types - (thanks Jens Engel) -- 1.5.3 fix handling of question marks -- 1.5.2 fix type conversion error with dotted names (thanks Sebastian Thiel) -- 1.5.1 implement handling of named datetime fields -- 1.5 add handling of dotted field names (thanks Sebastian Thiel) -- 1.4.1 fix parsing of "0" in int conversion (thanks James Rowe) -- 1.4 add __getitem__ convenience access on Result. -- 1.3.3 fix Python 2.5 setup.py issue. -- 1.3.2 fix Python 3.2 setup.py issue. -- 1.3.1 fix a couple of Python 3.2 compatibility issues. -- 1.3 added search() and findall(); removed compile() from ``import *`` - export as it overwrites builtin. -- 1.2 added ability for custom and override type conversions to be - provided; some cleanup -- 1.1.9 to keep things simpler number sign is handled automatically; - significant robustification in the face of edge-case input. -- 1.1.8 allow "d" fields to have number base "0x" etc. prefixes; - fix up some field type interactions after stress-testing the parser; - implement "%" type. -- 1.1.7 Python 3 compatibility tweaks (2.5 to 2.7 and 3.2 are supported). -- 1.1.6 add "e" and "g" field types; removed redundant "h" and "X"; - removed need for explicit "#". -- 1.1.5 accept textual dates in more places; Result now holds match span - positions. -- 1.1.4 fixes to some int type conversion; implemented "=" alignment; added - date/time parsing with a variety of formats handled. -- 1.1.3 type conversion is automatic based on specified field types. Also added - "f" and "n" types. -- 1.1.2 refactored, added compile() and limited ``from parse import *`` -- 1.1.1 documentation improvements -- 1.1.0 implemented more of the `Format Specification Mini-Language`_ - and removed the restriction on mixing fixed-position and named fields -- 1.0.0 initial release - -This code is copyright 2012-2019 Richard Jones -See the end of the source file for the license of use. -''' - -from __future__ import absolute_import -__version__ = '1.11.0' - -# yes, I now have two problems -import re -import sys -from datetime import datetime, time, tzinfo, timedelta -from decimal import Decimal -from functools import partial -import logging - -__all__ = 'parse search findall with_pattern'.split() - -log = logging.getLogger(__name__) - - -def with_pattern(pattern, regex_group_count=None): - """Attach a regular expression pattern matcher to a custom type converter - function. - - This annotates the type converter with the :attr:`pattern` attribute. - - EXAMPLE: - >>> import parse - >>> @parse.with_pattern(r"\d+") - ... def parse_number(text): - ... return int(text) - - is equivalent to: - - >>> def parse_number(text): - ... return int(text) - >>> parse_number.pattern = r"\d+" - - :param pattern: regular expression pattern (as text) - :param regex_group_count: Indicates how many regex-groups are in pattern. - :return: wrapped function - """ - def decorator(func): - func.pattern = pattern - func.regex_group_count = regex_group_count - return func - return decorator - - -def int_convert(base): - '''Convert a string to an integer. - - The string may start with a sign. - - It may be of a base other than 10. - - If may start with a base indicator, 0#nnnn, which we assume should - override the specified base. - - It may also have other non-numeric characters that we can ignore. - ''' - CHARS = '0123456789abcdefghijklmnopqrstuvwxyz' - - def f(string, match, base=base): - if string[0] == '-': - sign = -1 - else: - sign = 1 - - if string[0] == '0' and len(string) > 2: - if string[1] in 'bB': - base = 2 - elif string[1] in 'oO': - base = 8 - elif string[1] in 'xX': - base = 16 - else: - # just go with the base specifed - pass - - chars = CHARS[:base] - string = re.sub('[^%s]' % chars, '', string.lower()) - return sign * int(string, base) - return f - - -def percentage(string, match): - return float(string[:-1]) / 100. - - -class FixedTzOffset(tzinfo): - """Fixed offset in minutes east from UTC. - """ - ZERO = timedelta(0) - - def __init__(self, offset, name): - self._offset = timedelta(minutes=offset) - self._name = name - - def __repr__(self): - return '<%s %s %s>' % (self.__class__.__name__, self._name, - self._offset) - - def utcoffset(self, dt): - return self._offset - - def tzname(self, dt): - return self._name - - def dst(self, dt): - return self.ZERO - - def __eq__(self, other): - return self._name == other._name and self._offset == other._offset - - -MONTHS_MAP = dict( - Jan=1, January=1, - Feb=2, February=2, - Mar=3, March=3, - Apr=4, April=4, - May=5, - Jun=6, June=6, - Jul=7, July=7, - Aug=8, August=8, - Sep=9, September=9, - Oct=10, October=10, - Nov=11, November=11, - Dec=12, December=12 -) -DAYS_PAT = r'(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' -MONTHS_PAT = r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)' -ALL_MONTHS_PAT = r'(%s)' % '|'.join(MONTHS_MAP) -TIME_PAT = r'(\d{1,2}:\d{1,2}(:\d{1,2}(\.\d+)?)?)' -AM_PAT = r'(\s+[AP]M)' -TZ_PAT = r'(\s+[-+]\d\d?:?\d\d)' - - -def date_convert(string, match, ymd=None, mdy=None, dmy=None, - d_m_y=None, hms=None, am=None, tz=None, mm=None, dd=None): - '''Convert the incoming string containing some date / time info into a - datetime instance. - ''' - groups = match.groups() - time_only = False - if mm and dd: - y=datetime.today().year - m=groups[mm] - d=groups[dd] - elif ymd is not None: - y, m, d = re.split(r'[-/\s]', groups[ymd]) - elif mdy is not None: - m, d, y = re.split(r'[-/\s]', groups[mdy]) - elif dmy is not None: - d, m, y = re.split(r'[-/\s]', groups[dmy]) - elif d_m_y is not None: - d, m, y = d_m_y - d = groups[d] - m = groups[m] - y = groups[y] - else: - time_only = True - - H = M = S = u = 0 - if hms is not None and groups[hms]: - t = groups[hms].split(':') - if len(t) == 2: - H, M = t - else: - H, M, S = t - if '.' in S: - S, u = S.split('.') - u = int(float('.' + u) * 1000000) - S = int(S) - H = int(H) - M = int(M) - - if am is not None: - am = groups[am] - if am: - am = am.strip() - if am == 'AM' and H == 12: - # correction for "12" hour functioning as "0" hour: 12:15 AM = 00:15 by 24 hr clock - H -= 12 - elif am == 'PM' and H == 12: - # no correction needed: 12PM is midday, 12:00 by 24 hour clock - pass - elif am == 'PM': - H += 12 - - if tz is not None: - tz = groups[tz] - if tz == 'Z': - tz = FixedTzOffset(0, 'UTC') - elif tz: - tz = tz.strip() - if tz.isupper(): - # TODO use the awesome python TZ module? - pass - else: - sign = tz[0] - if ':' in tz: - tzh, tzm = tz[1:].split(':') - elif len(tz) == 4: # 'snnn' - tzh, tzm = tz[1], tz[2:4] - else: - tzh, tzm = tz[1:3], tz[3:5] - offset = int(tzm) + int(tzh) * 60 - if sign == '-': - offset = -offset - tz = FixedTzOffset(offset, tz) - - if time_only: - d = time(H, M, S, u, tzinfo=tz) - else: - y = int(y) - if m.isdigit(): - m = int(m) - else: - m = MONTHS_MAP[m] - d = int(d) - d = datetime(y, m, d, H, M, S, u, tzinfo=tz) - - return d - - -class TooManyFields(ValueError): - pass - - -class RepeatedNameError(ValueError): - pass - - -# note: {} are handled separately -# note: I don't use r'' here because Sublime Text 2 syntax highlight has a fit -REGEX_SAFETY = re.compile(r'([?\\\\.[\]()*+\^$!\|])') - -# allowed field types -ALLOWED_TYPES = set(list('nbox%fFegwWdDsSl') + - ['t' + c for c in 'ieahgcts']) - - -def extract_format(format, extra_types): - '''Pull apart the format [[fill]align][0][width][.precision][type] - ''' - fill = align = None - if format[0] in '<>=^': - align = format[0] - format = format[1:] - elif len(format) > 1 and format[1] in '<>=^': - fill = format[0] - align = format[1] - format = format[2:] - - zero = False - if format and format[0] == '0': - zero = True - format = format[1:] - - width = '' - while format: - if not format[0].isdigit(): - break - width += format[0] - format = format[1:] - - if format.startswith('.'): - # Precision isn't needed but we need to capture it so that - # the ValueError isn't raised. - format = format[1:] # drop the '.' - precision = '' - while format: - if not format[0].isdigit(): - break - precision += format[0] - format = format[1:] - - # the rest is the type, if present - type = format - if type and type not in ALLOWED_TYPES and type not in extra_types: - raise ValueError('format spec %r not recognised' % type) - - return locals() - - -PARSE_RE = re.compile(r"""({{|}}|{\w*(?:(?:\.\w+)|(?:\[[^\]]+\]))*(?::[^}]+)?})""") - - -class Parser(object): - '''Encapsulate a format string that may be used to parse other strings. - ''' - def __init__(self, format, extra_types=None, case_sensitive=False): - # a mapping of a name as in {hello.world} to a regex-group compatible - # name, like hello__world Its used to prevent the transformation of - # name-to-group and group to name to fail subtly, such as in: - # hello_.world-> hello___world->hello._world - self._group_to_name_map = {} - # also store the original field name to group name mapping to allow - # multiple instances of a name in the format string - self._name_to_group_map = {} - # and to sanity check the repeated instances store away the first - # field type specification for the named field - self._name_types = {} - - self._format = format - if extra_types is None: - extra_types = {} - self._extra_types = extra_types - if case_sensitive: - self._re_flags = re.DOTALL - else: - self._re_flags = re.IGNORECASE | re.DOTALL - self._fixed_fields = [] - self._named_fields = [] - self._group_index = 0 - self._type_conversions = {} - self._expression = self._generate_expression() - self.__search_re = None - self.__match_re = None - - log.debug('format %r -> %r', format, self._expression) - - def __repr__(self): - if len(self._format) > 20: - return '<%s %r>' % (self.__class__.__name__, - self._format[:17] + '...') - return '<%s %r>' % (self.__class__.__name__, self._format) - - @property - def _search_re(self): - if self.__search_re is None: - try: - self.__search_re = re.compile(self._expression, self._re_flags) - except AssertionError: - # access error through sys to keep py3k and backward compat - e = str(sys.exc_info()[1]) - if e.endswith('this version only supports 100 named groups'): - raise TooManyFields('sorry, you are attempting to parse ' - 'too many complex fields') - return self.__search_re - - @property - def _match_re(self): - if self.__match_re is None: - expression = r'^%s$' % self._expression - try: - self.__match_re = re.compile(expression, self._re_flags) - except AssertionError: - # access error through sys to keep py3k and backward compat - e = str(sys.exc_info()[1]) - if e.endswith('this version only supports 100 named groups'): - raise TooManyFields('sorry, you are attempting to parse ' - 'too many complex fields') - except re.error: - raise NotImplementedError("Group names (e.g. (?P) can " - "cause failure, as they are not escaped properly: '%s'" % - expression) - return self.__match_re - - def parse(self, string, evaluate_result=True): - '''Match my format to the string exactly. - - Return a Result or Match instance or None if there's no match. - ''' - m = self._match_re.match(string) - if m is None: - return None - - if evaluate_result: - return self.evaluate_result(m) - else: - return Match(self, m) - - def search(self, string, pos=0, endpos=None, evaluate_result=True): - '''Search the string for my format. - - Optionally start the search at "pos" character index and limit the - search to a maximum index of endpos - equivalent to - search(string[:endpos]). - - If the ``evaluate_result`` argument is set to ``False`` a - Match instance is returned instead of the actual Result instance. - - Return either a Result instance or None if there's no match. - ''' - if endpos is None: - endpos = len(string) - m = self._search_re.search(string, pos, endpos) - if m is None: - return None - - if evaluate_result: - return self.evaluate_result(m) - else: - return Match(self, m) - - def findall(self, string, pos=0, endpos=None, extra_types=None, evaluate_result=True): - '''Search "string" for all occurrences of "format". - - Optionally start the search at "pos" character index and limit the - search to a maximum index of endpos - equivalent to - search(string[:endpos]). - - Returns an iterator that holds Result or Match instances for each format match - found. - ''' - if endpos is None: - endpos = len(string) - return ResultIterator(self, string, pos, endpos, evaluate_result=evaluate_result) - - def _expand_named_fields(self, named_fields): - result = {} - for field, value in named_fields.items(): - # split 'aaa[bbb][ccc]...' into 'aaa' and '[bbb][ccc]...' - basename, subkeys = re.match(r'([^\[]+)(.*)', field).groups() - - # create nested dictionaries {'aaa': {'bbb': {'ccc': ...}}} - d = result - k = basename - - if subkeys: - for subkey in re.findall(r'\[[^\]]+\]', subkeys): - d = d.setdefault(k,{}) - k = subkey[1:-1] - - # assign the value to the last key - d[k] = value - - return result - - def evaluate_result(self, m): - '''Generate a Result instance for the given regex match object''' - # ok, figure the fixed fields we've pulled out and type convert them - fixed_fields = list(m.groups()) - for n in self._fixed_fields: - if n in self._type_conversions: - fixed_fields[n] = self._type_conversions[n](fixed_fields[n], m) - fixed_fields = tuple(fixed_fields[n] for n in self._fixed_fields) - - # grab the named fields, converting where requested - groupdict = m.groupdict() - named_fields = {} - name_map = {} - for k in self._named_fields: - korig = self._group_to_name_map[k] - name_map[korig] = k - if k in self._type_conversions: - value = self._type_conversions[k](groupdict[k], m) - else: - value = groupdict[k] - - named_fields[korig] = value - - # now figure the match spans - spans = dict((n, m.span(name_map[n])) for n in named_fields) - spans.update((i, m.span(n + 1)) - for i, n in enumerate(self._fixed_fields)) - - # and that's our result - return Result(fixed_fields, self._expand_named_fields(named_fields), spans) - - def _regex_replace(self, match): - return '\\' + match.group(1) - - def _generate_expression(self): - # turn my _format attribute into the _expression attribute - e = [] - for part in PARSE_RE.split(self._format): - if not part: - continue - elif part == '{{': - e.append(r'\{') - elif part == '}}': - e.append(r'\}') - elif part[0] == '{': - # this will be a braces-delimited field to handle - e.append(self._handle_field(part)) - else: - # just some text to match - e.append(REGEX_SAFETY.sub(self._regex_replace, part)) - return ''.join(e) - - def _to_group_name(self, field): - # return a version of field which can be used as capture group, even - # though it might contain '.' - group = field.replace('.', '_').replace('[', '_').replace(']', '_') - - # make sure we don't collide ("a.b" colliding with "a_b") - n = 1 - while group in self._group_to_name_map: - n += 1 - if '.' in field: - group = field.replace('.', '_' * n) - elif '_' in field: - group = field.replace('_', '_' * n) - else: - raise KeyError('duplicated group name %r' % (field,)) - - # save off the mapping - self._group_to_name_map[group] = field - self._name_to_group_map[field] = group - return group - - def _handle_field(self, field): - # first: lose the braces - field = field[1:-1] - - # now figure whether this is an anonymous or named field, and whether - # there's any format specification - format = '' - if field and field[0].isalpha(): - if ':' in field: - name, format = field.split(':') - else: - name = field - if name in self._name_to_group_map: - if self._name_types[name] != format: - raise RepeatedNameError('field type %r for field "%s" ' - 'does not match previous seen type %r' % (format, - name, self._name_types[name])) - group = self._name_to_group_map[name] - # match previously-seen value - return r'(?P=%s)' % group - else: - group = self._to_group_name(name) - self._name_types[name] = format - self._named_fields.append(group) - # this will become a group, which must not contain dots - wrap = r'(?P<%s>%%s)' % group - else: - self._fixed_fields.append(self._group_index) - wrap = r'(%s)' - if ':' in field: - format = field[1:] - group = self._group_index - - # simplest case: no type specifier ({} or {name}) - if not format: - self._group_index += 1 - return wrap % r'.+?' - - # decode the format specification - format = extract_format(format, self._extra_types) - - # figure type conversions, if any - type = format['type'] - is_numeric = type and type in 'n%fegdobh' - if type in self._extra_types: - type_converter = self._extra_types[type] - s = getattr(type_converter, 'pattern', r'.+?') - regex_group_count = getattr(type_converter, 'regex_group_count', 0) - if regex_group_count is None: - regex_group_count = 0 - self._group_index += regex_group_count - - def f(string, m): - return type_converter(string) - self._type_conversions[group] = f - elif type == 'n': - s = r'\d{1,3}([,.]\d{3})*' - self._group_index += 1 - self._type_conversions[group] = int_convert(10) - elif type == 'b': - s = r'(0[bB])?[01]+' - self._type_conversions[group] = int_convert(2) - self._group_index += 1 - elif type == 'o': - s = r'(0[oO])?[0-7]+' - self._type_conversions[group] = int_convert(8) - self._group_index += 1 - elif type == 'x': - s = r'(0[xX])?[0-9a-fA-F]+' - self._type_conversions[group] = int_convert(16) - self._group_index += 1 - elif type == '%': - s = r'\d+(\.\d+)?%' - self._group_index += 1 - self._type_conversions[group] = percentage - elif type == 'f': - s = r'\d+\.\d+' - self._type_conversions[group] = lambda s, m: float(s) - elif type == 'F': - s = r'\d+\.\d+' - self._type_conversions[group] = lambda s, m: Decimal(s) - elif type == 'e': - s = r'\d+\.\d+[eE][-+]?\d+|nan|NAN|[-+]?inf|[-+]?INF' - self._type_conversions[group] = lambda s, m: float(s) - elif type == 'g': - s = r'\d+(\.\d+)?([eE][-+]?\d+)?|nan|NAN|[-+]?inf|[-+]?INF' - self._group_index += 2 - self._type_conversions[group] = lambda s, m: float(s) - elif type == 'd': - if format.get('width'): - width = r'{1,%s}' % int(format['width']) - else: - width = '+' - s = r'\d{w}|0[xX][0-9a-fA-F]{w}|0[bB][01]{w}|0[oO][0-7]{w}'.format(w=width) - self._type_conversions[group] = int_convert(10) - elif type == 'ti': - s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % \ - TIME_PAT - n = self._group_index - self._type_conversions[group] = partial(date_convert, ymd=n + 1, - hms=n + 4, tz=n + 7) - self._group_index += 7 - elif type == 'tg': - s = r'(\d{1,2}[-/](\d{1,2}|%s)[-/]\d{4})(\s+%s)?%s?%s?' % ( - ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, dmy=n + 1, - hms=n + 5, am=n + 8, tz=n + 9) - self._group_index += 9 - elif type == 'ta': - s = r'((\d{1,2}|%s)[-/]\d{1,2}[-/]\d{4})(\s+%s)?%s?%s?' % ( - ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, mdy=n + 1, - hms=n + 5, am=n + 8, tz=n + 9) - self._group_index += 9 - elif type == 'te': - # this will allow microseconds through if they're present, but meh - s = r'(%s,\s+)?(\d{1,2}\s+%s\s+\d{4})\s+%s%s' % (DAYS_PAT, - MONTHS_PAT, TIME_PAT, TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, dmy=n + 3, - hms=n + 5, tz=n + 8) - self._group_index += 8 - elif type == 'th': - # slight flexibility here from the stock Apache format - s = r'(\d{1,2}[-/]%s[-/]\d{4}):%s%s' % (MONTHS_PAT, TIME_PAT, - TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, dmy=n + 1, - hms=n + 3, tz=n + 6) - self._group_index += 6 - elif type == 'tc': - s = r'(%s)\s+%s\s+(\d{1,2})\s+%s\s+(\d{4})' % ( - DAYS_PAT, MONTHS_PAT, TIME_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, - d_m_y=(n + 4, n + 3, n + 8), hms=n + 5) - self._group_index += 8 - elif type == 'tt': - s = r'%s?%s?%s?' % (TIME_PAT, AM_PAT, TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, hms=n + 1, - am=n + 4, tz=n + 5) - self._group_index += 5 - elif type == 'ts': - s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % MONTHS_PAT - n = self._group_index - self._type_conversions[group] = partial(date_convert, mm=n+1, dd=n+3, - hms=n + 5) - self._group_index += 5 - elif type == 'l': - s = r'[A-Za-z]+' - elif type: - s = r'\%s+' % type - elif format.get('precision'): - if format.get('width'): - s = r'.{%s,%s}?' % (format['width'], format['precision']) - else: - s = r'.{1,%s}?' % format['precision'] - elif format.get('width'): - s = r'.{%s,}?' % format['width'] - else: - s = r'.+?' - - align = format['align'] - fill = format['fill'] - - # handle some numeric-specific things like fill and sign - if is_numeric: - # prefix with something (align "=" trumps zero) - if align == '=': - # special case - align "=" acts like the zero above but with - # configurable fill defaulting to "0" - if not fill: - fill = '0' - s = r'%s*' % fill + s - - # allow numbers to be prefixed with a sign - s = r'[-+ ]?' + s - - if not fill: - fill = ' ' - - # Place into a group now - this captures the value we want to keep. - # Everything else from now is just padding to be stripped off - if wrap: - s = wrap % s - self._group_index += 1 - - if format['width']: - # all we really care about is that if the format originally - # specified a width then there will probably be padding - without - # an explicit alignment that'll mean right alignment with spaces - # padding - if not align: - align = '>' - - if fill in r'.\+?*[](){}^$': - fill = '\\' + fill - - # align "=" has been handled - if align == '<': - s = '%s%s*' % (s, fill) - elif align == '>': - s = '%s*%s' % (fill, s) - elif align == '^': - s = '%s*%s%s*' % (fill, s, fill) - - return s - - -class Result(object): - '''The result of a parse() or search(). - - Fixed results may be looked up using `result[index]`. - - Named results may be looked up using `result['name']`. - - Named results may be tested for existence using `'name' in result`. - ''' - def __init__(self, fixed, named, spans): - self.fixed = fixed - self.named = named - self.spans = spans - - def __getitem__(self, item): - if isinstance(item, int): - return self.fixed[item] - return self.named[item] - - def __repr__(self): - return '<%s %r %r>' % (self.__class__.__name__, self.fixed, - self.named) - - def __contains__(self, name): - return name in self.named - - -class Match(object): - '''The result of a parse() or search() if no results are generated. - - This class is only used to expose internal used regex match objects - to the user and use them for external Parser.evaluate_result calls. - ''' - def __init__(self, parser, match): - self.parser = parser - self.match = match - - def evaluate_result(self): - '''Generate results for this Match''' - return self.parser.evaluate_result(self.match) - - -class ResultIterator(object): - '''The result of a findall() operation. - - Each element is a Result instance. - ''' - def __init__(self, parser, string, pos, endpos, evaluate_result=True): - self.parser = parser - self.string = string - self.pos = pos - self.endpos = endpos - self.evaluate_result = evaluate_result - - def __iter__(self): - return self - - def __next__(self): - m = self.parser._search_re.search(self.string, self.pos, self.endpos) - if m is None: - raise StopIteration() - self.pos = m.end() - - if self.evaluate_result: - return self.parser.evaluate_result(m) - else: - return Match(self.parser, m) - - # pre-py3k compat - next = __next__ - - -def parse(format, string, extra_types=None, evaluate_result=True, case_sensitive=False): - '''Using "format" attempt to pull values from "string". - - The format must match the string contents exactly. If the value - you're looking for is instead just a part of the string use - search(). - - If ``evaluate_result`` is True the return value will be an Result instance with two attributes: - - .fixed - tuple of fixed-position values from the string - .named - dict of named values from the string - - If ``evaluate_result`` is False the return value will be a Match instance with one method: - - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - If the format is invalid a ValueError will be raised. - - See the module documentation for the use of "extra_types". - - In the case there is no match parse() will return None. - ''' - p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) - return p.parse(string, evaluate_result=evaluate_result) - - -def search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, - case_sensitive=False): - '''Search "string" for the first occurrence of "format". - - The format may occur anywhere within the string. If - instead you wish for the format to exactly match the string - use parse(). - - Optionally start the search at "pos" character index and limit the search - to a maximum index of endpos - equivalent to search(string[:endpos]). - - If ``evaluate_result`` is True the return value will be an Result instance with two attributes: - - .fixed - tuple of fixed-position values from the string - .named - dict of named values from the string - - If ``evaluate_result`` is False the return value will be a Match instance with one method: - - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - If the format is invalid a ValueError will be raised. - - See the module documentation for the use of "extra_types". - - In the case there is no match parse() will return None. - ''' - p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) - return p.search(string, pos, endpos, evaluate_result=evaluate_result) - - -def findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, - case_sensitive=False): - '''Search "string" for all occurrences of "format". - - You will be returned an iterator that holds Result instances - for each format match found. - - Optionally start the search at "pos" character index and limit the search - to a maximum index of endpos - equivalent to search(string[:endpos]). - - If ``evaluate_result`` is True each returned Result instance has two attributes: - - .fixed - tuple of fixed-position values from the string - .named - dict of named values from the string - - If ``evaluate_result`` is False each returned value is a Match instance with one method: - - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - If the format is invalid a ValueError will be raised. - - See the module documentation for the use of "extra_types". - ''' - p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) - return Parser(format, extra_types=extra_types).findall(string, pos, endpos, evaluate_result=evaluate_result) - - -def compile(format, extra_types=None, case_sensitive=False): - '''Create a Parser instance to parse "format". - - The resultant Parser has a method .parse(string) which - behaves in the same manner as parse(format, string). - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - Use this function if you intend to parse many strings - with the same format. - - See the module documentation for the use of "extra_types". - - Returns a Parser instance. - ''' - return Parser(format, extra_types=extra_types) - - -# Copyright (c) 2012-2019 Richard Jones -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# vim: set filetype=python ts=4 sw=4 et si tw=75 diff --git a/devel/setup.cfg b/devel/setup.cfg deleted file mode 100644 index 3bf77b8..0000000 --- a/devel/setup.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[flake8] -# flake8 is an automated code formatting pedant. -# Use it, please. -# -# python3 -m flake8 . -# -ignore = E501 -exclude = .git \ No newline at end of file diff --git a/devel/update-words.sh b/devel/update-words.sh deleted file mode 100755 index f64d1cb..0000000 --- a/devel/update-words.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -set +e - -url='https://rawgit.com/first20hours/google-10000-english/master/google-10000-english-no-swears.txt' -getter="curl -sL" -fn="answer_words.txt" - -filterer() { - grep '......*' -} - -if ! curl -h >/dev/null 2>/dev/null; then - getter="wget -q -O -" -elif ! wget -h >/dev/null 2>/dev/null; then - echo "[!] I don't know how to download. I need curl or wget." -fi - -$getter "${url}" | filterer > ${fn}.tmp \ - && mv -f ${fn}.tmp ${fn} diff --git a/devel/validate.py b/devel/validate.py deleted file mode 100644 index d73dd89..0000000 --- a/devel/validate.py +++ /dev/null @@ -1,229 +0,0 @@ -#!/usr/bin/python3 - -"""A validator for MOTH puzzles""" - -import logging -import os -import os.path -import re - -import moth - -# pylint: disable=len-as-condition, line-too-long - -DEFAULT_REQUIRED_FIELDS = ["answers", "authors", "summary"] - -LOGGER = logging.getLogger(__name__) - - -class MothValidationError(Exception): - - """An exception for encapsulating MOTH puzzle validation errors""" - - -class MothValidator: - - """A class which validates MOTH categories""" - - def __init__(self, fields): - self.required_fields = fields - self.results = {"category": {}, "checks": []} - - def validate(self, categorydir, only_errors=False): - """Run validation checks against a category""" - LOGGER.debug("Loading category from %s", categorydir) - try: - category = moth.Category(categorydir, 0) - except NotADirectoryError: - return - - LOGGER.debug("Found %d puzzles in %s", len(category.pointvals()), categorydir) - - self.results["category"][categorydir] = { - "puzzles": {}, - "name": os.path.basename(categorydir.strip(os.sep)), - } - curr_category = self.results["category"][categorydir] - - for check_function_name in [x for x in dir(self) if x.startswith("check_") and callable(getattr(self, x))]: - if check_function_name not in self.results["checks"]: - self.results["checks"].append(check_function_name) - - for puzzle in category: - LOGGER.info("Processing %s: %s", categorydir, puzzle.points) - - curr_category["puzzles"][puzzle.points] = {} - curr_puzzle = curr_category["puzzles"][puzzle.points] - curr_puzzle["failures"] = [] - - for check_function_name in [x for x in dir(self) if x.startswith("check_") and callable(getattr(self, x))]: - check_function = getattr(self, check_function_name) - LOGGER.debug("Running %s on %d", check_function_name, puzzle.points) - - try: - check_function(puzzle) - except MothValidationError as ex: - curr_puzzle["failures"].append(str(ex)) - - if only_errors and len(curr_puzzle["failures"]) == 0: - del curr_category["puzzles"][puzzle.points] - - def check_fields(self, puzzle): - """Check if the puzzle has the requested fields""" - for field in self.required_fields: - if not hasattr(puzzle, field) or \ - getattr(puzzle,field) is None or \ - getattr(puzzle,field) == "": - raise MothValidationError("Missing field %s" % (field,)) - - @staticmethod - def check_has_answers(puzzle): - """Check if the puzle has answers defined""" - if len(puzzle.answers) == 0: - raise MothValidationError("No answers provided") - - @staticmethod - def check_unique_answers(puzzle): - """Check if puzzle answers are unique""" - known_answers = [] - duplicate_answers = [] - - for answer in puzzle.answers: - if answer not in known_answers: - known_answers.append(answer) - else: - duplicate_answers.append(answer) - - if len(duplicate_answers) > 0: - raise MothValidationError("Duplicate answer(s) %s" % ", ".join(duplicate_answers)) - - @staticmethod - def check_has_authors(puzzle): - """Check if the puzzle has authors defined""" - if len(puzzle.authors) == 0: - raise MothValidationError("No authors provided") - - @staticmethod - def check_unique_authors(puzzle): - """Check if puzzle authors are unique""" - known_authors = [] - duplicate_authors = [] - - for author in puzzle.authors: - if author not in known_authors: - known_authors.append(author) - else: - duplicate_authors.append(author) - - if len(duplicate_authors) > 0: - raise MothValidationError("Duplicate author(s) %s" % ", ".join(duplicate_authors)) - - @staticmethod - def check_has_summary(puzzle): - """Check if the puzzle has a summary""" - if puzzle.summary is None: - raise MothValidationError("Summary has not been provided") - - @staticmethod - def check_has_body(puzzle): - """Check if the puzzle has a body defined""" - old_pos = puzzle.body.tell() - puzzle.body.seek(0) - if len(puzzle.body.read()) == 0: - puzzle.body.seek(old_pos) - raise MothValidationError("No body provided") - - puzzle.body.seek(old_pos) - - @staticmethod - def check_ksa_format(puzzle): - """Check if KSAs are properly formatted""" - - ksa_re = re.compile("^[KSA]\d{4}$") - - if hasattr(puzzle, "ksa"): - for ksa in puzzle.ksa: - if ksa_re.match(ksa) is None: - raise MothValidationError("Unrecognized KSA format (%s)" % (ksa,)) - - @staticmethod - def check_success(puzzle): - """Check if success criteria are defined""" - - if not hasattr(puzzle, "success"): - raise MothValidationError("Success not defined") - - criteria = ["acceptable", "mastery"] - missing_criteria = [] - for criterion in criteria: - if criterion not in puzzle.success.keys() or \ - puzzle.success[criterion] is None or \ - len(puzzle.success[criterion]) == 0: - missing_criteria.append(criterion) - - if len(missing_criteria) > 0: - raise MothValidationError("Missing success criteria (%s)" % (", ".join(missing_criteria))) - - -def output_json(data): - """Output results in JSON format""" - import json - print(json.dumps(data)) - - -def output_text(data): - """Output results in a text-based tabular format""" - - longest_category = max([len(y["name"]) for x, y in data["category"].items()]) - longest_category = max([longest_category, len("Category")]) - longest_failure = len("Failures") - for category_data in data["category"].values(): - for points, puzzle_data in category_data["puzzles"].items(): - longest_failure = max([longest_failure, len(", ".join(puzzle_data["failures"]))]) - - formatstr = "| %%%ds | %%6s | %%%ds |" % (longest_category, longest_failure) - headerfmt = formatstr % ("Category", "Points", "Failures") - - print(headerfmt) - for cat_data in data["category"].values(): - for points, puzzle_data in sorted(cat_data["puzzles"].items()): - print(formatstr % (cat_data["name"], points, ", ".join([str(x) for x in puzzle_data["failures"]]))) - - -def main(): - """Main function""" - # pylint: disable=invalid-name - import argparse - - LOGGER.addHandler(logging.StreamHandler()) - - parser = argparse.ArgumentParser(description="Validate MOTH puzzle field compliance") - parser.add_argument("category", nargs="+", help="Categories to validate") - parser.add_argument("-f", "--fields", help="Comma-separated list of fields to check for", default=",".join(DEFAULT_REQUIRED_FIELDS)) - - parser.add_argument("-o", "--output-format", choices=["text", "json"], default="text", help="Output format (default: text)") - parser.add_argument("-e", "--only-errors", action="store_true", default=False, help="Only output errors") - parser.add_argument("-v", "--verbose", action="count", default=0, help="Increase verbosity of output, repeat to increase") - - args = parser.parse_args() - - if args.verbose == 1: - LOGGER.setLevel("INFO") - elif args.verbose > 1: - LOGGER.setLevel("DEBUG") - - LOGGER.debug(args) - validator = MothValidator(args.fields.split(",")) - - for category in args.category: - LOGGER.info("Validating %s", category) - validator.validate(category, only_errors=args.only_errors) - - if args.output_format == "text": - output_text(validator.results) - elif args.output_format == "json": - output_json(validator.results) - - -if __name__ == "__main__": - main() diff --git a/doc/administration.md b/doc/administration.md new file mode 100644 index 0000000..aa49c73 --- /dev/null +++ b/doc/administration.md @@ -0,0 +1,125 @@ +Administration +========= + +Everything you need to do happens through the filesystem. +Usually, in `/srv/moth/state`. + +The server doesn't cache anything in memory, +so the `state` directory always contains the current state. + + +Backing up current state +--------------------------- + + tar czf backup.tar.gz /srv/moth/state # Full backup + curl http://localhost:8080/state > state.json # Pull anonymized event log and team names (scoreboard) + + +Pausing/resuming scoring +------------------- + + rm /srv/moth/state/enabled # Pause scoring + touch /srv/moth/state/enabled # Resume scoring + +When scoring is paused, +participants can still submit answers, +and the system will tell them whether the answer is correct. +As soon as you unpause, +all correctly-submitted answers will be scored. + + +Scheduling an automatic pause and resume +----------------------------------- + + printf '-'; date --rfc-3339=s -d '10:00 PM' >> /srv/moth/state/hours.txt # Schedule suspend at 10:00 PM + printf '+'; date --rfc-3339=s -d '08:00 tomorrow' >> /srv/moth/state/hours.txt # Schedule resume at 08:00 tomorrow + +You might prefer to open `/srv/moth/state/hours.txt` in a text editor. +I do. + + +Re-initalize +------------------- + + rm /srv/moth/state/initialized + +This will reset the following: + +* team registrations +* points log + +Team tokens stick around, though. + + +Setting up custom team IDs +------------------- + + echo > /srv/moth/state/teamids.txt # Teams must be registered manually + seq 9999 > /srv/moth/state/teamids.txt # Allow all 4-digit numbers + +`teamids.txt` is a list of acceptable team IDs, +one per line. +You can make it anything you want. + +New instances will initialize this with some hex values. + +Remember that team IDs are essentially passwords. + + +Adjusting scores +------------------ + + rm /srv/moth/state/enabled # Suspend scoring + nano /srv/moth/state/points.log + touch /srv/moth/state/enabled # Resume scoring + +We don't warn participants before we do this: +any points scored while scoring is suspended are queued up and processed as soon as scoreing is resumed. + +It's very important to suspend scoring before mucking around with the points log. +The maintenance loop assumes it is the only thing writing to this file, +and any edits you make could blow aware points scored. + +No, I don't use nano. +None of us use nano. + + +Changing a team name +---------------------- + + grep . /srv/moth/state/teams/* # Show all team IDs and names + echo 'exciting new team name' > /srv/moth/state/teams/$teamid + +Please remember, you have to replace `$teamid` with the actual team ID that you want to edit. + + +Dealing with puzzles +=========== + +Checking on an answer +---------------------- + +Mothballs are just zip files. +If you need to check something about a running category, +just unzip the mothball for that category. + + mkdir /tmp/category + cd /tmp/category + unzip /srv/moth/mothballs/category.zip + cat answers.txt # Show all valid answers for all puzzles. Watch your shoulder! + + +Installing new categories +------------------- + +Just drop a new mothball in the `mothballs' directory. + + cp new-category.mb /srv/moth/mothballs + + +Taking a category offline +------------------------- + + rm /srv/moth/mothballs/old-category.mb + +Removing a category won't remove points that have been scored in it! diff --git a/doc/devel-server.md b/doc/devel-server.md deleted file mode 100644 index 2ae34c8..0000000 --- a/doc/devel-server.md +++ /dev/null @@ -1,63 +0,0 @@ -Using the MOTH Development Server -====================== - -To make puzzle development easier, -MOTH comes with a standalone web server written in Python, -which will show you how your puzzles are going to look without making you compile or package anything. - -It even works in Windows, -because that is what my career has become. - - -Getting It Going ----------------- - -### With Docker - -If you can use docker, you are in luck: - - docker run --rm -t -p 8080:8080 dirtbags/moth-devel - -Gets you a development puzzle server running on port 8080, -with the sample puzzle directory set up. - - -### Without Docker - -If you can't use docker, -try this: - - apt install python3 - pip3 install scapy pillow PyYAML - git clone https://github.com/dirtbags/moth/ - cd moth - python3 devel/devel-server.py --puzzles example-puzzles - - -Installing New Puzzles ------------------------------ - -The development server wants to see category directories under `puzzles`, -like this: - - $ find puzzles -type d - puzzles/ - puzzles/category1/ - puzzles/category1/10/ - puzzles/category1/20/ - puzzles/category1/30/ - puzzles/category2/ - puzzles/category2/100/ - puzzles/category2/200/ - puzzles/category2/300/ - - -### With Docker - - docker run --rm -t -v /path/to/my/puzzles:/puzzles:ro -p 8080:8080 dirtbags/moth-devel - - -### Without Docker - -You can use the `--puzzles` argument to `devel-server.py` -to specify a path to your puzzles directory. diff --git a/doc/development.md b/doc/development.md new file mode 100644 index 0000000..cf7d0ba --- /dev/null +++ b/doc/development.md @@ -0,0 +1,105 @@ +Developing Content +============================ + +The development server shows debugging for each puzzle, +and will compile puzzles on the fly. + +Use it along with a text editor and shell to create new puzzles and categories. + + +Set up some example puzzles +--------- + +If you don't have puzzles of your own to start with, +you can copy the example puzzles that come with the source: + + cp -r /path/to/src/moth/example-puzzles /srv/moth/puzzles + + +Run the server in development mode +--------------- + +These recipes run the server in the foreground, +so you can watch the access log and any error messages. + + +### Podman + + podman run --rm -it -p 8080:8080 -v /srv/moth/puzzles:/puzzles:ro dirtbags/moth -puzzles /puzzles + + +### Docker + + docker run --rm -it -p 8080:8080 -v /srv/moth/puzzles:/puzzles:ro dirtbags/moth -puzzles /puzzles + +### Native + +I assume you've built and installed the `moth` command from the source tree. + +If you don't know how to build Go packages, +please consider using Podman or Docker. +Building Go software is not a skill related to running MOTH or puzzle events, +unless you plan on hacking on the source code. + + mkdir -p /srv/moth/state + cp -r /path/to/src/moth/theme /srv/moth/theme + cd /srv/moth + moth -puzzles puzzles + + +Log In +----- + +Point a browser to http://localhost:8080/ (or whatever host is running the server). +You will be logged in automatically. + + +Browse the example puzzles +------------ + + +The example puzzles are written to demonstrate various features of MOTH, +and serve as documentation of the puzzle format. + + +Make your own puzzle category +------------------------- + + cp -r /srv/moth/puzzles/example /srv/moth/puzzles/my-category + + +Edit the one point puzzle +-------- + + nano /srv/moth/puzzles/my-category/1/puzzle.md + +I don't use nano, personally, +but if you're advanced enough to have an opinion about nano, +you're advanced enough to know how to use a different editor. + + +Read our advice +--------------- + +The [Writing Puzzles](writing-puzzles.md) document +has some tips on how we approach puzzle writing. +There may be something in here that will help you out! + + +Stop the server +------- + +You can hit Control-C in the terminal where you started the server, +and it will exit. + + +Mothballs +======= + +In the list of puzzle categories and puzzles, +there will be a button to download a mothball. + +Once your category is set up the way you like it, +download a mothball for it, +and you're ready to [get started](getting-started.md) +with the production server. diff --git a/doc/getting-started.md b/doc/getting-started.md new file mode 100644 index 0000000..a1a9258 --- /dev/null +++ b/doc/getting-started.md @@ -0,0 +1,77 @@ +Getting Started +=============== + +Compile Mothballs +-------------------- + +Mothballs are compiled, static-content versions of a puzzle category. +You need a mothball for every category you want to run. + +To get some mothballs, you'll need to run a development server, which includes the category compiler. +See [development](development.md) for details. + + +Set up directories +-------------------- + + mkdir -p /srv/moth/state + mkdir -p /srv/moth/mothballs + cp -r /path/to/src/moth/theme /srv/moth/theme # Skip if using Docker/Podman/Kubernetes + +MOTH needs three directories. We recommend putting them all in `/srv/moth`. + +* `/srv/moth/state`: (read-write) an empty directory for the server to record its state +* `/srv/moth/mothballs`: (read-only) drop your mothballs here +* `/srv/moth/theme`: (read-only) The HTML5 MOTH client: static content served to web browsers + + + +Run the server +---------------- + +We're going to assume you put everything in `/srv/moth`, like we suggested. + +### Podman + + podman run --name=moth -d -v /srv/moth/mothballs:/mothballs:ro -v /srv/moth/state:/state dirtbags/moth + +### Docker + + docker run --name=moth -d -v /srv/moth/mothballs:/mothballs:ro -v /srv/moth/state:/state dirtbags/moth + +### Native + + cd /srv/moth + moth + + +Copy in some mothballs +------------------------- + + cp category1.mb category2.mb /srv/moth/mothballs + +You can add and remove mothballs at any time while the server is running. + + +Get a list of valid team tokens +----------------------- + + cat /srv/moth/state/tokens.txt + +You can edit or replace this file if you want to use different tokens than the pre-generated ones. + + +Connect to the server +------------------------ + +Open http://localhost:8080/ + +Substitute the hostname appropriately if you're a fancypants with a cloud. + + +Yay! +------- + +You should be all set now! + +See [administration](administration.md) for how to keep your new MOTH server running the way you want. diff --git a/doc/philosophy.md b/doc/philosophy.md index 745e76e..5a8b107 100644 --- a/doc/philosophy.md +++ b/doc/philosophy.md @@ -1,7 +1,10 @@ Philosophy ========== -This is just some scattered thoughts by the architect, Neale. +Some scattered thoughts by the architect, Neale. + +Hardening +----------- People are going to try to break this thing. It needs to be bulletproof. @@ -10,21 +13,23 @@ This pretty much set the entire design: * As much as possible is done client-side * Participants can attack their own web browsers as much as they feel like * Also reduces server load - * We will help you create brute-force attacks! + * We even made a puzzle category to walk people through creating brute-force attacks! * Your laptop is faster than our server * We give you the carrot of hashed answers and the hashing function * This removes one incentive to DoS the server * Generate static content whenever possible - * Puzzles are statically compiled before the event even starts - * `points.json` and `puzzles.json` are generated and cached by a maintenance loop + * Puzzles must be statically compiled before the event even starts + * As much content as possible is generated by a maintenance loop * Minimize dynamic handling - * There are only two (2) dynamic handlers + * There are only three (3) dynamic handlers * team registration * answer validation + * server state (open puzzles + event log) * You can disable team registration if you want, just remove `teamids.txt` * I even removed token handling once I realized we replicate the user experience with the `answer` handler and some client-side JavaScript * As much as possible is read-only - * The only rw directory is `state` + * The only read-write directory is `state` + * This plays very well with Docker, which didn't exist when we designed MOTH * Server code should be as tiny as possible * Server should provide highly limited functionality * It should be easy to remember in your head everything it does @@ -35,3 +40,21 @@ This pretty much set the entire design: * Want to provide a time bonus for quick answers? I don't, but if you do, you can just modify the scoreboard to do so. * Maybe you want to show a graph of team rankings over time: just replay the event log. * Want to do some analysis of what puzzles take the longest to answer? It's all there. + +Fairness +--------- + +We spend a lot of time thinking about whether new content is going to feel fair. +Or, more importantly, if there's a possibility for it to be viewed as unfair. + +It's possible to run fun events that don't focus so much on fairness, +but those aren't the type of events we run. + +* People generally don't mind discovering that they could improve +* People can get furious if they feel like some system is unfairly targeting them +* Every team that does the same amount of work should have the same score + * No time bonuses / decaying points + * No penalties for trying things that don't work out +* No one should ever feel like it's impossible to catch up + * Achievements ("cheevos") work well here + * Time-based awards (flags) don't mesh with this idea diff --git a/doc/tokens.md b/doc/tokens.md index 848a069..8ea1f70 100644 --- a/doc/tokens.md +++ b/doc/tokens.md @@ -1,12 +1,42 @@ Tokens ====== -Tokens are good for a single point in a single category. They are -formed by prepending the category and a colon to the bubblebabble digest -of 3 random octets. A token for the "merfing" category might look like -this: +We used to use tokens extensively for categories outside of MOTH +(like scavenger hunts, Dirtbags Tanks, and other standalone stuff). - merfing:xunap-motex +We still occasionally pull out tokens to deal with oddball categories +that we want to score alongside MOTH categories. + +Here's how they work. + +Description +------------ + +Tokens are a 3-tuple: + +> (category, points, nonce) + +We build a mothball with nothing but `answers.txt`, +and a special 1-point puzzle that uses JavaScript to parse and submit tokens. + +Generally, tokens use colon separators, so they look like this: + + category:12:xunap-motex + +Uniqueness +-------- + +Because they work just like normal categories, +you can't have two distinct tokens worth the same number of points. + +When we need two or more tokens worth the same amount, +we make the point values very high, +so the least significant digit doesn't have much impact on the overall value. +For instance: + + category:1000001:xylep-nanox + category:1000002:xenod-relix + category:1000003:xoter-darox Entropy diff --git a/lib/python/answer_words.txt b/lib/python/answer_words.txt deleted file mode 100644 index 7adc1a8..0000000 --- a/lib/python/answer_words.txt +++ /dev/null @@ -1,7713 +0,0 @@ -about -search -other -information -which -their -there -contact -business -online -first -would -services -these -click -service -price -people -state -email -health -world -products -music -should -product -system -policy -number -please -available -copyright -support -message -after -software -video -where -rights -public -books -school -through -links -review -years -order -privacy -items -company -group -under -general -research -university -january -reviews -program -games -management -could -great -united -hotel -international -center -store -travel -comments -development -report -member -details -terms -before -hotels -right -because -local -those -using -results -office -education -national -design -posted -internet -address -community -within -states -phone -shipping -reserved -subject -between -forum -family -based -black -check -special -prices -website -index -being -women -today -technology -south -project -pages -version -section -found -sports -house -related -security -county -american -photo -members -power -while -network -computer -systems -three -total -place -following -download -without -access -think -north -resources -current -posts -media -control -water -history -pictures -personal -since -including -guide -directory -board -location -change -white -small -rating -government -children -during -return -students -shopping -account -times -sites -level -digital -profile -previous -events -hours -image -department -title -description -insurance -another -shall -property -class -still -money -quality -every -listing -content -country -private -little -visit -tools -reply -customer -december -compare -movies -include -college -value -article -provide -source -author -different -press -learn -around -print -course -canada -process -stock -training -credit -point -science -categories -advanced -sales -english -estate -conditions -select -windows -photos -thread -category -large -gallery -table -register -however -october -november -market -library -really -action -start -series -model -features -industry -human -provided -required -second -accessories -movie -forums -march -september -better -questions -yahoo -going -medical -friend -server -study -application -staff -articles -feedback -again -looking -issues -april -never -users -complete -street -topic -comment -financial -things -working -against -standard -person -below -mobile -party -payment -equipment -login -student -programs -offers -legal -above -recent -stores -problem -memory -performance -social -august -quote -language -story -options -experience -rates -create -young -america -important -field -paper -single -activities -example -girls -additional -password -latest -something -question -changes -night -texas -poker -status -browse -issue -range -building -seller -court -february -always -result -audio -light -write -offer -groups -given -files -event -release -analysis -request -china -making -picture -needs -possible -might -professional -month -major -areas -future -space -committee -cards -problems -london -washington -meeting -become -interest -child -enter -california -share -similar -garden -schools -million -added -reference -companies -listed -learning -energy -delivery -popular -stories -computers -journal -reports -welcome -central -images -president -notice -original -radio -until -color -council -includes -track -australia -discussion -archive -others -entertainment -agreement -format -least -society -months -safety -friends -trade -edition -messages -marketing -further -updated -association -having -provides -david -already -green -studies -close -common -drive -specific -several -living -collection -called -short -display -limited -powered -solutions -means -director -daily -beach -natural -whether -electronics -period -planning -database -official -weather -average -technical -window -france -region -island -record -direct -microsoft -conference -environment -records -district -calendar -costs -style -front -statement -update -parts -downloads -early -miles -sound -resource -present -applications -either -document -works -material -written -federal -hosting -rules -final -adult -tickets -thing -centre -requirements -cheap -finance -minutes -third -gifts -europe -reading -topics -individual -cover -usually -together -videos -percent -function -getting -global -economic -player -projects -lyrics -often -subscribe -submit -germany -amount -watch -included -though -thanks -everything -deals -various -words -linux -production -commercial -james -weight -heart -advertising -received -choose -treatment -newsletter -archives -points -knowledge -magazine -error -camera -currently -construction -registered -clear -receive -domain -methods -chapter -makes -protection -policies -beauty -manager -india -position -taken -listings -models -michael -known -cases -engineering -florida -simple -quick -wireless -license -friday -whole -annual -published -later -basic -shows -corporate -google -church -method -purchase -customers -active -response -practice -hardware -figure -materials -holiday -enough -designed -along -among -death -writing -speed -countries -brand -discount -higher -effects -created -remember -standards -yellow -political -increase -advertise -kingdom -environmental -thought -stuff -french -storage -japan -doing -loans -shoes -entry -nature -orders -availability -africa -summary -growth -notes -agency -monday -european -activity -although -western -income -force -employment -overall -river -commission -package -contents -players -engine -album -regional -supplies -started -administration -institute -views -plans -double -build -screen -exchange -types -sponsored -lines -electronic -continue -across -benefits -needed -season -apply -someone -anything -printer -condition -effective -believe -organization -effect -asked -sunday -selection -casino -volume -cross -anyone -mortgage -silver -corporation -inside -solution -mature -rather -weeks -addition -supply -nothing -certain -executive -running -lower -necessary -union -jewelry -according -clothing -particular -names -robert -homepage -skills -islands -advice -career -military -rental -decision -leave -british -teens -woman -facilities -sellers -middle -cable -opportunities -taking -values -division -coming -tuesday -object -lesbian -appropriate -machine -length -actually -score -statistics -client -returns -capital -follow -sample -investment -shown -saturday -christmas -england -culture -flash -george -choice -starting -registration -thursday -courses -consumer -airport -foreign -artist -outside -furniture -levels -channel -letter -phones -ideas -wednesday -structure -summer -allow -degree -contract -button -releases -homes -super -matter -custom -virginia -almost -located -multiple -asian -distribution -editor -industrial -cause -potential -focus -featured -rooms -female -responsible -communications -associated -thomas -primary -cancer -numbers -reason -browser -spring -foundation -answer -voice -friendly -schedule -documents -communication -purpose -feature -comes -police -everyone -independent -approach -cameras -brown -physical -operating -medicine -ratings -chicago -forms -glass -happy -smith -wanted -developed -thank -unique -survey -prior -telephone -sport -ready -animal -sources -mexico -population -regular -secure -navigation -operations -therefore -simply -evidence -station -christian -round -paypal -favorite -understand -option -master -valley -recently -probably -rentals -built -publications -blood -worldwide -improve -connection -publisher -larger -networks -earth -parents -nokia -impact -transfer -introduction -kitchen -strong -carolina -wedding -properties -hospital -ground -overview -accommodation -owners -disease -excellent -italy -perfect -opportunity -classic -basis -command -cities -william -express -award -distance -peter -assessment -ensure -involved -extra -especially -interface -partners -budget -rated -guides -success -maximum -operation -existing -quite -selected -amazon -patients -restaurants -beautiful -warning -locations -horse -forward -flowers -stars -significant -lists -technologies -owner -retail -animals -useful -directly -manufacturer -providing -housing -takes -bring -catalog -searches -trying -mother -authority -considered -traffic -programme -joined -input -strategy -agent -valid -modern -senior -ireland -teaching -grand -testing -trial -charge -units -instead -canadian -normal -wrote -enterprise -ships -entire -educational -leading -metal -positive -fitness -chinese -opinion -football -abstract -output -funds -greater -likely -develop -employees -artists -alternative -processing -responsibility -resolution -guest -seems -publication -relations -trust -contains -session -multi -photography -republic -components -vacation -century -academic -assistance -completed -graphics -indian -expected -grade -dating -pacific -mountain -organizations -filter -mailing -vehicle -longer -consider -northern -behind -panel -floor -german -buying -match -proposed -default -require -outdoor -morning -otherwise -allows -protein -plant -reported -transportation -politics -partner -disclaimer -authors -boards -faculty -parties -membership -mission -string -sense -modified -released -stage -internal -goods -recommended -unless -richard -detailed -japanese -approved -background -target -except -character -maintenance -ability -maybe -functions -moving -brands -places -pretty -trademarks -phentermine -spain -southern -yourself -winter -battery -youth -pressure -submitted -boston -keywords -medium -television -interested -break -purposes -throughout -dance -itself -defined -papers -playing -awards -studio -reader -virtual -device -established -answers -remote -programming -external -apple -regarding -instructions -offered -theory -enjoy -remove -surface -minimum -visual -variety -teachers -martin -manual -block -subjects -agents -increased -repair -civil -steel -understanding -songs -fixed -wrong -beginning -hands -associates -finally -updates -desktop -classes -paris -sector -capacity -requires -jersey -fully -father -electric -instruments -quotes -officer -driver -businesses -respect -unknown -specified -restaurant -worth -procedures -teacher -relationship -workers -georgia -peace -traditional -campus -showing -creative -coast -benefit -progress -funding -devices -grant -agree -fiction -sometimes -watches -careers -beyond -families -museum -themselves -transport -interesting -blogs -evaluation -accepted -former -implementation -complex -galleries -references -presented -agencies -literature -respective -parent -spanish -michigan -columbia -setting -scale -stand -economy -highest -helpful -monthly -critical -frame -musical -definition -secretary -angeles -networking -australian -employee -chief -gives -bottom -magazines -packages -detail -francisco -changed -heard -begin -individuals -colorado -royal -clean -switch -russian -largest -african -titles -relevant -guidelines -justice -connect -bible -basket -applied -weekly -installation -described -demand -suite -vegas -square -chris -attention -advance -auction -difference -allowed -correct -charles -nation -selling -piece -sheet -seven -older -illinois -regulations -elements -species -cells -module -resort -facility -random -pricing -certificate -minister -motion -looks -fashion -directions -visitors -documentation -monitor -trading -forest -calls -whose -coverage -couple -giving -chance -vision -ending -clients -actions -listen -discuss -accept -automotive -naked -successful -communities -clinical -situation -sciences -markets -lowest -highly -publishing -appear -emergency -developing -lives -currency -leather -determine -temperature -announcements -patient -actual -historical -stone -commerce -ringtones -perhaps -persons -difficult -scientific -satellite -tests -village -accounts -amateur -particularly -factors -coffee -settings -buyer -cultural -steve -easily -poster -functional -closed -holidays -zealand -balance -monitoring -graduate -replies -architecture -initial -label -thinking -scott -recommend -canon -league -waste -minute -provider -optional -dictionary -accounting -manufacturing -sections -chair -fishing -effort -phase -fields -fantasy -letters -motor -professor -context -install -shirt -apparel -generally -continued -crime -count -breast -techniques -johnson -quickly -dollars -websites -religion -claim -driving -permission -surgery -patch -measures -generation -kansas -chemical -doctor -reduce -brought -himself -component -enable -exercise -santa -guarantee -leader -diamond -israel -processes -servers -alone -meetings -seconds -jones -arizona -keyword -interests -flight -congress -username -produced -italian -paperback -classifieds -supported -pocket -saint -freedom -argument -competition -creating -drugs -joint -premium -providers -fresh -characters -attorney -upgrade -factor -growing -thousands -stream -apartments -hearing -eastern -auctions -therapy -entries -dates -generated -signed -upper -administrative -serious -prime -samsung -limit -began -louis -steps -errors -shops -efforts -informed -thoughts -creek -worked -quantity -urban -practices -sorted -reporting -essential -myself -tours -platform -affiliate -labor -immediately -admin -nursing -defense -machines -designated -heavy -covered -recovery -integrated -configuration -merchant -comprehensive -expert -universal -protect -solid -presentation -languages -became -orange -compliance -vehicles -prevent -theme -campaign -marine -improvement -guitar -finding -pennsylvania -examples -saying -spirit -claims -challenge -motorola -acceptance -strategies -affairs -touch -intended -towards -goals -election -suggest -branch -charges -serve -affiliates -reasons -magic -mount -smart -talking -latin -multimedia -avoid -certified -manage -corner -computing -oregon -element -birth -virus -abuse -interactive -requests -separate -quarter -procedure -leadership -tables -define -racing -religious -facts -breakfast -column -plants -faith -chain -developer -identify -avenue -missing -approximately -domestic -sitemap -recommendations -moved -houston -reach -comparison -mental -viewed -moment -extended -sequence -attack -sorry -centers -opening -damage -reserve -recipes -gamma -plastic -produce -placed -truth -counter -failure -follows -weekend -dollar -ontario -automatically -minnesota -films -bridge -native -williams -movement -printing -baseball -owned -approval -draft -chart -played -contacts -jesus -readers -clubs -jackson -equal -adventure -matching -offering -shirts -profit -leaders -posters -institutions -assistant -variable -advertisement -expect -parking -headlines -yesterday -compared -determined -wholesale -workshop -russia -codes -kinds -extension -seattle -statements -golden -completely -teams -lighting -senate -forces -funny -brother -turned -portable -tried -electrical -applicable -returned -pattern -named -theatre -laser -earlier -manufacturers -sponsor -classical -warranty -dedicated -indiana -direction -harry -basketball -objects -delete -evening -assembly -nuclear -taxes -mouse -signal -criminal -issued -brain -sexual -wisconsin -powerful -dream -obtained -false -flower -personnel -passed -supplied -identified -falls -opinions -promote -stated -stats -hawaii -professionals -appears -carry -decided -covers -advantage -hello -designs -maintain -tourism -priority -newsletters -adults -clips -savings -graphic -payments -estimated -binding -brief -ended -winning -eight -anonymous -straight -script -served -wants -miscellaneous -prepared -dining -alert -integration -atlanta -dakota -interview -framework -installed -queen -credits -clearly -handle -sweet -criteria -pubmed -massachusetts -diego -associate -truck -behavior -enlarge -frequently -revenue -measure -changing -votes -looked -discussions -festival -laboratory -ocean -flights -experts -signs -depth -whatever -logged -laptop -vintage -train -exactly -explore -maryland -concept -nearly -eligible -checkout -reality -forgot -handling -origin -gaming -feeds -billion -destination -scotland -faster -intelligence -dallas -bought -nations -route -followed -specifications -broken -tripadvisor -frank -alaska -battle -residential -anime -speak -decisions -industries -protocol -query -partnership -editorial -expression -equity -provisions -speech -principles -suggestions -rural -shared -sounds -replacement -strategic -judge -economics -bytes -forced -compatible -fight -apartment -height -speaker -filed -netherlands -obtain -consulting -recreation -offices -designer -remain -managed -failed -marriage -korea -banks -participants -secret -kelly -leads -negative -austin -favorites -toronto -theater -springs -missouri -andrew -perform -healthy -translation -estimates -assets -injury -joseph -ministry -drivers -lawyer -figures -married -protected -proposal -sharing -philadelphia -portal -waiting -birthday -gratis -banking -officials -brian -toward -slightly -assist -conduct -contained -lingerie -legislation -calling -parameters -serving -profiles -miami -comics -matters -houses -postal -relationships -tennessee -controls -breaking -combined -ultimate -wales -representative -frequency -introduced -minor -finish -departments -residents -noted -displayed -reduced -physics -spent -performed -extreme -samples -davis -daniel -reviewed -forecast -removed -helps -singles -administrator -cycle -amounts -contain -accuracy -sleep -pharmacy -brazil -creation -static -scene -hunter -addresses -crystal -famous -writer -chairman -violence -oklahoma -speakers -drink -academy -dynamic -gender -permanent -agriculture -cleaning -constitutes -portfolio -practical -delivered -collectibles -infrastructure -exclusive -concerns -colour -vendor -originally -intel -utilities -philosophy -regulation -officers -reduction -referred -supports -nutrition -recording -regions -junior -rings -meaning -secondary -wonderful -ladies -henry -ticket -announced -guess -agreed -prevention -soccer -import -posting -presence -instant -mentioned -automatic -healthcare -viewing -maintained -increasing -majority -connected -christ -directors -aspects -austria -ahead -participation -scheme -utility -preview -manner -matrix -containing -combination -devel -amendment -despite -strength -guaranteed -turkey -libraries -proper -distributed -degrees -singapore -enterprises -delta -seeking -inches -phoenix -convention -shares -principal -daughter -standing -comfort -colors -cisco -ordering -alpha -appeal -cruise -bonus -certification -previously -bookmark -buildings -specials -disney -household -batteries -adobe -smoking -becomes -drives -alabama -improved -trees -achieve -positions -dress -subscription -dealer -contemporary -nearby -carried -happen -exposure -panasonic -permalink -signature -gambling -refer -miller -provision -outdoors -clothes -caused -luxury -babes -frames -certainly -indeed -newspaper -circuit -layer -printed -removal -easier -liability -trademark -printers -adding -kentucky -mostly -taylor -trackback -prints -spend -factory -interior -revised -americans -optical -promotion -relative -amazing -clock -identity -suites -conversion -feeling -hidden -reasonable -victoria -serial -relief -revision -broadband -influence -ratio -importance -planet -webmaster -copies -recipe -permit -seeing -proof -tennis -prescription -bedroom -empty -instance -licensed -orlando -specifically -bureau -maine -represent -conservation -ideal -specs -recorded -pieces -finished -parks -dinner -lawyers -sydney -stress -cream -trends -discover -patterns -boxes -louisiana -hills -javascript -fourth -advisor -marketplace -aware -wilson -shape -evolution -irish -certificates -objectives -stations -suggested -remains -greatest -firms -concerned -operator -structures -generic -encyclopedia -usage -charts -continuing -mixed -census -interracial -competitive -exist -wheel -transit -suppliers -compact -poetry -lights -tracking -angel -keeping -preparation -attempt -receiving -matches -accordance -width -noise -engines -forget -array -discussed -accurate -stephen -elizabeth -climate -reservations -playstation -alcohol -greek -instruction -managing -annotation -sister -differences -walking -explain -smaller -newest -establish -happened -expressed -extent -sharp -lesbians -paragraph -mathematics -compensation -export -managers -aircraft -modules -sweden -conflict -conducted -versions -employer -occur -percentage -knows -mississippi -describe -concern -backup -requested -citizens -connecticut -heritage -personals -immediate -holding -trouble -spread -coach -kevin -agricultural -expand -supporting -audience -assigned -jordan -collections -participate -specialist -affect -virgin -experienced -investigation -raised -institution -directed -dealers -searching -sporting -helping -affected -totally -plate -expenses -indicate -blonde -proceedings -favourite -transmission -anderson -characteristics -organic -experiences -albums -cheats -extremely -verzeichnis -contracts -guests -hosted -diseases -concerning -developers -equivalent -chemistry -neighborhood -nevada -thailand -variables -agenda -anyway -continues -tracks -advisory -curriculum -logic -template -prince -circle -grants -anywhere -psychology -responses -atlantic -circumstances -edward -investor -identification -leaving -wildlife -appliances -elementary -cooking -speaking -sponsors -unlimited -respond -sizes -plain -entered -launch -checking -costa -belgium -printable -guidance -trail -enforcement -symbol -crafts -highway -buddy -hardcover -observed -setup -booking -glossary -fiscal -celebrity -styles -denver -filled -channels -ericsson -appendix -notify -blues -chocolate -portion -scope -hampshire -supplier -cables -cotton -bluetooth -controlled -requirement -authorities -biology -dental -killed -border -ancient -debate -representatives -starts -pregnancy -causes -arkansas -biography -leisure -attractions -learned -transactions -notebook -explorer -historic -attached -opened -husband -disabled -authorized -crazy -upcoming -britain -concert -retirement -scores -financing -efficiency -comedy -adopted -efficient -weblog -linear -commitment -specialty -bears -carrier -edited -constant -mouth -jewish -meter -linked -portland -interviews -concepts -reflect -deliver -wonder -lessons -fruit -begins -qualified -reform -alerts -treated -discovery -mysql -classified -relating -assume -confidence -alliance -confirm -neither -lewis -howard -offline -leaves -engineer -lifestyle -consistent -replace -clearance -connections -inventory -converter -organisation -checks -reached -becoming -safari -objective -indicated -sugar -stick -securities -allen -relation -enabled -genre -slide -montana -volunteer -tested -democratic -enhance -switzerland -exact -bound -parameter -adapter -processor -formal -dimensions -contribute -hockey -storm -micro -colleges -laptops -showed -challenges -editors -threads -supreme -brothers -recognition -presents -submission -dolls -estimate -encourage -regulatory -inspection -consumers -cancel -limits -territory -transaction -manchester -weapons -paint -delay -pilot -outlet -contributions -continuous -czech -resulting -cambridge -initiative -novel -execution -disability -increases -ultra -winner -idaho -contractor -episode -examination -potter -plays -bulletin -indicates -modify -oxford -truly -epinions -painting -committed -extensive -affordable -universe -candidate -databases -patent -outstanding -eating -perspective -planned -watching -lodge -messenger -mirror -tournament -consideration -discounts -sterling -sessions -kernel -stocks -buyers -journals -catalogue -jennifer -antonio -charged -broad -taiwan -chosen -greece -swiss -sarah -clark -labour -terminal -publishers -nights -behalf -caribbean -liquid -nebraska -salary -reservation -foods -gourmet -guard -properly -orleans -saving -remaining -empire -resume -twenty -newly -raise -prepare -avatar -depending -illegal -expansion -hundreds -lincoln -helped -premier -tomorrow -purchased -decide -consent -drama -visiting -performing -downtown -keyboard -contest -collected -bands -suitable -absolutely -millions -lunch -audit -chamber -guinea -findings -muscle -featuring -implement -clicking -scheduled -polls -typical -tower -yours -calculator -significantly -chicken -temporary -attend -shower -sending -jason -tonight -sufficient -holdem -shell -province -catholic -awareness -vancouver -governor -seemed -contribution -measurement -swimming -spyware -formula -constitution -packaging -solar -catch -pakistan -reliable -consultation -northwest -doubt -finder -unable -periods -classroom -tasks -democracy -attacks -wallpaper -merchandise -const -resistance -doors -symptoms -resorts -biggest -memorial -visitor -forth -insert -baltimore -gateway -alumni -drawing -candidates -charlotte -ordered -biological -fighting -transition -happens -preferences -romance -instrument -bruce -split -themes -powers -heaven -pregnant -twice -classification -focused -egypt -physician -hollywood -bargain -wikipedia -cellular -norway -vermont -asking -blocks -normally -spiritual -hunting -diabetes -shift -bodies -photographs -cutting -simon -writers -marks -flexible -loved -favourites -mapping -numerous -relatively -birds -satisfaction -represents -indexed -pittsburgh -superior -preferred -saved -paying -cartoon -shots -intellectual -moore -granted -choices -carbon -spending -comfortable -magnetic -interaction -listening -effectively -registry -crisis -outlook -massive -denmark -employed -bright -treat -header -poverty -formed -piano -sheets -patrick -experimental -puerto -revolution -consolidation -displays -plasma -allowing -earnings -mystery -landscape -dependent -mechanical -journey -delaware -bidding -consultants -risks -banner -applicant -charter -barbara -cooperation -counties -acquisition -ports -implemented -directories -recognized -dreams -blogger -notification -licensing -stands -teach -occurred -textbooks -rapid -hairy -diversity -cleveland -reverse -deposit -seminar -investments -latina -wheels -specify -accessibility -dutch -sensitive -templates -formats -depends -boots -holds -router -concrete -editing -poland -folder -womens -completion -upload -pulse -universities -technique -contractors -voting -courts -notices -subscriptions -calculate -detroit -alexander -broadcast -converted -metro -toshiba -anniversary -improvements -strip -specification -pearl -accident -accessible -accessory -resident -possibly -airline -typically -representation -regard -exists -arrangements -smooth -conferences -uniprotkb -strike -consumption -birmingham -flashing -narrow -afternoon -threat -surveys -sitting -putting -consultant -controller -ownership -committees -legislative -researchers -vietnam -trailer -castle -gardens -missed -malaysia -unsubscribe -antique -labels -willing -molecular -acting -heads -stored -logos -residence -attorneys -antiques -density -hundred -operators -strange -sustainable -philippines -statistical -mention -innovation -employers -parallel -honda -amended -operate -bills -bathroom -stable -opera -definitions -doctors -lesson -cinema -asset -elections -drinking -reaction -blank -enhanced -entitled -severe -generate -stainless -newspapers -hospitals -deluxe -humor -monitors -exception -lived -duration -successfully -indonesia -pursuant -fabric -visits -primarily -tight -domains -capabilities -contrast -recommendation -flying -recruitment -berlin -organized -siemens -adoption -improving -expensive -meant -capture -pounds -buffalo -organisations -plane -explained -programmes -desire -expertise -mechanism -camping -jewellery -meets -welfare -caught -eventually -marked -driven -measured -medline -bottle -agreements -considering -innovative -marshall -massage -rubber -conclusion -closing -tampa -thousand -legend -grace -susan -adams -python -monster -villa -columns -disorders -collaboration -hamilton -detection -cookies -inner -formation -tutorial -engineers -entity -cruises -holder -proposals -moderator -tutorials -settlement -portugal -lawrence -roman -duties -valuable -collectables -ethics -forever -dragon -captain -fantastic -imagine -brings -heating -governments -purchasing -scripts -stereo -appointed -taste -dealing -commit -operational -airlines -liberal -livecam -trips -sides -turns -corresponding -descriptions -cache -jacket -determination -animation -oracle -matthew -lease -productions -aviation -hobbies -proud -excess -disaster -console -commands -telecommunications -instructor -giant -achieved -injuries -shipped -seats -approaches -alarm -voltage -anthony -nintendo -usual -loading -stamps -appeared -franklin -angle -vinyl -highlights -mining -designers -melbourne -ongoing -worst -imaging -betting -scientists -liberty -wyoming -blackjack -argentina -convert -possibility -analyst -commissioner -dangerous -garage -exciting -reliability -thongs -unfortunately -respectively -volunteers -attachment -ringtone -finland -morgan -derived -pleasure -honor -oriented -eagle -desktops -pants -columbus -nurse -prayer -appointment -workshops -hurricane -quiet -postage -producer -represented -mortgages -responsibilities -cheese -comic -carefully -productivity -investors -crown -underground -diagnosis -maker -crack -principle -picks -vacations -semester -calculated -fetish -applies -casinos -appearance -smoke -apache -filters -incorporated -craft -notebooks -apart -fellow -blind -lounge -algorithm -coins -gross -strongly -valentine -hilton -proteins -horror -familiar -capable -douglas -debian -involving -investing -christopher -admission -epson -elected -carrying -victory -madison -terrorism -editions -mainly -ethnic -parliament -actor -finds -situations -fifth -allocated -citizen -vertical -corrections -structural -municipal -describes -prize -occurs -absolute -disabilities -consists -anytime -substance -prohibited -addressed -soldiers -guardian -lecture -simulation -layout -initiatives -concentration -classics -interpretation -horses -dirty -wayne -donate -taught -bankruptcy -worker -optimization -alive -temple -substances -prove -discovered -wings -breaks -genetic -restrictions -participating -waters -promise -exhibition -prefer -ridge -cabinet -modem -harris -bringing -evaluate -tiffany -tropical -collect -composition -toyota -streets -nationwide -vector -definitely -shaved -turning -buffer -purple -existence -commentary -larry -limousines -developments -immigration -destinations -mutual -pipeline -necessarily -syntax -attribute -prison -skill -chairs -everyday -apparently -surrounding -mountains -moves -popularity -inquiry -ethernet -checked -exhibit -throw -trend -sierra -visible -desert -postposted -oldest -rhode -coordinator -obviously -mercury -steven -handbook -navigate -worse -summit -victims -spaces -fundamental -burning -escape -coupons -somewhat -receiver -substantial -progressive -cialis -boats -glance -scottish -championship -arcade -richmond -sacramento -impossible -russell -tells -obvious -fiber -depression -graph -covering -platinum -judgment -bedrooms -talks -filing -foster -modeling -passing -awarded -testimonials -trials -tissue -memorabilia -clinton -masters -bonds -cartridge -alberta -explanation -commons -cincinnati -subsection -fraud -electricity -permitted -spectrum -arrival -pottery -emphasis -roger -aspect -workplace -awesome -mexican -confirmed -counts -priced -wallpapers -crash -desired -inter -closer -assumes -heights -shadow -riding -infection -firefox -expense -grove -eligibility -venture -clinic -korean -healing -princess -entering -packet -spray -studios -involvement -buttons -placement -observations -vbulletin -funded -thompson -winners -extend -roads -subsequent -dublin -rolling -motorcycle -disclosure -establishment -memories -nelson -arrived -creates -faces -tourist -mayor -murder -adequate -senator -yield -presentations -grades -cartoons -digest -lodging -hence -entirely -replaced -radar -rescue -undergraduate -losses -combat -reducing -stopped -occupation -lakes -donations -associations -citysearch -closely -radiation -diary -seriously -kings -shooting -flags -baker -launched -elsewhere -pollution -conservative -guestbook -shock -effectiveness -walls -abroad -ebony -drawn -arthur -visited -walker -demonstrate -atmosphere -suggests -beast -operated -experiment -targets -overseas -purchases -dodge -counsel -federation -pizza -invited -yards -assignment -chemicals -gordon -farmers -queries -ukraine -absence -nearest -cluster -vendors -whereas -serves -woods -surprise -partial -shoppers -everybody -couples -nashville -ranking -jokes -simpson -twiki -sublime -counseling -palace -acceptable -satisfied -measurements -verify -globe -trusted -copper -milwaukee -medication -warehouse -shareware -dicke -kerry -receipt -supposed -ordinary -nobody -ghost -violation -configure -stability -applying -southwest -pride -institutional -expectations -independence -knowing -reporter -metabolism -keith -champion -cloudy -linda -personally -chile -plenty -sentence -throat -ignore -maria -uniform -excellence -wealth -somewhere -vacuum -dancing -attributes -recognize -brass -writes -plaza -outcomes -survival -quest -publish -screening -thumbnail -trans -jonathan -whenever -lifetime -pioneer -booty -forgotten -acrobat -plates -acres -venue -athletic -thermal -essays -behaviour -vital -telling -fairly -coastal -config -charity -intelligent -edinburgh -excel -modes -obligation -campbell -stupid -harbor -hungary -traveler -segment -realize -regardless -enemy -puzzle -rising -aluminum -wells -wishlist -opens -insight -restricted -republican -secrets -lucky -latter -merchants -thick -trailers -repeat -syndrome -philips -attendance -penalty -glasses -enables -iraqi -builder -vista -jessica -chips -terry -flood -arguments -amsterdam -arena -adventures -pupils -stewart -announcement -outcome -appreciate -expanded -casual -grown -polish -lovely -extras -centres -jerry -clause -smile -lands -troops -indoor -bulgaria -armed -broker -charger -regularly -believed -cooling -trucks -mechanisms -divorce -laura -shopper -tokyo -partly -nikon -customize -tradition -candy -pills -tiger -donald -folks -sensor -exposed -telecom -angels -deputy -indicators -sealed -emissions -physicians -loaded -complaint -scenes -experiments -afghanistan -boost -spanking -scholarship -governance -founded -supplements -chronic -icons -moral -catering -finger -keeps -pound -locate -camcorder -trained -implementing -roses -ourselves -bread -tobacco -wooden -motors -tough -roberts -incident -gonna -dynamics -conversation -decrease -chest -pension -billy -revenues -emerging -worship -capability -craig -herself -producing -churches -precision -damages -reserves -contributed -solve -shorts -reproduction -minority -diverse -ingredients -johnny -franchise -recorder -complaints -facing -nancy -promotions -tones -passion -rehabilitation -maintaining -sight -defence -patches -refund -towns -environments -trembl -divided -reception -emails -cyprus -correctly -insider -seminars -consequences -makers -hearts -geography -appearing -integrity -worry -discrimination -carter -legacy -pleased -danger -vitamin -widely -processed -phrase -genuine -raising -implications -functionality -paradise -hybrid -reads -roles -intermediate -emotional -glory -platforms -bigger -billing -diesel -versus -combine -overnight -geographic -exceed -saudi -fault -preliminary -districts -introduce -promotional -chevrolet -babies -karen -compiled -romantic -revealed -specialists -generator -albert -examine -jimmy -graham -suspension -bristol -margaret -compaq -correction -slowly -authentication -communicate -rugby -supplement -showtimes -portions -infant -promoting -sectors -samuel -fluid -grounds -regards -machinery -bandwidth -unlike -equation -baskets -probability -dimension -wright -barry -proven -schedules -admissions -cached -warren -studied -reviewer -involves -quarterly -profits -devil -grass -comply -marie -florist -illustrated -cherry -continental -alternate -deutsch -achievement -limitations -kenya -webcam -funeral -nutten -earrings -enjoyed -automated -chapters -charlie -quebec -passenger -convenient -dennis -francis -sized -manga -noticed -socket -silent -literary -signals -orientation -theft -childhood -swing -symbols -humans -analog -facial -choosing -talent -dated -flexibility -seeker -wisdom -shoot -boundary -packard -offset -payday -philip -elite -holders -believes -swedish -poems -deadline -jurisdiction -robot -displaying -witness -collins -equipped -stages -encouraged -winds -powder -broadway -acquired -assess -cartridges -stones -entrance -gnome -roots -declaration -losing -attempts -gadgets -noble -glasgow -automation -impacts -gospel -advantages -shore -loves -induced -knight -preparing -loose -recipient -linking -extensions -appeals -earned -illness -islamic -athletics -southeast -alternatives -pending -parker -determining -lebanon -personalized -kennedy -conditioning -teenage -triple -cooper -vincent -secured -unusual -answered -partnerships -destruction -slots -increasingly -migration -disorder -routine -toolbar -basically -rocks -conventional -titans -applicants -wearing -sought -genes -mounted -habitat -firewall -median -scanner -herein -occupational -animated -judicial -adjustment -integer -treatments -bachelor -attitude -camcorders -engaged -falling -basics -montreal -carpet -struct -lenses -binary -genetics -attended -difficulty -collective -coalition -dropped -enrollment -walter -besides -producers -collector -hosts -interfaces -advertisers -moments -atlas -strings -representing -observation -feels -torture -deleted -mitchell -restoration -convenience -returning -ralph -opposition -container -defendant -warner -confirmation -embedded -inkjet -supervisor -wizard -corps -actors -liver -peripherals -liable -brochure -morris -bestsellers -petition -eminem -recall -antenna -picked -assumed -departure -minneapolis -belief -killing -bikini -memphis -shoulder -decor -lookup -texts -harvard -brokers -diameter -ottawa -podcast -seasons -interactions -refine -bidder -singer -evans -herald -literacy -fails -aging -intervention -plugin -attraction -diving -invite -modification -alice -latinas -suppose -customized -involve -moderate -terror -younger -thirty -opposite -understood -rapidly -dealtime -intro -mercedes -assurance -clerk -happening -mills -outline -amendments -tramadol -holland -receives -jeans -metropolitan -compilation -verification -fonts -refers -favor -veterans -sigma -attractive -xhtml -occasion -recordings -jefferson -victim -demands -sleeping -careful -gardening -obligations -arrive -orchestra -sunset -tracked -moreover -minimal -polyphonic -lottery -framed -aside -outsourcing -licence -adjustable -allocation -michelle -essay -discipline -demonstrated -dialogue -identifying -alphabetical -camps -declared -dispatched -aaron -handheld -trace -disposal -florists -packs -installing -switches -romania -voluntary -consult -greatly -blogging -cycling -midnight -commonly -photographer -inform -turkish -messaging -pentium -quantum -murray -intent -largely -pleasant -announce -constructed -additions -requiring -spoke -arrow -engagement -sampling -rough -weird -refinance -inspired -holes -weddings -blade -suddenly -oxygen -cookie -meals -canyon -meters -merely -calendars -arrangement -conclusions -passes -bibliography -pointer -compatibility -stretch -durham -furthermore -permits -cooperative -muslim -sleeve -netscape -cleaner -cricket -feeding -stroke -township -rankings -measuring -robin -robinson -jacksonville -strap -headquarters -sharon -crowd -transfers -olympic -transformation -remained -attachments -entities -customs -administrators -personality -rainbow -roulette -decline -gloves -israeli -medicare -skiing -cloud -facilitate -subscriber -valve -hewlett -explains -proceed -flickr -feelings -knife -jamaica -priorities -shelf -bookstore -timing -liked -parenting -adopt -denied -fotos -incredible -britney -freeware -donation -outer -deaths -rivers -commonwealth -pharmaceutical -manhattan -tales -katrina -workforce -islam -nodes -thumbs -seeds -cited -targeted -organizational -skype -realized -twelve -founder -decade -gamecube -dispute -portuguese -tired -titten -adverse -everywhere -excerpt -steam -discharge -drinks -voices -acute -halloween -climbing -stood -perfume -carol -honest -albany -hazardous -restore -stack -methodology -somebody -housewares -reputation -resistant -democrats -recycling -curve -creator -amber -qualifications -museums -coding -slideshow -tracker -variation -passage -transferred -trunk -hiking -pierre -jelsoft -headset -photograph -oakland -colombia -waves -camel -distributor -lamps -underlying -wrestling -suicide -archived -photoshop -arabia -gathering -projection -juice -chase -mathematical -logical -sauce -extract -specialized -diagnostic -panama -indianapolis -payable -corporations -courtesy -criticism -automobile -confidential -statutory -accommodations -athens -northeast -downloaded -judges -retired -remarks -detected -decades -paintings -walked -arising -nissan -bracelet -juvenile -injection -yorkshire -populations -protective -afraid -acoustic -railway -cassette -initially -indicator -pointed -causing -mistake -norton -locked -eliminate -fusion -mineral -sunglasses -steering -beads -fortune -preference -canvas -threshold -parish -claimed -screens -cemetery -planner -croatia -flows -stadium -venezuela -exploration -fewer -sequences -coupon -nurses -proxy -astronomy -lanka -edwards -contests -translate -announces -costume -tagged -berkeley -voted -killer -bikes -gates -adjusted -bishop -pulled -shaped -compression -seasonal -establishing -farmer -counters -constitutional -perfectly -slave -instantly -cultures -norfolk -coaching -examined -encoding -litigation -submissions -heroes -painted -lycos -zdnet -broadcasting -horizontal -artwork -cosmetic -resulted -portrait -terrorist -informational -ethical -carriers -ecommerce -mobility -floral -builders -struggle -schemes -suffering -neutral -fisher -spears -prospective -bedding -ultimately -joining -heading -equally -artificial -bearing -spectacular -coordination -connector -combo -seniors -worlds -guilty -affiliated -activation -naturally -haven -tablet -subscribers -charm -violent -mitsubishi -underwear -basin -potentially -ranch -constraints -crossing -inclusive -dimensional -cottage -drunk -considerable -crimes -resolved -mozilla -toner -latex -branches -anymore -delhi -holdings -alien -locator -selecting -processors -pantyhose -broke -nepal -zimbabwe -difficulties -complexity -constantly -browsing -resolve -barcelona -presidential -documentary -territories -melissa -moscow -thesis -nylon -palestinian -discs -rocky -bargains -frequent -nigeria -ceiling -pixels -ensuring -hispanic -legislature -hospitality -anybody -procurement -diamonds -fleet -untitled -bunch -totals -marriott -singing -theoretical -afford -exercises -starring -referral -surveillance -optimal -distinct -protocols -highlight -substitute -inclusion -hopefully -brilliant -turner -sucking -cents -reuters -spoken -omega -evaluated -stayed -civic -assignments -manuals -termination -watched -saver -thereof -grill -households -redeem -rogers -grain -authentic -regime -wanna -wishes -montgomery -architectural -louisville -depend -differ -macintosh -movements -ranging -monica -repairs -breath -amenities -virtually -candle -hanging -colored -authorization -verified -formerly -projector -situated -comparative -seeks -herbal -loving -strictly -routing -stanley -psychological -surprised -retailer -vitamins -elegant -gains -renewal -genealogy -opposed -deemed -scoring -expenditure -brooklyn -liverpool -sisters -critics -connectivity -spots -algorithms -hacker -madrid -similarly -margin -solely -salon -collaborative -norman -excluding -turbo -headed -voters -madonna -commander -murphy -thinks -thats -suggestion -soldier -phillips -aimed -justin -interval -mirrors -spotlight -tricks -reset -brush -investigate -expansys -panels -repeated -assault -connecting -spare -logistics -kodak -tongue -bowling -danish -monkey -proportion -filename -skirt -florence -invest -honey -analyses -drawings -significance -scenario -lovers -atomic -approx -symposium -arabic -gauge -essentials -junction -protecting -faced -rachel -solving -transmitted -weekends -screenshots -produces -intensive -chains -kingston -sixth -engage -deviant -switching -quoted -adapters -correspondence -farms -imports -supervision -cheat -bronze -expenditures -sandy -separation -testimony -suspect -celebrities -macro -sender -mandatory -boundaries -crucial -syndication -celebration -adjacent -filtering -tuition -spouse -exotic -viewer -signup -threats -luxembourg -puzzles -reaching -damaged -receptor -laugh -surgical -destroy -citation -pitch -autos -premises -perry -proved -offensive -imperial -dozen -benjamin -deployment -teeth -cloth -studying -colleagues -stamp -lotus -salmon -olympus -separated -cargo -directive -salem -starter -upgrades -likes -butter -pepper -weapon -luggage -burden -tapes -zones -races -stylish -maple -grocery -offshore -governing -retailers -depot -kenneth -blend -harrison -julie -occasionally -attending -emission -finest -realty -janet -recruiting -apparent -instructional -phpbb -autumn -traveling -probe -permissions -biotechnology -toilet -ranked -jackets -routes -packed -excited -outreach -helen -mounting -recover -lopez -balanced -prescribed -catherine -timely -talked -debug -delayed -chuck -reproduced -explicit -calculation -villas -ebook -consolidated -exclude -peeing -occasions -brooks -equations -newton -exceptional -anxiety -bingo -whilst -spatial -respondents -ceramic -prompt -precious -minds -annually -considerations -scanners -xanax -fingers -sunny -ebooks -delivers -queensland -necklace -musicians -leeds -composite -unavailable -cedar -arranged -theaters -advocacy -raleigh -essentially -designing -threaded -qualify -blair -hopes -assessments -mason -diagram -burns -pumps -footwear -beijing -peoples -victor -mario -attach -licenses -utils -removing -advised -brunswick -spider -ranges -pairs -sensitivity -trails -preservation -hudson -isolated -calgary -interim -assisted -divine -streaming -approve -chose -compound -intensity -technological -syndicate -abortion -dialog -venues -blast -wellness -calcium -newport -antivirus -addressing -discounted -indians -shield -harvest -membrane -prague -previews -bangladesh -constitute -locally -concluded -pickup -desperate -mothers -nascar -iceland -demonstration -governmental -manufactured -candles -graduation -sailing -variations -sacred -addiction -morocco -chrome -tommy -springfield -refused -brake -exterior -greeting -ecology -oliver -congo -botswana -delays -synthesis -olive -undefined -unemployment -cyber -verizon -scored -enhancement -newcastle -clone -dicks -velocity -lambda -relay -composed -tears -performances -oasis -baseline -angry -societies -silicon -brazilian -identical -petroleum -compete -norwegian -lover -belong -honolulu -beatles -retention -exchanges -rolls -thomson -barnes -soundtrack -wondering -malta -daddy -ferry -rabbit -profession -seating -separately -physiology -collecting -exports -omaha -participant -scholarships -recreational -dominican -electron -loads -friendship -heather -passport -motel -unions -treasury -warrant -solaris -frozen -occupied -royalty -scales -rally -observer -sunshine -strain -ceremony -somehow -arrested -expanding -provincial -investigations -yamaha -medications -hebrew -gained -rochester -dying -laundry -stuck -solomon -placing -stops -homework -adjust -assessed -advertiser -enabling -encryption -filling -downloadable -sophisticated -imposed -silence -focuses -soviet -possession -laboratories -treaty -vocal -trainer -organ -stronger -volumes -advances -vegetables -lemon -toxic -thumbnails -darkness -bizrate -vienna -implied -stanford -stockings -respondent -packing -statute -rejected -satisfy -destroyed -shelter -chapel -gamespot -manufacture -layers -wordpress -guided -vulnerability -accountability -celebrate -accredited -appliance -compressed -bahamas -powell -mixture -bench -rider -scheduling -radius -perspectives -mortality -logging -hampton -christians -borders -therapeutic -butts -bobby -impressive -sheep -accordingly -architect -railroad -lectures -challenging -wines -nursery -harder -microwave -cheapest -accidents -travesti -relocation -stuart -contributors -salvador -salad -monroe -tender -violations -temperatures -paste -clouds -competitions -discretion -tanzania -preserve -unsigned -staying -cosmetics -easter -theories -repository -praise -jeremy -venice -concentrations -estonia -christianity -veteran -streams -landing -signing -executed -katie -negotiations -realistic -showcase -integral -relax -namibia -generating -christina -congressional -synopsis -hardly -prairie -reunion -composer -sword -absent -photographic -sells -ecuador -hoping -accessed -spirits -modifications -coral -pixel -float -colin -imported -paths -bubble -acquire -contrary -millennium -tribune -vessel -acids -focusing -viruses -cheaper -admitted -dairy -admit -fancy -equality -samoa -achieving -stickers -fisheries -exceptions -reactions -leasing -lauren -beliefs -macromedia -companion -squad -analyze -ashley -scroll -relate -divisions -wages -additionally -suffer -forests -fellowship -invalid -concerts -martial -males -victorian -retain -colours -execute -tunnel -genres -cambodia -patents -copyrights -chaos -lithuania -mastercard -wheat -chronicles -obtaining -beaver -updating -distribute -readings -decorative -kijiji -confused -compiler -enlargement -eagles -bases -accused -campaigns -unity -conjunction -bride -defines -airports -instances -indigenous -begun -brunette -packets -anchor -socks -validation -parade -corruption -trigger -incentives -cholesterol -gathered -essex -slovenia -notified -differential -beaches -folders -dramatic -surfaces -terrible -routers -pendant -dresses -baptist -scientist -starsmerchant -hiring -clocks -arthritis -females -wallace -nevertheless -reflects -taxation -fever -cuisine -surely -practitioners -transcript -myspace -theorem -inflation -stylus -compounds -drums -contracting -arnold -structured -reasonably -chicks -cattle -radical -graduates -rover -recommends -controlling -treasure -reload -distributors -flame -levitra -tanks -assuming -monetary -elderly -arlington -particles -floating -extraordinary -indicating -bolivia -spell -hottest -stevens -coordinate -kuwait -exclusively -emily -alleged -limitation -widescreen -compile -webster -struck -illustration -plymouth -warnings -construct -inquiries -bridal -annex -inspiration -tribal -curious -affecting -freight -rebate -meetup -eclipse -sudan -downloading -shuttle -aggregate -stunning -cycles -affects -forecasts -detect -actively -ampland -complicated -fastest -butler -shopzilla -injured -decorating -payroll -cookbook -expressions -courier -uploaded -shakespeare -hints -collapse -americas -connectors -unlikely -conflicts -techno -beverage -tribute -wired -elvis -immune -latvia -travelers -forestry -barriers -rarely -infected -offerings -martha -genesis -barrier -argue -incorrect -trains -metals -bicycle -furnishings -letting -arise -guatemala -celtic -thereby -jamie -particle -perception -minerals -advise -humidity -bottles -boxing -bangkok -renaissance -pathology -ordinance -hughes -photographers -infections -jeffrey -chess -operates -brisbane -configured -survive -oscar -festivals -menus -possibilities -reveal -canal -amino -contributing -herbs -clinics -manitoba -analytical -missions -watson -lying -costumes -strict -saddam -circulation -drill -offense -bryan -protest -assumption -jerusalem -hobby -tries -transexuales -invention -nickname -technician -inline -executives -enquiries -washing -staffing -cognitive -exploring -trick -enquiry -closure -timber -intense -playlist -registrar -showers -supporters -ruling -steady -statutes -withdrawal -myers -drops -predicted -wider -saskatchewan -cancellation -plugins -enrolled -sensors -screw -ministers -publicly -hourly -blame -geneva -freebsd -veterinary -prostores -reseller -handed -suffered -intake -informal -relevance -incentive -butterfly -tucson -mechanics -heavily -swingers -fifty -headers -mistakes -numerical -uncle -defining -counting -reflection -accompanied -assure -invitation -devoted -princeton -jacob -sodium -randy -spirituality -hormone -meanwhile -proprietary -timothy -childrens -brick -naval -thumbzilla -medieval -porcelain -bridges -pichunter -captured -thehun -decent -casting -dayton -translated -shortly -cameron -columnists -carlos -donna -andreas -warrior -diploma -cabin -innocent -scanning -consensus -valium -copying -delivering -cordless -patricia -eddie -uganda -fired -journalism -trivia -adidas -perth -grammar -intention -syria -disagree -klein -harvey -tires -undertaken -hazard -retro -statewide -semiconductor -gregory -episodes -boolean -circular -anger -mainland -illustrations -suits -chances -interact -happiness -substantially -bizarre -glenn -auckland -olympics -fruits -identifier -ribbon -calculations -conducting -startup -suzuki -trinidad -kissing -handy -exempt -crops -reduces -accomplished -calculators -geometry -impression -slovakia -guild -correlation -gorgeous -capitol -dishes -barbados -chrysler -nervous -refuse -extends -fragrance -mcdonald -replica -plumbing -brussels -tribe -neighbors -trades -superb -transparent -trinity -charleston -handled -legends -champions -floors -selections -projectors -inappropriate -exhaust -comparing -shanghai -speaks -burton -vocational -davidson -copied -scotia -farming -gibson -pharmacies -roller -introducing -batch -organize -appreciated -alter -nicole -latino -ghana -edges -mixing -handles -skilled -fitted -albuquerque -harmony -distinguished -asthma -projected -assumptions -shareholders -twins -developmental -regulated -triangle -amend -anticipated -oriental -reward -windsor -zambia -completing -hydrogen -webshots -sprint -comparable -chick -advocate -confusion -copyrighted -inputs -warranties -genome -escorts -documented -thong -medal -paperbacks -coaches -vessels -harbour -walks -keyboards -knives -vulnerable -arrange -artistic -honors -booth -indie -reflected -unified -bones -breed -detector -ignored -polar -fallen -precise -sussex -respiratory -notifications -msgid -transexual -mainstream -invoice -evaluating -subcommittee -gather -maternity -backed -alfred -colonial -carey -motels -forming -embassy -journalists -danny -rebecca -slight -proceeds -indirect -amongst -foundations -msgstr -arrest -volleyball -adipex -horizon -deeply -toolbox -marina -liabilities -prizes -bosnia -browsers -decreased -patio -tolerance -surfing -creativity -lloyd -describing -optics -pursue -lightning -overcome -quotations -inspector -attract -brighton -beans -bookmarks -ellis -disable -snake -succeed -leonard -lending -reminder -searched -behavioral -riverside -bathrooms -plains -raymond -insights -abilities -initiated -sullivan -midwest -karaoke -lonely -nonprofit -lancaster -suspended -hereby -observe -julia -containers -attitudes -berry -collar -simultaneously -racial -integrate -bermuda -amanda -sociology -mobiles -screenshot -exhibitions -kelkoo -confident -retrieved -exhibits -officially -consortium -terrace -bacteria -replied -seafood -novels -recipients -ought -delicious -traditions -safely -finite -kidney -periodically -fixes -sends -durable -mazda -allied -throws -moisture -hungarian -roster -referring -symantec -spencer -wichita -nasdaq -uruguay -transform -timer -tablets -tuning -gotten -educators -tyler -futures -vegetable -verse -highs -humanities -independently -wanting -custody -scratch -launches -alignment -masturbating -henderson -britannica -ellen -competitors -rocket -bullet -towers -racks -nasty -visibility -latitude -consciousness -tumor -deposits -beverly -mistress -encounter -trustees -watts -duncan -reprints -bernard -resolutions -accessing -forty -tubes -attempted -midlands -priest -floyd -ronald -analysts -queue -trance -locale -nicholas -bundle -hammer -invasion -witnesses -runner -administered -notion -skins -mailed -fujitsu -spelling -arctic -exams -rewards -beneath -strengthen -defend -frederick -medicaid -infrared -seventh -welsh -belly -aggressive -advertisements -quarters -stolen -soonest -haiti -disturbed -determines -sculpture -naturals -motivation -lenders -pharmacology -fitting -fixtures -bloggers -agrees -passengers -quantities -petersburg -consistently -powerpoint -surplus -elder -sonic -obituaries -cheers -punishment -appreciation -subsequently -belarus -zoning -gravity -providence -thumb -restriction -incorporate -backgrounds -treasurer -guitars -essence -flooring -lightweight -ethiopia -mighty -athletes -humanity -transcription -holmes -complications -scholars -scripting -remembered -galaxy -chester -snapshot -caring -synthetic -segments -testament -dominant -twist -specifics -itunes -stomach -partially -buried -newbie -minimize -darwin -ranks -wilderness -debut -generations -tournaments -bradley -anatomy -sponsorship -headphones -fraction -proceeding -defects -volkswagen -uncertainty -breakdown -milton -marker -reconstruction -subsidiary -strengths -clarity -sandra -adelaide -encouraging -furnished -monaco -settled -folding -emirates -terrorists -airfare -comparisons -beneficial -distributions -vaccine -belize -viewpicture -promised -volvo -penny -robust -bookings -threatened -minolta -republicans -discusses -porter -jungle -responded -abstracts -ivory -alpine -prediction -pharmaceuticals -andale -fabulous -remix -alias -thesaurus -individually -battlefield -literally -newer -ecological -spice -implies -cooler -appraisal -consisting -maritime -periodic -submitting -overhead -ascii -prospect -shipment -breeding -citations -geographical -donor -mozambique -tension -trash -shapes -manor -envelope -diane -homeland -disclaimers -championships -excluded -andrea -breeds -rapids -disco -sheffield -bailey -endif -finishing -emotions -wellington -incoming -prospects -lexmark -cleaners -bulgarian -eternal -cashiers -aboriginal -remarkable -rotation -preventing -productive -boulevard -eugene -metric -compliant -minus -penalties -bennett -imagination -hotmail -refurbished -joshua -armenia -varied -grande -closest -activated -actress -conferencing -assign -armstrong -politicians -trackbacks -accommodate -tigers -aurora -slides -milan -premiere -lender -villages -shade -chorus -christine -rhythm -digit -argued -dietary -symphony -clarke -sudden -accepting -precipitation -marilyn -lions -findlaw -pools -lyric -claire -isolation -speeds -sustained -matched -approximate -carroll -rational -programmer -fighters -chambers -greetings -inherited -warming -incomplete -vocals -chronicle -fountain -chubby -grave -legitimate -biographies -burner -investigator -plaintiff -finnish -gentle -prisoners -deeper -muslims -mediterranean -nightlife -footage -howto -worthy -reveals -architects -saints -entrepreneur -carries -freelance -excessive -devon -screensaver -helena -saves -regarded -valuation -unexpected -cigarette -characteristic -marion -lobby -egyptian -tunisia -metallica -outlined -consequently -headline -treating -punch -appointments -gotta -cowboy -narrative -bahrain -enormous -karma -consist -betty -queens -academics -quantitative -lucas -screensavers -subdivision -tribes -defeat -clicks -distinction -honduras -naughty -hazards -insured -harper -livestock -mardi -exemption -tenant -sustainability -cabinets -tattoo -shake -algebra -shadows -holly -formatting -silly -nutritional -mercy -hartford -freely -marcus -sunrise -wrapping -nicaragua -weblogs -timeline -belongs -readily -affiliation -fence -nudist -infinite -diana -ensures -relatives -lindsay -legally -shame -satisfactory -revolutionary -bracelets -civilian -telephony -fatal -remedy -realtors -breathing -briefly -thickness -adjustments -graphical -genius -discussing -aerospace -fighter -meaningful -flesh -retreat -adapted -barely -wherever -estates -democrat -borough -maintains -failing -shortcuts -retained -voyeurweb -pamela -andrews -marble -extending -jesse -specifies -logitech -surrey -briefing -belkin -accreditation -blackberry -highland -meditation -modular -microphone -macedonia -combining -brandon -instrumental -giants -organizing -balloon -moderators -winston -solved -kazakhstan -hawaiian -standings -partition -invisible -gratuit -consoles -qatar -magnet -translations -porsche -cayman -jaguar -sheer -commodity -posing -kilometers -thanksgiving -hopkins -urgent -guarantees -infants -gothic -cylinder -witch -indication -congratulations -cohen -puppy -kathy -graphs -surround -cigarettes -revenge -expires -enemies -controllers -consultancy -finances -accepts -enjoying -conventions -patrol -smell -italiano -coordinates -carnival -roughly -sticker -promises -responding -physically -divide -stakeholders -hydrocodone -consecutive -cornell -satin -deserve -attempting -mailto -promo -representations -worried -tunes -garbage -competing -combines -bradford -phrases -peninsula -chelsea -boring -reynolds -accurately -speeches -reaches -schema -considers -catalogs -ministries -vacancies -quizzes -parliamentary -prefix -lucia -savannah -barrel -typing -nerve -planets -deficit -boulder -pointing -renew -coupled -myanmar -metadata -harold -circuits -floppy -texture -handbags -somerset -incurred -acknowledge -thoroughly -antigua -nottingham -thunder -caution -identifies -questionnaire -qualification -locks -modelling -namely -miniature -euros -interstate -pirates -aerial -consequence -rebel -systematic -perceived -origins -hired -makeup -textile -madagascar -nathan -tobago -presenting -troubleshooting -uzbekistan -indexes -centuries -magnitude -richardson -hindu -fragrances -vocabulary -licking -earthquake -fundraising -markers -weights -albania -geological -assessing -lasting -wicked -introduces -kills -roommate -webcams -pushed -webmasters -computational -acdbentity -participated -handhelds -answering -impressed -slope -reggae -failures -conspiracy -surname -theology -nails -evident -whats -rides -rehab -saturn -organizer -allergy -twisted -combinations -preceding -merit -enzyme -cumulative -zshops -planes -edmonton -tackle -disks -condo -pokemon -amplifier -ambien -arbitrary -prominent -retrieve -lexington -vernon -worldcat -titanium -fairy -builds -contacted -shaft -recorders -occasional -leslie -casio -deutsche -postings -innovations -kitty -postcards -drain -monte -fires -algeria -blessed -reviewing -cardiff -cornwall -favors -potato -panic -explicitly -sticks -leone -transsexual -citizenship -excuse -reforms -basement -onion -strand -sandwich -lawsuit -informative -girlfriend -bloomberg -cheque -hierarchy -influenced -banners -reject -abandoned -circles -italic -beats -merry -scuba -complement -passive -mauritius -valued -checklist -requesting -courage -verde -lauderdale -scenarios -gazette -hitachi -extraction -batman -elevation -hearings -coleman -utilization -beverages -calibration -efficiently -anaheim -textbook -dried -entertaining -prerequisite -luther -frontier -settle -stopping -refugees -knights -hypothesis -palmer -medicines -derby -peaceful -altered -pontiac -regression -doctrine -scenic -trainers -enhancements -renewable -intersection -passwords -sewing -consistency -collectors -conclude -recognised -munich -celebs -propose -azerbaijan -lighter -astrology -advisors -pavilion -tactics -trusts -occurring -supplemental -travelling -talented -annie -pillow -induction -derek -precisely -shorter -harley -spreading -provinces -relying -finals -paraguay -steal -parcel -refined -fifteen -widespread -incidence -fears -predict -boutique -acrylic -rolled -tuner -incidents -peterson -shannon -toddler -enhancing -flavor -alike -homeless -horrible -hungry -metallic -blocked -interference -warriors -palestine -listprice -cadillac -atmospheric -malawi -sagem -knowledgestorm -curtis -parental -referenced -strikes -lesser -publicity -marathon -proposition -pressing -gasoline -dressed -scout -belfast -dealt -niagara -warcraft -charms -catalyst -trader -bucks -allowance -denial -designation -thrown -prepaid -raises -duplicate -electro -criterion -badge -wrist -civilization -analyzed -vietnamese -heath -tremendous -ballot -lexus -varying -remedies -validity -trustee -weighted -angola -performs -plastics -realm -corrected -jenny -helmet -salaries -postcard -elephant -yemen -encountered -tsunami -scholar -nickel -internationally -surrounded -buses -expedia -geology -creatures -coating -commented -wallet -cleared -smilies -accomplish -boating -drainage -shakira -corners -broader -vegetarian -rouge -yeast -newfoundland -clearing -investigated -ambassador -coated -intend -stephanie -contacting -vegetation -findarticles -louise -kenny -specially -routines -hitting -yukon -beings -aquatic -reliance -habits -striking -infectious -podcasts -singh -gilbert -ferrari -continuity -brook -outputs -phenomenon -ensemble -insulin -assured -biblical -conscious -accent -mysimon -eleven -wives -ambient -utilize -mileage -prostate -adaptor -auburn -unlock -hyundai -pledge -vampire -angela -relates -nitrogen -xerox -merger -softball -referrals -differently -firewire -nextel -framing -organised -musician -blocking -rwanda -sorts -integrating -vsnet -limiting -dispatch -revisions -papua -restored -armor -riders -chargers -remark -dozens -varies -reasoning -rendered -picking -charitable -guards -annotated -convinced -openings -burlington -replacing -researcher -watershed -councils -occupations -acknowledged -kruger -pockets -granny -equilibrium -viral -inquire -pipes -characterized -laden -aruba -cottages -realtor -merge -privilege -edgar -develops -qualifying -chassis -dubai -estimation -pushing -fleece -pediatric -pierce -allan -dressing -techrepublic -sperm -filme -craps -frost -institutes -sally -yacht -tracy -prefers -drilling -brochures -breach -whale -traveller -appropriations -suspected -tomatoes -benchmark -beginners -instructors -highlighted -bedford -stationery -mustang -unauthorized -clusters -antibody -competent -momentum -wiring -pastor -calvin -shark -contributor -demonstrates -phases -grateful -emerald -gradually -laughing -grows -cliff -desirable -tract -ballet -journalist -abraham -bumper -afterwards -webpage -religions -garlic -hostels -shine -senegal -explosion -banned -wendy -briefs -signatures -diffs -mumbai -ozone -disciplines -daughters -conversations -radios -tariff -nvidia -opponent -pasta -simplified -muscles -serum -wrapped -swift -motherboard -runtime -inbox -focal -bibliographic -distant -champagne -decimal -deviation -superintendent -propecia -samba -hostel -housewives -employ -mongolia -penguin -magical -influences -inspections -irrigation -miracle -manually -reprint -hydraulic -centered -robertson -yearly -penetration -wound -belle -conviction -omissions -writings -hamburg -retrieval -qualities -cindy -fathers -charging -marvel -lined -prototype -importantly -petite -apparatus -terrain -explaining -strips -gossip -rangers -nomination -empirical -rotary -dependence -discrete -beginner -boxed -sexuality -polyester -cubic -commitments -suggesting -sapphire -kinase -skirts -remainder -crawford -labeled -privileges -televisions -specializing -marking -commodities -serbia -sheriff -griffin -declined -guyana -spies -neighbor -motorcycles -elect -highways -thinkpad -concentrate -intimate -reproductive -preston -deadly -bunny -chevy -molecules -rounds -longest -refrigerator -tions -intervals -sentences -dentists -exclusion -workstation -holocaust -flyer -dosage -receivers -customise -disposition -variance -navigator -investigators -cameroon -baking -marijuana -adaptive -computed -needle -baths -cathedral -brakes -nirvana -fairfield -invision -sticky -destiny -generous -madness -emacs -climb -blowing -fascinating -landscapes -heated -lafayette -jackie -computation -cardiovascular -sparc -cardiac -salvation -dover -adrian -predictions -accompanying -vatican -brutal -learners -selective -arbitration -configuring -token -editorials -sacrifice -seekers -removable -convergence -yields -gibraltar -suited -numeric -anthropology -skating -kinda -aberdeen -emperor -malpractice -dylan -belts -blacks -educated -rebates -reporters -burke -proudly -necessity -rendering -inserted -pulling -basename -obesity -curves -suburban -touring -clara -vertex -hepatitis -nationally -tomato -andorra -waterproof -expired -travels -flush -waiver -specialties -hayes -humanitarian -invitations -functioning -delight -survivor -garcia -cingular -economies -alexandria -bacterial -moses -counted -undertake -declare -continuously -johns -valves -impaired -achievements -donors -jewel -teddy -convertible -teaches -ventures -bufing -stranger -tragedy -julian -dryer -painful -velvet -tribunal -ruled -pensions -prayers -funky -secretariat -nowhere -paragraphs -joins -adolescent -nominations -wesley -lately -cancelled -scary -mattress -mpegs -brunei -likewise -banana -introductory -slovak -cakes -reservoir -occurrence -mixer -remind -worcester -sbjct -demographic -charming -tooth -disciplinary -annoying -respected -stays -disclose -affair -drove -washer -upset -restrict -springer -beside -mines -portraits -rebound -logan -mentor -interpreted -evaluations -fought -baghdad -elimination -metres -hypothetical -immigrants -complimentary -helicopter -pencil -freeze -performer -titled -commissions -sphere -powerseller -ratios -concord -graduated -endorsed -surprising -walnut -lance -ladder -italia -unnecessary -dramatically -liberia -sherman -maximize -hansen -senators -workout -yugoslavia -bleeding -characterization -colon -likelihood -lanes -purse -fundamentals -contamination -endangered -compromise -masturbation -optimize -stating -caroline -expiration -namespace -align -peripheral -bless -engaging -negotiation -crest -opponents -triumph -nominated -confidentiality -electoral -changelog -welding -deferred -alternatively -alloy -condos -plots -polished -gently -greensboro -tulsa -locking -casey -controversial -draws -fridge -blanket -bloom -simpsons -elliott -recovered -fraser -justify -upgrading -blades -loops -surge -frontpage -trauma -tahoe -advert -possess -demanding -defensive -flashers -subaru -forbidden -vanilla -programmers -monitored -installations -deutschland -picnic -souls -arrivals -spank -practitioner -motivated -smithsonian -hollow -vault -securely -examining -fioricet -groove -revelation -pursuit -delegation -wires -dictionaries -mails -backing -greenhouse -sleeps -blake -transparency -travis -endless -figured -orbit -currencies -niger -bacon -survivors -positioning -heater -colony -cannon -circus -promoted -forbes -moldova -descending -paxil -spine -trout -enclosed -temporarily -cooked -thriller -transmit -apnic -fatty -gerald -pressed -frequencies -scanned -reflections -hunger -mariah -municipality -joyce -detective -surgeon -cement -experiencing -fireplace -endorsement -planners -disputes -textiles -missile -intranet -closes -psychiatry -persistent -deborah -marco -assists -summaries -gabriel -auditor -aquarium -violin -prophet -bracket -looksmart -isaac -oxide -magnificent -colleague -naples -promptly -modems -adaptation -harmful -paintball -prozac -sexually -enclosure -dividend -newark -glucose -phantom -playback -supervisors -westminster -turtle -distances -absorption -treasures -warned -neural -fossil -hometown -badly -transcripts -apollo -disappointed -persian -continually -communist -collectible -handmade -greene -entrepreneurs -robots -grenada -creations -scoop -acquisitions -earning -mailman -sanyo -nested -biodiversity -excitement -somalia -movers -verbal -blink -presently -carlo -workflow -mysterious -novelty -bryant -tiles -voyuer -librarian -subsidiaries -switched -stockholm -tamil -garmin -fuzzy -indonesian -grams -therapist -richards -budgets -toolkit -promising -relaxation -render -carmen -thereafter -hardwood -erotica -temporal -forge -commissioners -dense -brave -forwarding -awful -nightmare -airplane -reductions -southampton -istanbul -impose -organisms -telescope -viewers -asbestos -portsmouth -meyer -enters -savage -advancement -harassment -willow -resumes -throwing -existed -generators -wagon -barbie -favour -knock -generates -potatoes -thorough -replication -inexpensive -receptors -peers -roland -optimum -interventions -quilt -huntington -creature -mounts -syracuse -internship -refresh -aluminium -snowboard -beastality -webcast -michel -evanescence -subtle -coordinated -notre -shipments -maldives -stripes -firmware -antarctica -shepherd -canberra -cradle -chancellor -mambo -flour -controversy -legendary -sympathy -choir -avoiding -beautifully -blond -expects -jumping -fabrics -antibodies -polymer -hygiene -poultry -virtue -burst -examinations -surgeons -bouquet -immunology -promotes -mandate -wiley -departmental -corpus -johnston -terminology -gentleman -fibre -reproduce -convicted -shades -indices -roommates -adware -threatening -spokesman -zoloft -activists -frankfurt -prisoner -daisy -halifax -encourages -ultram -cursor -assembled -earliest -donated -stuffed -restructuring -insects -terminals -crude -morrison -maiden -simulations -sufficiently -examines -viking -myrtle -bored -cleanup -conditional -crossword -bother -budapest -conceptual -knitting -attacked -bhutan -liechtenstein -mating -compute -redhead -arrives -translator -automobiles -tractor -allah -continent -unwrap -fares -longitude -resist -challenged -telecharger -hoped -safer -insertion -instrumentation -wagner -constraint -groundwater -touched -strengthening -cologne -wishing -ranger -smallest -insulation -newman -marsh -ricky -scared -theta -infringement -subjective -monsters -asylum -lightbox -robbie -stake -cocktail -outlets -swaziland -varieties -arbor -mediawiki -configurations -poison diff --git a/lib/python/devel-server.py b/lib/python/devel-server.py deleted file mode 100755 index 7f35e33..0000000 --- a/lib/python/devel-server.py +++ /dev/null @@ -1,287 +0,0 @@ -#!/usr/bin/python3 - -import asyncio -import cgitb -import html -import cgi -import http.server -import io -import json -import mimetypes -import moth -import logging -import os -import pathlib -import random -import shutil -import sys -import traceback -import mothballer -import parse -import urllib.parse -import posixpath -from http import HTTPStatus - - -sys.dont_write_bytecode = True # Don't write .pyc files - -try: - ThreadingHTTPServer = http.server.ThreadingHTTPServer -except AttributeError: - import socketserver - class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): - daemon_threads = True - -class MothServer(ThreadingHTTPServer): - def __init__(self, server_address, RequestHandlerClass): - super().__init__(server_address, RequestHandlerClass) - self.args = {} - - -class MothRequestHandler(http.server.SimpleHTTPRequestHandler): - endpoints = [] - - def __init__(self, request, client_address, server): - self.directory = str(server.args["theme_dir"]) - try: - super().__init__(request, client_address, server, directory=server.args["theme_dir"]) - except TypeError: - super().__init__(request, client_address, server) - - - # Backport from Python 3.7 - def translate_path(self, path): - # I guess we just hope that some other thread doesn't call getcwd - getcwd = os.getcwd - os.getcwd = lambda: self.directory - ret = super().translate_path(path) - os.getcwd = getcwd - return ret - - - def get_puzzle(self): - category = self.req.get("cat") - points = int(self.req.get("points")) - catpath = str(self.server.args["puzzles_dir"].joinpath(category)) - cat = moth.Category(catpath, self.seed) - puzzle = cat.puzzle(points) - return puzzle - - - def handle_answer(self): - for f in ("cat", "points", "answer"): - self.req[f] = self.fields.getfirst(f) - puzzle = self.get_puzzle() - ret = { - "status": "success", - "data": { - "short": "", - "description": "Provided answer was not in list of answers" - }, - } - - if self.req.get("answer") in puzzle.answers: - ret["data"]["description"] = "Answer is correct" - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(json.dumps(ret).encode("utf-8")) - endpoints.append(('/{seed}/answer', handle_answer)) - - - def handle_puzzlelist(self): - puzzles = { - "__devel__": [[0, ""]], - } - for p in self.server.args["puzzles_dir"].glob("*"): - if not p.is_dir() or p.match(".*"): - continue - catName = p.parts[-1] - cat = moth.Category(str(p), self.seed) - puzzles[catName] = [[i, str(i)] for i in cat.pointvals()] - puzzles[catName].append([0, ""]) - if len(puzzles) <= 1: - logging.warning("No directories found matching {}/*".format(self.server.args["puzzles_dir"])) - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(json.dumps(puzzles).encode("utf-8")) - endpoints.append(('/{seed}/puzzles.json', handle_puzzlelist)) - - - def handle_puzzle(self): - puzzle = self.get_puzzle() - - obj = puzzle.package() - obj["answers"] = puzzle.answers - obj["hint"] = puzzle.hint - obj["summary"] = puzzle.summary - obj["logs"] = puzzle.logs - - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(json.dumps(obj).encode("utf-8")) - endpoints.append(('/{seed}/content/{cat}/{points}/puzzle.json', handle_puzzle)) - - - def handle_puzzlefile(self): - puzzle = self.get_puzzle() - - try: - file = puzzle.files[self.req["filename"]] - except KeyError: - self.send_error( - HTTPStatus.NOT_FOUND, - "File Not Found", - ) - return - - self.send_response(200) - self.send_header("Content-Type", mimetypes.guess_type(file.name)) - self.end_headers() - shutil.copyfileobj(file.stream, self.wfile) - endpoints.append(("/{seed}/content/{cat}/{points}/{filename}", handle_puzzlefile)) - - - def handle_mothballer(self): - category = self.req.get("cat") - - try: - catdir = self.server.args["puzzles_dir"].joinpath(category) - mb = mothballer.package(category, catdir, self.seed) - except Exception as ex: - logging.exception(ex) - self.send_response(200) - self.send_header("Content-Type", "text/html; charset=\"utf-8\"") - self.end_headers() - self.wfile.write(cgitb.html(sys.exc_info())) - return - - self.send_response(200) - self.send_header("Content-Type", "application/octet_stream") - self.end_headers() - shutil.copyfileobj(mb, self.wfile) - endpoints.append(("/{seed}/mothballer/{cat}.mb", handle_mothballer)) - - - def handle_index(self): - seed = random.getrandbits(32) - body = """ - - - Dev Server - - - -

      Dev Server

      - -

      - Pick a seed: -

      -
        -
      • {seed}: a special seed I made just for you!
      • -
      • random: will use a different seed every time you load a page (could be useful for debugging)
      • -
      • You can also hack your own seed into the URL, if you want to.
      • -
      - -

      - Puzzles can be generated from Python code: these puzzles can use a random number generator if they want. - The seed is used to create these random numbers. -

      - -

      - We like to make a new seed for every contest, - and re-use that seed whenever we regenerate a category during an event - (say to fix a bug). - By using the same seed, - we make sure that all the dynamically-generated puzzles have the same values - in any new packages we build. -

      - - -""".format(seed=seed) - - self.send_response(200) - self.send_header("Content-Type", "text/html; charset=utf-8") - self.end_headers() - self.wfile.write(body.encode('utf-8')) - endpoints.append((r"/", handle_index)) - endpoints.append((r"/{ignored}", handle_index)) - - - def handle_theme_file(self): - self.path = "/" + self.req.get("path", "") - super().do_GET() - endpoints.append(("/{seed}/", handle_theme_file)) - endpoints.append(("/{seed}/{path}", handle_theme_file)) - - - def do_GET(self): - self.fields = cgi.FieldStorage( - fp=self.rfile, - headers=self.headers, - environ={ - "REQUEST_METHOD": self.command, - "CONTENT_TYPE": self.headers["Content-Type"], - }, - ) - - for pattern, function in self.endpoints: - result = parse.parse(pattern, self.path) - if result: - self.req = result.named - seed = self.req.get("seed", "random") - if seed == "random": - self.seed = random.getrandbits(32) - else: - self.seed = int(seed) - return function(self) - super().do_GET() - - def do_POST(self): - self.do_GET() - - def do_HEAD(self): - self.send_error( - HTTPStatus.NOT_IMPLEMENTED, - "Unsupported method (%r)" % self.command, - ) - - -if __name__ == '__main__': - import argparse - - parser = argparse.ArgumentParser(description="MOTH puzzle development server") - parser.add_argument( - '--puzzles', default='puzzles', - help="Directory containing your puzzles" - ) - parser.add_argument( - '--theme', default='theme', - help="Directory containing theme files") - parser.add_argument( - '--bind', default="127.0.0.1:8080", - help="Bind to ip:port" - ) - parser.add_argument( - '--base', default="", - help="Base URL to this server, for reverse proxy setup" - ) - args = parser.parse_args() - parts = args.bind.split(":") - addr = parts[0] or "0.0.0.0" - port = int(parts[1]) - - logging.basicConfig(level=logging.INFO) - - server = MothServer((addr, port), MothRequestHandler) - server.args["base_url"] = args.base - server.args["puzzles_dir"] = pathlib.Path(args.puzzles) - server.args["theme_dir"] = args.theme - - logging.info("Listening on %s:%d", addr, port) - server.serve_forever() diff --git a/lib/python/mistune.py b/lib/python/mistune.py deleted file mode 100644 index a81c4c1..0000000 --- a/lib/python/mistune.py +++ /dev/null @@ -1,1190 +0,0 @@ -# coding: utf-8 -"""mistune - ~~~~~~~ - - The fastest markdown parser in pure Python with renderer feature. - - Copyright (c) 2014 - 2015, Hsiaoming Yang - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the creator nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS - BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. -""" - -import re -import inspect - -__version__ = '0.7.3' -__author__ = 'Hsiaoming Yang ' -__all__ = [ - 'BlockGrammar', 'BlockLexer', - 'InlineGrammar', 'InlineLexer', - 'Renderer', 'Markdown', - 'markdown', 'escape', -] - - -_key_pattern = re.compile(r'\s+') -_nonalpha_pattern = re.compile(r'\W') -_escape_pattern = re.compile(r'&(?!#?\w+;)') -_newline_pattern = re.compile(r'\r\n|\r') -_block_quote_leading_pattern = re.compile(r'^ *> ?', flags=re.M) -_block_code_leading_pattern = re.compile(r'^ {4}', re.M) -_inline_tags = [ - 'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data', - 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u', 'mark', - 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr', 'ins', 'del', - 'img', 'font', -] -_pre_tags = ['pre', 'script', 'style'] -_valid_end = r'(?!:/|[^\w\s@]*@)\b' -_valid_attr = r'''\s*[a-zA-Z\-](?:\=(?:"[^"]*"|'[^']*'|\d+))*''' -_block_tag = r'(?!(?:%s)\b)\w+%s' % ('|'.join(_inline_tags), _valid_end) -_scheme_blacklist = ('javascript:', 'vbscript:') - - -def _pure_pattern(regex): - pattern = regex.pattern - if pattern.startswith('^'): - pattern = pattern[1:] - return pattern - - -def _keyify(key): - return _key_pattern.sub(' ', key.lower()) - - -def escape(text, quote=False, smart_amp=True): - """Replace special characters "&", "<" and ">" to HTML-safe sequences. - - The original cgi.escape will always escape "&", but you can control - this one for a smart escape amp. - - :param quote: if set to True, " and ' will be escaped. - :param smart_amp: if set to False, & will always be escaped. - """ - if smart_amp: - text = _escape_pattern.sub('&', text) - else: - text = text.replace('&', '&') - text = text.replace('<', '<') - text = text.replace('>', '>') - if quote: - text = text.replace('"', '"') - text = text.replace("'", ''') - return text - - -def escape_link(url): - """Remove dangerous URL schemes like javascript: and escape afterwards.""" - lower_url = url.lower().strip('\x00\x1a \n\r\t') - for scheme in _scheme_blacklist: - if lower_url.startswith(scheme): - return '' - return escape(url, quote=True, smart_amp=False) - - -def preprocessing(text, tab=4): - text = _newline_pattern.sub('\n', text) - text = text.expandtabs(tab) - text = text.replace('\u00a0', ' ') - text = text.replace('\u2424', '\n') - pattern = re.compile(r'^ +$', re.M) - return pattern.sub('', text) - - -class BlockGrammar(object): - """Grammars for block level tokens.""" - - def_links = re.compile( - r'^ *\[([^^\]]+)\]: *' # [key]: - r']+)>?' # or link - r'(?: +["(]([^\n]+)[")])? *(?:\n+|$)' - ) - def_footnotes = re.compile( - r'^\[\^([^\]]+)\]: *(' - r'[^\n]*(?:\n+|$)' # [^key]: - r'(?: {1,}[^\n]*(?:\n+|$))*' - r')' - ) - - newline = re.compile(r'^\n+') - block_code = re.compile(r'^( {4}[^\n]+\n*)+') - fences = re.compile( - r'^ *(`{3,}|~{3,}) *(\S+)? *\n' # ```lang - r'([\s\S]+?)\s*' - r'\1 *(?:\n+|$)' # ``` - ) - hrule = re.compile(r'^ {0,3}[-*_](?: *[-*_]){2,} *(?:\n+|$)') - heading = re.compile(r'^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)') - lheading = re.compile(r'^([^\n]+)\n *(=|-)+ *(?:\n+|$)') - block_quote = re.compile(r'^( *>[^\n]+(\n[^\n]+)*\n*)+') - list_block = re.compile( - r'^( *)([*+-]|\d+\.) [\s\S]+?' - r'(?:' - r'\n+(?=\1?(?:[-*_] *){3,}(?:\n+|$))' # hrule - r'|\n+(?=%s)' # def links - r'|\n+(?=%s)' # def footnotes - r'|\n{2,}' - r'(?! )' - r'(?!\1(?:[*+-]|\d+\.) )\n*' - r'|' - r'\s*$)' % ( - _pure_pattern(def_links), - _pure_pattern(def_footnotes), - ) - ) - list_item = re.compile( - r'^(( *)(?:[*+-]|\d+\.) [^\n]*' - r'(?:\n(?!\2(?:[*+-]|\d+\.) )[^\n]*)*)', - flags=re.M - ) - list_bullet = re.compile(r'^ *(?:[*+-]|\d+\.) +') - paragraph = re.compile( - r'^((?:[^\n]+\n?(?!' - r'%s|%s|%s|%s|%s|%s|%s|%s|%s' - r'))+)\n*' % ( - _pure_pattern(fences).replace(r'\1', r'\2'), - _pure_pattern(list_block).replace(r'\1', r'\3'), - _pure_pattern(hrule), - _pure_pattern(heading), - _pure_pattern(lheading), - _pure_pattern(block_quote), - _pure_pattern(def_links), - _pure_pattern(def_footnotes), - '<' + _block_tag, - ) - ) - block_html = re.compile( - r'^ *(?:%s|%s|%s) *(?:\n{2,}|\s*$)' % ( - r'', - r'<(%s)((?:%s)*?)>([\s\S]*?)<\/\1>' % (_block_tag, _valid_attr), - r'<%s(?:%s)*?\s*\/?>' % (_block_tag, _valid_attr), - ) - ) - table = re.compile( - r'^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*' - ) - nptable = re.compile( - r'^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*' - ) - text = re.compile(r'^[^\n]+') - - -class BlockLexer(object): - """Block level lexer for block grammars.""" - grammar_class = BlockGrammar - - default_rules = [ - 'newline', 'hrule', 'block_code', 'fences', 'heading', - 'nptable', 'lheading', 'block_quote', - 'list_block', 'block_html', 'def_links', - 'def_footnotes', 'table', 'paragraph', 'text' - ] - - list_rules = ( - 'newline', 'block_code', 'fences', 'lheading', 'hrule', - 'block_quote', 'list_block', 'block_html', 'text', - ) - - footnote_rules = ( - 'newline', 'block_code', 'fences', 'heading', - 'nptable', 'lheading', 'hrule', 'block_quote', - 'list_block', 'block_html', 'table', 'paragraph', 'text' - ) - - def __init__(self, rules=None, **kwargs): - self.tokens = [] - self.def_links = {} - self.def_footnotes = {} - - if not rules: - rules = self.grammar_class() - - self.rules = rules - - def __call__(self, text, rules=None): - return self.parse(text, rules) - - def parse(self, text, rules=None): - text = text.rstrip('\n') - - if not rules: - rules = self.default_rules - - def manipulate(text): - for key in rules: - rule = getattr(self.rules, key) - m = rule.match(text) - if not m: - continue - getattr(self, 'parse_%s' % key)(m) - return m - return False # pragma: no cover - - while text: - m = manipulate(text) - if m is not False: - text = text[len(m.group(0)):] - continue - if text: # pragma: no cover - raise RuntimeError('Infinite loop at: %s' % text) - return self.tokens - - def parse_newline(self, m): - length = len(m.group(0)) - if length > 1: - self.tokens.append({'type': 'newline'}) - - def parse_block_code(self, m): - # clean leading whitespace - code = _block_code_leading_pattern.sub('', m.group(0)) - self.tokens.append({ - 'type': 'code', - 'lang': None, - 'text': code, - }) - - def parse_fences(self, m): - self.tokens.append({ - 'type': 'code', - 'lang': m.group(2), - 'text': m.group(3), - }) - - def parse_heading(self, m): - self.tokens.append({ - 'type': 'heading', - 'level': len(m.group(1)), - 'text': m.group(2), - }) - - def parse_lheading(self, m): - """Parse setext heading.""" - self.tokens.append({ - 'type': 'heading', - 'level': 1 if m.group(2) == '=' else 2, - 'text': m.group(1), - }) - - def parse_hrule(self, m): - self.tokens.append({'type': 'hrule'}) - - def parse_list_block(self, m): - bull = m.group(2) - self.tokens.append({ - 'type': 'list_start', - 'ordered': '.' in bull, - }) - cap = m.group(0) - self._process_list_item(cap, bull) - self.tokens.append({'type': 'list_end'}) - - def _process_list_item(self, cap, bull): - cap = self.rules.list_item.findall(cap) - - _next = False - length = len(cap) - - for i in range(length): - item = cap[i][0] - - # remove the bullet - space = len(item) - item = self.rules.list_bullet.sub('', item) - - # outdent - if '\n ' in item: - space = space - len(item) - pattern = re.compile(r'^ {1,%d}' % space, flags=re.M) - item = pattern.sub('', item) - - # determine whether item is loose or not - loose = _next - if not loose and re.search(r'\n\n(?!\s*$)', item): - loose = True - - rest = len(item) - if i != length - 1 and rest: - _next = item[rest - 1] == '\n' - if not loose: - loose = _next - - if loose: - t = 'loose_item_start' - else: - t = 'list_item_start' - - self.tokens.append({'type': t}) - # recurse - self.parse(item, self.list_rules) - self.tokens.append({'type': 'list_item_end'}) - - def parse_block_quote(self, m): - self.tokens.append({'type': 'block_quote_start'}) - # clean leading > - cap = _block_quote_leading_pattern.sub('', m.group(0)) - self.parse(cap) - self.tokens.append({'type': 'block_quote_end'}) - - def parse_def_links(self, m): - key = _keyify(m.group(1)) - self.def_links[key] = { - 'link': m.group(2), - 'title': m.group(3), - } - - def parse_def_footnotes(self, m): - key = _keyify(m.group(1)) - if key in self.def_footnotes: - # footnote is already defined - return - - self.def_footnotes[key] = 0 - - self.tokens.append({ - 'type': 'footnote_start', - 'key': key, - }) - - text = m.group(2) - - if '\n' in text: - lines = text.split('\n') - whitespace = None - for line in lines[1:]: - space = len(line) - len(line.lstrip()) - if space and (not whitespace or space < whitespace): - whitespace = space - newlines = [lines[0]] - for line in lines[1:]: - newlines.append(line[whitespace:]) - text = '\n'.join(newlines) - - self.parse(text, self.footnote_rules) - - self.tokens.append({ - 'type': 'footnote_end', - 'key': key, - }) - - def parse_table(self, m): - item = self._process_table(m) - - cells = re.sub(r'(?: *\| *)?\n$', '', m.group(3)) - cells = cells.split('\n') - for i, v in enumerate(cells): - v = re.sub(r'^ *\| *| *\| *$', '', v) - cells[i] = re.split(r' *\| *', v) - - item['cells'] = cells - self.tokens.append(item) - - def parse_nptable(self, m): - item = self._process_table(m) - - cells = re.sub(r'\n$', '', m.group(3)) - cells = cells.split('\n') - for i, v in enumerate(cells): - cells[i] = re.split(r' *\| *', v) - - item['cells'] = cells - self.tokens.append(item) - - def _process_table(self, m): - header = re.sub(r'^ *| *\| *$', '', m.group(1)) - header = re.split(r' *\| *', header) - align = re.sub(r' *|\| *$', '', m.group(2)) - align = re.split(r' *\| *', align) - - for i, v in enumerate(align): - if re.search(r'^ *-+: *$', v): - align[i] = 'right' - elif re.search(r'^ *:-+: *$', v): - align[i] = 'center' - elif re.search(r'^ *:-+ *$', v): - align[i] = 'left' - else: - align[i] = None - - item = { - 'type': 'table', - 'header': header, - 'align': align, - } - return item - - def parse_block_html(self, m): - tag = m.group(1) - if not tag: - text = m.group(0) - self.tokens.append({ - 'type': 'close_html', - 'text': text - }) - else: - attr = m.group(2) - text = m.group(3) - self.tokens.append({ - 'type': 'open_html', - 'tag': tag, - 'extra': attr, - 'text': text - }) - - def parse_paragraph(self, m): - text = m.group(1).rstrip('\n') - self.tokens.append({'type': 'paragraph', 'text': text}) - - def parse_text(self, m): - text = m.group(0) - self.tokens.append({'type': 'text', 'text': text}) - - -class InlineGrammar(object): - """Grammars for inline level tokens.""" - - escape = re.compile(r'^\\([\\`*{}\[\]()#+\-.!_>~|])') # \* \+ \! .... - inline_html = re.compile( - r'^(?:%s|%s|%s)' % ( - r'', - r'<(\w+%s)((?:%s)*?)\s*>([\s\S]*?)<\/\1>' % (_valid_end, _valid_attr), - r'<\w+%s(?:%s)*?\s*\/?>' % (_valid_end, _valid_attr), - ) - ) - autolink = re.compile(r'^<([^ >]+(@|:)[^ >]+)>') - link = re.compile( - r'^!?\[(' - r'(?:\[[^^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*' - r')\]\(' - r'''\s*(<)?([\s\S]*?)(?(2)>)(?:\s+['"]([\s\S]*?)['"])?\s*''' - r'\)' - ) - reflink = re.compile( - r'^!?\[(' - r'(?:\[[^^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*' - r')\]\s*\[([^^\]]*)\]' - ) - nolink = re.compile(r'^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]') - url = re.compile(r'''^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])''') - double_emphasis = re.compile( - r'^_{2}([\s\S]+?)_{2}(?!_)' # __word__ - r'|' - r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word** - ) - emphasis = re.compile( - r'^\b_((?:__|[^_])+?)_\b' # _word_ - r'|' - r'^\*((?:\*\*|[^\*])+?)\*(?!\*)' # *word* - ) - code = re.compile(r'^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)') # `code` - linebreak = re.compile(r'^ {2,}\n(?!\s*$)') - strikethrough = re.compile(r'^~~(?=\S)([\s\S]*?\S)~~') # ~~word~~ - footnote = re.compile(r'^\[\^([^\]]+)\]') - text = re.compile(r'^[\s\S]+?(?=[\\%s' % (tag, extra, text, tag) - else: - html = m.group(0) - return self.renderer.inline_html(html) - - def output_footnote(self, m): - key = _keyify(m.group(1)) - if key not in self.footnotes: - return None - if self.footnotes[key]: - return None - self.footnote_index += 1 - self.footnotes[key] = self.footnote_index - return self.renderer.footnote_ref(key, self.footnote_index) - - def output_link(self, m): - return self._process_link(m, m.group(3), m.group(4)) - - def output_reflink(self, m): - key = _keyify(m.group(2) or m.group(1)) - if key not in self.links: - return None - ret = self.links[key] - return self._process_link(m, ret['link'], ret['title']) - - def output_nolink(self, m): - key = _keyify(m.group(1)) - if key not in self.links: - return None - ret = self.links[key] - return self._process_link(m, ret['link'], ret['title']) - - def _process_link(self, m, link, title=None): - line = m.group(0) - text = m.group(1) - if line[0] == '!': - return self.renderer.image(link, title, text) - - self._in_link = True - text = self.output(text) - self._in_link = False - return self.renderer.link(link, title, text) - - def output_double_emphasis(self, m): - text = m.group(2) or m.group(1) - text = self.output(text) - return self.renderer.double_emphasis(text) - - def output_emphasis(self, m): - text = m.group(2) or m.group(1) - text = self.output(text) - return self.renderer.emphasis(text) - - def output_code(self, m): - text = m.group(2) - return self.renderer.codespan(text) - - def output_linebreak(self, m): - return self.renderer.linebreak() - - def output_strikethrough(self, m): - text = self.output(m.group(1)) - return self.renderer.strikethrough(text) - - def output_text(self, m): - text = m.group(0) - return self.renderer.text(text) - - -class Renderer(object): - """The default HTML renderer for rendering Markdown. - """ - - def __init__(self, **kwargs): - self.options = kwargs - - def placeholder(self): - """Returns the default, empty output value for the renderer. - - All renderer methods use the '+=' operator to append to this value. - Default is a string so rendering HTML can build up a result string with - the rendered Markdown. - - Can be overridden by Renderer subclasses to be types like an empty - list, allowing the renderer to create a tree-like structure to - represent the document (which can then be reprocessed later into a - separate format like docx or pdf). - """ - return '' - - def block_code(self, code, lang=None): - """Rendering block level code. ``pre > code``. - - :param code: text content of the code block. - :param lang: language of the given code. - """ - code = code.rstrip('\n') - if not lang: - code = escape(code, smart_amp=False) - return '
      %s\n
      \n' % code - code = escape(code, quote=True, smart_amp=False) - return '
      %s\n
      \n' % (lang, code) - - def block_quote(self, text): - """Rendering
      with the given text. - - :param text: text content of the blockquote. - """ - return '
      %s\n
      \n' % text.rstrip('\n') - - def block_html(self, html): - """Rendering block level pure html content. - - :param html: text content of the html snippet. - """ - if self.options.get('skip_style') and \ - html.lower().startswith('`` ``

      ``. - - :param text: rendered text content for the header. - :param level: a number for the header level, for example: 1. - :param raw: raw text content of the header. - """ - return '%s\n' % (level, text, level) - - def hrule(self): - """Rendering method for ``
      `` tag.""" - if self.options.get('use_xhtml'): - return '
      \n' - return '
      \n' - - def list(self, body, ordered=True): - """Rendering list tags like ``
        `` and ``
          ``. - - :param body: body contents of the list. - :param ordered: whether this list is ordered or not. - """ - tag = 'ul' - if ordered: - tag = 'ol' - return '<%s>\n%s\n' % (tag, body, tag) - - def list_item(self, text): - """Rendering list item snippet. Like ``
        1. ``.""" - return '
        2. %s
        3. \n' % text - - def paragraph(self, text): - """Rendering paragraph tags. Like ``

          ``.""" - return '

          %s

          \n' % text.strip(' ') - - def table(self, header, body): - """Rendering table element. Wrap header and body in it. - - :param header: header part of the table. - :param body: body part of the table. - """ - return ( - '\n%s\n' - '\n%s\n
          \n' - ) % (header, body) - - def table_row(self, content): - """Rendering a table row. Like ````. - - :param content: content of current table row. - """ - return '\n%s\n' % content - - def table_cell(self, content, **flags): - """Rendering a table cell. Like ```` ````. - - :param content: content of current table cell. - :param header: whether this is header or not. - :param align: align of current table cell. - """ - if flags['header']: - tag = 'th' - else: - tag = 'td' - align = flags['align'] - if not align: - return '<%s>%s\n' % (tag, content, tag) - return '<%s style="text-align:%s">%s\n' % ( - tag, align, content, tag - ) - - def double_emphasis(self, text): - """Rendering **strong** text. - - :param text: text content for emphasis. - """ - return '%s' % text - - def emphasis(self, text): - """Rendering *emphasis* text. - - :param text: text content for emphasis. - """ - return '%s' % text - - def codespan(self, text): - """Rendering inline `code` text. - - :param text: text content for inline code. - """ - text = escape(text.rstrip(), smart_amp=False) - return '%s' % text - - def linebreak(self): - """Rendering line break like ``
          ``.""" - if self.options.get('use_xhtml'): - return '
          \n' - return '
          \n' - - def strikethrough(self, text): - """Rendering ~~strikethrough~~ text. - - :param text: text content for strikethrough. - """ - return '%s' % text - - def text(self, text): - """Rendering unformatted text. - - :param text: text content. - """ - return escape(text) - - def escape(self, text): - """Rendering escape sequence. - - :param text: text content. - """ - return escape(text) - - def autolink(self, link, is_email=False): - """Rendering a given link or email address. - - :param link: link content or email address. - :param is_email: whether this is an email or not. - """ - text = link = escape(link) - if is_email: - link = 'mailto:%s' % link - return '%s' % (link, text) - - def link(self, link, title, text): - """Rendering a given link with content and title. - - :param link: href link for ```` tag. - :param title: title content for `title` attribute. - :param text: text content for description. - """ - link = escape_link(link) - if not title: - return '%s' % (link, text) - title = escape(title, quote=True) - return '%s' % (link, title, text) - - def image(self, src, title, text): - """Rendering a image with title and text. - - :param src: source link of the image. - :param title: title text of the image. - :param text: alt text of the image. - """ - src = escape_link(src) - text = escape(text, quote=True) - if title: - title = escape(title, quote=True) - html = '%s' % html - return '%s>' % html - - def inline_html(self, html): - """Rendering span level pure html content. - - :param html: text content of the html snippet. - """ - if self.options.get('escape'): - return escape(html) - return html - - def newline(self): - """Rendering newline element.""" - return '' - - def footnote_ref(self, key, index): - """Rendering the ref anchor of a footnote. - - :param key: identity key for the footnote. - :param index: the index count of current footnote. - """ - html = ( - '' - '%d' - ) % (escape(key), escape(key), index) - return html - - def footnote_item(self, key, text): - """Rendering a footnote item. - - :param key: identity key for the footnote. - :param text: text content of the footnote. - """ - back = ( - '' - ) % escape(key) - text = text.rstrip() - if text.endswith('

          '): - text = re.sub(r'<\/p>$', r'%s

          ' % back, text) - else: - text = '%s

          %s

          ' % (text, back) - html = '
        4. %s
        5. \n' % (escape(key), text) - return html - - def footnotes(self, text): - """Wrapper for all footnotes. - - :param text: contents of all footnotes. - """ - html = '
          \n%s
            %s
          \n
          \n' - return html % (self.hrule(), text) - - -class Markdown(object): - """The Markdown parser. - - :param renderer: An instance of ``Renderer``. - :param inline: An inline lexer class or instance. - :param block: A block lexer class or instance. - """ - def __init__(self, renderer=None, inline=None, block=None, **kwargs): - if not renderer: - renderer = Renderer(**kwargs) - else: - kwargs.update(renderer.options) - - self.renderer = renderer - - if inline and inspect.isclass(inline): - inline = inline(renderer, **kwargs) - if block and inspect.isclass(block): - block = block(**kwargs) - - if inline: - self.inline = inline - else: - self.inline = InlineLexer(renderer, **kwargs) - - self.block = block or BlockLexer(BlockGrammar()) - self.footnotes = [] - self.tokens = [] - - # detect if it should parse text in block html - self._parse_block_html = kwargs.get('parse_block_html') - - def __call__(self, text): - return self.parse(text) - - def render(self, text): - """Render the Markdown text. - - :param text: markdown formatted text content. - """ - return self.parse(text) - - def parse(self, text): - out = self.output(preprocessing(text)) - - keys = self.block.def_footnotes - - # reset block - self.block.def_links = {} - self.block.def_footnotes = {} - - # reset inline - self.inline.links = {} - self.inline.footnotes = {} - - if not self.footnotes: - return out - - footnotes = filter(lambda o: keys.get(o['key']), self.footnotes) - self.footnotes = sorted( - footnotes, key=lambda o: keys.get(o['key']), reverse=True - ) - - body = self.renderer.placeholder() - while self.footnotes: - note = self.footnotes.pop() - body += self.renderer.footnote_item( - note['key'], note['text'] - ) - - out += self.renderer.footnotes(body) - return out - - def pop(self): - if not self.tokens: - return None - self.token = self.tokens.pop() - return self.token - - def peek(self): - if self.tokens: - return self.tokens[-1] - return None # pragma: no cover - - def output(self, text, rules=None): - self.tokens = self.block(text, rules) - self.tokens.reverse() - - self.inline.setup(self.block.def_links, self.block.def_footnotes) - - out = self.renderer.placeholder() - while self.pop(): - out += self.tok() - return out - - def tok(self): - t = self.token['type'] - - # sepcial cases - if t.endswith('_start'): - t = t[:-6] - - return getattr(self, 'output_%s' % t)() - - def tok_text(self): - text = self.token['text'] - while self.peek()['type'] == 'text': - text += '\n' + self.pop()['text'] - return self.inline(text) - - def output_newline(self): - return self.renderer.newline() - - def output_hrule(self): - return self.renderer.hrule() - - def output_heading(self): - return self.renderer.header( - self.inline(self.token['text']), - self.token['level'], - self.token['text'], - ) - - def output_code(self): - return self.renderer.block_code( - self.token['text'], self.token['lang'] - ) - - def output_table(self): - aligns = self.token['align'] - aligns_length = len(aligns) - cell = self.renderer.placeholder() - - # header part - header = self.renderer.placeholder() - for i, value in enumerate(self.token['header']): - align = aligns[i] if i < aligns_length else None - flags = {'header': True, 'align': align} - cell += self.renderer.table_cell(self.inline(value), **flags) - - header += self.renderer.table_row(cell) - - # body part - body = self.renderer.placeholder() - for i, row in enumerate(self.token['cells']): - cell = self.renderer.placeholder() - for j, value in enumerate(row): - align = aligns[j] if j < aligns_length else None - flags = {'header': False, 'align': align} - cell += self.renderer.table_cell(self.inline(value), **flags) - body += self.renderer.table_row(cell) - - return self.renderer.table(header, body) - - def output_block_quote(self): - body = self.renderer.placeholder() - while self.pop()['type'] != 'block_quote_end': - body += self.tok() - return self.renderer.block_quote(body) - - def output_list(self): - ordered = self.token['ordered'] - body = self.renderer.placeholder() - while self.pop()['type'] != 'list_end': - body += self.tok() - return self.renderer.list(body, ordered) - - def output_list_item(self): - body = self.renderer.placeholder() - while self.pop()['type'] != 'list_item_end': - if self.token['type'] == 'text': - body += self.tok_text() - else: - body += self.tok() - - return self.renderer.list_item(body) - - def output_loose_item(self): - body = self.renderer.placeholder() - while self.pop()['type'] != 'list_item_end': - body += self.tok() - return self.renderer.list_item(body) - - def output_footnote(self): - self.inline._in_footnote = True - body = self.renderer.placeholder() - key = self.token['key'] - while self.pop()['type'] != 'footnote_end': - body += self.tok() - self.footnotes.append({'key': key, 'text': body}) - self.inline._in_footnote = False - return self.renderer.placeholder() - - def output_close_html(self): - text = self.token['text'] - return self.renderer.block_html(text) - - def output_open_html(self): - text = self.token['text'] - tag = self.token['tag'] - if self._parse_block_html and tag not in _pre_tags: - text = self.inline(text, rules=self.inline.inline_html_rules) - extra = self.token.get('extra') or '' - html = '<%s%s>%s' % (tag, extra, text, tag) - return self.renderer.block_html(html) - - def output_paragraph(self): - return self.renderer.paragraph(self.inline(self.token['text'])) - - def output_text(self): - return self.renderer.paragraph(self.tok_text()) - - -def markdown(text, escape=True, **kwargs): - """Render markdown formatted text to html. - - :param text: markdown formatted text content. - :param escape: if set to False, all html tags will not be escaped. - :param use_xhtml: output with xhtml tags. - :param hard_wrap: if set to True, it will use the GFM line breaks feature. - :param parse_block_html: parse text only in block level html. - :param parse_inline_html: parse text only in inline level html. - """ - return Markdown(escape=escape, **kwargs)(text) diff --git a/lib/python/moth.py b/lib/python/moth.py deleted file mode 100644 index 92e2ecb..0000000 --- a/lib/python/moth.py +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/python3 - -import argparse -import contextlib -import glob -import hashlib -import html -import io -import importlib.machinery -import mistune -import os -import random -import string -import tempfile -import shlex -import yaml - -messageChars = b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' - -def djb2hash(str): - h = 5381 - for c in str.encode("utf-8"): - h = ((h * 33) + c) & 0xffffffff - return h - -@contextlib.contextmanager -def pushd(newdir): - curdir = os.getcwd() - os.chdir(newdir) - try: - yield - finally: - os.chdir(curdir) - - -def loadmod(name, path): - abspath = os.path.abspath(path) - loader = importlib.machinery.SourceFileLoader(name, abspath) - return loader.load_module() - - -# 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'))] - -class PuzzleFile: - """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.""" - - def __init__(self, stream, name, visible=True): - self.stream = stream - self.name = name - self.visible = visible - - -class Puzzle: - def __init__(self, category_seed, points): - """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 points: The point value of the puzzle. - """ - - super().__init__() - - self.points = points - self.summary = None - self.authors = [] - self.answers = [] - self.scripts = [] - self.pattern = None - self.hint = None - self.files = {} - self.body = io.StringIO() - self.logs = [] - self.randseed = category_seed * self.points - self.rand = random.Random(self.randseed) - - def log(self, *vals): - """Add a new log message to this puzzle.""" - msg = ' '.join(str(v) for v in vals) - self.logs.append(msg) - - def read_stream(self, stream): - header = True - line = "" - if stream.read(3) == "---": - header = "yaml" - else: - header = "moth" - - stream.seek(0) - - if header == "yaml": - self.read_yaml_header(stream) - elif header == "moth": - self.read_moth_header(stream) - - for line in stream: - self.body.write(line) - - def read_yaml_header(self, stream): - contents = "" - header = False - for line in stream: - if line.strip() == "---" and header: # Handle last line - break - elif line.strip() == "---": # Handle first line - header = True - continue - else: - contents += line - - config = yaml.safe_load(contents) - for key, value in config.items(): - key = key.lower() - self.handle_header_key(key, value) - - - def read_moth_header(self, stream): - for line in stream: - line = line.strip() - if not line: - break - - key, val = line.split(':', 1) - key = key.lower() - val = val.strip() - self.handle_header_key(key, val) - - def handle_header_key(self, key, val): - if key == 'author': - self.authors.append(val) - elif key == 'summary': - self.summary = val - elif key == 'answer': - if not isinstance(val, str): - raise ValueError("Answers must be strings, got %s, instead" % (type(val),)) - self.answers.append(val) - elif key == "answers": - for answer in val: - if not isinstance(answer, str): - raise ValueError("Answers must be strings, got %s, instead" % (type(answer),)) - self.answers.append(answer) - elif key == 'pattern': - self.pattern = val - elif key == 'hint': - self.hint = val - elif key == 'name': - pass - elif key == 'file': - parts = shlex.split(val) - name = parts[0] - hidden = False - stream = open(name, 'rb') - try: - name = parts[1] - hidden = (parts[2].lower() == "hidden") - except IndexError: - pass - self.files[name] = PuzzleFile(stream, name, not hidden) - elif key == 'script': - stream = open(val, 'rb') - # Make sure this shows up in the header block of the HTML output. - self.files[val] = PuzzleFile(stream, val, visible=False) - self.scripts.append(val) - else: - raise ValueError("Unrecognized header field: {}".format(key)) - - - def read_directory(self, path): - try: - puzzle_mod = loadmod("puzzle", os.path.join(path, "puzzle.py")) - except FileNotFoundError: - puzzle_mod = None - - with pushd(path): - if puzzle_mod: - puzzle_mod.make(self) - else: - with open('puzzle.moth') as f: - self.read_stream(f) - - def random_hash(self): - """Create a file basename (no extension) with our number generator.""" - return ''.join(self.rand.choice(string.ascii_lowercase) for i in range(8)) - - 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 - """ - - stream = tempfile.TemporaryFile() - self.add_stream(stream, name, visible) - return stream - - def add_stream(self, stream, name=None, visible=True): - if name is None: - name = self.random_hash() - self.files[name] = PuzzleFile(stream, name, visible) - - def add_file(self, filename, visible=True): - fd = open(filename, 'rb') - name = os.path.basename(filename) - self.add_stream(fd, name=name, visible=visible) - - def randword(self): - """Return a randomly-chosen word""" - - return self.rand.choice(ANSWER_WORDS) - - def make_answer(self, word_count=4, 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 - """ - - words = [self.randword() for i in range(word_count)] - answer = sep.join(words) - self.answers.append(answer) - return answer - - hexdump_stdch = stdch = ( - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - ' !"#$%&\'()*+,-./' - '0123456789:;<=>?' - '@ABCDEFGHIJKLMNO' - 'PQRSTUVWXYZ[\]^_' - '`abcdefghijklmno' - 'pqrstuvwxyz{|}~Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - 'Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·Ā·' - ) - - def hexdump(self, buf, charset=hexdump_stdch, gap=('ļæ½', 'āŒ·')): - hexes, chars = [], [] - out = [] - - for b in buf: - if len(chars) == 16: - out.append((hexes, chars)) - hexes, chars = [], [] - - if b is None: - h, c = gap - else: - h = '{:02x}'.format(b) - c = charset[b] - chars.append(c) - hexes.append(h) - - out.append((hexes, chars)) - - offset = 0 - elided = False - lastchars = None - self.body.write('
          ')
          -        for hexes, chars in out:
          -            if chars == lastchars:
          -                offset += len(chars)
          -                if not elided:
          -                    self.body.write('*\n')
          -                    elided = True
          -                continue
          -            lastchars = chars[:]
          -            elided = False
          -
          -            pad = 16 - len(chars)
          -            hexes += ['  '] * pad
          -
          -            self.body.write('{:08x}  '.format(offset))
          -            self.body.write(' '.join(hexes[:8]))
          -            self.body.write('  ')
          -            self.body.write(' '.join(hexes[8:]))
          -            self.body.write('  |')
          -            self.body.write(html.escape(''.join(chars)))
          -            self.body.write('|\n')
          -            offset += len(chars)
          -        self.body.write('{:08x}\n'.format(offset))
          -        self.body.write('
          ') - - def get_authors(self): - return self.authors or [self.author] - - def get_body(self): - return self.body.getvalue() - - def html_body(self): - """Format and return the markdown for the puzzle body.""" - return mistune.markdown(self.get_body(), escape=False) - - def package(self, answers=False): - """Return a dict packaging of the puzzle.""" - - files = [fn for fn,f in self.files.items() if f.visible] - return { - 'authors': self.authors, - 'hashes': self.hashes(), - 'files': files, - 'scripts': self.scripts, - 'pattern': self.pattern, - 'body': self.html_body(), - } - - def hashes(self): - "Return a list of answer hashes" - - return [djb2hash(a) for a in self.answers] - - -class Category: - def __init__(self, path, seed): - self.path = path - self.seed = seed - self.catmod = None - - try: - self.catmod = loadmod('category', os.path.join(path, 'category.py')) - except FileNotFoundError: - self.catmod = None - - def pointvals(self): - if self.catmod: - with pushd(self.path): - pointvals = self.catmod.pointvals() - else: - pointvals = [] - for fpath in glob.glob(os.path.join(self.path, "[0-9]*")): - pn = os.path.basename(fpath) - points = int(pn) - pointvals.append(points) - return sorted(pointvals) - - def puzzle(self, points): - puzzle = Puzzle(self.seed, points) - path = os.path.join(self.path, str(points)) - if self.catmod: - with pushd(self.path): - self.catmod.make(points, puzzle) - else: - puzzle.read_directory(path) - return puzzle - - def __iter__(self): - for points in self.pointvals(): - yield self.puzzle(points) diff --git a/lib/python/mothballer.py b/lib/python/mothballer.py deleted file mode 100755 index 19bd46d..0000000 --- a/lib/python/mothballer.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import binascii -import hashlib -import io -import json -import logging -import moth -import os -import shutil -import tempfile -import zipfile -import random - -SEEDFN = "SEED" - - -def write_kv_pairs(ziphandle, filename, kv): - """ Write out a sorted map to file - :param ziphandle: a zipfile object - :param filename: The filename to write within the zipfile object - :param kv: the map to write out - :return: - """ - filehandle = io.StringIO() - for key in sorted(kv.keys()): - if isinstance(kv[key], list): - for val in kv[key]: - filehandle.write("%s %s\n" % (key, val)) - else: - filehandle.write("%s %s\n" % (key, kv[key])) - filehandle.seek(0) - ziphandle.writestr(filename, filehandle.read()) - - -def escape(s): - return s.replace('&', '&').replace('<', '<').replace('>', '>') - - -def build_category(categorydir, outdir): - category_seed = random.getrandbits(32) - - categoryname = os.path.basename(categorydir.strip(os.sep)) - zipfilename = os.path.join(outdir, "%s.mb" % categoryname) - logging.info("Building {} from {}".format(zipfilename, categorydir)) - - if os.path.exists(zipfilename): - # open and gather some state - existing = zipfile.ZipFile(zipfilename, 'r') - try: - category_seed = int(existing.open(SEEDFN).read().strip()) - except Exception: - pass - existing.close() - logging.debug("Using PRNG seed {}".format(category_seed)) - - zipfileraw = tempfile.NamedTemporaryFile(delete=False) - mothball = package(categoryname, categorydir, category_seed) - shutil.copyfileobj(mothball, zipfileraw) - zipfileraw.close() - shutil.move(zipfileraw.name, zipfilename) - - -# Returns a file-like object containing the contents of the new zip file -def package(categoryname, categorydir, seed): - zfraw = io.BytesIO() - zf = zipfile.ZipFile(zfraw, 'x') - zf.writestr("category_seed.txt", str(seed)) - - cat = moth.Category(categorydir, seed) - mapping = {} - answers = {} - summary = {} - for puzzle in cat: - logging.info("Processing point value {}".format(puzzle.points)) - - hashmap = hashlib.sha1(str(seed).encode('utf-8')) - hashmap.update(str(puzzle.points).encode('utf-8')) - puzzlehash = hashmap.hexdigest() - - mapping[puzzle.points] = puzzlehash - answers[puzzle.points] = puzzle.answers - summary[puzzle.points] = puzzle.summary - - puzzledir = os.path.join("content", puzzlehash) - for fn, f in puzzle.files.items(): - payload = f.stream.read() - zf.writestr(os.path.join(puzzledir, fn), payload) - - obj = puzzle.package() - zf.writestr(os.path.join(puzzledir, 'puzzle.json'), json.dumps(obj)) - - write_kv_pairs(zf, 'map.txt', mapping) - write_kv_pairs(zf, 'answers.txt', answers) - write_kv_pairs(zf, 'summaries.txt', summary) - - # clean up - zf.close() - zfraw.seek(0) - return zfraw - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Build a category package') - parser.add_argument('outdir', help='Output directory') - parser.add_argument('categorydirs', nargs='+', help='Directory of category source') - args = parser.parse_args() - - logging.basicConfig(level=logging.DEBUG) - - outdir = os.path.abspath(args.outdir) - for categorydir in args.categorydirs: - categorydir = os.path.abspath(categorydir) - build_category(categorydir, outdir) diff --git a/lib/python/package-puzzles.py b/lib/python/package-puzzles.py deleted file mode 100755 index cbd7429..0000000 --- a/lib/python/package-puzzles.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import binascii -import glob -import hashlib -import io -import json -import logging -import moth -import os -import shutil -import string -import sys -import tempfile -import zipfile - -def write_kv_pairs(ziphandle, filename, kv): - """ Write out a sorted map to file - :param ziphandle: a zipfile object - :param filename: The filename to write within the zipfile object - :param kv: the map to write out - :return: - """ - filehandle = io.StringIO() - for key in sorted(kv.keys()): - if type(kv[key]) == type([]): - for val in kv[key]: - filehandle.write("%s %s\n" % (key, val)) - else: - filehandle.write("%s %s\n" % (key, kv[key])) - filehandle.seek(0) - ziphandle.writestr(filename, filehandle.read()) - -def escape(s): - return s.replace('&', '&').replace('<', '<').replace('>', '>') - -def generate_html(ziphandle, puzzle, puzzledir, category, points, authors, files): - html_content = io.StringIO() - file_content = io.StringIO() - if files: - file_content.write( -'''
          -

          Associated files:

          -
            -''') - for fn in files: - file_content.write('
          • {efn}
          • \n'.format(fn=fn, efn=escape(fn))) - file_content.write( -'''
          -
          -''') - scripts = [''.format(s) for s in puzzle.scripts] - - html_content.write( -''' - - - - - {category} {points} - - {scripts} - - -

          {category} for {points} points

          -
          -{body}
          -{file_content}
          -
          - - -
          Team hash:
          -
          Answer:
          - -
          -
          -
          Puzzle by {authors}
          - -'''.format( - category=category, - points=points, - body=puzzle.html_body(), - file_content=file_content.getvalue(), - authors=', '.join(authors), - scripts='\n'.join(scripts), - ) - ) - ziphandle.writestr(os.path.join(puzzledir, 'index.html'), html_content.getvalue()) - -def build_category(categorydir, outdir): - zipfileraw = tempfile.NamedTemporaryFile(delete=False) - zf = zipfile.ZipFile(zipfileraw, 'x') - - category_seed = binascii.b2a_hex(os.urandom(20)) - puzzles_dict = {} - secrets = {} - - categoryname = os.path.basename(categorydir.strip(os.sep)) - seedfn = os.path.join("category_seed.txt") - zipfilename = os.path.join(outdir, "%s.mb" % categoryname) - logging.info("Building {} from {}".format(zipfilename, categorydir)) - - if os.path.exists(zipfilename): - # open and gather some state - existing = zipfile.ZipFile(zipfilename, 'r') - try: - category_seed = existing.open(seedfn).read().strip() - except: - pass - existing.close() - logging.debug("Using PRNG seed {}".format(category_seed)) - - zf.writestr(seedfn, category_seed) - - cat = moth.Category(categorydir, category_seed) - mapping = {} - answers = {} - summary = {} - for puzzle in cat: - logging.info("Processing point value {}".format(puzzle.points)) - - hashmap = hashlib.sha1(category_seed) - hashmap.update(str(puzzle.points).encode('utf-8')) - puzzlehash = hashmap.hexdigest() - - mapping[puzzle.points] = puzzlehash - answers[puzzle.points] = puzzle.answers - summary[puzzle.points] = puzzle.summary - - puzzledir = os.path.join('content', puzzlehash) - files = [] - for fn, f in puzzle.files.items(): - if f.visible: - files.append(fn) - payload = f.stream.read() - zf.writestr(os.path.join(puzzledir, fn), payload) - - puzzledict = { - 'authors': puzzle.authors, - 'hashes': puzzle.hashes(), - 'files': files, - 'body': puzzle.html_body(), - } - puzzlejson = json.dumps(puzzledict) - zf.writestr(os.path.join(puzzledir, 'puzzle.json'), puzzlejson) - generate_html(zf, puzzle, puzzledir, categoryname, puzzle.points, puzzle.get_authors(), files) - - write_kv_pairs(zf, 'map.txt', mapping) - write_kv_pairs(zf, 'answers.txt', answers) - write_kv_pairs(zf, 'summaries.txt', summary) - - # clean up - zf.close() - - shutil.move(zipfileraw.name, zipfilename) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Build a category package') - parser.add_argument('outdir', help='Output directory') - parser.add_argument('categorydirs', nargs='+', help='Directory of category source') - args = parser.parse_args() - - logging.basicConfig(level=logging.DEBUG) - - for categorydir in args.categorydirs: - build_category(categorydir, args.outdir) - diff --git a/lib/python/parse.py b/lib/python/parse.py deleted file mode 100644 index 80ca637..0000000 --- a/lib/python/parse.py +++ /dev/null @@ -1,1335 +0,0 @@ -# -*- encoding: utf-8 -*- -r'''Parse strings using a specification based on the Python format() syntax. - - ``parse()`` is the opposite of ``format()`` - -The module is set up to only export ``parse()``, ``search()``, ``findall()``, -and ``with_pattern()`` when ``import \*`` is used: - ->>> from parse import * - -From there it's a simple thing to parse a string: - ->>> parse("It's {}, I love it!", "It's spam, I love it!") - ->>> _[0] -'spam' - -Or to search a string for some pattern: - ->>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n') - - -Or find all the occurrences of some pattern in a string: - ->>> ''.join(r.fixed[0] for r in findall(">{}<", "

          the bold text

          ")) -'the bold text' - -If you're going to use the same pattern to match lots of strings you can -compile it once: - ->>> from parse import compile ->>> p = compile("It's {}, I love it!") ->>> print(p) - ->>> p.parse("It's spam, I love it!") - - -("compile" is not exported for ``import *`` usage as it would override the -built-in ``compile()`` function) - -The default behaviour is to match strings case insensitively. You may match with -case by specifying `case_sensitive=True`: - ->>> parse('SPAM', 'spam', case_sensitive=True) is None -True - - -Format Syntax -------------- - -A basic version of the `Format String Syntax`_ is supported with anonymous -(fixed-position), named and formatted fields:: - - {[field name]:[format spec]} - -Field names must be a valid Python identifiers, including dotted names; -element indexes imply dictionaries (see below for example). - -Numbered fields are also not supported: the result of parsing will include -the parsed fields in the order they are parsed. - -The conversion of fields to types other than strings is done based on the -type in the format specification, which mirrors the ``format()`` behaviour. -There are no "!" field conversions like ``format()`` has. - -Some simple parse() format string examples: - ->>> parse("Bring me a {}", "Bring me a shrubbery") - ->>> r = parse("The {} who say {}", "The knights who say Ni!") ->>> print(r) - ->>> print(r.fixed) -('knights', 'Ni!') ->>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade") ->>> print(r) - ->>> print(r.named) -{'item': 'hand grenade'} ->>> print(r['item']) -hand grenade ->>> 'item' in r -True - -Note that `in` only works if you have named fields. Dotted names and indexes -are possible though the application must make additional sense of the result: - ->>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!") ->>> print(r) - ->>> print(r.named) -{'food.type': 'spam'} ->>> print(r['food.type']) -spam ->>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!") ->>> print(r) - ->>> print(r['quest']) -{'name': 'to seek the holy grail!'} ->>> print(r['quest']['name']) -to seek the holy grail! - -If the text you're matching has braces in it you can match those by including -a double-brace ``{{`` or ``}}`` in your format string, just like format() does. - - -Format Specification --------------------- - -Most often a straight format-less ``{}`` will suffice where a more complex -format specification might have been used. - -Most of `format()`'s `Format Specification Mini-Language`_ is supported: - - [[fill]align][0][width][.precision][type] - -The differences between `parse()` and `format()` are: - -- The align operators will cause spaces (or specified fill character) to be - stripped from the parsed value. The width is not enforced; it just indicates - there may be whitespace or "0"s to strip. -- Numeric parsing will automatically handle a "0b", "0o" or "0x" prefix. - That is, the "#" format character is handled automatically by d, b, o - and x formats. For "d" any will be accepted, but for the others the correct - prefix must be present if at all. -- Numeric sign is handled automatically. -- The thousands separator is handled automatically if the "n" type is used. -- The types supported are a slightly different mix to the format() types. Some - format() types come directly over: "d", "n", "%", "f", "e", "b", "o" and "x". - In addition some regular expression character group types "D", "w", "W", "s" - and "S" are also available. -- The "e" and "g" types are case-insensitive so there is not need for - the "E" or "G" types. - -===== =========================================== ======== -Type Characters Matched Output -===== =========================================== ======== -l Letters (ASCII) str -w Letters, numbers and underscore str -W Not letters, numbers and underscore str -s Whitespace str -S Non-whitespace str -d Digits (effectively integer numbers) int -D Non-digit str -n Numbers with thousands separators (, or .) int -% Percentage (converted to value/100.0) float -f Fixed-point numbers float -F Decimal numbers Decimal -e Floating-point numbers with exponent float - e.g. 1.1e-10, NAN (all case insensitive) -g General number format (either d, f or e) float -b Binary numbers int -o Octal numbers int -x Hexadecimal numbers (lower and upper case) int -ti ISO 8601 format date/time datetime - e.g. 1972-01-20T10:21:36Z ("T" and "Z" - optional) -te RFC2822 e-mail format date/time datetime - e.g. Mon, 20 Jan 1972 10:21:36 +1000 -tg Global (day/month) format date/time datetime - e.g. 20/1/1972 10:21:36 AM +1:00 -ta US (month/day) format date/time datetime - e.g. 1/20/1972 10:21:36 PM +10:30 -tc ctime() format date/time datetime - e.g. Sun Sep 16 01:03:52 1973 -th HTTP log format date/time datetime - e.g. 21/Nov/2011:00:07:11 +0000 -ts Linux system log format date/time datetime - e.g. Nov 9 03:37:44 -tt Time time - e.g. 10:21:36 PM -5:30 -===== =========================================== ======== - -Some examples of typed parsing with ``None`` returned if the typing -does not match: - ->>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...') - ->>> parse('Our {:d} {:w} are...', 'Our three weapons are...') ->>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM') - - -And messing about with alignment: - ->>> parse('with {:>} herring', 'with a herring') - ->>> parse('spam {:^} spam', 'spam lovely spam') - - -Note that the "center" alignment does not test to make sure the value is -centered - it just strips leading and trailing whitespace. - -Width and precision may be used to restrict the size of matched text -from the input. Width specifies a minimum size and precision specifies -a maximum. For example: - ->>> parse('{:.2}{:.2}', 'look') # specifying precision - ->>> parse('{:4}{:4}', 'look at that') # specifying width - ->>> parse('{:4}{:.4}', 'look at that') # specifying both - ->>> parse('{:2d}{:2d}', '0440') # parsing two contiguous numbers - - -Some notes for the date and time types: - -- the presence of the time part is optional (including ISO 8601, starting - at the "T"). A full datetime object will always be returned; the time - will be set to 00:00:00. You may also specify a time without seconds. -- when a seconds amount is present in the input fractions will be parsed - to give microseconds. -- except in ISO 8601 the day and month digits may be 0-padded. -- the date separator for the tg and ta formats may be "-" or "/". -- named months (abbreviations or full names) may be used in the ta and tg - formats in place of numeric months. -- as per RFC 2822 the e-mail format may omit the day (and comma), and the - seconds but nothing else. -- hours greater than 12 will be happily accepted. -- the AM/PM are optional, and if PM is found then 12 hours will be added - to the datetime object's hours amount - even if the hour is greater - than 12 (for consistency.) -- in ISO 8601 the "Z" (UTC) timezone part may be a numeric offset -- timezones are specified as "+HH:MM" or "-HH:MM". The hour may be one or two - digits (0-padded is OK.) Also, the ":" is optional. -- the timezone is optional in all except the e-mail format (it defaults to - UTC.) -- named timezones are not handled yet. - -Note: attempting to match too many datetime fields in a single parse() will -currently result in a resource allocation issue. A TooManyFields exception -will be raised in this instance. The current limit is about 15. It is hoped -that this limit will be removed one day. - -.. _`Format String Syntax`: - http://docs.python.org/library/string.html#format-string-syntax -.. _`Format Specification Mini-Language`: - http://docs.python.org/library/string.html#format-specification-mini-language - - -Result and Match Objects ------------------------- - -The result of a ``parse()`` and ``search()`` operation is either ``None`` (no match), a -``Result`` instance or a ``Match`` instance if ``evaluate_result`` is False. - -The ``Result`` instance has three attributes: - -fixed - A tuple of the fixed-position, anonymous fields extracted from the input. -named - A dictionary of the named fields extracted from the input. -spans - A dictionary mapping the names and fixed position indices matched to a - 2-tuple slice range of where the match occurred in the input. - The span does not include any stripped padding (alignment or width). - -The ``Match`` instance has one method: - -evaluate_result() - Generates and returns a ``Result`` instance for this ``Match`` object. - - - -Custom Type Conversions ------------------------ - -If you wish to have matched fields automatically converted to your own type you -may pass in a dictionary of type conversion information to ``parse()`` and -``compile()``. - -The converter will be passed the field string matched. Whatever it returns -will be substituted in the ``Result`` instance for that field. - -Your custom type conversions may override the builtin types if you supply one -with the same identifier. - ->>> def shouty(string): -... return string.upper() -... ->>> parse('{:shouty} world', 'hello world', dict(shouty=shouty)) - - -If the type converter has the optional ``pattern`` attribute, it is used as -regular expression for better pattern matching (instead of the default one). - ->>> def parse_number(text): -... return int(text) ->>> parse_number.pattern = r'\d+' ->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) - ->>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number)) ->>> assert _ is None, "MISMATCH" - -You can also use the ``with_pattern(pattern)`` decorator to add this -information to a type converter function: - ->>> from parse import with_pattern ->>> @with_pattern(r'\d+') -... def parse_number(text): -... return int(text) ->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) - - -A more complete example of a custom type might be: - ->>> yesno_mapping = { -... "yes": True, "no": False, -... "on": True, "off": False, -... "true": True, "false": False, -... } ->>> @with_pattern(r"|".join(yesno_mapping)) -... def parse_yesno(text): -... return yesno_mapping[text.lower()] - - -If the type converter ``pattern`` uses regex-grouping (with parenthesis), -you should indicate this by using the optional ``regex_group_count`` parameter -in the ``with_pattern()`` decorator: - ->>> @with_pattern(r'((\d+))', regex_group_count=2) -... def parse_number2(text): -... return int(text) ->>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2)) - - -Otherwise, this may cause parsing problems with unnamed/fixed parameters. - - -Potential Gotchas ------------------ - -`parse()` will always match the shortest text necessary (from left to right) -to fulfil the parse pattern, so for example: - ->>> pattern = '{dir1}/{dir2}' ->>> data = 'root/parent/subdir' ->>> sorted(parse(pattern, data).named.items()) -[('dir1', 'root'), ('dir2', 'parent/subdir')] - -So, even though `{'dir1': 'root/parent', 'dir2': 'subdir'}` would also fit -the pattern, the actual match represents the shortest successful match for -`dir1`. - ----- - -**Version history (in brief)**: - -- 1.11.0 Implement `__contains__` for Result instances. -- 1.10.0 Introduce a "letters" matcher, since "w" matches numbers - also. -- 1.9.1 Fix deprecation warnings around backslashes in regex strings - (thanks Mickaƫl Schoentgen). Also fix some documentation formatting - issues. -- 1.9.0 We now honor precision and width specifiers when parsing numbers - and strings, allowing parsing of concatenated elements of fixed width - (thanks Julia Signell) -- 1.8.4 Add LICENSE file at request of packagers. - Correct handling of AM/PM to follow most common interpretation. - Correct parsing of hexadecimal that looks like a binary prefix. - Add ability to parse case sensitively. - Add parsing of numbers to Decimal with "F" (thanks John Vandenberg) -- 1.8.3 Add regex_group_count to with_pattern() decorator to support - user-defined types that contain brackets/parenthesis (thanks Jens Engel) -- 1.8.2 add documentation for including braces in format string -- 1.8.1 ensure bare hexadecimal digits are not matched -- 1.8.0 support manual control over result evaluation (thanks Timo Furrer) -- 1.7.0 parse dict fields (thanks Mark Visser) and adapted to allow - more than 100 re groups in Python 3.5+ (thanks David King) -- 1.6.6 parse Linux system log dates (thanks Alex Cowan) -- 1.6.5 handle precision in float format (thanks Levi Kilcher) -- 1.6.4 handle pipe "|" characters in parse string (thanks Martijn Pieters) -- 1.6.3 handle repeated instances of named fields, fix bug in PM time - overflow -- 1.6.2 fix logging to use local, not root logger (thanks Necku) -- 1.6.1 be more flexible regarding matched ISO datetimes and timezones in - general, fix bug in timezones without ":" and improve docs -- 1.6.0 add support for optional ``pattern`` attribute in user-defined types - (thanks Jens Engel) -- 1.5.3 fix handling of question marks -- 1.5.2 fix type conversion error with dotted names (thanks Sebastian Thiel) -- 1.5.1 implement handling of named datetime fields -- 1.5 add handling of dotted field names (thanks Sebastian Thiel) -- 1.4.1 fix parsing of "0" in int conversion (thanks James Rowe) -- 1.4 add __getitem__ convenience access on Result. -- 1.3.3 fix Python 2.5 setup.py issue. -- 1.3.2 fix Python 3.2 setup.py issue. -- 1.3.1 fix a couple of Python 3.2 compatibility issues. -- 1.3 added search() and findall(); removed compile() from ``import *`` - export as it overwrites builtin. -- 1.2 added ability for custom and override type conversions to be - provided; some cleanup -- 1.1.9 to keep things simpler number sign is handled automatically; - significant robustification in the face of edge-case input. -- 1.1.8 allow "d" fields to have number base "0x" etc. prefixes; - fix up some field type interactions after stress-testing the parser; - implement "%" type. -- 1.1.7 Python 3 compatibility tweaks (2.5 to 2.7 and 3.2 are supported). -- 1.1.6 add "e" and "g" field types; removed redundant "h" and "X"; - removed need for explicit "#". -- 1.1.5 accept textual dates in more places; Result now holds match span - positions. -- 1.1.4 fixes to some int type conversion; implemented "=" alignment; added - date/time parsing with a variety of formats handled. -- 1.1.3 type conversion is automatic based on specified field types. Also added - "f" and "n" types. -- 1.1.2 refactored, added compile() and limited ``from parse import *`` -- 1.1.1 documentation improvements -- 1.1.0 implemented more of the `Format Specification Mini-Language`_ - and removed the restriction on mixing fixed-position and named fields -- 1.0.0 initial release - -This code is copyright 2012-2019 Richard Jones -See the end of the source file for the license of use. -''' - -from __future__ import absolute_import -__version__ = '1.11.0' - -# yes, I now have two problems -import re -import sys -from datetime import datetime, time, tzinfo, timedelta -from decimal import Decimal -from functools import partial -import logging - -__all__ = 'parse search findall with_pattern'.split() - -log = logging.getLogger(__name__) - - -def with_pattern(pattern, regex_group_count=None): - """Attach a regular expression pattern matcher to a custom type converter - function. - - This annotates the type converter with the :attr:`pattern` attribute. - - EXAMPLE: - >>> import parse - >>> @parse.with_pattern(r"\d+") - ... def parse_number(text): - ... return int(text) - - is equivalent to: - - >>> def parse_number(text): - ... return int(text) - >>> parse_number.pattern = r"\d+" - - :param pattern: regular expression pattern (as text) - :param regex_group_count: Indicates how many regex-groups are in pattern. - :return: wrapped function - """ - def decorator(func): - func.pattern = pattern - func.regex_group_count = regex_group_count - return func - return decorator - - -def int_convert(base): - '''Convert a string to an integer. - - The string may start with a sign. - - It may be of a base other than 10. - - If may start with a base indicator, 0#nnnn, which we assume should - override the specified base. - - It may also have other non-numeric characters that we can ignore. - ''' - CHARS = '0123456789abcdefghijklmnopqrstuvwxyz' - - def f(string, match, base=base): - if string[0] == '-': - sign = -1 - else: - sign = 1 - - if string[0] == '0' and len(string) > 2: - if string[1] in 'bB': - base = 2 - elif string[1] in 'oO': - base = 8 - elif string[1] in 'xX': - base = 16 - else: - # just go with the base specifed - pass - - chars = CHARS[:base] - string = re.sub('[^%s]' % chars, '', string.lower()) - return sign * int(string, base) - return f - - -def percentage(string, match): - return float(string[:-1]) / 100. - - -class FixedTzOffset(tzinfo): - """Fixed offset in minutes east from UTC. - """ - ZERO = timedelta(0) - - def __init__(self, offset, name): - self._offset = timedelta(minutes=offset) - self._name = name - - def __repr__(self): - return '<%s %s %s>' % (self.__class__.__name__, self._name, - self._offset) - - def utcoffset(self, dt): - return self._offset - - def tzname(self, dt): - return self._name - - def dst(self, dt): - return self.ZERO - - def __eq__(self, other): - return self._name == other._name and self._offset == other._offset - - -MONTHS_MAP = dict( - Jan=1, January=1, - Feb=2, February=2, - Mar=3, March=3, - Apr=4, April=4, - May=5, - Jun=6, June=6, - Jul=7, July=7, - Aug=8, August=8, - Sep=9, September=9, - Oct=10, October=10, - Nov=11, November=11, - Dec=12, December=12 -) -DAYS_PAT = r'(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' -MONTHS_PAT = r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)' -ALL_MONTHS_PAT = r'(%s)' % '|'.join(MONTHS_MAP) -TIME_PAT = r'(\d{1,2}:\d{1,2}(:\d{1,2}(\.\d+)?)?)' -AM_PAT = r'(\s+[AP]M)' -TZ_PAT = r'(\s+[-+]\d\d?:?\d\d)' - - -def date_convert(string, match, ymd=None, mdy=None, dmy=None, - d_m_y=None, hms=None, am=None, tz=None, mm=None, dd=None): - '''Convert the incoming string containing some date / time info into a - datetime instance. - ''' - groups = match.groups() - time_only = False - if mm and dd: - y=datetime.today().year - m=groups[mm] - d=groups[dd] - elif ymd is not None: - y, m, d = re.split(r'[-/\s]', groups[ymd]) - elif mdy is not None: - m, d, y = re.split(r'[-/\s]', groups[mdy]) - elif dmy is not None: - d, m, y = re.split(r'[-/\s]', groups[dmy]) - elif d_m_y is not None: - d, m, y = d_m_y - d = groups[d] - m = groups[m] - y = groups[y] - else: - time_only = True - - H = M = S = u = 0 - if hms is not None and groups[hms]: - t = groups[hms].split(':') - if len(t) == 2: - H, M = t - else: - H, M, S = t - if '.' in S: - S, u = S.split('.') - u = int(float('.' + u) * 1000000) - S = int(S) - H = int(H) - M = int(M) - - if am is not None: - am = groups[am] - if am: - am = am.strip() - if am == 'AM' and H == 12: - # correction for "12" hour functioning as "0" hour: 12:15 AM = 00:15 by 24 hr clock - H -= 12 - elif am == 'PM' and H == 12: - # no correction needed: 12PM is midday, 12:00 by 24 hour clock - pass - elif am == 'PM': - H += 12 - - if tz is not None: - tz = groups[tz] - if tz == 'Z': - tz = FixedTzOffset(0, 'UTC') - elif tz: - tz = tz.strip() - if tz.isupper(): - # TODO use the awesome python TZ module? - pass - else: - sign = tz[0] - if ':' in tz: - tzh, tzm = tz[1:].split(':') - elif len(tz) == 4: # 'snnn' - tzh, tzm = tz[1], tz[2:4] - else: - tzh, tzm = tz[1:3], tz[3:5] - offset = int(tzm) + int(tzh) * 60 - if sign == '-': - offset = -offset - tz = FixedTzOffset(offset, tz) - - if time_only: - d = time(H, M, S, u, tzinfo=tz) - else: - y = int(y) - if m.isdigit(): - m = int(m) - else: - m = MONTHS_MAP[m] - d = int(d) - d = datetime(y, m, d, H, M, S, u, tzinfo=tz) - - return d - - -class TooManyFields(ValueError): - pass - - -class RepeatedNameError(ValueError): - pass - - -# note: {} are handled separately -# note: I don't use r'' here because Sublime Text 2 syntax highlight has a fit -REGEX_SAFETY = re.compile(r'([?\\\\.[\]()*+\^$!\|])') - -# allowed field types -ALLOWED_TYPES = set(list('nbox%fFegwWdDsSl') + - ['t' + c for c in 'ieahgcts']) - - -def extract_format(format, extra_types): - '''Pull apart the format [[fill]align][0][width][.precision][type] - ''' - fill = align = None - if format[0] in '<>=^': - align = format[0] - format = format[1:] - elif len(format) > 1 and format[1] in '<>=^': - fill = format[0] - align = format[1] - format = format[2:] - - zero = False - if format and format[0] == '0': - zero = True - format = format[1:] - - width = '' - while format: - if not format[0].isdigit(): - break - width += format[0] - format = format[1:] - - if format.startswith('.'): - # Precision isn't needed but we need to capture it so that - # the ValueError isn't raised. - format = format[1:] # drop the '.' - precision = '' - while format: - if not format[0].isdigit(): - break - precision += format[0] - format = format[1:] - - # the rest is the type, if present - type = format - if type and type not in ALLOWED_TYPES and type not in extra_types: - raise ValueError('format spec %r not recognised' % type) - - return locals() - - -PARSE_RE = re.compile(r"""({{|}}|{\w*(?:(?:\.\w+)|(?:\[[^\]]+\]))*(?::[^}]+)?})""") - - -class Parser(object): - '''Encapsulate a format string that may be used to parse other strings. - ''' - def __init__(self, format, extra_types=None, case_sensitive=False): - # a mapping of a name as in {hello.world} to a regex-group compatible - # name, like hello__world Its used to prevent the transformation of - # name-to-group and group to name to fail subtly, such as in: - # hello_.world-> hello___world->hello._world - self._group_to_name_map = {} - # also store the original field name to group name mapping to allow - # multiple instances of a name in the format string - self._name_to_group_map = {} - # and to sanity check the repeated instances store away the first - # field type specification for the named field - self._name_types = {} - - self._format = format - if extra_types is None: - extra_types = {} - self._extra_types = extra_types - if case_sensitive: - self._re_flags = re.DOTALL - else: - self._re_flags = re.IGNORECASE | re.DOTALL - self._fixed_fields = [] - self._named_fields = [] - self._group_index = 0 - self._type_conversions = {} - self._expression = self._generate_expression() - self.__search_re = None - self.__match_re = None - - log.debug('format %r -> %r', format, self._expression) - - def __repr__(self): - if len(self._format) > 20: - return '<%s %r>' % (self.__class__.__name__, - self._format[:17] + '...') - return '<%s %r>' % (self.__class__.__name__, self._format) - - @property - def _search_re(self): - if self.__search_re is None: - try: - self.__search_re = re.compile(self._expression, self._re_flags) - except AssertionError: - # access error through sys to keep py3k and backward compat - e = str(sys.exc_info()[1]) - if e.endswith('this version only supports 100 named groups'): - raise TooManyFields('sorry, you are attempting to parse ' - 'too many complex fields') - return self.__search_re - - @property - def _match_re(self): - if self.__match_re is None: - expression = r'^%s$' % self._expression - try: - self.__match_re = re.compile(expression, self._re_flags) - except AssertionError: - # access error through sys to keep py3k and backward compat - e = str(sys.exc_info()[1]) - if e.endswith('this version only supports 100 named groups'): - raise TooManyFields('sorry, you are attempting to parse ' - 'too many complex fields') - except re.error: - raise NotImplementedError("Group names (e.g. (?P) can " - "cause failure, as they are not escaped properly: '%s'" % - expression) - return self.__match_re - - def parse(self, string, evaluate_result=True): - '''Match my format to the string exactly. - - Return a Result or Match instance or None if there's no match. - ''' - m = self._match_re.match(string) - if m is None: - return None - - if evaluate_result: - return self.evaluate_result(m) - else: - return Match(self, m) - - def search(self, string, pos=0, endpos=None, evaluate_result=True): - '''Search the string for my format. - - Optionally start the search at "pos" character index and limit the - search to a maximum index of endpos - equivalent to - search(string[:endpos]). - - If the ``evaluate_result`` argument is set to ``False`` a - Match instance is returned instead of the actual Result instance. - - Return either a Result instance or None if there's no match. - ''' - if endpos is None: - endpos = len(string) - m = self._search_re.search(string, pos, endpos) - if m is None: - return None - - if evaluate_result: - return self.evaluate_result(m) - else: - return Match(self, m) - - def findall(self, string, pos=0, endpos=None, extra_types=None, evaluate_result=True): - '''Search "string" for all occurrences of "format". - - Optionally start the search at "pos" character index and limit the - search to a maximum index of endpos - equivalent to - search(string[:endpos]). - - Returns an iterator that holds Result or Match instances for each format match - found. - ''' - if endpos is None: - endpos = len(string) - return ResultIterator(self, string, pos, endpos, evaluate_result=evaluate_result) - - def _expand_named_fields(self, named_fields): - result = {} - for field, value in named_fields.items(): - # split 'aaa[bbb][ccc]...' into 'aaa' and '[bbb][ccc]...' - basename, subkeys = re.match(r'([^\[]+)(.*)', field).groups() - - # create nested dictionaries {'aaa': {'bbb': {'ccc': ...}}} - d = result - k = basename - - if subkeys: - for subkey in re.findall(r'\[[^\]]+\]', subkeys): - d = d.setdefault(k,{}) - k = subkey[1:-1] - - # assign the value to the last key - d[k] = value - - return result - - def evaluate_result(self, m): - '''Generate a Result instance for the given regex match object''' - # ok, figure the fixed fields we've pulled out and type convert them - fixed_fields = list(m.groups()) - for n in self._fixed_fields: - if n in self._type_conversions: - fixed_fields[n] = self._type_conversions[n](fixed_fields[n], m) - fixed_fields = tuple(fixed_fields[n] for n in self._fixed_fields) - - # grab the named fields, converting where requested - groupdict = m.groupdict() - named_fields = {} - name_map = {} - for k in self._named_fields: - korig = self._group_to_name_map[k] - name_map[korig] = k - if k in self._type_conversions: - value = self._type_conversions[k](groupdict[k], m) - else: - value = groupdict[k] - - named_fields[korig] = value - - # now figure the match spans - spans = dict((n, m.span(name_map[n])) for n in named_fields) - spans.update((i, m.span(n + 1)) - for i, n in enumerate(self._fixed_fields)) - - # and that's our result - return Result(fixed_fields, self._expand_named_fields(named_fields), spans) - - def _regex_replace(self, match): - return '\\' + match.group(1) - - def _generate_expression(self): - # turn my _format attribute into the _expression attribute - e = [] - for part in PARSE_RE.split(self._format): - if not part: - continue - elif part == '{{': - e.append(r'\{') - elif part == '}}': - e.append(r'\}') - elif part[0] == '{': - # this will be a braces-delimited field to handle - e.append(self._handle_field(part)) - else: - # just some text to match - e.append(REGEX_SAFETY.sub(self._regex_replace, part)) - return ''.join(e) - - def _to_group_name(self, field): - # return a version of field which can be used as capture group, even - # though it might contain '.' - group = field.replace('.', '_').replace('[', '_').replace(']', '_') - - # make sure we don't collide ("a.b" colliding with "a_b") - n = 1 - while group in self._group_to_name_map: - n += 1 - if '.' in field: - group = field.replace('.', '_' * n) - elif '_' in field: - group = field.replace('_', '_' * n) - else: - raise KeyError('duplicated group name %r' % (field,)) - - # save off the mapping - self._group_to_name_map[group] = field - self._name_to_group_map[field] = group - return group - - def _handle_field(self, field): - # first: lose the braces - field = field[1:-1] - - # now figure whether this is an anonymous or named field, and whether - # there's any format specification - format = '' - if field and field[0].isalpha(): - if ':' in field: - name, format = field.split(':') - else: - name = field - if name in self._name_to_group_map: - if self._name_types[name] != format: - raise RepeatedNameError('field type %r for field "%s" ' - 'does not match previous seen type %r' % (format, - name, self._name_types[name])) - group = self._name_to_group_map[name] - # match previously-seen value - return r'(?P=%s)' % group - else: - group = self._to_group_name(name) - self._name_types[name] = format - self._named_fields.append(group) - # this will become a group, which must not contain dots - wrap = r'(?P<%s>%%s)' % group - else: - self._fixed_fields.append(self._group_index) - wrap = r'(%s)' - if ':' in field: - format = field[1:] - group = self._group_index - - # simplest case: no type specifier ({} or {name}) - if not format: - self._group_index += 1 - return wrap % r'.+?' - - # decode the format specification - format = extract_format(format, self._extra_types) - - # figure type conversions, if any - type = format['type'] - is_numeric = type and type in 'n%fegdobh' - if type in self._extra_types: - type_converter = self._extra_types[type] - s = getattr(type_converter, 'pattern', r'.+?') - regex_group_count = getattr(type_converter, 'regex_group_count', 0) - if regex_group_count is None: - regex_group_count = 0 - self._group_index += regex_group_count - - def f(string, m): - return type_converter(string) - self._type_conversions[group] = f - elif type == 'n': - s = r'\d{1,3}([,.]\d{3})*' - self._group_index += 1 - self._type_conversions[group] = int_convert(10) - elif type == 'b': - s = r'(0[bB])?[01]+' - self._type_conversions[group] = int_convert(2) - self._group_index += 1 - elif type == 'o': - s = r'(0[oO])?[0-7]+' - self._type_conversions[group] = int_convert(8) - self._group_index += 1 - elif type == 'x': - s = r'(0[xX])?[0-9a-fA-F]+' - self._type_conversions[group] = int_convert(16) - self._group_index += 1 - elif type == '%': - s = r'\d+(\.\d+)?%' - self._group_index += 1 - self._type_conversions[group] = percentage - elif type == 'f': - s = r'\d+\.\d+' - self._type_conversions[group] = lambda s, m: float(s) - elif type == 'F': - s = r'\d+\.\d+' - self._type_conversions[group] = lambda s, m: Decimal(s) - elif type == 'e': - s = r'\d+\.\d+[eE][-+]?\d+|nan|NAN|[-+]?inf|[-+]?INF' - self._type_conversions[group] = lambda s, m: float(s) - elif type == 'g': - s = r'\d+(\.\d+)?([eE][-+]?\d+)?|nan|NAN|[-+]?inf|[-+]?INF' - self._group_index += 2 - self._type_conversions[group] = lambda s, m: float(s) - elif type == 'd': - if format.get('width'): - width = r'{1,%s}' % int(format['width']) - else: - width = '+' - s = r'\d{w}|0[xX][0-9a-fA-F]{w}|0[bB][01]{w}|0[oO][0-7]{w}'.format(w=width) - self._type_conversions[group] = int_convert(10) - elif type == 'ti': - s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % \ - TIME_PAT - n = self._group_index - self._type_conversions[group] = partial(date_convert, ymd=n + 1, - hms=n + 4, tz=n + 7) - self._group_index += 7 - elif type == 'tg': - s = r'(\d{1,2}[-/](\d{1,2}|%s)[-/]\d{4})(\s+%s)?%s?%s?' % ( - ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, dmy=n + 1, - hms=n + 5, am=n + 8, tz=n + 9) - self._group_index += 9 - elif type == 'ta': - s = r'((\d{1,2}|%s)[-/]\d{1,2}[-/]\d{4})(\s+%s)?%s?%s?' % ( - ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, mdy=n + 1, - hms=n + 5, am=n + 8, tz=n + 9) - self._group_index += 9 - elif type == 'te': - # this will allow microseconds through if they're present, but meh - s = r'(%s,\s+)?(\d{1,2}\s+%s\s+\d{4})\s+%s%s' % (DAYS_PAT, - MONTHS_PAT, TIME_PAT, TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, dmy=n + 3, - hms=n + 5, tz=n + 8) - self._group_index += 8 - elif type == 'th': - # slight flexibility here from the stock Apache format - s = r'(\d{1,2}[-/]%s[-/]\d{4}):%s%s' % (MONTHS_PAT, TIME_PAT, - TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, dmy=n + 1, - hms=n + 3, tz=n + 6) - self._group_index += 6 - elif type == 'tc': - s = r'(%s)\s+%s\s+(\d{1,2})\s+%s\s+(\d{4})' % ( - DAYS_PAT, MONTHS_PAT, TIME_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, - d_m_y=(n + 4, n + 3, n + 8), hms=n + 5) - self._group_index += 8 - elif type == 'tt': - s = r'%s?%s?%s?' % (TIME_PAT, AM_PAT, TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial(date_convert, hms=n + 1, - am=n + 4, tz=n + 5) - self._group_index += 5 - elif type == 'ts': - s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % MONTHS_PAT - n = self._group_index - self._type_conversions[group] = partial(date_convert, mm=n+1, dd=n+3, - hms=n + 5) - self._group_index += 5 - elif type == 'l': - s = r'[A-Za-z]+' - elif type: - s = r'\%s+' % type - elif format.get('precision'): - if format.get('width'): - s = r'.{%s,%s}?' % (format['width'], format['precision']) - else: - s = r'.{1,%s}?' % format['precision'] - elif format.get('width'): - s = r'.{%s,}?' % format['width'] - else: - s = r'.+?' - - align = format['align'] - fill = format['fill'] - - # handle some numeric-specific things like fill and sign - if is_numeric: - # prefix with something (align "=" trumps zero) - if align == '=': - # special case - align "=" acts like the zero above but with - # configurable fill defaulting to "0" - if not fill: - fill = '0' - s = r'%s*' % fill + s - - # allow numbers to be prefixed with a sign - s = r'[-+ ]?' + s - - if not fill: - fill = ' ' - - # Place into a group now - this captures the value we want to keep. - # Everything else from now is just padding to be stripped off - if wrap: - s = wrap % s - self._group_index += 1 - - if format['width']: - # all we really care about is that if the format originally - # specified a width then there will probably be padding - without - # an explicit alignment that'll mean right alignment with spaces - # padding - if not align: - align = '>' - - if fill in r'.\+?*[](){}^$': - fill = '\\' + fill - - # align "=" has been handled - if align == '<': - s = '%s%s*' % (s, fill) - elif align == '>': - s = '%s*%s' % (fill, s) - elif align == '^': - s = '%s*%s%s*' % (fill, s, fill) - - return s - - -class Result(object): - '''The result of a parse() or search(). - - Fixed results may be looked up using `result[index]`. - - Named results may be looked up using `result['name']`. - - Named results may be tested for existence using `'name' in result`. - ''' - def __init__(self, fixed, named, spans): - self.fixed = fixed - self.named = named - self.spans = spans - - def __getitem__(self, item): - if isinstance(item, int): - return self.fixed[item] - return self.named[item] - - def __repr__(self): - return '<%s %r %r>' % (self.__class__.__name__, self.fixed, - self.named) - - def __contains__(self, name): - return name in self.named - - -class Match(object): - '''The result of a parse() or search() if no results are generated. - - This class is only used to expose internal used regex match objects - to the user and use them for external Parser.evaluate_result calls. - ''' - def __init__(self, parser, match): - self.parser = parser - self.match = match - - def evaluate_result(self): - '''Generate results for this Match''' - return self.parser.evaluate_result(self.match) - - -class ResultIterator(object): - '''The result of a findall() operation. - - Each element is a Result instance. - ''' - def __init__(self, parser, string, pos, endpos, evaluate_result=True): - self.parser = parser - self.string = string - self.pos = pos - self.endpos = endpos - self.evaluate_result = evaluate_result - - def __iter__(self): - return self - - def __next__(self): - m = self.parser._search_re.search(self.string, self.pos, self.endpos) - if m is None: - raise StopIteration() - self.pos = m.end() - - if self.evaluate_result: - return self.parser.evaluate_result(m) - else: - return Match(self.parser, m) - - # pre-py3k compat - next = __next__ - - -def parse(format, string, extra_types=None, evaluate_result=True, case_sensitive=False): - '''Using "format" attempt to pull values from "string". - - The format must match the string contents exactly. If the value - you're looking for is instead just a part of the string use - search(). - - If ``evaluate_result`` is True the return value will be an Result instance with two attributes: - - .fixed - tuple of fixed-position values from the string - .named - dict of named values from the string - - If ``evaluate_result`` is False the return value will be a Match instance with one method: - - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - If the format is invalid a ValueError will be raised. - - See the module documentation for the use of "extra_types". - - In the case there is no match parse() will return None. - ''' - p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) - return p.parse(string, evaluate_result=evaluate_result) - - -def search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, - case_sensitive=False): - '''Search "string" for the first occurrence of "format". - - The format may occur anywhere within the string. If - instead you wish for the format to exactly match the string - use parse(). - - Optionally start the search at "pos" character index and limit the search - to a maximum index of endpos - equivalent to search(string[:endpos]). - - If ``evaluate_result`` is True the return value will be an Result instance with two attributes: - - .fixed - tuple of fixed-position values from the string - .named - dict of named values from the string - - If ``evaluate_result`` is False the return value will be a Match instance with one method: - - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - If the format is invalid a ValueError will be raised. - - See the module documentation for the use of "extra_types". - - In the case there is no match parse() will return None. - ''' - p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) - return p.search(string, pos, endpos, evaluate_result=evaluate_result) - - -def findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, - case_sensitive=False): - '''Search "string" for all occurrences of "format". - - You will be returned an iterator that holds Result instances - for each format match found. - - Optionally start the search at "pos" character index and limit the search - to a maximum index of endpos - equivalent to search(string[:endpos]). - - If ``evaluate_result`` is True each returned Result instance has two attributes: - - .fixed - tuple of fixed-position values from the string - .named - dict of named values from the string - - If ``evaluate_result`` is False each returned value is a Match instance with one method: - - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - If the format is invalid a ValueError will be raised. - - See the module documentation for the use of "extra_types". - ''' - p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) - return Parser(format, extra_types=extra_types).findall(string, pos, endpos, evaluate_result=evaluate_result) - - -def compile(format, extra_types=None, case_sensitive=False): - '''Create a Parser instance to parse "format". - - The resultant Parser has a method .parse(string) which - behaves in the same manner as parse(format, string). - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - Use this function if you intend to parse many strings - with the same format. - - See the module documentation for the use of "extra_types". - - Returns a Parser instance. - ''' - return Parser(format, extra_types=extra_types) - - -# Copyright (c) 2012-2019 Richard Jones -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# vim: set filetype=python ts=4 sw=4 et si tw=75 diff --git a/lib/python/setup.cfg b/lib/python/setup.cfg deleted file mode 100644 index 3bf77b8..0000000 --- a/lib/python/setup.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[flake8] -# flake8 is an automated code formatting pedant. -# Use it, please. -# -# python3 -m flake8 . -# -ignore = E501 -exclude = .git \ No newline at end of file diff --git a/lib/python/update-words.sh b/lib/python/update-words.sh deleted file mode 100755 index f64d1cb..0000000 --- a/lib/python/update-words.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -set +e - -url='https://rawgit.com/first20hours/google-10000-english/master/google-10000-english-no-swears.txt' -getter="curl -sL" -fn="answer_words.txt" - -filterer() { - grep '......*' -} - -if ! curl -h >/dev/null 2>/dev/null; then - getter="wget -q -O -" -elif ! wget -h >/dev/null 2>/dev/null; then - echo "[!] I don't know how to download. I need curl or wget." -fi - -$getter "${url}" | filterer > ${fn}.tmp \ - && mv -f ${fn}.tmp ${fn} diff --git a/lib/python/validate.py b/lib/python/validate.py deleted file mode 100644 index 150f0d3..0000000 --- a/lib/python/validate.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/python3 - -"""A validator for MOTH puzzles""" - -import logging -import os -import os.path - -import moth - -# pylint: disable=len-as-condition, line-too-long - -DEFAULT_REQUIRED_FIELDS = ["answers", "authors", "summary"] - -LOGGER = logging.getLogger(__name__) - - -class MothValidationError(Exception): - - """An exception for encapsulating MOTH puzzle validation errors""" - - -class MothValidator: - - """A class which validates MOTH categories""" - - def __init__(self, fields): - self.required_fields = fields - self.results = {"category": {}, "checks": []} - - def validate(self, categorydir, only_errors=False): - """Run validation checks against a category""" - LOGGER.debug("Loading category from %s", categorydir) - try: - category = moth.Category(categorydir, 0) - except NotADirectoryError: - return - - LOGGER.debug("Found %d puzzles in %s", len(category.pointvals()), categorydir) - - self.results["category"][categorydir] = { - "puzzles": {}, - "name": os.path.basename(categorydir.strip(os.sep)), - } - curr_category = self.results["category"][categorydir] - - for check_function_name in [x for x in dir(self) if x.startswith("check_") and callable(getattr(self, x))]: - if check_function_name not in self.results["checks"]: - self.results["checks"].append(check_function_name) - - for puzzle in category: - LOGGER.info("Processing %s: %s", categorydir, puzzle.points) - - curr_category["puzzles"][puzzle.points] = {} - curr_puzzle = curr_category["puzzles"][puzzle.points] - curr_puzzle["failures"] = [] - - for check_function_name in [x for x in dir(self) if x.startswith("check_") and callable(getattr(self, x))]: - check_function = getattr(self, check_function_name) - LOGGER.debug("Running %s on %d", check_function_name, puzzle.points) - - try: - check_function(puzzle) - except MothValidationError as ex: - curr_puzzle["failures"].append(str(ex)) - - if only_errors and len(curr_puzzle["failures"]) == 0: - del curr_category["puzzles"][puzzle.points] - - def check_fields(self, puzzle): - """Check if the puzzle has the requested fields""" - for field in self.required_fields: - if not hasattr(puzzle, field): - raise MothValidationError("Missing field %s" % (field,)) - - @staticmethod - def check_has_answers(puzzle): - """Check if the puzle has answers defined""" - if len(puzzle.answers) == 0: - raise MothValidationError("No answers provided") - - @staticmethod - def check_unique_answers(puzzle): - """Check if puzzle answers are unique""" - known_answers = [] - duplicate_answers = [] - - for answer in puzzle.answers: - if answer not in known_answers: - known_answers.append(answer) - else: - duplicate_answers.append(answer) - - if len(duplicate_answers) > 0: - raise MothValidationError("Duplicate answer(s) %s" % ", ".join(duplicate_answers)) - - @staticmethod - def check_has_authors(puzzle): - """Check if the puzzle has authors defined""" - if len(puzzle.authors) == 0: - raise MothValidationError("No authors provided") - - @staticmethod - def check_unique_authors(puzzle): - """Check if puzzle authors are unique""" - known_authors = [] - duplicate_authors = [] - - for author in puzzle.authors: - if author not in known_authors: - known_authors.append(author) - else: - duplicate_authors.append(author) - - if len(duplicate_authors) > 0: - raise MothValidationError("Duplicate author(s) %s" % ", ".join(duplicate_authors)) - - @staticmethod - def check_has_summary(puzzle): - """Check if the puzzle has a summary""" - if puzzle.summary is None: - raise MothValidationError("Summary has not been provided") - - @staticmethod - def check_has_body(puzzle): - """Check if the puzzle has a body defined""" - old_pos = puzzle.body.tell() - puzzle.body.seek(0) - if len(puzzle.body.read()) == 0: - puzzle.body.seek(old_pos) - raise MothValidationError("No body provided") - - puzzle.body.seek(old_pos) - - # Leaving this as a placeholder until KSAs are formally supported - @staticmethod - def check_ksa_format(puzzle): - """Check if KSAs are properly formatted""" - if hasattr(puzzle, "ksa"): - for ksa in puzzle.ksa: - if not ksa.startswith("K"): - raise MothValidationError("Unrecognized KSA format") - - -def output_json(data): - """Output results in JSON format""" - import json - print(json.dumps(data)) - - -def output_text(data): - """Output results in a text-based tabular format""" - - longest_category = max([len(y["name"]) for x, y in data["category"].items()]) - longest_category = max([longest_category, len("Category")]) - longest_failure = len("Failures") - for category_data in data["category"].values(): - for points, puzzle_data in category_data["puzzles"].items(): - longest_failure = max([longest_failure, len(", ".join(puzzle_data["failures"]))]) - - formatstr = "| %%%ds | %%6s | %%%ds |" % (longest_category, longest_failure) - headerfmt = formatstr % ("Category", "Points", "Failures") - - print(headerfmt) - for cat_data in data["category"].values(): - for points, puzzle_data in sorted(cat_data["puzzles"].items()): - print(formatstr % (cat_data["name"], points, ", ".join([str(x) for x in puzzle_data["failures"]]))) - - -def main(): - """Main function""" - # pylint: disable=invalid-name - import argparse - - LOGGER.addHandler(logging.StreamHandler()) - - parser = argparse.ArgumentParser(description="Validate MOTH puzzle field compliance") - parser.add_argument("category", nargs="+", help="Categories to validate") - parser.add_argument("-f", "--fields", help="Comma-separated list of fields to check for", default=",".join(DEFAULT_REQUIRED_FIELDS)) - - parser.add_argument("-o", "--output-format", choices=["text", "json"], default="text", help="Output format (default: text)") - parser.add_argument("-e", "--only-errors", action="store_true", default=False, help="Only output errors") - parser.add_argument("-v", "--verbose", action="count", default=0, help="Increase verbosity of output, repeat to increase") - - args = parser.parse_args() - - if args.verbose == 1: - LOGGER.setLevel("INFO") - elif args.verbose > 1: - LOGGER.setLevel("DEBUG") - - LOGGER.debug(args) - validator = MothValidator(args.fields.split(",")) - - for category in args.category: - LOGGER.info("Validating %s", category) - validator.validate(category, only_errors=args.only_errors) - - if args.output_format == "text": - output_text(validator.results) - elif args.output_format == "json": - output_json(validator.results) - - -if __name__ == "__main__": - main()