1 Star 0 Fork 0

linuxkerneltravel / lmp_cli

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
clipp.h 211.67 KB
一键复制 编辑 原始数据 按行查看 历史
刘冰 提交于 2022-10-17 11:20 . A demo of 1.0 version
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024
/*****************************************************************************
* ___ _ _ ___ ___
* | _|| | | | | _ \ _ \ CLIPP - command line interfaces for modern C++
* | |_ | |_ | | | _/ _/ version 1.2.3
* |___||___||_| |_| |_| https://github.com/muellan/clipp
*
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
* Copyright (c) 2017-2018 André Müller <foss@andremueller-online.de>
*
* ---------------------------------------------------------------------------
* 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.
*
*****************************************************************************/
#ifndef AM_CLIPP_H__
#define AM_CLIPP_H__
#include <cstring>
#include <string>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <memory>
#include <vector>
#include <limits>
#include <stack>
#include <algorithm>
#include <sstream>
#include <utility>
#include <iterator>
#include <functional>
/*************************************************************************//**
*
* @brief primary namespace
*
*****************************************************************************/
namespace clipp {
/*****************************************************************************
*
* basic constants and datatype definitions
*
*****************************************************************************/
using arg_index = int;
using arg_string = std::string;
using doc_string = std::string;
using arg_list = std::vector<arg_string>;
/*************************************************************************//**
*
* @brief tristate
*
*****************************************************************************/
enum class tri : char { no, yes, either };
inline constexpr bool operator == (tri t, bool b) noexcept {
return b ? t != tri::no : t != tri::yes;
}
inline constexpr bool operator == (bool b, tri t) noexcept { return (t == b); }
inline constexpr bool operator != (tri t, bool b) noexcept { return !(t == b); }
inline constexpr bool operator != (bool b, tri t) noexcept { return !(t == b); }
/*************************************************************************//**
*
* @brief (start,size) index range
*
*****************************************************************************/
class subrange {
public:
using size_type = arg_string::size_type;
/** @brief default: no match */
explicit constexpr
subrange() noexcept :
at_{arg_string::npos}, length_{0}
{}
/** @brief match length & position within subject string */
explicit constexpr
subrange(size_type pos, size_type len) noexcept :
at_{pos}, length_{len}
{}
/** @brief position of the match within the subject string */
constexpr size_type at() const noexcept { return at_; }
/** @brief length of the matching subsequence */
constexpr size_type length() const noexcept { return length_; }
/** @brief returns true, if query string is a prefix of the subject string */
constexpr bool prefix() const noexcept {
return at_ == 0;
}
/** @brief returns true, if query is a substring of the query string */
constexpr explicit operator bool () const noexcept {
return at_ != arg_string::npos;
}
private:
size_type at_;
size_type length_;
};
/*************************************************************************//**
*
* @brief match predicates
*
*****************************************************************************/
using match_predicate = std::function<bool(const arg_string&)>;
using match_function = std::function<subrange(const arg_string&)>;
/*************************************************************************//**
*
* @brief type traits (NOT FOR DIRECT USE IN CLIENT CODE!)
* no interface guarantees; might be changed or removed in the future
*
*****************************************************************************/
namespace traits {
/*************************************************************************//**
*
* @brief function (class) signature type trait
*
*****************************************************************************/
template<class Fn, class Ret, class... Args>
constexpr auto
check_is_callable(int) -> decltype(
std::declval<Fn>()(std::declval<Args>()...),
std::integral_constant<bool,
std::is_same<Ret,typename std::result_of<Fn(Args...)>::type>::value>{} );
template<class,class,class...>
constexpr auto
check_is_callable(long) -> std::false_type;
template<class Fn, class Ret>
constexpr auto
check_is_callable_without_arg(int) -> decltype(
std::declval<Fn>()(),
std::integral_constant<bool,
std::is_same<Ret,typename std::result_of<Fn()>::type>::value>{} );
template<class,class>
constexpr auto
check_is_callable_without_arg(long) -> std::false_type;
template<class Fn, class... Args>
constexpr auto
check_is_void_callable(int) -> decltype(
std::declval<Fn>()(std::declval<Args>()...), std::true_type{});
template<class,class,class...>
constexpr auto
check_is_void_callable(long) -> std::false_type;
template<class Fn>
constexpr auto
check_is_void_callable_without_arg(int) -> decltype(
std::declval<Fn>()(), std::true_type{});
template<class>
constexpr auto
check_is_void_callable_without_arg(long) -> std::false_type;
template<class Fn, class Ret>
struct is_callable;
template<class Fn, class Ret, class... Args>
struct is_callable<Fn, Ret(Args...)> :
decltype(check_is_callable<Fn,Ret,Args...>(0))
{};
template<class Fn, class Ret>
struct is_callable<Fn,Ret()> :
decltype(check_is_callable_without_arg<Fn,Ret>(0))
{};
template<class Fn, class... Args>
struct is_callable<Fn, void(Args...)> :
decltype(check_is_void_callable<Fn,Args...>(0))
{};
template<class Fn>
struct is_callable<Fn,void()> :
decltype(check_is_void_callable_without_arg<Fn>(0))
{};
/*************************************************************************//**
*
* @brief input range type trait
*
*****************************************************************************/
template<class T>
constexpr auto
check_is_input_range(int) -> decltype(
begin(std::declval<T>()), end(std::declval<T>()),
std::true_type{});
template<class T>
constexpr auto
check_is_input_range(char) -> decltype(
std::begin(std::declval<T>()), std::end(std::declval<T>()),
std::true_type{});
template<class>
constexpr auto
check_is_input_range(long) -> std::false_type;
template<class T>
struct is_input_range :
decltype(check_is_input_range<T>(0))
{};
/*************************************************************************//**
*
* @brief size() member type trait
*
*****************************************************************************/
template<class T>
constexpr auto
check_has_size_getter(int) ->
decltype(std::declval<T>().size(), std::true_type{});
template<class>
constexpr auto
check_has_size_getter(long) -> std::false_type;
template<class T>
struct has_size_getter :
decltype(check_has_size_getter<T>(0))
{};
} // namespace traits
/*************************************************************************//**
*
* @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
* no interface guarantees; might be changed or removed in the future
*
*****************************************************************************/
namespace detail {
/*************************************************************************//**
* @brief forwards string to first non-whitespace char;
* std string -> unsigned conv yields max value, but we want 0;
* also checks for nullptr
*****************************************************************************/
inline bool
fwd_to_unsigned_int(const char*& s)
{
if(!s) return false;
for(; std::isspace(*s); ++s);
if(!s[0] || s[0] == '-') return false;
if(s[0] == '-') return false;
return true;
}
/*************************************************************************//**
*
* @brief value limits clamping
*
*****************************************************************************/
template<class T, class V, bool = (sizeof(V) > sizeof(T))>
struct limits_clamped {
static T from(const V& v) {
if(v >= V(std::numeric_limits<T>::max())) {
return std::numeric_limits<T>::max();
}
if(v <= V(std::numeric_limits<T>::lowest())) {
return std::numeric_limits<T>::lowest();
}
return T(v);
}
};
template<class T, class V>
struct limits_clamped<T,V,false> {
static T from(const V& v) { return T(v); }
};
/*************************************************************************//**
*
* @brief returns value of v as a T, clamped at T's maximum
*
*****************************************************************************/
template<class T, class V>
inline T clamped_on_limits(const V& v) {
return limits_clamped<T,V>::from(v);
}
/*************************************************************************//**
*
* @brief type conversion helpers
*
*****************************************************************************/
template<class T>
struct make {
static inline T from(const char* s) {
if(!s) return false;
//a conversion from const char* to / must exist
return static_cast<T>(s);
}
};
template<>
struct make<bool> {
static inline bool from(const char* s) {
if(!s) return false;
return static_cast<bool>(s);
}
};
template<>
struct make<unsigned char> {
static inline unsigned char from(const char* s) {
if(!fwd_to_unsigned_int(s)) return (0);
return clamped_on_limits<unsigned char>(std::strtoull(s,nullptr,10));
}
};
template<>
struct make<unsigned short int> {
static inline unsigned short int from(const char* s) {
if(!fwd_to_unsigned_int(s)) return (0);
return clamped_on_limits<unsigned short int>(std::strtoull(s,nullptr,10));
}
};
template<>
struct make<unsigned int> {
static inline unsigned int from(const char* s) {
if(!fwd_to_unsigned_int(s)) return (0);
return clamped_on_limits<unsigned int>(std::strtoull(s,nullptr,10));
}
};
template<>
struct make<unsigned long int> {
static inline unsigned long int from(const char* s) {
if(!fwd_to_unsigned_int(s)) return (0);
return clamped_on_limits<unsigned long int>(std::strtoull(s,nullptr,10));
}
};
template<>
struct make<unsigned long long int> {
static inline unsigned long long int from(const char* s) {
if(!fwd_to_unsigned_int(s)) return (0);
return clamped_on_limits<unsigned long long int>(std::strtoull(s,nullptr,10));
}
};
template<>
struct make<char> {
static inline char from(const char* s) {
//parse as single character?
const auto n = std::strlen(s);
if(n == 1) return s[0];
//parse as integer
return clamped_on_limits<char>(std::strtoll(s,nullptr,10));
}
};
template<>
struct make<short int> {
static inline short int from(const char* s) {
return clamped_on_limits<short int>(std::strtoll(s,nullptr,10));
}
};
template<>
struct make<int> {
static inline int from(const char* s) {
return clamped_on_limits<int>(std::strtoll(s,nullptr,10));
}
};
template<>
struct make<long int> {
static inline long int from(const char* s) {
return clamped_on_limits<long int>(std::strtoll(s,nullptr,10));
}
};
template<>
struct make<long long int> {
static inline long long int from(const char* s) {
return (std::strtoll(s,nullptr,10));
}
};
template<>
struct make<float> {
static inline float from(const char* s) {
return (std::strtof(s,nullptr));
}
};
template<>
struct make<double> {
static inline double from(const char* s) {
return (std::strtod(s,nullptr));
}
};
template<>
struct make<long double> {
static inline long double from(const char* s) {
return (std::strtold(s,nullptr));
}
};
template<>
struct make<std::string> {
static inline std::string from(const char* s) {
return std::string(s);
}
};
/*************************************************************************//**
*
* @brief assigns boolean constant to one or multiple target objects
*
*****************************************************************************/
template<class T, class V = T>
class assign_value
{
public:
template<class X>
explicit constexpr
assign_value(T& target, X&& value) noexcept :
t_{std::addressof(target)}, v_{std::forward<X>(value)}
{}
void operator () () const {
if(t_) *t_ = v_;
}
private:
T* t_;
V v_;
};
/*************************************************************************//**
*
* @brief flips bools
*
*****************************************************************************/
class flip_bool
{
public:
explicit constexpr
flip_bool(bool& target) noexcept :
b_{&target}
{}
void operator () () const {
if(b_) *b_ = !*b_;
}
private:
bool* b_;
};
/*************************************************************************//**
*
* @brief increments using operator ++
*
*****************************************************************************/
template<class T>
class increment
{
public:
explicit constexpr
increment(T& target) noexcept : t_{std::addressof(target)} {}
void operator () () const {
if(t_) ++(*t_);
}
private:
T* t_;
};
/*************************************************************************//**
*
* @brief decrements using operator --
*
*****************************************************************************/
template<class T>
class decrement
{
public:
explicit constexpr
decrement(T& target) noexcept : t_{std::addressof(target)} {}
void operator () () const {
if(t_) --(*t_);
}
private:
T* t_;
};
/*************************************************************************//**
*
* @brief increments by a fixed amount using operator +=
*
*****************************************************************************/
template<class T>
class increment_by
{
public:
explicit constexpr
increment_by(T& target, T by) noexcept :
t_{std::addressof(target)}, by_{std::move(by)}
{}
void operator () () const {
if(t_) (*t_) += by_;
}
private:
T* t_;
T by_;
};
/*************************************************************************//**
*
* @brief makes a value from a string and assigns it to an object
*
*****************************************************************************/
template<class T>
class map_arg_to
{
public:
explicit constexpr
map_arg_to(T& target) noexcept : t_{std::addressof(target)} {}
void operator () (const char* s) const {
if(t_ && s) *t_ = detail::make<T>::from(s);
}
private:
T* t_;
};
//-------------------------------------------------------------------
/**
* @brief specialization for vectors: append element
*/
template<class T>
class map_arg_to<std::vector<T>>
{
public:
map_arg_to(std::vector<T>& target): t_{std::addressof(target)} {}
void operator () (const char* s) const {
if(t_ && s) t_->push_back(detail::make<T>::from(s));
}
private:
std::vector<T>* t_;
};
//-------------------------------------------------------------------
/**
* @brief specialization for bools:
* set to true regardless of string content
*/
template<>
class map_arg_to<bool>
{
public:
map_arg_to(bool& target): t_{&target} {}
void operator () (const char* s) const {
if(t_ && s) *t_ = true;
}
private:
bool* t_;
};
} // namespace detail
/*************************************************************************//**
*
* @brief string matching and processing tools
*
*****************************************************************************/
namespace str {
/*************************************************************************//**
*
* @brief converts string to value of target type 'T'
*
*****************************************************************************/
template<class T>
T make(const arg_string& s)
{
return detail::make<T>::from(s);
}
/*************************************************************************//**
*
* @brief removes trailing whitespace from string
*
*****************************************************************************/
template<class C, class T, class A>
inline void
trimr(std::basic_string<C,T,A>& s)
{
if(s.empty()) return;
s.erase(
std::find_if_not(s.rbegin(), s.rend(),
[](char c) { return std::isspace(c);} ).base(),
s.end() );
}
/*************************************************************************//**
*
* @brief removes leading whitespace from string
*
*****************************************************************************/
template<class C, class T, class A>
inline void
triml(std::basic_string<C,T,A>& s)
{
if(s.empty()) return;
s.erase(
s.begin(),
std::find_if_not(s.begin(), s.end(),
[](char c) { return std::isspace(c);})
);
}
/*************************************************************************//**
*
* @brief removes leading and trailing whitespace from string
*
*****************************************************************************/
template<class C, class T, class A>
inline void
trim(std::basic_string<C,T,A>& s)
{
triml(s);
trimr(s);
}
/*************************************************************************//**
*
* @brief removes all whitespaces from string
*
*****************************************************************************/
template<class C, class T, class A>
inline void
remove_ws(std::basic_string<C,T,A>& s)
{
if(s.empty()) return;
s.erase(std::remove_if(s.begin(), s.end(),
[](char c) { return std::isspace(c); }),
s.end() );
}
/*************************************************************************//**
*
* @brief returns true, if the 'prefix' argument
* is a prefix of the 'subject' argument
*
*****************************************************************************/
template<class C, class T, class A>
inline bool
has_prefix(const std::basic_string<C,T,A>& subject,
const std::basic_string<C,T,A>& prefix)
{
if(prefix.size() > subject.size()) return false;
return subject.find(prefix) == 0;
}
/*************************************************************************//**
*
* @brief returns true, if the 'postfix' argument
* is a postfix of the 'subject' argument
*
*****************************************************************************/
template<class C, class T, class A>
inline bool
has_postfix(const std::basic_string<C,T,A>& subject,
const std::basic_string<C,T,A>& postfix)
{
if(postfix.size() > subject.size()) return false;
return (subject.size() - postfix.size()) == subject.find(postfix);
}
/*************************************************************************//**
*
* @brief returns longest common prefix of several
* sequential random access containers
*
* @details InputRange require begin and end (member functions or overloads)
* the elements of InputRange require a size() member
*
*****************************************************************************/
template<class InputRange>
auto
longest_common_prefix(const InputRange& strs)
-> typename std::decay<decltype(*begin(strs))>::type
{
static_assert(traits::is_input_range<InputRange>(),
"parameter must satisfy the InputRange concept");
static_assert(traits::has_size_getter<
typename std::decay<decltype(*begin(strs))>::type>(),
"elements of input range must have a ::size() member function");
using std::begin;
using std::end;
using item_t = typename std::decay<decltype(*begin(strs))>::type;
using str_size_t = typename std::decay<decltype(begin(strs)->size())>::type;
const auto n = size_t(distance(begin(strs), end(strs)));
if(n < 1) return item_t("");
if(n == 1) return *begin(strs);
//length of shortest string
auto m = std::min_element(begin(strs), end(strs),
[](const item_t& a, const item_t& b) {
return a.size() < b.size(); })->size();
//check each character until we find a mismatch
for(str_size_t i = 0; i < m; ++i) {
for(str_size_t j = 1; j < n; ++j) {
if(strs[j][i] != strs[j-1][i])
return strs[0].substr(0, i);
}
}
return strs[0].substr(0, m);
}
/*************************************************************************//**
*
* @brief returns longest substring range that could be found in 'arg'
*
* @param arg string to be searched in
* @param substrings range of candidate substrings
*
*****************************************************************************/
template<class C, class T, class A, class InputRange>
subrange
longest_substring_match(const std::basic_string<C,T,A>& arg,
const InputRange& substrings)
{
using string_t = std::basic_string<C,T,A>;
static_assert(traits::is_input_range<InputRange>(),
"parameter must satisfy the InputRange concept");
static_assert(std::is_same<string_t,
typename std::decay<decltype(*begin(substrings))>::type>(),
"substrings must have same type as 'arg'");
auto i = string_t::npos;
auto n = string_t::size_type(0);
for(const auto& s : substrings) {
auto j = arg.find(s);
if(j != string_t::npos && s.size() > n) {
i = j;
n = s.size();
}
}
return subrange{i,n};
}
/*************************************************************************//**
*
* @brief returns longest prefix range that could be found in 'arg'
*
* @param arg string to be searched in
* @param prefixes range of candidate prefix strings
*
*****************************************************************************/
template<class C, class T, class A, class InputRange>
subrange
longest_prefix_match(const std::basic_string<C,T,A>& arg,
const InputRange& prefixes)
{
using string_t = std::basic_string<C,T,A>;
using s_size_t = typename string_t::size_type;
static_assert(traits::is_input_range<InputRange>(),
"parameter must satisfy the InputRange concept");
static_assert(std::is_same<string_t,
typename std::decay<decltype(*begin(prefixes))>::type>(),
"prefixes must have same type as 'arg'");
auto i = string_t::npos;
auto n = s_size_t(0);
for(const auto& s : prefixes) {
auto j = arg.find(s);
if(j == 0 && s.size() > n) {
i = 0;
n = s.size();
}
}
return subrange{i,n};
}
/*************************************************************************//**
*
* @brief returns the first occurrence of 'query' within 'subject'
*
*****************************************************************************/
template<class C, class T, class A>
inline subrange
substring_match(const std::basic_string<C,T,A>& subject,
const std::basic_string<C,T,A>& query)
{
if(subject.empty() && query.empty()) return subrange(0,0);
if(subject.empty() || query.empty()) return subrange{};
auto i = subject.find(query);
if(i == std::basic_string<C,T,A>::npos) return subrange{};
return subrange{i,query.size()};
}
/*************************************************************************//**
*
* @brief returns first substring match (pos,len) within the input string
* that represents a number
* (with at maximum one decimal point and digit separators)
*
*****************************************************************************/
template<class C, class T, class A>
subrange
first_number_match(std::basic_string<C,T,A> s,
C digitSeparator = C(','),
C decimalPoint = C('.'),
C exponential = C('e'))
{
using string_t = std::basic_string<C,T,A>;
str::trim(s);
if(s.empty()) return subrange{};
auto i = s.find_first_of("0123456789+-");
if(i == string_t::npos) {
i = s.find(decimalPoint);
if(i == string_t::npos) return subrange{};
}
bool point = false;
bool sep = false;
auto exp = string_t::npos;
auto j = i + 1;
for(; j < s.size(); ++j) {
if(s[j] == digitSeparator) {
if(!sep) sep = true; else break;
}
else {
sep = false;
if(s[j] == decimalPoint) {
//only one decimal point before exponent allowed
if(!point && exp == string_t::npos) point = true; else break;
}
else if(std::tolower(s[j]) == std::tolower(exponential)) {
//only one exponent separator allowed
if(exp == string_t::npos) exp = j; else break;
}
else if(exp != string_t::npos && (exp+1) == j) {
//only sign or digit after exponent separator
if(s[j] != '+' && s[j] != '-' && !std::isdigit(s[j])) break;
}
else if(!std::isdigit(s[j])) {
break;
}
}
}
//if length == 1 then must be a digit
if(j-i == 1 && !std::isdigit(s[i])) return subrange{};
return subrange{i,j-i};
}
/*************************************************************************//**
*
* @brief returns first substring match (pos,len)
* that represents an integer (with optional digit separators)
*
*****************************************************************************/
template<class C, class T, class A>
subrange
first_integer_match(std::basic_string<C,T,A> s,
C digitSeparator = C(','))
{
using string_t = std::basic_string<C,T,A>;
str::trim(s);
if(s.empty()) return subrange{};
auto i = s.find_first_of("0123456789+-");
if(i == string_t::npos) return subrange{};
bool sep = false;
auto j = i + 1;
for(; j < s.size(); ++j) {
if(s[j] == digitSeparator) {
if(!sep) sep = true; else break;
}
else {
sep = false;
if(!std::isdigit(s[j])) break;
}
}
//if length == 1 then must be a digit
if(j-i == 1 && !std::isdigit(s[i])) return subrange{};
return subrange{i,j-i};
}
/*************************************************************************//**
*
* @brief returns true if candidate string represents a number
*
*****************************************************************************/
template<class C, class T, class A>
bool represents_number(const std::basic_string<C,T,A>& candidate,
C digitSeparator = C(','),
C decimalPoint = C('.'),
C exponential = C('e'))
{
const auto match = str::first_number_match(candidate, digitSeparator,
decimalPoint, exponential);
return (match && match.length() == candidate.size());
}
/*************************************************************************//**
*
* @brief returns true if candidate string represents an integer
*
*****************************************************************************/
template<class C, class T, class A>
bool represents_integer(const std::basic_string<C,T,A>& candidate,
C digitSeparator = C(','))
{
const auto match = str::first_integer_match(candidate, digitSeparator);
return (match && match.length() == candidate.size());
}
} // namespace str
/*************************************************************************//**
*
* @brief makes function object with a const char* parameter
* that assigns a value to a ref-captured object
*
*****************************************************************************/
template<class T, class V>
inline detail::assign_value<T,V>
set(T& target, V value) {
return detail::assign_value<T>{target, std::move(value)};
}
/*************************************************************************//**
*
* @brief makes parameter-less function object
* that assigns value(s) to a ref-captured object;
* value(s) are obtained by converting the const char* argument to
* the captured object types;
* bools are always set to true if the argument is not nullptr
*
*****************************************************************************/
template<class T>
inline detail::map_arg_to<T>
set(T& target) {
return detail::map_arg_to<T>{target};
}
/*************************************************************************//**
*
* @brief makes function object that sets a bool to true
*
*****************************************************************************/
inline detail::assign_value<bool>
set(bool& target) {
return detail::assign_value<bool>{target,true};
}
/*************************************************************************//**
*
* @brief makes function object that sets a bool to false
*
*****************************************************************************/
inline detail::assign_value<bool>
unset(bool& target) {
return detail::assign_value<bool>{target,false};
}
/*************************************************************************//**
*
* @brief makes function object that flips the value of a ref-captured bool
*
*****************************************************************************/
inline detail::flip_bool
flip(bool& b) {
return detail::flip_bool(b);
}
/*************************************************************************//**
*
* @brief makes function object that increments using operator ++
*
*****************************************************************************/
template<class T>
inline detail::increment<T>
increment(T& target) {
return detail::increment<T>{target};
}
/*************************************************************************//**
*
* @brief makes function object that decrements using operator --
*
*****************************************************************************/
template<class T>
inline detail::increment_by<T>
increment(T& target, T by) {
return detail::increment_by<T>{target, std::move(by)};
}
/*************************************************************************//**
*
* @brief makes function object that increments by a fixed amount using operator +=
*
*****************************************************************************/
template<class T>
inline detail::decrement<T>
decrement(T& target) {
return detail::decrement<T>{target};
}
/*************************************************************************//**
*
* @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
*
*****************************************************************************/
namespace detail {
/*************************************************************************//**
*
* @brief mixin that provides action definition and execution
*
*****************************************************************************/
template<class Derived>
class action_provider
{
private:
//---------------------------------------------------------------
using simple_action = std::function<void()>;
using arg_action = std::function<void(const char*)>;
using index_action = std::function<void(int)>;
//-----------------------------------------------------
class simple_action_adapter {
public:
simple_action_adapter() = default;
simple_action_adapter(const simple_action& a): action_(a) {}
simple_action_adapter(simple_action&& a): action_(std::move(a)) {}
void operator() (const char*) const { action_(); }
void operator() (int) const { action_(); }
private:
simple_action action_;
};
public:
//---------------------------------------------------------------
/** @brief adds an action that has an operator() that is callable
* with a 'const char*' argument */
Derived&
call(arg_action a) {
argActions_.push_back(std::move(a));
return *static_cast<Derived*>(this);
}
/** @brief adds an action that has an operator()() */
Derived&
call(simple_action a) {
argActions_.push_back(simple_action_adapter(std::move(a)));
return *static_cast<Derived*>(this);
}
/** @brief adds an action that has an operator() that is callable
* with a 'const char*' argument */
Derived& operator () (arg_action a) { return call(std::move(a)); }
/** @brief adds an action that has an operator()() */
Derived& operator () (simple_action a) { return call(std::move(a)); }
//---------------------------------------------------------------
/** @brief adds an action that will set the value of 't' from
* a 'const char*' arg */
template<class Target>
Derived&
set(Target& t) {
static_assert(!std::is_pointer<Target>::value,
"parameter target type must not be a pointer");
return call(clipp::set(t));
}
/** @brief adds an action that will set the value of 't' to 'v' */
template<class Target, class Value>
Derived&
set(Target& t, Value&& v) {
return call(clipp::set(t, std::forward<Value>(v)));
}
//---------------------------------------------------------------
/** @brief adds an action that will be called if a parameter
* matches an argument for the 2nd, 3rd, 4th, ... time
*/
Derived&
if_repeated(simple_action a) {
repeatActions_.push_back(simple_action_adapter{std::move(a)});
return *static_cast<Derived*>(this);
}
/** @brief adds an action that will be called with the argument's
* index if a parameter matches an argument for
* the 2nd, 3rd, 4th, ... time
*/
Derived&
if_repeated(index_action a) {
repeatActions_.push_back(std::move(a));
return *static_cast<Derived*>(this);
}
//---------------------------------------------------------------
/** @brief adds an action that will be called if a required parameter
* is missing
*/
Derived&
if_missing(simple_action a) {
missingActions_.push_back(simple_action_adapter{std::move(a)});
return *static_cast<Derived*>(this);
}
/** @brief adds an action that will be called if a required parameter
* is missing; the action will get called with the index of
* the command line argument where the missing event occurred first
*/
Derived&
if_missing(index_action a) {
missingActions_.push_back(std::move(a));
return *static_cast<Derived*>(this);
}
//---------------------------------------------------------------
/** @brief adds an action that will be called if a parameter
* was matched, but was unreachable in the current scope
*/
Derived&
if_blocked(simple_action a) {
blockedActions_.push_back(simple_action_adapter{std::move(a)});
return *static_cast<Derived*>(this);
}
/** @brief adds an action that will be called if a parameter
* was matched, but was unreachable in the current scope;
* the action will be called with the index of
* the command line argument where the problem occurred
*/
Derived&
if_blocked(index_action a) {
blockedActions_.push_back(std::move(a));
return *static_cast<Derived*>(this);
}
//---------------------------------------------------------------
/** @brief adds an action that will be called if a parameter match
* was in conflict with a different alternative parameter
*/
Derived&
if_conflicted(simple_action a) {
conflictActions_.push_back(simple_action_adapter{std::move(a)});
return *static_cast<Derived*>(this);
}
/** @brief adds an action that will be called if a parameter match
* was in conflict with a different alternative parameter;
* the action will be called with the index of
* the command line argument where the problem occurred
*/
Derived&
if_conflicted(index_action a) {
conflictActions_.push_back(std::move(a));
return *static_cast<Derived*>(this);
}
//---------------------------------------------------------------
/** @brief adds targets = either objects whose values should be
* set by command line arguments or actions that should
* be called in case of a match */
template<class T, class... Ts>
Derived&
target(T&& t, Ts&&... ts) {
target(std::forward<T>(t));
target(std::forward<Ts>(ts)...);
return *static_cast<Derived*>(this);
}
/** @brief adds action that should be called in case of a match */
template<class T, class = typename std::enable_if<
!std::is_fundamental<typename std::decay<T>::type>() &&
(traits::is_callable<T,void()>() ||
traits::is_callable<T,void(const char*)>() )
>::type>
Derived&
target(T&& t) {
call(std::forward<T>(t));
return *static_cast<Derived*>(this);
}
/** @brief adds object whose value should be set by command line arguments
*/
template<class T, class = typename std::enable_if<
std::is_fundamental<typename std::decay<T>::type>() ||
(!traits::is_callable<T,void()>() &&
!traits::is_callable<T,void(const char*)>() )
>::type>
Derived&
target(T& t) {
set(t);
return *static_cast<Derived*>(this);
}
//TODO remove ugly empty param list overload
Derived&
target() {
return *static_cast<Derived*>(this);
}
//---------------------------------------------------------------
/** @brief adds target, see member function 'target' */
template<class Target>
inline friend Derived&
operator << (Target&& t, Derived& p) {
p.target(std::forward<Target>(t));
return p;
}
/** @brief adds target, see member function 'target' */
template<class Target>
inline friend Derived&&
operator << (Target&& t, Derived&& p) {
p.target(std::forward<Target>(t));
return std::move(p);
}
//-----------------------------------------------------
/** @brief adds target, see member function 'target' */
template<class Target>
inline friend Derived&
operator >> (Derived& p, Target&& t) {
p.target(std::forward<Target>(t));
return p;
}
/** @brief adds target, see member function 'target' */
template<class Target>
inline friend Derived&&
operator >> (Derived&& p, Target&& t) {
p.target(std::forward<Target>(t));
return std::move(p);
}
//---------------------------------------------------------------
/** @brief executes all argument actions */
void execute_actions(const arg_string& arg) const {
int i = 0;
for(const auto& a : argActions_) {
++i;
a(arg.c_str());
}
}
/** @brief executes repeat actions */
void notify_repeated(arg_index idx) const {
for(const auto& a : repeatActions_) a(idx);
}
/** @brief executes missing error actions */
void notify_missing(arg_index idx) const {
for(const auto& a : missingActions_) a(idx);
}
/** @brief executes blocked error actions */
void notify_blocked(arg_index idx) const {
for(const auto& a : blockedActions_) a(idx);
}
/** @brief executes conflict error actions */
void notify_conflict(arg_index idx) const {
for(const auto& a : conflictActions_) a(idx);
}
private:
//---------------------------------------------------------------
std::vector<arg_action> argActions_;
std::vector<index_action> repeatActions_;
std::vector<index_action> missingActions_;
std::vector<index_action> blockedActions_;
std::vector<index_action> conflictActions_;
};
/*************************************************************************//**
*
* @brief mixin that provides basic common settings of parameters and groups
*
*****************************************************************************/
template<class Derived>
class token
{
public:
//---------------------------------------------------------------
using doc_string = clipp::doc_string;
//---------------------------------------------------------------
/** @brief returns documentation string */
const doc_string& doc() const noexcept {
return doc_;
}
/** @brief sets documentations string */
Derived& doc(const doc_string& txt) {
doc_ = txt;
return *static_cast<Derived*>(this);
}
/** @brief sets documentations string */
Derived& doc(doc_string&& txt) {
doc_ = std::move(txt);
return *static_cast<Derived*>(this);
}
//---------------------------------------------------------------
/** @brief returns if a group/parameter is repeatable */
bool repeatable() const noexcept {
return repeatable_;
}
/** @brief sets repeatability of group/parameter */
Derived& repeatable(bool yes) noexcept {
repeatable_ = yes;
return *static_cast<Derived*>(this);
}
//---------------------------------------------------------------
/** @brief returns if a group/parameter is blocking/positional */
bool blocking() const noexcept {
return blocking_;
}
/** @brief determines, if a group/parameter is blocking/positional */
Derived& blocking(bool yes) noexcept {
blocking_ = yes;
return *static_cast<Derived*>(this);
}
private:
//---------------------------------------------------------------
doc_string doc_;
bool repeatable_ = false;
bool blocking_ = false;
};
/*************************************************************************//**
*
* @brief sets documentation strings on a token
*
*****************************************************************************/
template<class T>
inline T&
operator % (doc_string docstr, token<T>& p)
{
return p.doc(std::move(docstr));
}
//---------------------------------------------------------
template<class T>
inline T&&
operator % (doc_string docstr, token<T>&& p)
{
return std::move(p.doc(std::move(docstr)));
}
//---------------------------------------------------------
template<class T>
inline T&
operator % (token<T>& p, doc_string docstr)
{
return p.doc(std::move(docstr));
}
//---------------------------------------------------------
template<class T>
inline T&&
operator % (token<T>&& p, doc_string docstr)
{
return std::move(p.doc(std::move(docstr)));
}
/*************************************************************************//**
*
* @brief sets documentation strings on a token
*
*****************************************************************************/
template<class T>
inline T&
doc(doc_string docstr, token<T>& p)
{
return p.doc(std::move(docstr));
}
//---------------------------------------------------------
template<class T>
inline T&&
doc(doc_string docstr, token<T>&& p)
{
return std::move(p.doc(std::move(docstr)));
}
} // namespace detail
/*************************************************************************//**
*
* @brief contains parameter matching functions and function classes
*
*****************************************************************************/
namespace match {
/*************************************************************************//**
*
* @brief predicate that is always true
*
*****************************************************************************/
inline bool
any(const arg_string&) { return true; }
/*************************************************************************//**
*
* @brief predicate that is always false
*
*****************************************************************************/
inline bool
none(const arg_string&) { return false; }
/*************************************************************************//**
*
* @brief predicate that returns true if the argument string is non-empty string
*
*****************************************************************************/
inline bool
nonempty(const arg_string& s) {
return !s.empty();
}
/*************************************************************************//**
*
* @brief predicate that returns true if the argument is a non-empty
* string that consists only of alphanumeric characters
*
*****************************************************************************/
inline bool
alphanumeric(const arg_string& s) {
if(s.empty()) return false;
return std::all_of(s.begin(), s.end(), [](char c) {return std::isalnum(c); });
}
/*************************************************************************//**
*
* @brief predicate that returns true if the argument is a non-empty
* string that consists only of alphabetic characters
*
*****************************************************************************/
inline bool
alphabetic(const arg_string& s) {
return std::all_of(s.begin(), s.end(), [](char c) {return std::isalpha(c); });
}
/*************************************************************************//**
*
* @brief predicate that returns false if the argument string is
* equal to any string from the exclusion list
*
*****************************************************************************/
class none_of
{
public:
none_of(arg_list strs):
excluded_{std::move(strs)}
{}
template<class... Strings>
none_of(arg_string str, Strings&&... strs):
excluded_{std::move(str), std::forward<Strings>(strs)...}
{}
template<class... Strings>
none_of(const char* str, Strings&&... strs):
excluded_{arg_string(str), std::forward<Strings>(strs)...}
{}
bool operator () (const arg_string& arg) const {
return (std::find(begin(excluded_), end(excluded_), arg)
== end(excluded_));
}
private:
arg_list excluded_;
};
/*************************************************************************//**
*
* @brief predicate that returns the first substring match within the input
* string that rmeepresents a number
* (with at maximum one decimal point and digit separators)
*
*****************************************************************************/
class numbers
{
public:
explicit
numbers(char decimalPoint = '.',
char digitSeparator = ' ',
char exponentSeparator = 'e')
:
decpoint_{decimalPoint}, separator_{digitSeparator},
exp_{exponentSeparator}
{}
subrange operator () (const arg_string& s) const {
return str::first_number_match(s, separator_, decpoint_, exp_);
}
private:
char decpoint_;
char separator_;
char exp_;
};
/*************************************************************************//**
*
* @brief predicate that returns true if the input string represents an integer
* (with optional digit separators)
*
*****************************************************************************/
class integers {
public:
explicit
integers(char digitSeparator = ' '): separator_{digitSeparator} {}
subrange operator () (const arg_string& s) const {
return str::first_integer_match(s, separator_);
}
private:
char separator_;
};
/*************************************************************************//**
*
* @brief predicate that returns true if the input string represents
* a non-negative integer (with optional digit separators)
*
*****************************************************************************/
class positive_integers {
public:
explicit
positive_integers(char digitSeparator = ' '): separator_{digitSeparator} {}
subrange operator () (const arg_string& s) const {
auto match = str::first_integer_match(s, separator_);
if(!match) return subrange{};
if(s[match.at()] == '-') return subrange{};
return match;
}
private:
char separator_;
};
/*************************************************************************//**
*
* @brief predicate that returns true if the input string
* contains a given substring
*
*****************************************************************************/
class substring
{
public:
explicit
substring(arg_string str): str_{std::move(str)} {}
subrange operator () (const arg_string& s) const {
return str::substring_match(s, str_);
}
private:
arg_string str_;
};
/*************************************************************************//**
*
* @brief predicate that returns true if the input string starts
* with a given prefix
*
*****************************************************************************/
class prefix {
public:
explicit
prefix(arg_string p): prefix_{std::move(p)} {}
bool operator () (const arg_string& s) const {
return s.find(prefix_) == 0;
}
private:
arg_string prefix_;
};
/*************************************************************************//**
*
* @brief predicate that returns true if the input string does not start
* with a given prefix
*
*****************************************************************************/
class prefix_not {
public:
explicit
prefix_not(arg_string p): prefix_{std::move(p)} {}
bool operator () (const arg_string& s) const {
return s.find(prefix_) != 0;
}
private:
arg_string prefix_;
};
/** @brief alias for prefix_not */
using noprefix = prefix_not;
/*************************************************************************//**
*
* @brief predicate that returns true if the length of the input string
* is wihtin a given interval
*
*****************************************************************************/
class length {
public:
explicit
length(std::size_t exact):
min_{exact}, max_{exact}
{}
explicit
length(std::size_t min, std::size_t max):
min_{min}, max_{max}
{}
bool operator () (const arg_string& s) const {
return s.size() >= min_ && s.size() <= max_;
}
private:
std::size_t min_;
std::size_t max_;
};
/*************************************************************************//**
*
* @brief makes function object that returns true if the input string has a
* given minimum length
*
*****************************************************************************/
inline length min_length(std::size_t min)
{
return length{min, arg_string::npos-1};
}
/*************************************************************************//**
*
* @brief makes function object that returns true if the input string is
* not longer than a given maximum length
*
*****************************************************************************/
inline length max_length(std::size_t max)
{
return length{0, max};
}
} // namespace match
/*************************************************************************//**
*
* @brief command line parameter that can match one or many arguments.
*
*****************************************************************************/
class parameter :
public detail::token<parameter>,
public detail::action_provider<parameter>
{
/** @brief adapts a 'match_predicate' to the 'match_function' interface */
class predicate_adapter {
public:
explicit
predicate_adapter(match_predicate pred): match_{std::move(pred)} {}
subrange operator () (const arg_string& arg) const {
return match_(arg) ? subrange{0,arg.size()} : subrange{};
}
private:
match_predicate match_;
};
public:
//---------------------------------------------------------------
/** @brief makes default parameter, that will match nothing */
parameter():
flags_{},
matcher_{predicate_adapter{match::none}},
label_{}, required_{false}, greedy_{false}
{}
/** @brief makes "flag" parameter */
template<class... Strings>
explicit
parameter(arg_string str, Strings&&... strs):
flags_{},
matcher_{predicate_adapter{match::none}},
label_{}, required_{false}, greedy_{false}
{
add_flags(std::move(str), std::forward<Strings>(strs)...);
}
/** @brief makes "flag" parameter from range of strings */
explicit
parameter(const arg_list& flaglist):
flags_{},
matcher_{predicate_adapter{match::none}},
label_{}, required_{false}, greedy_{false}
{
add_flags(flaglist);
}
//-----------------------------------------------------
/** @brief makes "value" parameter with custom match predicate
* (= yes/no matcher)
*/
explicit
parameter(match_predicate filter):
flags_{},
matcher_{predicate_adapter{std::move(filter)}},
label_{}, required_{false}, greedy_{false}
{}
/** @brief makes "value" parameter with custom match function
* (= partial matcher)
*/
explicit
parameter(match_function filter):
flags_{},
matcher_{std::move(filter)},
label_{}, required_{false}, greedy_{false}
{}
//---------------------------------------------------------------
/** @brief returns if a parameter is required */
bool
required() const noexcept {
return required_;
}
/** @brief determines if a parameter is required */
parameter&
required(bool yes) noexcept {
required_ = yes;
return *this;
}
//---------------------------------------------------------------
/** @brief returns if a parameter should match greedily */
bool
greedy() const noexcept {
return greedy_;
}
/** @brief determines if a parameter should match greedily */
parameter&
greedy(bool yes) noexcept {
greedy_ = yes;
return *this;
}
//---------------------------------------------------------------
/** @brief returns parameter label;
* will be used for documentation, if flags are empty
*/
const doc_string&
label() const {
return label_;
}
/** @brief sets parameter label;
* will be used for documentation, if flags are empty
*/
parameter&
label(const doc_string& lbl) {
label_ = lbl;
return *this;
}
/** @brief sets parameter label;
* will be used for documentation, if flags are empty
*/
parameter&
label(doc_string&& lbl) {
label_ = lbl;
return *this;
}
//---------------------------------------------------------------
/** @brief returns either longest matching prefix of 'arg' in any
* of the flags or the result of the custom match operation
*/
subrange
match(const arg_string& arg) const
{
if(flags_.empty()) {
return matcher_(arg);
}
else {
//empty flags are not allowed
if(arg.empty()) return subrange{};
if(std::find(flags_.begin(), flags_.end(), arg) != flags_.end()) {
return subrange{0,arg.size()};
}
return str::longest_prefix_match(arg, flags_);
}
}
//---------------------------------------------------------------
/** @brief access range of flag strings */
const arg_list&
flags() const noexcept {
return flags_;
}
/** @brief access custom match operation */
const match_function&
matcher() const noexcept {
return matcher_;
}
//---------------------------------------------------------------
/** @brief prepend prefix to each flag */
inline friend parameter&
with_prefix(const arg_string& prefix, parameter& p)
{
if(prefix.empty() || p.flags().empty()) return p;
for(auto& f : p.flags_) {
if(f.find(prefix) != 0) f.insert(0, prefix);
}
return p;
}
/** @brief prepend prefix to each flag
*/
inline friend parameter&
with_prefixes_short_long(
const arg_string& shortpfx, const arg_string& longpfx,
parameter& p)
{
if(shortpfx.empty() && longpfx.empty()) return p;
if(p.flags().empty()) return p;
for(auto& f : p.flags_) {
if(f.size() == 1) {
if(f.find(shortpfx) != 0) f.insert(0, shortpfx);
} else {
if(f.find(longpfx) != 0) f.insert(0, longpfx);
}
}
return p;
}
//---------------------------------------------------------------
/** @brief prepend suffix to each flag */
inline friend parameter&
with_suffix(const arg_string& suffix, parameter& p)
{
if(suffix.empty() || p.flags().empty()) return p;
for(auto& f : p.flags_) {
if(f.find(suffix) + suffix.size() != f.size()) {
f.insert(f.end(), suffix.begin(), suffix.end());
}
}
return p;
}
/** @brief prepend suffix to each flag
*/
inline friend parameter&
with_suffixes_short_long(
const arg_string& shortsfx, const arg_string& longsfx,
parameter& p)
{
if(shortsfx.empty() && longsfx.empty()) return p;
if(p.flags().empty()) return p;
for(auto& f : p.flags_) {
if(f.size() == 1) {
if(f.find(shortsfx) + shortsfx.size() != f.size()) {
f.insert(f.end(), shortsfx.begin(), shortsfx.end());
}
} else {
if(f.find(longsfx) + longsfx.size() != f.size()) {
f.insert(f.end(), longsfx.begin(), longsfx.end());
}
}
}
return p;
}
private:
//---------------------------------------------------------------
void add_flags(arg_string str) {
//empty flags are not allowed
str::remove_ws(str);
if(!str.empty()) flags_.push_back(std::move(str));
}
//---------------------------------------------------------------
void add_flags(const arg_list& strs) {
if(strs.empty()) return;
flags_.reserve(flags_.size() + strs.size());
for(const auto& s : strs) add_flags(s);
}
template<class String1, class String2, class... Strings>
void
add_flags(String1&& s1, String2&& s2, Strings&&... ss) {
flags_.reserve(2 + sizeof...(ss));
add_flags(std::forward<String1>(s1));
add_flags(std::forward<String2>(s2), std::forward<Strings>(ss)...);
}
arg_list flags_;
match_function matcher_;
doc_string label_;
bool required_ = false;
bool greedy_ = false;
};
/*************************************************************************//**
*
* @brief makes required non-blocking exact match parameter
*
*****************************************************************************/
template<class String, class... Strings>
inline parameter
command(String&& flag, Strings&&... flags)
{
return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
.required(true).blocking(true).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes required non-blocking exact match parameter
*
*****************************************************************************/
template<class String, class... Strings>
inline parameter
required(String&& flag, Strings&&... flags)
{
return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
.required(true).blocking(false).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes optional, non-blocking exact match parameter
*
*****************************************************************************/
template<class String, class... Strings>
inline parameter
option(String&& flag, Strings&&... flags)
{
return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
.required(false).blocking(false).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes required, blocking, repeatable value parameter;
* matches any non-empty string
*
*****************************************************************************/
template<class... Targets>
inline parameter
value(const doc_string& label, Targets&&... tgts)
{
return parameter{match::nonempty}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(false);
}
template<class Filter, class... Targets, class = typename std::enable_if<
traits::is_callable<Filter,bool(const char*)>::value ||
traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
value(Filter&& filter, doc_string label, Targets&&... tgts)
{
return parameter{std::forward<Filter>(filter)}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes required, blocking, repeatable value parameter;
* matches any non-empty string
*
*****************************************************************************/
template<class... Targets>
inline parameter
values(const doc_string& label, Targets&&... tgts)
{
return parameter{match::nonempty}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(true);
}
template<class Filter, class... Targets, class = typename std::enable_if<
traits::is_callable<Filter,bool(const char*)>::value ||
traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
values(Filter&& filter, doc_string label, Targets&&... tgts)
{
return parameter{std::forward<Filter>(filter)}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(true);
}
/*************************************************************************//**
*
* @brief makes optional, blocking value parameter;
* matches any non-empty string
*
*****************************************************************************/
template<class... Targets>
inline parameter
opt_value(const doc_string& label, Targets&&... tgts)
{
return parameter{match::nonempty}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(false);
}
template<class Filter, class... Targets, class = typename std::enable_if<
traits::is_callable<Filter,bool(const char*)>::value ||
traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
opt_value(Filter&& filter, doc_string label, Targets&&... tgts)
{
return parameter{std::forward<Filter>(filter)}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes optional, blocking, repeatable value parameter;
* matches any non-empty string
*
*****************************************************************************/
template<class... Targets>
inline parameter
opt_values(const doc_string& label, Targets&&... tgts)
{
return parameter{match::nonempty}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(true);
}
template<class Filter, class... Targets, class = typename std::enable_if<
traits::is_callable<Filter,bool(const char*)>::value ||
traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
opt_values(Filter&& filter, doc_string label, Targets&&... tgts)
{
return parameter{std::forward<Filter>(filter)}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(true);
}
/*************************************************************************//**
*
* @brief makes required, blocking value parameter;
* matches any string consisting of alphanumeric characters
*
*****************************************************************************/
template<class... Targets>
inline parameter
word(const doc_string& label, Targets&&... tgts)
{
return parameter{match::alphanumeric}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes required, blocking, repeatable value parameter;
* matches any string consisting of alphanumeric characters
*
*****************************************************************************/
template<class... Targets>
inline parameter
words(const doc_string& label, Targets&&... tgts)
{
return parameter{match::alphanumeric}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(true);
}
/*************************************************************************//**
*
* @brief makes optional, blocking value parameter;
* matches any string consisting of alphanumeric characters
*
*****************************************************************************/
template<class... Targets>
inline parameter
opt_word(const doc_string& label, Targets&&... tgts)
{
return parameter{match::alphanumeric}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes optional, blocking, repeatable value parameter;
* matches any string consisting of alphanumeric characters
*
*****************************************************************************/
template<class... Targets>
inline parameter
opt_words(const doc_string& label, Targets&&... tgts)
{
return parameter{match::alphanumeric}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(true);
}
/*************************************************************************//**
*
* @brief makes required, blocking value parameter;
* matches any string that represents a number
*
*****************************************************************************/
template<class... Targets>
inline parameter
number(const doc_string& label, Targets&&... tgts)
{
return parameter{match::numbers{}}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes required, blocking, repeatable value parameter;
* matches any string that represents a number
*
*****************************************************************************/
template<class... Targets>
inline parameter
numbers(const doc_string& label, Targets&&... tgts)
{
return parameter{match::numbers{}}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(true);
}
/*************************************************************************//**
*
* @brief makes optional, blocking value parameter;
* matches any string that represents a number
*
*****************************************************************************/
template<class... Targets>
inline parameter
opt_number(const doc_string& label, Targets&&... tgts)
{
return parameter{match::numbers{}}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes optional, blocking, repeatable value parameter;
* matches any string that represents a number
*
*****************************************************************************/
template<class... Targets>
inline parameter
opt_numbers(const doc_string& label, Targets&&... tgts)
{
return parameter{match::numbers{}}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(true);
}
/*************************************************************************//**
*
* @brief makes required, blocking value parameter;
* matches any string that represents an integer
*
*****************************************************************************/
template<class... Targets>
inline parameter
integer(const doc_string& label, Targets&&... tgts)
{
return parameter{match::integers{}}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes required, blocking, repeatable value parameter;
* matches any string that represents an integer
*
*****************************************************************************/
template<class... Targets>
inline parameter
integers(const doc_string& label, Targets&&... tgts)
{
return parameter{match::integers{}}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(true).blocking(true).repeatable(true);
}
/*************************************************************************//**
*
* @brief makes optional, blocking value parameter;
* matches any string that represents an integer
*
*****************************************************************************/
template<class... Targets>
inline parameter
opt_integer(const doc_string& label, Targets&&... tgts)
{
return parameter{match::integers{}}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(false);
}
/*************************************************************************//**
*
* @brief makes optional, blocking, repeatable value parameter;
* matches any string that represents an integer
*
*****************************************************************************/
template<class... Targets>
inline parameter
opt_integers(const doc_string& label, Targets&&... tgts)
{
return parameter{match::integers{}}
.label(label)
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(true);
}
/*************************************************************************//**
*
* @brief makes catch-all value parameter
*
*****************************************************************************/
template<class... Targets>
inline parameter
any_other(Targets&&... tgts)
{
return parameter{match::any}
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(true);
}
/*************************************************************************//**
*
* @brief makes catch-all value parameter with custom filter
*
*****************************************************************************/
template<class Filter, class... Targets, class = typename std::enable_if<
traits::is_callable<Filter,bool(const char*)>::value ||
traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
any(Filter&& filter, Targets&&... tgts)
{
return parameter{std::forward<Filter>(filter)}
.target(std::forward<Targets>(tgts)...)
.required(false).blocking(false).repeatable(true);
}
/*************************************************************************//**
*
* @brief group of parameters and/or other groups;
* can be configured to act as a group of alternatives (exclusive match)
*
*****************************************************************************/
class group :
public detail::token<group>
{
//---------------------------------------------------------------
/**
* @brief tagged union type that either stores a parameter or a group
* and provides a common interface to them
* could be replaced by std::variant in the future
*
* Note to future self: do NOT try again to do this with
* dynamic polymorphism; there are a couple of
* nasty problems associated with it and the implementation
* becomes bloated and needlessly complicated.
*/
template<class Param, class Group>
struct child_t {
enum class type : char {param, group};
public:
explicit
child_t(const Param& v) : m_{v}, type_{type::param} {}
child_t( Param&& v) noexcept : m_{std::move(v)}, type_{type::param} {}
explicit
child_t(const Group& g) : m_{g}, type_{type::group} {}
child_t( Group&& g) noexcept : m_{std::move(g)}, type_{type::group} {}
child_t(const child_t& src): type_{src.type_} {
switch(type_) {
default:
case type::param: new(&m_)data{src.m_.param}; break;
case type::group: new(&m_)data{src.m_.group}; break;
}
}
child_t(child_t&& src) noexcept : type_{src.type_} {
switch(type_) {
default:
case type::param: new(&m_)data{std::move(src.m_.param)}; break;
case type::group: new(&m_)data{std::move(src.m_.group)}; break;
}
}
child_t& operator = (const child_t& src) {
destroy_content();
type_ = src.type_;
switch(type_) {
default:
case type::param: new(&m_)data{src.m_.param}; break;
case type::group: new(&m_)data{src.m_.group}; break;
}
return *this;
}
child_t& operator = (child_t&& src) noexcept {
destroy_content();
type_ = src.type_;
switch(type_) {
default:
case type::param: new(&m_)data{std::move(src.m_.param)}; break;
case type::group: new(&m_)data{std::move(src.m_.group)}; break;
}
return *this;
}
~child_t() {
destroy_content();
}
const doc_string&
doc() const noexcept {
switch(type_) {
default:
case type::param: return m_.param.doc();
case type::group: return m_.group.doc();
}
}
bool blocking() const noexcept {
switch(type_) {
case type::param: return m_.param.blocking();
case type::group: return m_.group.blocking();
default: return false;
}
}
bool repeatable() const noexcept {
switch(type_) {
case type::param: return m_.param.repeatable();
case type::group: return m_.group.repeatable();
default: return false;
}
}
bool required() const noexcept {
switch(type_) {
case type::param: return m_.param.required();
case type::group:
return (m_.group.exclusive() && m_.group.all_required() ) ||
(!m_.group.exclusive() && m_.group.any_required() );
default: return false;
}
}
bool exclusive() const noexcept {
switch(type_) {
case type::group: return m_.group.exclusive();
case type::param:
default: return false;
}
}
std::size_t param_count() const noexcept {
switch(type_) {
case type::group: return m_.group.param_count();
case type::param:
default: return std::size_t(1);
}
}
std::size_t depth() const noexcept {
switch(type_) {
case type::group: return m_.group.depth();
case type::param:
default: return std::size_t(0);
}
}
void execute_actions(const arg_string& arg) const {
switch(type_) {
default:
case type::group: return;
case type::param: m_.param.execute_actions(arg); break;
}
}
void notify_repeated(arg_index idx) const {
switch(type_) {
default:
case type::group: return;
case type::param: m_.param.notify_repeated(idx); break;
}
}
void notify_missing(arg_index idx) const {
switch(type_) {
default:
case type::group: return;
case type::param: m_.param.notify_missing(idx); break;
}
}
void notify_blocked(arg_index idx) const {
switch(type_) {
default:
case type::group: return;
case type::param: m_.param.notify_blocked(idx); break;
}
}
void notify_conflict(arg_index idx) const {
switch(type_) {
default:
case type::group: return;
case type::param: m_.param.notify_conflict(idx); break;
}
}
bool is_param() const noexcept { return type_ == type::param; }
bool is_group() const noexcept { return type_ == type::group; }
Param& as_param() noexcept { return m_.param; }
Group& as_group() noexcept { return m_.group; }
const Param& as_param() const noexcept { return m_.param; }
const Group& as_group() const noexcept { return m_.group; }
private:
void destroy_content() {
switch(type_) {
default:
case type::param: m_.param.~Param(); break;
case type::group: m_.group.~Group(); break;
}
}
union data {
data() {}
data(const Param& v) : param{v} {}
data( Param&& v) noexcept : param{std::move(v)} {}
data(const Group& g) : group{g} {}
data( Group&& g) noexcept : group{std::move(g)} {}
~data() {}
Param param;
Group group;
};
data m_;
type type_;
};
public:
//---------------------------------------------------------------
using child = child_t<parameter,group>;
using value_type = child;
private:
using children_store = std::vector<child>;
public:
using const_iterator = children_store::const_iterator;
using iterator = children_store::iterator;
using size_type = children_store::size_type;
//---------------------------------------------------------------
/**
* @brief recursively iterates over all nodes
*/
class depth_first_traverser
{
public:
//-----------------------------------------------------
struct context {
context() = default;
context(const group& p):
parent{&p}, cur{p.begin()}, end{p.end()}
{}
const group* parent = nullptr;
const_iterator cur;
const_iterator end;
};
using context_list = std::vector<context>;
//-----------------------------------------------------
class memento {
friend class depth_first_traverser;
int level_;
context context_;
public:
int level() const noexcept { return level_; }
const child* param() const noexcept { return &(*context_.cur); }
};
depth_first_traverser() = default;
explicit
depth_first_traverser(const group& cur): stack_{} {
if(!cur.empty()) stack_.emplace_back(cur);
}
explicit operator bool() const noexcept {
return !stack_.empty();
}
int level() const noexcept {
return int(stack_.size());
}
bool is_first_in_parent() const noexcept {
if(stack_.empty()) return false;
return (stack_.back().cur == stack_.back().parent->begin());
}
bool is_last_in_parent() const noexcept {
if(stack_.empty()) return false;
return (stack_.back().cur+1 == stack_.back().end);
}
bool is_last_in_path() const noexcept {
if(stack_.empty()) return false;
for(const auto& t : stack_) {
if(t.cur+1 != t.end) return false;
}
const auto& top = stack_.back();
//if we have to descend into group on next ++ => not last in path
if(top.cur->is_group()) return false;
return true;
}
/** @brief inside a group of alternatives >= minlevel */
bool is_alternative(int minlevel = 0) const noexcept {
if(stack_.empty()) return false;
if(minlevel > 0) minlevel -= 1;
if(minlevel >= int(stack_.size())) return false;
return std::any_of(stack_.begin() + minlevel, stack_.end(),
[](const context& c) { return c.parent->exclusive(); });
}
/** @brief repeatable or inside a repeatable group >= minlevel */
bool is_repeatable(int minlevel = 0) const noexcept {
if(stack_.empty()) return false;
if(stack_.back().cur->repeatable()) return true;
if(minlevel > 0) minlevel -= 1;
if(minlevel >= int(stack_.size())) return false;
return std::any_of(stack_.begin() + minlevel, stack_.end(),
[](const context& c) { return c.parent->repeatable(); });
}
/** @brief inside a particular group */
bool is_inside(const group* g) const noexcept {
if(!g) return false;
return std::any_of(stack_.begin(), stack_.end(),
[g](const context& c) { return c.parent == g; });
}
/** @brief inside group with joinable flags */
bool joinable() const noexcept {
if(stack_.empty()) return false;
return std::any_of(stack_.begin(), stack_.end(),
[](const context& c) { return c.parent->joinable(); });
}
const context_list&
stack() const {
return stack_;
}
/** @brief innermost repeat group */
const group*
innermost_repeat_group() const noexcept {
auto i = std::find_if(stack_.rbegin(), stack_.rend(),
[](const context& c) { return c.parent->repeatable(); });
return i != stack_.rend() ? i->parent : nullptr;
}
/** @brief innermost exclusive (alternatives) group */
const group*
innermost_exclusive_group() const noexcept {
auto i = std::find_if(stack_.rbegin(), stack_.rend(),
[](const context& c) { return c.parent->exclusive(); });
return i != stack_.rend() ? i->parent : nullptr;
}
/** @brief innermost blocking group */
const group*
innermost_blocking_group() const noexcept {
auto i = std::find_if(stack_.rbegin(), stack_.rend(),
[](const context& c) { return c.parent->blocking(); });
return i != stack_.rend() ? i->parent : nullptr;
}
/** @brief returns the outermost group that will be left on next ++*/
const group*
outermost_blocking_group_fully_explored() const noexcept {
if(stack_.empty()) return nullptr;
const group* g = nullptr;
for(auto i = stack_.rbegin(); i != stack_.rend(); ++i) {
if(i->cur+1 == i->end) {
if(i->parent->blocking()) g = i->parent;
} else {
return g;
}
}
return g;
}
/** @brief outermost join group */
const group*
outermost_join_group() const noexcept {
auto i = std::find_if(stack_.begin(), stack_.end(),
[](const context& c) { return c.parent->joinable(); });
return i != stack_.end() ? i->parent : nullptr;
}
const group* root() const noexcept {
return stack_.empty() ? nullptr : stack_.front().parent;
}
/** @brief common flag prefix of all flags in current group */
arg_string common_flag_prefix() const noexcept {
if(stack_.empty()) return "";
auto g = outermost_join_group();
return g ? g->common_flag_prefix() : arg_string("");
}
const child&
operator * () const noexcept {
return *stack_.back().cur;
}
const child*
operator -> () const noexcept {
return &(*stack_.back().cur);
}
const group&
parent() const noexcept {
return *(stack_.back().parent);
}
/** @brief go to next element of depth first search */
depth_first_traverser&
operator ++ () {
if(stack_.empty()) return *this;
//at group -> decend into group
if(stack_.back().cur->is_group()) {
stack_.emplace_back(stack_.back().cur->as_group());
}
else {
next_sibling();
}
return *this;
}
/** @brief go to next sibling of current */
depth_first_traverser&
next_sibling() {
if(stack_.empty()) return *this;
++stack_.back().cur;
//at the end of current group?
while(stack_.back().cur == stack_.back().end) {
//go to parent
stack_.pop_back();
if(stack_.empty()) return *this;
//go to next sibling in parent
++stack_.back().cur;
}
return *this;
}
/** @brief go to next position after siblings of current */
depth_first_traverser&
next_after_siblings() {
if(stack_.empty()) return *this;
stack_.back().cur = stack_.back().end-1;
next_sibling();
return *this;
}
/**
* @brief
*/
depth_first_traverser&
back_to_ancestor(const group* g) {
if(!g) return *this;
while(!stack_.empty()) {
const auto& top = stack_.back().cur;
if(top->is_group() && &(top->as_group()) == g) return *this;
stack_.pop_back();
}
return *this;
}
/** @brief don't visit next siblings, go back to parent on next ++
* note: renders siblings unreachable for *this
**/
depth_first_traverser&
skip_siblings() {
if(stack_.empty()) return *this;
//future increments won't visit subsequent siblings:
stack_.back().end = stack_.back().cur+1;
return *this;
}
/** @brief skips all other alternatives in surrounding exclusive groups
* on next ++
* note: renders alternatives unreachable for *this
*/
depth_first_traverser&
skip_alternatives() {
if(stack_.empty()) return *this;
//exclude all other alternatives in surrounding groups
//by making their current position the last one
for(auto& c : stack_) {
if(c.parent && c.parent->exclusive() && c.cur < c.end)
c.end = c.cur+1;
}
return *this;
}
void invalidate() {
stack_.clear();
}
inline friend bool operator == (const depth_first_traverser& a,
const depth_first_traverser& b)
{
if(a.stack_.empty() || b.stack_.empty()) return false;
//parents not the same -> different position
if(a.stack_.back().parent != b.stack_.back().parent) return false;
bool aEnd = a.stack_.back().cur == a.stack_.back().end;
bool bEnd = b.stack_.back().cur == b.stack_.back().end;
//either both at the end of the same parent => same position
if(aEnd && bEnd) return true;
//or only one at the end => not at the same position
if(aEnd || bEnd) return false;
return std::addressof(*a.stack_.back().cur) ==
std::addressof(*b.stack_.back().cur);
}
inline friend bool operator != (const depth_first_traverser& a,
const depth_first_traverser& b)
{
return !(a == b);
}
memento
undo_point() const {
memento m;
m.level_ = int(stack_.size());
if(!stack_.empty()) m.context_ = stack_.back();
return m;
}
void undo(const memento& m) {
if(m.level_ < 1) return;
if(m.level_ <= int(stack_.size())) {
stack_.erase(stack_.begin() + m.level_, stack_.end());
stack_.back() = m.context_;
}
else if(stack_.empty() && m.level_ == 1) {
stack_.push_back(m.context_);
}
}
private:
context_list stack_;
};
//---------------------------------------------------------------
group() = default;
template<class Param, class... Params>
explicit
group(doc_string docstr, Param param, Params... params):
children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
{
doc(std::move(docstr));
push_back(std::move(param), std::move(params)...);
}
template<class... Params>
explicit
group(parameter param, Params... params):
children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
{
push_back(std::move(param), std::move(params)...);
}
template<class P2, class... Ps>
explicit
group(group p1, P2 p2, Ps... ps):
children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
{
push_back(std::move(p1), std::move(p2), std::move(ps)...);
}
//-----------------------------------------------------
group(const group&) = default;
group(group&&) = default;
//---------------------------------------------------------------
group& operator = (const group&) = default;
group& operator = (group&&) = default;
//---------------------------------------------------------------
/** @brief determines if a command line argument can be matched by a
* combination of (partial) matches through any number of children
*/
group& joinable(bool yes) {
joinable_ = yes;
return *this;
}
/** @brief returns if a command line argument can be matched by a
* combination of (partial) matches through any number of children
*/
bool joinable() const noexcept {
return joinable_;
}
//---------------------------------------------------------------
/** @brief turns explicit scoping on or off
* operators , & | and other combinating functions will
* not merge groups that are marked as scoped
*/
group& scoped(bool yes) {
scoped_ = yes;
return *this;
}
/** @brief returns true if operators , & | and other combinating functions
* will merge groups and false otherwise
*/
bool scoped() const noexcept
{
return scoped_;
}
//---------------------------------------------------------------
/** @brief determines if children are mutually exclusive alternatives */
group& exclusive(bool yes) {
exclusive_ = yes;
return *this;
}
/** @brief returns if children are mutually exclusive alternatives */
bool exclusive() const noexcept {
return exclusive_;
}
//---------------------------------------------------------------
/** @brief returns true, if any child is required to match */
bool any_required() const
{
return std::any_of(children_.begin(), children_.end(),
[](const child& n){ return n.required(); });
}
/** @brief returns true, if all children are required to match */
bool all_required() const
{
return std::all_of(children_.begin(), children_.end(),
[](const child& n){ return n.required(); });
}
//---------------------------------------------------------------
/** @brief returns true if any child is optional (=non-required) */
bool any_optional() const {
return !all_required();
}
/** @brief returns true if all children are optional (=non-required) */
bool all_optional() const {
return !any_required();
}
//---------------------------------------------------------------
/** @brief returns if the entire group is blocking / positional */
bool blocking() const noexcept {
return token<group>::blocking() || (exclusive() && all_blocking());
}
//-----------------------------------------------------
/** @brief determines if the entire group is blocking / positional */
group& blocking(bool yes) {
return token<group>::blocking(yes);
}
//---------------------------------------------------------------
/** @brief returns true if any child is blocking */
bool any_blocking() const
{
return std::any_of(children_.begin(), children_.end(),
[](const child& n){ return n.blocking(); });
}
//---------------------------------------------------------------
/** @brief returns true if all children is blocking */
bool all_blocking() const
{
return std::all_of(children_.begin(), children_.end(),
[](const child& n){ return n.blocking(); });
}
//---------------------------------------------------------------
/** @brief returns if any child is a value parameter (recursive) */
bool any_flagless() const
{
return std::any_of(children_.begin(), children_.end(),
[](const child& p){
return p.is_param() && p.as_param().flags().empty();
});
}
/** @brief returns if all children are value parameters (recursive) */
bool all_flagless() const
{
return std::all_of(children_.begin(), children_.end(),
[](const child& p){
return p.is_param() && p.as_param().flags().empty();
});
}
//---------------------------------------------------------------
/** @brief adds child parameter at the end */
group&
push_back(const parameter& v) {
children_.emplace_back(v);
return *this;
}
//-----------------------------------------------------
/** @brief adds child parameter at the end */
group&
push_back(parameter&& v) {
children_.emplace_back(std::move(v));
return *this;
}
//-----------------------------------------------------
/** @brief adds child group at the end */
group&
push_back(const group& g) {
children_.emplace_back(g);
return *this;
}
//-----------------------------------------------------
/** @brief adds child group at the end */
group&
push_back(group&& g) {
children_.emplace_back(std::move(g));
return *this;
}
//-----------------------------------------------------
/** @brief adds children (groups and/or parameters) */
template<class Param1, class Param2, class... Params>
group&
push_back(Param1&& param1, Param2&& param2, Params&&... params)
{
children_.reserve(children_.size() + 2 + sizeof...(params));
push_back(std::forward<Param1>(param1));
push_back(std::forward<Param2>(param2), std::forward<Params>(params)...);
return *this;
}
//---------------------------------------------------------------
/** @brief adds child parameter at the beginning */
group&
push_front(const parameter& v) {
children_.emplace(children_.begin(), v);
return *this;
}
//-----------------------------------------------------
/** @brief adds child parameter at the beginning */
group&
push_front(parameter&& v) {
children_.emplace(children_.begin(), std::move(v));
return *this;
}
//-----------------------------------------------------
/** @brief adds child group at the beginning */
group&
push_front(const group& g) {
children_.emplace(children_.begin(), g);
return *this;
}
//-----------------------------------------------------
/** @brief adds child group at the beginning */
group&
push_front(group&& g) {
children_.emplace(children_.begin(), std::move(g));
return *this;
}
//---------------------------------------------------------------
/** @brief adds all children of other group at the end */
group&
merge(group&& g)
{
children_.insert(children_.end(),
std::make_move_iterator(g.begin()),
std::make_move_iterator(g.end()));
return *this;
}
//-----------------------------------------------------
/** @brief adds all children of several other groups at the end */
template<class... Groups>
group&
merge(group&& g1, group&& g2, Groups&&... gs)
{
merge(std::move(g1));
merge(std::move(g2), std::forward<Groups>(gs)...);
return *this;
}
//---------------------------------------------------------------
/** @brief indexed, nutable access to child */
child& operator [] (size_type index) noexcept {
return children_[index];
}
/** @brief indexed, non-nutable access to child */
const child& operator [] (size_type index) const noexcept {
return children_[index];
}
//---------------------------------------------------------------
/** @brief mutable access to first child */
child& front() noexcept { return children_.front(); }
/** @brief non-mutable access to first child */
const child& front() const noexcept { return children_.front(); }
//-----------------------------------------------------
/** @brief mutable access to last child */
child& back() noexcept { return children_.back(); }
/** @brief non-mutable access to last child */
const child& back() const noexcept { return children_.back(); }
//---------------------------------------------------------------
/** @brief returns true, if group has no children, false otherwise */
bool empty() const noexcept { return children_.empty(); }
/** @brief returns number of children */
size_type size() const noexcept { return children_.size(); }
/** @brief returns number of nested levels; 1 for a flat group */
size_type depth() const {
size_type n = 0;
for(const auto& c : children_) {
auto l = 1 + c.depth();
if(l > n) n = l;
}
return n;
}
//---------------------------------------------------------------
/** @brief returns mutating iterator to position of first element */
iterator begin() noexcept { return children_.begin(); }
/** @brief returns non-mutating iterator to position of first element */
const_iterator begin() const noexcept { return children_.begin(); }
/** @brief returns non-mutating iterator to position of first element */
const_iterator cbegin() const noexcept { return children_.begin(); }
/** @brief returns mutating iterator to position one past the last element */
iterator end() noexcept { return children_.end(); }
/** @brief returns non-mutating iterator to position one past the last element */
const_iterator end() const noexcept { return children_.end(); }
/** @brief returns non-mutating iterator to position one past the last element */
const_iterator cend() const noexcept { return children_.end(); }
//---------------------------------------------------------------
/** @brief returns augmented iterator for depth first searches
* @details traverser knows end of iteration and can skip over children
*/
depth_first_traverser
begin_dfs() const noexcept {
return depth_first_traverser{*this};
}
//---------------------------------------------------------------
/** @brief returns recursive parameter count */
size_type param_count() const {
size_type c = 0;
for(const auto& n : children_) {
c += n.param_count();
}
return c;
}
//---------------------------------------------------------------
/** @brief returns range of all flags (recursive) */
arg_list all_flags() const
{
std::vector<arg_string> all;
gather_flags(children_, all);
return all;
}
/** @brief returns true, if no flag occurs as true
* prefix of any other flag (identical flags will be ignored) */
bool flags_are_prefix_free() const
{
const auto fs = all_flags();
using std::begin; using std::end;
for(auto i = begin(fs), e = end(fs); i != e; ++i) {
if(!i->empty()) {
for(auto j = i+1; j != e; ++j) {
if(!j->empty() && *i != *j) {
if(i->find(*j) == 0) return false;
if(j->find(*i) == 0) return false;
}
}
}
}
return true;
}
//---------------------------------------------------------------
/** @brief returns longest common prefix of all flags */
arg_string common_flag_prefix() const
{
arg_list prefixes;
gather_prefixes(children_, prefixes);
return str::longest_common_prefix(prefixes);
}
private:
//---------------------------------------------------------------
static void
gather_flags(const children_store& nodes, arg_list& all)
{
for(const auto& p : nodes) {
if(p.is_group()) {
gather_flags(p.as_group().children_, all);
}
else {
const auto& pf = p.as_param().flags();
using std::begin;
using std::end;
if(!pf.empty()) all.insert(end(all), begin(pf), end(pf));
}
}
}
//---------------------------------------------------------------
static void
gather_prefixes(const children_store& nodes, arg_list& all)
{
for(const auto& p : nodes) {
if(p.is_group()) {
gather_prefixes(p.as_group().children_, all);
}
else if(!p.as_param().flags().empty()) {
auto pfx = str::longest_common_prefix(p.as_param().flags());
if(!pfx.empty()) all.push_back(std::move(pfx));
}
}
}
//---------------------------------------------------------------
children_store children_;
bool exclusive_ = false;
bool joinable_ = false;
bool scoped_ = false;
};
/*************************************************************************//**
*
* @brief group or parameter
*
*****************************************************************************/
using pattern = group::child;
/*************************************************************************//**
*
* @brief apply an action to all parameters in a group
*
*****************************************************************************/
template<class Action>
void for_all_params(group& g, Action&& action)
{
for(auto& p : g) {
if(p.is_group()) {
for_all_params(p.as_group(), action);
}
else {
action(p.as_param());
}
}
}
template<class Action>
void for_all_params(const group& g, Action&& action)
{
for(auto& p : g) {
if(p.is_group()) {
for_all_params(p.as_group(), action);
}
else {
action(p.as_param());
}
}
}
/*************************************************************************//**
*
* @brief makes a group of parameters and/or groups
*
*****************************************************************************/
inline group
operator , (parameter a, parameter b)
{
return group{std::move(a), std::move(b)}.scoped(false);
}
//---------------------------------------------------------
inline group
operator , (parameter a, group b)
{
return !b.scoped() && !b.blocking() && !b.exclusive() && !b.repeatable()
&& !b.joinable() && (b.doc().empty() || b.doc() == a.doc())
? b.push_front(std::move(a))
: group{std::move(a), std::move(b)}.scoped(false);
}
//---------------------------------------------------------
inline group
operator , (group a, parameter b)
{
return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
&& !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
? a.push_back(std::move(b))
: group{std::move(a), std::move(b)}.scoped(false);
}
//---------------------------------------------------------
inline group
operator , (group a, group b)
{
return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
&& !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
? a.push_back(std::move(b))
: group{std::move(a), std::move(b)}.scoped(false);
}
/*************************************************************************//**
*
* @brief makes a group of alternative parameters or groups
*
*****************************************************************************/
template<class Param, class... Params>
inline group
one_of(Param param, Params... params)
{
return group{std::move(param), std::move(params)...}.exclusive(true);
}
/*************************************************************************//**
*
* @brief makes a group of alternative parameters or groups
*
*****************************************************************************/
inline group
operator | (parameter a, parameter b)
{
return group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
}
//-------------------------------------------------------------------
inline group
operator | (parameter a, group b)
{
return !b.scoped() && !b.blocking() && b.exclusive() && !b.repeatable()
&& !b.joinable()
&& (b.doc().empty() || b.doc() == a.doc())
? b.push_front(std::move(a))
: group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
}
//-------------------------------------------------------------------
inline group
operator | (group a, parameter b)
{
return !a.scoped() && a.exclusive() && !a.repeatable() && !a.joinable()
&& a.blocking() == b.blocking()
&& (a.doc().empty() || a.doc() == b.doc())
? a.push_back(std::move(b))
: group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
}
inline group
operator | (group a, group b)
{
return !a.scoped() && a.exclusive() &&!a.repeatable() && !a.joinable()
&& a.blocking() == b.blocking()
&& (a.doc().empty() || a.doc() == b.doc())
? a.push_back(std::move(b))
: group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
}
/*************************************************************************//**
*
* @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
* no interface guarantees; might be changed or removed in the future
*
*****************************************************************************/
namespace detail {
inline void set_blocking(bool) {}
template<class P, class... Ps>
void set_blocking(bool yes, P& p, Ps&... ps) {
p.blocking(yes);
set_blocking(yes, ps...);
}
} // namespace detail
/*************************************************************************//**
*
* @brief makes a parameter/group sequence by making all input objects blocking
*
*****************************************************************************/
template<class Param, class... Params>
inline group
in_sequence(Param param, Params... params)
{
detail::set_blocking(true, param, params...);
return group{std::move(param), std::move(params)...}.scoped(true);
}
/*************************************************************************//**
*
* @brief makes a parameter/group sequence by making all input objects blocking
*
*****************************************************************************/
inline group
operator & (parameter a, parameter b)
{
a.blocking(true);
b.blocking(true);
return group{std::move(a), std::move(b)}.scoped(true);
}
//---------------------------------------------------------
inline group
operator & (parameter a, group b)
{
a.blocking(true);
return group{std::move(a), std::move(b)}.scoped(true);
}
//---------------------------------------------------------
inline group
operator & (group a, parameter b)
{
b.blocking(true);
if(a.all_blocking() && !a.exclusive() && !a.repeatable() && !a.joinable()
&& (a.doc().empty() || a.doc() == b.doc()))
{
return a.push_back(std::move(b));
}
else {
if(!a.all_blocking()) a.blocking(true);
return group{std::move(a), std::move(b)}.scoped(true);
}
}
inline group
operator & (group a, group b)
{
if(!b.all_blocking()) b.blocking(true);
if(a.all_blocking() && !a.exclusive() && !a.repeatable()
&& !a.joinable() && (a.doc().empty() || a.doc() == b.doc()))
{
return a.push_back(std::move(b));
}
else {
if(!a.all_blocking()) a.blocking(true);
return group{std::move(a), std::move(b)}.scoped(true);
}
}
/*************************************************************************//**
*
* @brief makes a group of parameters and/or groups
* where all single char flag params ("-a", "b", ...) are joinable
*
*****************************************************************************/
inline group
joinable(group g) {
return g.joinable(true);
}
//-------------------------------------------------------------------
template<class... Params>
inline group
joinable(parameter param, Params... params)
{
return group{std::move(param), std::move(params)...}.joinable(true);
}
template<class P2, class... Ps>
inline group
joinable(group p1, P2 p2, Ps... ps)
{
return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true);
}
template<class Param, class... Params>
inline group
joinable(doc_string docstr, Param param, Params... params)
{
return group{std::move(param), std::move(params)...}
.joinable(true).doc(std::move(docstr));
}
/*************************************************************************//**
*
* @brief makes a repeatable copy of a parameter
*
*****************************************************************************/
inline parameter
repeatable(parameter p) {
return p.repeatable(true);
}
/*************************************************************************//**
*
* @brief makes a repeatable copy of a group
*
*****************************************************************************/
inline group
repeatable(group g) {
return g.repeatable(true);
}
/*************************************************************************//**
*
* @brief makes a group of parameters and/or groups
* that is repeatable as a whole
* Note that a repeatable group consisting entirely of non-blocking
* children is equivalent to a non-repeatable group of
* repeatable children.
*
*****************************************************************************/
template<class P2, class... Ps>
inline group
repeatable(parameter p1, P2 p2, Ps... ps)
{
return group{std::move(p1), std::move(p2),
std::move(ps)...}.repeatable(true);
}
template<class P2, class... Ps>
inline group
repeatable(group p1, P2 p2, Ps... ps)
{
return group{std::move(p1), std::move(p2),
std::move(ps)...}.repeatable(true);
}
/*************************************************************************//**
*
* @brief makes a parameter greedy (match with top priority)
*
*****************************************************************************/
inline parameter
greedy(parameter p) {
return p.greedy(true);
}
inline parameter
operator ! (parameter p) {
return greedy(p);
}
/*************************************************************************//**
*
* @brief recursively prepends a prefix to all flags
*
*****************************************************************************/
inline parameter&&
with_prefix(const arg_string& prefix, parameter&& p) {
return std::move(with_prefix(prefix, p));
}
//-------------------------------------------------------------------
inline group&
with_prefix(const arg_string& prefix, group& g)
{
for(auto& p : g) {
if(p.is_group()) {
with_prefix(prefix, p.as_group());
} else {
with_prefix(prefix, p.as_param());
}
}
return g;
}
inline group&&
with_prefix(const arg_string& prefix, group&& params)
{
return std::move(with_prefix(prefix, params));
}
template<class Param, class... Params>
inline group
with_prefix(arg_string prefix, Param&& param, Params&&... params)
{
return with_prefix(prefix, group{std::forward<Param>(param),
std::forward<Params>(params)...});
}
/*************************************************************************//**
*
* @brief recursively prepends a prefix to all flags
*
* @param shortpfx : used for single-letter flags
* @param longpfx : used for flags with length > 1
*
*****************************************************************************/
inline parameter&&
with_prefixes_short_long(const arg_string& shortpfx, const arg_string& longpfx,
parameter&& p)
{
return std::move(with_prefixes_short_long(shortpfx, longpfx, p));
}
//-------------------------------------------------------------------
inline group&
with_prefixes_short_long(const arg_string& shortFlagPrefix,
const arg_string& longFlagPrefix,
group& g)
{
for(auto& p : g) {
if(p.is_group()) {
with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_group());
} else {
with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_param());
}
}
return g;
}
inline group&&
with_prefixes_short_long(const arg_string& shortFlagPrefix,
const arg_string& longFlagPrefix,
group&& params)
{
return std::move(with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
params));
}
template<class Param, class... Params>
inline group
with_prefixes_short_long(const arg_string& shortFlagPrefix,
const arg_string& longFlagPrefix,
Param&& param, Params&&... params)
{
return with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
group{std::forward<Param>(param),
std::forward<Params>(params)...});
}
/*************************************************************************//**
*
* @brief recursively prepends a suffix to all flags
*
*****************************************************************************/
inline parameter&&
with_suffix(const arg_string& suffix, parameter&& p) {
return std::move(with_suffix(suffix, p));
}
//-------------------------------------------------------------------
inline group&
with_suffix(const arg_string& suffix, group& g)
{
for(auto& p : g) {
if(p.is_group()) {
with_suffix(suffix, p.as_group());
} else {
with_suffix(suffix, p.as_param());
}
}
return g;
}
inline group&&
with_suffix(const arg_string& suffix, group&& params)
{
return std::move(with_suffix(suffix, params));
}
template<class Param, class... Params>
inline group
with_suffix(arg_string suffix, Param&& param, Params&&... params)
{
return with_suffix(suffix, group{std::forward<Param>(param),
std::forward<Params>(params)...});
}
/*************************************************************************//**
*
* @brief recursively prepends a suffix to all flags
*
* @param shortsfx : used for single-letter flags
* @param longsfx : used for flags with length > 1
*
*****************************************************************************/
inline parameter&&
with_suffixes_short_long(const arg_string& shortsfx, const arg_string& longsfx,
parameter&& p)
{
return std::move(with_suffixes_short_long(shortsfx, longsfx, p));
}
//-------------------------------------------------------------------
inline group&
with_suffixes_short_long(const arg_string& shortFlagSuffix,
const arg_string& longFlagSuffix,
group& g)
{
for(auto& p : g) {
if(p.is_group()) {
with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_group());
} else {
with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_param());
}
}
return g;
}
inline group&&
with_suffixes_short_long(const arg_string& shortFlagSuffix,
const arg_string& longFlagSuffix,
group&& params)
{
return std::move(with_suffixes_short_long(shortFlagSuffix, longFlagSuffix,
params));
}
template<class Param, class... Params>
inline group
with_suffixes_short_long(const arg_string& shortFlagSuffix,
const arg_string& longFlagSuffix,
Param&& param, Params&&... params)
{
return with_suffixes_short_long(shortFlagSuffix, longFlagSuffix,
group{std::forward<Param>(param),
std::forward<Params>(params)...});
}
/*************************************************************************//**
*
* @brief parsing implementation details
*
*****************************************************************************/
namespace detail {
/*************************************************************************//**
*
* @brief DFS traverser that keeps track of 'scopes'
* scope = all parameters that are either bounded by
* two blocking parameters on the same depth level
* or the beginning/end of the outermost group
*
*****************************************************************************/
class scoped_dfs_traverser
{
public:
using dfs_traverser = group::depth_first_traverser;
scoped_dfs_traverser() = default;
explicit
scoped_dfs_traverser(const group& g):
pos_{g}, lastMatch_{}, posAfterLastMatch_{}, scopes_{},
ignoreBlocks_{false},
repeatGroupStarted_{false}, repeatGroupContinues_{false}
{}
const dfs_traverser& base() const noexcept { return pos_; }
const dfs_traverser& last_match() const noexcept { return lastMatch_; }
const group& parent() const noexcept { return pos_.parent(); }
const group* innermost_repeat_group() const noexcept {
return pos_.innermost_repeat_group();
}
const group* outermost_join_group() const noexcept {
return pos_.outermost_join_group();
}
const group* innermost_blocking_group() const noexcept {
return pos_.innermost_blocking_group();
}
const group* innermost_exclusive_group() const noexcept {
return pos_.innermost_exclusive_group();
}
const pattern* operator ->() const noexcept { return pos_.operator->(); }
const pattern& operator *() const noexcept { return *pos_; }
const pattern* ptr() const noexcept { return pos_.operator->(); }
explicit operator bool() const noexcept { return bool(pos_); }
bool joinable() const noexcept { return pos_.joinable(); }
arg_string common_flag_prefix() const { return pos_.common_flag_prefix(); }
void ignore_blocking(bool yes) { ignoreBlocks_ = yes; }
void invalidate() {
pos_.invalidate();
}
bool matched() const noexcept {
return (pos_ == lastMatch_);
}
bool start_of_repeat_group() const noexcept { return repeatGroupStarted_; }
//-----------------------------------------------------
scoped_dfs_traverser&
next_sibling() { pos_.next_sibling(); return *this; }
scoped_dfs_traverser&
next_after_siblings() { pos_.next_after_siblings(); return *this; }
//-----------------------------------------------------
scoped_dfs_traverser&
operator ++ ()
{
if(!pos_) return *this;
if(pos_.is_last_in_path()) {
return_to_outermost_scope();
return *this;
}
//current pattern can block if it didn't match already
if(ignoreBlocks_ || matched()) {
++pos_;
}
else if(!pos_->is_group()) {
//current group can block if we didn't have any match in it
const group* g = pos_.outermost_blocking_group_fully_explored();
//no match in 'g' before -> skip to after its siblings
if(g && !lastMatch_.is_inside(g)) {
pos_.back_to_ancestor(g).next_after_siblings();
if(!pos_) return_to_outermost_scope();
}
else if(pos_->blocking()) {
if(pos_.parent().exclusive()) {
pos_.next_sibling();
} else {
//no match => skip siblings of blocking param
pos_.next_after_siblings();
}
if(!pos_) return_to_outermost_scope();
} else {
++pos_;
}
} else {
++pos_;
}
check_if_left_scope();
return *this;
}
//-----------------------------------------------------
void next_after_match(scoped_dfs_traverser match)
{
if(!match || ignoreBlocks_) return;
check_repeat_group_start(match);
lastMatch_ = match.base();
// if there is a blocking ancestor -> go back to it
if(!match->blocking()) {
match.pos_.back_to_ancestor(match.innermost_blocking_group());
}
//if match is not in current position & current position is blocking
//=> current position has to be advanced by one so that it is
//no longer reachable within current scope
//(can happen for repeatable, blocking parameters)
if(match.base() != pos_ && pos_->blocking()) pos_.next_sibling();
if(match->blocking()) {
if(match.pos_.is_alternative()) {
//discard other alternatives
match.pos_.skip_alternatives();
}
if(is_last_in_current_scope(match.pos_)) {
//if current param is not repeatable -> back to previous scope
if(!match->repeatable() && !match->is_group()) {
pos_ = std::move(match.pos_);
if(!scopes_.empty()) pos_.undo(scopes_.top());
}
else { //stay at match position
pos_ = std::move(match.pos_);
}
}
else { //not last in current group
//if current param is not repeatable, go directly to next
if(!match->repeatable() && !match->is_group()) {
++match.pos_;
}
if(match.pos_.level() > pos_.level()) {
scopes_.push(pos_.undo_point());
pos_ = std::move(match.pos_);
}
else if(match.pos_.level() < pos_.level()) {
return_to_level(match.pos_.level());
}
else {
pos_ = std::move(match.pos_);
}
}
posAfterLastMatch_ = pos_;
}
else {
if(match.pos_.level() < pos_.level()) {
return_to_level(match.pos_.level());
}
posAfterLastMatch_ = pos_;
}
repeatGroupContinues_ = repeat_group_continues();
}
private:
//-----------------------------------------------------
bool is_last_in_current_scope(const dfs_traverser& pos) const
{
if(scopes_.empty()) return pos.is_last_in_path();
//check if we would leave the current scope on ++
auto p = pos;
++p;
return p.level() < scopes_.top().level();
}
//-----------------------------------------------------
void check_repeat_group_start(const scoped_dfs_traverser& newMatch)
{
const auto newrg = newMatch.innermost_repeat_group();
if(!newrg) {
repeatGroupStarted_ = false;
}
else if(lastMatch_.innermost_repeat_group() != newrg) {
repeatGroupStarted_ = true;
}
else if(!repeatGroupContinues_ || !newMatch.repeatGroupContinues_) {
repeatGroupStarted_ = true;
}
else {
//special case: repeat group is outermost group
//=> we can never really 'leave' and 'reenter' it
//but if the current scope is the first element, then we are
//conceptually at a position 'before' the group
repeatGroupStarted_ = scopes_.empty() || (
newrg == pos_.root() &&
scopes_.top().param() == &(*pos_.root()->begin()) );
}
repeatGroupContinues_ = repeatGroupStarted_;
}
//-----------------------------------------------------
bool repeat_group_continues() const
{
if(!repeatGroupContinues_) return false;
const auto curRepGroup = pos_.innermost_repeat_group();
if(!curRepGroup) return false;
if(curRepGroup != lastMatch_.innermost_repeat_group()) return false;
if(!posAfterLastMatch_) return false;
return true;
}
//-----------------------------------------------------
void check_if_left_scope()
{
if(posAfterLastMatch_) {
if(pos_.level() < posAfterLastMatch_.level()) {
while(!scopes_.empty() && scopes_.top().level() >= pos_.level()) {
pos_.undo(scopes_.top());
scopes_.pop();
}
posAfterLastMatch_.invalidate();
}
}
while(!scopes_.empty() && scopes_.top().level() > pos_.level()) {
pos_.undo(scopes_.top());
scopes_.pop();
}
repeatGroupContinues_ = repeat_group_continues();
}
//-----------------------------------------------------
void return_to_outermost_scope()
{
posAfterLastMatch_.invalidate();
if(scopes_.empty()) {
pos_.invalidate();
repeatGroupContinues_ = false;
return;
}
while(!scopes_.empty() && (!pos_ || pos_.level() >= 1)) {
pos_.undo(scopes_.top());
scopes_.pop();
}
while(!scopes_.empty()) scopes_.pop();
repeatGroupContinues_ = repeat_group_continues();
}
//-----------------------------------------------------
void return_to_level(int level)
{
if(pos_.level() <= level) return;
while(!scopes_.empty() && pos_.level() > level) {
pos_.undo(scopes_.top());
scopes_.pop();
}
};
dfs_traverser pos_;
dfs_traverser lastMatch_;
dfs_traverser posAfterLastMatch_;
std::stack<dfs_traverser::memento> scopes_;
bool ignoreBlocks_ = false;
bool repeatGroupStarted_ = false;
bool repeatGroupContinues_ = false;
};
/*****************************************************************************
*
* some parameter property predicates
*
*****************************************************************************/
struct select_all {
bool operator () (const parameter&) const noexcept { return true; }
};
struct select_flags {
bool operator () (const parameter& p) const noexcept {
return !p.flags().empty();
}
};
struct select_values {
bool operator () (const parameter& p) const noexcept {
return p.flags().empty();
}
};
/*************************************************************************//**
*
* @brief result of a matching operation
*
*****************************************************************************/
class match_t {
public:
using size_type = arg_string::size_type;
match_t() = default;
match_t(arg_string s, scoped_dfs_traverser p):
str_{std::move(s)}, pos_{std::move(p)}
{}
size_type length() const noexcept { return str_.size(); }
const arg_string& str() const noexcept { return str_; }
const scoped_dfs_traverser& pos() const noexcept { return pos_; }
explicit operator bool() const noexcept { return bool(pos_); }
private:
arg_string str_;
scoped_dfs_traverser pos_;
};
/*************************************************************************//**
*
* @brief finds the first parameter that matches a given string;
* candidate parameters are traversed using a scoped DFS traverser
*
*****************************************************************************/
template<class ParamSelector>
match_t
full_match(scoped_dfs_traverser pos, const arg_string& arg,
const ParamSelector& select)
{
while(pos) {
if(pos->is_param()) {
const auto& param = pos->as_param();
if(select(param)) {
const auto match = param.match(arg);
if(match && match.length() == arg.size()) {
return match_t{arg, std::move(pos)};
}
}
}
++pos;
}
return match_t{};
}
/*************************************************************************//**
*
* @brief finds the first parameter that matches any (non-empty) prefix
* of a given string;
* candidate parameters are traversed using a scoped DFS traverser
*
*****************************************************************************/
template<class ParamSelector>
match_t
longest_prefix_match(scoped_dfs_traverser pos, const arg_string& arg,
const ParamSelector& select)
{
match_t longest;
while(pos) {
if(pos->is_param()) {
const auto& param = pos->as_param();
if(select(param)) {
auto match = param.match(arg);
if(match.prefix()) {
if(match.length() == arg.size()) {
return match_t{arg, std::move(pos)};
}
else if(match.length() > longest.length()) {
longest = match_t{arg.substr(match.at(), match.length()),
pos};
}
}
}
}
++pos;
}
return longest;
}
/*************************************************************************//**
*
* @brief finds the first parameter that partially matches a given string;
* candidate parameters are traversed using a scoped DFS traverser
*
*****************************************************************************/
template<class ParamSelector>
match_t
partial_match(scoped_dfs_traverser pos, const arg_string& arg,
const ParamSelector& select)
{
while(pos) {
if(pos->is_param()) {
const auto& param = pos->as_param();
if(select(param)) {
const auto match = param.match(arg);
if(match) {
return match_t{arg.substr(match.at(), match.length()),
std::move(pos)};
}
}
}
++pos;
}
return match_t{};
}
} //namespace detail
/***************************************************************//**
*
* @brief default command line arguments parser
*
*******************************************************************/
class parser
{
public:
using dfs_traverser = group::depth_first_traverser;
using scoped_dfs_traverser = detail::scoped_dfs_traverser;
/*****************************************************//**
* @brief arg -> parameter mapping
*********************************************************/
class arg_mapping {
public:
friend class parser;
explicit
arg_mapping(arg_index idx, arg_string s,
const dfs_traverser& match)
:
index_{idx}, arg_{std::move(s)}, match_{match},
repeat_{0}, startsRepeatGroup_{false},
blocked_{false}, conflict_{false}
{}
explicit
arg_mapping(arg_index idx, arg_string s) :
index_{idx}, arg_{std::move(s)}, match_{},
repeat_{0}, startsRepeatGroup_{false},
blocked_{false}, conflict_{false}
{}
arg_index index() const noexcept { return index_; }
const arg_string& arg() const noexcept { return arg_; }
const parameter* param() const noexcept {
return match_ && match_->is_param()
? &(match_->as_param()) : nullptr;
}
std::size_t repeat() const noexcept { return repeat_; }
bool blocked() const noexcept { return blocked_; }
bool conflict() const noexcept { return conflict_; }
bool bad_repeat() const noexcept {
if(!param()) return false;
return repeat_ > 0 && !param()->repeatable()
&& !match_.innermost_repeat_group();
}
bool any_error() const noexcept {
return !match_ || blocked() || conflict() || bad_repeat();
}
private:
arg_index index_;
arg_string arg_;
dfs_traverser match_;
std::size_t repeat_;
bool startsRepeatGroup_;
bool blocked_;
bool conflict_;
};
/*****************************************************//**
* @brief references a non-matched, required parameter
*********************************************************/
class missing_event {
public:
explicit
missing_event(const parameter* p, arg_index after):
param_{p}, aftIndex_{after}
{}
const parameter* param() const noexcept { return param_; }
arg_index after_index() const noexcept { return aftIndex_; }
private:
const parameter* param_;
arg_index aftIndex_;
};
//-----------------------------------------------------
using missing_events = std::vector<missing_event>;
using arg_mappings = std::vector<arg_mapping>;
private:
struct miss_candidate {
miss_candidate(dfs_traverser p, arg_index idx,
bool firstInRepeatGroup = false):
pos{std::move(p)}, index{idx},
startsRepeatGroup{firstInRepeatGroup}
{}
dfs_traverser pos;
arg_index index;
bool startsRepeatGroup;
};
using miss_candidates = std::vector<miss_candidate>;
public:
//---------------------------------------------------------------
/** @brief initializes parser with a command line interface
* @param offset = argument index offset used for reports
* */
explicit
parser(const group& root, arg_index offset = 0):
root_{&root}, pos_{root},
index_{offset-1}, eaten_{0},
args_{}, missCand_{}, blocked_{false}
{
for_each_potential_miss(dfs_traverser{root},
[this](const dfs_traverser& p){
missCand_.emplace_back(p, index_);
});
}
//---------------------------------------------------------------
/** @brief processes one command line argument */
bool operator() (const arg_string& arg)
{
++eaten_;
++index_;
if(!valid()) return false;
if(!blocked_ && try_match(arg)) return true;
if(try_match_blocked(arg)) return false;
//skipping of blocking & required patterns is not allowed
if(!blocked_ && !pos_.matched() && pos_->required() && pos_->blocking()) {
blocked_ = true;
}
add_nomatch(arg);
return false;
}
//---------------------------------------------------------------
/** @brief returns range of argument -> parameter mappings */
const arg_mappings& args() const {
return args_;
}
/** @brief returns list of missing events */
missing_events missed() const {
missing_events misses;
misses.reserve(missCand_.size());
for(auto i = missCand_.begin(); i != missCand_.end(); ++i) {
misses.emplace_back(&(i->pos->as_param()), i->index);
}
return misses;
}
/** @brief returns number of processed command line arguments */
arg_index parse_count() const noexcept { return eaten_; }
/** @brief returns false if previously processed command line arguments
* lead to an invalid / inconsistent parsing result
*/
bool valid() const noexcept { return bool(pos_); }
/** @brief returns false if previously processed command line arguments
* lead to an invalid / inconsistent parsing result
*/
explicit operator bool() const noexcept { return valid(); }
private:
//---------------------------------------------------------------
using match_t = detail::match_t;
//---------------------------------------------------------------
/** @brief try to match argument with unreachable parameter */
bool try_match_blocked(const arg_string& arg)
{
//try to match ahead (using temporary parser)
if(pos_) {
auto ahead = *this;
if(try_match_blocked(std::move(ahead), arg)) return true;
}
//try to match from the beginning (using temporary parser)
if(root_) {
parser all{*root_, index_+1};
if(try_match_blocked(std::move(all), arg)) return true;
}
return false;
}
//---------------------------------------------------------------
bool try_match_blocked(parser&& parse, const arg_string& arg)
{
const auto nold = int(parse.args_.size());
parse.pos_.ignore_blocking(true);
if(!parse.try_match(arg)) return false;
for(auto i = parse.args_.begin() + nold; i != parse.args_.end(); ++i) {
args_.push_back(*i);
args_.back().blocked_ = true;
}
return true;
}
//---------------------------------------------------------------
/** @brief try to find a parameter/pattern that matches 'arg' */
bool try_match(const arg_string& arg)
{
//match greedy parameters before everything else
if(pos_->is_param() && pos_->blocking() && pos_->as_param().greedy()) {
const auto match = pos_->as_param().match(arg);
if(match && match.length() == arg.size()) {
add_match(detail::match_t{arg,pos_});
return true;
}
}
//try flags first (alone, joinable or strict sequence)
if(try_match_full(arg, detail::select_flags{})) return true;
if(try_match_joined_flags(arg)) return true;
if(try_match_joined_sequence(arg, detail::select_flags{})) return true;
//try value params (alone or strict sequence)
if(try_match_full(arg, detail::select_values{})) return true;
if(try_match_joined_sequence(arg, detail::select_all{})) return true;
//try joinable params + values in any order
if(try_match_joined_params(arg)) return true;
return false;
}
//---------------------------------------------------------------
/**
* @brief try to match full argument
* @param select : predicate that candidate parameters must satisfy
*/
template<class ParamSelector>
bool try_match_full(const arg_string& arg, const ParamSelector& select)
{
auto match = detail::full_match(pos_, arg, select);
if(!match) return false;
add_match(match);
return true;
}
//---------------------------------------------------------------
/**
* @brief try to match argument as blocking sequence of parameters
* @param select : predicate that a parameter matching the prefix of
* 'arg' must satisfy
*/
template<class ParamSelector>
bool try_match_joined_sequence(arg_string arg,
const ParamSelector& acceptFirst)
{
auto fstMatch = detail::longest_prefix_match(pos_, arg, acceptFirst);
if(!fstMatch) return false;
if(fstMatch.str().size() == arg.size()) {
add_match(fstMatch);
return true;
}
if(!fstMatch.pos()->blocking()) return false;
auto pos = fstMatch.pos();
pos.ignore_blocking(true);
const auto parent = &pos.parent();
if(!pos->repeatable()) ++pos;
arg.erase(0, fstMatch.str().size());
std::vector<match_t> matches { std::move(fstMatch) };
while(!arg.empty() && pos &&
pos->blocking() && pos->is_param() &&
(&pos.parent() == parent))
{
auto match = pos->as_param().match(arg);
if(match.prefix()) {
matches.emplace_back(arg.substr(0,match.length()), pos);
arg.erase(0, match.length());
if(!pos->repeatable()) ++pos;
}
else {
if(!pos->repeatable()) return false;
++pos;
}
}
//if arg not fully covered => discard temporary matches
if(!arg.empty() || matches.empty()) return false;
for(const auto& m : matches) add_match(m);
return true;
}
//-----------------------------------------------------
/** @brief try to match 'arg' as a concatenation of joinable flags */
bool try_match_joined_flags(const arg_string& arg)
{
return find_join_group(pos_, [&](const group& g) {
return try_match_joined(g, arg, detail::select_flags{},
g.common_flag_prefix());
});
}
//---------------------------------------------------------------
/** @brief try to match 'arg' as a concatenation of joinable parameters */
bool try_match_joined_params(const arg_string& arg)
{
return find_join_group(pos_, [&](const group& g) {
return try_match_joined(g, arg, detail::select_all{});
});
}
//-----------------------------------------------------
/** @brief try to match 'arg' as concatenation of joinable parameters
* that are all contained within one group
*/
template<class ParamSelector>
bool try_match_joined(const group& joinGroup, arg_string arg,
const ParamSelector& select,
const arg_string& prefix = "")
{
//temporary parser with 'joinGroup' as top-level group
parser parse {joinGroup};
//records temporary matches
std::vector<match_t> matches;
while(!arg.empty()) {
auto match = detail::longest_prefix_match(parse.pos_, arg, select);
if(!match) return false;
arg.erase(0, match.str().size());
//make sure prefix is always present after the first match
//so that, e.g., flags "-a" and "-b" will be found in "-ab"
if(!arg.empty() && !prefix.empty() && arg.find(prefix) != 0 &&
prefix != match.str())
{
arg.insert(0,prefix);
}
parse.add_match(match);
matches.push_back(std::move(match));
}
if(!arg.empty() || matches.empty()) return false;
if(!parse.missCand_.empty()) return false;
for(const auto& a : parse.args_) if(a.any_error()) return false;
//replay matches onto *this
for(const auto& m : matches) add_match(m);
return true;
}
//-----------------------------------------------------
template<class GroupSelector>
bool find_join_group(const scoped_dfs_traverser& start,
const GroupSelector& accept) const
{
if(start && start.parent().joinable()) {
const auto& g = start.parent();
if(accept(g)) return true;
return false;
}
auto pos = start;
while(pos) {
if(pos->is_group() && pos->as_group().joinable()) {
const auto& g = pos->as_group();
if(accept(g)) return true;
pos.next_sibling();
}
else {
++pos;
}
}
return false;
}
//---------------------------------------------------------------
void add_nomatch(const arg_string& arg) {
args_.emplace_back(index_, arg);
}
//---------------------------------------------------------------
void add_match(const match_t& match)
{
const auto& pos = match.pos();
if(!pos || !pos->is_param()) return;
pos_.next_after_match(pos);
arg_mapping newArg{index_, match.str(), pos.base()};
newArg.repeat_ = occurrences_of(&pos->as_param());
newArg.conflict_ = check_conflicts(pos.base());
newArg.startsRepeatGroup_ = pos_.start_of_repeat_group();
args_.push_back(std::move(newArg));
add_miss_candidates_after(pos);
clean_miss_candidates_for(pos.base());
discard_alternative_miss_candidates(pos.base());
}
//-----------------------------------------------------
bool check_conflicts(const dfs_traverser& match)
{
if(pos_.start_of_repeat_group()) return false;
bool conflict = false;
for(const auto& m : match.stack()) {
if(m.parent->exclusive()) {
for(auto i = args_.rbegin(); i != args_.rend(); ++i) {
if(!i->blocked()) {
for(const auto& c : i->match_.stack()) {
//sibling within same exclusive group => conflict
if(c.parent == m.parent && c.cur != m.cur) {
conflict = true;
i->conflict_ = true;
}
}
}
//check for conflicts only within current repeat cycle
if(i->startsRepeatGroup_) break;
}
}
}
return conflict;
}
//-----------------------------------------------------
void clean_miss_candidates_for(const dfs_traverser& match)
{
auto i = std::find_if(missCand_.rbegin(), missCand_.rend(),
[&](const miss_candidate& m) {
return &(*m.pos) == &(*match);
});
if(i != missCand_.rend()) {
missCand_.erase(prev(i.base()));
}
}
//-----------------------------------------------------
void discard_alternative_miss_candidates(const dfs_traverser& match)
{
if(missCand_.empty()) return;
//find out, if miss candidate is sibling of one of the same
//alternative groups that the current match is a member of
//if so, we can discard the miss
//go through all exclusive groups of matching pattern
for(const auto& m : match.stack()) {
if(m.parent->exclusive()) {
for(auto i = int(missCand_.size())-1; i >= 0; --i) {
bool removed = false;
for(const auto& c : missCand_[i].pos.stack()) {
//sibling within same exclusive group => discard
if(c.parent == m.parent && c.cur != m.cur) {
missCand_.erase(missCand_.begin() + i);
if(missCand_.empty()) return;
removed = true;
break;
}
}
//remove miss candidates only within current repeat cycle
if(i > 0 && removed) {
if(missCand_[i-1].startsRepeatGroup) break;
} else {
if(missCand_[i].startsRepeatGroup) break;
}
}
}
}
}
//-----------------------------------------------------
void add_miss_candidates_after(const scoped_dfs_traverser& match)
{
auto npos = match.base();
if(npos.is_alternative()) npos.skip_alternatives();
++npos;
//need to add potential misses if:
//either new repeat group was started
const auto newRepGroup = match.innermost_repeat_group();
if(newRepGroup) {
if(pos_.start_of_repeat_group()) {
for_each_potential_miss(std::move(npos),
[&,this](const dfs_traverser& pos) {
//only add candidates within repeat group
if(newRepGroup == pos.innermost_repeat_group()) {
missCand_.emplace_back(pos, index_, true);
}
});
}
}
//... or an optional blocking param was hit
else if(match->blocking() && !match->required() &&
npos.level() >= match.base().level())
{
for_each_potential_miss(std::move(npos),
[&,this](const dfs_traverser& pos) {
//only add new candidates
if(std::find_if(missCand_.begin(), missCand_.end(),
[&](const miss_candidate& c){
return &(*c.pos) == &(*pos);
}) == missCand_.end())
{
missCand_.emplace_back(pos, index_);
}
});
}
}
//-----------------------------------------------------
template<class Action>
static void
for_each_potential_miss(dfs_traverser pos, Action&& action)
{
const auto level = pos.level();
while(pos && pos.level() >= level) {
if(pos->is_group() ) {
const auto& g = pos->as_group();
if(g.all_optional() || (g.exclusive() && g.any_optional())) {
pos.next_sibling();
} else {
++pos;
}
} else { //param
if(pos->required()) {
action(pos);
++pos;
} else if(pos->blocking()) { //optional + blocking
pos.next_after_siblings();
} else {
++pos;
}
}
}
}
//---------------------------------------------------------------
std::size_t occurrences_of(const parameter* p) const
{
if(!p) return 0;
auto i = std::find_if(args_.rbegin(), args_.rend(),
[p](const arg_mapping& a){ return a.param() == p; });
if(i != args_.rend()) return i->repeat() + 1;
return 0;
}
//---------------------------------------------------------------
const group* root_;
scoped_dfs_traverser pos_;
arg_index index_;
arg_index eaten_;
arg_mappings args_;
miss_candidates missCand_;
bool blocked_;
};
/*************************************************************************//**
*
* @brief contains argument -> parameter mappings
* and missing parameters
*
*****************************************************************************/
class parsing_result
{
public:
using arg_mapping = parser::arg_mapping;
using arg_mappings = parser::arg_mappings;
using missing_event = parser::missing_event;
using missing_events = parser::missing_events;
using iterator = arg_mappings::const_iterator;
//-----------------------------------------------------
/** @brief default: empty result */
parsing_result() = default;
parsing_result(arg_mappings arg2param, missing_events misses):
arg2param_{std::move(arg2param)}, missing_{std::move(misses)}
{}
//-----------------------------------------------------
/** @brief returns number of arguments that could not be mapped to
* a parameter
*/
arg_mappings::size_type
unmapped_args_count() const noexcept {
return std::count_if(arg2param_.begin(), arg2param_.end(),
[](const arg_mapping& a){ return !a.param(); });
}
/** @brief returns if any argument could only be matched by an
* unreachable parameter
*/
bool any_blocked() const noexcept {
return std::any_of(arg2param_.begin(), arg2param_.end(),
[](const arg_mapping& a){ return a.blocked(); });
}
/** @brief returns if any argument matched more than one parameter
* that were mutually exclusive */
bool any_conflict() const noexcept {
return std::any_of(arg2param_.begin(), arg2param_.end(),
[](const arg_mapping& a){ return a.conflict(); });
}
/** @brief returns if any parameter matched repeatedly although
* it was not allowed to */
bool any_bad_repeat() const noexcept {
return std::any_of(arg2param_.begin(), arg2param_.end(),
[](const arg_mapping& a){ return a.bad_repeat(); });
}
/** @brief returns true if any parsing error / violation of the
* command line interface definition occurred */
bool any_error() const noexcept {
return unmapped_args_count() > 0 || !missing().empty() ||
any_blocked() || any_conflict() || any_bad_repeat();
}
/** @brief returns true if no parsing error / violation of the
* command line interface definition occurred */
explicit operator bool() const noexcept { return !any_error(); }
/** @brief access to range of missing parameter match events */
const missing_events& missing() const noexcept { return missing_; }
/** @brief returns non-mutating iterator to position of
* first argument -> parameter mapping */
iterator begin() const noexcept { return arg2param_.begin(); }
/** @brief returns non-mutating iterator to position one past the
* last argument -> parameter mapping */
iterator end() const noexcept { return arg2param_.end(); }
private:
//-----------------------------------------------------
arg_mappings arg2param_;
missing_events missing_;
};
namespace detail {
namespace {
/*************************************************************************//**
*
* @brief correct some common problems
* does not - and MUST NOT - change the number of arguments
* (no insertions or deletions allowed)
*
*****************************************************************************/
void sanitize_args(arg_list& args)
{
//e.g. {"-o12", ".34"} -> {"-o", "12.34"}
if(args.empty()) return;
for(auto i = begin(args)+1; i != end(args); ++i) {
if(i != begin(args) && i->size() > 1 &&
i->find('.') == 0 && std::isdigit((*i)[1]) )
{
//find trailing digits in previous arg
using std::prev;
auto& prv = *prev(i);
auto fstDigit = std::find_if_not(prv.rbegin(), prv.rend(),
[](arg_string::value_type c){
return std::isdigit(c);
}).base();
//handle leading sign
if(fstDigit > prv.begin() &&
(*prev(fstDigit) == '+' || *prev(fstDigit) == '-'))
{
--fstDigit;
}
//prepend digits from previous arg
i->insert(begin(*i), fstDigit, end(prv));
//erase digits in previous arg
prv.erase(fstDigit, end(prv));
}
}
}
/*************************************************************************//**
*
* @brief executes actions based on a parsing result
*
*****************************************************************************/
void execute_actions(const parsing_result& res)
{
for(const auto& m : res) {
if(m.param()) {
const auto& param = *(m.param());
if(m.repeat() > 0) param.notify_repeated(m.index());
if(m.blocked()) param.notify_blocked(m.index());
if(m.conflict()) param.notify_conflict(m.index());
//main action
if(!m.any_error()) param.execute_actions(m.arg());
}
}
for(auto m : res.missing()) {
if(m.param()) m.param()->notify_missing(m.after_index());
}
}
/*************************************************************************//**
*
* @brief parses input args
*
*****************************************************************************/
static parsing_result
parse_args(const arg_list& args, const group& cli,
arg_index offset = 0)
{
//parse args and store unrecognized arg indices
parser parse{cli, offset};
for(const auto& arg : args) {
parse(arg);
if(!parse.valid()) break;
}
return parsing_result{parse.args(), parse.missed()};
}
/*************************************************************************//**
*
* @brief parses input args & executes actions
*
*****************************************************************************/
static parsing_result
parse_and_execute(const arg_list& args, const group& cli,
arg_index offset = 0)
{
auto result = parse_args(args, cli, offset);
execute_actions(result);
return result;
}
} //anonymous namespace
} // namespace detail
/*************************************************************************//**
*
* @brief parses vector of arg strings and executes actions
*
*****************************************************************************/
inline parsing_result
parse(arg_list args, const group& cli)
{
detail::sanitize_args(args);
return detail::parse_and_execute(args, cli);
}
/*************************************************************************//**
*
* @brief parses initializer_list of C-style arg strings and executes actions
*
*****************************************************************************/
inline parsing_result
parse(std::initializer_list<const char*> arglist, const group& cli)
{
arg_list args;
args.reserve(arglist.size());
for(auto a : arglist) {
args.push_back(a);
}
return parse(std::move(args), cli);
}
/*************************************************************************//**
*
* @brief parses range of arg strings and executes actions
*
*****************************************************************************/
template<class InputIterator>
inline parsing_result
parse(InputIterator first, InputIterator last, const group& cli)
{
return parse(arg_list(first,last), cli);
}
/*************************************************************************//**
*
* @brief parses the standard array of command line arguments; omits argv[0]
*
*****************************************************************************/
inline parsing_result
parse(const int argc, char* argv[], const group& cli, arg_index offset = 1)
{
arg_list args;
if(offset < argc) args.assign(argv+offset, argv+argc);
detail::sanitize_args(args);
return detail::parse_and_execute(args, cli, offset);
}
/*************************************************************************//**
*
* @brief filter predicate for parameters and groups;
* Can be used to limit documentation generation to parameter subsets.
*
*****************************************************************************/
class param_filter
{
public:
/** @brief only allow parameters with given prefix */
param_filter& prefix(const arg_string& p) noexcept {
prefix_ = p; return *this;
}
/** @brief only allow parameters with given prefix */
param_filter& prefix(arg_string&& p) noexcept {
prefix_ = std::move(p); return *this;
}
const arg_string& prefix() const noexcept { return prefix_; }
/** @brief only allow parameters with given requirement status */
param_filter& required(tri t) noexcept { required_ = t; return *this; }
tri required() const noexcept { return required_; }
/** @brief only allow parameters with given blocking status */
param_filter& blocking(tri t) noexcept { blocking_ = t; return *this; }
tri blocking() const noexcept { return blocking_; }
/** @brief only allow parameters with given repeatable status */
param_filter& repeatable(tri t) noexcept { repeatable_ = t; return *this; }
tri repeatable() const noexcept { return repeatable_; }
/** @brief only allow parameters with given docstring status */
param_filter& has_doc(tri t) noexcept { hasDoc_ = t; return *this; }
tri has_doc() const noexcept { return hasDoc_; }
/** @brief returns true, if parameter satisfies all filters */
bool operator() (const parameter& p) const noexcept {
if(!prefix_.empty()) {
if(!std::any_of(p.flags().begin(), p.flags().end(),
[&](const arg_string& flag){
return str::has_prefix(flag, prefix_);
})) return false;
}
if(required() != p.required()) return false;
if(blocking() != p.blocking()) return false;
if(repeatable() != p.repeatable()) return false;
if(has_doc() != !p.doc().empty()) return false;
return true;
}
private:
arg_string prefix_;
tri required_ = tri::either;
tri blocking_ = tri::either;
tri repeatable_ = tri::either;
tri hasDoc_ = tri::yes;
};
/*************************************************************************//**
*
* @brief documentation formatting options
*
*****************************************************************************/
class doc_formatting
{
public:
using string = doc_string;
/** @brief same as 'first_column' */
#if __cplusplus >= 201402L
[[deprecated]]
#endif
doc_formatting& start_column(int col) { return first_column(col); }
#if __cplusplus >= 201402L
[[deprecated]]
#endif
int start_column() const noexcept { return first_column(); }
/** @brief determines column where documentation printing starts */
doc_formatting&
first_column(int col) {
//limit to [0,last_column] but push doc_column to the right if necessary
if(col < 0) col = 0;
else if(col > last_column()) col = last_column();
if(col > doc_column()) doc_column(first_column());
firstCol_ = col;
return *this;
}
int first_column() const noexcept {
return firstCol_;
}
/** @brief determines column where docstrings start */
doc_formatting&
doc_column(int col) {
//limit to [first_column,last_column]
if(col < 0) col = 0;
else if(col < first_column()) col = first_column();
else if(col > last_column()) col = last_column();
docCol_ = col;
return *this;
}
int doc_column() const noexcept {
return docCol_;
}
/** @brief determines column that no documentation text must exceed;
* (text should be wrapped appropriately after this column)
*/
doc_formatting&
last_column(int col) {
//limit to [first_column,oo] but push doc_column to the left if necessary
if(col < first_column()) col = first_column();
if(col < doc_column()) doc_column(col);
lastCol_ = col;
return *this;
}
int last_column() const noexcept {
return lastCol_;
}
/** @brief determines indent of documentation lines
* for children of a documented group */
doc_formatting& indent_size(int indent) { indentSize_ = indent; return *this; }
int indent_size() const noexcept { return indentSize_; }
/** @brief determines string to be used
* if a parameter has no flags and no label */
doc_formatting& empty_label(const string& label) {
emptyLabel_ = label;
return *this;
}
const string& empty_label() const noexcept { return emptyLabel_; }
/** @brief determines string for separating parameters */
doc_formatting& param_separator(const string& sep) {
paramSep_ = sep;
return *this;
}
const string& param_separator() const noexcept { return paramSep_; }
/** @brief determines string for separating groups (in usage lines) */
doc_formatting& group_separator(const string& sep) {
groupSep_ = sep;
return *this;
}
const string& group_separator() const noexcept { return groupSep_; }
/** @brief determines string for separating alternative parameters */
doc_formatting& alternative_param_separator(const string& sep) {
altParamSep_ = sep;
return *this;
}
const string& alternative_param_separator() const noexcept { return altParamSep_; }
/** @brief determines string for separating alternative groups */
doc_formatting& alternative_group_separator(const string& sep) {
altGroupSep_ = sep;
return *this;
}
const string& alternative_group_separator() const noexcept { return altGroupSep_; }
/** @brief determines string for separating flags of the same parameter */
doc_formatting& flag_separator(const string& sep) {
flagSep_ = sep;
return *this;
}
const string& flag_separator() const noexcept { return flagSep_; }
/** @brief determines strings surrounding parameter labels */
doc_formatting&
surround_labels(const string& prefix, const string& postfix) {
labelPre_ = prefix;
labelPst_ = postfix;
return *this;
}
const string& label_prefix() const noexcept { return labelPre_; }
const string& label_postfix() const noexcept { return labelPst_; }
/** @brief determines strings surrounding optional parameters/groups */
doc_formatting&
surround_optional(const string& prefix, const string& postfix) {
optionPre_ = prefix;
optionPst_ = postfix;
return *this;
}
const string& optional_prefix() const noexcept { return optionPre_; }
const string& optional_postfix() const noexcept { return optionPst_; }
/** @brief determines strings surrounding repeatable parameters/groups */
doc_formatting&
surround_repeat(const string& prefix, const string& postfix) {
repeatPre_ = prefix;
repeatPst_ = postfix;
return *this;
}
const string& repeat_prefix() const noexcept { return repeatPre_; }
const string& repeat_postfix() const noexcept { return repeatPst_; }
/** @brief determines strings surrounding exclusive groups */
doc_formatting&
surround_alternatives(const string& prefix, const string& postfix) {
alternPre_ = prefix;
alternPst_ = postfix;
return *this;
}
const string& alternatives_prefix() const noexcept { return alternPre_; }
const string& alternatives_postfix() const noexcept { return alternPst_; }
/** @brief determines strings surrounding alternative flags */
doc_formatting&
surround_alternative_flags(const string& prefix, const string& postfix) {
alternFlagPre_ = prefix;
alternFlagPst_ = postfix;
return *this;
}
const string& alternative_flags_prefix() const noexcept { return alternFlagPre_; }
const string& alternative_flags_postfix() const noexcept { return alternFlagPst_; }
/** @brief determines strings surrounding non-exclusive groups */
doc_formatting&
surround_group(const string& prefix, const string& postfix) {
groupPre_ = prefix;
groupPst_ = postfix;
return *this;
}
const string& group_prefix() const noexcept { return groupPre_; }
const string& group_postfix() const noexcept { return groupPst_; }
/** @brief determines strings surrounding joinable groups */
doc_formatting&
surround_joinable(const string& prefix, const string& postfix) {
joinablePre_ = prefix;
joinablePst_ = postfix;
return *this;
}
const string& joinable_prefix() const noexcept { return joinablePre_; }
const string& joinable_postfix() const noexcept { return joinablePst_; }
/** @brief determines maximum number of flags per parameter to be printed
* in detailed parameter documentation lines */
doc_formatting& max_flags_per_param_in_doc(int max) {
maxAltInDocs_ = max > 0 ? max : 0;
return *this;
}
int max_flags_per_param_in_doc() const noexcept { return maxAltInDocs_; }
/** @brief determines maximum number of flags per parameter to be printed
* in usage lines */
doc_formatting& max_flags_per_param_in_usage(int max) {
maxAltInUsage_ = max > 0 ? max : 0;
return *this;
}
int max_flags_per_param_in_usage() const noexcept { return maxAltInUsage_; }
/** @brief determines number of empty rows after one single-line
* documentation entry */
doc_formatting& line_spacing(int lines) {
lineSpc_ = lines > 0 ? lines : 0;
return *this;
}
int line_spacing() const noexcept { return lineSpc_; }
/** @brief determines number of empty rows before and after a paragraph;
* a paragraph is defined by a documented group or if
* a parameter documentation entry used more than one line */
doc_formatting& paragraph_spacing(int lines) {
paragraphSpc_ = lines > 0 ? lines : 0;
return *this;
}
int paragraph_spacing() const noexcept { return paragraphSpc_; }
/** @brief determines if alternative flags with a common prefix should
* be printed in a merged fashion */
doc_formatting& merge_alternative_flags_with_common_prefix(bool yes = true) {
mergeAltCommonPfx_ = yes;
return *this;
}
bool merge_alternative_flags_with_common_prefix() const noexcept {
return mergeAltCommonPfx_;
}
/** @brief determines if joinable flags with a common prefix should
* be printed in a merged fashion */
doc_formatting& merge_joinable_with_common_prefix(bool yes = true) {
mergeJoinableCommonPfx_ = yes;
return *this;
}
bool merge_joinable_with_common_prefix() const noexcept {
return mergeJoinableCommonPfx_;
}
/** @brief determines if children of exclusive groups should be printed
* on individual lines if the exceed 'alternatives_min_split_size'
*/
doc_formatting& split_alternatives(bool yes = true) {
splitTopAlt_ = yes;
return *this;
}
bool split_alternatives() const noexcept {
return splitTopAlt_;
}
/** @brief determines how many children exclusive groups can have before
* their children are printed on individual usage lines */
doc_formatting& alternatives_min_split_size(int size) {
groupSplitSize_ = size > 0 ? size : 0;
return *this;
}
int alternatives_min_split_size() const noexcept { return groupSplitSize_; }
/** @brief determines whether to ignore new line characters in docstrings
*/
doc_formatting& ignore_newline_chars(bool yes = true) {
ignoreNewlines_ = yes;
return *this;
}
bool ignore_newline_chars() const noexcept {
return ignoreNewlines_;
}
private:
string paramSep_ = string(" ");
string groupSep_ = string(" ");
string altParamSep_ = string("|");
string altGroupSep_ = string(" | ");
string flagSep_ = string(", ");
string labelPre_ = string("<");
string labelPst_ = string(">");
string optionPre_ = string("[");
string optionPst_ = string("]");
string repeatPre_ = string("");
string repeatPst_ = string("...");
string groupPre_ = string("(");
string groupPst_ = string(")");
string alternPre_ = string("(");
string alternPst_ = string(")");
string alternFlagPre_ = string("");
string alternFlagPst_ = string("");
string joinablePre_ = string("(");
string joinablePst_ = string(")");
string emptyLabel_ = string("");
int firstCol_ = 8;
int docCol_ = 20;
int lastCol_ = 100;
int indentSize_ = 4;
int maxAltInUsage_ = 1;
int maxAltInDocs_ = 32;
int lineSpc_ = 0;
int paragraphSpc_ = 1;
int groupSplitSize_ = 3;
bool splitTopAlt_ = true;
bool mergeAltCommonPfx_ = false;
bool mergeJoinableCommonPfx_ = true;
bool ignoreNewlines_ = false;
};
namespace detail {
/*************************************************************************//**
*
* @brief stream decorator
* that applies formatting like line wrapping
*
*****************************************************************************/
template<class OStream = std::ostream, class StringT = doc_string>
class formatting_ostream
{
public:
using string_type = StringT;
using size_type = typename string_type::size_type;
using char_type = typename string_type::value_type;
formatting_ostream(OStream& os):
os_(os),
curCol_{0}, firstCol_{0}, lastCol_{100},
hangingIndent_{0}, paragraphSpacing_{0}, paragraphSpacingThreshold_{2},
curBlankLines_{0}, curParagraphLines_{1},
totalNonBlankLines_{0},
ignoreInputNls_{false}
{}
//---------------------------------------------------------------
const OStream& base() const noexcept { return os_; }
OStream& base() noexcept { return os_; }
bool good() const { return os_.good(); }
//---------------------------------------------------------------
/** @brief determines the leftmost border of the text body */
formatting_ostream& first_column(int c) {
firstCol_ = c < 0 ? 0 : c;
return *this;
}
int first_column() const noexcept { return firstCol_; }
/** @brief determines the rightmost border of the text body */
formatting_ostream& last_column(int c) {
lastCol_ = c < 0 ? 0 : c;
return *this;
}
int last_column() const noexcept { return lastCol_; }
int text_width() const noexcept {
return lastCol_ - firstCol_;
}
/** @brief additional indentation for the 2nd, 3rd, ... line of
a paragraph (sequence of soft-wrapped lines) */
formatting_ostream& hanging_indent(int amount) {
hangingIndent_ = amount;
return *this;
}
int hanging_indent() const noexcept {
return hangingIndent_;
}
/** @brief amount of blank lines between paragraphs */
formatting_ostream& paragraph_spacing(int lines) {
paragraphSpacing_ = lines;
return *this;
}
int paragraph_spacing() const noexcept {
return paragraphSpacing_;
}
/** @brief insert paragraph spacing
if paragraph is at least 'lines' lines long */
formatting_ostream& min_paragraph_lines_for_spacing(int lines) {
paragraphSpacingThreshold_ = lines;
return *this;
}
int min_paragraph_lines_for_spacing() const noexcept {
return paragraphSpacingThreshold_;
}
/** @brief if set to true, newline characters will be ignored */
formatting_ostream& ignore_newline_chars(bool yes) {
ignoreInputNls_ = yes;
return *this;
}
bool ignore_newline_chars() const noexcept {
return ignoreInputNls_;
}
//---------------------------------------------------------------
/* @brief insert 'n' spaces */
void write_spaces(int n) {
if(n < 1) return;
os_ << string_type(size_type(n), ' ');
curCol_ += n;
}
/* @brief go to new line, but continue current paragraph */
void wrap_soft(int times = 1) {
if(times < 1) return;
if(times > 1) {
os_ << string_type(size_type(times), '\n');
} else {
os_ << '\n';
}
curCol_ = 0;
++curParagraphLines_;
}
/* @brief go to new line, and start a new paragraph */
void wrap_hard(int times = 1) {
if(times < 1) return;
if(paragraph_spacing() > 0 &&
paragraph_lines() >= min_paragraph_lines_for_spacing())
{
times = paragraph_spacing() + 1;
}
if(times > 1) {
os_ << string_type(size_type(times), '\n');
curBlankLines_ += times - 1;
} else {
os_ << '\n';
}
if(at_begin_of_line()) {
++curBlankLines_;
}
curCol_ = 0;
curParagraphLines_ = 1;
}
//---------------------------------------------------------------
bool at_begin_of_line() const noexcept {
return curCol_ <= current_line_begin();
}
int current_line_begin() const noexcept {
return in_hanging_part_of_paragraph()
? firstCol_ + hangingIndent_
: firstCol_;
}
int current_column() const noexcept {
return curCol_;
}
int total_non_blank_lines() const noexcept {
return totalNonBlankLines_;
}
int paragraph_lines() const noexcept {
return curParagraphLines_;
}
int blank_lines_before_paragraph() const noexcept {
return curBlankLines_;
}
//---------------------------------------------------------------
template<class T>
friend formatting_ostream&
operator << (formatting_ostream& os, const T& x) {
os.write(x);
return os;
}
void flush() {
os_.flush();
}
private:
bool in_hanging_part_of_paragraph() const noexcept {
return hanging_indent() > 0 && paragraph_lines() > 1;
}
bool current_line_empty() const noexcept {
return curCol_ < 1;
}
bool left_of_text_area() const noexcept {
return curCol_ < current_line_begin();
}
bool right_of_text_area() const noexcept {
return curCol_ > lastCol_;
}
int columns_left_in_line() const noexcept {
return lastCol_ - std::max(current_line_begin(), curCol_);
}
void fix_indent() {
if(left_of_text_area()) {
const auto fst = current_line_begin();
write_spaces(fst - curCol_);
curCol_ = fst;
}
}
template<class Iter>
bool only_whitespace(Iter first, Iter last) const {
return last == std::find_if_not(first, last,
[](char_type c) { return std::isspace(c); });
}
/** @brief write any object */
template<class T>
void write(const T& x) {
std::ostringstream ss;
ss << x;
write(std::move(ss).str());
}
/** @brief write a stringstream */
void write(const std::ostringstream& s) {
write(s.str());
}
/** @brief write a string */
void write(const string_type& s) {
write(s.begin(), s.end());
}
/** @brief partition output into lines */
template<class Iter>
void write(Iter first, Iter last)
{
if(first == last) return;
if(*first == '\n') {
if(!ignore_newline_chars()) wrap_hard();
++first;
if(first == last) return;
}
auto i = std::find(first, last, '\n');
if(i != last) {
if(ignore_newline_chars()) ++i;
if(i != last) {
write_line(first, i);
write(i, last);
}
}
else {
write_line(first, last);
}
}
/** @brief handle line wrapping due to column constraints */
template<class Iter>
void write_line(Iter first, Iter last)
{
if(first == last) return;
if(only_whitespace(first, last)) return;
if(right_of_text_area()) wrap_soft();
if(at_begin_of_line()) {
//discard whitespace, it we start a new line
first = std::find_if(first, last,
[](char_type c) { return !std::isspace(c); });
if(first == last) return;
}
const auto n = int(std::distance(first,last));
const auto m = columns_left_in_line();
//if text to be printed is too long for one line -> wrap
if(n > m) {
//break before word, if break is mid-word
auto breakat = first + m;
while(breakat > first && !std::isspace(*breakat)) --breakat;
//could not find whitespace before word -> try after the word
if(!std::isspace(*breakat) && breakat == first) {
breakat = std::find_if(first+m, last,
[](char_type c) { return std::isspace(c); });
}
if(breakat > first) {
if(curCol_ < 1) ++totalNonBlankLines_;
fix_indent();
std::copy(first, breakat, std::ostream_iterator<char_type>(os_));
curBlankLines_ = 0;
}
if(breakat < last) {
wrap_soft();
write_line(breakat, last);
}
}
else {
if(curCol_ < 1) ++totalNonBlankLines_;
fix_indent();
std::copy(first, last, std::ostream_iterator<char_type>(os_));
curCol_ += n;
curBlankLines_ = 0;
}
}
/** @brief write a single character */
void write(char_type c)
{
if(c == '\n') {
if(!ignore_newline_chars()) wrap_hard();
}
else {
if(at_begin_of_line()) ++totalNonBlankLines_;
fix_indent();
os_ << c;
++curCol_;
}
}
OStream& os_;
int curCol_;
int firstCol_;
int lastCol_;
int hangingIndent_;
int paragraphSpacing_;
int paragraphSpacingThreshold_;
int curBlankLines_;
int curParagraphLines_;
int totalNonBlankLines_;
bool ignoreInputNls_;
};
}
/*************************************************************************//**
*
* @brief generates usage lines
*
* @details lazily evaluated
*
*****************************************************************************/
class usage_lines
{
public:
using string = doc_string;
usage_lines(const group& cli, string prefix = "",
const doc_formatting& fmt = doc_formatting{})
:
cli_(cli), fmt_(fmt), prefix_(std::move(prefix))
{
if(!prefix_.empty()) prefix_ += ' ';
}
usage_lines(const group& cli, const doc_formatting& fmt):
usage_lines(cli, "", fmt)
{}
usage_lines& ommit_outermost_group_surrounders(bool yes) {
ommitOutermostSurrounders_ = yes;
return *this;
}
bool ommit_outermost_group_surrounders() const {
return ommitOutermostSurrounders_;
}
template<class OStream>
inline friend OStream& operator << (OStream& os, const usage_lines& p) {
p.write(os);
return os;
}
string str() const {
std::ostringstream os; os << *this; return os.str();
}
private:
using stream_t = detail::formatting_ostream<>;
const group& cli_;
doc_formatting fmt_;
string prefix_;
bool ommitOutermostSurrounders_ = false;
//-----------------------------------------------------
struct context {
group::depth_first_traverser pos;
std::stack<string> separators;
std::stack<string> postfixes;
int level = 0;
const group* outermost = nullptr;
bool linestart = false;
bool useOutermost = true;
int line = 0;
bool is_singleton() const noexcept {
return linestart && pos.is_last_in_path();
}
bool is_alternative() const noexcept {
return pos.parent().exclusive();
}
};
/***************************************************************//**
*
* @brief writes usage text for command line parameters
*
*******************************************************************/
template<class OStream>
void write(OStream& os) const
{
detail::formatting_ostream<OStream> fos(os);
fos.first_column(fmt_.first_column());
fos.last_column(fmt_.last_column());
auto hindent = int(prefix_.size());
if(fos.first_column() + hindent >= int(0.4 * fos.text_width())) {
hindent = fmt_.indent_size();
}
fos.hanging_indent(hindent);
fos.paragraph_spacing(fmt_.paragraph_spacing());
fos.min_paragraph_lines_for_spacing(2);
fos.ignore_newline_chars(fmt_.ignore_newline_chars());
context cur;
cur.pos = cli_.begin_dfs();
cur.linestart = true;
cur.level = cur.pos.level();
cur.outermost = &cli_;
write(fos, cur, prefix_);
}
/***************************************************************//**
*
* @brief writes usage text for command line parameters
*
* @param prefix all that goes in front of current things to print
*
*******************************************************************/
template<class OStream>
void write(OStream& os, context cur, string prefix) const
{
if(!cur.pos) return;
std::ostringstream buf;
if(cur.linestart) buf << prefix;
const auto initPos = buf.tellp();
cur.level = cur.pos.level();
if(cur.useOutermost) {
//we cannot start outside of the outermost group
//so we have to treat it separately
start_group(buf, cur.pos.parent(), cur);
if(!cur.pos) {
os << buf.str();
return;
}
}
else {
//don't visit siblings of starter node
cur.pos.skip_siblings();
}
check_end_group(buf, cur);
do {
if(buf.tellp() > initPos) cur.linestart = false;
if(!cur.linestart && !cur.pos.is_first_in_parent()) {
buf << cur.separators.top();
}
if(cur.pos->is_group()) {
start_group(buf, cur.pos->as_group(), cur);
if(!cur.pos) {
os << buf.str();
return;
}
}
else {
buf << param_label(cur.pos->as_param(), cur);
++cur.pos;
}
check_end_group(buf, cur);
} while(cur.pos);
os << buf.str();
}
/***************************************************************//**
*
* @brief handles pattern group surrounders and separators
* and alternative splitting
*
*******************************************************************/
void start_group(std::ostringstream& os,
const group& group, context& cur) const
{
//does cur.pos already point to a member or to group itself?
//needed for special treatment of outermost group
const bool alreadyInside = &(cur.pos.parent()) == &group;
auto lbl = joined_label(group, cur);
if(!lbl.empty()) {
os << lbl;
cur.linestart = false;
//skip over entire group as its label has already been created
if(alreadyInside) {
cur.pos.next_after_siblings();
} else {
cur.pos.next_sibling();
}
}
else {
const bool splitAlternatives = group.exclusive() &&
fmt_.split_alternatives() &&
std::any_of(group.begin(), group.end(),
[this](const pattern& p) {
return int(p.param_count()) >= fmt_.alternatives_min_split_size();
});
if(splitAlternatives) {
cur.postfixes.push("");
cur.separators.push("");
//recursively print alternative paths in decision-DAG
//enter group?
if(!alreadyInside) ++cur.pos;
cur.linestart = true;
cur.useOutermost = false;
auto pfx = os.str();
os.str("");
//print paths in DAG starting at each group member
for(std::size_t i = 0; i < group.size(); ++i) {
std::stringstream buf;
cur.outermost = cur.pos->is_group() ? &(cur.pos->as_group()) : nullptr;
write(buf, cur, pfx);
if(buf.tellp() > int(pfx.size())) {
os << buf.str();
if(i < group.size()-1) {
if(cur.line > 0) {
os << string(fmt_.line_spacing(), '\n');
}
++cur.line;
os << '\n';
}
}
cur.pos.next_sibling(); //do not descend into members
}
cur.pos.invalidate(); //signal end-of-path
return;
}
else {
//pre & postfixes, separators
auto surround = group_surrounders(group, cur);
os << surround.first;
cur.postfixes.push(std::move(surround.second));
cur.separators.push(group_separator(group, fmt_));
//descend into group?
if(!alreadyInside) ++cur.pos;
}
}
cur.level = cur.pos.level();
}
/***************************************************************//**
*
*******************************************************************/
void check_end_group(std::ostringstream& os, context& cur) const
{
for(; cur.level > cur.pos.level(); --cur.level) {
os << cur.postfixes.top();
cur.postfixes.pop();
cur.separators.pop();
}
cur.level = cur.pos.level();
}
/***************************************************************//**
*
* @brief makes usage label for one command line parameter
*
*******************************************************************/
string param_label(const parameter& p, const context& cur) const
{
const auto& parent = cur.pos.parent();
const bool startsOptionalSequence =
parent.size() > 1 && p.blocking() && cur.pos.is_first_in_parent();
const bool outermost =
ommitOutermostSurrounders_ && cur.outermost == &parent;
const bool showopt = !cur.is_alternative() && !p.required()
&& !startsOptionalSequence && !outermost;
const bool showrep = p.repeatable() && !outermost;
string lbl;
if(showrep) lbl += fmt_.repeat_prefix();
if(showopt) lbl += fmt_.optional_prefix();
const auto& flags = p.flags();
if(!flags.empty()) {
const int n = std::min(fmt_.max_flags_per_param_in_usage(),
int(flags.size()));
const bool surrAlt = n > 1 && !showopt && !cur.is_singleton();
if(surrAlt) lbl += fmt_.alternative_flags_prefix();
bool sep = false;
for(int i = 0; i < n; ++i) {
if(sep) {
if(cur.is_singleton())
lbl += fmt_.alternative_group_separator();
else
lbl += fmt_.flag_separator();
}
lbl += flags[i];
sep = true;
}
if(surrAlt) lbl += fmt_.alternative_flags_postfix();
}
else {
if(!p.label().empty()) {
lbl += fmt_.label_prefix()
+ p.label()
+ fmt_.label_postfix();
} else if(!fmt_.empty_label().empty()) {
lbl += fmt_.label_prefix()
+ fmt_.empty_label()
+ fmt_.label_postfix();
} else {
return "";
}
}
if(showopt) lbl += fmt_.optional_postfix();
if(showrep) lbl += fmt_.repeat_postfix();
return lbl;
}
/***************************************************************//**
*
* @brief prints flags in one group in a merged fashion
*
*******************************************************************/
string joined_label(const group& g, const context& cur) const
{
if(!fmt_.merge_alternative_flags_with_common_prefix() &&
!fmt_.merge_joinable_with_common_prefix()) return "";
const bool flagsonly = std::all_of(g.begin(), g.end(),
[](const pattern& p){
return p.is_param() && !p.as_param().flags().empty();
});
if(!flagsonly) return "";
const bool showOpt = g.all_optional() &&
!(ommitOutermostSurrounders_ && cur.outermost == &g);
auto pfx = g.common_flag_prefix();
if(pfx.empty()) return "";
const auto n = pfx.size();
if(g.exclusive() &&
fmt_.merge_alternative_flags_with_common_prefix())
{
string lbl;
if(showOpt) lbl += fmt_.optional_prefix();
lbl += pfx + fmt_.alternatives_prefix();
bool first = true;
for(const auto& p : g) {
if(p.is_param()) {
if(first)
first = false;
else
lbl += fmt_.alternative_param_separator();
lbl += p.as_param().flags().front().substr(n);
}
}
lbl += fmt_.alternatives_postfix();
if(showOpt) lbl += fmt_.optional_postfix();
return lbl;
}
//no alternatives, but joinable flags
else if(g.joinable() &&
fmt_.merge_joinable_with_common_prefix())
{
const bool allSingleChar = std::all_of(g.begin(), g.end(),
[&](const pattern& p){
return p.is_param() &&
p.as_param().flags().front().substr(n).size() == 1;
});
if(allSingleChar) {
string lbl;
if(showOpt) lbl += fmt_.optional_prefix();
lbl += pfx;
for(const auto& p : g) {
if(p.is_param())
lbl += p.as_param().flags().front().substr(n);
}
if(showOpt) lbl += fmt_.optional_postfix();
return lbl;
}
}
return "";
}
/***************************************************************//**
*
* @return symbols with which to surround a group
*
*******************************************************************/
std::pair<string,string>
group_surrounders(const group& group, const context& cur) const
{
string prefix;
string postfix;
const bool isOutermost = &group == cur.outermost;
if(isOutermost && ommitOutermostSurrounders_)
return {string{}, string{}};
if(group.exclusive()) {
if(group.all_optional()) {
prefix = fmt_.optional_prefix();
postfix = fmt_.optional_postfix();
if(group.all_flagless()) {
prefix += fmt_.label_prefix();
postfix = fmt_.label_prefix() + postfix;
}
} else if(group.all_flagless()) {
prefix = fmt_.label_prefix();
postfix = fmt_.label_postfix();
} else if(!cur.is_singleton() || !isOutermost) {
prefix = fmt_.alternatives_prefix();
postfix = fmt_.alternatives_postfix();
}
}
else if(group.size() > 1 &&
group.front().blocking() && !group.front().required())
{
prefix = fmt_.optional_prefix();
postfix = fmt_.optional_postfix();
}
else if(group.size() > 1 && cur.is_alternative() &&
&group != cur.outermost)
{
prefix = fmt_.group_prefix();
postfix = fmt_.group_postfix();
}
else if(!group.exclusive() &&
group.joinable() && !cur.linestart)
{
prefix = fmt_.joinable_prefix();
postfix = fmt_.joinable_postfix();
}
if(group.repeatable()) {
if(prefix.empty()) prefix = fmt_.group_prefix();
prefix = fmt_.repeat_prefix() + prefix;
if(postfix.empty()) postfix = fmt_.group_postfix();
postfix += fmt_.repeat_postfix();
}
return {std::move(prefix), std::move(postfix)};
}
/***************************************************************//**
*
* @return symbol that separates members of a group
*
*******************************************************************/
static string
group_separator(const group& group, const doc_formatting& fmt)
{
const bool only1ParamPerMember = std::all_of(group.begin(), group.end(),
[](const pattern& p) { return p.param_count() < 2; });
if(only1ParamPerMember) {
if(group.exclusive()) {
return fmt.alternative_param_separator();
} else {
return fmt.param_separator();
}
}
else { //there is at least one large group inside
if(group.exclusive()) {
return fmt.alternative_group_separator();
} else {
return fmt.group_separator();
}
}
}
};
/*************************************************************************//**
*
* @brief generates parameter and group documentation from docstrings
*
* @details lazily evaluated
*
*****************************************************************************/
class documentation
{
public:
using string = doc_string;
using filter_function = std::function<bool(const parameter&)>;
documentation(const group& cli,
const doc_formatting& fmt = doc_formatting{},
filter_function filter = param_filter{})
:
cli_(cli), fmt_{fmt}, usgFmt_{fmt}, filter_{std::move(filter)}
{
//necessary, because we re-use "usage_lines" to generate
//labels for documented groups
usgFmt_.max_flags_per_param_in_usage(
usgFmt_.max_flags_per_param_in_doc());
}
documentation(const group& cli, filter_function filter) :
documentation{cli, doc_formatting{}, std::move(filter)}
{}
documentation(const group& cli, const param_filter& filter) :
documentation{cli, doc_formatting{},
[filter](const parameter& p) { return filter(p); }}
{}
template<class OStream>
inline friend OStream& operator << (OStream& os, const documentation& p) {
p.write(os);
return os;
}
string str() const {
std::ostringstream os;
write(os);
return os.str();
}
private:
using dfs_traverser = group::depth_first_traverser;
const group& cli_;
doc_formatting fmt_;
doc_formatting usgFmt_;
filter_function filter_;
enum class paragraph { param, group };
/***************************************************************//**
*
* @brief writes documentation to output stream
*
*******************************************************************/
template<class OStream>
void write(OStream& os) const {
detail::formatting_ostream<OStream> fos(os);
fos.first_column(fmt_.first_column());
fos.last_column(fmt_.last_column());
fos.hanging_indent(0);
fos.paragraph_spacing(0);
fos.ignore_newline_chars(fmt_.ignore_newline_chars());
print_doc(fos, cli_);
}
/***************************************************************//**
*
* @brief writes full documentation text for command line parameters
*
*******************************************************************/
template<class OStream>
void print_doc(detail::formatting_ostream<OStream>& os,
const group& cli, int indentLvl = 0) const
{
if(cli.empty()) return;
//if group itself doesn't have docstring
if(cli.doc().empty()) {
for(const auto& p : cli) {
print_doc(os, p, indentLvl);
}
}
else { //group itself does have docstring
bool anyDocInside = std::any_of(cli.begin(), cli.end(),
[](const pattern& p){ return !p.doc().empty(); });
if(anyDocInside) { //group docstring as title, then child entries
handle_spacing(os, paragraph::group, indentLvl);
os << cli.doc();
for(const auto& p : cli) {
print_doc(os, p, indentLvl + 1);
}
}
else { //group label first then group docstring
auto lbl = usage_lines(cli, usgFmt_)
.ommit_outermost_group_surrounders(true).str();
str::trim(lbl);
handle_spacing(os, paragraph::param, indentLvl);
print_entry(os, lbl, cli.doc());
}
}
}
/***************************************************************//**
*
* @brief writes documentation text for one group or parameter
*
*******************************************************************/
template<class OStream>
void print_doc(detail::formatting_ostream<OStream>& os,
const pattern& ptrn, int indentLvl) const
{
if(ptrn.is_group()) {
print_doc(os, ptrn.as_group(), indentLvl);
}
else {
const auto& p = ptrn.as_param();
if(!filter_(p)) return;
handle_spacing(os, paragraph::param, indentLvl);
print_entry(os, param_label(p, fmt_), p.doc());
}
}
/***************************************************************//**
*
* @brief handles line and paragraph spacings
*
*******************************************************************/
template<class OStream>
void handle_spacing(detail::formatting_ostream<OStream>& os,
paragraph p, int indentLvl) const
{
const auto oldIndent = os.first_column();
const auto indent = fmt_.first_column() + indentLvl * fmt_.indent_size();
if(os.total_non_blank_lines() < 1) {
os.first_column(indent);
return;
}
if(os.paragraph_lines() > 1 || indent < oldIndent) {
os.wrap_hard(fmt_.paragraph_spacing() + 1);
} else {
os.wrap_hard();
}
if(p == paragraph::group) {
if(os.blank_lines_before_paragraph() < fmt_.paragraph_spacing()) {
os.wrap_hard(fmt_.paragraph_spacing() - os.blank_lines_before_paragraph());
}
}
else if(os.blank_lines_before_paragraph() < fmt_.line_spacing()) {
os.wrap_hard(fmt_.line_spacing() - os.blank_lines_before_paragraph());
}
os.first_column(indent);
}
/*********************************************************************//**
*
* @brief prints one entry = label + docstring
*
************************************************************************/
template<class OStream>
void print_entry(detail::formatting_ostream<OStream>& os,
const string& label, const string& docstr) const
{
if(label.empty()) return;
os << label;
if(!docstr.empty()) {
if(os.current_column() >= fmt_.doc_column()) os.wrap_soft();
const auto oldcol = os.first_column();
os.first_column(fmt_.doc_column());
os << docstr;
os.first_column(oldcol);
}
}
/*********************************************************************//**
*
* @brief makes label for one parameter
*
************************************************************************/
static doc_string
param_label(const parameter& param, const doc_formatting& fmt)
{
doc_string lbl;
if(param.repeatable()) lbl += fmt.repeat_prefix();
const auto& flags = param.flags();
if(!flags.empty()) {
lbl += flags[0];
const int n = std::min(fmt.max_flags_per_param_in_doc(),
int(flags.size()));
for(int i = 1; i < n; ++i) {
lbl += fmt.flag_separator() + flags[i];
}
}
else if(!param.label().empty() || !fmt.empty_label().empty()) {
lbl += fmt.label_prefix();
if(!param.label().empty()) {
lbl += param.label();
} else {
lbl += fmt.empty_label();
}
lbl += fmt.label_postfix();
}
if(param.repeatable()) lbl += fmt.repeat_postfix();
return lbl;
}
};
/*************************************************************************//**
*
* @brief stores strings for man page sections
*
*****************************************************************************/
class man_page
{
public:
//---------------------------------------------------------------
using string = doc_string;
//---------------------------------------------------------------
/** @brief man page section */
class section {
public:
using string = doc_string;
section(string stitle, string scontent):
title_{std::move(stitle)}, content_{std::move(scontent)}
{}
const string& title() const noexcept { return title_; }
const string& content() const noexcept { return content_; }
private:
string title_;
string content_;
};
private:
using section_store = std::vector<section>;
public:
//---------------------------------------------------------------
using value_type = section;
using const_iterator = section_store::const_iterator;
using size_type = section_store::size_type;
//---------------------------------------------------------------
man_page&
append_section(string title, string content)
{
sections_.emplace_back(std::move(title), std::move(content));
return *this;
}
//-----------------------------------------------------
man_page&
prepend_section(string title, string content)
{
sections_.emplace(sections_.begin(),
std::move(title), std::move(content));
return *this;
}
//---------------------------------------------------------------
const section& operator [] (size_type index) const noexcept {
return sections_[index];
}
//---------------------------------------------------------------
size_type size() const noexcept { return sections_.size(); }
bool empty() const noexcept { return sections_.empty(); }
//---------------------------------------------------------------
const_iterator begin() const noexcept { return sections_.begin(); }
const_iterator end() const noexcept { return sections_.end(); }
//---------------------------------------------------------------
man_page& program_name(const string& n) {
progName_ = n;
return *this;
}
man_page& program_name(string&& n) {
progName_ = std::move(n);
return *this;
}
const string& program_name() const noexcept {
return progName_;
}
//---------------------------------------------------------------
man_page& section_row_spacing(int rows) {
sectionSpc_ = rows > 0 ? rows : 0;
return *this;
}
int section_row_spacing() const noexcept { return sectionSpc_; }
private:
int sectionSpc_ = 1;
section_store sections_;
string progName_;
};
/*************************************************************************//**
*
* @brief generates man sections from command line parameters
* with sections "synopsis" and "options"
*
*****************************************************************************/
inline man_page
make_man_page(const group& cli,
doc_string progname = "",
const doc_formatting& fmt = doc_formatting{})
{
man_page man;
man.append_section("SYNOPSIS", usage_lines(cli,progname,fmt).str());
man.append_section("OPTIONS", documentation(cli,fmt).str());
return man;
}
/*************************************************************************//**
*
* @brief generates man page based on command line parameters
*
*****************************************************************************/
template<class OStream>
OStream&
operator << (OStream& os, const man_page& man)
{
bool first = true;
const auto secSpc = doc_string(man.section_row_spacing() + 1, '\n');
for(const auto& section : man) {
if(!section.content().empty()) {
if(first) first = false; else os << secSpc;
if(!section.title().empty()) os << section.title() << '\n';
os << section.content();
}
}
os << '\n';
return os;
}
/*************************************************************************//**
*
* @brief printing methods for debugging command line interfaces
*
*****************************************************************************/
namespace debug {
/*************************************************************************//**
*
* @brief prints first flag or value label of a parameter
*
*****************************************************************************/
inline doc_string doc_label(const parameter& p)
{
if(!p.flags().empty()) return p.flags().front();
if(!p.label().empty()) return p.label();
return doc_string{"<?>"};
}
inline doc_string doc_label(const group&)
{
return "<group>";
}
inline doc_string doc_label(const pattern& p)
{
return p.is_group() ? doc_label(p.as_group()) : doc_label(p.as_param());
}
/*************************************************************************//**
*
* @brief prints parsing result
*
*****************************************************************************/
template<class OStream>
void print(OStream& os, const parsing_result& result)
{
for(const auto& m : result) {
os << "#" << m.index() << " " << m.arg() << " -> ";
auto p = m.param();
if(p) {
os << doc_label(*p) << " \t";
if(m.repeat() > 0) {
os << (m.bad_repeat() ? "[bad repeat " : "[repeat ")
<< m.repeat() << "]";
}
if(m.blocked()) os << " [blocked]";
if(m.conflict()) os << " [conflict]";
os << '\n';
}
else {
os << " [unmapped]\n";
}
}
for(const auto& m : result.missing()) {
auto p = m.param();
if(p) {
os << doc_label(*p) << " \t";
os << " [missing after " << m.after_index() << "]\n";
}
}
}
/*************************************************************************//**
*
* @brief prints parameter label and some properties
*
*****************************************************************************/
template<class OStream>
void print(OStream& os, const parameter& p)
{
if(p.greedy()) os << '!';
if(p.blocking()) os << '~';
if(!p.required()) os << '[';
os << doc_label(p);
if(p.repeatable()) os << "...";
if(!p.required()) os << "]";
}
//-------------------------------------------------------------------
template<class OStream>
void print(OStream& os, const group& g, int level = 0);
/*************************************************************************//**
*
* @brief prints group or parameter; uses indentation
*
*****************************************************************************/
template<class OStream>
void print(OStream& os, const pattern& param, int level = 0)
{
if(param.is_group()) {
print(os, param.as_group(), level);
}
else {
os << doc_string(4*level, ' ');
print(os, param.as_param());
}
}
/*************************************************************************//**
*
* @brief prints group and its contents; uses indentation
*
*****************************************************************************/
template<class OStream>
void print(OStream& os, const group& g, int level)
{
auto indent = doc_string(4*level, ' ');
os << indent;
if(g.blocking()) os << '~';
if(g.joinable()) os << 'J';
os << (g.exclusive() ? "(|\n" : "(\n");
for(const auto& p : g) {
print(os, p, level+1);
}
os << '\n' << indent << (g.exclusive() ? "|)" : ")");
if(g.repeatable()) os << "...";
os << '\n';
}
} // namespace debug
} //namespace clipp
#endif
1
https://gitee.com/linuxkerneltravel/lmp_cli.git
git@gitee.com:linuxkerneltravel/lmp_cli.git
linuxkerneltravel
lmp_cli
lmp_cli
main

搜索帮助