1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602 |
- /*
- Copyright 2017 Remko Popma
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package picocli;
- import java.io.*;
- import java.lang.annotation.Annotation;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- import java.lang.reflect.*;
- import java.math.BigDecimal;
- import java.math.BigInteger;
- import java.net.InetAddress;
- import java.net.MalformedURLException;
- import java.net.NetworkInterface;
- import java.net.URI;
- import java.net.URISyntaxException;
- import java.net.URL;
- import java.nio.ByteOrder;
- import java.nio.charset.Charset;
- import java.text.BreakIterator;
- import java.text.ParseException;
- import java.text.SimpleDateFormat;
- import java.util.*;
- import java.util.concurrent.Callable;
- import java.util.regex.Pattern;
- import picocli.CommandLine.Help.Ansi.IStyle;
- import picocli.CommandLine.Help.Ansi.Style;
- import picocli.CommandLine.Help.Ansi.Text;
- import picocli.CommandLine.Model.*;
- import picocli.CommandLine.ParseResult.MatchedGroup;
- import static java.util.Locale.ENGLISH;
- import static picocli.CommandLine.Help.Column.Overflow.SPAN;
- import static picocli.CommandLine.Help.Column.Overflow.TRUNCATE;
- import static picocli.CommandLine.Help.Column.Overflow.WRAP;
- /**
- * <p>
- * CommandLine interpreter that uses reflection to initialize an annotated domain object with values obtained from the
- * command line arguments.
- * </p><h2>Example</h2>
- * <pre>import static picocli.CommandLine.*;
- *
- * @Command(mixinStandardHelpOptions = true, version = "v3.0.0",
- * header = "Encrypt FILE(s), or standard input, to standard output or to the output file.")
- * public class Encrypt {
- *
- * @Parameters(type = File.class, description = "Any number of input files")
- * private List<File> files = new ArrayList<File>();
- *
- * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
- * private File outputFile;
- *
- * @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
- * private boolean[] verbose;
- * }
- * </pre>
- * <p>
- * Use {@code CommandLine} to initialize a domain object as follows:
- * </p><pre>
- * public static void main(String... args) {
- * Encrypt encrypt = new Encrypt();
- * try {
- * ParseResult parseResult = new CommandLine(encrypt).parseArgs(args);
- * if (!CommandLine.printHelpIfRequested(parseResult)) {
- * runProgram(encrypt);
- * }
- * } catch (ParameterException ex) { // command line arguments could not be parsed
- * System.err.println(ex.getMessage());
- * ex.getCommandLine().usage(System.err);
- * }
- * }
- * </pre><p>
- * Invoke the above program with some command line arguments. The below are all equivalent:
- * </p>
- * <pre>
- * --verbose --out=outfile in1 in2
- * --verbose --out outfile in1 in2
- * -v --out=outfile in1 in2
- * -v -o outfile in1 in2
- * -v -o=outfile in1 in2
- * -vo outfile in1 in2
- * -vo=outfile in1 in2
- * -v -ooutfile in1 in2
- * -vooutfile in1 in2
- * </pre>
- * <p>
- * Another example that implements {@code Callable} and uses the {@link #call(Callable, String...) CommandLine.call} convenience API to run in a single line of code:
- * </p>
- * <pre>
- * @Command(description = "Prints the checksum (MD5 by default) of a file to STDOUT.",
- * name = "checksum", mixinStandardHelpOptions = true, version = "checksum 3.0")
- * class CheckSum implements Callable<Void> {
- *
- * @Parameters(index = "0", description = "The file whose checksum to calculate.")
- * private File file;
- *
- * @Option(names = {"-a", "--algorithm"}, description = "MD5, SHA-1, SHA-256, ...")
- * private String algorithm = "MD5";
- *
- * public static void main(String[] args) throws Exception {
- * // CheckSum implements Callable, so parsing, error handling and handling user
- * // requests for usage help or version help can be done with one line of code.
- * CommandLine.call(new CheckSum(), args);
- * }
- *
- * @Override
- * public Void call() throws Exception {
- * // your business logic goes here...
- * byte[] fileContents = Files.readAllBytes(file.toPath());
- * byte[] digest = MessageDigest.getInstance(algorithm).digest(fileContents);
- * System.out.println(javax.xml.bind.DatatypeConverter.printHexBinary(digest));
- * return null;
- * }
- * }
- * </pre>
- * <h2>Classes and Interfaces for Defining a CommandSpec Model</h2>
- * <p>
- * <img src="doc-files/class-diagram-definition.png" alt="Classes and Interfaces for Defining a CommandSpec Model">
- * </p>
- * <h2>Classes Related to Parsing Command Line Arguments</h2>
- * <p>
- * <img src="doc-files/class-diagram-parsing.png" alt="Classes Related to Parsing Command Line Arguments">
- * </p>
- */
- public class CommandLine {
- /** This is picocli version {@value}. */
- public static final String VERSION = "4.0.0-alpha-2-SNAPSHOT";
- private final Tracer tracer = new Tracer();
- private final CommandSpec commandSpec;
- private final Interpreter interpreter;
- private final IFactory factory;
- /**
- * Constructs a new {@code CommandLine} interpreter with the specified object (which may be an annotated user object or a {@link CommandSpec CommandSpec}) and a default subcommand factory.
- * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated
- * user object with {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically
- * constructs a {@code CommandSpec} from this user object.
- * </p><p>
- * When the {@link #parse(String...)} method is called, the {@link CommandSpec CommandSpec} object will be
- * initialized based on command line arguments. If the commandSpec is created from an annotated user object, this
- * user object will be initialized based on the command line arguments.</p>
- * @param command an annotated user object or a {@code CommandSpec} object to initialize from the command line arguments
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- */
- public CommandLine(Object command) {
- this(command, new DefaultFactory());
- }
- /**
- * Constructs a new {@code CommandLine} interpreter with the specified object (which may be an annotated user object or a {@link CommandSpec CommandSpec}) and object factory.
- * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated
- * user object with {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically
- * constructs a {@code CommandSpec} from this user object.
- * </p><p> If the specified command object is an interface {@code Class} with {@code @Option} and {@code @Parameters}-annotated methods,
- * picocli creates a {@link java.lang.reflect.Proxy Proxy} whose methods return the matched command line values.
- * If the specified command object is a concrete {@code Class}, picocli delegates to the {@linkplain IFactory factory} to get an instance.
- * </p><p>
- * When the {@link #parse(String...)} method is called, the {@link CommandSpec CommandSpec} object will be
- * initialized based on command line arguments. If the commandSpec is created from an annotated user object, this
- * user object will be initialized based on the command line arguments.</p>
- * @param command an annotated user object or a {@code CommandSpec} object to initialize from the command line arguments
- * @param factory the factory used to create instances of {@linkplain Command#subcommands() subcommands}, {@linkplain Option#converter() converters}, etc., that are registered declaratively with annotation attributes
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @since 2.2 */
- public CommandLine(Object command, IFactory factory) {
- this.factory = Assert.notNull(factory, "factory");
- interpreter = new Interpreter();
- commandSpec = CommandSpec.forAnnotatedObject(command, factory);
- commandSpec.commandLine(this);
- commandSpec.validate();
- if (commandSpec.unmatchedArgsBindings().size() > 0) { setUnmatchedArgumentsAllowed(true); }
- }
- /**
- * Returns the {@code CommandSpec} model that this {@code CommandLine} was constructed with.
- * @return the {@code CommandSpec} model
- * @since 3.0 */
- public CommandSpec getCommandSpec() { return commandSpec; }
- /**
- * Adds the options and positional parameters in the specified mixin to this command.
- * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a user object with
- * {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically
- * constructs a {@code CommandSpec} from this user object.
- * </p>
- * @param name the name by which the mixin object may later be retrieved
- * @param mixin an annotated user object or a {@link CommandSpec CommandSpec} object whose options and positional parameters to add to this command
- * @return this CommandLine object, to allow method chaining
- * @since 3.0 */
- public CommandLine addMixin(String name, Object mixin) {
- getCommandSpec().addMixin(name, CommandSpec.forAnnotatedObject(mixin, factory));
- return this;
- }
- /**
- * Returns a map of user objects whose options and positional parameters were added to ("mixed in" with) this command.
- * @return a new Map containing the user objects mixed in with this command. If {@code CommandSpec} objects without
- * user objects were programmatically added, use the {@link CommandSpec#mixins() underlying model} directly.
- * @since 3.0 */
- public Map<String, Object> getMixins() {
- Map<String, CommandSpec> mixins = getCommandSpec().mixins();
- Map<String, Object> result = new LinkedHashMap<String, Object>();
- for (String name : mixins.keySet()) { result.put(name, mixins.get(name).userObject); }
- return result;
- }
- /** Registers a subcommand with the specified name. For example:
- * <pre>
- * CommandLine commandLine = new CommandLine(new Git())
- * .addSubcommand("status", new GitStatus())
- * .addSubcommand("commit", new GitCommit();
- * .addSubcommand("add", new GitAdd())
- * .addSubcommand("branch", new GitBranch())
- * .addSubcommand("checkout", new GitCheckout())
- * //...
- * ;
- * </pre>
- *
- * <p>The specified object can be an annotated object or a
- * {@code CommandLine} instance with its own nested subcommands. For example:</p>
- * <pre>
- * CommandLine commandLine = new CommandLine(new MainCommand())
- * .addSubcommand("cmd1", new ChildCommand1()) // subcommand
- * .addSubcommand("cmd2", new ChildCommand2())
- * .addSubcommand("cmd3", new CommandLine(new ChildCommand3()) // subcommand with nested sub-subcommands
- * .addSubcommand("cmd3sub1", new GrandChild3Command1())
- * .addSubcommand("cmd3sub2", new GrandChild3Command2())
- * .addSubcommand("cmd3sub3", new CommandLine(new GrandChild3Command3()) // deeper nesting
- * .addSubcommand("cmd3sub3sub1", new GreatGrandChild3Command3_1())
- * .addSubcommand("cmd3sub3sub2", new GreatGrandChild3Command3_2())
- * )
- * );
- * </pre>
- * <p>The default type converters are available on all subcommands and nested sub-subcommands, but custom type
- * converters are registered only with the subcommand hierarchy as it existed when the custom type was registered.
- * To ensure a custom type converter is available to all subcommands, register the type converter last, after
- * adding subcommands.</p>
- * <p>See also the {@link Command#subcommands()} annotation to register subcommands declaratively.</p>
- *
- * @param name the string to recognize on the command line as a subcommand
- * @param command the object to initialize with command line arguments following the subcommand name.
- * This may be a {@code CommandLine} instance with its own (nested) subcommands
- * @return this CommandLine object, to allow method chaining
- * @see #registerConverter(Class, ITypeConverter)
- * @since 0.9.7
- * @see Command#subcommands()
- */
- public CommandLine addSubcommand(String name, Object command) {
- return addSubcommand(name, command, new String[0]);
- }
- /** Registers a subcommand with the specified name and all specified aliases. See also {@link #addSubcommand(String, Object)}.
- *
- *
- * @param name the string to recognize on the command line as a subcommand
- * @param command the object to initialize with command line arguments following the subcommand name.
- * This may be a {@code CommandLine} instance with its own (nested) subcommands
- * @param aliases zero or more alias names that are also recognized on the command line as this subcommand
- * @return this CommandLine object, to allow method chaining
- * @since 3.1
- * @see #addSubcommand(String, Object)
- */
- public CommandLine addSubcommand(String name, Object command, String... aliases) {
- CommandLine subcommandLine = toCommandLine(command, factory);
- subcommandLine.getCommandSpec().aliases.addAll(Arrays.asList(aliases));
- getCommandSpec().addSubcommand(name, subcommandLine);
- CommandLine.Model.CommandReflection.initParentCommand(subcommandLine.getCommandSpec().userObject(), getCommandSpec().userObject());
- return this;
- }
- /** Returns a map with the subcommands {@linkplain #addSubcommand(String, Object) registered} on this instance.
- * @return a map with the registered subcommands
- * @since 0.9.7
- */
- public Map<String, CommandLine> getSubcommands() {
- return new LinkedHashMap<String, CommandLine>(getCommandSpec().subcommands());
- }
- /**
- * Returns the command that this is a subcommand of, or {@code null} if this is a top-level command.
- * @return the command that this is a subcommand of, or {@code null} if this is a top-level command
- * @see #addSubcommand(String, Object)
- * @see Command#subcommands()
- * @since 0.9.8
- */
- public CommandLine getParent() {
- CommandSpec parent = getCommandSpec().parent();
- return parent == null ? null : parent.commandLine();
- }
- /** Returns the annotated user object that this {@code CommandLine} instance was constructed with.
- * @param <T> the type of the variable that the return value is being assigned to
- * @return the annotated object that this {@code CommandLine} instance was constructed with
- * @since 0.9.7
- */
- @SuppressWarnings("unchecked")
- public <T> T getCommand() {
- return (T) getCommandSpec().userObject();
- }
- /** Returns {@code true} if an option annotated with {@link Option#usageHelp()} was specified on the command line.
- * @return whether the parser encountered an option annotated with {@link Option#usageHelp()}.
- * @since 0.9.8 */
- public boolean isUsageHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.usageHelpRequested; }
- /** Returns {@code true} if an option annotated with {@link Option#versionHelp()} was specified on the command line.
- * @return whether the parser encountered an option annotated with {@link Option#versionHelp()}.
- * @since 0.9.8 */
- public boolean isVersionHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.versionHelpRequested; }
- /** Returns the {@code IHelpFactory} that is used to construct the usage help message.
- * @see #setHelpFactory(IHelpFactory)
- * @since 3.9
- */
- public IHelpFactory getHelpFactory() {
- return getCommandSpec().usageMessage().helpFactory();
- }
- /** Sets a new {@code IHelpFactory} to customize the usage help message.
- * @param helpFactory the new help factory. Must be non-{@code null}.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.9
- */
- public CommandLine setHelpFactory(IHelpFactory helpFactory) {
- getCommandSpec().usageMessage().helpFactory(helpFactory);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setHelpFactory(helpFactory);
- }
- return this;
- }
- /**
- * Returns the section keys in the order that the usage help message should render the sections.
- * This ordering may be modified with {@link #setHelpSectionKeys(List) setSectionKeys}. The default keys are (in order):
- * <ol>
- * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER_HEADING SECTION_KEY_HEADER_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER SECTION_KEY_HEADER}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS_HEADING SECTION_KEY_SYNOPSIS_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS SECTION_KEY_SYNOPSIS}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION_HEADING SECTION_KEY_DESCRIPTION_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION SECTION_KEY_DESCRIPTION}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST_HEADING SECTION_KEY_PARAMETER_LIST_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST SECTION_KEY_PARAMETER_LIST}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST_HEADING SECTION_KEY_OPTION_LIST_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST SECTION_KEY_OPTION_LIST}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST_HEADING SECTION_KEY_COMMAND_LIST_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST SECTION_KEY_COMMAND_LIST}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER_HEADING SECTION_KEY_FOOTER_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER SECTION_KEY_FOOTER}</li>
- * </ol>
- * @since 3.9
- */
- public List<String> getHelpSectionKeys() { return getCommandSpec().usageMessage().sectionKeys(); }
- /**
- * Sets the section keys in the order that the usage help message should render the sections.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * <p>Use {@link UsageMessageSpec#sectionKeys(List)} to customize a command without affecting its subcommands.</p>
- * @see #getHelpSectionKeys
- * @since 3.9
- */
- public CommandLine setHelpSectionKeys(List<String> keys) {
- getCommandSpec().usageMessage().sectionKeys(keys);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setHelpSectionKeys(keys);
- }
- return this;
- }
- /**
- * Returns the map of section keys and renderers used to construct the usage help message.
- * The usage help message can be customized by adding, replacing and removing section renderers from this map.
- * Sections can be reordered with {@link #setHelpSectionKeys(List) setSectionKeys}.
- * Sections that are either not in this map or not in the list returned by {@link #getHelpSectionKeys() getSectionKeys} are omitted.
- * <p>
- * NOTE: By modifying the returned {@code Map}, only the usage help message <em>of this command</em> is affected.
- * Use {@link #setHelpSectionMap(Map)} to customize the usage help message for this command <em>and all subcommands</em>.
- * </p>
- * @since 3.9
- */
- public Map<String, IHelpSectionRenderer> getHelpSectionMap() { return getCommandSpec().usageMessage().sectionMap(); }
- /**
- * Sets the map of section keys and renderers used to construct the usage help message.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * <p>Use {@link UsageMessageSpec#sectionMap(Map)} to customize a command without affecting its subcommands.</p>
- * @see #getHelpSectionMap
- * @since 3.9
- */
- public CommandLine setHelpSectionMap(Map<String, IHelpSectionRenderer> map) {
- getCommandSpec().usageMessage().sectionMap(map);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setHelpSectionMap(map);
- }
- return this;
- }
- /** Returns whether the value of boolean flag options should be "toggled" when the option is matched.
- * By default, flags are toggled, so if the value is {@code true} it is set to {@code false}, and when the value is
- * {@code false} it is set to {@code true}. If toggling is off, flags are simply set to {@code true}.
- * @return {@code true} the value of boolean flag options should be "toggled" when the option is matched, {@code false} otherwise
- * @since 3.0
- */
- public boolean isToggleBooleanFlags() {
- return getCommandSpec().parser().toggleBooleanFlags();
- }
- /** Sets whether the value of boolean flag options should be "toggled" when the option is matched. The default is {@code true}.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue the new setting
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.0
- */
- public CommandLine setToggleBooleanFlags(boolean newValue) {
- getCommandSpec().parser().toggleBooleanFlags(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setToggleBooleanFlags(newValue);
- }
- return this;
- }
- /** Returns whether options for single-value fields can be specified multiple times on the command line.
- * The default is {@code false} and a {@link OverwrittenOptionException} is thrown if this happens.
- * When {@code true}, the last specified value is retained.
- * @return {@code true} if options for single-value fields can be specified multiple times on the command line, {@code false} otherwise
- * @since 0.9.7
- */
- public boolean isOverwrittenOptionsAllowed() {
- return getCommandSpec().parser().overwrittenOptionsAllowed();
- }
- /** Sets whether options for single-value fields can be specified multiple times on the command line without a {@link OverwrittenOptionException} being thrown.
- * The default is {@code false}.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue the new setting
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 0.9.7
- */
- public CommandLine setOverwrittenOptionsAllowed(boolean newValue) {
- getCommandSpec().parser().overwrittenOptionsAllowed(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setOverwrittenOptionsAllowed(newValue);
- }
- return this;
- }
- /** Returns whether the parser accepts clustered short options. The default is {@code true}.
- * @return {@code true} if short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}, {@code false} otherwise
- * @since 3.0 */
- public boolean isPosixClusteredShortOptionsAllowed() { return getCommandSpec().parser().posixClusteredShortOptionsAllowed(); }
- /** Sets whether short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}. The default is {@code true}.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue the new setting
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.0
- */
- public CommandLine setPosixClusteredShortOptionsAllowed(boolean newValue) {
- getCommandSpec().parser().posixClusteredShortOptionsAllowed(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setPosixClusteredShortOptionsAllowed(newValue);
- }
- return this;
- }
- /** Returns whether the parser should ignore case when converting arguments to {@code enum} values. The default is {@code false}.
- * @return {@code true} if enum values can be specified that don't match the {@code toString()} value of the enum constant, {@code false} otherwise;
- * e.g., for an option of type <a href="https://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html">java.time.DayOfWeek</a>,
- * values {@code MonDaY}, {@code monday} and {@code MONDAY} are all recognized if {@code true}.
- * @since 3.4 */
- public boolean isCaseInsensitiveEnumValuesAllowed() { return getCommandSpec().parser().caseInsensitiveEnumValuesAllowed(); }
- /** Sets whether the parser should ignore case when converting arguments to {@code enum} values. The default is {@code false}.
- * When set to true, for example, for an option of type <a href="https://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html">java.time.DayOfWeek</a>,
- * values {@code MonDaY}, {@code monday} and {@code MONDAY} are all recognized if {@code true}.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue the new setting
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.4
- */
- public CommandLine setCaseInsensitiveEnumValuesAllowed(boolean newValue) {
- getCommandSpec().parser().caseInsensitiveEnumValuesAllowed(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setCaseInsensitiveEnumValuesAllowed(newValue);
- }
- return this;
- }
- /** Returns whether the parser should trim quotes from command line arguments before processing them. The default is
- * read from the system property "picocli.trimQuotes" and will be {@code true} if the property is present and empty,
- * or if its value is "true".
- * @return {@code true} if the parser should trim quotes from command line arguments before processing them, {@code false} otherwise;
- * @since 3.7 */
- public boolean isTrimQuotes() { return getCommandSpec().parser().trimQuotes(); }
- /** Sets whether the parser should trim quotes from command line arguments before processing them. The default is
- * read from the system property "picocli.trimQuotes" and will be {@code true} if the property is set and empty, or
- * if its value is "true".
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * <p>Calling this method will cause the "picocli.trimQuotes" property to have no effect.</p>
- * @param newValue the new setting
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.7
- */
- public CommandLine setTrimQuotes(boolean newValue) {
- getCommandSpec().parser().trimQuotes(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setTrimQuotes(newValue);
- }
- return this;
- }
- /** Returns whether the parser is allowed to split quoted Strings or not. The default is {@code false},
- * so quoted strings are treated as a single value that cannot be split.
- * @return {@code true} if the parser is allowed to split quoted Strings, {@code false} otherwise;
- * @see ArgSpec#splitRegex()
- * @since 3.7 */
- public boolean isSplitQuotedStrings() { return getCommandSpec().parser().splitQuotedStrings(); }
- /** Sets whether the parser is allowed to split quoted Strings. The default is {@code false}.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue the new setting
- * @return this {@code CommandLine} object, to allow method chaining
- * @see ArgSpec#splitRegex()
- * @since 3.7
- */
- public CommandLine setSplitQuotedStrings(boolean newValue) {
- getCommandSpec().parser().splitQuotedStrings(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setSplitQuotedStrings(newValue);
- }
- return this;
- }
- /** Returns the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters.
- * @return the end-of-options delimiter. The default is {@code "--"}.
- * @since 3.5 */
- public String getEndOfOptionsDelimiter() { return getCommandSpec().parser().endOfOptionsDelimiter(); }
- /** Sets the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters.
- * @param delimiter the end-of-options delimiter; must not be {@code null}. The default is {@code "--"}.
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.5 */
- public CommandLine setEndOfOptionsDelimiter(String delimiter) {
- getCommandSpec().parser().endOfOptionsDelimiter(delimiter);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setEndOfOptionsDelimiter(delimiter);
- }
- return this;
- }
- /** Returns the default value provider for the command, or {@code null} if none has been set.
- * @return the default value provider for this command, or {@code null}
- * @since 3.6
- * @see Command#defaultValueProvider()
- * @see CommandSpec#defaultValueProvider()
- * @see ArgSpec#defaultValueString()
- */
- public IDefaultValueProvider getDefaultValueProvider() {
- return getCommandSpec().defaultValueProvider();
- }
- /** Sets a default value provider for the command and sub-commands
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * sub-commands and nested sub-subcommands <em>at the moment this method is called</em>. Sub-commands added
- * later will have the default setting. To ensure a setting is applied to all
- * sub-commands, call the setter last, after adding sub-commands.</p>
- * @param newValue the default value provider to use
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.6
- */
- public CommandLine setDefaultValueProvider(IDefaultValueProvider newValue) {
- getCommandSpec().defaultValueProvider(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setDefaultValueProvider(newValue);
- }
- return this;
- }
- /** Returns whether the parser interprets the first positional parameter as "end of options" so the remaining
- * arguments are all treated as positional parameters. The default is {@code false}.
- * @return {@code true} if all values following the first positional parameter should be treated as positional parameters, {@code false} otherwise
- * @since 2.3
- */
- public boolean isStopAtPositional() {
- return getCommandSpec().parser().stopAtPositional();
- }
- /** Sets whether the parser interprets the first positional parameter as "end of options" so the remaining
- * arguments are all treated as positional parameters. The default is {@code false}.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue {@code true} if all values following the first positional parameter should be treated as positional parameters, {@code false} otherwise
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 2.3
- */
- public CommandLine setStopAtPositional(boolean newValue) {
- getCommandSpec().parser().stopAtPositional(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setStopAtPositional(newValue);
- }
- return this;
- }
- /** Returns whether the parser should stop interpreting options and positional parameters as soon as it encounters an
- * unmatched option. Unmatched options are arguments that look like an option but are not one of the known options, or
- * positional arguments for which there is no available slots (the command has no positional parameters or their size is limited).
- * The default is {@code false}.
- * <p>Setting this flag to {@code true} automatically sets the {@linkplain #isUnmatchedArgumentsAllowed() unmatchedArgumentsAllowed} flag to {@code true} also.</p>
- * @return {@code true} when an unmatched option should result in the remaining command line arguments to be added to the
- * {@linkplain #getUnmatchedArguments() unmatchedArguments list}
- * @since 2.3
- */
- public boolean isStopAtUnmatched() {
- return getCommandSpec().parser().stopAtUnmatched();
- }
- /** Sets whether the parser should stop interpreting options and positional parameters as soon as it encounters an
- * unmatched option. Unmatched options are arguments that look like an option but are not one of the known options, or
- * positional arguments for which there is no available slots (the command has no positional parameters or their size is limited).
- * The default is {@code false}.
- * <p>Setting this flag to {@code true} automatically sets the {@linkplain #setUnmatchedArgumentsAllowed(boolean) unmatchedArgumentsAllowed} flag to {@code true} also.</p>
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue {@code true} when an unmatched option should result in the remaining command line arguments to be added to the
- * {@linkplain #getUnmatchedArguments() unmatchedArguments list}
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 2.3
- */
- public CommandLine setStopAtUnmatched(boolean newValue) {
- getCommandSpec().parser().stopAtUnmatched(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setStopAtUnmatched(newValue);
- }
- if (newValue) { setUnmatchedArgumentsAllowed(true); }
- return this;
- }
- /** Returns whether arguments on the command line that resemble an option should be treated as positional parameters.
- * The default is {@code false} and the parser behaviour depends on {@link #isUnmatchedArgumentsAllowed()}.
- * @return {@code true} arguments on the command line that resemble an option should be treated as positional parameters, {@code false} otherwise
- * @see #getUnmatchedArguments()
- * @since 3.0
- */
- public boolean isUnmatchedOptionsArePositionalParams() {
- return getCommandSpec().parser().unmatchedOptionsArePositionalParams();
- }
- /** Sets whether arguments on the command line that resemble an option should be treated as positional parameters.
- * The default is {@code false}.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue the new setting. When {@code true}, arguments on the command line that resemble an option should be treated as positional parameters.
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.0
- * @see #getUnmatchedArguments()
- * @see #isUnmatchedArgumentsAllowed
- */
- public CommandLine setUnmatchedOptionsArePositionalParams(boolean newValue) {
- getCommandSpec().parser().unmatchedOptionsArePositionalParams(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setUnmatchedOptionsArePositionalParams(newValue);
- }
- return this;
- }
- /** Returns whether the end user may specify arguments on the command line that are not matched to any option or parameter fields.
- * The default is {@code false} and a {@link UnmatchedArgumentException} is thrown if this happens.
- * When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
- * @return {@code true} if the end use may specify unmatched arguments on the command line, {@code false} otherwise
- * @see #getUnmatchedArguments()
- * @since 0.9.7
- */
- public boolean isUnmatchedArgumentsAllowed() {
- return getCommandSpec().parser().unmatchedArgumentsAllowed();
- }
- /** Sets whether the end user may specify unmatched arguments on the command line without a {@link UnmatchedArgumentException} being thrown.
- * The default is {@code false}.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue the new setting. When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 0.9.7
- * @see #getUnmatchedArguments()
- */
- public CommandLine setUnmatchedArgumentsAllowed(boolean newValue) {
- getCommandSpec().parser().unmatchedArgumentsAllowed(newValue);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setUnmatchedArgumentsAllowed(newValue);
- }
- return this;
- }
- /** Returns the list of unmatched command line arguments, if any.
- * @return the list of unmatched command line arguments or an empty list
- * @see #isUnmatchedArgumentsAllowed()
- * @since 0.9.7
- */
- public List<String> getUnmatchedArguments() {
- return interpreter.parseResultBuilder == null ? Collections.<String>emptyList() : UnmatchedArgumentException.stripErrorMessage(interpreter.parseResultBuilder.unmatched);
- }
- /**
- * <p>
- * Convenience method that initializes the specified annotated object from the specified command line arguments.
- * </p><p>
- * This is equivalent to
- * </p><pre>
- * CommandLine cli = new CommandLine(command);
- * cli.parse(args);
- * return command;
- * </pre>
- *
- * @param command the object to initialize. This object contains fields annotated with
- * {@code @Option} or {@code @Parameters}.
- * @param args the command line arguments to parse
- * @param <T> the type of the annotated object
- * @return the specified annotated object
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ParameterException if the specified command line arguments are invalid
- * @since 0.9.7
- */
- public static <T> T populateCommand(T command, String... args) {
- CommandLine cli = toCommandLine(command, new DefaultFactory());
- cli.parse(args);
- return command;
- }
- /**
- * <p>
- * Convenience method that derives the command specification from the specified interface class, and returns an
- * instance of the specified interface. The interface is expected to have annotated getter methods. Picocli will
- * instantiate the interface and the getter methods will return the option and positional parameter values matched on the command line.
- * </p><p>
- * This is equivalent to
- * </p><pre>
- * CommandLine cli = new CommandLine(spec);
- * cli.parse(args);
- * return cli.getCommand();
- * </pre>
- *
- * @param spec the interface that defines the command specification. This object contains getter methods annotated with
- * {@code @Option} or {@code @Parameters}.
- * @param args the command line arguments to parse
- * @param <T> the type of the annotated object
- * @return an instance of the specified annotated interface
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ParameterException if the specified command line arguments are invalid
- * @since 3.1
- */
- public static <T> T populateSpec(Class<T> spec, String... args) {
- CommandLine cli = toCommandLine(spec, new DefaultFactory());
- cli.parse(args);
- return cli.getCommand();
- }
- /** Parses the specified command line arguments and returns a list of {@code CommandLine} objects representing the
- * top-level command and any subcommands (if any) that were recognized and initialized during the parsing process.
- * <p>
- * If parsing succeeds, the first element in the returned list is always {@code this CommandLine} object. The
- * returned list may contain more elements if subcommands were {@linkplain #addSubcommand(String, Object) registered}
- * and these subcommands were initialized by matching command line arguments. If parsing fails, a
- * {@link ParameterException} is thrown.
- * </p>
- *
- * @param args the command line arguments to parse
- * @return a list with the top-level command and any subcommands initialized by this method
- * @throws ParameterException if the specified command line arguments are invalid; use
- * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid
- */
- public List<CommandLine> parse(String... args) {
- return interpreter.parse(args);
- }
- /** Parses the specified command line arguments and returns a list of {@code ParseResult} with the options, positional
- * parameters, and subcommands (if any) that were recognized and initialized during the parsing process.
- * <p>If parsing fails, a {@link ParameterException} is thrown.</p>
- *
- * @param args the command line arguments to parse
- * @return a list with the top-level command and any subcommands initialized by this method
- * @throws ParameterException if the specified command line arguments are invalid; use
- * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid
- */
- public ParseResult parseArgs(String... args) {
- interpreter.parse(args);
- return getParseResult();
- }
- public ParseResult getParseResult() { return interpreter.parseResultBuilder == null ? null : interpreter.parseResultBuilder.build(); }
- /**
- * Represents a function that can process a List of {@code CommandLine} objects resulting from successfully
- * {@linkplain #parse(String...) parsing} the command line arguments. This is a
- * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
- * whose functional method is {@link #handleParseResult(List, PrintStream, CommandLine.Help.Ansi)}.
- * <p>
- * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler}
- * methods to take some next step after the command line was successfully parsed.
- * </p>
- * @see RunFirst
- * @see RunLast
- * @see RunAll
- * @deprecated Use {@link IParseResultHandler2} instead.
- * @since 2.0 */
- @Deprecated public static interface IParseResultHandler {
- /** Processes a List of {@code CommandLine} objects resulting from successfully
- * {@linkplain #parse(String...) parsing} the command line arguments and optionally returns a list of results.
- * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @return a list of results, or an empty list if there are no results
- * @throws ParameterException if a help command was invoked for an unknown subcommand. Any {@code ParameterExceptions}
- * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) throws ExecutionException;
- }
- /**
- * Represents a function that can process the {@code ParseResult} object resulting from successfully
- * {@linkplain #parseArgs(String...) parsing} the command line arguments. This is a
- * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
- * whose functional method is {@link IParseResultHandler2#handleParseResult(CommandLine.ParseResult)}.
- * <p>
- * Implementations of this function can be passed to the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) CommandLine::parseWithHandlers}
- * methods to take some next step after the command line was successfully parsed.
- * </p><p>
- * This interface replaces the {@link IParseResultHandler} interface; it takes the parse result as a {@code ParseResult}
- * object instead of a List of {@code CommandLine} objects, and it has the freedom to select the {@link Help.Ansi} style
- * to use and what {@code PrintStreams} to print to.
- * </p>
- * @param <R> the return type of this handler
- * @see RunFirst
- * @see RunLast
- * @see RunAll
- * @since 3.0 */
- public static interface IParseResultHandler2<R> {
- /** Processes the {@code ParseResult} object resulting from successfully
- * {@linkplain CommandLine#parseArgs(String...) parsing} the command line arguments and returns a return value.
- * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
- * @throws ParameterException if a help command was invoked for an unknown subcommand. Any {@code ParameterExceptions}
- * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler2}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- R handleParseResult(ParseResult parseResult) throws ExecutionException;
- }
- /**
- * Represents a function that can handle a {@code ParameterException} that occurred while
- * {@linkplain #parse(String...) parsing} the command line arguments. This is a
- * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
- * whose functional method is {@link #handleException(CommandLine.ParameterException, PrintStream, CommandLine.Help.Ansi, String...)}.
- * <p>
- * Implementations of this function can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandlers}
- * methods to handle situations when the command line could not be parsed.
- * </p>
- * @deprecated Use {@link IExceptionHandler2} instead.
- * @see DefaultExceptionHandler
- * @since 2.0 */
- @Deprecated public static interface IExceptionHandler {
- /** Handles a {@code ParameterException} that occurred while {@linkplain #parse(String...) parsing} the command
- * line arguments and optionally returns a list of results.
- * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
- * and the CommandLine representing the command or subcommand whose input was invalid
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @param args the command line arguments that could not be parsed
- * @return a list of results, or an empty list if there are no results
- */
- List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args);
- }
- /**
- * Classes implementing this interface know how to handle {@code ParameterExceptions} (usually from invalid user input)
- * and {@code ExecutionExceptions} that occurred while executing the {@code Runnable} or {@code Callable} command.
- * <p>
- * Implementations of this interface can be passed to the
- * {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) CommandLine::parseWithHandlers} method.
- * </p><p>
- * This interface replaces the {@link IParseResultHandler} interface.
- * </p>
- * @param <R> the return type of this handler
- * @see DefaultExceptionHandler
- * @since 3.0 */
- public static interface IExceptionHandler2<R> {
- /** Handles a {@code ParameterException} that occurred while {@linkplain #parseArgs(String...) parsing} the command
- * line arguments and optionally returns a list of results.
- * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
- * and the CommandLine representing the command or subcommand whose input was invalid
- * @param args the command line arguments that could not be parsed
- * @return an object resulting from handling the exception
- */
- R handleParseException(ParameterException ex, String[] args);
- /** Handles a {@code ExecutionException} that occurred while executing the {@code Runnable} or
- * {@code Callable} command and optionally returns a list of results.
- * @param ex the ExecutionException describing the problem that occurred while executing the {@code Runnable} or
- * {@code Callable} command, and the CommandLine representing the command or subcommand that was being executed
- * @param parseResult the result of parsing the command line arguments
- * @return an object resulting from handling the exception
- */
- R handleExecutionException(ExecutionException ex, ParseResult parseResult);
- }
- /** Abstract superclass for {@link IParseResultHandler2} and {@link IExceptionHandler2} implementations.
- * <p>Note that {@code AbstractHandler} is a generic type. This, along with the abstract {@code self} method,
- * allows method chaining to work properly in subclasses, without the need for casts. An example subclass can look like this:</p>
- * <pre>{@code
- * class MyResultHandler extends AbstractHandler<MyReturnType, MyResultHandler> implements IParseResultHandler2<MyReturnType> {
- *
- * public MyReturnType handleParseResult(ParseResult parseResult) { ... }
- *
- * protected MyResultHandler self() { return this; }
- * }
- * }</pre>
- * @param <R> the return type of this handler
- * @param <T> The type of the handler subclass; for fluent API method chaining
- * @since 3.0 */
- public static abstract class AbstractHandler<R, T extends AbstractHandler<R, T>> {
- private Help.Ansi ansi = Help.Ansi.AUTO;
- private Integer exitCode;
- private PrintStream out = System.out;
- private PrintStream err = System.err;
- /** Returns the stream to print command output to. Defaults to {@code System.out}, unless {@link #useOut(PrintStream)}
- * was called with a different stream.
- * <p>{@code IParseResultHandler2} implementations should use this stream.
- * By <a href="http://www.gnu.org/prep/standards/html_node/_002d_002dhelp.html">convention</a>, when the user requests
- * help with a {@code --help} or similar option, the usage help message is printed to the standard output stream so that it can be easily searched and paged.</p> */
- public PrintStream out() { return out; }
- /** Returns the stream to print diagnostic messages to. Defaults to {@code System.err}, unless {@link #useErr(PrintStream)}
- * was called with a different stream. <p>{@code IExceptionHandler2} implementations should use this stream to print error
- * messages (which may include a usage help message) when an unexpected error occurs.</p> */
- public PrintStream err() { return err; }
- /** Returns the ANSI style to use. Defaults to {@code Help.Ansi.AUTO}, unless {@link #useAnsi(CommandLine.Help.Ansi)} was called with a different setting. */
- public Help.Ansi ansi() { return ansi; }
- /** Returns the exit code to use as the termination status, or {@code null} (the default) if the handler should
- * not call {@link System#exit(int)} after processing completes.
- * @see #andExit(int) */
- public Integer exitCode() { return exitCode; }
- /** Returns {@code true} if an exit code was set with {@link #andExit(int)}, or {@code false} (the default) if
- * the handler should not call {@link System#exit(int)} after processing completes. */
- public boolean hasExitCode() { return exitCode != null; }
- /** Convenience method for subclasses that returns the specified result object if no exit code was set,
- * or otherwise, if an exit code {@linkplain #andExit(int) was set}, calls {@code System.exit} with the configured
- * exit code to terminate the currently running Java virtual machine. */
- protected R returnResultOrExit(R result) {
- if (hasExitCode()) { exit(exitCode()); }
- return result;
- }
- /** Convenience method for subclasses that throws the specified ExecutionException if no exit code was set,
- * or otherwise, if an exit code {@linkplain #andExit(int) was set}, prints the stacktrace of the specified exception
- * to the diagnostic error stream and calls {@code System.exit} with the configured
- * exit code to terminate the currently running Java virtual machine. */
- protected R throwOrExit(ExecutionException ex) {
- if (hasExitCode()) {
- ex.printStackTrace(this.err());
- exit(exitCode());
- }
- throw ex;
- }
- /** Calls {@code System.exit(int)} with the specified exit code. */
- protected void exit(int exitCode) { System.exit(exitCode); }
- /** Returns {@code this} to allow method chaining when calling the setters for a fluent API. */
- protected abstract T self();
- /** Sets the stream to print command output to. For use by {@code IParseResultHandler2} implementations.
- * @see #out() */
- public T useOut(PrintStream out) { this.out = Assert.notNull(out, "out"); return self(); }
- /** Sets the stream to print diagnostic messages to. For use by {@code IExceptionHandler2} implementations.
- * @see #err()*/
- public T useErr(PrintStream err) { this.err = Assert.notNull(err, "err"); return self(); }
- /** Sets the ANSI style to use.
- * @see #ansi() */
- public T useAnsi(Help.Ansi ansi) { this.ansi = Assert.notNull(ansi, "ansi"); return self(); }
- /** Indicates that the handler should call {@link System#exit(int)} after processing completes and sets the exit code to use as the termination status. */
- public T andExit(int exitCode) { this.exitCode = exitCode; return self(); }
- }
- /**
- * Default exception handler that handles invalid user input by printing the exception message, followed by the usage
- * message for the command or subcommand whose input was invalid.
- * <p>{@code ParameterExceptions} (invalid user input) is handled like this:</p>
- * <pre>
- * err().println(paramException.getMessage());
- * paramException.getCommandLine().usage(err(), ansi());
- * if (hasExitCode()) System.exit(exitCode()); else return returnValue;
- * </pre>
- * <p>{@code ExecutionExceptions} that occurred while executing the {@code Runnable} or {@code Callable} command are simply rethrown and not handled.</p>
- * @since 2.0 */
- @SuppressWarnings("deprecation")
- public static class DefaultExceptionHandler<R> extends AbstractHandler<R, DefaultExceptionHandler<R>> implements IExceptionHandler, IExceptionHandler2<R> {
- public List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args) {
- internalHandleParseException(ex, out, ansi, args); return Collections.<Object>emptyList(); }
- /** Prints the message of the specified exception, followed by the usage message for the command or subcommand
- * whose input was invalid, to the stream returned by {@link #err()}.
- * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
- * and the CommandLine representing the command or subcommand whose input was invalid
- * @param args the command line arguments that could not be parsed
- * @return the empty list
- * @since 3.0 */
- public R handleParseException(ParameterException ex, String[] args) {
- internalHandleParseException(ex, err(), ansi(), args); return returnResultOrExit(null); }
- private void internalHandleParseException(ParameterException ex, PrintStream out, Help.Ansi ansi, String[] args) {
- out.println(ex.getMessage());
- if (!UnmatchedArgumentException.printSuggestions(ex, out)) {
- ex.getCommandLine().usage(out, ansi);
- }
- }
- /** This implementation always simply rethrows the specified exception.
- * @param ex the ExecutionException describing the problem that occurred while executing the {@code Runnable} or {@code Callable} command
- * @param parseResult the result of parsing the command line arguments
- * @return nothing: this method always rethrows the specified exception
- * @throws ExecutionException always rethrows the specified exception
- * @since 3.0 */
- public R handleExecutionException(ExecutionException ex, ParseResult parseResult) { return throwOrExit(ex); }
- @Override protected DefaultExceptionHandler<R> self() { return this; }
- }
- /** Convenience method that returns {@code new DefaultExceptionHandler<List<Object>>()}. */
- public static DefaultExceptionHandler<List<Object>> defaultExceptionHandler() { return new DefaultExceptionHandler<List<Object>>(); }
- /** @deprecated use {@link #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} instead
- * @since 2.0 */
- @Deprecated public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
- return printHelpIfRequested(parsedCommands, out, out, ansi);
- }
- /** Delegates to {@link #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} with
- * {@code parseResult.asCommandLineList(), System.out, System.err, Help.Ansi.AUTO}.
- * @since 3.0 */
- public static boolean printHelpIfRequested(ParseResult parseResult) {
- return printHelpIfRequested(parseResult.asCommandLineList(), System.out, System.err, Help.Ansi.AUTO);
- }
- /**
- * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully
- * {@linkplain #parse(String...) parsing} command line arguments. This method prints out
- * {@linkplain #usage(PrintStream, Help.Ansi) usage help} if {@linkplain #isUsageHelpRequested() requested}
- * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested}
- * and returns {@code true}. If the command is a {@link Command#helpCommand()} and {@code runnable} or {@code callable},
- * that command is executed and this method returns {@code true}.
- * Otherwise, if none of the specified {@code CommandLine} objects have help requested,
- * this method returns {@code false}.<p>
- * Note that this method <em>only</em> looks at the {@link Option#usageHelp() usageHelp} and
- * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored.
- * </p><p><b>Implementation note:</b></p><p>
- * When an error occurs while processing the help request, it is recommended custom Help commands throw a
- * {@link ParameterException} with a reference to the parent command. This will print the error message and the
- * usage for the parent command, and will use the exit code of the exception handler if one was set.
- * </p>
- * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested
- * @param out the {@code PrintStream} to print help to if requested
- * @param err the error string to print diagnostic messages to, in addition to the output from the exception handler
- * @param ansi for printing help messages using ANSI styles and colors
- * @return {@code true} if help was printed, {@code false} otherwise
- * @see IHelpCommandInitializable
- * @since 3.0 */
- public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, PrintStream err, Help.Ansi ansi) {
- return printHelpIfRequested(parsedCommands, out, err, Help.defaultColorScheme(ansi));
- }
- /**
- * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully
- * {@linkplain #parse(String...) parsing} command line arguments. This method prints out
- * {@linkplain #usage(PrintStream, Help.ColorScheme) usage help} if {@linkplain #isUsageHelpRequested() requested}
- * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested}
- * and returns {@code true}. If the command is a {@link Command#helpCommand()} and {@code runnable} or {@code callable},
- * that command is executed and this method returns {@code true}.
- * Otherwise, if none of the specified {@code CommandLine} objects have help requested,
- * this method returns {@code false}.<p>
- * Note that this method <em>only</em> looks at the {@link Option#usageHelp() usageHelp} and
- * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored.
- * </p><p><b>Implementation note:</b></p><p>
- * When an error occurs while processing the help request, it is recommended custom Help commands throw a
- * {@link ParameterException} with a reference to the parent command. This will print the error message and the
- * usage for the parent command, and will use the exit code of the exception handler if one was set.
- * </p>
- * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested
- * @param out the {@code PrintStream} to print help to if requested
- * @param err the error string to print diagnostic messages to, in addition to the output from the exception handler
- * @param colorScheme for printing help messages using ANSI styles and colors
- * @return {@code true} if help was printed, {@code false} otherwise
- * @see IHelpCommandInitializable
- * @since 3.6 */
- public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, PrintStream err, Help.ColorScheme colorScheme) {
- for (int i = 0; i < parsedCommands.size(); i++) {
- CommandLine parsed = parsedCommands.get(i);
- if (parsed.isUsageHelpRequested()) {
- parsed.usage(out, colorScheme);
- return true;
- } else if (parsed.isVersionHelpRequested()) {
- parsed.printVersionHelp(out, colorScheme.ansi);
- return true;
- } else if (parsed.getCommandSpec().helpCommand()) {
- if (parsed.getCommand() instanceof IHelpCommandInitializable) {
- ((IHelpCommandInitializable) parsed.getCommand()).init(parsed, colorScheme.ansi, out, err);
- }
- execute(parsed, new ArrayList<Object>());
- return true;
- }
- }
- return false;
- }
- private static List<Object> execute(CommandLine parsed, List<Object> executionResult) {
- Object command = parsed.getCommand();
- if (command instanceof Runnable) {
- try {
- ((Runnable) command).run();
- executionResult.add(null); // for compatibility with picocli 2.x
- return executionResult;
- } catch (ParameterException ex) {
- throw ex;
- } catch (ExecutionException ex) {
- throw ex;
- } catch (Exception ex) {
- throw new ExecutionException(parsed, "Error while running command (" + command + "): " + ex, ex);
- }
- } else if (command instanceof Callable) {
- try {
- @SuppressWarnings("unchecked") Callable<Object> callable = (Callable<Object>) command;
- executionResult.add(callable.call());
- return executionResult;
- } catch (ParameterException ex) {
- throw ex;
- } catch (ExecutionException ex) {
- throw ex;
- } catch (Exception ex) {
- throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + ex, ex);
- }
- } else if (command instanceof Method) {
- try {
- if (Modifier.isStatic(((Method) command).getModifiers())) {
- // invoke static method
- executionResult.add(((Method) command).invoke(null, parsed.getCommandSpec().argValues()));
- return executionResult;
- } else if (parsed.getCommandSpec().parent() != null) {
- executionResult.add(((Method) command).invoke(parsed.getCommandSpec().parent().userObject(), parsed.getCommandSpec().argValues()));
- return executionResult;
- } else {
- for (Constructor<?> constructor : ((Method) command).getDeclaringClass().getDeclaredConstructors()) {
- if (constructor.getParameterTypes().length == 0) {
- executionResult.add(((Method) command).invoke(constructor.newInstance(), parsed.getCommandSpec().argValues()));
- return executionResult;
- }
- }
- throw new UnsupportedOperationException("Invoking non-static method without default constructor not implemented");
- }
- } catch (InvocationTargetException ex) {
- Throwable t = ex.getTargetException();
- if (t instanceof ParameterException) {
- throw (ParameterException) t;
- } else if (t instanceof ExecutionException) {
- throw (ExecutionException) t;
- } else {
- throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + t, t);
- }
- } catch (Exception ex) {
- throw new ExecutionException(parsed, "Unhandled error while calling command (" + command + "): " + ex, ex);
- }
- }
- throw new ExecutionException(parsed, "Parsed command (" + command + ") is not Method, Runnable or Callable");
- }
- /** Command line parse result handler that returns a value. This handler prints help if requested, and otherwise calls
- * {@link #handle(CommandLine.ParseResult)} with the parse result. Facilitates implementation of the {@link IParseResultHandler2} interface.
- * <p>Note that {@code AbstractParseResultHandler} is a generic type. This, along with the abstract {@code self} method,
- * allows method chaining to work properly in subclasses, without the need for casts. An example subclass can look like this:</p>
- * <pre>{@code
- * class MyResultHandler extends AbstractParseResultHandler<MyReturnType> {
- *
- * protected MyReturnType handle(ParseResult parseResult) throws ExecutionException { ... }
- *
- * protected MyResultHandler self() { return this; }
- * }
- * }</pre>
- * @since 3.0 */
- public abstract static class AbstractParseResultHandler<R> extends AbstractHandler<R, AbstractParseResultHandler<R>> implements IParseResultHandler2<R> {
- /** Prints help if requested, and otherwise calls {@link #handle(CommandLine.ParseResult)}.
- * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.
- *
- * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
- * @return the result of {@link #handle(ParseResult) processing parse results}
- * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
- * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler2}
- * @throws ExecutionException if a problem occurred while processing the parse results; client code can use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- public R handleParseResult(ParseResult parseResult) throws ExecutionException {
- if (printHelpIfRequested(parseResult.asCommandLineList(), out(), err(), ansi())) {
- return returnResultOrExit(null);
- }
- return returnResultOrExit(handle(parseResult));
- }
- /** Processes the specified {@code ParseResult} and returns the result as a list of objects.
- * Implementations are responsible for catching any exceptions thrown in the {@code handle} method, and
- * rethrowing an {@code ExecutionException} that details the problem and captures the offending {@code CommandLine} object.
- *
- * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
- * @return the result of processing parse results
- * @throws ExecutionException if a problem occurred while processing the parse results; client code can use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- protected abstract R handle(ParseResult parseResult) throws ExecutionException;
- }
- /**
- * Command line parse result handler that prints help if requested, and otherwise executes the top-level
- * {@code Runnable} or {@code Callable} command.
- * For use in the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) parseWithHandler} methods.
- * @since 2.0 */
- public static class RunFirst extends AbstractParseResultHandler<List<Object>> implements IParseResultHandler {
- /** Prints help if requested, and otherwise executes the top-level {@code Runnable} or {@code Callable} command.
- * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.
- * If the top-level command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
- * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
- *
- * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @return an empty list if help was requested, or a list containing a single element: the result of calling the
- * {@code Callable}, or a {@code null} element if the top-level command was a {@code Runnable}
- * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
- * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
- if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); }
- return returnResultOrExit(execute(parsedCommands.get(0), new ArrayList<Object>()));
- }
- /** Executes the top-level {@code Runnable} or {@code Callable} subcommand.
- * If the top-level command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
- * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
- *
- * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
- * @return an empty list if help was requested, or a list containing a single element: the result of calling the
- * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- * @since 3.0 */
- protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
- return execute(parseResult.commandSpec().commandLine(), new ArrayList<Object>()); // first
- }
- @Override protected RunFirst self() { return this; }
- }
- /**
- * Command line parse result handler that prints help if requested, and otherwise executes the most specific
- * {@code Runnable} or {@code Callable} subcommand.
- * For use in the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) parseWithHandler} methods.
- * <p>
- * Something like this:</p>
- * <pre>{@code
- * // RunLast implementation: print help if requested, otherwise execute the most specific subcommand
- * List<CommandLine> parsedCommands = parseResult.asCommandLineList();
- * if (CommandLine.printHelpIfRequested(parsedCommands, out(), err(), ansi())) {
- * return emptyList();
- * }
- * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
- * Object command = last.getCommand();
- * Object result = null;
- * if (command instanceof Runnable) {
- * try {
- * ((Runnable) command).run();
- * } catch (Exception ex) {
- * throw new ExecutionException(last, "Error in runnable " + command, ex);
- * }
- * } else if (command instanceof Callable) {
- * try {
- * result = ((Callable) command).call();
- * } catch (Exception ex) {
- * throw new ExecutionException(last, "Error in callable " + command, ex);
- * }
- * } else {
- * throw new ExecutionException(last, "Parsed command (" + command + ") is not Runnable or Callable");
- * }
- * if (hasExitCode()) { System.exit(exitCode()); }
- * return Arrays.asList(result);
- * }</pre>
- * <p>
- * From picocli v2.0, {@code RunLast} is used to implement the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run}
- * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} convenience methods.
- * </p>
- * @since 2.0 */
- public static class RunLast extends AbstractParseResultHandler<List<Object>> implements IParseResultHandler {
- /** Prints help if requested, and otherwise executes the most specific {@code Runnable} or {@code Callable} subcommand.
- * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.
- * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
- * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
- *
- * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @return an empty list if help was requested, or a list containing a single element: the result of calling the
- * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
- * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
- * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
- if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); }
- return returnResultOrExit(execute(parsedCommands.get(parsedCommands.size() - 1), new ArrayList<Object>()));
- }
- /** Executes the most specific {@code Runnable} or {@code Callable} subcommand.
- * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
- * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
- *
- * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
- * @return an empty list if help was requested, or a list containing a single element: the result of calling the
- * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- * @since 3.0 */
- protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
- List<CommandLine> parsedCommands = parseResult.asCommandLineList();
- return execute(parsedCommands.get(parsedCommands.size() - 1), new ArrayList<Object>());
- }
- @Override protected RunLast self() { return this; }
- }
- /**
- * Command line parse result handler that prints help if requested, and otherwise executes the top-level command and
- * all subcommands as {@code Runnable} or {@code Callable}.
- * For use in the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) parseWithHandler} methods.
- * @since 2.0 */
- public static class RunAll extends AbstractParseResultHandler<List<Object>> implements IParseResultHandler {
- /** Prints help if requested, and otherwise executes the top-level command and all subcommands as {@code Runnable}
- * or {@code Callable}. Finally, either a list of result objects is returned, or the JVM is terminated if an exit
- * code {@linkplain #andExit(int) was set}. If any of the {@code CommandLine} commands does not implement either
- * {@code Runnable} or {@code Callable}, an {@code ExecutionException}
- * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
- *
- * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @return an empty list if help was requested, or a list containing the result of executing all commands:
- * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable}
- * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
- * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
- if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); }
- List<Object> result = new ArrayList<Object>();
- for (CommandLine parsed : parsedCommands) {
- execute(parsed, result);
- }
- return returnResultOrExit(result);
- }
- /** Executes the top-level command and all subcommands as {@code Runnable} or {@code Callable}.
- * If any of the {@code CommandLine} commands does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
- * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
- *
- * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
- * @return an empty list if help was requested, or a list containing the result of executing all commands:
- * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- * @since 3.0 */
- protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
- List<Object> result = new ArrayList<Object>();
- execute(parseResult.commandSpec().commandLine(), result);
- while (parseResult.hasSubcommand()) {
- parseResult = parseResult.subcommand();
- execute(parseResult.commandSpec().commandLine(), result);
- }
- return returnResultOrExit(result);
- }
- @Override protected RunAll self() { return this; }
- }
- /** @deprecated use {@link #parseWithHandler(IParseResultHandler2, String[])} instead
- * @since 2.0 */
- @Deprecated public List<Object> parseWithHandler(IParseResultHandler handler, PrintStream out, String... args) {
- return parseWithHandlers(handler, out, Help.Ansi.AUTO, defaultExceptionHandler(), args);
- }
- /**
- * Returns the result of calling {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)} with
- * a new {@link DefaultExceptionHandler} in addition to the specified parse result handler and the specified command line arguments.
- * <p>
- * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run}
- * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
- * support for nested subcommands.
- * </p>
- * <p>Calling this method roughly expands to:</p>
- * <pre>{@code
- * try {
- * ParseResult parseResult = parseArgs(args);
- * return handler.handleParseResult(parseResult);
- * } catch (ParameterException ex) {
- * return new DefaultExceptionHandler<R>().handleParseException(ex, args);
- * }
- * }</pre>
- * <p>
- * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
- * The following handlers are available:</p>
- * <ul>
- * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
- * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
- * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
- * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
- * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
- * </ul>
- * @param <R> the return type of this handler
- * @param handler the function that will handle the result of successfully parsing the command line arguments
- * @param args the command line arguments
- * @return an object resulting from handling the parse result or the exception that occurred while parsing the input
- * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the
- * parse results; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- * @see RunLast
- * @see RunAll
- * @since 3.0 */
- public <R> R parseWithHandler(IParseResultHandler2<R> handler, String[] args) {
- return parseWithHandlers(handler, new DefaultExceptionHandler<R>(), args);
- }
- /** @deprecated use {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)} instead
- * @since 2.0 */
- @Deprecated public List<Object> parseWithHandlers(IParseResultHandler handler, PrintStream out, Help.Ansi ansi, IExceptionHandler exceptionHandler, String... args) {
- try {
- List<CommandLine> result = parse(args);
- return handler.handleParseResult(result, out, ansi);
- } catch (ParameterException ex) {
- return exceptionHandler.handleException(ex, out, ansi, args);
- }
- }
- /**
- * Tries to {@linkplain #parseArgs(String...) parse} the specified command line arguments, and if successful, delegates
- * the processing of the resulting {@code ParseResult} object to the specified {@linkplain IParseResultHandler2 handler}.
- * If the command line arguments were invalid, the {@code ParameterException} thrown from the {@code parse} method
- * is caught and passed to the specified {@link IExceptionHandler2}.
- * <p>
- * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run}
- * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
- * support for nested subcommands.
- * </p>
- * <p>Calling this method roughly expands to:</p>
- * <pre>
- * ParseResult parseResult = null;
- * try {
- * parseResult = parseArgs(args);
- * return handler.handleParseResult(parseResult);
- * } catch (ParameterException ex) {
- * return exceptionHandler.handleParseException(ex, (String[]) args);
- * } catch (ExecutionException ex) {
- * return exceptionHandler.handleExecutionException(ex, parseResult);
- * }
- * </pre>
- * <p>
- * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
- * The following handlers are available:</p>
- * <ul>
- * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
- * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
- * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
- * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
- * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
- * </ul>
- *
- * @param handler the function that will handle the result of successfully parsing the command line arguments
- * @param exceptionHandler the function that can handle the {@code ParameterException} thrown when the command line arguments are invalid
- * @param args the command line arguments
- * @return an object resulting from handling the parse result or the exception that occurred while parsing the input
- * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the parse
- * result {@code ParseResult} object; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- * @param <R> the return type of the result handler and exception handler
- * @see RunLast
- * @see RunAll
- * @see DefaultExceptionHandler
- * @since 3.0 */
- public <R> R parseWithHandlers(IParseResultHandler2<R> handler, IExceptionHandler2<R> exceptionHandler, String... args) {
- ParseResult parseResult = null;
- try {
- parseResult = parseArgs(args);
- return handler.handleParseResult(parseResult);
- } catch (ParameterException ex) {
- return exceptionHandler.handleParseException(ex, args);
- } catch (ExecutionException ex) {
- return exceptionHandler.handleExecutionException(ex, parseResult);
- }
- }
- static String versionString() {
- return String.format("%s, JVM: %s (%s %s %s), OS: %s %s %s", VERSION,
- System.getProperty("java.version"), System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.vm.version"),
- System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"));
- }
- /**
- * Equivalent to {@code new CommandLine(command).usage(out)}. See {@link #usage(PrintStream)} for details.
- * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
- * @param out the print stream to print the help message to
- * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- */
- public static void usage(Object command, PrintStream out) {
- toCommandLine(command, new DefaultFactory()).usage(out);
- }
- /**
- * Equivalent to {@code new CommandLine(command).usage(out, ansi)}.
- * See {@link #usage(PrintStream, Help.Ansi)} for details.
- * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
- * @param out the print stream to print the help message to
- * @param ansi whether the usage message should contain ANSI escape codes or not
- * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- */
- public static void usage(Object command, PrintStream out, Help.Ansi ansi) {
- toCommandLine(command, new DefaultFactory()).usage(out, ansi);
- }
- /**
- * Equivalent to {@code new CommandLine(command).usage(out, colorScheme)}.
- * See {@link #usage(PrintStream, Help.ColorScheme)} for details.
- * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
- * @param out the print stream to print the help message to
- * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
- * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- */
- public static void usage(Object command, PrintStream out, Help.ColorScheme colorScheme) {
- toCommandLine(command, new DefaultFactory()).usage(out, colorScheme);
- }
- /**
- * Delegates to {@link #usage(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
- * @param out the printStream to print to
- * @see #usage(PrintStream, Help.ColorScheme)
- */
- public void usage(PrintStream out) { usage(out, Help.Ansi.AUTO); }
- /**
- * Delegates to {@link #usage(PrintWriter, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
- * @param writer the PrintWriter to print to
- * @see #usage(PrintWriter, Help.ColorScheme)
- * @since 3.0 */
- public void usage(PrintWriter writer) { usage(writer, Help.Ansi.AUTO); }
- /**
- * Delegates to {@link #usage(PrintStream, Help.ColorScheme)} with the {@linkplain Help#defaultColorScheme(CommandLine.Help.Ansi) default color scheme}.
- * @param out the printStream to print to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @see #usage(PrintStream, Help.ColorScheme)
- */
- public void usage(PrintStream out, Help.Ansi ansi) { usage(out, Help.defaultColorScheme(ansi)); }
- /** Similar to {@link #usage(PrintStream, Help.Ansi)} but with the specified {@code PrintWriter} instead of a {@code PrintStream}.
- * @since 3.0 */
- public void usage(PrintWriter writer, Help.Ansi ansi) { usage(writer, Help.defaultColorScheme(ansi)); }
- /**
- * Prints a usage help message for the annotated command class to the specified {@code PrintStream}.
- * Delegates construction of the usage help message to the {@link Help} inner class and is equivalent to:
- * <pre>
- * Help.ColorScheme colorScheme = Help.defaultColorScheme(Help.Ansi.AUTO);
- * Help help = getHelpFactory().create(getCommandSpec(), colorScheme)
- * StringBuilder sb = new StringBuilder();
- * for (String key : getHelpSectionKeys()) {
- * IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
- * if (renderer != null) { sb.append(renderer.render(help)); }
- * }
- * out.print(sb);
- * </pre>
- * <p>Annotate your class with {@link Command} to control many aspects of the usage help message, including
- * the program name, text of section headings and section contents, and some aspects of the auto-generated sections
- * of the usage help message.
- * <p>To customize the auto-generated sections of the usage help message, like how option details are displayed,
- * instantiate a {@link Help} object and use a {@link Help.TextTable} with more of fewer columns, a custom
- * {@linkplain Help.Layout layout}, and/or a custom option {@linkplain Help.IOptionRenderer renderer}
- * for ultimate control over which aspects of an Option or Field are displayed where.</p>
- * @param out the {@code PrintStream} to print the usage help message to
- * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
- * @see UsageMessageSpec
- */
- public void usage(PrintStream out, Help.ColorScheme colorScheme) {
- out.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)));
- }
- /** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but with the specified {@code PrintWriter} instead of a {@code PrintStream}.
- * @since 3.0 */
- public void usage(PrintWriter writer, Help.ColorScheme colorScheme) {
- writer.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)));
- }
- /** Similar to {@link #usage(PrintStream)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}.
- * @since 3.2 */
- public String getUsageMessage() {
- return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), Help.defaultColorScheme(Help.Ansi.AUTO))).toString();
- }
- /** Similar to {@link #usage(PrintStream, Help.Ansi)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}.
- * @since 3.2 */
- public String getUsageMessage(Help.Ansi ansi) {
- return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), Help.defaultColorScheme(ansi))).toString();
- }
- /** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}.
- * @since 3.2 */
- public String getUsageMessage(Help.ColorScheme colorScheme) {
- return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)).toString();
- }
- private StringBuilder usage(StringBuilder sb, Help help) {
- for (String key : getHelpSectionKeys()) {
- IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
- if (renderer != null) { sb.append(renderer.render(help)); }
- }
- return sb;
- }
- /**
- * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
- * @param out the printStream to print to
- * @see #printVersionHelp(PrintStream, Help.Ansi)
- * @since 0.9.8
- */
- public void printVersionHelp(PrintStream out) { printVersionHelp(out, Help.Ansi.AUTO); }
- /**
- * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
- * Each element of the array of version strings is printed on a separate line. Version strings may contain
- * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
- * @param out the printStream to print to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @see Command#version()
- * @see Option#versionHelp()
- * @see #isVersionHelpRequested()
- * @since 0.9.8
- */
- public void printVersionHelp(PrintStream out, Help.Ansi ansi) {
- for (String versionInfo : getCommandSpec().version()) {
- out.println(ansi.new Text(versionInfo));
- }
- }
- /**
- * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
- * Each element of the array of version strings is {@linkplain String#format(String, Object...) formatted} with the
- * specified parameters, and printed on a separate line. Both version strings and parameters may contain
- * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
- * @param out the printStream to print to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param params Arguments referenced by the format specifiers in the version strings
- * @see Command#version()
- * @see Option#versionHelp()
- * @see #isVersionHelpRequested()
- * @since 1.0.0
- */
- public void printVersionHelp(PrintStream out, Help.Ansi ansi, Object... params) {
- for (String versionInfo : getCommandSpec().version()) {
- out.println(ansi.new Text(format(versionInfo, params)));
- }
- }
- /**
- * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
- * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
- * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param args the command line arguments to parse
- * @param <C> the annotated object must implement Callable
- * @param <T> the return type of the most specific command (must implement {@code Callable})
- * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @since 3.0
- */
- public static <C extends Callable<T>, T> T call(C callable, String... args) {
- return call(callable, System.out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for
- * diagnostic error messages and {@link Help.Ansi#AUTO}.
- * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param out the printStream to print the usage help message to when the user requested help
- * @param args the command line arguments to parse
- * @param <C> the annotated object must implement Callable
- * @param <T> the return type of the most specific command (must implement {@code Callable})
- * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- */
- public static <C extends Callable<T>, T> T call(C callable, PrintStream out, String... args) {
- return call(callable, out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages.
- * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param out the printStream to print the usage help message to when the user requested help
- * @param ansi the ANSI style to use
- * @param args the command line arguments to parse
- * @param <C> the annotated object must implement Callable
- * @param <T> the return type of the most specific command (must implement {@code Callable})
- * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- */
- public static <C extends Callable<T>, T> T call(C callable, PrintStream out, Help.Ansi ansi, String... args) {
- return call(callable, out, System.err, ansi, args);
- }
- /**
- * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
- * The annotated object needs to implement {@link Callable}. Calling this method is equivalent to:
- * <pre>{@code
- * CommandLine cmd = new CommandLine(callable);
- * List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
- * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
- * args);
- * T result = results == null || results.isEmpty() ? null : (T) results.get(0);
- * return result;
- * }</pre>
- * <p>
- * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
- * command line is executed.
- * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
- * method with the {@link RunAll} handler or a custom handler.
- * </p><p>
- * Use {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...) call(Class, IFactory, ...)} instead of this method
- * if you want to use a factory that performs Dependency Injection.
- * </p>
- * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds.
- * @param out the printStream to print the usage help message to when the user requested help
- * @param err the printStream to print diagnostic messages to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param args the command line arguments to parse
- * @param <C> the annotated object must implement Callable
- * @param <T> the return type of the specified {@code Callable}
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
- * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- * @since 3.0
- */
- public static <C extends Callable<T>, T> T call(C callable, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
- CommandLine cmd = new CommandLine(callable);
- List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
- @SuppressWarnings("unchecked") T result = (results == null || results.isEmpty()) ? null : (T) results.get(0);
- return result;
- }
- /**
- * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
- * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
- * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param factory the factory responsible for instantiating the specified callable class and potentially inject other components
- * @param args the command line arguments to parse
- * @param <C> the annotated class must implement Callable
- * @param <T> the return type of the most specific command (must implement {@code Callable})
- * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @since 3.2
- */
- public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, String... args) {
- return call(callableClass, factory, System.out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
- * {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
- * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components
- * @param out the printStream to print the usage help message to when the user requested help
- * @param args the command line arguments to parse
- * @param <C> the annotated class must implement Callable
- * @param <T> the return type of the most specific command (must implement {@code Callable})
- * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @since 3.2
- */
- public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, PrintStream out, String... args) {
- return call(callableClass, factory, out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
- * {@code System.err} for diagnostic error messages.
- * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components
- * @param out the printStream to print the usage help message to when the user requested help
- * @param ansi the ANSI style to use
- * @param args the command line arguments to parse
- * @param <C> the annotated class must implement Callable
- * @param <T> the return type of the most specific command (must implement {@code Callable})
- * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @since 3.2
- */
- public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, PrintStream out, Help.Ansi ansi, String... args) {
- return call(callableClass, factory, out, System.err, ansi, args);
- }
- /**
- * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
- * The specified {@linkplain IFactory factory} will create an instance of the specified {@code callableClass};
- * use this method instead of {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call(Callable, ...)}
- * if you want to use a factory that performs Dependency Injection.
- * The annotated class needs to implement {@link Callable}. Calling this method is equivalent to:
- * <pre>{@code
- * CommandLine cmd = new CommandLine(callableClass, factory);
- * List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
- * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
- * args);
- * T result = results == null || results.isEmpty() ? null : (T) results.get(0);
- * return result;
- * }</pre>
- * <p>
- * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
- * command line is executed.
- * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
- * method with the {@link RunAll} handler or a custom handler.
- * </p>
- * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components
- * @param out the printStream to print the usage help message to when the user requested help
- * @param err the printStream to print diagnostic messages to
- * @param ansi the ANSI style to use
- * @param args the command line arguments to parse
- * @param <C> the annotated class must implement Callable
- * @param <T> the return type of the most specific command (must implement {@code Callable})
- * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
- * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @since 3.2
- */
- public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
- CommandLine cmd = new CommandLine(callableClass, factory);
- List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
- @SuppressWarnings("unchecked") T result = (results == null || results.isEmpty()) ? null : (T) results.get(0);
- return result;
- }
- /**
- * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
- * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
- * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param args the command line arguments to parse
- * @param <R> the annotated object must implement Runnable
- * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- * @since 3.0
- */
- public static <R extends Runnable> void run(R runnable, String... args) {
- run(runnable, System.out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages and {@link Help.Ansi#AUTO}.
- * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param out the printStream to print the usage help message to when the user requested help
- * @param args the command line arguments to parse
- * @param <R> the annotated object must implement Runnable
- * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandler(IParseResultHandler2, String[])
- * @see RunLast
- */
- public static <R extends Runnable> void run(R runnable, PrintStream out, String... args) {
- run(runnable, out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages.
- * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param out the printStream to print the usage help message to when the user requested help
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param args the command line arguments to parse
- * @param <R> the annotated object must implement Runnable
- * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- */
- public static <R extends Runnable> void run(R runnable, PrintStream out, Help.Ansi ansi, String... args) {
- run(runnable, out, System.err, ansi, args);
- }
- /**
- * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
- * The annotated object needs to implement {@link Runnable}. Calling this method is equivalent to:
- * <pre>{@code
- * CommandLine cmd = new CommandLine(runnable);
- * cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
- * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
- * args);
- * }</pre>
- * <p>
- * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
- * command line is executed.
- * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
- * method with the {@link RunAll} handler or a custom handler.
- * </p><p>
- * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) requested},
- * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
- * </p><p>
- * Use {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...) run(Class, IFactory, ...)} instead of this method
- * if you want to use a factory that performs Dependency Injection.
- * </p>
- * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds.
- * @param out the printStream to print the usage help message to when the user requested help
- * @param err the printStream to print diagnostic messages to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param args the command line arguments to parse
- * @param <R> the annotated object must implement Runnable
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @since 3.0
- */
- public static <R extends Runnable> void run(R runnable, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
- CommandLine cmd = new CommandLine(runnable);
- cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
- }
- /**
- * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
- * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
- * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
- * @param args the command line arguments to parse
- * @param <R> the annotated class must implement Runnable
- * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- * @since 3.2
- */
- public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, String... args) {
- run(runnableClass, factory, System.out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
- * {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
- * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
- * @param out the printStream to print the usage help message to when the user requested help
- * @param args the command line arguments to parse
- * @param <R> the annotated class must implement Runnable
- * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- * @since 3.2
- */
- public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, PrintStream out, String... args) {
- run(runnableClass, factory, out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
- * {@code System.err} for diagnostic error messages.
- * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
- * @param out the printStream to print the usage help message to when the user requested help
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param args the command line arguments to parse
- * @param <R> the annotated class must implement Runnable
- * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- * @since 3.2
- */
- public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, PrintStream out, Help.Ansi ansi, String... args) {
- run(runnableClass, factory, out, System.err, ansi, args);
- }
- /**
- * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
- * The specified {@linkplain IFactory factory} will create an instance of the specified {@code runnableClass};
- * use this method instead of {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run(Runnable, ...)}
- * if you want to use a factory that performs Dependency Injection.
- * The annotated class needs to implement {@link Runnable}. Calling this method is equivalent to:
- * <pre>{@code
- * CommandLine cmd = new CommandLine(runnableClass, factory);
- * cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
- * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
- * args);
- * }</pre>
- * <p>
- * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
- * command line is executed.
- * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
- * method with the {@link RunAll} handler or a custom handler.
- * </p><p>
- * This method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) requested},
- * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
- * </p>
- * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
- * @param out the printStream to print the usage help message to when the user requested help
- * @param err the printStream to print diagnostic messages to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param args the command line arguments to parse
- * @param <R> the annotated class must implement Runnable
- * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @see RunLast
- * @since 3.2
- */
- public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
- CommandLine cmd = new CommandLine(runnableClass, factory);
- cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
- }
- /**
- * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
- * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
- * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
- * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
- * @param args the command line arguments to parse
- * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified method does not have a {@link Command} annotation,
- * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @since 3.6
- */
- public static Object invoke(String methodName, Class<?> cls, String... args) {
- return invoke(methodName, cls, System.out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with the specified stream for
- * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
- * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
- * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
- * @param out the printstream to print requested help message to
- * @param args the command line arguments to parse
- * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified method does not have a {@link Command} annotation,
- * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @since 3.6
- */
- public static Object invoke(String methodName, Class<?> cls, PrintStream out, String... args) {
- return invoke(methodName, cls, out, System.err, Help.Ansi.AUTO, args);
- }
- /**
- * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with the specified stream for
- * requested usage help messages, {@code System.err} for diagnostic error messages, and the specified Ansi mode.
- * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
- * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
- * @param out the printstream to print requested help message to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param args the command line arguments to parse
- * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified method does not have a {@link Command} annotation,
- * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @since 3.6
- */
- public static Object invoke(String methodName, Class<?> cls, PrintStream out, Help.Ansi ansi, String... args) {
- return invoke(methodName, cls, out, System.err, ansi, args);
- }
- /**
- * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
- * Constructs a {@link CommandSpec} model from the {@code @Option} and {@code @Parameters}-annotated method parameters
- * of the {@code @Command}-annotated method, parses the specified command line arguments and invokes the specified method.
- * Calling this method is equivalent to:
- * <pre>{@code
- * Method commandMethod = getCommandMethods(cls, methodName).get(0);
- * CommandLine cmd = new CommandLine(commandMethod);
- * List<Object> list = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
- * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
- * args);
- * return list == null ? null : list.get(0);
- * }</pre>
- * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
- * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
- * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
- * @param out the printStream to print the usage help message to when the user requested help
- * @param err the printStream to print diagnostic messages to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param args the command line arguments to parse
- * @throws InitializationException if the specified method does not have a {@link Command} annotation,
- * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
- * @throws ExecutionException if the method throws an exception
- * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
- * @since 3.6
- */
- public static Object invoke(String methodName, Class<?> cls, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
- List<Method> candidates = getCommandMethods(cls, methodName);
- if (candidates.size() != 1) { throw new InitializationException("Expected exactly one @Command-annotated method for " + cls.getName() + "::" + methodName + "(...), but got: " + candidates); }
- Method method = candidates.get(0);
- CommandLine cmd = new CommandLine(method);
- List<Object> list = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
- return list == null ? null : list.get(0);
- }
- /**
- * Helper to get methods of a class annotated with {@link Command @Command} via reflection, optionally filtered by method name (not {@link Command#name() @Command.name}).
- * Methods have to be either public (inherited) members or be declared by {@code cls}, that is "inherited" static or protected methods will not be picked up.
- *
- * @param cls the class to search for methods annotated with {@code @Command}
- * @param methodName if not {@code null}, return only methods whose method name (not {@link Command#name() @Command.name}) equals this string. Ignored if {@code null}.
- * @return the matching command methods, or an empty list
- * @see #invoke(String, Class, String...)
- * @since 3.6.0
- */
- public static List<Method> getCommandMethods(Class<?> cls, String methodName) {
- Set<Method> candidates = new HashSet<Method>();
- // traverse public member methods (excludes static/non-public, includes inherited)
- candidates.addAll(Arrays.asList(Assert.notNull(cls, "class").getMethods()));
- // traverse directly declared methods (includes static/non-public, excludes inherited)
- candidates.addAll(Arrays.asList(Assert.notNull(cls, "class").getDeclaredMethods()));
- List<Method> result = new ArrayList<Method>();
- for (Method method : candidates) {
- if (method.isAnnotationPresent(Command.class)) {
- if (methodName == null || methodName.equals(method.getName())) { result.add(method); }
- }
- }
- Collections.sort(result, new Comparator<Method>() {
- public int compare(Method o1, Method o2) { return o1.getName().compareTo(o2.getName()); }
- });
- return result;
- }
- /**
- * Registers the specified type converter for the specified class. When initializing fields annotated with
- * {@link Option}, the field's type is used as a lookup key to find the associated type converter, and this
- * type converter converts the original command line argument string value to the correct type.
- * <p>
- * Java 8 lambdas make it easy to register custom type converters:
- * </p>
- * <pre>
- * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
- * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
- * <p>
- * Built-in type converters are pre-registered for the following java 1.5 types:
- * </p>
- * <ul>
- * <li>all primitive types</li>
- * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
- * <li>any enum</li>
- * <li>java.io.File</li>
- * <li>java.math.BigDecimal</li>
- * <li>java.math.BigInteger</li>
- * <li>java.net.InetAddress</li>
- * <li>java.net.URI</li>
- * <li>java.net.URL</li>
- * <li>java.nio.charset.Charset</li>
- * <li>java.sql.Time</li>
- * <li>java.util.Date</li>
- * <li>java.util.UUID</li>
- * <li>java.util.regex.Pattern</li>
- * <li>StringBuilder</li>
- * <li>CharSequence</li>
- * <li>String</li>
- * </ul>
- * <p>The specified converter will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment the converter is registered</em>. Subcommands added
- * later will not have this converter added automatically. To ensure a custom type converter is available to all
- * subcommands, register the type converter last, after adding subcommands.</p>
- *
- * @param cls the target class to convert parameter string values to
- * @param converter the class capable of converting string values to the specified target type
- * @param <K> the target type
- * @return this CommandLine object, to allow method chaining
- * @see #addSubcommand(String, Object)
- */
- public <K> CommandLine registerConverter(Class<K> cls, ITypeConverter<K> converter) {
- interpreter.converterRegistry.put(Assert.notNull(cls, "class"), Assert.notNull(converter, "converter"));
- for (CommandLine command : getCommandSpec().commands.values()) {
- command.registerConverter(cls, converter);
- }
- return this;
- }
- /** Returns the String that separates option names from option values when parsing command line options.
- * @return the String the parser uses to separate option names from option values
- * @see ParserSpec#separator() */
- public String getSeparator() { return getCommandSpec().parser().separator(); }
- /** Sets the String the parser uses to separate option names from option values to the specified value.
- * The separator may also be set declaratively with the {@link CommandLine.Command#separator()} annotation attribute.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param separator the String that separates option names from option values
- * @see ParserSpec#separator(String)
- * @return this {@code CommandLine} object, to allow method chaining */
- public CommandLine setSeparator(String separator) {
- getCommandSpec().parser().separator(Assert.notNull(separator, "separator"));
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setSeparator(separator);
- }
- return this;
- }
- /** Returns the ResourceBundle of this command or {@code null} if no resource bundle is set.
- * @see Command#resourceBundle()
- * @see CommandSpec#resourceBundle()
- * @since 3.6 */
- public ResourceBundle getResourceBundle() { return getCommandSpec().resourceBundle(); }
- /** Sets the ResourceBundle containing usage help message strings.
- * <p>The specified bundle will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will not be impacted. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param bundle the ResourceBundle containing usage help message strings
- * @return this {@code CommandLine} object, to allow method chaining
- * @see Command#resourceBundle()
- * @see CommandSpec#resourceBundle(ResourceBundle)
- * @since 3.6 */
- public CommandLine setResourceBundle(ResourceBundle bundle) {
- getCommandSpec().resourceBundle(bundle);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.getCommandSpec().resourceBundle(bundle);
- }
- return this;
- }
- /** Returns the maximum width of the usage help message. The default is 80.
- * @see UsageMessageSpec#width() */
- public int getUsageHelpWidth() { return getCommandSpec().usageMessage().width(); }
- /** Sets the maximum width of the usage help message. Longer lines are wrapped.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param width the maximum width of the usage help message
- * @see UsageMessageSpec#width(int)
- * @return this {@code CommandLine} object, to allow method chaining */
- public CommandLine setUsageHelpWidth(int width) {
- getCommandSpec().usageMessage().width(width);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setUsageHelpWidth(width);
- }
- return this;
- }
- /** Returns the command name (also called program name) displayed in the usage help synopsis.
- * @return the command name (also called program name) displayed in the usage
- * @see CommandSpec#name()
- * @since 2.0 */
- public String getCommandName() { return getCommandSpec().name(); }
- /** Sets the command name (also called program name) displayed in the usage help synopsis to the specified value.
- * Note that this method only modifies the usage help message, it does not impact parsing behaviour.
- * The command name may also be set declaratively with the {@link CommandLine.Command#name()} annotation attribute.
- * @param commandName command name (also called program name) displayed in the usage help synopsis
- * @return this {@code CommandLine} object, to allow method chaining
- * @see CommandSpec#name(String)
- * @since 2.0 */
- public CommandLine setCommandName(String commandName) {
- getCommandSpec().name(Assert.notNull(commandName, "commandName"));
- return this;
- }
- /** Returns whether arguments starting with {@code '@'} should be treated as the path to an argument file and its
- * contents should be expanded into separate arguments for each line in the specified file.
- * This property is {@code true} by default.
- * @return whether "argument files" or {@code @files} should be expanded into their content
- * @since 2.1 */
- public boolean isExpandAtFiles() { return getCommandSpec().parser().expandAtFiles(); }
- /** Sets whether arguments starting with {@code '@'} should be treated as the path to an argument file and its
- * contents should be expanded into separate arguments for each line in the specified file. ({@code true} by default.)
- * @param expandAtFiles whether "argument files" or {@code @files} should be expanded into their content
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 2.1 */
- public CommandLine setExpandAtFiles(boolean expandAtFiles) {
- getCommandSpec().parser().expandAtFiles(expandAtFiles);
- return this;
- }
- /** Returns the character that starts a single-line comment or {@code null} if all content of argument files should
- * be interpreted as arguments (without comments).
- * If specified, all characters from the comment character to the end of the line are ignored.
- * @return the character that starts a single-line comment or {@code null}. The default is {@code '#'}.
- * @since 3.5 */
- public Character getAtFileCommentChar() { return getCommandSpec().parser().atFileCommentChar(); }
- /** Sets the character that starts a single-line comment or {@code null} if all content of argument files should
- * be interpreted as arguments (without comments).
- * If specified, all characters from the comment character to the end of the line are ignored.
- * @param atFileCommentChar the character that starts a single-line comment or {@code null}. The default is {@code '#'}.
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.5 */
- public CommandLine setAtFileCommentChar(Character atFileCommentChar) {
- getCommandSpec().parser().atFileCommentChar(atFileCommentChar);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setAtFileCommentChar(atFileCommentChar);
- }
- return this;
- }
- /** Returns whether to use a simplified argument file format that is compatible with JCommander.
- * In this format, every line (except empty lines and comment lines)
- * is interpreted as a single argument. Arguments containing whitespace do not need to be quoted.
- * When system property {@code "picocli.useSimplifiedAtFiles"} is defined, the system property value overrides the programmatically set value.
- * @return whether to use a simplified argument file format. The default is {@code false}.
- * @since 3.9 */
- public boolean isUseSimplifiedAtFiles() { return getCommandSpec().parser().useSimplifiedAtFiles(); }
- /** Sets whether to use a simplified argument file format that is compatible with JCommander.
- * In this format, every line (except empty lines and comment lines)
- * is interpreted as a single argument. Arguments containing whitespace do not need to be quoted.
- * When system property {@code "picocli.useSimplifiedAtFiles"} is defined, the system property value overrides the programmatically set value.
- * @param simplifiedAtFiles whether to use a simplified argument file format. The default is {@code false}.
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 3.9 */
- public CommandLine setUseSimplifiedAtFiles(boolean simplifiedAtFiles) {
- getCommandSpec().parser().useSimplifiedAtFiles(simplifiedAtFiles);
- for (CommandLine command : getCommandSpec().subcommands().values()) {
- command.setUseSimplifiedAtFiles(simplifiedAtFiles);
- }
- return this;
- }
- private static boolean empty(String str) { return str == null || str.trim().length() == 0; }
- private static boolean empty(Object[] array) { return array == null || array.length == 0; }
- private static String str(String[] arr, int i) { return (arr == null || arr.length <= i) ? "" : arr[i]; }
- private static boolean isBoolean(Class<?> type) { return type == Boolean.class || type == Boolean.TYPE; }
- private static CommandLine toCommandLine(Object obj, IFactory factory) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj, factory);}
- private static boolean isMultiValue(Class<?> cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); }
- private static String format(String formatString, Object... params) {
- try {
- return formatString == null ? "" : String.format(formatString, params);
- } catch (IllegalFormatException ex) {
- new Tracer().warn("Could not format '%s' (Underlying error: %s). " +
- "Using raw String: '%%n' format strings have not been replaced with newlines. " +
- "Please ensure to escape '%%' characters with another '%%'.%n", formatString, ex.getMessage());
- return formatString;
- }
- }
- private static class NoCompletionCandidates implements Iterable<String> {
- public Iterator<String> iterator() { throw new UnsupportedOperationException(); }
- }
- /**
- * <p>
- * Annotate fields in your class with {@code @Option} and picocli will initialize these fields when matching
- * arguments are specified on the command line. In the case of command methods (annotated with {@code @Command}),
- * command options can be defined by annotating method parameters with {@code @Option}.
- * </p><p>
- * Command class example:
- * </p>
- * <pre>
- * import static picocli.CommandLine.*;
- *
- * public class MyClass {
- * @Parameters(description = "Any number of input files")
- * private List<File> files = new ArrayList<File>();
- *
- * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
- * private File outputFile;
- *
- * @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
- * private boolean[] verbose;
- *
- * @Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
- * private boolean help;
- * }
- * </pre>
- * <p>
- * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a
- * {@code ParameterException} is thrown.
- * </p>
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
- public @interface Option {
- /**
- * One or more option names. At least one option name is required.
- * <p>
- * Different environments have different conventions for naming options, but usually options have a prefix
- * that sets them apart from parameters.
- * Picocli supports all of the below styles. The default separator is {@code '='}, but this can be configured.
- * </p><p>
- * <b>*nix</b>
- * </p><p>
- * In Unix and Linux, options have a short (single-character) name, a long name or both.
- * Short options
- * (<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02">POSIX
- * style</a> are single-character and are preceded by the {@code '-'} character, e.g., {@code `-v'}.
- * <a href="https://www.gnu.org/software/tar/manual/html_node/Long-Options.html">GNU-style</a> long
- * (or <em>mnemonic</em>) options start with two dashes in a row, e.g., {@code `--file'}.
- * </p><p>Picocli supports the POSIX convention that short options can be grouped, with the last option
- * optionally taking a parameter, which may be attached to the option name or separated by a space or
- * a {@code '='} character. The below examples are all equivalent:
- * </p><pre>
- * -xvfFILE
- * -xvf FILE
- * -xvf=FILE
- * -xv --file FILE
- * -xv --file=FILE
- * -x -v --file FILE
- * -x -v --file=FILE
- * </pre><p>
- * <b>DOS</b>
- * </p><p>
- * DOS options mostly have upper case single-character names and start with a single slash {@code '/'} character.
- * Option parameters are separated by a {@code ':'} character. Options cannot be grouped together but
- * must be specified separately. For example:
- * </p><pre>
- * DIR /S /A:D /T:C
- * </pre><p>
- * <b>PowerShell</b>
- * </p><p>
- * Windows PowerShell options generally are a word preceded by a single {@code '-'} character, e.g., {@code `-Help'}.
- * Option parameters are separated by a space or by a {@code ':'} character.
- * </p>
- * @return one or more option names
- */
- String[] names();
- /**
- * Indicates whether this option is required. By default this is false.
- * <p>If an option is required, but a user invokes the program without specifying the required option,
- * a {@link MissingParameterException} is thrown from the {@link #parse(String...)} method.</p>
- * <p>Required options that are part of a {@linkplain ArgGroup group} are required <em>within the group</em>, not required within the command:
- * the group's {@linkplain ArgGroup#multiplicity() multiplicity} determines whether the group itself is required or optional.</p>
- * @return whether this option is required
- */
- boolean required() default false;
- /**
- * Set {@code help=true} if this option should disable validation of the remaining arguments:
- * If the {@code help} option is specified, no error message is generated for missing required options.
- * <p>
- * This attribute is useful for special options like help ({@code -h} and {@code --help} on unix,
- * {@code -?} and {@code -Help} on Windows) or version ({@code -V} and {@code --version} on unix,
- * {@code -Version} on Windows).
- * </p>
- * <p>
- * Note that the {@link #parse(String...)} method will not print help documentation. It will only set
- * the value of the annotated field. It is the responsibility of the caller to inspect the annotated fields
- * and take the appropriate action.
- * </p>
- * @return whether this option disables validation of the other arguments
- * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. See {@link #printHelpIfRequested(List, PrintStream, CommandLine.Help.Ansi)}
- */
- @Deprecated boolean help() default false;
- /**
- * Set {@code usageHelp=true} for the {@code --help} option that triggers display of the usage help message.
- * The <a href="http://picocli.info/#_printing_help_automatically">convenience methods</a> {@code Commandline.call},
- * {@code Commandline.run}, and {@code Commandline.parseWithHandler(s)} will automatically print usage help
- * when an option with {@code usageHelp=true} was specified on the command line.
- * <p>
- * By default, <em>all</em> options and positional parameters are included in the usage help message
- * <em>except when explicitly marked {@linkplain #hidden() hidden}.</em>
- * </p><p>
- * If this option is specified on the command line, picocli will not validate the remaining arguments (so no "missing required
- * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}.
- * </p><p>
- * Alternatively, consider annotating your command with {@linkplain Command#mixinStandardHelpOptions() @Command(mixinStandardHelpOptions = true)}.
- * </p>
- * @return whether this option allows the user to request usage help
- * @since 0.9.8
- * @see #hidden()
- * @see #run(Runnable, String...)
- * @see #call(Callable, String...)
- * @see #parseWithHandler(IParseResultHandler2, String[])
- * @see #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)
- */
- boolean usageHelp() default false;
- /**
- * Set {@code versionHelp=true} for the {@code --version} option that triggers display of the version information.
- * The <a href="http://picocli.info/#_printing_help_automatically">convenience methods</a> {@code Commandline.call},
- * {@code Commandline.run}, and {@code Commandline.parseWithHandler(s)} will automatically print version information
- * when an option with {@code versionHelp=true} was specified on the command line.
- * <p>
- * The version information string is obtained from the command's {@linkplain Command#version() version} annotation
- * or from the {@linkplain Command#versionProvider() version provider}.
- * </p><p>
- * If this option is specified on the command line, picocli will not validate the remaining arguments (so no "missing required
- * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}.
- * </p><p>
- * Alternatively, consider annotating your command with {@linkplain Command#mixinStandardHelpOptions() @Command(mixinStandardHelpOptions = true)}.
- * </p>
- * @return whether this option allows the user to request version information
- * @since 0.9.8
- * @see #hidden()
- * @see #run(Runnable, String...)
- * @see #call(Callable, String...)
- * @see #parseWithHandler(IParseResultHandler2, String[])
- * @see #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)
- */
- boolean versionHelp() default false;
- /**
- * Description of this option, used when generating the usage documentation. Each element of the array is rendered on a separate line.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.
- * </p><p>
- * The description may contain variables that are rendered when help is requested.
- * The string {@code ${DEFAULT-VALUE}} is replaced with the default value of the option. This is regardless of
- * the command's {@link Command#showDefaultValues() showDefaultValues} setting or the option's {@link #showDefaultValue() showDefaultValue} setting.
- * The string {@code ${COMPLETION-CANDIDATES}} is replaced with the completion candidates generated by
- * {@link #completionCandidates()} in the description for this option.
- * Also, embedded {@code %n} newline markers are converted to actual newlines.
- * </p>
- * @return the description of this option
- */
- String[] description() default {};
- /**
- * Specifies the minimum number of required parameters and the maximum number of accepted parameters.
- * If an option declares a positive arity, and the user specifies an insufficient number of parameters on the
- * command line, a {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
- * <p>
- * In many cases picocli can deduce the number of required parameters from the field's type.
- * By default, flags (boolean options) have arity zero,
- * and single-valued type fields (String, int, Integer, double, Double, File, Date, etc) have arity one.
- * Generally, fields with types that cannot hold multiple values can omit the {@code arity} attribute.
- * </p><p>
- * Fields used to capture options with arity two or higher should have a type that can hold multiple values,
- * like arrays or Collections. See {@link #type()} for strongly-typed Collection fields.
- * </p><p>
- * For example, if an option has 2 required parameters and any number of optional parameters,
- * specify {@code @Option(names = "-example", arity = "2..*")}.
- * </p>
- * <b>A note on boolean options</b>
- * <p>
- * By default picocli does not expect boolean options (also called "flags" or "switches") to have a parameter.
- * You can make a boolean option take a required parameter by annotating your field with {@code arity="1"}.
- * For example: </p>
- * <pre>@Option(names = "-v", arity = "1") boolean verbose;</pre>
- * <p>
- * Because this boolean field is defined with arity 1, the user must specify either {@code <program> -v false}
- * or {@code <program> -v true}
- * on the command line, or a {@link MissingParameterException} is thrown by the {@link #parse(String...)}
- * method.
- * </p><p>
- * To make the boolean parameter possible but optional, define the field with {@code arity = "0..1"}.
- * For example: </p>
- * <pre>@Option(names="-v", arity="0..1") boolean verbose;</pre>
- * <p>This will accept any of the below without throwing an exception:</p>
- * <pre>
- * -v
- * -v true
- * -v false
- * </pre>
- * @return how many arguments this option requires
- */
- String arity() default "";
- /**
- * Specify a {@code paramLabel} for the option parameter to be used in the usage help message. If omitted,
- * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
- * <pre>class Example {
- * @Option(names = {"-o", "--output"}, paramLabel="FILE", description="path of the output file")
- * private File out;
- * @Option(names = {"-j", "--jobs"}, arity="0..1", description="Allow N jobs at once; infinite jobs with no arg.")
- * private int maxJobs = -1;
- * }</pre>
- * <p>By default, the above gives a usage help message like the following:</p><pre>
- * Usage: <main class> [OPTIONS]
- * -o, --output FILE path of the output file
- * -j, --jobs [<maxJobs>] Allow N jobs at once; infinite jobs with no arg.
- * </pre>
- * @return name of the option parameter used in the usage help message
- */
- String paramLabel() default "";
- /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
- * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
- * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
- * @since 3.6.0 */
- boolean hideParamSyntax() default false;
- /** <p>
- * Optionally specify a {@code type} to control exactly what Class the option parameter should be converted
- * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
- * be declared to have type {@code java.lang.Number}, and annotating {@code @Option(type=Short.class)}
- * ensures that the option parameter value is converted to a {@code Short} before setting the field value.
- * </p><p>
- * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
- * For example, a field with type {@code Number[]} may be annotated with {@code @Option(type=Short.class)}
- * to ensure that option parameter values are converted to {@code Short} before adding an element to the array.
- * </p><p>
- * Picocli will use the {@link ITypeConverter} that is
- * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
- * the raw String values before modifying the field value.
- * </p><p>
- * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
- * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
- * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the option parameter
- * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
- * enum value, and the value should be converted to a {@code Long}. No {@code @Option(type=...)} type attribute
- * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
- * as the Class to convert to, unless the {@code @Option} annotation specifies an explicit {@code type} attribute.
- * </p><p>
- * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
- * or if the generic type's type arguments are interfaces or abstract classes, you may
- * specify a {@code type} attribute to control the Class that the option parameter should be converted to.
- * @return the type(s) to convert the raw String values
- */
- Class<?>[] type() default {};
- /**
- * Optionally specify one or more {@link ITypeConverter} classes to use to convert the command line argument into
- * a strongly typed value (or key-value pair for map fields). This is useful when a particular field should
- * use a custom conversion that is different from the normal conversion for the field's type.
- * <p>For example, for a specific field you may want to use a converter that maps the constant names defined
- * in {@link java.sql.Types java.sql.Types} to the {@code int} value of these constants, but any other {@code int} fields should
- * not be affected by this and should continue to use the standard int converter that parses numeric values.</p>
- * @return the type converter(s) to use to convert String values to strongly typed values for this field
- * @see CommandLine#registerConverter(Class, ITypeConverter)
- */
- Class<? extends ITypeConverter<?>>[] converter() default {};
- /**
- * Specify a regular expression to use to split option parameter values before applying them to the field.
- * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
- * @return a regular expression to split option parameter values or {@code ""} if the value should not be split
- * @see String#split(String)
- */
- String split() default "";
- /**
- * Set {@code hidden=true} if this option should not be included in the usage help message.
- * @return whether this option should be excluded from the usage documentation
- */
- boolean hidden() default false;
- /** Returns the default value of this option, before splitting and type conversion.
- * @return a String that (after type conversion) will be used as the value for this option if no value was specified on the command line
- * @since 3.2 */
- String defaultValue() default "__no_default_value__";
- /** Use this attribute to control for a specific option whether its default value should be shown in the usage
- * help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()}
- * is set {@code true} on the command. Use this attribute to specify whether the default value
- * for this specific option should always be shown or never be shown, regardless of the command setting.
- * <p>Note that picocli 3.2 allows {@linkplain #description() embedding default values} anywhere in the description that ignores this setting.</p>
- * @return whether this option's default value should be shown in the usage help message
- */
- Help.Visibility showDefaultValue() default Help.Visibility.ON_DEMAND;
- /** Use this attribute to specify an {@code Iterable<String>} class that generates completion candidates for this option.
- * For map fields, completion candidates should be in {@code key=value} form.
- * <p>
- * Completion candidates are used in bash completion scripts generated by the {@code picocli.AutoComplete} class.
- * Bash has special completion options to generate file names and host names, and the bash completion scripts
- * generated by {@code AutoComplete} delegate to these bash built-ins for {@code @Options} whose {@code type} is
- * {@code java.io.File}, {@code java.nio.file.Path} or {@code java.net.InetAddress}.
- * </p><p>
- * For {@code @Options} whose {@code type} is a Java {@code enum}, {@code AutoComplete} can generate completion
- * candidates from the type. For other types, use this attribute to specify completion candidates.
- * </p>
- *
- * @return a class whose instances can iterate over the completion candidates for this option
- * @see picocli.CommandLine.IFactory
- * @since 3.2 */
- Class<? extends Iterable<String>> completionCandidates() default NoCompletionCandidates.class;
- /**
- * Set {@code interactive=true} if this option will prompt the end user for a value (like a password).
- * Only supported for single-value options (not arrays, collections or maps).
- * When running on Java 6 or greater, this will use the {@link Console#readPassword()} API to get a value without echoing input to the console.
- * @return whether this option prompts the end user for a value to be entered on the command line
- * @since 3.5
- */
- boolean interactive() default false;
- /** ResourceBundle key for this option. If not specified, (and a ResourceBundle {@linkplain Command#resourceBundle() exists for this command}) an attempt
- * is made to find the option description using any of the option names (without leading hyphens) as key.
- * @see OptionSpec#description()
- * @since 3.6
- */
- String descriptionKey() default "";
- /**
- * When {@link Command#sortOptions() @Command(sortOptions = false)} is specified, this attribute can be used to control the order in which options are listed in the usage help message.
- * @return the position in the options list at which this option should be shown. Options with a lower number are shown before options with a higher number. Gaps are allowed.
- * @since 3.9
- */
- int order() default -1;
- /**
- * Specify the name of one or more options that this option is mutually exclusive with.
- * Picocli will internally create a mutually exclusive {@linkplain ArgGroup group} with all specified options (and
- * any options that the specified options are mutually exclusive with).
- * <p>
- * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
- * where an option is part of multiple groups must be simplified so that the option is in just one group.
- * For example: {@code (-a | -b) | (-a -x)} can be simplified to {@code (-a [-x] | -b)}.
- * </p>
- * @return the name or names of the option(s) that this option is mutually exclusive with.
- * @since 4.0
- */
- String[] excludes() default {};
- /**
- * Specify the name of one or more options that this option must co-occur with.
- * Picocli will internally create a co-occurring {@linkplain ArgGroup group} with all specified options (and
- * any options that the specified options must co-occur with).
- * <p>
- * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
- * where an option is part of multiple groups must be simplified so that the option is in just one group.
- * For example: {@code (-a -x) | (-a -y)} can be simplified to {@code (-a [-x | -y])}.
- * </p>
- * @return the name or names of the option(s) that this option must co-occur with.
- * @since 4.0
- */
- String[] needs() default {};
- }
- /**
- * <p>
- * Fields annotated with {@code @Parameters} will be initialized with positional parameters. By specifying the
- * {@link #index()} attribute you can pick the exact position or a range of positional parameters to apply. If no
- * index is specified, the field will get all positional parameters (and so it should be an array or a collection).
- * </p><p>
- * In the case of command methods (annotated with {@code @Command}), method parameters may be annotated with {@code @Parameters},
- * but are are considered positional parameters by default, unless they are annotated with {@code @Option}.
- * </p><p>
- * Command class example:
- * </p>
- * <pre>
- * import static picocli.CommandLine.*;
- *
- * public class MyCalcParameters {
- * @Parameters(description = "Any number of input numbers")
- * private List<BigDecimal> files = new ArrayList<BigDecimal>();
- *
- * @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display this help and exit")
- * private boolean help;
- * }
- * </pre><p>
- * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a {@code ParameterException}
- * is thrown.</p>
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
- public @interface Parameters {
- /** Specify an index ("0", or "1", etc.) to pick which of the command line arguments should be assigned to this
- * field. For array or Collection fields, you can also specify an index range ("0..3", or "2..*", etc.) to assign
- * a subset of the command line arguments to this field. The default is "*", meaning all command line arguments.
- * @return an index or range specifying which of the command line arguments should be assigned to this field
- */
- String index() default "";
- /** Description of the parameter(s), used when generating the usage documentation. Each element of the array is rendered on a separate line.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.
- * </p><p>
- * The description may contain variables that are rendered when help is requested.
- * The string {@code ${DEFAULT-VALUE}} is replaced with the default value of the positional parameter. This is regardless of
- * the command's {@link Command#showDefaultValues() showDefaultValues} setting or the positional parameter's {@link #showDefaultValue() showDefaultValue} setting.
- * The string {@code ${COMPLETION-CANDIDATES}} is replaced with the completion candidates generated by
- * {@link #completionCandidates()} in the description for this positional parameter.
- * Also, embedded {@code %n} newline markers are converted to actual newlines.
- * </p>
- * @return the description of the parameter(s)
- */
- String[] description() default {};
- /**
- * Specifies the minimum number of required parameters and the maximum number of accepted parameters. If a
- * positive arity is declared, and the user specifies an insufficient number of parameters on the command line,
- * {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
- * <p>The default depends on the type of the parameter: booleans require no parameters, arrays and Collections
- * accept zero to any number of parameters, and any other type accepts one parameter.</p>
- * <p>For single-value parameters, setting {@code arity = "0..1"} makes a positional parameter optional, while setting {@code arity = "1"} makes it required.</p>
- * <p>Required parameters that are part of a {@linkplain ArgGroup group} are required <em>within the group</em>, not required within the command:
- * the group's {@linkplain ArgGroup#multiplicity() multiplicity} determines whether the group itself is required or optional.</p>
- * @return the range of minimum and maximum parameters accepted by this command
- */
- String arity() default "";
- /**
- * Specify a {@code paramLabel} for the parameter to be used in the usage help message. If omitted,
- * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
- * <pre>class Example {
- * @Parameters(paramLabel="FILE", description="path of the input FILE(s)")
- * private File[] inputFiles;
- * }</pre>
- * <p>By default, the above gives a usage help message like the following:</p><pre>
- * Usage: <main class> [FILE...]
- * [FILE...] path of the input FILE(s)
- * </pre>
- * @return name of the positional parameter used in the usage help message
- */
- String paramLabel() default "";
- /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
- * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
- * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
- * @since 3.6.0 */
- boolean hideParamSyntax() default false;
- /**
- * <p>
- * Optionally specify a {@code type} to control exactly what Class the positional parameter should be converted
- * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
- * be declared to have type {@code java.lang.Number}, and annotating {@code @Parameters(type=Short.class)}
- * ensures that the positional parameter value is converted to a {@code Short} before setting the field value.
- * </p><p>
- * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
- * For example, a field with type {@code Number[]} may be annotated with {@code @Parameters(type=Short.class)}
- * to ensure that positional parameter values are converted to {@code Short} before adding an element to the array.
- * </p><p>
- * Picocli will use the {@link ITypeConverter} that is
- * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
- * the raw String values before modifying the field value.
- * </p><p>
- * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
- * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
- * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the positional parameter
- * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
- * enum value, and the value should be converted to a {@code Long}. No {@code @Parameters(type=...)} type attribute
- * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
- * as the Class to convert to, unless the {@code @Parameters} annotation specifies an explicit {@code type} attribute.
- * </p><p>
- * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
- * or if the generic type's type arguments are interfaces or abstract classes, you may
- * specify a {@code type} attribute to control the Class that the positional parameter should be converted to.
- * @return the type(s) to convert the raw String values
- */
- Class<?>[] type() default {};
- /**
- * Optionally specify one or more {@link ITypeConverter} classes to use to convert the command line argument into
- * a strongly typed value (or key-value pair for map fields). This is useful when a particular field should
- * use a custom conversion that is different from the normal conversion for the field's type.
- * <p>For example, for a specific field you may want to use a converter that maps the constant names defined
- * in {@link java.sql.Types java.sql.Types} to the {@code int} value of these constants, but any other {@code int} fields should
- * not be affected by this and should continue to use the standard int converter that parses numeric values.</p>
- * @return the type converter(s) to use to convert String values to strongly typed values for this field
- * @see CommandLine#registerConverter(Class, ITypeConverter)
- */
- Class<? extends ITypeConverter<?>>[] converter() default {};
- /**
- * Specify a regular expression to use to split positional parameter values before applying them to the field.
- * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
- * @return a regular expression to split operand values or {@code ""} if the value should not be split
- * @see String#split(String)
- */
- String split() default "";
- /**
- * Set {@code hidden=true} if this parameter should not be included in the usage message.
- * @return whether this parameter should be excluded from the usage message
- */
- boolean hidden() default false;
- /** Returns the default value of this positional parameter, before splitting and type conversion.
- * @return a String that (after type conversion) will be used as the value for this positional parameter if no value was specified on the command line
- * @since 3.2 */
- String defaultValue() default "__no_default_value__";
- /** Use this attribute to control for a specific positional parameter whether its default value should be shown in the usage
- * help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()}
- * is set {@code true} on the command. Use this attribute to specify whether the default value
- * for this specific positional parameter should always be shown or never be shown, regardless of the command setting.
- * <p>Note that picocli 3.2 allows {@linkplain #description() embedding default values} anywhere in the description that ignores this setting.</p>
- * @return whether this positional parameter's default value should be shown in the usage help message
- */
- Help.Visibility showDefaultValue() default Help.Visibility.ON_DEMAND;
- /** Use this attribute to specify an {@code Iterable<String>} class that generates completion candidates for
- * this positional parameter. For map fields, completion candidates should be in {@code key=value} form.
- * <p>
- * Completion candidates are used in bash completion scripts generated by the {@code picocli.AutoComplete} class.
- * Unfortunately, {@code picocli.AutoComplete} is not very good yet at generating completions for positional parameters.
- * </p>
- *
- * @return a class whose instances can iterate over the completion candidates for this positional parameter
- * @see picocli.CommandLine.IFactory
- * @since 3.2 */
- Class<? extends Iterable<String>> completionCandidates() default NoCompletionCandidates.class;
- /**
- * Set {@code interactive=true} if this positional parameter will prompt the end user for a value (like a password).
- * Only supported for single-value positional parameters (not arrays, collections or maps).
- * When running on Java 6 or greater, this will use the {@link Console#readPassword()} API to get a value without echoing input to the console.
- * @return whether this positional parameter prompts the end user for a value to be entered on the command line
- * @since 3.5
- */
- boolean interactive() default false;
- /** ResourceBundle key for this option. If not specified, (and a ResourceBundle {@linkplain Command#resourceBundle() exists for this command}) an attempt
- * is made to find the positional parameter description using {@code paramLabel() + "[" + index() + "]"} as key.
- *
- * @see PositionalParamSpec#description()
- * @since 3.6
- */
- String descriptionKey() default "";
- /**
- * Specify the name of one or more options that this positional parameter is mutually exclusive with.
- * Picocli will internally create a mutually exclusive {@linkplain ArgGroup group} with all specified options (and
- * any options and positional parameters that the specified options are mutually exclusive with).
- * <p>
- * An option or positional parameter cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
- * where an option is part of multiple groups must be simplified so that the option is in just one group.
- * For example: {@code (-a | -b) | (-a -x)} can be simplified to {@code (-a [-x] | -b)}.
- * </p>
- * @return the name or names of the option(s) that this positional parameter is mutually exclusive with.
- * @since 4.0
- */
- String[] excludes() default {};
- /**
- * Specify the name of one or more options that this option must co-occur with.
- * Picocli will internally create a co-occurring {@linkplain ArgGroup group} with all specified options (and
- * any options that the specified options must co-occur with).
- * <p>
- * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
- * where an option is part of multiple groups must be simplified so that the option is in just one group.
- * For example: {@code (-a -x) | (-a -y)} can be simplified to {@code (-a [-x | -y])}.
- * </p>
- * @return the name or names of the option(s) that this option must co-occur with.
- * @since 4.0
- */
- String[] needs() default {};
- }
- /**
- * <p>
- * Fields annotated with {@code @ParentCommand} will be initialized with the parent command of the current subcommand.
- * If the current command does not have a parent command, this annotation has no effect.
- * </p><p>
- * Parent commands often define options that apply to all the subcommands.
- * This annotation offers a convenient way to inject a reference to the parent command into a subcommand, so the
- * subcommand can access its parent options. For example:
- * </p><pre>
- * @Command(name = "top", subcommands = Sub.class)
- * class Top implements Runnable {
- *
- * @Option(names = {"-d", "--directory"}, description = "this option applies to all subcommands")
- * File baseDirectory;
- *
- * public void run() { System.out.println("Hello from top"); }
- * }
- *
- * @Command(name = "sub")
- * class Sub implements Runnable {
- *
- * @ParentCommand
- * private Top parent;
- *
- * public void run() {
- * System.out.println("Subcommand: parent command 'directory' is " + parent.baseDirectory);
- * }
- * }
- * </pre>
- * @since 2.2
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- public @interface ParentCommand { }
- /**
- * Fields annotated with {@code @Unmatched} will be initialized with the list of unmatched command line arguments, if any.
- * If this annotation is found, picocli automatically sets {@linkplain CommandLine#setUnmatchedArgumentsAllowed(boolean) unmatchedArgumentsAllowed} to {@code true}.
- * @see CommandLine#isUnmatchedArgumentsAllowed()
- * @since 3.0
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- public @interface Unmatched { }
- /**
- * <p>
- * Fields annotated with {@code @Mixin} are "expanded" into the current command: {@link Option @Option} and
- * {@link Parameters @Parameters} in the mixin class are added to the options and positional parameters of this command.
- * A {@link DuplicateOptionAnnotationsException} is thrown if any of the options in the mixin has the same name as
- * an option in this command.
- * </p><p>
- * The {@code Mixin} annotation provides a way to reuse common options and parameters without subclassing. For example:
- * </p><pre>
- * class HelloWorld implements Runnable {
- *
- * // adds the --help and --version options to this command
- * @Mixin
- * private HelpOptions = new HelpOptions();
- *
- * @Option(names = {"-u", "--userName"}, required = true, description = "The user name")
- * String userName;
- *
- * public void run() { System.out.println("Hello, " + userName); }
- * }
- *
- * // Common reusable help options.
- * class HelpOptions {
- *
- * @Option(names = { "-h", "--help"}, usageHelp = true, description = "Display this help and exit")
- * private boolean help;
- *
- * @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version info and exit")
- * private boolean versionHelp;
- * }
- * </pre>
- * @since 3.0
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.FIELD, ElementType.PARAMETER})
- public @interface Mixin {
- /** Optionally specify a name that the mixin object can be retrieved with from the {@code CommandSpec}.
- * If not specified the name of the annotated field is used.
- * @return a String to register the mixin object with, or an empty String if the name of the annotated field should be used */
- String name() default "";
- }
- /**
- * Fields annotated with {@code @Spec} will be initialized with the {@code CommandSpec} for the command the field is part of. Example usage:
- * <pre>
- * class InjectSpecExample implements Runnable {
- * @Spec CommandSpec commandSpec;
- * //...
- * public void run() {
- * // do something with the injected objects
- * }
- * }
- * </pre>
- * @since 3.2
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.FIELD, ElementType.METHOD})
- public @interface Spec { }
- /**
- * <p>Annotate your class with {@code @Command} when you want more control over the format of the generated help
- * message. From 3.6, methods can also be annotated with {@code @Command}, where the method parameters define the
- * command options and positional parameters.
- * </p><pre>
- * @Command(name = "Encrypt", mixinStandardHelpOptions = true,
- * description = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
- * version = "Encrypt version 1.0",
- * footer = "Copyright (c) 2017")
- * public class Encrypt {
- * @Parameters(paramLabel = "FILE", description = "Any number of input files")
- * private List<File> files = new ArrayList<File>();
- *
- * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
- * private File outputFile;
- *
- * @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
- * private boolean[] verbose;
- * }</pre>
- * <p>
- * The structure of a help message looks like this:
- * </p><ul>
- * <li>[header]</li>
- * <li>[synopsis]: {@code Usage: <commandName> [OPTIONS] [FILE...]}</li>
- * <li>[description]</li>
- * <li>[parameter list]: {@code [FILE...] Any number of input files}</li>
- * <li>[option list]: {@code -h, --help prints this help message and exits}</li>
- * <li>[footer]</li>
- * </ul> */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PACKAGE, ElementType.METHOD})
- public @interface Command {
- /** Program name to show in the synopsis. If omitted, {@code "<main class>"} is used.
- * For {@linkplain #subcommands() declaratively added} subcommands, this attribute is also used
- * by the parser to recognize subcommands in the command line arguments.
- * @return the program name to show in the synopsis
- * @see CommandSpec#name()
- * @see Help#commandName() */
- String name() default "<main class>";
- /** Alternative command names by which this subcommand is recognized on the command line.
- * @return one or more alternative command names
- * @since 3.1 */
- String[] aliases() default {};
- /** A list of classes to instantiate and register as subcommands. When registering subcommands declaratively
- * like this, you don't need to call the {@link CommandLine#addSubcommand(String, Object)} method. For example, this:
- * <pre>
- * @Command(subcommands = {
- * GitStatus.class,
- * GitCommit.class,
- * GitBranch.class })
- * public class Git { ... }
- *
- * CommandLine commandLine = new CommandLine(new Git());
- * </pre> is equivalent to this:
- * <pre>
- * // alternative: programmatically add subcommands.
- * // NOTE: in this case there should be no `subcommands` attribute on the @Command annotation.
- * @Command public class Git { ... }
- *
- * CommandLine commandLine = new CommandLine(new Git())
- * .addSubcommand("status", new GitStatus())
- * .addSubcommand("commit", new GitCommit())
- * .addSubcommand("branch", new GitBranch());
- * </pre>
- * @return the declaratively registered subcommands of this command, or an empty array if none
- * @see CommandLine#addSubcommand(String, Object)
- * @see HelpCommand
- * @since 0.9.8
- */
- Class<?>[] subcommands() default {};
- /** Specify whether methods annotated with {@code @Command} should be registered as subcommands of their
- * enclosing {@code @Command} class.
- * The default is {@code true}. For example:
- * <pre>
- * @Command
- * public class Git {
- * @Command
- * void status() { ... }
- * }
- *
- * CommandLine git = new CommandLine(new Git());
- * </pre> is equivalent to this:
- * <pre>
- * // don't add command methods as subcommands automatically
- * @Command(addMethodSubcommands = false)
- * public class Git {
- * @Command
- * void status() { ... }
- * }
- *
- * // add command methods as subcommands programmatically
- * CommandLine git = new CommandLine(new Git());
- * CommandLine status = new CommandLine(CommandLine.getCommandMethods(Git.class, "status").get(0));
- * git.addSubcommand("status", status);
- * </pre>
- * @return whether methods annotated with {@code @Command} should be registered as subcommands
- * @see CommandLine#addSubcommand(String, Object)
- * @see CommandLine#getCommandMethods(Class, String)
- * @see CommandSpec#addMethodSubcommands()
- * @since 3.6.0 */
- boolean addMethodSubcommands() default true;
- /** String that separates options from option parameters. Default is {@code "="}. Spaces are also accepted.
- * @return the string that separates options from option parameters, used both when parsing and when generating usage help
- * @see CommandLine#setSeparator(String) */
- String separator() default "=";
- /** Version information for this command, to print to the console when the user specifies an
- * {@linkplain Option#versionHelp() option} to request version help. Each element of the array is rendered on a separate line.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * <p>This is not part of the usage help message.</p>
- *
- * @return a string or an array of strings with version information about this command (each string in the array is displayed on a separate line).
- * @since 0.9.8
- * @see CommandLine#printVersionHelp(PrintStream)
- */
- String[] version() default {};
- /** Class that can provide version information dynamically at runtime. An implementation may return version
- * information obtained from the JAR manifest, a properties file or some other source.
- * @return a Class that can provide version information dynamically at runtime
- * @since 2.2 */
- Class<? extends IVersionProvider> versionProvider() default NoVersionProvider.class;
- /**
- * Adds the standard {@code -h} and {@code --help} {@linkplain Option#usageHelp() usageHelp} options and {@code -V}
- * and {@code --version} {@linkplain Option#versionHelp() versionHelp} options to the options of this command.
- * <p>
- * Note that if no {@link #version()} or {@link #versionProvider()} is specified, the {@code --version} option will not print anything.
- * </p><p>
- * For {@linkplain #resourceBundle() internationalization}: the help option has {@code descriptionKey = "mixinStandardHelpOptions.help"},
- * and the version option has {@code descriptionKey = "mixinStandardHelpOptions.version"}.
- * </p>
- * @return whether the auto-help mixin should be added to this command
- * @since 3.0 */
- boolean mixinStandardHelpOptions() default false;
- /** Set this attribute to {@code true} if this subcommand is a help command, and required options and positional
- * parameters of the parent command should not be validated. If a subcommand marked as {@code helpCommand} is
- * specified on the command line, picocli will not validate the parent arguments (so no "missing required
- * option" errors) and the {@link CommandLine#printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} method will return {@code true}.
- * @return {@code true} if this subcommand is a help command and picocli should not check for missing required
- * options and positional parameters on the parent command
- * @since 3.0 */
- boolean helpCommand() default false;
- /** Set the heading preceding the header section.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return the heading preceding the header section
- * @see UsageMessageSpec#headerHeading()
- * @see Help#headerHeading(Object...) */
- String headerHeading() default "";
- /** Optional summary description of the command, shown before the synopsis. Each element of the array is rendered on a separate line.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return summary description of the command
- * @see UsageMessageSpec#header()
- * @see Help#header(Object...) */
- String[] header() default {};
- /** Set the heading preceding the synopsis text. The default heading is {@code "Usage: "} (without a line break between the heading and the synopsis text).
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return the heading preceding the synopsis text
- * @see Help#synopsisHeading(Object...) */
- String synopsisHeading() default "Usage: ";
- /** Specify {@code true} to generate an abbreviated synopsis like {@code "<main> [OPTIONS] [PARAMETERS...]"}.
- * By default, a detailed synopsis with individual option names and parameters is generated.
- * @return whether the synopsis should be abbreviated
- * @see Help#abbreviatedSynopsis()
- * @see Help#detailedSynopsis(Comparator, boolean) */
- boolean abbreviateSynopsis() default false;
- /** Specify one or more custom synopsis lines to display instead of an auto-generated synopsis. Each element of the array is rendered on a separate line.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return custom synopsis text to replace the auto-generated synopsis
- * @see Help#customSynopsis(Object...) */
- String[] customSynopsis() default {};
- /** Set the heading preceding the description section.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return the heading preceding the description section
- * @see Help#descriptionHeading(Object...) */
- String descriptionHeading() default "";
- /** Optional text to display between the synopsis line(s) and the list of options. Each element of the array is rendered on a separate line.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return description of this command
- * @see Help#description(Object...) */
- String[] description() default {};
- /** Set the heading preceding the parameters list.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return the heading preceding the parameters list
- * @see Help#parameterListHeading(Object...) */
- String parameterListHeading() default "";
- /** Set the heading preceding the options list.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return the heading preceding the options list
- * @see Help#optionListHeading(Object...) */
- String optionListHeading() default "";
- /** Specify {@code false} to show Options in declaration order. The default is to sort alphabetically.
- * @return whether options should be shown in alphabetic order. */
- boolean sortOptions() default true;
- /** Prefix required options with this character in the options list. The default is no marker: the synopsis
- * indicates which options and parameters are required.
- * @return the character to show in the options list to mark required options */
- char requiredOptionMarker() default ' ';
- /** Class that can provide default values dynamically at runtime. An implementation may return default
- * value obtained from a configuration file like a properties file or some other source.
- * @return a Class that can provide default values dynamically at runtime
- * @since 3.6 */
- Class<? extends IDefaultValueProvider> defaultValueProvider() default NoDefaultProvider.class;
- /** Specify {@code true} to show default values in the description column of the options list (except for
- * boolean options). False by default.
- * <p>Note that picocli 3.2 allows {@linkplain Option#description() embedding default values} anywhere in the
- * option or positional parameter description that ignores this setting.</p>
- * @return whether the default values for options and parameters should be shown in the description column */
- boolean showDefaultValues() default false;
- /** Set the heading preceding the subcommands list. The default heading is {@code "Commands:%n"} (with a line break at the end).
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return the heading preceding the subcommands list
- * @see Help#commandListHeading(Object...) */
- String commandListHeading() default "Commands:%n";
- /** Set the heading preceding the footer section.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return the heading preceding the footer section
- * @see Help#footerHeading(Object...) */
- String footerHeading() default "";
- /** Optional text to display after the list of options. Each element of the array is rendered on a separate line.
- * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
- * @return text to display after the list of options
- * @see Help#footer(Object...) */
- String[] footer() default {};
- /**
- * Set {@code hidden=true} if this command should not be included in the list of commands in the usage help of the parent command.
- * @return whether this command should be excluded from the usage message
- * @since 3.0
- */
- boolean hidden() default false;
- /** Set the base name of the ResourceBundle to find option and positional parameters descriptions, as well as
- * usage help message sections and section headings. <p>See {@link Messages} for more details and an example.</p>
- * @return the base name of the ResourceBundle for usage help strings
- * @see ArgSpec#messages()
- * @see UsageMessageSpec#messages()
- * @see CommandSpec#resourceBundle()
- * @see CommandLine#setResourceBundle(ResourceBundle)
- * @since 3.6
- */
- String resourceBundle() default "";
- /** Set the {@link UsageMessageSpec#width(int) usage help message width}. The default is 80.
- * @since 3.7
- */
- int usageHelpWidth() default 80;
- }
- /** A {@code Command} may define one or more {@code ArgGroups}: a group of options, positional parameters or a mixture of the two.
- * Groups can be used to:
- * <ul>
- * <li>define <b>mutually exclusive</b> arguments. By default, options and positional parameters
- * in a group are mutually exclusive. This can be controlled with the {@link #exclusive() exclusive} attribute.
- * Picocli will throw a {@link MutuallyExclusiveArgsException} if the command line contains multiple arguments that are mutually exclusive.</li>
- * <li>define a set of arguments that <b>must co-occur</b>. Set {@link #exclusive() exclusive = false}
- * to define a group of options and positional parameters that must always be specified together.
- * Picocli will throw a {@link MissingParameterException MissingParameterException} if not all the options and positional parameters in a co-occurring group are specified together.</li>
- * <li>create an <b>option section</b> in the usage help message.
- * To be shown in the usage help message, a group needs to have a {@link #heading() heading} (which may come from a {@linkplain #headingKey() resource bundle}).
- * Groups without a heading are only used for validation.
- * Set {@link #validate() validate = false} for groups whose purpose is only to customize the usage help message.</li>
- * <li>define <b>composite repeating argument groups</b>. Groups may contain other groups to create composite groups.</li>
- * </ul>
- * <p>Groups may be optional ({@code multiplicity = "0..1"}), required ({@code multiplicity = "1"}), or repeating groups ({@code multiplicity = "0..*"} or {@code multiplicity = "1..*"}).
- * For a group of mutually exclusive arguments, making the group required means that one of the arguments in the group must appear on the command line, or a {@link MissingParameterException MissingParameterException} is thrown.
- * For a group of co-occurring arguments, all arguments in the group must appear on the command line.
- * </p>
- * <p>Groups can be composed for validation purposes:</p>
- * <ul>
- * <li>When the parent group is mutually exclusive, only one of the subgroups may be present.</li>
- * <li>When the parent group is a co-occurring group, all subgroups must be present.</li>
- * <li>When the parent group is required, at least one subgroup must be present.</li>
- * </ul>
- * <p>
- * Below is an example of an {@code ArgGroup} defining a set of dependent options that must occur together.
- * All options are required <em>within the group</em>, while the group itself is optional:</p>
- * <pre>
- * public class DependentOptions {
- * @ArgGroup(exclusive = false, multiplicity = "0..1")
- * Dependent group;
- *
- * static class Dependent {
- * @Option(names = "-a", required = true) int a;
- * @Option(names = "-b", required = true) int b;
- * @Option(names = "-c", required = true) int c;
- * }
- * }</pre>
- * @see ArgGroupSpec
- * @since 4.0 */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
- public @interface ArgGroup {
- /** The heading of this group, used when generating the usage documentation.
- * When neither a {@link #heading() heading} nor a {@link #headingKey() headingKey} are specified,
- * this group is used for validation only and does not change the usage help message. */
- String heading() default "__no_heading__";
- /** ResourceBundle key for this group's usage help message section heading.
- * When neither a {@link #heading() heading} nor a {@link #headingKey() headingKey} are specified,
- * this group is used for validation only and does not change the usage help message. */
- String headingKey() default "__no_heading_key__";
- /** Determines whether this is a mutually exclusive group; {@code true} by default.
- * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}. */
- boolean exclusive() default true;
- /** Determines how often this group can be specified on the command line; {@code "0..1"} (optional) by default.
- * For a group of mutually exclusive arguments, making the group required {@code multiplicity = "1"} means that
- * one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
- * For a group of co-occurring arguments, making the group required means that all arguments in the group must appear on the command line.
- * Ignored if {@link #validate()} is {@code false}. */
- String multiplicity() default "0..1";
- /** Determines whether picocli should validate the rules of this group ({@code true} by default).
- * For a mutually exclusive group validation means verifying that no more than one elements of the group is specified on the command line;
- * for a co-ocurring group validation means verifying that all elements of the group are specified on the command line.
- * Set {@link #validate() validate = false} for groups whose purpose is only to customize the usage help message.
- * @see #multiplicity()
- * @see #heading() */
- boolean validate() default true;
- /** Determines the position in the options list in the usage help message at which this group should be shown.
- * Options with a lower number are shown before options with a higher number.
- * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.*/
- int order() default -1;
- }
- /**
- * <p>
- * When parsing command line arguments and initializing
- * fields annotated with {@link Option @Option} or {@link Parameters @Parameters},
- * String values can be converted to any type for which a {@code ITypeConverter} is registered.
- * </p><p>
- * This interface defines the contract for classes that know how to convert a String into some domain object.
- * Custom converters can be registered with the {@link #registerConverter(Class, ITypeConverter)} method.
- * </p><p>
- * Java 8 lambdas make it easy to register custom type converters:
- * </p>
- * <pre>
- * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
- * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
- * <p>
- * Built-in type converters are pre-registered for the following java 1.5 types:
- * </p>
- * <ul>
- * <li>all primitive types</li>
- * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
- * <li>any enum</li>
- * <li>java.io.File</li>
- * <li>java.math.BigDecimal</li>
- * <li>java.math.BigInteger</li>
- * <li>java.net.InetAddress</li>
- * <li>java.net.URI</li>
- * <li>java.net.URL</li>
- * <li>java.nio.charset.Charset</li>
- * <li>java.sql.Time</li>
- * <li>java.util.Date</li>
- * <li>java.util.UUID</li>
- * <li>java.util.regex.Pattern</li>
- * <li>StringBuilder</li>
- * <li>CharSequence</li>
- * <li>String</li>
- * </ul>
- * @param <K> the type of the object that is the result of the conversion
- */
- public interface ITypeConverter<K> {
- /**
- * Converts the specified command line argument value to some domain object.
- * @param value the command line argument String value
- * @return the resulting domain object
- * @throws Exception an exception detailing what went wrong during the conversion
- */
- K convert(String value) throws Exception;
- }
- /**
- * Provides version information for a command. Commands may configure a provider with the
- * {@link Command#versionProvider()} annotation attribute.
- * @since 2.2 */
- public interface IVersionProvider {
- /**
- * Returns version information for a command.
- * @return version information (each string in the array is displayed on a separate line)
- * @throws Exception an exception detailing what went wrong when obtaining version information
- */
- String[] getVersion() throws Exception;
- }
- private static class NoVersionProvider implements IVersionProvider {
- public String[] getVersion() throws Exception { throw new UnsupportedOperationException(); }
- }
- /**
- * Provides default value for a command. Commands may configure a provider with the
- * {@link Command#defaultValueProvider()} annotation attribute.
- * @since 3.6 */
- public interface IDefaultValueProvider {
- /** Returns the default value for an option or positional parameter or {@code null}.
- * The returned value is converted to the type of the option/positional parameter
- * via the same type converter used when populating this option/positional
- * parameter from a command line argument.
- * @param argSpec the option or positional parameter, never {@code null}
- * @return the default value for the option or positional parameter, or {@code null} if
- * this provider has no default value for the specified option or positional parameter
- * @throws Exception when there was a problem obtaining the default value
- */
- String defaultValue(ArgSpec argSpec) throws Exception;
- }
- private static class NoDefaultProvider implements IDefaultValueProvider {
- public String defaultValue(ArgSpec argSpec) { throw new UnsupportedOperationException(); }
- }
- /**
- * Creates the {@link Help} instance used to render the usage help message.
- * @since 3.9
- */
- public interface IHelpFactory {
- /** Returns a {@code Help} instance to assist in rendering the usage help message
- * @param commandSpec the command to create usage help for
- * @param colorScheme the color scheme to use when rendering usage help
- * @return a {@code Help} instance
- */
- Help create(CommandSpec commandSpec, Help.ColorScheme colorScheme);
- }
- private static class DefaultHelpFactory implements IHelpFactory {
- public Help create(CommandSpec commandSpec, Help.ColorScheme colorScheme) {
- return new Help(commandSpec, colorScheme);
- }
- }
- /**
- * Factory for instantiating classes that are registered declaratively with annotation attributes, like
- * {@link Command#subcommands()}, {@link Option#converter()}, {@link Parameters#converter()} and {@link Command#versionProvider()}.
- * <p>The default factory implementation simply creates a new instance of the specified class when {@link #create(Class)} is invoked.
- * </p><p>
- * You may provide a custom implementation of this interface.
- * For example, a custom factory implementation could delegate to a dependency injection container that provides the requested instance.
- * </p>
- * @see picocli.CommandLine#CommandLine(Object, IFactory)
- * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
- * @since 2.2 */
- public interface IFactory {
- /**
- * Returns an instance of the specified class.
- * @param cls the class of the object to return
- * @param <K> the type of the object to return
- * @return the instance
- * @throws Exception an exception detailing what went wrong when creating or obtaining the instance
- */
- <K> K create(Class<K> cls) throws Exception;
- }
- /** Returns a default {@link IFactory} implementation. Package-protected for testing purposes. */
- static IFactory defaultFactory() { return new DefaultFactory(); }
- private static class DefaultFactory implements IFactory {
- public <T> T create(Class<T> cls) throws Exception {
- if (cls.isInterface() && Collection.class.isAssignableFrom(cls)) {
- if (List.class.isAssignableFrom(cls)) {
- return cls.cast(new ArrayList<Object>());
- } else if (SortedSet.class.isAssignableFrom(cls)) {
- return cls.cast(new TreeSet<Object>());
- } else if (Set.class.isAssignableFrom(cls)) {
- return cls.cast(new LinkedHashSet<Object>());
- } else if (Queue.class.isAssignableFrom(cls)) {
- return cls.cast(new LinkedList<Object>()); // ArrayDeque is only available since 1.6
- }
- return cls.cast(new ArrayList<Object>());
- }
- if (Map.class.isAssignableFrom(cls)) {
- try { // if it is an implementation class, instantiate it
- return cls.cast(cls.getDeclaredConstructor().newInstance());
- } catch (Exception ignored) { }
- return cls.cast(new LinkedHashMap<Object, Object>());
- }
- try {
- return cls.getDeclaredConstructor().newInstance();
- } catch (Exception ex) {
- Constructor<T> constructor = cls.getDeclaredConstructor();
- constructor.setAccessible(true);
- return constructor.newInstance();
- }
- }
- private static ITypeConverter<?>[] createConverter(IFactory factory, Class<? extends ITypeConverter<?>>[] classes) {
- ITypeConverter<?>[] result = new ITypeConverter<?>[classes.length];
- for (int i = 0; i < classes.length; i++) { result[i] = create(factory, classes[i]); }
- return result;
- }
- static IVersionProvider createVersionProvider(IFactory factory, Class<? extends IVersionProvider> cls) {
- return create(factory, cls);
- }
- static IDefaultValueProvider createDefaultValueProvider(IFactory factory, Class<? extends IDefaultValueProvider> cls) {
- return create(factory, cls);
- }
- static Iterable<String> createCompletionCandidates(IFactory factory, Class<? extends Iterable<String>> cls) {
- return create(factory, cls);
- }
- static <T> T create(IFactory factory, Class<T> cls) {
- try { return factory.create(cls); }
- catch (Exception ex) { throw new InitializationException("Could not instantiate " + cls + ": " + ex, ex); }
- }
- }
- /** Describes the number of parameters required and accepted by an option or a positional parameter.
- * @since 0.9.7
- */
- public static class Range implements Comparable<Range> {
- /** Required number of parameters for an option or positional parameter. */
- public final int min;
- /** Maximum accepted number of parameters for an option or positional parameter. */
- public final int max;
- public final boolean isVariable;
- private final boolean isUnspecified;
- private final String originalValue;
- /** Constructs a new Range object with the specified parameters.
- * @param min minimum number of required parameters
- * @param max maximum number of allowed parameters (or Integer.MAX_VALUE if variable)
- * @param variable {@code true} if any number or parameters is allowed, {@code false} otherwise
- * @param unspecified {@code true} if no arity was specified on the option/parameter (value is based on type)
- * @param originalValue the original value that was specified on the option or parameter
- */
- public Range(int min, int max, boolean variable, boolean unspecified, String originalValue) {
- if (min < 0 || max < 0) { throw new InitializationException("Invalid negative range (min=" + min + ", max=" + max + ")"); }
- if (min > max) { throw new InitializationException("Invalid range (min=" + min + ", max=" + max + ")"); }
- this.min = min;
- this.max = max;
- this.isVariable = variable;
- this.isUnspecified = unspecified;
- this.originalValue = originalValue;
- }
- /** Returns a new {@code Range} based on the {@link Option#arity()} annotation on the specified field,
- * or the field type's default arity if no arity was specified.
- * @param field the field whose Option annotation to inspect
- * @return a new {@code Range} based on the Option arity annotation on the specified field */
- public static Range optionArity(Field field) { return optionArity(new TypedMember(field)); }
- private static Range optionArity(IAnnotatedElement member) {
- return member.isAnnotationPresent(Option.class)
- ? adjustForType(Range.valueOf(member.getAnnotation(Option.class).arity()), member)
- : new Range(0, 0, false, true, "0");
- }
- /** Returns a new {@code Range} based on the {@link Parameters#arity()} annotation on the specified field,
- * or the field type's default arity if no arity was specified.
- * @param field the field whose Parameters annotation to inspect
- * @return a new {@code Range} based on the Parameters arity annotation on the specified field */
- public static Range parameterArity(Field field) { return parameterArity(new TypedMember(field)); }
- private static Range parameterArity(IAnnotatedElement member) {
- if (member.isAnnotationPresent(Parameters.class)) {
- return adjustForType(Range.valueOf(member.getAnnotation(Parameters.class).arity()), member);
- } else {
- return member.isMethodParameter()
- ? adjustForType(Range.valueOf(""), member)
- : new Range(0, 0, false, true, "0");
- }
- }
- /** Returns a new {@code Range} based on the {@link Parameters#index()} annotation on the specified field.
- * @param field the field whose Parameters annotation to inspect
- * @return a new {@code Range} based on the Parameters index annotation on the specified field */
- public static Range parameterIndex(Field field) { return parameterIndex(new TypedMember(field)); }
- private static Range parameterIndex(IAnnotatedElement member) {
- if (member.isAnnotationPresent(Parameters.class)) {
- Range result = Range.valueOf(member.getAnnotation(Parameters.class).index());
- if (!result.isUnspecified) { return result; }
- }
- if (member.isMethodParameter()) {
- int min = member.getMethodParamPosition();
- int max = member.isMultiValue() ? Integer.MAX_VALUE : min;
- return new Range(min, max, member.isMultiValue(), false, "");
- }
- return Range.valueOf("*"); // the default
- }
- static Range adjustForType(Range result, IAnnotatedElement member) {
- return result.isUnspecified ? defaultArity(member) : result;
- }
- /** Returns the default arity {@code Range}: for {@link Option options} this is 0 for booleans and 1 for
- * other types, for {@link Parameters parameters} booleans have arity 0, arrays or Collections have
- * arity "0..*", and other types have arity 1.
- * @param field the field whose default arity to return
- * @return a new {@code Range} indicating the default arity of the specified field
- * @since 2.0 */
- public static Range defaultArity(Field field) { return defaultArity(new TypedMember(field)); }
- private static Range defaultArity(IAnnotatedElement member) {
- ITypeInfo info = member.getTypeInfo();
- if (member.isAnnotationPresent(Option.class)) {
- boolean zeroArgs = info.isBoolean() || (info.isMultiValue() && info.getAuxiliaryTypeInfos().get(0).isBoolean());
- return zeroArgs ? Range.valueOf("0").unspecified(true)
- : Range.valueOf("1").unspecified(true);
- }
- if (info.isMultiValue()) {
- return Range.valueOf("0..1").unspecified(true);
- }
- return Range.valueOf("1").unspecified(true);// for single-valued fields (incl. boolean positional parameters)
- }
- /** Returns the default arity {@code Range} for {@link Option options}: booleans have arity 0, other types have arity 1.
- * @param type the type whose default arity to return
- * @return a new {@code Range} indicating the default arity of the specified type
- * @deprecated use {@link #defaultArity(Field)} instead */
- @Deprecated public static Range defaultArity(Class<?> type) {
- return isBoolean(type) ? Range.valueOf("0").unspecified(true) : Range.valueOf("1").unspecified(true);
- }
- private int size() { return 1 + max - min; }
- static Range parameterCapacity(IAnnotatedElement member) {
- Range arity = parameterArity(member);
- if (!member.isMultiValue()) { return arity; }
- Range index = parameterIndex(member);
- return parameterCapacity(arity, index);
- }
- private static Range parameterCapacity(Range arity, Range index) {
- if (arity.max == 0) { return arity; }
- if (index.size() == 1) { return arity; }
- if (index.isVariable) { return Range.valueOf(arity.min + "..*"); }
- if (arity.size() == 1) { return Range.valueOf(arity.min * index.size() + ""); }
- if (arity.isVariable) { return Range.valueOf(arity.min * index.size() + "..*"); }
- return Range.valueOf(arity.min * index.size() + ".." + arity.max * index.size());
- }
- /** Leniently parses the specified String as an {@code Range} value and return the result. A range string can
- * be a fixed integer value or a range of the form {@code MIN_VALUE + ".." + MAX_VALUE}. If the
- * {@code MIN_VALUE} string is not numeric, the minimum is zero. If the {@code MAX_VALUE} is not numeric, the
- * range is taken to be variable and the maximum is {@code Integer.MAX_VALUE}.
- * @param range the value range string to parse
- * @return a new {@code Range} value */
- public static Range valueOf(String range) {
- range = range.trim();
- boolean unspecified = range.length() == 0 || range.startsWith(".."); // || range.endsWith("..");
- int min = -1, max = -1;
- boolean variable = false;
- int dots = -1;
- if ((dots = range.indexOf("..")) >= 0) {
- min = parseInt(range.substring(0, dots), 0);
- max = parseInt(range.substring(dots + 2), Integer.MAX_VALUE);
- variable = max == Integer.MAX_VALUE;
- } else {
- max = parseInt(range, Integer.MAX_VALUE);
- variable = max == Integer.MAX_VALUE;
- min = variable ? 0 : max;
- }
- Range result = new Range(min, max, variable, unspecified, range);
- return result;
- }
- private static int parseInt(String str, int defaultValue) {
- try {
- return Integer.parseInt(str);
- } catch (Exception ex) {
- return defaultValue;
- }
- }
- /** Returns a new Range object with the {@code min} value replaced by the specified value.
- * The {@code max} of the returned Range is guaranteed not to be less than the new {@code min} value.
- * @param newMin the {@code min} value of the returned Range object
- * @return a new Range object with the specified {@code min} value */
- public Range min(int newMin) { return new Range(newMin, Math.max(newMin, max), isVariable, isUnspecified, originalValue); }
- /** Returns a new Range object with the {@code max} value replaced by the specified value.
- * The {@code min} of the returned Range is guaranteed not to be greater than the new {@code max} value.
- * @param newMax the {@code max} value of the returned Range object
- * @return a new Range object with the specified {@code max} value */
- public Range max(int newMax) { return new Range(Math.min(min, newMax), newMax, isVariable, isUnspecified, originalValue); }
- /** Returns a new Range object with the {@code isUnspecified} value replaced by the specified value.
- * @param unspecified the {@code unspecified} value of the returned Range object
- * @return a new Range object with the specified {@code unspecified} value */
- public Range unspecified(boolean unspecified) { return new Range(min, max, isVariable, unspecified, originalValue); }
- /** Returns {@code true} if this Range is a default value, {@code false} if the user specified this value.
- * @since 4.0 */
- public boolean isUnspecified() { return isUnspecified; }
- /**
- * Returns {@code true} if this Range includes the specified value, {@code false} otherwise.
- * @param value the value to check
- * @return {@code true} if the specified value is not less than the minimum and not greater than the maximum of this Range
- */
- public boolean contains(int value) { return min <= value && max >= value; }
- public boolean equals(Object object) {
- if (!(object instanceof Range)) { return false; }
- Range other = (Range) object;
- return other.max == this.max && other.min == this.min && other.isVariable == this.isVariable;
- }
- public int hashCode() {
- return ((17 * 37 + max) * 37 + min) * 37 + (isVariable ? 1 : 0);
- }
- public String toString() {
- return min == max ? String.valueOf(min) : min + ".." + (isVariable ? "*" : max);
- }
- public int compareTo(Range other) {
- int result = min - other.min;
- return (result == 0) ? max - other.max : result;
- }
- boolean overlaps(Range index) {
- return contains(index.min) || contains(index.max) || index.contains(min) || index.contains(max);
- }
- }
- private static void validatePositionalParameters(List<PositionalParamSpec> positionalParametersFields) {
- int min = 0;
- for (PositionalParamSpec positional : positionalParametersFields) {
- Range index = positional.index();
- if (index.min > min) {
- throw new ParameterIndexGapException("Command definition should have a positional parameter with index=" + min +
- ". Nearest positional parameter '" + positional.paramLabel() + "' has index=" + index.min);
- }
- min = Math.max(min, index.max);
- min = min == Integer.MAX_VALUE ? min : min + 1;
- }
- }
- @SuppressWarnings("unchecked") private static Stack<String> copy(Stack<String> stack) { return (Stack<String>) stack.clone(); }
- private static <T> Stack<T> reverse(Stack<T> stack) {
- Collections.reverse(stack);
- return stack;
- }
- private static <T> List<T> reverseList(List<T> list) {
- Collections.reverse(list);
- return list;
- }
- /** This class provides a namespace for classes and interfaces that model concepts and attributes of command line interfaces in picocli.
- * @since 3.0 */
- public static final class Model {
- private Model() {}
- /** The scope of a binding is the context where the current value should be gotten from or set to.
- * For a field, the scope is the object whose field value to get/set. For a method binding, it is the
- * object on which the method should be invoked.
- * <p>The getter and setter of the scope allow you to change the object onto which the option and positional parameter getters and setters should be applied.</p>
- * @since 4.0
- */
- public interface IScope extends IGetter, ISetter {}
- /** Customizable getter for obtaining the current value of an option or positional parameter.
- * When an option or positional parameter is matched on the command line, its getter or setter is invoked to capture the value.
- * For example, an option can be bound to a field or a method, and when the option is matched on the command line, the
- * field's value is set or the method is invoked with the option parameter value.
- * @since 3.0 */
- public static interface IGetter {
- /** Returns the current value of the binding. For multi-value options and positional parameters,
- * this method returns an array, collection or map to add values to.
- * @throws PicocliException if a problem occurred while obtaining the current value
- * @throws Exception internally, picocli call sites will catch any exceptions thrown from here and rethrow them wrapped in a PicocliException */
- <T> T get() throws Exception;
- }
- /** Customizable setter for modifying the value of an option or positional parameter.
- * When an option or positional parameter is matched on the command line, its setter is invoked to capture the value.
- * For example, an option can be bound to a field or a method, and when the option is matched on the command line, the
- * field's value is set or the method is invoked with the option parameter value.
- * @since 3.0 */
- public static interface ISetter {
- /** Sets the new value of the option or positional parameter.
- *
- * @param value the new value of the option or positional parameter
- * @param <T> type of the value
- * @return the previous value of the binding (if supported by this binding)
- * @throws PicocliException if a problem occurred while setting the new value
- * @throws Exception internally, picocli call sites will catch any exceptions thrown from here and rethrow them wrapped in a PicocliException */
- <T> T set(T value) throws Exception;
- }
- /** The {@code CommandSpec} class models a command specification, including the options, positional parameters and subcommands
- * supported by the command, as well as attributes for the version help message and the usage help message of the command.
- * <p>
- * Picocli views a command line application as a hierarchy of commands: there is a top-level command (usually the Java
- * class with the {@code main} method) with optionally a set of command line options, positional parameters and subcommands.
- * Subcommands themselves can have options, positional parameters and nested sub-subcommands to any level of depth.
- * </p><p>
- * The object model has a corresponding hierarchy of {@code CommandSpec} objects, each with a set of {@link OptionSpec},
- * {@link PositionalParamSpec} and {@linkplain CommandLine subcommands} associated with it.
- * This object model is used by the picocli command line interpreter and help message generator.
- * </p><p>Picocli can construct a {@code CommandSpec} automatically from classes with {@link Command @Command}, {@link Option @Option} and
- * {@link Parameters @Parameters} annotations. Alternatively a {@code CommandSpec} can be constructed programmatically.
- * </p>
- * @since 3.0 */
- public static class CommandSpec {
- /** Constant String holding the default program name: {@code "<main class>" }. */
- static final String DEFAULT_COMMAND_NAME = "<main class>";
- /** Constant Boolean holding the default setting for whether this is a help command: <code>{@value}</code>.*/
- static final Boolean DEFAULT_IS_HELP_COMMAND = Boolean.FALSE;
- /** Constant Boolean holding the default setting for whether method commands should be added as subcommands: <code>{@value}</code>.*/
- static final Boolean DEFAULT_IS_ADD_METHOD_SUBCOMMANDS = Boolean.TRUE;
- private final Map<String, CommandLine> commands = new LinkedHashMap<String, CommandLine>();
- private final Map<String, OptionSpec> optionsByNameMap = new LinkedHashMap<String, OptionSpec>();
- private final Map<Character, OptionSpec> posixOptionsByKeyMap = new LinkedHashMap<Character, OptionSpec>();
- private final Map<String, CommandSpec> mixins = new LinkedHashMap<String, CommandSpec>();
- private final List<ArgSpec> requiredArgs = new ArrayList<ArgSpec>();
- private final List<ArgSpec> args = new ArrayList<ArgSpec>();
- private final List<OptionSpec> options = new ArrayList<OptionSpec>();
- private final List<PositionalParamSpec> positionalParameters = new ArrayList<PositionalParamSpec>();
- private final List<UnmatchedArgsBinding> unmatchedArgs = new ArrayList<UnmatchedArgsBinding>();
- private final List<ArgGroupSpec> groups = new ArrayList<ArgGroupSpec>();
- private final ParserSpec parser = new ParserSpec();
- private final UsageMessageSpec usageMessage = new UsageMessageSpec();
- private final Object userObject;
- private CommandLine commandLine;
- private CommandSpec parent;
- private Boolean isAddMethodSubcommands;
- private String name;
- private Set<String> aliases = new LinkedHashSet<String>();
- private Boolean isHelpCommand;
- private IVersionProvider versionProvider;
- private IDefaultValueProvider defaultValueProvider;
- private String[] version;
- private String toString;
- private CommandSpec(Object userObject) { this.userObject = userObject; }
- /** Creates and returns a new {@code CommandSpec} without any associated user object. */
- public static CommandSpec create() { return wrapWithoutInspection(null); }
- /** Creates and returns a new {@code CommandSpec} with the specified associated user object.
- * The specified user object is <em>not</em> inspected for annotations.
- * @param userObject the associated user object. May be any object, may be {@code null}.
- */
- public static CommandSpec wrapWithoutInspection(Object userObject) { return new CommandSpec(userObject); }
- /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. The specified
- * user object must have at least one {@link Command}, {@link Option} or {@link Parameters} annotation.
- * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
- * @throws InitializationException if the specified object has no picocli annotations or has invalid annotations
- */
- public static CommandSpec forAnnotatedObject(Object userObject) { return forAnnotatedObject(userObject, new DefaultFactory()); }
- /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. The specified
- * user object must have at least one {@link Command}, {@link Option} or {@link Parameters} annotation.
- * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
- * @param factory the factory used to create instances of {@linkplain Command#subcommands() subcommands}, {@linkplain Option#converter() converters}, etc., that are registered declaratively with annotation attributes
- * @throws InitializationException if the specified object has no picocli annotations or has invalid annotations
- */
- public static CommandSpec forAnnotatedObject(Object userObject, IFactory factory) { return CommandReflection.extractCommandSpec(userObject, factory, true); }
- /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. If the specified
- * user object has no {@link Command}, {@link Option} or {@link Parameters} annotations, an empty {@code CommandSpec} is returned.
- * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
- * @throws InitializationException if the specified object has invalid annotations
- */
- public static CommandSpec forAnnotatedObjectLenient(Object userObject) { return forAnnotatedObjectLenient(userObject, new DefaultFactory()); }
- /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. If the specified
- * user object has no {@link Command}, {@link Option} or {@link Parameters} annotations, an empty {@code CommandSpec} is returned.
- * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
- * @param factory the factory used to create instances of {@linkplain Command#subcommands() subcommands}, {@linkplain Option#converter() converters}, etc., that are registered declaratively with annotation attributes
- * @throws InitializationException if the specified object has invalid annotations
- */
- public static CommandSpec forAnnotatedObjectLenient(Object userObject, IFactory factory) { return CommandReflection.extractCommandSpec(userObject, factory, false); }
- /** Ensures all attributes of this {@code CommandSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */
- void validate() {
- Collections.sort(positionalParameters, new PositionalParametersSorter());
- validatePositionalParameters(positionalParameters);
- List<String> wrongUsageHelpAttr = new ArrayList<String>();
- List<String> wrongVersionHelpAttr = new ArrayList<String>();
- List<String> usageHelpAttr = new ArrayList<String>();
- List<String> versionHelpAttr = new ArrayList<String>();
- for (OptionSpec option : options()) {
- if (option.usageHelp()) {
- usageHelpAttr.add(option.longestName());
- if (!isBoolean(option.type())) { wrongUsageHelpAttr.add(option.longestName()); }
- }
- if (option.versionHelp()) {
- versionHelpAttr.add(option.longestName());
- if (!isBoolean(option.type())) { wrongVersionHelpAttr.add(option.longestName()); }
- }
- }
- String wrongType = "Non-boolean options like %s should not be marked as '%s=true'. Usually a command has one %s boolean flag that triggers display of the %s. Alternatively, consider using @Command(mixinStandardHelpOptions = true) on your command instead.";
- String multiple = "Multiple options %s are marked as '%s=true'. Usually a command has only one %s option that triggers display of the %s. Alternatively, consider using @Command(mixinStandardHelpOptions = true) on your command instead.%n";
- if (!wrongUsageHelpAttr.isEmpty()) {
- throw new InitializationException(String.format(wrongType, wrongUsageHelpAttr, "usageHelp", "--help", "usage help message"));
- }
- if (!wrongVersionHelpAttr.isEmpty()) {
- throw new InitializationException(String.format(wrongType, wrongVersionHelpAttr, "versionHelp", "--version", "version information"));
- }
- if (usageHelpAttr.size() > 1) { new Tracer().warn(multiple, usageHelpAttr, "usageHelp", "--help", "usage help message"); }
- if (versionHelpAttr.size() > 1) { new Tracer().warn(multiple, versionHelpAttr, "versionHelp", "--version", "version information"); }
- }
- /** Returns the user object associated with this command.
- * @see CommandLine#getCommand() */
- public Object userObject() { return userObject; }
- /** Returns the CommandLine constructed with this {@code CommandSpec} model. */
- public CommandLine commandLine() { return commandLine;}
- /** Sets the CommandLine constructed with this {@code CommandSpec} model. */
- protected CommandSpec commandLine(CommandLine commandLine) {
- this.commandLine = commandLine;
- for (CommandSpec mixedInSpec : mixins.values()) {
- mixedInSpec.commandLine(commandLine);
- }
- for (CommandLine sub : commands.values()) {
- sub.getCommandSpec().parent(this);
- }
- return this;
- }
- /** Returns the parser specification for this command. */
- public ParserSpec parser() { return parser; }
- /** Initializes the parser specification for this command from the specified settings and returns this commandSpec.*/
- public CommandSpec parser(ParserSpec settings) { parser.initFrom(settings); return this; }
- /** Returns the usage help message specification for this command. */
- public UsageMessageSpec usageMessage() { return usageMessage; }
- /** Initializes the usageMessage specification for this command from the specified settings and returns this commandSpec.*/
- public CommandSpec usageMessage(UsageMessageSpec settings) { usageMessage.initFrom(settings, this); return this; }
- /** Returns the resource bundle base name for this command.
- * @return the resource bundle base name from the {@linkplain UsageMessageSpec#messages()}
- * @since 4.0 */
- public String resourceBundleBaseName() { return Messages.resourceBundleBaseName(usageMessage.messages()); }
- /** Initializes the resource bundle for this command: sets the {@link UsageMessageSpec#messages(Messages) UsageMessageSpec.messages} to
- * a {@link Messages Messages} object created from this command spec and the specified bundle, and then sets the
- * {@link ArgSpec#messages(Messages) ArgSpec.messages} of all options and positional parameters in this command
- * to the same {@code Messages} instance. Subcommands are not modified.
- * <p>This method is preferable to {@link #resourceBundle(ResourceBundle)} for pre-Java 8</p>
- * @param resourceBundleBaseName the base name of the ResourceBundle to set, may be {@code null}
- * @return this commandSpec
- * @see #addSubcommand(String, CommandLine)
- * @since 4.0 */
- public CommandSpec resourceBundleBaseName(String resourceBundleBaseName) {
- ResourceBundle bundle = empty(resourceBundleBaseName) ? null : ResourceBundle.getBundle(resourceBundleBaseName);
- setBundle(resourceBundleBaseName, bundle);
- return this;
- }
- /** Returns the resource bundle for this command.
- * @return the resource bundle from the {@linkplain UsageMessageSpec#messages()}
- * @since 3.6 */
- public ResourceBundle resourceBundle() { return Messages.resourceBundle(usageMessage.messages()); }
- /** Initializes the resource bundle for this command: sets the {@link UsageMessageSpec#messages(Messages) UsageMessageSpec.messages} to
- * a {@link Messages Messages} object created from this command spec and the specified bundle, and then sets the
- * {@link ArgSpec#messages(Messages) ArgSpec.messages} of all options and positional parameters in this command
- * to the same {@code Messages} instance. Subcommands are not modified.
- * @param bundle the ResourceBundle to set, may be {@code null}
- * @return this commandSpec
- * @see #addSubcommand(String, CommandLine)
- * @since 3.6 */
- public CommandSpec resourceBundle(ResourceBundle bundle) {
- setBundle(Messages.extractName(bundle), bundle);
- return this;
- }
- private void setBundle(String bundleBaseName, ResourceBundle bundle) {
- usageMessage().messages(new Messages(this, bundleBaseName, bundle));
- updateArgSpecMessages();
- }
- private void updateArgSpecMessages() {
- for (OptionSpec opt : options()) { opt.messages(usageMessage().messages()); }
- for (PositionalParamSpec pos : positionalParameters()) { pos.messages(usageMessage().messages()); }
- }
- /** Returns a read-only view of the subcommand map. */
- public Map<String, CommandLine> subcommands() { return Collections.unmodifiableMap(commands); }
- /** Adds the specified subcommand with the specified name.
- * If the specified subcommand does not have a ResourceBundle set, it is initialized to the ResourceBundle of this command spec.
- * @param name subcommand name - when this String is encountered in the command line arguments the subcommand is invoked
- * @param subcommand describes the subcommand to envoke when the name is encountered on the command line
- * @return this {@code CommandSpec} object for method chaining */
- public CommandSpec addSubcommand(String name, CommandSpec subcommand) {
- return addSubcommand(name, new CommandLine(subcommand));
- }
- /** Adds the specified subcommand with the specified name.
- * If the specified subcommand does not have a ResourceBundle set, it is initialized to the ResourceBundle of this command spec.
- * @param name subcommand name - when this String is encountered in the command line arguments the subcommand is invoked
- * @param subCommandLine the subcommand to envoke when the name is encountered on the command line
- * @return this {@code CommandSpec} object for method chaining */
- public CommandSpec addSubcommand(String name, CommandLine subCommandLine) {
- Tracer t = new Tracer();
- if (t.isDebug()) {t.debug("Adding subcommand '%s' to '%s'%n", name, this.qualifiedName());}
- CommandLine previous = commands.put(name, subCommandLine);
- if (previous != null && previous != subCommandLine) { throw new InitializationException("Another subcommand named '" + name + "' already exists for command '" + this.name() + "'"); }
- CommandSpec subSpec = subCommandLine.getCommandSpec();
- if (subSpec.name == null) { subSpec.name(name); }
- subSpec.parent(this);
- for (String alias : subSpec.aliases()) {
- if (t.isDebug()) {t.debug("Adding alias '%s' for subcommand '%s' to '%s'%n", alias, name, this.qualifiedName());}
- previous = commands.put(alias, subCommandLine);
- if (previous != null && previous != subCommandLine) { throw new InitializationException("Alias '" + alias + "' for subcommand '" + name + "' is already used by another subcommand of '" + this.name() + "'"); }
- }
- subSpec.initCommandHierarchyWithResourceBundle(resourceBundleBaseName(), resourceBundle());
- return this;
- }
- private void initCommandHierarchyWithResourceBundle(String bundleBaseName, ResourceBundle rb) {
- if (resourceBundle() == null) {
- setBundle(bundleBaseName, rb);
- }
- for (CommandLine sub : commands.values()) { // percolate down the hierarchy
- sub.getCommandSpec().initCommandHierarchyWithResourceBundle(bundleBaseName, rb);
- }
- }
- /** Returns whether method commands should be added as subcommands. Used by the annotation processor.
- * @since 4.0 */
- public boolean isAddMethodSubcommands() { return (isAddMethodSubcommands == null) ? DEFAULT_IS_ADD_METHOD_SUBCOMMANDS : isAddMethodSubcommands; }
- /** Sets whether method commands should be added as subcommands. Used by the annotation processor.
- * @since 4.0 */
- public CommandSpec setAddMethodSubcommands(Boolean addMethodSubcommands) { isAddMethodSubcommands = addMethodSubcommands; return this; }
- /** Reflects on the class of the {@linkplain #userObject() user object} and registers any command methods
- * (class methods annotated with {@code @Command}) as subcommands.
- *
- * @return this {@link CommandSpec} object for method chaining
- * @see #addMethodSubcommands(IFactory)
- * @see #addSubcommand(String, CommandLine)
- * @since 3.6.0
- */
- public CommandSpec addMethodSubcommands() { return addMethodSubcommands(new DefaultFactory()); }
- /** Reflects on the class of the {@linkplain #userObject() user object} and registers any command methods
- * (class methods annotated with {@code @Command}) as subcommands.
- * @param factory the factory used to create instances of subcommands, converters, etc., that are registered declaratively with annotation attributes
- * @return this {@link CommandSpec} object for method chaining
- * @see #addSubcommand(String, CommandLine)
- * @since 3.7.0
- */
- public CommandSpec addMethodSubcommands(IFactory factory) {
- if (userObject() instanceof Method) {
- throw new InitializationException("Cannot discover subcommand methods of this Command Method: " + userObject());
- }
- for (CommandLine sub : createMethodSubcommands(userObject().getClass(), factory)) {
- addSubcommand(sub.getCommandName(), sub);
- }
- isAddMethodSubcommands = true;
- return this;
- }
- static List<CommandLine> createMethodSubcommands(Class<?> cls, IFactory factory) {
- List<CommandLine> result = new ArrayList<CommandLine>();
- for (Method method : getCommandMethods(cls, null)) {
- result.add(new CommandLine(method, factory));
- }
- return result;
- }
- /** Returns the parent command of this subcommand, or {@code null} if this is a top-level command. */
- public CommandSpec parent() { return parent; }
- /** Sets the parent command of this subcommand.
- * @return this CommandSpec for method chaining */
- public CommandSpec parent(CommandSpec parent) { this.parent = parent; return this; }
- /** Adds the specified option spec or positional parameter spec to the list of configured arguments to expect.
- * @param arg the option spec or positional parameter spec to add
- * @return this CommandSpec for method chaining */
- public CommandSpec add(ArgSpec arg) { return arg.isOption() ? addOption((OptionSpec) arg) : addPositional((PositionalParamSpec) arg); }
- /** Adds the specified option spec to the list of configured arguments to expect.
- * The option's {@linkplain OptionSpec#description()} may now return Strings from this
- * CommandSpec's {@linkplain UsageMessageSpec#messages() messages}.
- * The option parameter's {@linkplain OptionSpec#defaultValueString()} may
- * now return Strings from this CommandSpec's {@link CommandSpec#defaultValueProvider()} IDefaultValueProvider}.
- * @param option the option spec to add
- * @return this CommandSpec for method chaining
- * @throws DuplicateOptionAnnotationsException if any of the names of the specified option is the same as the name of another option */
- public CommandSpec addOption(OptionSpec option) {
- for (String name : option.names()) { // cannot be null or empty
- OptionSpec existing = optionsByNameMap.put(name, option);
- if (existing != null) { /* was: && !existing.equals(option)) {*/ // since 4.0 ArgGroups: an option cannot be in multiple groups
- throw DuplicateOptionAnnotationsException.create(name, option, existing);
- }
- if (name.length() == 2 && name.startsWith("-")) { posixOptionsByKeyMap.put(name.charAt(1), option); }
- }
- options.add(option);
- return addArg(option);
- }
- /** Adds the specified positional parameter spec to the list of configured arguments to expect.
- * The positional parameter's {@linkplain PositionalParamSpec#description()} may
- * now return Strings from this CommandSpec's {@linkplain UsageMessageSpec#messages() messages}.
- * The positional parameter's {@linkplain PositionalParamSpec#defaultValueString()} may
- * now return Strings from this CommandSpec's {@link CommandSpec#defaultValueProvider()} IDefaultValueProvider}.
- * @param positional the positional parameter spec to add
- * @return this CommandSpec for method chaining */
- public CommandSpec addPositional(PositionalParamSpec positional) {
- positionalParameters.add(positional);
- return addArg(positional);
- }
- private CommandSpec addArg(ArgSpec arg) {
- args.add(arg);
- if (arg.required() && arg.group() == null) { requiredArgs.add(arg); }
- arg.messages(usageMessage().messages());
- arg.commandSpec = this;
- return this;
- }
- /** Adds the specified {@linkplain ArgGroupSpec argument group} to the groups in this command.
- * @param group the group spec to add
- * @return this CommandSpec for method chaining
- * @throws InitializationException if the specified group or one of its {@linkplain ArgGroupSpec#parentGroup() ancestors} has already been added
- * @since 4.0 */
- public CommandSpec addArgGroup(ArgGroupSpec group) {
- Assert.notNull(group, "group");
- if (group.parentGroup() != null) {
- throw new InitializationException("Groups that are part of another group should not be added to a command. Add only the top-level group.");
- }
- check(group, flatten(groups, new HashSet<ArgGroupSpec>()));
- this.groups.add(group);
- addGroupArgsToCommand(group, new HashMap<String, ArgGroupSpec>());
- return this;
- }
- private void addGroupArgsToCommand(ArgGroupSpec group, Map<String, ArgGroupSpec> added) {
- for (ArgSpec arg : group.args()) {
- if (arg.isOption()) {
- for (String name : ((OptionSpec) arg).names()) {
- if (added.containsKey(name)) {
- throw new DuplicateNameException("An option cannot be in multiple groups but " + name + " is in " + group.synopsis() + " and " + added.get(name).synopsis() + ". Refactor to avoid this. For example, (-a | (-a -b)) can be rewritten as (-a [-b]), and (-a -b | -a -c) can be rewritten as (-a (-b | -c)).");
- }
- }
- for (String name : ((OptionSpec) arg).names()) { added.put(name, group); }
- }
- add(arg);
- }
- for (ArgGroupSpec sub : group.subgroups()) { addGroupArgsToCommand(sub, added); }
- }
- private Set<ArgGroupSpec> flatten(Collection<ArgGroupSpec> groups, Set<ArgGroupSpec> result) {
- for (ArgGroupSpec group : groups) { flatten(group, result); } return result;
- }
- private Set<ArgGroupSpec> flatten(ArgGroupSpec group, Set<ArgGroupSpec> result) {
- result.add(group);
- for (ArgGroupSpec sub : group.subgroups()) { flatten(sub, result); }
- return result;
- }
- private void check(ArgGroupSpec group, Set<ArgGroupSpec> existing) {
- if (existing.contains(group)) {
- throw new InitializationException("The specified group " + group.synopsis() + " has already been added to the " + qualifiedName() + " command.");
- }
- for (ArgGroupSpec sub : group.subgroups()) { check(sub, existing); }
- }
- /** Adds the specified mixin {@code CommandSpec} object to the map of mixins for this command.
- * @param name the name that can be used to later retrieve the mixin
- * @param mixin the mixin whose options and positional parameters and other attributes to add to this command
- * @return this CommandSpec for method chaining */
- public CommandSpec addMixin(String name, CommandSpec mixin) {
- mixins.put(name, mixin);
- parser.initSeparator(mixin.parser.separator());
- initName(mixin.name());
- initVersion(mixin.version());
- initHelpCommand(mixin.helpCommand());
- initVersionProvider(mixin.versionProvider());
- initDefaultValueProvider(mixin.defaultValueProvider());
- usageMessage.initFromMixin(mixin.usageMessage, this);
- for (Map.Entry<String, CommandLine> entry : mixin.subcommands().entrySet()) {
- addSubcommand(entry.getKey(), entry.getValue());
- }
- for (OptionSpec optionSpec : mixin.options()) { addOption(optionSpec); }
- for (PositionalParamSpec paramSpec : mixin.positionalParameters()) { addPositional(paramSpec); }
- return this;
- }
- /** Adds the specified {@code UnmatchedArgsBinding} to the list of model objects to capture unmatched arguments for this command.
- * @param spec the unmatched arguments binding to capture unmatched arguments
- * @return this CommandSpec for method chaining */
- public CommandSpec addUnmatchedArgsBinding(UnmatchedArgsBinding spec) { unmatchedArgs.add(spec); parser().unmatchedArgumentsAllowed(true); return this; }
- /** Returns a map of the mixin names to mixin {@code CommandSpec} objects configured for this command.
- * @return an immutable map of mixins added to this command. */
- public Map<String, CommandSpec> mixins() { return Collections.unmodifiableMap(mixins); }
- /** Returns the list of options configured for this command.
- * @return an immutable list of options that this command recognizes. */
- public List<OptionSpec> options() { return Collections.unmodifiableList(options); }
- /** Returns the list of positional parameters configured for this command.
- * @return an immutable list of positional parameters that this command recognizes. */
- public List<PositionalParamSpec> positionalParameters() { return Collections.unmodifiableList(positionalParameters); }
- /** Returns the {@linkplain ArgGroupSpec argument groups} in this command.
- * @return an immutable list of groups of options and positional parameters in this command
- * @since 4.0 */
- public List<ArgGroupSpec> argGroups() { return Collections.unmodifiableList(groups); }
- /** Returns a map of the option names to option spec objects configured for this command.
- * @return an immutable map of options that this command recognizes. */
- public Map<String, OptionSpec> optionsMap() { return Collections.unmodifiableMap(optionsByNameMap); }
- /** Returns a map of the short (single character) option names to option spec objects configured for this command.
- * @return an immutable map of options that this command recognizes. */
- public Map<Character, OptionSpec> posixOptionsMap() { return Collections.unmodifiableMap(posixOptionsByKeyMap); }
- /** Returns the list of required options and positional parameters configured for this command.
- * This does not include options and positional parameters that are part of a {@linkplain ArgGroupSpec group}.
- * @return an immutable list of the required options and positional parameters for this command. */
- public List<ArgSpec> requiredArgs() { return Collections.unmodifiableList(requiredArgs); }
- /** Returns the list of {@link UnmatchedArgsBinding UnmatchedArgumentsBindings} configured for this command;
- * each {@code UnmatchedArgsBinding} captures the arguments that could not be matched to any options or positional parameters. */
- public List<UnmatchedArgsBinding> unmatchedArgsBindings() { return Collections.unmodifiableList(unmatchedArgs); }
- /** Returns name of this command. Used in the synopsis line of the help message.
- * {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} if defined.
- * @see #qualifiedName() */
- public String name() { return (name == null) ? DEFAULT_COMMAND_NAME : name; }
- /** Returns the alias command names of this subcommand.
- * @since 3.1 */
- public String[] aliases() { return aliases.toArray(new String[0]); }
- /** Returns all names of this command, including {@link #name()} and {@link #aliases()}.
- * @since 3.9 */
- public Set<String> names() {
- Set<String> result = new LinkedHashSet<String>();
- result.add(name());
- result.addAll(Arrays.asList(aliases()));
- return result;
- }
- /** Returns the list of all options and positional parameters configured for this command.
- * @return an immutable list of all options and positional parameters for this command. */
- public List<ArgSpec> args() { return Collections.unmodifiableList(args); }
- Object[] argValues() {
- Map<Class<?>, CommandSpec> allMixins = null;
- int argsLength = args.size();
- int shift = 0;
- for (Map.Entry<String, CommandSpec> mixinEntry : mixins.entrySet()) {
- if (mixinEntry.getKey().equals(AutoHelpMixin.KEY)) {
- shift = 2;
- argsLength -= shift;
- continue;
- }
- CommandSpec mixin = mixinEntry.getValue();
- int mixinArgs = mixin.args.size();
- argsLength -= (mixinArgs - 1); // subtract 1 because that's the mixin
- if (allMixins == null) {
- allMixins = new IdentityHashMap<Class<?>, CommandSpec>(mixins.size());
- }
- allMixins.put(mixin.userObject.getClass(), mixin);
- }
- Object[] values = new Object[argsLength];
- if (allMixins == null) {
- for (int i = 0; i < values.length; i++) { values[i] = args.get(i + shift).getValue(); }
- } else {
- int argIndex = shift;
- Class<?>[] methodParams = ((Method) userObject).getParameterTypes();
- for (int i = 0; i < methodParams.length; i++) {
- final Class<?> param = methodParams[i];
- CommandSpec mixin = allMixins.remove(param);
- if (mixin == null) {
- values[i] = args.get(argIndex++).getValue();
- } else {
- values[i] = mixin.userObject;
- argIndex += mixin.args.size();
- }
- }
- }
- return values;
- }
- /** Returns the String to use as the program name in the synopsis line of the help message:
- * this command's {@link #name() name}, preceded by the qualified name of the parent command, if any, separated by a space.
- * @return {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} and the parent command if defined.
- * @since 3.0.1 */
- public String qualifiedName() { return qualifiedName(" "); }
- /** Returns this command's fully qualified name, which is its {@link #name() name}, preceded by the qualified name of the parent command, if this command has a parent command.
- * @return {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} and the parent command if any.
- * @param separator the string to put between the names of the commands in the hierarchy
- * @since 3.6 */
- public String qualifiedName(String separator) {
- String result = name();
- if (parent() != null) { result = parent().qualifiedName(separator) + separator + result; }
- return result;
- }
- /** Returns version information for this command, to print to the console when the user specifies an
- * {@linkplain OptionSpec#versionHelp() option} to request version help. This is not part of the usage help message.
- * @return the version strings generated by the {@link #versionProvider() version provider} if one is set, otherwise the {@linkplain #version(String...) version literals}*/
- public String[] version() {
- if (versionProvider != null) {
- try {
- return versionProvider.getVersion();
- } catch (Exception ex) {
- String msg = "Could not get version info from " + versionProvider + ": " + ex;
- throw new ExecutionException(this.commandLine, msg, ex);
- }
- }
- return version == null ? UsageMessageSpec.DEFAULT_MULTI_LINE : version;
- }
- /** Returns the version provider for this command, to generate the {@link #version()} strings.
- * @return the version provider or {@code null} if the version strings should be returned from the {@linkplain #version(String...) version literals}.*/
- public IVersionProvider versionProvider() { return versionProvider; }
- /** Returns whether this subcommand is a help command, and required options and positional
- * parameters of the parent command should not be validated.
- * @return {@code true} if this subcommand is a help command and picocli should not check for missing required
- * options and positional parameters on the parent command
- * @see Command#helpCommand() */
- public boolean helpCommand() { return (isHelpCommand == null) ? DEFAULT_IS_HELP_COMMAND : isHelpCommand; }
- /** Returns {@code true} if the standard help options have been mixed in with this command, {@code false} otherwise. */
- public boolean mixinStandardHelpOptions() { return mixins.containsKey(AutoHelpMixin.KEY); }
- /** Returns a string representation of this command, used in error messages and trace messages. */
- public String toString() { return toString; }
- /** Sets the String to use as the program name in the synopsis line of the help message.
- * @return this CommandSpec for method chaining */
- public CommandSpec name(String name) { this.name = name; return this; }
- /** Sets the alternative names by which this subcommand is recognized on the command line.
- * @return this CommandSpec for method chaining
- * @since 3.1 */
- public CommandSpec aliases(String... aliases) {
- this.aliases = new LinkedHashSet<String>(Arrays.asList(aliases == null ? new String[0] : aliases));
- return this;
- }
- /** Returns the default value provider for this command.
- * @return the default value provider or {@code null}
- * @since 3.6 */
- public IDefaultValueProvider defaultValueProvider() { return defaultValueProvider; }
- /** Sets default value provider for this command.
- * @param defaultValueProvider the default value provider to use, or {@code null}.
- * @return this CommandSpec for method chaining
- * @since 3.6 */
- public CommandSpec defaultValueProvider(IDefaultValueProvider defaultValueProvider) { this.defaultValueProvider = defaultValueProvider; return this; }
- /** Sets version information literals for this command, to print to the console when the user specifies an
- * {@linkplain OptionSpec#versionHelp() option} to request version help. Only used if no {@link #versionProvider() versionProvider} is set.
- * @return this CommandSpec for method chaining */
- public CommandSpec version(String... version) { this.version = version; return this; }
- /** Sets version provider for this command, to generate the {@link #version()} strings.
- * @param versionProvider the version provider to use to generate the version strings, or {@code null} if the {@linkplain #version(String...) version literals} should be used.
- * @return this CommandSpec for method chaining */
- public CommandSpec versionProvider(IVersionProvider versionProvider) { this.versionProvider = versionProvider; return this; }
- /** Sets whether this is a help command and required parameter checking should be suspended.
- * @return this CommandSpec for method chaining
- * @see Command#helpCommand() */
- public CommandSpec helpCommand(boolean newValue) {isHelpCommand = newValue; return this;}
- /** Sets whether the standard help options should be mixed in with this command.
- * @return this CommandSpec for method chaining
- * @see Command#mixinStandardHelpOptions() */
- public CommandSpec mixinStandardHelpOptions(boolean newValue) {
- if (newValue) {
- CommandSpec mixin = CommandSpec.forAnnotatedObject(new AutoHelpMixin(), new DefaultFactory());
- addMixin(AutoHelpMixin.KEY, mixin);
- } else {
- CommandSpec helpMixin = mixins.remove(AutoHelpMixin.KEY);
- if (helpMixin != null) {
- options.removeAll(helpMixin.options);
- for (OptionSpec option : helpMixin.options()) {
- for (String name : option.names) {
- optionsByNameMap.remove(name);
- if (name.length() == 2 && name.startsWith("-")) { posixOptionsByKeyMap.remove(name.charAt(1)); }
- }
- }
- }
- }
- return this;
- }
- /** Sets the string representation of this command, used in error messages and trace messages.
- * @param newValue the string representation
- * @return this CommandSpec for method chaining */
- public CommandSpec withToString(String newValue) { this.toString = newValue; return this; }
- /**
- * Updates the following attributes from the specified {@code @Command} annotation:
- * aliases, {@link ParserSpec#separator() parser separator}, command name, version, help command,
- * version provider, default provider and {@link UsageMessageSpec usage message spec}.
- * @param cmd the {@code @Command} annotation to get attribute values from
- * @param factory factory used to instantiate classes
- * @since 3.7
- */
- public void updateCommandAttributes(Command cmd, IFactory factory) {
- aliases(cmd.aliases());
- parser().updateSeparator(cmd.separator());
- updateName(cmd.name());
- updateVersion(cmd.version());
- updateHelpCommand(cmd.helpCommand());
- updateAddMethodSubcommands(cmd.addMethodSubcommands());
- usageMessage().updateFromCommand(cmd, this);
- if (factory != null) {
- updateVersionProvider(cmd.versionProvider(), factory);
- initDefaultValueProvider(cmd.defaultValueProvider(), factory);
- }
- }
- void initName(String value) { if (initializable(name, value, DEFAULT_COMMAND_NAME)) {name = value;} }
- void initHelpCommand(boolean value) { if (initializable(isHelpCommand, value, DEFAULT_IS_HELP_COMMAND)) {isHelpCommand = value;} }
- void initVersion(String[] value) { if (initializable(version, value, UsageMessageSpec.DEFAULT_MULTI_LINE)) {version = value.clone();} }
- void initVersionProvider(IVersionProvider value) { if (versionProvider == null) { versionProvider = value; } }
- void initDefaultValueProvider(IDefaultValueProvider value) { if (defaultValueProvider == null) { defaultValueProvider = value; } }
- void initDefaultValueProvider(Class<? extends IDefaultValueProvider> value, IFactory factory) {
- if (initializable(defaultValueProvider, value, NoDefaultProvider.class)) { defaultValueProvider = (DefaultFactory.createDefaultValueProvider(factory, value)); }
- }
- void updateName(String value) { if (isNonDefault(value, DEFAULT_COMMAND_NAME)) {name = value;} }
- void updateHelpCommand(boolean value) { if (isNonDefault(value, DEFAULT_IS_HELP_COMMAND)) {isHelpCommand = value;} }
- void updateAddMethodSubcommands(boolean value) { if (isNonDefault(value, DEFAULT_IS_ADD_METHOD_SUBCOMMANDS)) {isAddMethodSubcommands = value;} }
- void updateVersion(String[] value) { if (isNonDefault(value, UsageMessageSpec.DEFAULT_MULTI_LINE)) {version = value.clone();} }
- void updateVersionProvider(Class<? extends IVersionProvider> value, IFactory factory) {
- if (isNonDefault(value, NoVersionProvider.class)) { versionProvider = (DefaultFactory.createVersionProvider(factory, value)); }
- }
- /** Returns the option with the specified short name, or {@code null} if no option with that name is defined for this command. */
- public OptionSpec findOption(char shortName) { return findOption(shortName, options()); }
- /** Returns the option with the specified name, or {@code null} if no option with that name is defined for this command.
- * @param name used to search the options. May include option name prefix characters or not. */
- public OptionSpec findOption(String name) { return findOption(name, options()); }
- static OptionSpec findOption(char shortName, Iterable<OptionSpec> options) {
- for (OptionSpec option : options) {
- for (String name : option.names()) {
- if (name.length() == 2 && name.charAt(0) == '-' && name.charAt(1) == shortName) { return option; }
- if (name.length() == 1 && name.charAt(0) == shortName) { return option; }
- }
- }
- return null;
- }
- static OptionSpec findOption(String name, List<OptionSpec> options) {
- for (OptionSpec option : options) {
- for (String prefixed : option.names()) {
- if (prefixed.equals(name) || stripPrefix(prefixed).equals(name)) { return option; }
- }
- }
- return null;
- }
- static String stripPrefix(String prefixed) {
- for (int i = 0; i < prefixed.length(); i++) {
- if (Character.isJavaIdentifierPart(prefixed.charAt(i))) { return prefixed.substring(i); }
- }
- return prefixed;
- }
- List<String> findOptionNamesWithPrefix(String prefix) {
- List<String> result = new ArrayList<String>();
- for (OptionSpec option : options()) {
- for (String name : option.names()) {
- if (stripPrefix(name).startsWith(prefix)) { result.add(name); }
- }
- }
- return result;
- }
- boolean resemblesOption(String arg, Tracer tracer) {
- if (parser().unmatchedOptionsArePositionalParams()) {
- if (tracer != null && tracer.isDebug()) {tracer.debug("Parser is configured to treat all unmatched options as positional parameter%n", arg);}
- return false;
- }
- if (arg.length() == 1) {
- if (tracer != null && tracer.isDebug()) {tracer.debug("Single-character arguments that don't match known options are considered positional parameters%n", arg);}
- return false;
- }
- if (options().isEmpty()) {
- boolean result = arg.startsWith("-");
- if (tracer != null && tracer.isDebug()) {tracer.debug("'%s' %s an option%n", arg, (result ? "resembles" : "doesn't resemble"));}
- return result;
- }
- int count = 0;
- for (String optionName : optionsMap().keySet()) {
- for (int i = 0; i < arg.length(); i++) {
- if (optionName.length() > i && arg.charAt(i) == optionName.charAt(i)) { count++; } else { break; }
- }
- }
- boolean result = count > 0 && count * 10 >= optionsMap().size() * 9; // at least one prefix char in common with 9 out of 10 options
- if (tracer != null && tracer.isDebug()) {tracer.debug("'%s' %s an option: %d matching prefix chars out of %d option names%n", arg, (result ? "resembles" : "doesn't resemble"), count, optionsMap().size());}
- return result;
- }
- }
- private static boolean initializable(Object current, Object candidate, Object defaultValue) {
- return current == null && isNonDefault(candidate, defaultValue);
- }
- private static boolean initializable(Object current, Object[] candidate, Object[] defaultValue) {
- return current == null && isNonDefault(candidate, defaultValue);
- }
- private static boolean isNonDefault(Object candidate, Object defaultValue) {
- return !Assert.notNull(defaultValue, "defaultValue").equals(candidate);
- }
- private static boolean isNonDefault(Object[] candidate, Object[] defaultValue) {
- return !Arrays.equals(Assert.notNull(defaultValue, "defaultValue"), candidate);
- }
- /** Models the usage help message specification and can be used to customize the usage help message.
- * <p>
- * This class provides two ways to customize the usage help message:
- * </p>
- * <ul>
- * <li>Change the text of the predefined sections (this may also be done declaratively using the annotations)</li>
- * <li>Add custom sections, or remove or re-order predefined sections</li>
- * </ul>
- * <p>
- * The pre-defined sections have getters and setters that return a String (or array of Strings). For example:
- * {@link #description()} and {@link #description(String...)} or {@link #header()} and {@link #header(String...)}.
- * </p><p>
- * Changing the section order, or adding custom sections can be accomplished with {@link #sectionKeys(List)} and {@link #sectionMap(Map)}.
- * This gives complete freedom on how a usage help message section is rendered, but it also means that the {@linkplain IHelpSectionRenderer section renderer}
- * is responsible for all aspects of rendering the section, including layout and emitting ANSI escape codes.
- * The {@link Help.TextTable} and {@link Help.Ansi.Text} classes, and the {@link CommandLine.Help.Ansi#string(String)} and {@link CommandLine.Help.Ansi#text(String)} methods may be useful.
- * </p><p>
- * The usage help message is created more or less like this:
- * </p>
- * <pre>
- * // CommandLine.usage(...) or CommandLine.getUsageMessage(...)
- * Help.ColorScheme colorScheme = Help.defaultColorScheme(Help.Ansi.AUTO);
- * Help help = getHelpFactory().create(getCommandSpec(), colorScheme)
- * StringBuilder result = new StringBuilder();
- * for (String key : getHelpSectionKeys()) {
- * IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
- * if (renderer != null) { result.append(renderer.render(help)); }
- * }
- * // return or print result
- * </pre>
- * <p>
- * Where the default {@linkplain #sectionMap() help section map} is constructed like this:</p>
- * <pre>{@code
- * // The default section renderers delegate to methods in Help for their implementation
- * // (using Java 8 lambda notation for brevity):
- * Map<String, IHelpSectionRenderer> sectionMap = new HashMap<>();
- * sectionMap.put(SECTION_KEY_HEADER_HEADING, help -> help.headerHeading());
- * sectionMap.put(SECTION_KEY_HEADER, help -> help.header());
- * sectionMap.put(SECTION_KEY_SYNOPSIS_HEADING, help -> help.synopsisHeading()); //e.g. Usage:
- * sectionMap.put(SECTION_KEY_SYNOPSIS, help -> help.synopsis(help.synopsisHeadingLength())); //e.g. <cmd> [OPTIONS] <subcmd> [COMMAND-OPTIONS] [ARGUMENTS]
- * sectionMap.put(SECTION_KEY_DESCRIPTION_HEADING, help -> help.descriptionHeading()); //e.g. %nDescription:%n%n
- * sectionMap.put(SECTION_KEY_DESCRIPTION, help -> help.description()); //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
- * sectionMap.put(SECTION_KEY_PARAMETER_LIST_HEADING, help -> help.parameterListHeading()); //e.g. %nPositional parameters:%n%n
- * sectionMap.put(SECTION_KEY_PARAMETER_LIST, help -> help.parameterList()); //e.g. [FILE...] the files to convert
- * sectionMap.put(SECTION_KEY_OPTION_LIST_HEADING, help -> help.optionListHeading()); //e.g. %nOptions:%n%n
- * sectionMap.put(SECTION_KEY_OPTION_LIST, help -> help.optionList()); //e.g. -h, --help displays this help and exits
- * sectionMap.put(SECTION_KEY_COMMAND_LIST_HEADING, help -> help.commandListHeading()); //e.g. %nCommands:%n%n
- * sectionMap.put(SECTION_KEY_COMMAND_LIST, help -> help.commandList()); //e.g. add adds the frup to the frooble
- * sectionMap.put(SECTION_KEY_FOOTER_HEADING, help -> help.footerHeading());
- * sectionMap.put(SECTION_KEY_FOOTER, help -> help.footer());
- * }</pre>
- *
- * @since 3.0 */
- public static class UsageMessageSpec {
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Header Heading section.
- * The default renderer for this section calls {@link Help#headerHeading(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_HEADER_HEADING = "headerHeading";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Header section.
- * The default renderer for this section calls {@link Help#header(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_HEADER = "header";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Synopsis Heading section.
- * The default renderer for this section calls {@link Help#synopsisHeading(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_SYNOPSIS_HEADING = "synopsisHeading";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Synopsis section.
- * The default renderer for this section calls {@link Help#synopsis(int)}.
- * @since 3.9 */
- public static final String SECTION_KEY_SYNOPSIS = "synopsis";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Description Heading section.
- * The default renderer for this section calls {@link Help#descriptionHeading(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_DESCRIPTION_HEADING = "descriptionHeading";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Description section.
- * The default renderer for this section calls {@link Help#description(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_DESCRIPTION = "description";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Parameter List Heading section.
- * The default renderer for this section calls {@link Help#parameterListHeading(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_PARAMETER_LIST_HEADING = "parameterListHeading";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Parameter List section.
- * The default renderer for this section calls {@link Help#parameterList()}.
- * @since 3.9 */
- public static final String SECTION_KEY_PARAMETER_LIST = "parameterList";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Option List Heading section.
- * The default renderer for this section calls {@link Help#optionListHeading(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_OPTION_LIST_HEADING = "optionListHeading";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Option List section.
- * The default renderer for this section calls {@link Help#optionList()}.
- * @since 3.9 */
- public static final String SECTION_KEY_OPTION_LIST = "optionList";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Subcommand List Heading section.
- * The default renderer for this section calls {@link Help#commandListHeading(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_COMMAND_LIST_HEADING = "commandListHeading";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Subcommand List section.
- * The default renderer for this section calls {@link Help#commandList()}.
- * @since 3.9 */
- public static final String SECTION_KEY_COMMAND_LIST = "commandList";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Footer Heading section.
- * The default renderer for this section calls {@link Help#footerHeading(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_FOOTER_HEADING = "footerHeading";
- /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Footer section.
- * The default renderer for this section calls {@link Help#footer(Object...)}.
- * @since 3.9 */
- public static final String SECTION_KEY_FOOTER = "footer";
- /** Constant holding the default usage message width: <code>{@value}</code>. */
- public final static int DEFAULT_USAGE_WIDTH = 80;
- private final static int MINIMUM_USAGE_WIDTH = 55;
- /** Constant String holding the default synopsis heading: <code>{@value}</code>. */
- static final String DEFAULT_SYNOPSIS_HEADING = "Usage: ";
- /** Constant String holding the default command list heading: <code>{@value}</code>. */
- static final String DEFAULT_COMMAND_LIST_HEADING = "Commands:%n";
- /** Constant String holding the default string that separates options from option parameters: {@code ' '} ({@value}). */
- static final char DEFAULT_REQUIRED_OPTION_MARKER = ' ';
- /** Constant Boolean holding the default setting for whether to abbreviate the synopsis: <code>{@value}</code>.*/
- static final Boolean DEFAULT_ABBREVIATE_SYNOPSIS = Boolean.FALSE;
- /** Constant Boolean holding the default setting for whether to sort the options alphabetically: <code>{@value}</code>.*/
- static final Boolean DEFAULT_SORT_OPTIONS = Boolean.TRUE;
- /** Constant Boolean holding the default setting for whether to show default values in the usage help message: <code>{@value}</code>.*/
- static final Boolean DEFAULT_SHOW_DEFAULT_VALUES = Boolean.FALSE;
- /** Constant Boolean holding the default setting for whether this command should be listed in the usage help of the parent command: <code>{@value}</code>.*/
- static final Boolean DEFAULT_HIDDEN = Boolean.FALSE;
- static final String DEFAULT_SINGLE_VALUE = "";
- static final String[] DEFAULT_MULTI_LINE = {};
- private IHelpFactory helpFactory;
- private List<String> sectionKeys = Collections.unmodifiableList(Arrays.asList(
- SECTION_KEY_HEADER_HEADING,
- SECTION_KEY_HEADER,
- SECTION_KEY_SYNOPSIS_HEADING,
- SECTION_KEY_SYNOPSIS,
- SECTION_KEY_DESCRIPTION_HEADING,
- SECTION_KEY_DESCRIPTION,
- SECTION_KEY_PARAMETER_LIST_HEADING,
- SECTION_KEY_PARAMETER_LIST,
- SECTION_KEY_OPTION_LIST_HEADING,
- SECTION_KEY_OPTION_LIST,
- SECTION_KEY_COMMAND_LIST_HEADING,
- SECTION_KEY_COMMAND_LIST,
- SECTION_KEY_FOOTER_HEADING,
- SECTION_KEY_FOOTER));
- private Map<String, IHelpSectionRenderer> helpSectionRendererMap = createHelpSectionRendererMap();
- private String[] description;
- private String[] customSynopsis;
- private String[] header;
- private String[] footer;
- private Boolean abbreviateSynopsis;
- private Boolean sortOptions;
- private Boolean showDefaultValues;
- private Boolean hidden;
- private Character requiredOptionMarker;
- private String headerHeading;
- private String synopsisHeading;
- private String descriptionHeading;
- private String parameterListHeading;
- private String optionListHeading;
- private String commandListHeading;
- private String footerHeading;
- private int width = DEFAULT_USAGE_WIDTH;
- private Messages messages;
- /**
- * Sets the maximum usage help message width to the specified value. Longer values are wrapped.
- * @param newValue the new maximum usage help message width. Must be 55 or greater.
- * @return this {@code UsageMessageSpec} for method chaining
- * @throws IllegalArgumentException if the specified width is less than 55
- */
- public UsageMessageSpec width(int newValue) {
- if (newValue < MINIMUM_USAGE_WIDTH) {
- throw new InitializationException("Invalid usage message width " + newValue + ". Minimum value is " + MINIMUM_USAGE_WIDTH);
- }
- width = newValue; return this;
- }
- private static int getSysPropertyWidthOrDefault(int defaultWidth) {
- String userValue = System.getProperty("picocli.usage.width");
- if (userValue == null) { return defaultWidth; }
- try {
- int width = Integer.parseInt(userValue);
- if (width < MINIMUM_USAGE_WIDTH) {
- new Tracer().warn("Invalid picocli.usage.width value %d. Using minimum usage width %d.%n", width, MINIMUM_USAGE_WIDTH);
- return MINIMUM_USAGE_WIDTH;
- }
- return width;
- } catch (NumberFormatException ex) {
- new Tracer().warn("Invalid picocli.usage.width value '%s'. Using usage width %d.%n", userValue, defaultWidth);
- return defaultWidth;
- }
- }
- /** Returns the maximum usage help message width. Derived from system property {@code "picocli.usage.width"}
- * if set, otherwise returns the value set via the {@link #width(int)} method, or if not set, the {@linkplain #DEFAULT_USAGE_WIDTH default width}.
- * @return the maximum usage help message width. Never returns less than 55. */
- public int width() { return getSysPropertyWidthOrDefault(width); }
- /** Returns the help section renderers for the predefined section keys. see: {@link #sectionKeys()} */
- private Map<String, IHelpSectionRenderer> createHelpSectionRendererMap() {
- Map<String, IHelpSectionRenderer> result = new HashMap<String, IHelpSectionRenderer>();
- result.put(SECTION_KEY_HEADER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.headerHeading(); } });
- result.put(SECTION_KEY_HEADER, new IHelpSectionRenderer() { public String render(Help help) { return help.header(); } });
- //e.g. Usage:
- result.put(SECTION_KEY_SYNOPSIS_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsisHeading(); } });
- //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
- result.put(SECTION_KEY_SYNOPSIS, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsis(help.synopsisHeadingLength()); } });
- //e.g. %nDescription:%n%n
- result.put(SECTION_KEY_DESCRIPTION_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.descriptionHeading(); } });
- //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
- result.put(SECTION_KEY_DESCRIPTION, new IHelpSectionRenderer() { public String render(Help help) { return help.description(); } });
- //e.g. %nPositional parameters:%n%n
- result.put(SECTION_KEY_PARAMETER_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterListHeading(); } });
- //e.g. [FILE...] the files to convert
- result.put(SECTION_KEY_PARAMETER_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterList(); } });
- //e.g. %nOptions:%n%n
- result.put(SECTION_KEY_OPTION_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.optionListHeading(); } });
- //e.g. -h, --help displays this help and exits
- result.put(SECTION_KEY_OPTION_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.optionList(); } });
- //e.g. %nCommands:%n%n
- result.put(SECTION_KEY_COMMAND_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.commandListHeading(); } });
- //e.g. add adds the frup to the frooble
- result.put(SECTION_KEY_COMMAND_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.commandList(); } });
- result.put(SECTION_KEY_FOOTER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.footerHeading(); } });
- result.put(SECTION_KEY_FOOTER, new IHelpSectionRenderer() { public String render(Help help) { return help.footer(); } });
- return result;
- }
- /**
- * Returns the section keys in the order that the usage help message should render the sections.
- * This ordering may be modified with the {@link #sectionKeys(List) sectionKeys setter}. The default keys are (in order):
- * <ol>
- * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER_HEADING SECTION_KEY_HEADER_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER SECTION_KEY_HEADER}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS_HEADING SECTION_KEY_SYNOPSIS_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS SECTION_KEY_SYNOPSIS}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION_HEADING SECTION_KEY_DESCRIPTION_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION SECTION_KEY_DESCRIPTION}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST_HEADING SECTION_KEY_PARAMETER_LIST_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST SECTION_KEY_PARAMETER_LIST}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST_HEADING SECTION_KEY_OPTION_LIST_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST SECTION_KEY_OPTION_LIST}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST_HEADING SECTION_KEY_COMMAND_LIST_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST SECTION_KEY_COMMAND_LIST}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER_HEADING SECTION_KEY_FOOTER_HEADING}</li>
- * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER SECTION_KEY_FOOTER}</li>
- * </ol>
- * @since 3.9
- */
- public List<String> sectionKeys() { return sectionKeys; }
- /**
- * Sets the section keys in the order that the usage help message should render the sections.
- * @see #sectionKeys
- * @since 3.9
- */
- public UsageMessageSpec sectionKeys(List<String> keys) { sectionKeys = Collections.unmodifiableList(new ArrayList<String>(keys)); return this; }
- /**
- * Returns the map of section keys and renderers used to construct the usage help message.
- * The usage help message can be customized by adding, replacing and removing section renderers from this map.
- * Sections can be reordered with the {@link #sectionKeys(List) sectionKeys setter}.
- * Sections that are either not in this map or not in the list returned by {@link #sectionKeys() sectionKeys} are omitted.
- * @see #sectionKeys
- * @since 3.9
- */
- public Map<String, IHelpSectionRenderer> sectionMap() { return helpSectionRendererMap; }
- /**
- * Sets the map of section keys and renderers used to construct the usage help message to a copy of the specified map.
- * @param map the mapping of section keys to their renderers, must be non-{@code null}.
- * @return this UsageMessageSpec for method chaining
- * @see #sectionKeys
- * @see #setHelpSectionMap(Map)
- * @since 3.9
- */
- public UsageMessageSpec sectionMap(Map<String, IHelpSectionRenderer> map) { this.helpSectionRendererMap = new HashMap<String, IHelpSectionRenderer>(map); return this; }
- /** Returns the {@code IHelpFactory} that is used to construct the usage help message.
- * @see #setHelpFactory(IHelpFactory)
- * @since 3.9
- */
- public IHelpFactory helpFactory() {
- if (helpFactory == null) {
- helpFactory = new DefaultHelpFactory();
- }
- return helpFactory;
- }
- /** Sets a new {@code IHelpFactory} to customize the usage help message.
- * @param helpFactory the new help factory. Must be non-{@code null}.
- * @return this {@code UsageMessageSpec} object, to allow method chaining
- */
- public UsageMessageSpec helpFactory(IHelpFactory helpFactory) {
- this.helpFactory = Assert.notNull(helpFactory, "helpFactory");
- return this;
- }
- private String str(String localized, String value, String defaultValue) {
- return localized != null ? localized : (value != null ? value : defaultValue);
- }
- private String[] arr(String[] localized, String[] value, String[] defaultValue) {
- return localized != null ? localized : (value != null ? value.clone() : defaultValue);
- }
- private String resourceStr(String key) { return messages == null ? null : messages.getString(key, null); }
- private String[] resourceArr(String key) { return messages == null ? null : messages.getStringArray(key, null); }
- /** Returns the optional heading preceding the header section. Initialized from {@link Command#headerHeading()}, or null. */
- public String headerHeading() { return str(resourceStr("usage.headerHeading"), headerHeading, DEFAULT_SINGLE_VALUE); }
- /** Returns the optional header lines displayed at the top of the help message. For subcommands, the first header line is
- * displayed in the list of commands. Values are initialized from {@link Command#header()}
- * if the {@code Command} annotation is present, otherwise this is an empty array and the help message has no
- * header. Applications may programmatically set this field to create a custom help message. */
- public String[] header() { return arr(resourceArr("usage.header"), header, DEFAULT_MULTI_LINE); }
- /** Returns the optional heading preceding the synopsis. Initialized from {@link Command#synopsisHeading()}, {@code "Usage: "} by default. */
- public String synopsisHeading() { return str(resourceStr("usage.synopsisHeading"), synopsisHeading, DEFAULT_SYNOPSIS_HEADING); }
- /** Returns whether the synopsis line(s) should show an abbreviated synopsis without detailed option names. */
- public boolean abbreviateSynopsis() { return (abbreviateSynopsis == null) ? DEFAULT_ABBREVIATE_SYNOPSIS : abbreviateSynopsis; }
- /** Returns the optional custom synopsis lines to use instead of the auto-generated synopsis.
- * Initialized from {@link Command#customSynopsis()} if the {@code Command} annotation is present,
- * otherwise this is an empty array and the synopsis is generated.
- * Applications may programmatically set this field to create a custom help message. */
- public String[] customSynopsis() { return arr(resourceArr("usage.customSynopsis"), customSynopsis, DEFAULT_MULTI_LINE); }
- /** Returns the optional heading preceding the description section. Initialized from {@link Command#descriptionHeading()}, or null. */
- public String descriptionHeading() { return str(resourceStr("usage.descriptionHeading"), descriptionHeading, DEFAULT_SINGLE_VALUE); }
- /** Returns the optional text lines to use as the description of the help message, displayed between the synopsis and the
- * options list. Initialized from {@link Command#description()} if the {@code Command} annotation is present,
- * otherwise this is an empty array and the help message has no description.
- * Applications may programmatically set this field to create a custom help message. */
- public String[] description() { return arr(resourceArr("usage.description"), description, DEFAULT_MULTI_LINE); }
- /** Returns the optional heading preceding the parameter list. Initialized from {@link Command#parameterListHeading()}, or null. */
- public String parameterListHeading() { return str(resourceStr("usage.parameterListHeading"), parameterListHeading, DEFAULT_SINGLE_VALUE); }
- /** Returns the optional heading preceding the options list. Initialized from {@link Command#optionListHeading()}, or null. */
- public String optionListHeading() { return str(resourceStr("usage.optionListHeading"), optionListHeading, DEFAULT_SINGLE_VALUE); }
- /** Returns whether the options list in the usage help message should be sorted alphabetically. */
- public boolean sortOptions() { return (sortOptions == null) ? DEFAULT_SORT_OPTIONS : sortOptions; }
- /** Returns the character used to prefix required options in the options list. */
- public char requiredOptionMarker() { return (requiredOptionMarker == null) ? DEFAULT_REQUIRED_OPTION_MARKER : requiredOptionMarker; }
- /** Returns whether the options list in the usage help message should show default values for all non-boolean options. */
- public boolean showDefaultValues() { return (showDefaultValues == null) ? DEFAULT_SHOW_DEFAULT_VALUES : showDefaultValues; }
- /**
- * Returns whether this command should be hidden from the usage help message of the parent command.
- * @return {@code true} if this command should not appear in the usage help message of the parent command
- */
- public boolean hidden() { return (hidden == null) ? DEFAULT_HIDDEN : hidden; }
- /** Returns the optional heading preceding the subcommand list. Initialized from {@link Command#commandListHeading()}. {@code "Commands:%n"} by default. */
- public String commandListHeading() { return str(resourceStr("usage.commandListHeading"), commandListHeading, DEFAULT_COMMAND_LIST_HEADING); }
- /** Returns the optional heading preceding the footer section. Initialized from {@link Command#footerHeading()}, or null. */
- public String footerHeading() { return str(resourceStr("usage.footerHeading"), footerHeading, DEFAULT_SINGLE_VALUE); }
- /** Returns the optional footer text lines displayed at the bottom of the help message. Initialized from
- * {@link Command#footer()} if the {@code Command} annotation is present, otherwise this is an empty array and
- * the help message has no footer.
- * Applications may programmatically set this field to create a custom help message. */
- public String[] footer() { return arr(resourceArr("usage.footer"), footer, DEFAULT_MULTI_LINE); }
- /** Sets the heading preceding the header section. Initialized from {@link Command#headerHeading()}, or null.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec headerHeading(String headerHeading) { this.headerHeading = headerHeading; return this; }
- /** Sets the optional header lines displayed at the top of the help message. For subcommands, the first header line is
- * displayed in the list of commands.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec header(String... header) { this.header = header; return this; }
- /** Sets the optional heading preceding the synopsis.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec synopsisHeading(String newValue) {synopsisHeading = newValue; return this;}
- /** Sets whether the synopsis line(s) should show an abbreviated synopsis without detailed option names.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec abbreviateSynopsis(boolean newValue) {abbreviateSynopsis = newValue; return this;}
- /** Sets the optional custom synopsis lines to use instead of the auto-generated synopsis.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec customSynopsis(String... customSynopsis) { this.customSynopsis = customSynopsis; return this; }
- /** Sets the heading preceding the description section.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec descriptionHeading(String newValue) {descriptionHeading = newValue; return this;}
- /** Sets the optional text lines to use as the description of the help message, displayed between the synopsis and the
- * options list.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec description(String... description) { this.description = description; return this; }
- /** Sets the optional heading preceding the parameter list.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec parameterListHeading(String newValue) {parameterListHeading = newValue; return this;}
- /** Sets the heading preceding the options list.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec optionListHeading(String newValue) {optionListHeading = newValue; return this;}
- /** Sets whether the options list in the usage help message should be sorted alphabetically.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec sortOptions(boolean newValue) {sortOptions = newValue; return this;}
- /** Sets the character used to prefix required options in the options list.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec requiredOptionMarker(char newValue) {requiredOptionMarker = newValue; return this;}
- /** Sets whether the options list in the usage help message should show default values for all non-boolean options.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec showDefaultValues(boolean newValue) {showDefaultValues = newValue; return this;}
- /**
- * Set the hidden flag on this command to control whether to show or hide it in the help usage text of the parent command.
- * @param value enable or disable the hidden flag
- * @return this UsageMessageSpec for method chaining
- * @see Command#hidden() */
- public UsageMessageSpec hidden(boolean value) { hidden = value; return this; }
- /** Sets the optional heading preceding the subcommand list.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec commandListHeading(String newValue) {commandListHeading = newValue; return this;}
- /** Sets the optional heading preceding the footer section.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec footerHeading(String newValue) {footerHeading = newValue; return this;}
- /** Sets the optional footer text lines displayed at the bottom of the help message.
- * @return this UsageMessageSpec for method chaining */
- public UsageMessageSpec footer(String... footer) { this.footer = footer; return this; }
- /** Returns the Messages for this usage help message specification, or {@code null}.
- * @return the Messages object that encapsulates this {@linkplain CommandSpec#resourceBundle() command's resource bundle}
- * @since 3.6 */
- public Messages messages() { return messages; }
- /** Sets the Messages for this usageMessage specification, and returns this UsageMessageSpec.
- * @param msgs the new Messages value that encapsulates this {@linkplain CommandSpec#resourceBundle() command's resource bundle}, may be {@code null}
- * @since 3.6 */
- public UsageMessageSpec messages(Messages msgs) { messages = msgs; return this; }
- void updateFromCommand(Command cmd, CommandSpec commandSpec) {
- if (isNonDefault(cmd.synopsisHeading(), DEFAULT_SYNOPSIS_HEADING)) {synopsisHeading = cmd.synopsisHeading();}
- if (isNonDefault(cmd.commandListHeading(), DEFAULT_COMMAND_LIST_HEADING)) {commandListHeading = cmd.commandListHeading();}
- if (isNonDefault(cmd.requiredOptionMarker(), DEFAULT_REQUIRED_OPTION_MARKER)) {requiredOptionMarker = cmd.requiredOptionMarker();}
- if (isNonDefault(cmd.abbreviateSynopsis(), DEFAULT_ABBREVIATE_SYNOPSIS)) {abbreviateSynopsis = cmd.abbreviateSynopsis();}
- if (isNonDefault(cmd.sortOptions(), DEFAULT_SORT_OPTIONS)) {sortOptions = cmd.sortOptions();}
- if (isNonDefault(cmd.showDefaultValues(), DEFAULT_SHOW_DEFAULT_VALUES)) {showDefaultValues = cmd.showDefaultValues();}
- if (isNonDefault(cmd.hidden(), DEFAULT_HIDDEN)) {hidden = cmd.hidden();}
- if (isNonDefault(cmd.customSynopsis(), DEFAULT_MULTI_LINE)) {customSynopsis = cmd.customSynopsis().clone();}
- if (isNonDefault(cmd.description(), DEFAULT_MULTI_LINE)) {description = cmd.description().clone();}
- if (isNonDefault(cmd.descriptionHeading(), DEFAULT_SINGLE_VALUE)) {descriptionHeading = cmd.descriptionHeading();}
- if (isNonDefault(cmd.header(), DEFAULT_MULTI_LINE)) {header = cmd.header().clone();}
- if (isNonDefault(cmd.headerHeading(), DEFAULT_SINGLE_VALUE)) {headerHeading = cmd.headerHeading();}
- if (isNonDefault(cmd.footer(), DEFAULT_MULTI_LINE)) {footer = cmd.footer().clone();}
- if (isNonDefault(cmd.footerHeading(), DEFAULT_SINGLE_VALUE)) {footerHeading = cmd.footerHeading();}
- if (isNonDefault(cmd.parameterListHeading(), DEFAULT_SINGLE_VALUE)) {parameterListHeading = cmd.parameterListHeading();}
- if (isNonDefault(cmd.optionListHeading(), DEFAULT_SINGLE_VALUE)) {optionListHeading = cmd.optionListHeading();}
- if (isNonDefault(cmd.usageHelpWidth(), DEFAULT_USAGE_WIDTH)) {width(cmd.usageHelpWidth());} // validate
- if (!empty(cmd.resourceBundle())) { // else preserve superclass bundle
- messages(new Messages(commandSpec, cmd.resourceBundle()));
- }
- }
- void initFromMixin(UsageMessageSpec mixin, CommandSpec commandSpec) {
- if (initializable(synopsisHeading, mixin.synopsisHeading(), DEFAULT_SYNOPSIS_HEADING)) {synopsisHeading = mixin.synopsisHeading();}
- if (initializable(commandListHeading, mixin.commandListHeading(), DEFAULT_COMMAND_LIST_HEADING)) {commandListHeading = mixin.commandListHeading();}
- if (initializable(requiredOptionMarker, mixin.requiredOptionMarker(), DEFAULT_REQUIRED_OPTION_MARKER)) {requiredOptionMarker = mixin.requiredOptionMarker();}
- if (initializable(abbreviateSynopsis, mixin.abbreviateSynopsis(), DEFAULT_ABBREVIATE_SYNOPSIS)) {abbreviateSynopsis = mixin.abbreviateSynopsis();}
- if (initializable(sortOptions, mixin.sortOptions(), DEFAULT_SORT_OPTIONS)) {sortOptions = mixin.sortOptions();}
- if (initializable(showDefaultValues, mixin.showDefaultValues(), DEFAULT_SHOW_DEFAULT_VALUES)) {showDefaultValues = mixin.showDefaultValues();}
- if (initializable(hidden, mixin.hidden(), DEFAULT_HIDDEN)) {hidden = mixin.hidden();}
- if (initializable(customSynopsis, mixin.customSynopsis(), DEFAULT_MULTI_LINE)) {customSynopsis = mixin.customSynopsis().clone();}
- if (initializable(description, mixin.description(), DEFAULT_MULTI_LINE)) {description = mixin.description().clone();}
- if (initializable(descriptionHeading, mixin.descriptionHeading(), DEFAULT_SINGLE_VALUE)) {descriptionHeading = mixin.descriptionHeading();}
- if (initializable(header, mixin.header(), DEFAULT_MULTI_LINE)) {header = mixin.header().clone();}
- if (initializable(headerHeading, mixin.headerHeading(), DEFAULT_SINGLE_VALUE)) {headerHeading = mixin.headerHeading();}
- if (initializable(footer, mixin.footer(), DEFAULT_MULTI_LINE)) {footer = mixin.footer().clone();}
- if (initializable(footerHeading, mixin.footerHeading(), DEFAULT_SINGLE_VALUE)) {footerHeading = mixin.footerHeading();}
- if (initializable(parameterListHeading, mixin.parameterListHeading(), DEFAULT_SINGLE_VALUE)) {parameterListHeading = mixin.parameterListHeading();}
- if (initializable(optionListHeading, mixin.optionListHeading(), DEFAULT_SINGLE_VALUE)) {optionListHeading = mixin.optionListHeading();}
- if (Messages.empty(messages)) { messages(Messages.copy(commandSpec, mixin.messages())); }
- }
- void initFrom(UsageMessageSpec settings, CommandSpec commandSpec) {
- description = settings.description;
- customSynopsis = settings.customSynopsis;
- header = settings.header;
- footer = settings.footer;
- abbreviateSynopsis = settings.abbreviateSynopsis;
- sortOptions = settings.sortOptions;
- showDefaultValues = settings.showDefaultValues;
- hidden = settings.hidden;
- requiredOptionMarker = settings.requiredOptionMarker;
- headerHeading = settings.headerHeading;
- synopsisHeading = settings.synopsisHeading;
- descriptionHeading = settings.descriptionHeading;
- parameterListHeading = settings.parameterListHeading;
- optionListHeading = settings.optionListHeading;
- commandListHeading = settings.commandListHeading;
- footerHeading = settings.footerHeading;
- width = settings.width;
- messages = Messages.copy(commandSpec, settings.messages());
- }
- }
- /** Models parser configuration specification.
- * @since 3.0 */
- public static class ParserSpec {
- /** Constant String holding the default separator between options and option parameters: <code>{@value}</code>.*/
- static final String DEFAULT_SEPARATOR = "=";
- private String separator;
- private boolean stopAtUnmatched = false;
- private boolean stopAtPositional = false;
- private String endOfOptionsDelimiter = "--";
- private boolean toggleBooleanFlags = true;
- private boolean overwrittenOptionsAllowed = false;
- private boolean unmatchedArgumentsAllowed = false;
- private boolean expandAtFiles = true;
- private boolean useSimplifiedAtFiles = false;
- private Character atFileCommentChar = '#';
- private boolean posixClusteredShortOptionsAllowed = true;
- private boolean unmatchedOptionsArePositionalParams = false;
- private boolean limitSplit = false;
- private boolean aritySatisfiedByAttachedOptionParam = false;
- private boolean collectErrors = false;
- private boolean caseInsensitiveEnumValuesAllowed = false;
- private boolean trimQuotes = shouldTrimQuotes();
- private boolean splitQuotedStrings = false;
- /** Returns the String to use as the separator between options and option parameters. {@code "="} by default,
- * initialized from {@link Command#separator()} if defined.*/
- public String separator() { return (separator == null) ? DEFAULT_SEPARATOR : separator; }
- /** @see CommandLine#isStopAtUnmatched() */
- public boolean stopAtUnmatched() { return stopAtUnmatched; }
- /** @see CommandLine#isStopAtPositional() */
- public boolean stopAtPositional() { return stopAtPositional; }
- /** @see CommandLine#getEndOfOptionsDelimiter()
- * @since 3.5 */
- public String endOfOptionsDelimiter() { return endOfOptionsDelimiter; }
- /** @see CommandLine#isToggleBooleanFlags() */
- public boolean toggleBooleanFlags() { return toggleBooleanFlags; }
- /** @see CommandLine#isOverwrittenOptionsAllowed() */
- public boolean overwrittenOptionsAllowed() { return overwrittenOptionsAllowed; }
- /** @see CommandLine#isUnmatchedArgumentsAllowed() */
- public boolean unmatchedArgumentsAllowed() { return unmatchedArgumentsAllowed; }
- /** @see CommandLine#isExpandAtFiles() */
- public boolean expandAtFiles() { return expandAtFiles; }
- /** @see CommandLine#getAtFileCommentChar()
- * @since 3.5 */
- public Character atFileCommentChar() { return atFileCommentChar; }
- /** @see CommandLine#isUseSimplifiedAtFiles()
- * @since 3.9 */
- public boolean useSimplifiedAtFiles() {
- String value = System.getProperty("picocli.useSimplifiedAtFiles");
- if (value != null) {
- return "".equals(value) || Boolean.valueOf(value);
- }
- return useSimplifiedAtFiles;
- }
- /** @see CommandLine#isPosixClusteredShortOptionsAllowed() */
- public boolean posixClusteredShortOptionsAllowed() { return posixClusteredShortOptionsAllowed; }
- /** @see CommandLine#isCaseInsensitiveEnumValuesAllowed()
- * @since 3.4 */
- public boolean caseInsensitiveEnumValuesAllowed() { return caseInsensitiveEnumValuesAllowed; }
- /** @see CommandLine#isTrimQuotes()
- * @since 3.7 */
- public boolean trimQuotes() { return trimQuotes; }
- /** @see CommandLine#isSplitQuotedStrings()
- * @since 3.7 */
- public boolean splitQuotedStrings() { return splitQuotedStrings; }
- /** @see CommandLine#isUnmatchedOptionsArePositionalParams() */
- public boolean unmatchedOptionsArePositionalParams() { return unmatchedOptionsArePositionalParams; }
- private boolean splitFirst() { return limitSplit(); }
- /** Returns true if arguments should be split first before any further processing and the number of
- * parts resulting from the split is limited to the max arity of the argument. */
- public boolean limitSplit() { return limitSplit; }
- /** Returns true if options with attached arguments should not consume subsequent arguments and should not validate arity. The default is {@code false}. */
- public boolean aritySatisfiedByAttachedOptionParam() { return aritySatisfiedByAttachedOptionParam; }
- /** Returns true if exceptions during parsing should be collected instead of thrown.
- * Multiple errors may be encountered during parsing. These can be obtained from {@link ParseResult#errors()}.
- * @since 3.2 */
- public boolean collectErrors() { return collectErrors; }
- /** Sets the String to use as the separator between options and option parameters.
- * @return this ParserSpec for method chaining */
- public ParserSpec separator(String separator) { this.separator = separator; return this; }
- /** @see CommandLine#setStopAtUnmatched(boolean) */
- public ParserSpec stopAtUnmatched(boolean stopAtUnmatched) { this.stopAtUnmatched = stopAtUnmatched; return this; }
- /** @see CommandLine#setStopAtPositional(boolean) */
- public ParserSpec stopAtPositional(boolean stopAtPositional) { this.stopAtPositional = stopAtPositional; return this; }
- /** @see CommandLine#setEndOfOptionsDelimiter(String)
- * @since 3.5 */
- public ParserSpec endOfOptionsDelimiter(String delimiter) { this.endOfOptionsDelimiter = Assert.notNull(delimiter, "end-of-options delimiter"); return this; }
- /** @see CommandLine#setToggleBooleanFlags(boolean) */
- public ParserSpec toggleBooleanFlags(boolean toggleBooleanFlags) { this.toggleBooleanFlags = toggleBooleanFlags; return this; }
- /** @see CommandLine#setOverwrittenOptionsAllowed(boolean) */
- public ParserSpec overwrittenOptionsAllowed(boolean overwrittenOptionsAllowed) { this.overwrittenOptionsAllowed = overwrittenOptionsAllowed; return this; }
- /** @see CommandLine#setUnmatchedArgumentsAllowed(boolean) */
- public ParserSpec unmatchedArgumentsAllowed(boolean unmatchedArgumentsAllowed) { this.unmatchedArgumentsAllowed = unmatchedArgumentsAllowed; return this; }
- /** @see CommandLine#setExpandAtFiles(boolean) */
- public ParserSpec expandAtFiles(boolean expandAtFiles) { this.expandAtFiles = expandAtFiles; return this; }
- /** @see CommandLine#setAtFileCommentChar(Character)
- * @since 3.5 */
- public ParserSpec atFileCommentChar(Character atFileCommentChar) { this.atFileCommentChar = atFileCommentChar; return this; }
- /** @see CommandLine#setUseSimplifiedAtFiles(boolean)
- * @since 3.9 */
- public ParserSpec useSimplifiedAtFiles(boolean useSimplifiedAtFiles) { this.useSimplifiedAtFiles = useSimplifiedAtFiles; return this; }
- /** @see CommandLine#setPosixClusteredShortOptionsAllowed(boolean) */
- public ParserSpec posixClusteredShortOptionsAllowed(boolean posixClusteredShortOptionsAllowed) { this.posixClusteredShortOptionsAllowed = posixClusteredShortOptionsAllowed; return this; }
- /** @see CommandLine#setCaseInsensitiveEnumValuesAllowed(boolean)
- * @since 3.4 */
- public ParserSpec caseInsensitiveEnumValuesAllowed(boolean caseInsensitiveEnumValuesAllowed) { this.caseInsensitiveEnumValuesAllowed = caseInsensitiveEnumValuesAllowed; return this; }
- /** @see CommandLine#setTrimQuotes(boolean)
- * @since 3.7 */
- public ParserSpec trimQuotes(boolean trimQuotes) { this.trimQuotes = trimQuotes; return this; }
- /** @see CommandLine#setSplitQuotedStrings(boolean)
- * @since 3.7 */
- public ParserSpec splitQuotedStrings(boolean splitQuotedStrings) { this.splitQuotedStrings = splitQuotedStrings; return this; }
- /** @see CommandLine#setUnmatchedOptionsArePositionalParams(boolean) */
- public ParserSpec unmatchedOptionsArePositionalParams(boolean unmatchedOptionsArePositionalParams) { this.unmatchedOptionsArePositionalParams = unmatchedOptionsArePositionalParams; return this; }
- /** Sets whether exceptions during parsing should be collected instead of thrown.
- * Multiple errors may be encountered during parsing. These can be obtained from {@link ParseResult#errors()}.
- * @since 3.2 */
- public ParserSpec collectErrors(boolean collectErrors) { this.collectErrors = collectErrors; return this; }
- /** Returns true if options with attached arguments should not consume subsequent arguments and should not validate arity. The default is {@code false}.*/
- public ParserSpec aritySatisfiedByAttachedOptionParam(boolean newValue) { aritySatisfiedByAttachedOptionParam = newValue; return this; }
- /** Sets whether arguments should be {@linkplain ArgSpec#splitRegex() split} first before any further processing.
- * If true, the original argument will only be split into as many parts as allowed by max arity. */
- public ParserSpec limitSplit(boolean limitSplit) { this.limitSplit = limitSplit; return this; }
- private boolean shouldTrimQuotes() {
- String value = System.getProperty("picocli.trimQuotes");
- if ("".equals(value)) { value = "true"; }
- return Boolean.valueOf(value);
- }
- void initSeparator(String value) { if (initializable(separator, value, DEFAULT_SEPARATOR)) {separator = value;} }
- void updateSeparator(String value) { if (isNonDefault(value, DEFAULT_SEPARATOR)) {separator = value;} }
- public String toString() {
- return String.format("posixClusteredShortOptionsAllowed=%s, stopAtPositional=%s, stopAtUnmatched=%s, " +
- "separator=%s, overwrittenOptionsAllowed=%s, unmatchedArgumentsAllowed=%s, expandAtFiles=%s, " +
- "atFileCommentChar=%s, useSimplifiedAtFiles=%s, endOfOptionsDelimiter=%s, limitSplit=%s, aritySatisfiedByAttachedOptionParam=%s, " +
- "toggleBooleanFlags=%s, unmatchedOptionsArePositionalParams=%s, collectErrors=%s," +
- "caseInsensitiveEnumValuesAllowed=%s, trimQuotes=%s, splitQuotedStrings=%s",
- posixClusteredShortOptionsAllowed, stopAtPositional, stopAtUnmatched,
- separator, overwrittenOptionsAllowed, unmatchedArgumentsAllowed, expandAtFiles,
- atFileCommentChar, useSimplifiedAtFiles, endOfOptionsDelimiter, limitSplit, aritySatisfiedByAttachedOptionParam,
- toggleBooleanFlags, unmatchedOptionsArePositionalParams, collectErrors,
- caseInsensitiveEnumValuesAllowed, trimQuotes, splitQuotedStrings);
- }
- void initFrom(ParserSpec settings) {
- separator = settings.separator;
- stopAtUnmatched = settings.stopAtUnmatched;
- stopAtPositional = settings.stopAtPositional;
- endOfOptionsDelimiter = settings.endOfOptionsDelimiter;
- toggleBooleanFlags = settings.toggleBooleanFlags;
- overwrittenOptionsAllowed = settings.overwrittenOptionsAllowed;
- unmatchedArgumentsAllowed = settings.unmatchedArgumentsAllowed;
- expandAtFiles = settings.expandAtFiles;
- atFileCommentChar = settings.atFileCommentChar;
- posixClusteredShortOptionsAllowed = settings.posixClusteredShortOptionsAllowed;
- unmatchedOptionsArePositionalParams = settings.unmatchedOptionsArePositionalParams;
- limitSplit = settings.limitSplit;
- aritySatisfiedByAttachedOptionParam = settings.aritySatisfiedByAttachedOptionParam;
- collectErrors = settings.collectErrors;
- caseInsensitiveEnumValuesAllowed = settings.caseInsensitiveEnumValuesAllowed;
- trimQuotes = settings.trimQuotes;
- splitQuotedStrings = settings.splitQuotedStrings;
- }
- }
- /** Models the shared attributes of {@link OptionSpec} and {@link PositionalParamSpec}.
- * @since 3.0 */
- public abstract static class ArgSpec {
- static final String DESCRIPTION_VARIABLE_DEFAULT_VALUE = "${DEFAULT-VALUE}";
- static final String DESCRIPTION_VARIABLE_COMPLETION_CANDIDATES = "${COMPLETION-CANDIDATES}";
- private static final String NO_DEFAULT_VALUE = "__no_default_value__";
- // help-related fields
- private final boolean hidden;
- private final String paramLabel;
- private final boolean hideParamSyntax;
- private final String[] description;
- private final String descriptionKey;
- private final Help.Visibility showDefaultValue;
- private Messages messages;
- CommandSpec commandSpec;
- private ArgGroupSpec group;
- private final Object userObject;
- // parser fields
- private final boolean interactive;
- private final boolean required;
- private final String splitRegex;
- private final ITypeInfo typeInfo;
- private final ITypeConverter<?>[] converters;
- private final Iterable<String> completionCandidates;
- private final String defaultValue;
- private final Object initialValue;
- private final boolean hasInitialValue;
- private final IGetter getter;
- private final ISetter setter;
- private final IScope scope;
- private final Range arity;
- private List<String> stringValues = new ArrayList<String>();
- private List<String> originalStringValues = new ArrayList<String>();
- protected String toString;
- private List<Object> typedValues = new ArrayList<Object>();
- Map<Integer, Object> typedValueAtPosition = new TreeMap<Integer, Object>();
- /** Constructs a new {@code ArgSpec}. */
- private <T extends Builder<T>> ArgSpec(Builder<T> builder) {
- userObject = builder.userObject;
- description = builder.description == null ? new String[0] : builder.description;
- descriptionKey = builder.descriptionKey;
- splitRegex = builder.splitRegex == null ? "" : builder.splitRegex;
- paramLabel = empty(builder.paramLabel) ? "PARAM" : builder.paramLabel;
- hideParamSyntax = builder.hideParamSyntax;
- converters = builder.converters == null ? new ITypeConverter<?>[0] : builder.converters;
- showDefaultValue = builder.showDefaultValue == null ? Help.Visibility.ON_DEMAND : builder.showDefaultValue;
- hidden = builder.hidden;
- interactive = builder.interactive;
- initialValue = builder.initialValue;
- hasInitialValue = builder.hasInitialValue;
- defaultValue = NO_DEFAULT_VALUE.equals(builder.defaultValue) ? null : builder.defaultValue;
- required = builder.required && defaultValue == null; //#261 not required if it has a default
- toString = builder.toString;
- getter = builder.getter;
- setter = builder.setter;
- scope = builder.scope;
- Range tempArity = builder.arity;
- if (tempArity == null) {
- if (isOption()) {
- tempArity = (builder.type == null || isBoolean(builder.type)) ? Range.valueOf("0") : Range.valueOf("1");
- } else {
- tempArity = Range.valueOf("1");
- }
- tempArity = tempArity.unspecified(true);
- }
- arity = tempArity;
- if (builder.typeInfo == null) {
- this.typeInfo = RuntimeTypeInfo.create(builder.type, builder.auxiliaryTypes,
- Collections.<String>emptyList(), arity, (isOption() ? boolean.class : String.class));
- } else {
- this.typeInfo = builder.typeInfo;
- }
- if (builder.completionCandidates == null && typeInfo.isEnum()) {
- List<String> list = new ArrayList<String>();
- for (Object c : typeInfo.getEnumConstantNames()) { list.add(c.toString()); }
- completionCandidates = Collections.unmodifiableList(list);
- } else {
- completionCandidates = builder.completionCandidates;
- }
- if (interactive && (arity.min != 1 || arity.max != 1)) {
- throw new InitializationException("Interactive options and positional parameters are only supported for arity=1, not for arity=" + arity);
- }
- }
- void applyInitialValue(Tracer tracer) {
- if (hasInitialValue()) {
- try {
- setter().set(initialValue());
- tracer.debug("Set initial value for %s of type %s to %s.%n", this, type(), String.valueOf(initialValue()));
- } catch (Exception ex) {
- tracer.warn("Could not set initial value for %s of type %s to %s: %s%n", this, type(), String.valueOf(initialValue()), ex);
- }
- } else {
- tracer.debug("Initial value not available for %s%n", this);
- }
- }
- /** Returns whether this is a required option or positional parameter.
- * If this argument is part of a {@linkplain ArgGroup group}, this method returns whether this argument is required <em>within the group</em> (so it is not necessarily a required argument for the command).
- * @see Option#required() */
- public boolean required() { return required; }
- /** Returns whether this option will prompt the user to enter a value on the command line.
- * @see Option#interactive() */
- public boolean interactive() { return interactive; }
- /** Returns the description template of this option, before variables are rendered.
- * @see Option#description() */
- public String[] description() { return description.clone(); }
- /** Returns the description key of this arg spec, used to get the description from a resource bundle.
- * @see Option#descriptionKey()
- * @see Parameters#descriptionKey()
- * @since 3.6 */
- public String descriptionKey() { return descriptionKey; }
- /** Returns the description of this option, after variables are rendered. Used when generating the usage documentation.
- * @see Option#description()
- * @since 3.2 */
- public String[] renderedDescription() {
- String[] desc = description();
- if (desc.length == 0) { return desc; }
- StringBuilder candidates = new StringBuilder();
- if (completionCandidates() != null) {
- for (String c : completionCandidates()) {
- if (candidates.length() > 0) { candidates.append(", "); }
- candidates.append(c);
- }
- }
- String defaultValueString = defaultValueString();
- String[] result = new String[desc.length];
- for (int i = 0; i < desc.length; i++) {
- result[i] = format(desc[i].replace(DESCRIPTION_VARIABLE_DEFAULT_VALUE, defaultValueString.replace("%", "%%"))
- .replace(DESCRIPTION_VARIABLE_COMPLETION_CANDIDATES, candidates.toString()));
- }
- return result;
- }
- /** Returns how many arguments this option or positional parameter requires.
- * @see Option#arity() */
- public Range arity() { return arity; }
- /** Returns the name of the option or positional parameter used in the usage help message.
- * @see Option#paramLabel() {@link Parameters#paramLabel()} */
- public String paramLabel() { return paramLabel; }
- /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
- * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
- * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
- * @since 3.6.0 */
- public boolean hideParamSyntax() { return hideParamSyntax; }
- /** Returns auxiliary type information used when the {@link #type()} is a generic {@code Collection}, {@code Map} or an abstract class.
- * @see Option#type() */
- public Class<?>[] auxiliaryTypes() { return typeInfo.getAuxiliaryTypes(); }
- /** Returns one or more {@link CommandLine.ITypeConverter type converters} to use to convert the command line
- * argument into a strongly typed value (or key-value pair for map fields). This is useful when a particular
- * option or positional parameter should use a custom conversion that is different from the normal conversion for the arg spec's type.
- * @see Option#converter() */
- public ITypeConverter<?>[] converters() { return converters.clone(); }
- /** Returns a regular expression to split option parameter values or {@code ""} if the value should not be split.
- * @see Option#split() */
- public String splitRegex() { return splitRegex; }
- /** Returns whether this option should be excluded from the usage message.
- * @see Option#hidden() */
- public boolean hidden() { return hidden; }
- /** Returns the type to convert the option or positional parameter to before {@linkplain #setValue(Object) setting} the value. */
- public Class<?> type() { return typeInfo.getType(); }
- /** Returns the {@code ITypeInfo} that can be used both at compile time (by annotation processors) and at runtime.
- * @since 4.0 */
- public ITypeInfo typeInfo() { return typeInfo; }
- /** Returns the user object associated with this option or positional parameters.
- * @return may return the annotated program element, or some other useful object
- * @since 4.0 */
- public Object userObject() { return userObject; }
- /** Returns the default value of this option or positional parameter, before splitting and type conversion.
- * This method returns the programmatically set value; this may differ from the default value that is actually used:
- * if this ArgSpec is part of a CommandSpec with a {@link IDefaultValueProvider}, picocli will first try to obtain
- * the default value from the default value provider, and this method is only called if the default provider is
- * {@code null} or returned a {@code null} value.
- * @return the programmatically set default value of this option/positional parameter,
- * returning {@code null} means this option or positional parameter does not have a default
- * @see CommandSpec#defaultValueProvider()
- */
- public String defaultValue() { return defaultValue; }
- /** Returns the initial value this option or positional parameter. If {@link #hasInitialValue()} is true,
- * the option will be reset to the initial value before parsing (regardless of whether a default value exists),
- * to clear values that would otherwise remain from parsing previous input. */
- public Object initialValue() { return initialValue; }
- /** Determines whether the option or positional parameter will be reset to the {@link #initialValue()}
- * before parsing new input.*/
- public boolean hasInitialValue() { return hasInitialValue; }
- /** Returns whether this option or positional parameter's default value should be shown in the usage help. */
- public Help.Visibility showDefaultValue() { return showDefaultValue; }
- /** Returns the default value String displayed in the description. If this ArgSpec is part of a
- * CommandSpec with a {@link IDefaultValueProvider}, this method will first try to obtain
- * the default value from the default value provider; if the provider is {@code null} or if it
- * returns a {@code null} value, then next any value set to {@link ArgSpec#defaultValue()}
- * is returned, and if this is also {@code null}, finally the {@linkplain ArgSpec#initialValue() initial value} is returned.
- * @see CommandSpec#defaultValueProvider()
- * @see ArgSpec#defaultValue() */
- public String defaultValueString() {
- String fromProvider = defaultValueFromProvider();
- String defaultVal = fromProvider == null ? this.defaultValue() : fromProvider;
- Object value = defaultVal == null ? initialValue() : defaultVal;
- if (value != null && value.getClass().isArray()) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < Array.getLength(value); i++) {
- sb.append(i > 0 ? ", " : "").append(Array.get(value, i));
- }
- return sb.insert(0, "[").append("]").toString();
- }
- return String.valueOf(value);
- }
- private String defaultValueFromProvider() {
- String fromProvider = null;
- IDefaultValueProvider defaultValueProvider = null;
- try {
- defaultValueProvider = commandSpec.defaultValueProvider();
- fromProvider = defaultValueProvider == null ? null : defaultValueProvider.defaultValue(this);
- } catch (Exception ex) {
- new Tracer().info("Error getting default value for %s from %s: %s", this, defaultValueProvider, ex);
- }
- return fromProvider;
- }
- /** Returns the explicitly set completion candidates for this option or positional parameter, valid enum
- * constant names, or {@code null} if this option or positional parameter does not have any completion
- * candidates and its type is not an enum.
- * @return the completion candidates for this option or positional parameter, valid enum constant names,
- * or {@code null}
- * @since 3.2 */
- public Iterable<String> completionCandidates() { return completionCandidates; }
- /** Returns the {@link IGetter} that is responsible for supplying the value of this argument. */
- public IGetter getter() { return getter; }
- /** Returns the {@link ISetter} that is responsible for modifying the value of this argument. */
- public ISetter setter() { return setter; }
- /** Returns the {@link IScope} that determines on which object to set the value (or from which object to get the value) of this argument. */
- public IScope scope() { return scope; }
- /** Returns the current value of this argument. Delegates to the current {@link #getter()}. */
- public <T> T getValue() throws PicocliException {
- try {
- return getter.get();
- } catch (PicocliException ex) { throw ex;
- } catch (Exception ex) { throw new PicocliException("Could not get value for " + this + ": " + ex, ex);
- }
- }
- /** Sets the value of this argument to the specified value and returns the previous value. Delegates to the current {@link #setter()}. */
- public <T> T setValue(T newValue) throws PicocliException {
- try {
- return setter.set(newValue);
- } catch (PicocliException ex) { throw ex;
- } catch (Exception ex) { throw new PicocliException("Could not set value (" + newValue + ") for " + this + ": " + ex, ex);
- }
- }
- /** Sets the value of this argument to the specified value and returns the previous value. Delegates to the current {@link #setter()}.
- * @deprecated use {@link #setValue(Object)} instead. This was a design mistake.
- * @since 3.5 */
- @Deprecated public <T> T setValue(T newValue, CommandLine commandLine) throws PicocliException {
- return setValue(newValue);
- }
- /** Returns {@code true} if this argument's {@link #type()} is an array, a {@code Collection} or a {@code Map}, {@code false} otherwise. */
- public boolean isMultiValue() { return typeInfo.isMultiValue(); }
- /** Returns {@code true} if this argument is a named option, {@code false} otherwise. */
- public abstract boolean isOption();
- /** Returns {@code true} if this argument is a positional parameter, {@code false} otherwise. */
- public abstract boolean isPositional();
- /** Returns the groups this option or positional parameter belongs to, or {@code null} if this option is not part of a group.
- * @since 4.0 */
- public ArgGroupSpec group() { return group; }
- /** Returns the untyped command line arguments matched by this option or positional parameter spec.
- * @return the matched arguments after {@linkplain #splitRegex() splitting}, but before type conversion.
- * For map properties, {@code "key=value"} values are split into the key and the value part. */
- public List<String> stringValues() { return Collections.unmodifiableList(stringValues); }
- /** Returns the typed command line arguments matched by this option or positional parameter spec.
- * @return the matched arguments after {@linkplain #splitRegex() splitting} and type conversion.
- * For map properties, {@code "key=value"} values are split into the key and the value part. */
- public List<Object> typedValues() { return Collections.unmodifiableList(typedValues); }
- /** Sets the {@code stringValues} to a new list instance. */
- protected void resetStringValues() { stringValues = new ArrayList<String>(); }
- /** Returns the original command line arguments matched by this option or positional parameter spec.
- * @return the matched arguments as found on the command line: empty Strings for options without value, the
- * values have not been {@linkplain #splitRegex() split}, and for map properties values may look like {@code "key=value"}*/
- public List<String> originalStringValues() { return Collections.unmodifiableList(originalStringValues); }
- /** Sets the {@code originalStringValues} to a new list instance. */
- protected void resetOriginalStringValues() { originalStringValues = new ArrayList<String>(); }
- /** Returns whether the default for this option or positional parameter should be shown, potentially overriding the specified global setting.
- * @param usageHelpShowDefaults whether the command's UsageMessageSpec is configured to show default values. */
- protected boolean internalShowDefaultValue(boolean usageHelpShowDefaults) {
- if (showDefaultValue() == Help.Visibility.ALWAYS) { return true; } // override global usage help setting
- if (showDefaultValue() == Help.Visibility.NEVER) { return false; } // override global usage help setting
- if (initialValue == null && defaultValue() == null && defaultValueFromProvider() == null) { return false; } // no default value to show
- return usageHelpShowDefaults && !isBoolean(type());
- }
- /** Returns the Messages for this arg specification, or {@code null}.
- * @since 3.6 */
- public Messages messages() { return messages; }
- /** Sets the Messages for this ArgSpec, and returns this ArgSpec.
- * @param msgs the new Messages value, may be {@code null}
- * @see Command#resourceBundle()
- * @see OptionSpec#description()
- * @see PositionalParamSpec#description()
- * @since 3.6 */
- public ArgSpec messages(Messages msgs) { messages = msgs; return this; }
- /** Returns a string respresentation of this option or positional parameter. */
- public String toString() { return toString; }
- String[] splitValue(String value, ParserSpec parser, Range arity, int consumed) {
- if (splitRegex().length() == 0) { return new String[] {value}; }
- int limit = parser.limitSplit() ? Math.max(arity.max - consumed, 0) : 0;
- if (parser.splitQuotedStrings()) {
- return debug(value.split(splitRegex(), limit), "Split (ignoring quotes)", value);
- }
- return debug(splitRespectingQuotedStrings(value, limit, parser, this, splitRegex()), "Split", value);
- }
- private String[] debug(String[] result, String msg, String value) {
- Tracer t = new Tracer();
- if (t.isDebug()) {t.debug("%s with regex '%s' resulted in %s parts: %s%n", msg, splitRegex(), result.length, Arrays.asList(result));}
- return result;
- }
- // @since 3.7
- private static String[] splitRespectingQuotedStrings(String value, int limit, ParserSpec parser, ArgSpec argSpec, String splitRegex) {
- StringBuilder splittable = new StringBuilder();
- StringBuilder temp = new StringBuilder();
- StringBuilder current = splittable;
- Queue<String> quotedValues = new LinkedList<String>();
- boolean escaping = false, inQuote = false;
- for (int ch = 0, i = 0; i < value.length(); i += Character.charCount(ch)) {
- ch = value.codePointAt(i);
- switch (ch) {
- case '\\': escaping = !escaping; break;
- case '\"':
- if (!escaping) {
- inQuote = !inQuote;
- current = inQuote ? temp : splittable;
- if (inQuote) {
- splittable.appendCodePoint(ch);
- continue;
- } else {
- quotedValues.add(temp.toString());
- temp.setLength(0);
- }
- }
- break;
- default: escaping = false; break;
- }
- current.appendCodePoint(ch);
- }
- if (temp.length() > 0) {
- new Tracer().warn("Unbalanced quotes in [%s] for %s (value=%s)%n", temp, argSpec, value);
- quotedValues.add(temp.toString());
- temp.setLength(0);
- }
- String[] result = splittable.toString().split(splitRegex, limit);
- for (int i = 0; i < result.length; i++) {
- result[i] = restoreQuotedValues(result[i], quotedValues, parser);
- }
- if (!quotedValues.isEmpty()) {
- new Tracer().warn("Unable to respect quotes while splitting value %s for %s (unprocessed remainder: %s)%n", value, argSpec, quotedValues);
- return value.split(splitRegex, limit);
- }
- return result;
- }
- private static String restoreQuotedValues(String part, Queue<String> quotedValues, ParserSpec parser) {
- StringBuilder result = new StringBuilder();
- boolean escaping = false, inQuote = false, skip = false;
- for (int ch = 0, i = 0; i < part.length(); i += Character.charCount(ch)) {
- ch = part.codePointAt(i);
- switch (ch) {
- case '\\': escaping = !escaping; break;
- case '\"':
- if (!escaping) {
- inQuote = !inQuote;
- if (!inQuote) { result.append(quotedValues.remove()); }
- skip = parser.trimQuotes();
- }
- break;
- default: escaping = false; break;
- }
- if (!skip) { result.appendCodePoint(ch); }
- skip = false;
- }
- return result.toString();
- }
- protected boolean equalsImpl(ArgSpec other) {
- boolean result = Assert.equals(this.defaultValue, other.defaultValue)
- && Assert.equals(this.arity, other.arity)
- && Assert.equals(this.hidden, other.hidden)
- && Assert.equals(this.paramLabel, other.paramLabel)
- && Assert.equals(this.hideParamSyntax, other.hideParamSyntax)
- && Assert.equals(this.required, other.required)
- && Assert.equals(this.splitRegex, other.splitRegex)
- && Arrays.equals(this.description, other.description)
- && Assert.equals(this.descriptionKey, other.descriptionKey)
- && this.typeInfo.equals(other.typeInfo)
- ;
- return result;
- }
- protected int hashCodeImpl() {
- return 17
- + 37 * Assert.hashCode(defaultValue)
- + 37 * Assert.hashCode(arity)
- + 37 * Assert.hashCode(hidden)
- + 37 * Assert.hashCode(paramLabel)
- + 37 * Assert.hashCode(hideParamSyntax)
- + 37 * Assert.hashCode(required)
- + 37 * Assert.hashCode(splitRegex)
- + 37 * Arrays.hashCode(description)
- + 37 * Assert.hashCode(descriptionKey)
- + 37 * typeInfo.hashCode()
- ;
- }
- private static String describe(Collection<ArgSpec> args) {
- StringBuilder sb = new StringBuilder();
- for (ArgSpec arg : args) {
- if (sb.length() > 0) { sb.append(", "); }
- sb.append(describe(arg, "="));
- }
- return sb.toString();
- }
- /** Returns a description of the option or positional arg, e.g. {@code -a=<a>}
- * @param separator separator between arg and arg parameter label, usually '=' */
- private static String describe(ArgSpec argSpec, String separator) {
- return describe(argSpec, separator, argSpec.paramLabel());
- }
- /** Returns a description of the option or positional arg
- * @param separator separator between arg and arg parameter value, usually '='
- * @param value the value to append after the separator*/
- private static String describe(ArgSpec argSpec, String separator, String value) {
- String prefix = (argSpec.isOption())
- ? ((OptionSpec) argSpec).longestName()
- : "params[" + ((PositionalParamSpec) argSpec).index() + "]";
- return argSpec.arity().min > 0 ? prefix + separator + value : prefix;
- }
- abstract static class Builder<T extends Builder<T>> {
- private Object userObject;
- private Range arity;
- private String[] description;
- private String descriptionKey;
- private boolean required;
- private boolean interactive;
- private String paramLabel;
- private boolean hideParamSyntax;
- private String splitRegex;
- private boolean hidden;
- private Class<?> type;
- private Class<?>[] auxiliaryTypes;
- private ITypeInfo typeInfo;
- private ITypeConverter<?>[] converters;
- private String defaultValue;
- private Object initialValue;
- private boolean hasInitialValue = true;
- private Help.Visibility showDefaultValue;
- private Iterable<String> completionCandidates;
- private String toString;
- private IGetter getter = new ObjectBinding();
- private ISetter setter = (ISetter) getter;
- private IScope scope = new ObjectScope(null);
- Builder() {}
- Builder(ArgSpec original) {
- userObject = original.userObject;
- arity = original.arity;
- converters = original.converters;
- defaultValue = original.defaultValue;
- description = original.description;
- getter = original.getter;
- setter = original.setter;
- hidden = original.hidden;
- paramLabel = original.paramLabel;
- hideParamSyntax = original.hideParamSyntax;
- required = original.required;
- interactive = original.interactive;
- showDefaultValue = original.showDefaultValue;
- completionCandidates = original.completionCandidates;
- splitRegex = original.splitRegex;
- toString = original.toString;
- descriptionKey = original.descriptionKey;
- setTypeInfo(original.typeInfo);
- }
- Builder(IAnnotatedElement source) {
- userObject = source.userObject();
- setTypeInfo(source.getTypeInfo());
- toString = source.getToString();
- getter = source.getter();
- setter = source.setter();
- scope = source.scope();
- hasInitialValue = source.hasInitialValue();
- try { initialValue = source.getter().get(); } catch (Exception ex) { initialValue = null; hasInitialValue = false; }
- }
- Builder(Option option, IAnnotatedElement source, IFactory factory) {
- this(source);
- arity = Range.optionArity(source);
- required = option.required();
- paramLabel = inferLabel(option.paramLabel(), source.getName(), source.getTypeInfo());
- hideParamSyntax = option.hideParamSyntax();
- interactive = option.interactive();
- description = option.description();
- descriptionKey = option.descriptionKey();
- splitRegex = option.split();
- hidden = option.hidden();
- defaultValue = option.defaultValue();
- showDefaultValue = option.showDefaultValue();
- if (factory != null) {
- converters = DefaultFactory.createConverter(factory, option.converter());
- if (!NoCompletionCandidates.class.equals(option.completionCandidates())) {
- completionCandidates = DefaultFactory.createCompletionCandidates(factory, option.completionCandidates());
- }
- }
- }
- Builder(Parameters parameters, IAnnotatedElement source, IFactory factory) {
- this(source);
- arity = Range.parameterArity(source);
- required = arity.min > 0;
- // method parameters may be positional parameters without @Parameters annotation
- if (parameters == null) {
- paramLabel = inferLabel(null, source.getName(), source.getTypeInfo());
- } else {
- paramLabel = inferLabel(parameters.paramLabel(), source.getName(), source.getTypeInfo());
- hideParamSyntax = parameters.hideParamSyntax();
- interactive = parameters.interactive();
- description = parameters.description();
- descriptionKey = parameters.descriptionKey();
- splitRegex = parameters.split();
- hidden = parameters.hidden();
- defaultValue = parameters.defaultValue();
- showDefaultValue = parameters.showDefaultValue();
- if (factory != null) { // annotation processors will pass a null factory
- converters = DefaultFactory.createConverter(factory, parameters.converter());
- if (!NoCompletionCandidates.class.equals(parameters.completionCandidates())) {
- completionCandidates = DefaultFactory.createCompletionCandidates(factory, parameters.completionCandidates());
- }
- }
- }
- }
- private static String inferLabel(String label, String fieldName, ITypeInfo typeInfo) {
- if (!empty(label)) { return label.trim(); }
- String name = fieldName;
- if (typeInfo.isMap()) { // #195 better param labels for map fields
- List<ITypeInfo> aux = typeInfo.getAuxiliaryTypeInfos();
- if (aux.size() < 2 || aux.get(0) == null || aux.get(1) == null) {
- name = "String=String";
- } else { name = aux.get(0).getClassSimpleName() + "=" + aux.get(1).getClassSimpleName(); }
- }
- return "<" + name + ">";
- }
- public abstract ArgSpec build();
- protected abstract T self(); // subclasses must override to return "this"
- /** Returns whether this is a required option or positional parameter.
- * @see Option#required() */
- public boolean required() { return required; }
- /** Returns whether this option prompts the user to enter a value on the command line.
- * @see Option#interactive() */
- public boolean interactive() { return interactive; }
- /** Returns the description of this option, used when generating the usage documentation.
- * @see Option#description() */
- public String[] description() { return description; }
- /** Returns the description key of this arg spec, used to get the description from a resource bundle.
- * @see Option#descriptionKey()
- * @see Parameters#descriptionKey()
- * @since 3.6 */
- public String descriptionKey() { return descriptionKey; }
- /** Returns how many arguments this option or positional parameter requires.
- * @see Option#arity() */
- public Range arity() { return arity; }
- /** Returns the name of the option or positional parameter used in the usage help message.
- * @see Option#paramLabel() {@link Parameters#paramLabel()} */
- public String paramLabel() { return paramLabel; }
- /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
- * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
- * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
- * @since 3.6.0 */
- public boolean hideParamSyntax() { return hideParamSyntax; }
- /** Returns auxiliary type information used when the {@link #type()} is a generic {@code Collection}, {@code Map} or an abstract class.
- * @see Option#type() */
- public Class<?>[] auxiliaryTypes() { return auxiliaryTypes; }
- /** Returns one or more {@link CommandLine.ITypeConverter type converters} to use to convert the command line
- * argument into a strongly typed value (or key-value pair for map fields). This is useful when a particular
- * option or positional parameter should use a custom conversion that is different from the normal conversion for the arg spec's type.
- * @see Option#converter() */
- public ITypeConverter<?>[] converters() { return converters; }
- /** Returns a regular expression to split option parameter values or {@code ""} if the value should not be split.
- * @see Option#split() */
- public String splitRegex() { return splitRegex; }
- /** Returns whether this option should be excluded from the usage message.
- * @see Option#hidden() */
- public boolean hidden() { return hidden; }
- /** Returns the type to convert the option or positional parameter to before {@linkplain #setValue(Object) setting} the value. */
- public Class<?> type() { return type; }
- /** Returns the type info for this option or positional parameter.
- * @return type information that does not require {@code Class} objects and be constructed both at runtime and compile time
- * @since 4.0
- */
- public ITypeInfo typeInfo() { return typeInfo; }
- /** Returns the user object associated with this option or positional parameters.
- * @return may return the annotated program element, or some other useful object
- * @since 4.0 */
- public Object userObject() { return userObject; }
- /** Returns the default value of this option or positional parameter, before splitting and type conversion.
- * A value of {@code null} means this option or positional parameter does not have a default. */
- public String defaultValue() { return defaultValue; }
- /** Returns the initial value this option or positional parameter. If {@link #hasInitialValue()} is true,
- * the option will be reset to the initial value before parsing (regardless of whether a default value exists),
- * to clear values that would otherwise remain from parsing previous input. */
- public Object initialValue() { return initialValue; }
- /** Determines whether the option or positional parameter will be reset to the {@link #initialValue()}
- * before parsing new input.*/
- public boolean hasInitialValue() { return hasInitialValue; }
- /** Returns whether this option or positional parameter's default value should be shown in the usage help. */
- public Help.Visibility showDefaultValue() { return showDefaultValue; }
- /** Returns the completion candidates for this option or positional parameter, or {@code null}.
- * @since 3.2 */
- public Iterable<String> completionCandidates() { return completionCandidates; }
- /** Returns the {@link IGetter} that is responsible for supplying the value of this argument. */
- public IGetter getter() { return getter; }
- /** Returns the {@link ISetter} that is responsible for modifying the value of this argument. */
- public ISetter setter() { return setter; }
- /** Returns the {@link IScope} that determines where the setter sets the value (or the getter gets the value) of this argument. */
- public IScope scope() { return scope; }
- public String toString() { return toString; }
- /** Sets whether this is a required option or positional parameter, and returns this builder. */
- public T required(boolean required) { this.required = required; return self(); }
- /** Sets whether this option prompts the user to enter a value on the command line, and returns this builder. */
- public T interactive(boolean interactive) { this.interactive = interactive; return self(); }
- /** Sets the description of this option, used when generating the usage documentation, and returns this builder.
- * @see Option#description() */
- public T description(String... description) { this.description = Assert.notNull(description, "description").clone(); return self(); }
- /** Sets the description key that is used to look up the description in a resource bundle, and returns this builder.
- * @see Option#descriptionKey()
- * @see Parameters#descriptionKey()
- * @since 3.6 */
- public T descriptionKey(String descriptionKey) { this.descriptionKey = descriptionKey; return self(); }
- /** Sets how many arguments this option or positional parameter requires, and returns this builder. */
- public T arity(String range) { return arity(Range.valueOf(range)); }
- /** Sets how many arguments this option or positional parameter requires, and returns this builder. */
- public T arity(Range arity) { this.arity = Assert.notNull(arity, "arity"); return self(); }
- /** Sets the name of the option or positional parameter used in the usage help message, and returns this builder. */
- public T paramLabel(String paramLabel) { this.paramLabel = Assert.notNull(paramLabel, "paramLabel"); return self(); }
- /** Sets whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
- * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
- * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
- * @since 3.6.0 */
- public T hideParamSyntax(boolean hideParamSyntax) { this.hideParamSyntax = hideParamSyntax; return self(); }
- /** Sets auxiliary type information, and returns this builder.
- * @param types the element type(s) when the {@link #type()} is a generic {@code Collection} or a {@code Map};
- * or the concrete type when the {@link #type()} is an abstract class. */
- public T auxiliaryTypes(Class<?>... types) { this.auxiliaryTypes = Assert.notNull(types, "types").clone(); return self(); }
- /** Sets option/positional param-specific converter (or converters for Maps), and returns this builder. */
- public T converters(ITypeConverter<?>... cs) { this.converters = Assert.notNull(cs, "type converters").clone(); return self(); }
- /** Sets a regular expression to split option parameter values or {@code ""} if the value should not be split, and returns this builder. */
- public T splitRegex(String splitRegex) { this.splitRegex = Assert.notNull(splitRegex, "splitRegex"); return self(); }
- /** Sets whether this option or positional parameter's default value should be shown in the usage help, and returns this builder. */
- public T showDefaultValue(Help.Visibility visibility) { showDefaultValue = Assert.notNull(visibility, "visibility"); return self(); }
- /** Sets the completion candidates for this option or positional parameter, and returns this builder.
- * @since 3.2 */
- public T completionCandidates(Iterable<String> completionCandidates) { this.completionCandidates = completionCandidates; return self(); }
- /** Sets whether this option should be excluded from the usage message, and returns this builder. */
- public T hidden(boolean hidden) { this.hidden = hidden; return self(); }
- /** Sets the type to convert the option or positional parameter to before {@linkplain #setValue(Object) setting} the value, and returns this builder.
- * @param propertyType the type of this option or parameter. For multi-value options and positional parameters this can be an array, or a (sub-type of) Collection or Map. */
- public T type(Class<?> propertyType) { this.type = Assert.notNull(propertyType, "type"); return self(); }
- /** Sets the type info for this option or positional parameter, and returns this builder.
- * @param typeInfo type information that does not require {@code Class} objects and be constructed both at runtime and compile time
- * @since 4.0 */
- public T typeInfo(ITypeInfo typeInfo) {
- setTypeInfo(Assert.notNull(typeInfo, "typeInfo"));
- return self();
- }
- private void setTypeInfo(ITypeInfo newValue) {
- this.typeInfo = newValue;
- if (typeInfo != null) {
- type = typeInfo.getType();
- auxiliaryTypes = typeInfo.getAuxiliaryTypes();
- }
- }
- /** Sets the user object associated with this option or positional parameters, and returns this builder.
- * @param userObject may be the annotated program element, or some other useful object
- * @since 4.0 */
- public T userObject(Object userObject) { this.userObject = Assert.notNull(userObject, "userObject"); return self(); }
- /** Sets the default value of this option or positional parameter to the specified value, and returns this builder.
- * Before parsing the command line, the result of {@linkplain #splitRegex() splitting} and {@linkplain #converters() type converting}
- * this default value is applied to the option or positional parameter. A value of {@code null} or {@code "__no_default_value__"} means no default. */
- public T defaultValue(String defaultValue) { this.defaultValue = defaultValue; return self(); }
- /** Sets the initial value of this option or positional parameter to the specified value, and returns this builder.
- * If {@link #hasInitialValue()} is true, the option will be reset to the initial value before parsing (regardless
- * of whether a default value exists), to clear values that would otherwise remain from parsing previous input. */
- public T initialValue(Object initialValue) { this.initialValue = initialValue; return self(); }
- /** Determines whether the option or positional parameter will be reset to the {@link #initialValue()}
- * before parsing new input.*/
- public T hasInitialValue(boolean hasInitialValue) { this.hasInitialValue = hasInitialValue; return self(); }
- /** Sets the {@link IGetter} that is responsible for getting the value of this argument, and returns this builder. */
- public T getter(IGetter getter) { this.getter = getter; return self(); }
- /** Sets the {@link ISetter} that is responsible for modifying the value of this argument, and returns this builder. */
- public T setter(ISetter setter) { this.setter = setter; return self(); }
- /** Sets the {@link IScope} that targets where the setter sets the value, and returns this builder. */
- public T scope(IScope scope) { this.scope = scope; return self(); }
- /** Sets the string respresentation of this option or positional parameter to the specified value, and returns this builder. */
- public T withToString(String toString) { this.toString = toString; return self(); }
- }
- }
- /** The {@code OptionSpec} class models aspects of a <em>named option</em> of a {@linkplain CommandSpec command}, including whether
- * it is required or optional, the option parameters supported (or required) by the option,
- * and attributes for the usage help message describing the option.
- * <p>
- * An option has one or more names. The option is matched when the parser encounters one of the option names in the command line arguments.
- * Depending on the option's {@link #arity() arity},
- * the parser may expect it to have option parameters. The parser will call {@link #setValue(Object) setValue} on
- * the matched option for each of the option parameters encountered.
- * </p><p>
- * For multi-value options, the {@code type} may be an array, a {@code Collection} or a {@code Map}. In this case
- * the parser will get the data structure by calling {@link #getValue() getValue} and modify the contents of this data structure.
- * (In the case of arrays, the array is replaced with a new instance with additional elements.)
- * </p><p>
- * Before calling the setter, picocli converts the option parameter value from a String to the option parameter's type.
- * </p>
- * <ul>
- * <li>If a option-specific {@link #converters() converter} is configured, this will be used for type conversion.
- * If the option's type is a {@code Map}, the map may have different types for its keys and its values, so
- * {@link #converters() converters} should provide two converters: one for the map keys and one for the map values.</li>
- * <li>Otherwise, the option's {@link #type() type} is used to look up a converter in the list of
- * {@linkplain CommandLine#registerConverter(Class, ITypeConverter) registered converters}.
- * For multi-value options,
- * the {@code type} may be an array, or a {@code Collection} or a {@code Map}. In that case the elements are converted
- * based on the option's {@link #auxiliaryTypes() auxiliaryTypes}. The auxiliaryType is used to look up
- * the converter(s) to use to convert the individual parameter values.
- * Maps may have different types for its keys and its values, so {@link #auxiliaryTypes() auxiliaryTypes}
- * should provide two types: one for the map keys and one for the map values.</li>
- * </ul>
- * <p>
- * {@code OptionSpec} objects are used by the picocli command line interpreter and help message generator.
- * Picocli can construct an {@code OptionSpec} automatically from fields and methods with {@link Option @Option}
- * annotations. Alternatively an {@code OptionSpec} can be constructed programmatically.
- * </p><p>
- * When an {@code OptionSpec} is created from an {@link Option @Option} -annotated field or method, it is "bound"
- * to that field or method: this field is set (or the method is invoked) when the option is matched and
- * {@link #setValue(Object) setValue} is called.
- * Programmatically constructed {@code OptionSpec} instances will remember the value passed to the
- * {@link #setValue(Object) setValue} method so it can be retrieved with the {@link #getValue() getValue} method.
- * This behaviour can be customized by installing a custom {@link IGetter} and {@link ISetter} on the {@code OptionSpec}.
- * </p>
- * @since 3.0 */
- public static class OptionSpec extends ArgSpec implements IOrdered {
- static final int DEFAULT_ORDER = -1;
- private String[] names;
- private boolean help;
- private boolean usageHelp;
- private boolean versionHelp;
- private int order;
- public static OptionSpec.Builder builder(String name, String... names) {
- String[] copy = new String[Assert.notNull(names, "names").length + 1];
- copy[0] = Assert.notNull(name, "name");
- System.arraycopy(names, 0, copy, 1, names.length);
- return new Builder(copy);
- }
- public static OptionSpec.Builder builder(String[] names) { return new Builder(names); }
- public static OptionSpec.Builder builder(IAnnotatedElement source, IFactory factory) { return new Builder(source, factory); }
- /** Ensures all attributes of this {@code OptionSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */
- private OptionSpec(Builder builder) {
- super(builder);
- if (builder.names == null) {
- throw new InitializationException("OptionSpec names cannot be null. Specify at least one option name.");
- }
- names = builder.names.clone();
- help = builder.help;
- usageHelp = builder.usageHelp;
- versionHelp = builder.versionHelp;
- order = builder.order;
- if (names.length == 0 || Arrays.asList(names).contains("")) {
- throw new InitializationException("Invalid names: " + Arrays.toString(names));
- }
- if (toString() == null) { toString = "option " + longestName(); }
- // if (arity().max == 0 && !(isBoolean(type()) || (isMultiValue() && isBoolean(auxiliaryTypes()[0])))) {
- // throw new InitializationException("Option " + longestName() + " is not a boolean so should not be defined with arity=" + arity());
- // }
- }
- /** Returns a new Builder initialized with the attributes from this {@code OptionSpec}. Calling {@code build} immediately will return a copy of this {@code OptionSpec}.
- * @return a builder that can create a copy of this spec
- */
- public Builder toBuilder() { return new Builder(this); }
- @Override public boolean isOption() { return true; }
- @Override public boolean isPositional() { return false; }
- protected boolean internalShowDefaultValue(boolean usageMessageShowDefaults) {
- return super.internalShowDefaultValue(usageMessageShowDefaults) && !help() && !versionHelp() && !usageHelp();
- }
- /** Returns the description template of this option, before variables are {@linkplain Option#description() rendered}.
- * If a resource bundle has been {@linkplain ArgSpec#messages(Messages) set}, this method will first try to find a value in the resource bundle:
- * If the resource bundle has no entry for the {@code fully qualified commandName + "." + descriptionKey} or for the unqualified {@code descriptionKey},
- * an attempt is made to find the option description using any of the option names (without leading hyphens) as key,
- * first with the {@code fully qualified commandName + "."} prefix, then without.
- * @see CommandSpec#qualifiedName(String)
- * @see Option#description() */
- @Override public String[] description() {
- if (messages() == null) { return super.description(); }
- String[] newValue = messages().getStringArray(descriptionKey(), null);
- if (newValue != null) { return newValue; }
- for (String name : names()) {
- newValue = messages().getStringArray(CommandSpec.stripPrefix(name), null);
- if (newValue != null) { return newValue; }
- }
- return super.description();
- }
- /** Returns one or more option names. The returned array will contain at least one option name.
- * @see Option#names() */
- public String[] names() { return names.clone(); }
- /** Returns the longest {@linkplain #names() option name}. */
- public String longestName() { return Help.ShortestFirst.longestFirst(names.clone())[0]; }
- /** Returns the shortest {@linkplain #names() option name}.
- * @since 3.8 */
- public String shortestName() { return Help.ShortestFirst.sort(names.clone())[0]; }
- /** Returns the position in the options list in the usage help message at which this option should be shown.
- * Options with a lower number are shown before options with a higher number.
- * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.
- * @see Option#order()
- * @since 3.9 */
- public int order() { return this.order; }
- /** Returns whether this option disables validation of the other arguments.
- * @see Option#help()
- * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. */
- @Deprecated public boolean help() { return help; }
- /** Returns whether this option allows the user to request usage help.
- * @see Option#usageHelp() */
- public boolean usageHelp() { return usageHelp; }
- /** Returns whether this option allows the user to request version information.
- * @see Option#versionHelp() */
- public boolean versionHelp() { return versionHelp; }
- public boolean equals(Object obj) {
- if (obj == this) { return true; }
- if (!(obj instanceof OptionSpec)) { return false; }
- OptionSpec other = (OptionSpec) obj;
- boolean result = super.equalsImpl(other)
- && help == other.help
- && usageHelp == other.usageHelp
- && versionHelp == other.versionHelp
- && order == other.order
- && new HashSet<String>(Arrays.asList(names)).equals(new HashSet<String>(Arrays.asList(other.names)));
- return result;
- }
- public int hashCode() {
- return super.hashCodeImpl()
- + 37 * Assert.hashCode(help)
- + 37 * Assert.hashCode(usageHelp)
- + 37 * Assert.hashCode(versionHelp)
- + 37 * Arrays.hashCode(names)
- + 37 * order;
- }
- /** Builder responsible for creating valid {@code OptionSpec} objects.
- * @since 3.0
- */
- public static class Builder extends ArgSpec.Builder<Builder> {
- private String[] names;
- private boolean help;
- private boolean usageHelp;
- private boolean versionHelp;
- private int order = DEFAULT_ORDER;
- private Builder(String[] names) { this.names = names; }
- private Builder(OptionSpec original) {
- super(original);
- names = original.names;
- help = original.help;
- usageHelp = original.usageHelp;
- versionHelp = original.versionHelp;
- order = original.order;
- }
- private Builder(IAnnotatedElement member, IFactory factory) {
- super(member.getAnnotation(Option.class), member, factory);
- Option option = member.getAnnotation(Option.class);
- names = option.names();
- help = option.help();
- usageHelp = option.usageHelp();
- versionHelp = option.versionHelp();
- order = option.order();
- }
- /** Returns a valid {@code OptionSpec} instance. */
- @Override public OptionSpec build() { return new OptionSpec(this); }
- /** Returns this builder. */
- @Override protected Builder self() { return this; }
- /** Returns one or more option names. At least one option name is required.
- * @see Option#names() */
- public String[] names() { return names; }
- /** Returns whether this option disables validation of the other arguments.
- * @see Option#help()
- * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. */
- @Deprecated public boolean help() { return help; }
- /** Returns whether this option allows the user to request usage help.
- * @see Option#usageHelp() */
- public boolean usageHelp() { return usageHelp; }
- /** Returns whether this option allows the user to request version information.
- * @see Option#versionHelp() */
- public boolean versionHelp() { return versionHelp; }
- /** Returns the position in the options list in the usage help message at which this option should be shown.
- * Options with a lower number are shown before options with a higher number.
- * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.
- * @see Option#order()
- * @since 3.9 */
- public int order() { return order; }
- /** Replaces the option names with the specified values. At least one option name is required, and returns this builder.
- * @return this builder instance to provide a fluent interface */
- public Builder names(String... names) { this.names = Assert.notNull(names, "names").clone(); return self(); }
- /** Sets whether this option disables validation of the other arguments, and returns this builder. */
- public Builder help(boolean help) { this.help = help; return self(); }
- /** Sets whether this option allows the user to request usage help, and returns this builder. */
- public Builder usageHelp(boolean usageHelp) { this.usageHelp = usageHelp; return self(); }
- /** Sets whether this option allows the user to request version information, and returns this builder.*/
- public Builder versionHelp(boolean versionHelp) { this.versionHelp = versionHelp; return self(); }
- /** Sets the position in the options list in the usage help message at which this option should be shown, and returns this builder.
- * @since 3.9 */
- public Builder order(int order) { this.order = order; return self(); }
- }
- }
- /** The {@code PositionalParamSpec} class models aspects of a <em>positional parameter</em> of a {@linkplain CommandSpec command}, including whether
- * it is required or optional, and attributes for the usage help message describing the positional parameter.
- * <p>
- * Positional parameters have an {@link #index() index} (or a range of indices). A positional parameter is matched when the parser
- * encounters a command line argument at that index. Named options and their parameters do not change the index counter,
- * so the command line can contain a mixture of positional parameters and named options.
- * </p><p>
- * Depending on the positional parameter's {@link #arity() arity}, the parser may consume multiple command line
- * arguments starting from the current index. The parser will call {@link #setValue(Object) setValue} on
- * the {@code PositionalParamSpec} for each of the parameters encountered.
- * For multi-value positional parameters, the {@code type} may be an array, a {@code Collection} or a {@code Map}. In this case
- * the parser will get the data structure by calling {@link #getValue() getValue} and modify the contents of this data structure.
- * (In the case of arrays, the array is replaced with a new instance with additional elements.)
- * </p><p>
- * Before calling the setter, picocli converts the positional parameter value from a String to the parameter's type.
- * </p>
- * <ul>
- * <li>If a positional parameter-specific {@link #converters() converter} is configured, this will be used for type conversion.
- * If the positional parameter's type is a {@code Map}, the map may have different types for its keys and its values, so
- * {@link #converters() converters} should provide two converters: one for the map keys and one for the map values.</li>
- * <li>Otherwise, the positional parameter's {@link #type() type} is used to look up a converter in the list of
- * {@linkplain CommandLine#registerConverter(Class, ITypeConverter) registered converters}. For multi-value positional parameters,
- * the {@code type} may be an array, or a {@code Collection} or a {@code Map}. In that case the elements are converted
- * based on the positional parameter's {@link #auxiliaryTypes() auxiliaryTypes}. The auxiliaryType is used to look up
- * the converter(s) to use to convert the individual parameter values.
- * Maps may have different types for its keys and its values, so {@link #auxiliaryTypes() auxiliaryTypes}
- * should provide two types: one for the map keys and one for the map values.</li>
- * </ul>
- * <p>
- * {@code PositionalParamSpec} objects are used by the picocli command line interpreter and help message generator.
- * Picocli can construct a {@code PositionalParamSpec} automatically from fields and methods with {@link Parameters @Parameters}
- * annotations. Alternatively a {@code PositionalParamSpec} can be constructed programmatically.
- * </p><p>
- * When a {@code PositionalParamSpec} is created from a {@link Parameters @Parameters} -annotated field or method,
- * it is "bound" to that field or method: this field is set (or the method is invoked) when the position is matched
- * and {@link #setValue(Object) setValue} is called.
- * Programmatically constructed {@code PositionalParamSpec} instances will remember the value passed to the
- * {@link #setValue(Object) setValue} method so it can be retrieved with the {@link #getValue() getValue} method.
- * This behaviour can be customized by installing a custom {@link IGetter} and {@link ISetter} on the {@code PositionalParamSpec}.
- * </p>
- * @since 3.0 */
- public static class PositionalParamSpec extends ArgSpec {
- private Range index;
- private Range capacity;
- /** Ensures all attributes of this {@code PositionalParamSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */
- private PositionalParamSpec(Builder builder) {
- super(builder);
- index = builder.index == null ? Range.valueOf("*") : builder.index;
- capacity = builder.capacity == null ? Range.parameterCapacity(arity(), index) : builder.capacity;
- if (toString == null) { toString = "positional parameter[" + index() + "]"; }
- }
- public static Builder builder() { return new Builder(); }
- public static Builder builder(IAnnotatedElement source, IFactory factory) { return new Builder(source, factory); }
- /** Returns a new Builder initialized with the attributes from this {@code PositionalParamSpec}. Calling {@code build} immediately will return a copy of this {@code PositionalParamSpec}.
- * @return a builder that can create a copy of this spec
- */
- public Builder toBuilder() { return new Builder(this); }
- @Override public boolean isOption() { return false; }
- @Override public boolean isPositional() { return true; }
- /** Returns the description template of this positional parameter, before variables are {@linkplain Parameters#description() rendered}.
- * If a resource bundle has been {@linkplain ArgSpec#messages(Messages) set}, this method will first try to find a value in the resource bundle:
- * If the resource bundle has no entry for the {@code fully qualified commandName + "." + descriptionKey} or for the unqualified {@code descriptionKey},
- * an attempt is made to find the positional parameter description using {@code paramLabel() + "[" + index() + "]"} as key,
- * first with the {@code fully qualified commandName + "."} prefix, then without.
- * @see Parameters#description()
- * @see CommandSpec#qualifiedName(String)
- * @since 3.6 */
- @Override public String[] description() {
- if (messages() == null) { return super.description(); }
- String[] newValue = messages().getStringArray(descriptionKey(), null);
- if (newValue != null) { return newValue; }
- newValue = messages().getStringArray(paramLabel() + "[" + index() + "]", null);
- if (newValue != null) { return newValue; }
- return super.description();
- }
- /** Returns an index or range specifying which of the command line arguments should be assigned to this positional parameter.
- * @see Parameters#index() */
- public Range index() { return index; }
- private Range capacity() { return capacity; }
- public int hashCode() {
- return super.hashCodeImpl()
- + 37 * Assert.hashCode(capacity)
- + 37 * Assert.hashCode(index);
- }
- public boolean equals(Object obj) {
- if (obj == this) {
- return true;
- }
- if (!(obj instanceof PositionalParamSpec)) {
- return false;
- }
- PositionalParamSpec other = (PositionalParamSpec) obj;
- return super.equalsImpl(other)
- && Assert.equals(this.capacity, other.capacity)
- && Assert.equals(this.index, other.index);
- }
- /** Builder responsible for creating valid {@code PositionalParamSpec} objects.
- * @since 3.0
- */
- public static class Builder extends ArgSpec.Builder<Builder> {
- private Range capacity;
- private Range index;
- private Builder() {}
- private Builder(PositionalParamSpec original) {
- super(original);
- index = original.index;
- capacity = original.capacity;
- }
- private Builder(IAnnotatedElement member, IFactory factory) {
- super(member.getAnnotation(Parameters.class), member, factory);
- index = Range.parameterIndex(member);
- capacity = Range.parameterCapacity(member);
- }
- /** Returns a valid {@code PositionalParamSpec} instance. */
- @Override public PositionalParamSpec build() { return new PositionalParamSpec(this); }
- /** Returns this builder. */
- @Override protected Builder self() { return this; }
- /** Returns an index or range specifying which of the command line arguments should be assigned to this positional parameter.
- * @see Parameters#index() */
- public Range index() { return index; }
- /** Sets the index or range specifying which of the command line arguments should be assigned to this positional parameter, and returns this builder. */
- public Builder index(String range) { return index(Range.valueOf(range)); }
- /** Sets the index or range specifying which of the command line arguments should be assigned to this positional parameter, and returns this builder. */
- public Builder index(Range index) { this.index = index; return self(); }
- Range capacity() { return capacity; }
- Builder capacity(Range capacity) { this.capacity = capacity; return self(); }
- }
- }
- /** Interface for sorting {@link OptionSpec options} and {@link ArgGroupSpec groups} together.
- * @since 4.0 */
- public interface IOrdered {
- /** Returns the position in the options list in the usage help message at which this element should be shown.
- * Elements with a lower number are shown before elements with a higher number.
- * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command. */
- int order();
- }
- /** The {@code ArgGroupSpec} class models a {@link ArgGroup group} of arguments (options, positional parameters or a mixture of the two).
- * @see ArgGroup
- * @since 4.0 */
- public static class ArgGroupSpec implements IOrdered {
- static final int DEFAULT_ORDER = -1;
- private static final String NO_HEADING = "__no_heading__";
- private static final String NO_HEADING_KEY = "__no_heading_key__";
- private final String heading;
- private final String headingKey;
- private final boolean exclusive;
- private final Range multiplicity;
- private final boolean validate;
- private final int order;
- private final IGetter getter;
- private final ISetter setter;
- private final IScope scope;
- private final ITypeInfo typeInfo;
- private final List<ArgGroupSpec> subgroups;
- private final Set<ArgSpec> args;
- private ArgGroupSpec parentGroup;
- private String id = "1";
- ArgGroupSpec(ArgGroupSpec.Builder builder) {
- heading = NO_HEADING .equals(builder.heading) ? null : builder.heading;
- headingKey = NO_HEADING_KEY.equals(builder.headingKey) ? null : builder.headingKey;
- exclusive = builder.exclusive;
- multiplicity = builder.multiplicity;
- validate = builder.validate;
- order = builder.order;
- typeInfo = builder.typeInfo;
- getter = builder.getter;
- setter = builder.setter;
- scope = builder.scope;
- args = Collections.unmodifiableSet(new LinkedHashSet<ArgSpec>(builder.args()));
- subgroups = Collections.unmodifiableList(new ArrayList<ArgGroupSpec>(builder.subgroups()));
- if (args.isEmpty() && subgroups.isEmpty()) { throw new InitializationException("ArgGroup has no options or positional parameters, and no subgroups"); }
- int i = 1;
- for (ArgGroupSpec sub : subgroups) { sub.parentGroup = this; sub.id = id + "." + i++; }
- for (ArgSpec arg : args) { arg.group = this; }
- }
- /** Returns a new {@link Builder}.
- * @return a new ArgGroupSpec.Builder instance */
- public static Builder builder() { return new Builder(); }
- /** Returns a new {@link Builder} associated with the specified annotated element.
- * @param annotatedElement the annotated element containing {@code @Option} and {@code @Parameters}
- * @return a new ArgGroupSpec.Builder instance */
- public static Builder builder(IAnnotatedElement annotatedElement) { return new Builder(Assert.notNull(annotatedElement, "annotatedElement")); }
- /** Returns whether this is a mutually exclusive group; {@code true} by default.
- * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}.
- * @see ArgGroup#exclusive() */
- public boolean exclusive() { return exclusive; }
- /** Returns the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
- * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
- * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
- * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
- * Ignored if {@link #validate()} is {@code false}.
- * @see ArgGroup#multiplicity() */
- public Range multiplicity() { return multiplicity; }
- /** Returns whether picocli should validate the rules of this group:
- * for a mutually exclusive group this means that no more than one arguments in the group is specified on the command line;
- * for a co-ocurring group this means that all arguments in the group are specified on the command line.
- * {@code true} by default.
- * @see ArgGroup#validate() */
- public boolean validate() { return validate; }
- /** Returns the position in the options list in the usage help message at which this group should be shown.
- * Options with a lower number are shown before options with a higher number.
- * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command. */
- public int order() { return this.order; }
- /** Returns the heading of this group (may be {@code null}), used when generating the usage documentation.
- * @see ArgGroup#heading() */
- public String heading() { return heading; }
- /** Returns the heading key of this group (may be {@code null}), used to get the heading from a resource bundle.
- * @see ArgGroup#headingKey() */
- public String headingKey() { return headingKey; }
- /**
- * Returns the parent group that this group is part of, or {@code null} if this group is not part of a composite.
- */
- public ArgGroupSpec parentGroup() { return parentGroup; }
- /** Return the subgroups that this group is composed of; may be empty but not {@code null}.
- * @return immutable list of subgroups that this group is composed of. */
- public List<ArgGroupSpec> subgroups() { return subgroups; }
- /**
- * Returns {@code true} if this group is a subgroup (or a nested sub-subgroup, to any level of depth)
- * of the specified group, {@code false} otherwise.
- * @param group the group to check if it contains this group
- * @return {@code true} if this group is a subgroup or a nested sub-subgroup of the specified group
- */
- public boolean isSubgroupOf(ArgGroupSpec group) {
- for (ArgGroupSpec sub : group.subgroups) {
- if (this == sub) { return true; }
- if (isSubgroupOf(sub)) { return true; }
- }
- return false;
- }
- /** Returns the type info for the annotated program element associated with this group.
- * @return type information that does not require {@code Class} objects and be constructed both at runtime and compile time
- */
- public ITypeInfo typeInfo() { return typeInfo; }
- /** Returns the {@link IGetter} that is responsible for supplying the value of the annotated program element associated with this group. */
- public IGetter getter() { return getter; }
- /** Returns the {@link ISetter} that is responsible for modifying the value of the annotated program element associated with this group. */
- public ISetter setter() { return setter; }
- /** Returns the {@link IScope} that determines where the setter sets the value (or the getter gets the value) of the annotated program element associated with this group. */
- public IScope scope() { return scope; }
- Object userObject() { try { return getter.get(); } catch (Exception ex) { return ex.toString(); } }
- String id() { return id; }
- /** Returns the options and positional parameters in this group; may be empty but not {@code null}. */
- public Set<ArgSpec> args() { return args; }
- /** Returns the required options and positional parameters in this group; may be empty but not {@code null}. */
- public Set<ArgSpec> requiredArgs() {
- Set<ArgSpec> result = new LinkedHashSet<ArgSpec>(args);
- for (Iterator<ArgSpec> iter = result.iterator(); iter.hasNext(); ) {
- if (!iter.next().required()) { iter.remove(); }
- }
- return Collections.unmodifiableSet(result);
- }
- /** Returns the list of positional parameters configured for this group.
- * @return an immutable list of positional parameters in this group. */
- public List<PositionalParamSpec> positionalParameters() {
- List<PositionalParamSpec> result = new ArrayList<PositionalParamSpec>();
- for (ArgSpec arg : args()) { if (arg instanceof PositionalParamSpec) { result.add((PositionalParamSpec) arg); } }
- return Collections.unmodifiableList(result);
- }
- /** Returns the list of options configured for this group.
- * @return an immutable list of options in this group. */
- public List<OptionSpec> options() {
- List<OptionSpec> result = new ArrayList<OptionSpec>();
- for (ArgSpec arg : args()) { if (arg instanceof OptionSpec) { result.add((OptionSpec) arg); } }
- return Collections.unmodifiableList(result);
- }
- public String synopsis() {
- return synopsisText(new Help.ColorScheme(Help.Ansi.OFF)).toString();
- }
- public Text synopsisText(Help.ColorScheme colorScheme) {
- String infix = exclusive() ? " | " : " ";
- Text synopsis = colorScheme.ansi().new Text(0);
- for (ArgSpec arg : args()) {
- if (synopsis.length > 0) { synopsis = synopsis.concat(infix); }
- if (arg instanceof OptionSpec) {
- synopsis = concatOptionText(synopsis, colorScheme, (OptionSpec) arg);
- } else {
- synopsis = concatPositionalText(synopsis, colorScheme, (PositionalParamSpec) arg);
- }
- }
- for (ArgGroupSpec subgroup : subgroups()) {
- if (synopsis.length > 0) { synopsis = synopsis.concat(infix); }
- synopsis = synopsis.concat(subgroup.synopsisText(colorScheme));
- }
- String prefix = multiplicity().min > 0 ? "(" : "[";
- String postfix = multiplicity().min > 0 ? ")" : "]";
- Text result = colorScheme.ansi().text(prefix).concat(synopsis).concat(postfix);
- if (multiplicity().isVariable) {
- result = result.concat("...");
- } else {
- int i = 1;
- for (; i < multiplicity.min; i++) {
- result = result.concat(" (").concat(synopsis).concat(")");
- }
- for (; i < multiplicity.max; i++) {
- result = result.concat(" [").concat(synopsis).concat("]");
- }
- }
- return result;
- }
- private Text concatOptionText(Text text, Help.ColorScheme colorScheme, OptionSpec option) {
- if (!option.hidden()) {
- Text name = colorScheme.optionText(option.shortestName());
- Text param = createLabelRenderer(option.commandSpec).renderParameterLabel(option, colorScheme.ansi(), colorScheme.optionParamStyles);
- text = text.concat(open(option)).concat(name).concat(param).concat(close(option));
- if (option.isMultiValue()) { // e.g., -x=VAL [-x=VAL]...
- text = text.concat(" [").concat(name).concat(param).concat("]...");
- }
- }
- return text;
- }
- private Text concatPositionalText(Text text, Help.ColorScheme colorScheme, PositionalParamSpec positionalParam) {
- if (!positionalParam.hidden()) {
- Text label = createLabelRenderer(positionalParam.commandSpec).renderParameterLabel(positionalParam, colorScheme.ansi(), colorScheme.parameterStyles);
- text = text.concat(open(positionalParam)).concat(label).concat(close(positionalParam));
- }
- return text;
- }
- private String open(ArgSpec argSpec) { return argSpec.required() ? "" : "["; }
- private String close(ArgSpec argSpec) { return argSpec.required() ? "" : "]"; }
- public Help.IParamLabelRenderer createLabelRenderer(CommandSpec commandSpec) {
- return new Help.DefaultParamLabelRenderer(commandSpec == null ? CommandSpec.create() : commandSpec);
- }
- @Override public boolean equals(Object obj) {
- if (obj == this) { return true; }
- if (!(obj instanceof ArgGroupSpec)) { return false; }
- ArgGroupSpec other = (ArgGroupSpec) obj;
- return exclusive == other.exclusive
- && Assert.equals(multiplicity, other.multiplicity)
- && validate == other.validate
- && order == other.order
- && Assert.equals(heading, other.heading)
- && Assert.equals(headingKey, other.headingKey)
- && Assert.equals(subgroups, other.subgroups)
- && Assert.equals(args, other.args);
- }
- @Override public int hashCode() {
- int result = 17;
- result += 37 * result + Assert.hashCode(exclusive);
- result += 37 * result + Assert.hashCode(multiplicity);
- result += 37 * result + Assert.hashCode(validate);
- result += 37 * result + order;
- result += 37 * result + Assert.hashCode(heading);
- result += 37 * result + Assert.hashCode(headingKey);
- result += 37 * result + Assert.hashCode(subgroups);
- result += 37 * result + Assert.hashCode(args);
- return result;
- }
- @Override public String toString() {
- List<String> argNames = new ArrayList<String>();
- for (ArgSpec arg : args()) {
- if (arg instanceof OptionSpec) {
- argNames.add(((OptionSpec) arg).shortestName());
- } else {
- PositionalParamSpec p = (PositionalParamSpec) arg;
- argNames.add(p.index() + " (" + p.paramLabel() + ")");
- }
- }
- return "ArgGroup[exclusive=" + exclusive + ", multiplicity=" + multiplicity +
- ", validate=" + validate + ", order=" + order + ", args=[" + ArgSpec.describe(args()) +
- "], headingKey=" + quote(headingKey) + ", heading=" + quote(heading) +
- ", subgroups=" + subgroups + "]";
- }
- private static String quote(String s) { return s == null ? "null" : "'" + s + "'"; }
- void initUserObject(CommandLine commandLine) {
- if (commandLine == null) { new Tracer().debug("Could not create user object for %s with null CommandLine%n.", this); }
- try {
- tryInitUserObject(commandLine);
- } catch (PicocliException ex) {
- throw ex;
- } catch (Exception ex) {
- throw new InitializationException("Could not create user object for " + this, ex);
- }
- }
- void tryInitUserObject(CommandLine commandLine) throws Exception {
- Tracer tracer = commandLine.tracer;
- if (typeInfo() != null) {
- tracer.debug("Creating new user object of type %s for group %s%n", typeInfo().getAuxiliaryTypes()[0], synopsis());
- Object userObject = DefaultFactory.create(commandLine.factory, typeInfo().getAuxiliaryTypes()[0]);
- tracer.debug("Created %s, invoking setter %s with scope %s%n", userObject, setter(), scope());
- setUserObject(userObject, commandLine.factory);
- for (ArgSpec arg : args()) {
- tracer.debug("Initializing %s in group %s: setting scope to user object %s and initializing initial and default values%n", ArgSpec.describe(arg, "="), synopsis(), userObject);
- arg.scope().set(userObject); // flip the actual user object for the arg (and all other args in this group; they share the same IScope instance)
- commandLine.interpreter.parseResultBuilder.isInitializingDefaultValues = true;
- arg.applyInitialValue(tracer);
- commandLine.interpreter.applyDefault(commandLine.getCommandSpec().defaultValueProvider(), arg);
- commandLine.interpreter.parseResultBuilder.isInitializingDefaultValues = false;
- }
- for (ArgGroupSpec subgroup : subgroups()) {
- tracer.debug("Setting scope for subgroup %s with setter=%s in group %s to user object %s%n", subgroup.synopsis(), subgroup.setter(), synopsis(), userObject);
- subgroup.scope().set(userObject); // flip the actual user object for the arg (and all other args in this group; they share the same IScope instance)
- }
- } else {
- tracer.debug("No type information available for group %s: cannot create new user object. Scope for arg setters is not changed.%n", synopsis());
- }
- tracer.debug("Initialization complete for group %s%n", synopsis());
- }
- void setUserObject(Object userObject, IFactory factory) throws Exception {
- if (typeInfo().isCollection()) {
- @SuppressWarnings("unchecked") Collection<Object> c = (Collection<Object>) getter().get();
- if (c == null) {
- @SuppressWarnings("unchecked")
- Collection<Object> c2 = (Collection<Object>) DefaultFactory.create(factory, typeInfo.getType());
- setter().set(c = c2);
- }
- (c).add(userObject);
- } else if (typeInfo().isArray()) {
- Object old = getter().get();
- int oldSize = old == null ? 0 : Array.getLength(old);
- Object array = Array.newInstance(typeInfo().getAuxiliaryTypes()[0], oldSize + 1);
- for (int i = 0; i < oldSize; i++) {
- Array.set(array, i, Array.get(old, i));
- }
- Array.set(array, oldSize, userObject);
- setter().set(array);
- } else {
- setter().set(userObject);
- }
- }
- enum GroupValidationResult {
- SUCCESS_PRESENT, SUCCESS_ABSENT,
- FAILURE_PRESENT, FAILURE_ABSENT, FAILURE_PARTIAL;
- static boolean containsBlockingFailure(EnumSet<GroupValidationResult> set) {
- return set.contains(FAILURE_PRESENT) || set.contains(FAILURE_PARTIAL);
- }
- /** FAILURE_PRESENT or FAILURE_PARTIAL */
- boolean blockingFailure() { return this == FAILURE_PRESENT || this == FAILURE_PARTIAL; }
- boolean present() { return this == SUCCESS_PRESENT /*|| this == FAILURE_PRESENT*/; }
- boolean success() { return this == SUCCESS_ABSENT || this == SUCCESS_PRESENT; }
- }
- private ParameterException validationException;
- private GroupValidationResult validationResult;
- /** Clears temporary validation state for this group and its subgroups. */
- void clearValidationResult() {
- validationException = null;
- validationResult = null;
- for (ArgGroupSpec sub : subgroups()) { sub.clearValidationResult(); }
- }
- /** Throws an exception if the constraints in this group are not met by the specified match. */
- void validateConstraints(ParseResult parseResult) {
- if (!validate()) { return; }
- CommandLine commandLine = parseResult.commandSpec().commandLine();
- // first validate args in this group
- validationResult = validateArgs(commandLine, parseResult);
- if (validationResult.blockingFailure()) {
- commandLine.interpreter.maybeThrow(validationException); // composite parent validations cannot succeed anyway
- }
- // then validate sub groups
- EnumSet<GroupValidationResult> validationResults = validateSubgroups(parseResult);
- if (GroupValidationResult.containsBlockingFailure(validationResults)) {
- commandLine.interpreter.maybeThrow(validationException); // composite parent validations cannot succeed anyway
- }
- List<MatchedGroup> matchedGroups = parseResult.findMatchedGroup(this);
- if (matchedGroups.isEmpty()) {
- // TODO can/should we verify minimum multiplicity here?
- if (multiplicity().min > 0) {
- if (validationResult.success()) {
- validationResult = GroupValidationResult.FAILURE_ABSENT;
- validationException = new MissingParameterException(commandLine, args(),
- "Error: Group: " + synopsis() + " must be specified " + multiplicity().min + " times but was missing");
- }
- }
- }
- for (MatchedGroup matchedGroup : matchedGroups) {
- int matchCount = matchedGroup.multiples().size();
- // note: matchCount == 0 if only subgroup(s) are matched for a group without args (subgroups-only)
- boolean checkMinimum = matchCount > 0 || !args().isEmpty();
- if (checkMinimum && matchCount < multiplicity().min) {
- if (validationResult.success()) {
- validationResult = matchCount == 0 ? GroupValidationResult.FAILURE_ABSENT: GroupValidationResult.FAILURE_PARTIAL;
- validationException = new MissingParameterException(commandLine, args(),
- "Error: Group: " + synopsis() + " must be specified " + multiplicity().min + " times but was matched " + matchCount + " times");
- }
- } else if (matchCount > multiplicity().max) {
- if (!validationResult.blockingFailure()) {
- validationResult = GroupValidationResult.FAILURE_PRESENT;
- validationException = new MaxValuesExceededException(commandLine,
- "Error: Group: " + synopsis() + " can only be specified " + multiplicity().max + " times but was matched " + matchCount + " times.");
- }
- }
- if (validationResult.blockingFailure()) {
- commandLine.interpreter.maybeThrow(validationException);
- }
- }
- if (validationException != null && parentGroup == null) {
- commandLine.interpreter.maybeThrow(validationException);
- }
- }
- private EnumSet<GroupValidationResult> validateSubgroups(ParseResult parseResult) {
- EnumSet<GroupValidationResult> validationResults = EnumSet.of(validationResult);
- if (subgroups().isEmpty()) { return validationResults; }
- for (ArgGroupSpec subgroup : subgroups()) {
- subgroup.validateConstraints(parseResult);
- validationResults.add(Assert.notNull(subgroup.validationResult, "subgroup validation result"));
- if (subgroup.validationResult.blockingFailure()) { this.validationException = subgroup.validationException; break; }
- }
- // now do some coarse-grained checking for exclusive subgroups
- int elementCount = args().size() + subgroups().size();
- int presentCount = validationResult.present() ? 1 : 0;
- String exclusiveElements = "";
- for (ArgGroupSpec subgroup : subgroups()) {
- if (!parseResult.findMatchedGroup(subgroup).isEmpty()) { presentCount++; }
- //presentCount += parseResult.findMatchedGroup(subgroup).size(); // this would give incorrect error message if A and B are exclusive and A is matched 2x and B is not matched
- if (exclusiveElements.length() > 0) { exclusiveElements += " and "; }
- exclusiveElements += subgroup.synopsis();
- }
- validationResult = validate(parseResult.commandSpec().commandLine(), presentCount, presentCount < elementCount,
- presentCount > 0 && presentCount < elementCount, exclusiveElements, synopsis(), synopsis());
- validationResults.add(validationResult);
- return validationResults;
- }
- private GroupValidationResult validateArgs(CommandLine commandLine, ParseResult parseResult) {
- if (args().isEmpty()) { return GroupValidationResult.SUCCESS_ABSENT; }
- return validateArgs(commandLine, parseResult.findMatchedGroup(this));
- }
- private GroupValidationResult validateArgs(CommandLine commandLine, List<MatchedGroup> matchedGroups) {
- if (matchedGroups.isEmpty()) {
- int presentCount = 0;
- boolean haveMissing = true;
- boolean someButNotAllSpecified = false;
- String exclusiveElements = "";
- String missingElements = ArgSpec.describe(requiredArgs());
- return validate(commandLine, presentCount, haveMissing, someButNotAllSpecified, exclusiveElements, missingElements, missingElements);
- }
- GroupValidationResult result = GroupValidationResult.SUCCESS_ABSENT;
- Map<MatchedGroup, List<MatchedGroup>> byParent = groupByParent(matchedGroups);
- for (Map.Entry<MatchedGroup, List<MatchedGroup>> entry : byParent.entrySet()) {
- List<MatchedGroup> allForOneParent = entry.getValue();
- for (MatchedGroup oneForOneParent : allForOneParent) {
- result = validateMultiples(commandLine, oneForOneParent.multiples());
- if (result.blockingFailure()) { return result; }
- }
- }
- return result;
- }
- private Map<MatchedGroup, List<MatchedGroup>> groupByParent(List<MatchedGroup> matchedGroups) {
- Map<MatchedGroup, List<MatchedGroup>> result = new HashMap<MatchedGroup, List<MatchedGroup>>();
- for (MatchedGroup mg : matchedGroups) {
- addValueToListInMap(result, mg.parentMatchedGroup(), mg);
- }
- return result;
- }
- private List<ParseResult.MatchedGroupMultiple> flatListMultiples(Collection<MatchedGroup> matchedGroups) {
- List<ParseResult.MatchedGroupMultiple> all = new ArrayList<ParseResult.MatchedGroupMultiple>();
- for (MatchedGroup matchedGroup : matchedGroups) {
- all.addAll(matchedGroup.multiples());
- }
- return all;
- }
- private GroupValidationResult validateMultiples(CommandLine commandLine, List<ParseResult.MatchedGroupMultiple> multiples) {
- Set<ArgSpec> intersection = new LinkedHashSet<ArgSpec>(this.args());
- Set<ArgSpec> missing = new LinkedHashSet<ArgSpec>(this.requiredArgs());
- Set<ArgSpec> found = new LinkedHashSet<ArgSpec>();
- for (ParseResult.MatchedGroupMultiple multiple : multiples) {
- found.addAll(multiple.matchedValues.keySet());
- missing.removeAll(multiple.matchedValues.keySet());
- }
- intersection.retainAll(found);
- int presentCount = intersection.size();
- boolean haveMissing = !missing.isEmpty();
- boolean someButNotAllSpecified = haveMissing && !intersection.isEmpty();
- String exclusiveElements = ArgSpec.describe(intersection);
- String requiredElements = ArgSpec.describe(requiredArgs());
- String missingElements = ArgSpec.describe(missing);
- return validate(commandLine, presentCount, haveMissing, someButNotAllSpecified, exclusiveElements, requiredElements, missingElements);
- }
- private GroupValidationResult validate(CommandLine commandLine, int presentCount, boolean haveMissing, boolean someButNotAllSpecified, String exclusiveElements, String requiredElements, String missingElements) {
- if (exclusive()) {
- if (presentCount > 1) {
- validationException = new MutuallyExclusiveArgsException(commandLine,
- "Error: " + exclusiveElements + " are mutually exclusive (specify only one)");
- return GroupValidationResult.FAILURE_PRESENT;
- }
- // check that exactly one member was matched
- if (multiplicity().min > 0 && presentCount < 1) {
- validationException = new MissingParameterException(commandLine, args(),
- "Error: Missing required argument (specify one of these): " + requiredElements);
- return GroupValidationResult.FAILURE_ABSENT;
- }
- return GroupValidationResult.SUCCESS_PRESENT;
- } else { // co-occurring group
- if (someButNotAllSpecified) {
- validationException = new MissingParameterException(commandLine, args(),
- "Error: Missing required argument(s): " + missingElements);
- return GroupValidationResult.FAILURE_PARTIAL;
- }
- if ((multiplicity().min > 0 && haveMissing)) {
- validationException = new MissingParameterException(commandLine, args(),
- "Error: Missing required argument(s): " + missingElements);
- return GroupValidationResult.FAILURE_ABSENT;
- }
- return haveMissing ? GroupValidationResult.SUCCESS_ABSENT : GroupValidationResult.SUCCESS_PRESENT;
- }
- }
- /** Builder responsible for creating valid {@code ArgGroupSpec} objects.
- * @since 4.0 */
- public static class Builder {
- private IGetter getter;
- private ISetter setter;
- private IScope scope;
- private ITypeInfo typeInfo;
- private String heading;
- private String headingKey;
- private boolean exclusive = true;
- private Range multiplicity = Range.valueOf("0..1");
- private boolean validate = true;
- private int order = DEFAULT_ORDER;
- private List<ArgSpec> args = new ArrayList<ArgSpec>();
- private List<ArgGroupSpec> subgroups = new ArrayList<ArgGroupSpec>();
- // for topological sorting; private only
- private Boolean topologicalSortDone;
- private List<Builder> compositesReferencingMe = new ArrayList<Builder>();
- Builder() { }
- Builder(IAnnotatedElement source) {
- typeInfo = source.getTypeInfo();
- getter = source.getter();
- setter = source.setter();
- scope = source.scope();
- }
- /** Updates this builder from the specified annotation values.
- * @param group annotation values
- * @return this builder for method chaining */
- public Builder updateArgGroupAttributes(ArgGroup group) {
- return this
- .heading(group.heading())
- .headingKey(group.headingKey())
- .exclusive(group.exclusive())
- .multiplicity(group.multiplicity())
- .validate(group.validate())
- .order(group.order());
- }
- /** Returns a valid {@code ArgGroupSpec} instance. */
- public ArgGroupSpec build() { return new ArgGroupSpec(this); }
- /** Returns whether this is a mutually exclusive group; {@code true} by default.
- * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}.
- * @see ArgGroup#exclusive() */
- public boolean exclusive() { return exclusive; }
- /** Sets whether this is a mutually exclusive group; {@code true} by default.
- * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}.
- * @see ArgGroup#exclusive() */
- public Builder exclusive(boolean newValue) { exclusive = newValue; return this; }
- /** Returns the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
- * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
- * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
- * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
- * Ignored if {@link #validate()} is {@code false}.
- * @see ArgGroup#multiplicity() */
- public Range multiplicity() { return multiplicity; }
- /** Sets the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
- * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
- * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
- * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
- * Ignored if {@link #validate()} is {@code false}.
- * @see ArgGroup#multiplicity() */
- public Builder multiplicity(String newValue) { return multiplicity(Range.valueOf(newValue)); }
- /** Sets the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
- * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
- * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
- * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
- * Ignored if {@link #validate()} is {@code false}.
- * @see ArgGroup#multiplicity() */
- public Builder multiplicity(Range newValue) { multiplicity = newValue; return this; }
- /** Returns whether picocli should validate the rules of this group:
- * for a mutually exclusive group this means that no more than one arguments in the group is specified on the command line;
- * for a co-ocurring group this means that all arguments in the group are specified on the command line.
- * {@code true} by default.
- * @see ArgGroup#validate() */
- public boolean validate() { return validate; }
- /** Sets whether picocli should validate the rules of this group:
- * for a mutually exclusive group this means that no more than one arguments in the group is specified on the command line;
- * for a co-ocurring group this means that all arguments in the group are specified on the command line.
- * {@code true} by default.
- * @see ArgGroup#validate() */
- public Builder validate(boolean newValue) { validate = newValue; return this; }
- /** Returns the position in the options list in the usage help message at which this group should be shown.
- * Options with a lower number are shown before options with a higher number.
- * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.*/
- public int order() { return order; }
- /** Sets the position in the options list in the usage help message at which this group should be shown, and returns this builder. */
- public Builder order(int order) { this.order = order; return this; }
- /** Returns the heading of this group, used when generating the usage documentation.
- * @see ArgGroup#heading() */
- public String heading() { return heading; }
- /** Sets the heading of this group (may be {@code null}), used when generating the usage documentation.
- * @see ArgGroup#heading() */
- public Builder heading(String newValue) { this.heading = newValue; return this; }
- /** Returns the heading key of this group, used to get the heading from a resource bundle.
- * @see ArgGroup#headingKey() */
- public String headingKey() { return headingKey; }
- /** Sets the heading key of this group, used to get the heading from a resource bundle.
- * @see ArgGroup#headingKey() */
- public Builder headingKey(String newValue) { this.headingKey = newValue; return this; }
- /** Returns the type info for the annotated program element associated with this group.
- * @return type information that does not require {@code Class} objects and be constructed both at runtime and compile time
- */
- public ITypeInfo typeInfo() { return typeInfo; }
- /** Sets the type info for the annotated program element associated with this group, and returns this builder.
- * @param newValue type information that does not require {@code Class} objects and be constructed both at runtime and compile time
- */
- public Builder typeInfo(ITypeInfo newValue) { this.typeInfo = newValue; return this; }
- /** Returns the {@link IGetter} that is responsible for supplying the value of the annotated program element associated with this group. */
- public IGetter getter() { return getter; }
- /** Sets the {@link IGetter} that is responsible for getting the value of the annotated program element associated with this group, and returns this builder. */
- public Builder getter(IGetter getter) { this.getter = getter; return this; }
- /** Returns the {@link ISetter} that is responsible for modifying the value of the annotated program element associated with this group. */
- public ISetter setter() { return setter; }
- /** Sets the {@link ISetter} that is responsible for modifying the value of the annotated program element associated with this group, and returns this builder. */
- public Builder setter(ISetter setter) { this.setter = setter; return this; }
- /** Returns the {@link IScope} that determines where the setter sets the value (or the getter gets the value) of the annotated program element associated with this group. */
- public IScope scope() { return scope; }
- /** Sets the {@link IScope} that targets where the setter sets the value of the annotated program element associated with this group, and returns this builder. */
- public Builder scope(IScope scope) { this.scope = scope; return this; }
- /** Adds the specified argument to the list of options and positional parameters that depend on this group. */
- public Builder addArg(ArgSpec arg) { args.add(arg); return this; }
- /** Returns the list of options and positional parameters that depend on this group.*/
- public List<ArgSpec> args() { return args; }
- /** Adds the specified group to the list of subgroups that this group is composed of. */
- public Builder addSubgroup(ArgGroupSpec group) { subgroups.add(group); return this; }
- /** Returns the list of subgroups that this group is composed of.*/
- public List<ArgGroupSpec> subgroups() { return subgroups; }
- }
- }
- /** This class allows applications to specify a custom binding that will be invoked for unmatched arguments.
- * A binding can be created with a {@code ISetter} that consumes the unmatched arguments {@code String[]}, or with a
- * {@code IGetter} that produces a {@code Collection<String>} that the unmatched arguments can be added to.
- * @since 3.0 */
- public static class UnmatchedArgsBinding {
- private final IGetter getter;
- private final ISetter setter;
- /** Creates a {@code UnmatchedArgsBinding} for a setter that consumes {@code String[]} objects.
- * @param setter consumes the String[] array with unmatched arguments. */
- public static UnmatchedArgsBinding forStringArrayConsumer(ISetter setter) { return new UnmatchedArgsBinding(null, setter); }
- /** Creates a {@code UnmatchedArgsBinding} for a getter that produces a {@code Collection<String>} that the unmatched arguments can be added to.
- * @param getter supplies a {@code Collection<String>} that the unmatched arguments can be added to. */
- public static UnmatchedArgsBinding forStringCollectionSupplier(IGetter getter) { return new UnmatchedArgsBinding(getter, null); }
- private UnmatchedArgsBinding(IGetter getter, ISetter setter) {
- if (getter == null && setter == null) { throw new IllegalArgumentException("Getter and setter cannot both be null"); }
- this.setter = setter;
- this.getter = getter;
- }
- /** Returns the getter responsible for producing a {@code Collection} that the unmatched arguments can be added to. */
- public IGetter getter() { return getter; }
- /** Returns the setter responsible for consuming the unmatched arguments. */
- public ISetter setter() { return setter; }
- void addAll(String[] unmatched) {
- if (setter != null) {
- try {
- setter.set(unmatched);
- } catch (Exception ex) {
- throw new PicocliException(String.format("Could not invoke setter (%s) with unmatched argument array '%s': %s", setter, Arrays.toString(unmatched), ex), ex);
- }
- }
- if (getter != null) {
- try {
- Collection<String> collection = getter.get();
- Assert.notNull(collection, "getter returned null Collection");
- collection.addAll(Arrays.asList(unmatched));
- } catch (Exception ex) {
- throw new PicocliException(String.format("Could not add unmatched argument array '%s' to collection returned by getter (%s): %s",
- Arrays.toString(unmatched), getter, ex), ex);
- }
- }
- }
- }
- /** Command method parameter, similar to java.lang.reflect.Parameter (not available before Java 8).
- * @since 4.0 */
- public static class MethodParam extends AccessibleObject {
- final Method method;
- final int paramIndex;
- final String name;
- int position;
- public MethodParam(Method method, int paramIndex) {
- this.method = method;
- this.paramIndex = paramIndex;
- String tmp = "arg" + paramIndex;
- try {
- Method getParameters = Method.class.getMethod("getParameters");
- Object parameters = getParameters.invoke(method);
- Object parameter = Array.get(parameters, paramIndex);
- tmp = (String) Class.forName("java.lang.reflect.Parameter").getDeclaredMethod("getName").invoke(parameter);
- } catch (Exception ignored) {}
- this.name = tmp;
- }
- public Type getParameterizedType() { return method.getGenericParameterTypes()[paramIndex]; }
- public String getName() { return name; }
- public Class<?> getType() { return method.getParameterTypes()[paramIndex]; }
- public Method getDeclaringExecutable() { return method; }
- @Override public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
- for (Annotation annotation : getDeclaredAnnotations()) {
- if (annotationClass.isAssignableFrom(annotation.getClass())) { return annotationClass.cast(annotation); }
- }
- return null;
- }
- @Override public Annotation[] getDeclaredAnnotations() { return method.getParameterAnnotations()[paramIndex]; }
- @Override public void setAccessible(boolean flag) throws SecurityException { method.setAccessible(flag); }
- @Override public String toString() { return method.toString() + ":" + getName(); }
- }
- /** Encapculates type information for an option or parameter to make this information available both at runtime
- * and at compile time (when {@code Class} values are not available).
- * Most of the methods in this interface (but not all!) are safe to use by annotation processors.
- * @since 4.0
- */
- public interface ITypeInfo {
- /** Returns {@code true} if {@link #getType()} is {@code boolean} or {@code java.lang.Boolean}. */
- boolean isBoolean();
- /** Returns {@code true} if {@link #getType()} is an array, map or collection. */
- boolean isMultiValue();
- boolean isArray();
- boolean isCollection();
- boolean isMap();
- /** Returns {@code true} if {@link #getType()} is an enum. */
- boolean isEnum();
- List<String> getEnumConstantNames();
- String getClassName();
- String getClassSimpleName();
- /** Returns type information of components or elements of a {@link #isMultiValue() multivalue} type. */
- List<ITypeInfo> getAuxiliaryTypeInfos();
- /** Returns the names of the type arguments if this is a generic type. For example, returns {@code ["java.lang.String"]} if this type is {@code List<String>}. */
- List<String> getActualGenericTypeArguments();
- /** Returns the class that the option or parameter value should be converted to when matched on the command
- * line. This method is <em>not</em> safe for annotation processors to use.
- * @return the class that the option or parameter value should be converted to
- */
- Class<?> getType();
- /** Returns the component class of an array, or the parameter type of a generic Collection, or the parameter
- * types of the key and the value of a generic Map.
- * This method is <em>not</em> safe for annotation processors to use.
- * @return the component type or types of an array, Collection or Map type
- */
- Class<?>[] getAuxiliaryTypes();
- }
- static class RuntimeTypeInfo implements ITypeInfo {
- private final Class<?> type;
- private final Class<?>[] auxiliaryTypes;
- private final List<String> actualGenericTypeArguments;
- RuntimeTypeInfo(Class<?> type, Class<?>[] auxiliaryTypes, List<String> actualGenericTypeArguments) {
- this.type = Assert.notNull(type, "type");
- this.auxiliaryTypes = Assert.notNull(auxiliaryTypes, "auxiliaryTypes").clone();
- this.actualGenericTypeArguments = actualGenericTypeArguments == null ? Collections.<String>emptyList() : Collections.unmodifiableList(new ArrayList<String>(actualGenericTypeArguments));
- }
- static ITypeInfo createForAuxType(Class<?> type) {
- return create(type, new Class[0], (Type) null, Range.valueOf("1"), String.class);
- }
- public static ITypeInfo create(Class<?> type,
- Class<?>[] annotationTypes,
- Type genericType,
- Range arity,
- Class<?> defaultType) {
- Class<?>[] auxiliaryTypes = RuntimeTypeInfo.inferTypes(type, annotationTypes, genericType);
- List<String> actualGenericTypeArguments = new ArrayList<String>();
- if (genericType instanceof ParameterizedType) {
- Class[] declaredTypeParameters = extractTypeParameters((ParameterizedType) genericType);
- for (Class<?> c : declaredTypeParameters) { actualGenericTypeArguments.add(c.getName()); }
- }
- return create(type, auxiliaryTypes, actualGenericTypeArguments, arity, defaultType);
- }
- public static ITypeInfo create(Class<?> type, Class<?>[] auxiliaryTypes, List<String> actualGenericTypeArguments, Range arity, Class<?> defaultType) {
- if (type == null) {
- if (auxiliaryTypes == null || auxiliaryTypes.length == 0) {
- if (arity.isVariable || arity.max > 1) {
- type = String[].class;
- } else if (arity.max == 1) {
- type = String.class;
- } else {
- type = defaultType;
- }
- } else {
- type = auxiliaryTypes[0];
- }
- }
- if (auxiliaryTypes == null || auxiliaryTypes.length == 0) {
- if (type.isArray()) {
- auxiliaryTypes = new Class<?>[] {type.getComponentType()};
- } else if (Collection.class.isAssignableFrom(type)) { // type is a collection but element type is unspecified
- auxiliaryTypes = new Class<?>[] {String.class}; // use String elements
- } else if (Map.class.isAssignableFrom(type)) { // type is a map but element type is unspecified
- auxiliaryTypes = new Class<?>[] {String.class, String.class}; // use String keys and String values
- } else {
- auxiliaryTypes = new Class<?>[] {type};
- }
- }
- return new RuntimeTypeInfo(type, auxiliaryTypes, actualGenericTypeArguments);
- }
- static Class<?>[] inferTypes(Class<?> propertyType, Class<?>[] annotationTypes, Type genericType) {
- if (annotationTypes != null && annotationTypes.length > 0) { return annotationTypes; }
- if (propertyType.isArray()) { return new Class<?>[] { propertyType.getComponentType() }; }
- if (CommandLine.isMultiValue(propertyType)) {
- if (genericType instanceof ParameterizedType) {// e.g. Map<Long, ? extends Number>
- return extractTypeParameters((ParameterizedType) genericType);
- }
- return new Class<?>[] {String.class, String.class}; // field is multi-value but not ParameterizedType
- }
- return new Class<?>[] {propertyType}; // not a multi-value field
- }
- static Class<?>[] extractTypeParameters(ParameterizedType genericType) {
- ParameterizedType parameterizedType = genericType;
- Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number
- Class<?>[] result = new Class<?>[paramTypes.length];
- for (int i = 0; i < paramTypes.length; i++) {
- if (paramTypes[i] instanceof Class) { result[i] = (Class<?>) paramTypes[i]; continue; } // e.g. Long
- if (paramTypes[i] instanceof WildcardType) { // e.g. ? extends Number
- WildcardType wildcardType = (WildcardType) paramTypes[i];
- Type[] lower = wildcardType.getLowerBounds(); // e.g. []
- if (lower.length > 0 && lower[0] instanceof Class) { result[i] = (Class<?>) lower[0]; continue; }
- Type[] upper = wildcardType.getUpperBounds(); // e.g. Number
- if (upper.length > 0 && upper[0] instanceof Class) { result[i] = (Class<?>) upper[0]; continue; }
- }
- Arrays.fill(result, String.class); return result; // too convoluted generic type, giving up
- }
- return result; // we inferred all types from ParameterizedType
- }
- public boolean isBoolean() { return auxiliaryTypes[0] == boolean.class || auxiliaryTypes[0] == Boolean.class; }
- public boolean isMultiValue() { return CommandLine.isMultiValue(type); }
- public boolean isArray() { return type.isArray(); }
- public boolean isCollection() { return Collection.class.isAssignableFrom(type); }
- public boolean isMap() { return Map.class.isAssignableFrom(type); }
- public boolean isEnum() { return auxiliaryTypes[0].isEnum(); }
- public String getClassName() { return type.getName(); }
- public String getClassSimpleName() { return type.getSimpleName(); }
- public Class<?> getType() { return type; }
- public Class<?>[] getAuxiliaryTypes() { return auxiliaryTypes; }
- public List<String> getActualGenericTypeArguments() { return actualGenericTypeArguments; }
- public List<ITypeInfo> getAuxiliaryTypeInfos() {
- List<ITypeInfo> result = new ArrayList<ITypeInfo>();
- for (Class<?> c : auxiliaryTypes) { result.add(createForAuxType(c)); }
- return result;
- }
- public List<String> getEnumConstantNames() {
- if (!isEnum()) { return Collections.emptyList(); }
- List<String> result = new ArrayList<String>();
- for (Object c : auxiliaryTypes[0].getEnumConstants()) { result.add(c.toString()); }
- return result;
- }
- public boolean equals(Object obj) {
- if (obj == this) { return true; }
- if (!(obj instanceof RuntimeTypeInfo)) { return false; }
- RuntimeTypeInfo other = (RuntimeTypeInfo) obj;
- return Arrays.equals(other.auxiliaryTypes, auxiliaryTypes) && type.equals(other.type);
- }
- public int hashCode() {
- return Arrays.hashCode(auxiliaryTypes) + 37 * Assert.hashCode(type);
- }
- public String toString() {
- return String.format("RuntimeTypeInfo(%s, aux=%s, collection=%s, map=%s)",
- type.getCanonicalName(), Arrays.toString(auxiliaryTypes), isCollection(), isMap());
- }
- }
- /** Internal interface to allow annotation processors to construct a command model at compile time.
- * @since 4.0 */
- public interface IAnnotatedElement {
- Object userObject();
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
- <T extends Annotation> T getAnnotation(Class<T> annotationClass);
- String getName();
- String getMixinName();
- boolean isArgSpec();
- boolean isOption();
- boolean isParameter();
- boolean isArgGroup();
- boolean isMixin();
- boolean isUnmatched();
- boolean isInjectSpec();
- boolean isMultiValue();
- boolean hasInitialValue();
- boolean isMethodParameter();
- int getMethodParamPosition();
- CommandLine.Model.IScope scope();
- CommandLine.Model.IGetter getter();
- CommandLine.Model.ISetter setter();
- ITypeInfo getTypeInfo();
- String getToString();
- }
- static class TypedMember implements IAnnotatedElement {
- final AccessibleObject accessible;
- final String name;
- final ITypeInfo typeInfo;
- boolean hasInitialValue;
- private IScope scope;
- private IGetter getter;
- private ISetter setter;
- static TypedMember createIfAnnotated(Field field, IScope scope) {
- return isAnnotated(field) ? new TypedMember(field, scope) : null;
- }
- static boolean isAnnotated(AnnotatedElement e) {
- return false
- || e.isAnnotationPresent(Option.class)
- || e.isAnnotationPresent(Parameters.class)
- || e.isAnnotationPresent(ArgGroup.class)
- || e.isAnnotationPresent(Unmatched.class)
- || e.isAnnotationPresent(Mixin.class)
- || e.isAnnotationPresent(Spec.class)
- || e.isAnnotationPresent(ParentCommand.class);
- }
- TypedMember(Field field) {
- accessible = Assert.notNull(field, "field");
- accessible.setAccessible(true);
- name = field.getName();
- typeInfo = createTypeInfo(field.getType(), field.getGenericType());
- hasInitialValue = true;
- }
- private TypedMember(Field field, IScope scope) {
- this(field);
- Object obj = ObjectScope.tryGet(scope);
- if (obj != null && Proxy.isProxyClass(obj.getClass())) {
- throw new InitializationException("Invalid picocli annotation on interface field");
- }
- FieldBinding binding = new FieldBinding(scope, field);
- getter = binding; setter = binding;
- this.scope = scope;
- hasInitialValue &= obj != null ;
- }
- static TypedMember createIfAnnotated(Method method, IScope scope, CommandSpec spec) {
- return isAnnotated(method) ? new TypedMember(method, scope, spec) : null;
- }
- private TypedMember(Method method, IScope scope, CommandSpec spec) {
- accessible = Assert.notNull(method, "method");
- accessible.setAccessible(true);
- name = propertyName(method.getName());
- Class<?>[] parameterTypes = method.getParameterTypes();
- boolean isGetter = parameterTypes.length == 0 && method.getReturnType() != Void.TYPE && method.getReturnType() != Void.class;
- boolean isSetter = parameterTypes.length > 0;
- if (isSetter == isGetter) { throw new InitializationException("Invalid method, must be either getter or setter: " + method); }
- if (isGetter) {
- hasInitialValue = true;
- typeInfo = createTypeInfo(method.getReturnType(), method.getGenericReturnType());
- Object proxy = ObjectScope.tryGet(scope);
- if (Proxy.isProxyClass(proxy.getClass())) {
- PicocliInvocationHandler handler = (PicocliInvocationHandler) Proxy.getInvocationHandler(proxy);
- PicocliInvocationHandler.ProxyBinding binding = handler.new ProxyBinding(method);
- getter = binding; setter = binding;
- initializeInitialValue(method);
- } else {
- //throw new IllegalArgumentException("Getter method but not a proxy: " + scope + ": " + method);
- MethodBinding binding = new MethodBinding(scope, method, spec);
- getter = binding; setter = binding;
- }
- } else {
- hasInitialValue = false;
- typeInfo = createTypeInfo(parameterTypes[0], method.getGenericParameterTypes()[0]);
- MethodBinding binding = new MethodBinding(scope, method, spec);
- getter = binding; setter = binding;
- }
- }
- TypedMember(MethodParam param, IScope scope) {
- accessible = Assert.notNull(param, "command method parameter");
- accessible.setAccessible(true);
- name = param.getName();
- typeInfo = createTypeInfo(param.getType(), param.getParameterizedType());
- // bind parameter
- ObjectBinding binding = new ObjectBinding();
- getter = binding; setter = binding;
- initializeInitialValue(param);
- hasInitialValue = true;
- }
- private ITypeInfo createTypeInfo(Class<?> type, Type genericType) {
- Range arity = null;
- if (isOption()) { arity = Range.valueOf(getAnnotation(Option.class).arity()); }
- if (isParameter()) { arity = Range.valueOf(getAnnotation(Parameters.class).arity()); }
- if (arity == null || arity.isUnspecified) {
- if (isOption()) {
- arity = (type == null || isBoolean(type)) ? Range.valueOf("0") : Range.valueOf("1");
- } else {
- arity = Range.valueOf("1");
- }
- arity = arity.unspecified(true);
- }
- return RuntimeTypeInfo.create(type, annotationTypes(), genericType, arity, (isOption() ? boolean.class : String.class));
- }
- private void initializeInitialValue(Object arg) {
- Class<?> type = typeInfo.getType();
- try {
- if (type == Boolean.TYPE ) { setter.set(false); }
- else if (type == Byte.TYPE ) { setter.set(Byte.valueOf((byte) 0)); }
- else if (type == Character.TYPE) { setter.set(Character.valueOf((char) 0)); }
- else if (type == Short.TYPE ) { setter.set(Short.valueOf((short) 0)); }
- else if (type == Integer.TYPE ) { setter.set(Integer.valueOf(0)); }
- else if (type == Long.TYPE ) { setter.set(Long.valueOf(0L)); }
- else if (type == Float.TYPE ) { setter.set(Float.valueOf(0f)); }
- else if (type == Double.TYPE ) { setter.set(Double.valueOf(0d)); }
- else { setter.set(null); }
- } catch (Exception ex) {
- throw new InitializationException("Could not set initial value for " + arg + ": " + ex.toString(), ex);
- }
- }
- public Object userObject() { return accessible; }
- public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) { return accessible.isAnnotationPresent(annotationClass); }
- public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { return accessible.getAnnotation(annotationClass); }
- public String getName() { return name; }
- public boolean isArgSpec() { return isOption() || isParameter() || (isMethodParameter() && !isMixin()); }
- public boolean isOption() { return isAnnotationPresent(Option.class); }
- public boolean isParameter() { return isAnnotationPresent(Parameters.class); }
- public boolean isArgGroup() { return isAnnotationPresent(ArgGroup.class); }
- public boolean isMixin() { return isAnnotationPresent(Mixin.class); }
- public boolean isUnmatched() { return isAnnotationPresent(Unmatched.class); }
- public boolean isInjectSpec() { return isAnnotationPresent(Spec.class); }
- public boolean isMultiValue() { return CommandLine.isMultiValue(getType()); }
- public IScope scope() { return scope; }
- public IGetter getter() { return getter; }
- public ISetter setter() { return setter; }
- public ITypeInfo getTypeInfo() { return typeInfo; }
- public Class<?> getType() { return typeInfo.getType(); }
- public Class<?>[] getAuxiliaryTypes() { return typeInfo.getAuxiliaryTypes(); }
- private Class<?>[] annotationTypes() {
- if (isOption()) { return getAnnotation(Option.class).type(); }
- if (isParameter()) { return getAnnotation(Parameters.class).type(); }
- return new Class[0];
- }
- public String toString() { return accessible.toString(); }
- public String getToString() {
- if (isMixin()) { return abbreviate("mixin from member " + toGenericString()); }
- return (accessible instanceof Field ? "field " : accessible instanceof Method ? "method " : accessible.getClass().getSimpleName() + " ") + abbreviate(toGenericString());
- }
- public String toGenericString() { return accessible instanceof Field ? ((Field) accessible).toGenericString() : accessible instanceof Method ? ((Method) accessible).toGenericString() : ((MethodParam)accessible).toString(); }
- public boolean hasInitialValue() { return hasInitialValue; }
- public boolean isMethodParameter() { return accessible instanceof MethodParam; }
- public int getMethodParamPosition() { return isMethodParameter() ? ((MethodParam) accessible).position : -1; }
- public String getMixinName() {
- String annotationName = getAnnotation(Mixin.class).name();
- return empty(annotationName) ? getName() : annotationName;
- }
- static String propertyName(String methodName) {
- if (methodName.length() > 3 && (methodName.startsWith("get") || methodName.startsWith("set"))) { return decapitalize(methodName.substring(3)); }
- return decapitalize(methodName);
- }
- private static String decapitalize(String name) {
- if (name == null || name.length() == 0) { return name; }
- char[] chars = name.toCharArray();
- chars[0] = Character.toLowerCase(chars[0]);
- return new String(chars);
- }
- static String abbreviate(String text) {
- return text.replace("private ", "")
- .replace("protected ", "")
- .replace("public ", "")
- .replace("java.lang.", "");
- }
- }
- /** Utility class for getting resource bundle strings.
- * Enhances the standard <a href="https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
- * with support for String arrays and qualified keys: keys that may or may not be prefixed with the fully qualified command name.
- * <p>Example properties resource bundle:</p><pre>
- * # Usage Help Message Sections
- * # ---------------------------
- * # Numbered resource keys can be used to create multi-line sections.
- * usage.headerHeading = This is my app. There are other apps like it but this one is mine.%n
- * usage.header = header first line
- * usage.header.0 = header second line
- * usage.descriptionHeading = Description:%n
- * usage.description.0 = first line
- * usage.description.1 = second line
- * usage.description.2 = third line
- * usage.synopsisHeading = Usage:\u0020
- * # Leading whitespace is removed by default. Start with \u0020 to keep the leading whitespace.
- * usage.customSynopsis.0 = Usage: ln [OPTION]... [-T] TARGET LINK_NAME (1st form)
- * usage.customSynopsis.1 = \u0020 or: ln [OPTION]... TARGET (2nd form)
- * usage.customSynopsis.2 = \u0020 or: ln [OPTION]... TARGET... DIRECTORY (3rd form)
- * # Headings can contain the %n character to create multi-line values.
- * usage.parameterListHeading = %nPositional parameters:%n
- * usage.optionListHeading = %nOptions:%n
- * usage.commandListHeading = %nCommands:%n
- * usage.footerHeading = Powered by picocli%n
- * usage.footer = footer
- *
- * # Option Descriptions
- * # -------------------
- * # Use numbered keys to create multi-line descriptions.
- * help = Show this help message and exit.
- * version = Print version information and exit.
- * </pre>
- * <p>Resources for multiple commands can be specified in a single ResourceBundle. Keys and their value can be
- * shared by multiple commands (so you don't need to repeat them for every command), but keys can be prefixed with
- * {@code fully qualified command name + "."} to specify different values for different commands.
- * The most specific key wins. For example: </p>
- * <pre>
- * jfrog.rt.usage.header = Artifactory commands
- * jfrog.rt.config.usage.header = Configure Artifactory details.
- * jfrog.rt.upload.usage.header = Upload files.
- *
- * jfrog.bt.usage.header = Bintray commands
- * jfrog.bt.config.usage.header = Configure Bintray details.
- * jfrog.bt.upload.usage.header = Upload files.
- *
- * # shared between all commands
- * usage.footerHeading = Environment Variables:
- * usage.footer.0 = footer line 0
- * usage.footer.1 = footer line 1
- * </pre>
- * @see Command#resourceBundle()
- * @see Option#descriptionKey()
- * @see OptionSpec#description()
- * @see PositionalParamSpec#description()
- * @see CommandSpec#qualifiedName(String)
- * @since 3.6 */
- public static class Messages {
- private final CommandSpec spec;
- private final String bundleBaseName;
- private final ResourceBundle rb;
- private final Set<String> keys;
- public Messages(CommandSpec spec, String baseName) {
- this(spec, baseName, createBundle(baseName));
- }
- public Messages(CommandSpec spec, ResourceBundle rb) {
- this(spec, extractName(rb), rb);
- }
- public Messages(CommandSpec spec, String baseName, ResourceBundle rb) {
- this.spec = Assert.notNull(spec, "CommandSpec");
- this.bundleBaseName = baseName;
- this.rb = rb;
- this.keys = keys(rb);
- }
- private static ResourceBundle createBundle(String baseName) {
- return ResourceBundle.getBundle(baseName);
- }
- private static String extractName(ResourceBundle rb) {
- try { // ResourceBundle.getBaseBundleName was introduced in Java 8
- return (String) ResourceBundle.class.getDeclaredMethod("getBaseBundleName").invoke(rb);
- } catch (Exception ignored) { return ""; }
- }
- private static Set<String> keys(ResourceBundle rb) {
- if (rb == null) { return Collections.emptySet(); }
- Set<String> keys = new LinkedHashSet<String>();
- for (Enumeration<String> k = rb.getKeys(); k.hasMoreElements(); keys.add(k.nextElement()));
- return keys;
- }
- /** Returns a copy of the specified Messages object with the CommandSpec replaced by the specified one.
- * @param spec the CommandSpec of the returned Messages
- * @param original the Messages object whose ResourceBundle to reference
- * @return a Messages object with the specified CommandSpec and the ResourceBundle of the specified Messages object
- */
- public static Messages copy(CommandSpec spec, Messages original) {
- return original == null ? null : new Messages(spec, original.bundleBaseName, original.rb);
- }
- /** Returns {@code true} if the specified {@code Messages} is {@code null} or has a {@code null ResourceBundle}. */
- public static boolean empty(Messages messages) { return messages == null || messages.rb == null; }
- /** Returns the String value found in the resource bundle for the specified key, or the specified default value if not found.
- * @param key unqualified resource bundle key. This method will first try to find a value by qualifying the key with the command's fully qualified name,
- * and if not found, it will try with the unqualified key.
- * @param defaultValue value to return if the resource bundle is null or empty, or if no value was found by the qualified or unqualified key
- * @return the String value found in the resource bundle for the specified key, or the specified default value
- */
- public String getString(String key, String defaultValue) {
- if (isEmpty()) { return defaultValue; }
- String cmd = spec.qualifiedName(".");
- if (keys.contains(cmd + "." + key)) { return rb.getString(cmd + "." + key); }
- if (keys.contains(key)) { return rb.getString(key); }
- return defaultValue;
- }
- boolean isEmpty() { return rb == null || keys.isEmpty(); }
- /** Returns the String array value found in the resource bundle for the specified key, or the specified default value if not found.
- * Multi-line strings can be specified in the resource bundle with {@code key.0}, {@code key.1}, {@code key.2}, etc.
- * @param key unqualified resource bundle key. This method will first try to find a value by qualifying the key with the command's fully qualified name,
- * and if not found, it will try with the unqualified key.
- * @param defaultValues value to return if the resource bundle is null or empty, or if no value was found by the qualified or unqualified key
- * @return the String array value found in the resource bundle for the specified key, or the specified default value
- */
- public String[] getStringArray(String key, String[] defaultValues) {
- if (isEmpty()) { return defaultValues; }
- String cmd = spec.qualifiedName(".");
- List<String> result = addAllWithPrefix(rb, cmd + "." + key, keys, new ArrayList<String>());
- if (!result.isEmpty()) { return result.toArray(new String[0]); }
- addAllWithPrefix(rb, key, keys, result);
- return result.isEmpty() ? defaultValues : result.toArray(new String[0]);
- }
- private static List<String> addAllWithPrefix(ResourceBundle rb, String key, Set<String> keys, List<String> result) {
- if (keys.contains(key)) { result.add(rb.getString(key)); }
- for (int i = 0; true; i++) {
- String elementKey = key + "." + i;
- if (keys.contains(elementKey)) {
- result.add(rb.getString(elementKey));
- } else {
- return result;
- }
- }
- }
- /** Returns the ResourceBundle of the specified Messages object or {@code null} if the specified Messages object is {@code null}.
- * @since 4.0 */
- public static String resourceBundleBaseName(Messages messages) { return messages == null ? null : messages.resourceBundleBaseName(); }
- /** Returns the ResourceBundle of the specified Messages object or {@code null} if the specified Messages object is {@code null}. */
- public static ResourceBundle resourceBundle(Messages messages) { return messages == null ? null : messages.resourceBundle(); }
- /** Returns the base name of the ResourceBundle of this object or {@code null}.
- * @since 4.0 */
- public String resourceBundleBaseName() { return bundleBaseName; }
- /** Returns the ResourceBundle of this object or {@code null}. */
- public ResourceBundle resourceBundle() { return rb; }
- /** Returns the CommandSpec of this object, never {@code null}. */
- public CommandSpec commandSpec() { return spec; }
- }
- private static class CommandReflection {
- static ArgGroupSpec extractArgGroupSpec(IAnnotatedElement member, IFactory factory, CommandSpec commandSpec, boolean annotationsAreMandatory) throws Exception {
- Object instance = null;
- try { instance = member.getter().get(); } catch (Exception ignored) {}
- Class<?> cls = instance == null ? member.getTypeInfo().getType() : instance.getClass();
- Tracer t = new Tracer();
- if (member.isMultiValue()) {
- cls = member.getTypeInfo().getAuxiliaryTypes()[0];
- }
- IScope scope = new ObjectScope(instance);
- ArgGroupSpec.Builder builder = ArgGroupSpec.builder(member);
- builder.updateArgGroupAttributes(member.getAnnotation(ArgGroup.class));
- if (member.isOption() || member.isParameter()) {
- if (member instanceof TypedMember) { validateArgSpecMember((TypedMember) member); }
- builder.addArg(buildArgForMember(member, factory));
- }
- Stack<Class<?>> hierarchy = new Stack<Class<?>>();
- while (cls != null) { hierarchy.add(cls); cls = cls.getSuperclass(); }
- boolean hasArgAnnotation = false;
- while (!hierarchy.isEmpty()) {
- cls = hierarchy.pop();
- hasArgAnnotation |= initFromAnnotatedFields(scope, cls, commandSpec, builder, factory);
- }
- ArgGroupSpec result = builder.build();
- if (annotationsAreMandatory) {validateArgGroupSpec(result, hasArgAnnotation, cls.getName()); }
- return result;
- }
- static CommandSpec extractCommandSpec(Object command, IFactory factory, boolean annotationsAreMandatory) {
- Class<?> cls = command.getClass();
- Tracer t = new Tracer();
- t.debug("Creating CommandSpec for object of class %s with factory %s%n", cls.getName(), factory.getClass().getName());
- if (command instanceof CommandSpec) { return (CommandSpec) command; }
- Object[] tmp = getOrCreateInstance(cls, command, factory, t);
- cls = (Class<?>) tmp[0];
- Object instance = tmp[1];
- String commandClassName = (String) tmp[2];
- CommandSpec result = CommandSpec.wrapWithoutInspection(Assert.notNull(instance, "command"));
- ObjectScope scope = new ObjectScope(instance);
- Stack<Class<?>> hierarchy = new Stack<Class<?>>();
- while (cls != null) { hierarchy.add(cls); cls = cls.getSuperclass(); }
- boolean hasCommandAnnotation = false;
- boolean mixinStandardHelpOptions = false;
- while (!hierarchy.isEmpty()) {
- cls = hierarchy.pop();
- Command cmd = cls.getAnnotation(Command.class);
- if (cmd != null) {
- result.updateCommandAttributes(cmd, factory);
- initSubcommands(cmd, cls, result, factory);
- // addGroups(cmd, groupBuilders); // TODO delete
- hasCommandAnnotation = true;
- }
- hasCommandAnnotation |= initFromAnnotatedFields(scope, cls, result, null, factory);
- if (cls.isAnnotationPresent(Command.class)) {
- mixinStandardHelpOptions |= cls.getAnnotation(Command.class).mixinStandardHelpOptions();
- }
- }
- result.mixinStandardHelpOptions(mixinStandardHelpOptions); //#377 Standard help options should be added last
- if (command instanceof Method) {
- Method method = (Method) command;
- t.debug("Using method %s as command %n", method);
- commandClassName = method.toString();
- Command cmd = method.getAnnotation(Command.class);
- result.updateCommandAttributes(cmd, factory);
- result.setAddMethodSubcommands(false); // method commands don't have method subcommands
- initSubcommands(cmd, null, result, factory);
- hasCommandAnnotation = true;
- result.mixinStandardHelpOptions(method.getAnnotation(Command.class).mixinStandardHelpOptions());
- initFromMethodParameters(scope, method, result, null, factory);
- // set command name to method name, unless @Command#name is set
- result.initName(((Method)command).getName());
- }
- result.updateArgSpecMessages();
- if (annotationsAreMandatory) {validateCommandSpec(result, hasCommandAnnotation, commandClassName); }
- result.withToString(commandClassName).validate();
- return result;
- }
- private static Object[] getOrCreateInstance(Class<?> cls, Object command, IFactory factory, Tracer t) {
- Object instance = command;
- String commandClassName = cls.getName();
- if (command instanceof Class) {
- cls = (Class) command;
- commandClassName = cls.getName();
- try {
- t.debug("Getting a %s instance from the factory%n", cls.getName());
- instance = DefaultFactory.create(factory, cls);
- cls = instance.getClass();
- commandClassName = cls.getName();
- t.debug("Factory returned a %s instance%n", commandClassName);
- } catch (InitializationException ex) {
- if (cls.isInterface()) {
- t.debug("%s. Creating Proxy for interface %s%n", ex.getCause(), cls.getName());
- instance = Proxy.newProxyInstance(cls.getClassLoader(), new Class<?>[]{cls}, new PicocliInvocationHandler());
- } else {
- throw ex;
- }
- }
- } else if (command instanceof Method) {
- cls = null; // don't mix in options/positional params from outer class @Command
- } else if (instance == null) {
- t.debug("Getting a %s instance from the factory%n", cls.getName());
- instance = DefaultFactory.create(factory, cls);
- t.debug("Factory returned a %s instance%n", instance.getClass().getName());
- }
- return new Object[] { cls, instance, commandClassName };
- }
- private static void initSubcommands(Command cmd, Class<?> cls, CommandSpec parent, IFactory factory) {
- for (Class<?> sub : cmd.subcommands()) {
- try {
- if (Help.class == sub) { throw new InitializationException(Help.class.getName() + " is not a valid subcommand. Did you mean " + HelpCommand.class.getName() + "?"); }
- CommandLine subcommandLine = toCommandLine(factory.create(sub), factory);
- parent.addSubcommand(subcommandName(sub), subcommandLine);
- initParentCommand(subcommandLine.getCommandSpec().userObject(), parent.userObject());
- }
- catch (InitializationException ex) { throw ex; }
- catch (NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " +
- sub.getName() + ": the class has no constructor", ex); }
- catch (Exception ex) {
- throw new InitializationException("Could not instantiate and add subcommand " +
- sub.getName() + ": " + ex, ex);
- }
- }
- if (cmd.addMethodSubcommands() && cls != null) {
- for (CommandLine sub : CommandSpec.createMethodSubcommands(cls, factory)) {
- parent.addSubcommand(sub.getCommandName(), sub);
- }
- }
- }
- static void initParentCommand(Object subcommand, Object parent) {
- if (subcommand == null) { return; }
- try {
- Class<?> cls = subcommand.getClass();
- while (cls != null) {
- for (Field f : cls.getDeclaredFields()) {
- if (f.isAnnotationPresent(ParentCommand.class)) {
- f.setAccessible(true);
- f.set(subcommand, parent);
- }
- }
- cls = cls.getSuperclass();
- }
- } catch (Exception ex) {
- throw new InitializationException("Unable to initialize @ParentCommand field: " + ex, ex);
- }
- }
- private static String subcommandName(Class<?> sub) {
- Command subCommand = sub.getAnnotation(Command.class);
- if (subCommand == null || Help.DEFAULT_COMMAND_NAME.equals(subCommand.name())) {
- throw new InitializationException("Subcommand " + sub.getName() +
- " is missing the mandatory @Command annotation with a 'name' attribute");
- }
- return subCommand.name();
- }
- private static boolean initFromAnnotatedFields(IScope scope, Class<?> cls, CommandSpec receiver, ArgGroupSpec.Builder groupBuilder, IFactory factory) {
- boolean result = false;
- for (Field field : cls.getDeclaredFields()) {
- result |= initFromAnnotatedTypedMembers(TypedMember.createIfAnnotated(field, scope), receiver, groupBuilder, factory);
- }
- for (Method method : cls.getDeclaredMethods()) {
- result |= initFromAnnotatedTypedMembers(TypedMember.createIfAnnotated(method, scope, receiver), receiver, groupBuilder, factory);
- }
- return result;
- }
- @SuppressWarnings("unchecked")
- private static boolean initFromAnnotatedTypedMembers(TypedMember member,
- CommandSpec commandSpec,
- ArgGroupSpec.Builder groupBuilder,
- IFactory factory) {
- boolean result = false;
- if (member == null) { return result; }
- if (member.isMixin()) {
- assertNoDuplicateAnnotations(member, Mixin.class, Option.class, Parameters.class, Unmatched.class, Spec.class, ArgGroup.class);
- if (groupBuilder != null) {
- throw new InitializationException("@Mixins are not supported on @ArgGroups");
- // TODO groupBuilder.addMixin(member.getMixinName(), buildMixinForMember(member, factory));
- } else {
- commandSpec.addMixin(member.getMixinName(), buildMixinForMember(member, factory));
- }
- result = true;
- }
- if (member.isArgGroup()) {
- assertNoDuplicateAnnotations(member, ArgGroup.class, Spec.class, Parameters.class, Option.class, Unmatched.class, Mixin.class);
- if (groupBuilder != null) {
- groupBuilder.addSubgroup(buildArgGroupForMember(member, factory, commandSpec));
- } else {
- commandSpec.addArgGroup(buildArgGroupForMember(member, factory, commandSpec));
- }
- return true;
- }
- if (member.isUnmatched()) {
- assertNoDuplicateAnnotations(member, Unmatched.class, Mixin.class, Option.class, Parameters.class, Spec.class, ArgGroup.class);
- if (groupBuilder != null) {
- // we don't support @Unmatched on @ArgGroup class members...
- throw new InitializationException("@Unmatched are not supported on @ArgGroups");
- } else {
- commandSpec.addUnmatchedArgsBinding(buildUnmatchedForMember(member));
- }
- }
- if (member.isArgSpec()) {
- validateArgSpecMember(member);
- if (groupBuilder != null) {
- groupBuilder.addArg(buildArgForMember(member, factory));
- } else {
- commandSpec.add(buildArgForMember(member, factory));
- }
- result = true;
- }
- if (member.isInjectSpec()) {
- validateInjectSpec(member);
- try { member.setter().set(commandSpec); } catch (Exception ex) { throw new InitializationException("Could not inject spec", ex); }
- }
- return result;
- }
- private static boolean initFromMethodParameters(IScope scope, Method method, CommandSpec receiver, ArgGroupSpec.Builder groupBuilder, IFactory factory) {
- boolean result = false;
- int optionCount = 0;
- for (int i = 0, count = method.getParameterTypes().length; i < count; i++) {
- MethodParam param = new MethodParam(method, i);
- if (param.isAnnotationPresent(Option.class) || param.isAnnotationPresent(Mixin.class)) {
- optionCount++;
- } else {
- param.position = i - optionCount;
- }
- result |= initFromAnnotatedTypedMembers(new TypedMember(param, scope), receiver, groupBuilder, factory);
- }
- return result;
- }
- @SuppressWarnings("unchecked")
- private static void validateArgSpecMember(TypedMember member) {
- if (!member.isArgSpec()) { throw new IllegalStateException("Bug: validateArgSpecMember() should only be called with an @Option or @Parameters member"); }
- if (member.isOption()) {
- assertNoDuplicateAnnotations(member, Option.class, Unmatched.class, Mixin.class, Parameters.class, Spec.class, ArgGroup.class);
- } else {
- assertNoDuplicateAnnotations(member, Parameters.class, Option.class, Unmatched.class, Mixin.class, Spec.class, ArgGroup.class);
- }
- if (!(member.accessible instanceof Field)) { return; }
- Field field = (Field) member.accessible;
- if (Modifier.isFinal(field.getModifiers()) && (field.getType().isPrimitive() || String.class.isAssignableFrom(field.getType()))) {
- throw new InitializationException("Constant (final) primitive and String fields like " + field + " cannot be used as " +
- (member.isOption() ? "an @Option" : "a @Parameter") + ": compile-time constant inlining may hide new values written to it.");
- }
- }
- private static void validateCommandSpec(CommandSpec result, boolean hasCommandAnnotation, String commandClassName) {
- if (!hasCommandAnnotation && result.positionalParameters.isEmpty() && result.optionsByNameMap.isEmpty() && result.unmatchedArgs.isEmpty()) {
- throw new InitializationException(commandClassName + " is not a command: it has no @Command, @Option, @Parameters or @Unmatched annotations");
- }
- }
- private static void validateArgGroupSpec(ArgGroupSpec result, boolean hasArgAnnotation, String className) {
- if (!hasArgAnnotation && result.args().isEmpty()) {
- throw new InitializationException(className + " is not a group: it has no @Option or @Parameters annotations");
- }
- }
- @SuppressWarnings("unchecked")
- private static void validateInjectSpec(TypedMember member) {
- if (!member.isInjectSpec()) { throw new IllegalStateException("Bug: validateInjectSpec() should only be called with @Spec members"); }
- assertNoDuplicateAnnotations(member, Spec.class, Parameters.class, Option.class, Unmatched.class, Mixin.class, ArgGroup.class);
- if (!CommandSpec.class.getName().equals(member.getTypeInfo().getClassName())) {
- throw new InitializationException("@picocli.CommandLine.Spec annotation is only supported on fields of type " + CommandSpec.class.getName());
- }
- }
- private static void assertNoDuplicateAnnotations(TypedMember member, Class<? extends Annotation> myAnnotation, Class<? extends Annotation>... forbidden) {
- for (Class<? extends Annotation> annotation : forbidden) {
- if (member.isAnnotationPresent(annotation)) {
- throw new DuplicateOptionAnnotationsException("A member cannot have both @" + myAnnotation.getSimpleName() + " and @" + annotation.getSimpleName() + " annotations, but '" + member + "' has both.");
- }
- }
- }
- private static CommandSpec buildMixinForMember(IAnnotatedElement member, IFactory factory) {
- try {
- Object userObject = member.getter().get();
- if (userObject == null) {
- userObject = factory.create(member.getTypeInfo().getType());
- member.setter().set(userObject);
- }
- CommandSpec result = CommandSpec.forAnnotatedObject(userObject, factory);
- return result.withToString(member.getToString());
- } catch (InitializationException ex) {
- throw ex;
- } catch (Exception ex) {
- throw new InitializationException("Could not access or modify mixin member " + member + ": " + ex, ex);
- }
- }
- private static ArgSpec buildArgForMember(IAnnotatedElement member, IFactory factory) {
- if (member.isOption()) { return OptionSpec.builder(member, factory).build(); }
- else if (member.isParameter()) { return PositionalParamSpec.builder(member, factory).build(); }
- else { return PositionalParamSpec.builder(member, factory).build(); }
- }
- private static ArgGroupSpec buildArgGroupForMember(IAnnotatedElement member, IFactory factory, CommandSpec commandSpec) {
- try {
- return extractArgGroupSpec(member, factory, commandSpec, true);
- } catch (InitializationException ex) {
- throw ex;
- } catch (Exception ex) {
- throw new InitializationException("Could not access or modify ArgGroup member " + member + ": " + ex, ex);
- }
- }
- private static UnmatchedArgsBinding buildUnmatchedForMember(final IAnnotatedElement member) {
- ITypeInfo info = member.getTypeInfo();
- if (!(info.getClassName().equals(String[].class.getName()) ||
- (info.isCollection() && info.getActualGenericTypeArguments().equals(Arrays.asList(String.class.getName()))))) {
- throw new InitializationException("Invalid type for " + member + ": must be either String[] or List<String>");
- }
- if (info.getClassName().equals(String[].class.getName())) {
- return UnmatchedArgsBinding.forStringArrayConsumer(member.setter());
- } else {
- return UnmatchedArgsBinding.forStringCollectionSupplier(new IGetter() {
- @SuppressWarnings("unchecked") public <T> T get() throws Exception {
- List<String> result = (List<String>) member.getter().get();
- if (result == null) {
- result = new ArrayList<String>();
- member.setter().set(result);
- }
- return (T) result;
- }
- });
- }
- }
- }
- static class FieldBinding implements IGetter, ISetter {
- private final IScope scope;
- private final Field field;
- private static IScope asScope(Object scope) { return scope instanceof IScope ? ((IScope) scope) : new ObjectScope(scope); }
- FieldBinding(Object scope, Field field) { this(asScope(scope), field); }
- FieldBinding(IScope scope, Field field) { this.scope = scope; this.field = field; }
- public <T> T get() throws PicocliException {
- Object obj = null;
- try { obj = scope.get(); }
- catch (Exception ex) { throw new PicocliException("Could not get scope for field " + field, ex); }
- try {
- @SuppressWarnings("unchecked") T result = (T) field.get(obj);
- return result;
- } catch (Exception ex) {
- throw new PicocliException("Could not get value for field " + field, ex);
- }
- }
- public <T> T set(T value) throws PicocliException {
- Object obj = null;
- try { obj = scope.get(); }
- catch (Exception ex) { throw new PicocliException("Could not get scope for field " + field, ex); }
- try {
- @SuppressWarnings("unchecked") T result = (T) field.get(obj);
- field.set(obj, value);
- return result;
- } catch (Exception ex) {
- throw new PicocliException("Could not set value for field " + field + " to " + value, ex);
- }
- }
- public String toString() {
- return String.format("%s(%s %s.%s)", getClass().getSimpleName(), field.getType().getName(),
- field.getDeclaringClass().getName(), field.getName());
- }
- }
- static class MethodBinding implements IGetter, ISetter {
- private final IScope scope;
- private final Method method;
- private final CommandSpec spec;
- private Object currentValue;
- MethodBinding(IScope scope, Method method, CommandSpec spec) {
- this.scope = scope;
- this.method = method;
- this.spec = spec;
- }
- @SuppressWarnings("unchecked") public <T> T get() { return (T) currentValue; }
- public <T> T set(T value) throws PicocliException {
- Object obj = null;
- try { obj = scope.get(); }
- catch (Exception ex) { throw new PicocliException("Could not get scope for method " + method, ex); }
- try {
- @SuppressWarnings("unchecked") T result = (T) currentValue;
- method.invoke(obj, value);
- currentValue = value;
- return result;
- } catch (InvocationTargetException ex) {
- if (ex.getCause() instanceof PicocliException) { throw (PicocliException) ex.getCause(); }
- throw createParameterException(value, ex.getCause());
- } catch (Exception ex) {
- throw createParameterException(value, ex);
- }
- }
- private ParameterException createParameterException(Object value, Throwable t) {
- CommandLine cmd = spec.commandLine() == null ? new CommandLine(spec) : spec.commandLine();
- return new ParameterException(cmd, "Could not invoke " + method + " with " + value, t);
- }
- public String toString() {
- return String.format("%s(%s)", getClass().getSimpleName(), method);
- }
- }
- private static class PicocliInvocationHandler implements InvocationHandler {
- final Map<String, Object> map = new HashMap<String, Object>();
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- return map.get(method.getName());
- }
- class ProxyBinding implements IGetter, ISetter {
- private final Method method;
- ProxyBinding(Method method) { this.method = Assert.notNull(method, "method"); }
- @SuppressWarnings("unchecked") public <T> T get() { return (T) map.get(method.getName()); }
- public <T> T set(T value) {
- T result = get();
- map.put(method.getName(), value);
- return result;
- }
- }
- }
- private static class ObjectBinding implements IGetter, ISetter {
- private Object value;
- @SuppressWarnings("unchecked") public <T> T get() { return (T) value; }
- public <T> T set(T value) {
- @SuppressWarnings("unchecked") T result = value;
- this.value = value;
- return result;
- }
- public String toString() {
- return String.format("%s(value=%s)", getClass().getSimpleName(), value);
- }
- }
- static class ObjectScope implements IScope {
- private Object value;
- public ObjectScope(Object value) { this.value = value; }
- @SuppressWarnings("unchecked") public <T> T get() { return (T) value; }
- @SuppressWarnings("unchecked") public <T> T set(T value) { T old = (T) this.value; this.value = value; return old; }
- public static Object tryGet(IScope scope) {
- try {
- return scope.get();
- } catch (Exception e) {
- throw new InitializationException("Could not get scope value", e);
- }
- }
- public String toString() { return String.format("Scope(value=%s)", value); }
- }
- }
- /** Encapsulates the result of parsing an array of command line arguments.
- * @since 3.0 */
- public static class ParseResult {
- private final CommandSpec commandSpec;
- private final List<OptionSpec> matchedOptions;
- private final List<PositionalParamSpec> matchedUniquePositionals;
- private final List<String> originalArgs;
- private final List<String> unmatched;
- private final List<List<PositionalParamSpec>> matchedPositionalParams;
- private final List<Exception> errors;
- private final MatchedGroup matchedGroup;
- final List<Object> tentativeMatch;
- private final ParseResult subcommand;
- private final boolean usageHelpRequested;
- private final boolean versionHelpRequested;
- private ParseResult(ParseResult.Builder builder) {
- commandSpec = builder.commandSpec;
- subcommand = builder.subcommand;
- matchedOptions = new ArrayList<OptionSpec>(builder.options);
- unmatched = new ArrayList<String>(builder.unmatched);
- originalArgs = new ArrayList<String>(builder.originalArgList);
- matchedUniquePositionals = new ArrayList<PositionalParamSpec>(builder.positionals);
- matchedPositionalParams = new ArrayList<List<PositionalParamSpec>>(builder.positionalParams);
- errors = new ArrayList<Exception>(builder.errors);
- usageHelpRequested = builder.usageHelpRequested;
- versionHelpRequested = builder.versionHelpRequested;
- tentativeMatch = builder.nowProcessing;
- matchedGroup = builder.matchedGroup.trim();
- }
- /** Creates and returns a new {@code ParseResult.Builder} for the specified command spec. */
- public static Builder builder(CommandSpec commandSpec) { return new Builder(commandSpec); }
- /**
- * Returns the matches for the specified argument group.
- * @since 4.0 */
- public List<MatchedGroup> findMatchedGroup(ArgGroupSpec group) {
- return matchedGroup.findMatchedGroup(group, new ArrayList<MatchedGroup>());
- }
- /**
- * Returns the top-level container for the {@link ArgGroupSpec ArgGroupSpec} match or matches found.
- * <p>
- * If the user input was a valid combination of group arguments, the returned list should contain a single
- * {@linkplain MatchedGroupMultiple multiple}. Details of the {@linkplain MatchedGroup matched groups} encountered
- * on the command line can be obtained via its {@link MatchedGroupMultiple#matchedSubgroups() matchedSubgroups()} method.
- * The top-level multiple returned by this method contains no {@linkplain MatchedGroupMultiple#matchedValues(ArgSpec) matched arguments}.
- * </p><p>
- * If the returned list contains more than one {@linkplain MatchedGroupMultiple multiple}, the user input was invalid:
- * the maximum {@linkplain ArgGroup#multiplicity() multiplicity} of a group was exceeded, and the parser created an extra
- * {@code multiple} to capture the values. Usually this results in a {@link ParameterException ParameterException}
- * being thrown by the {@code parse} method, unless the parser is configured to {@linkplain ParserSpec#collectErrors() collect errors}.
- * </p>
- * @since 4.0 */
- public List<MatchedGroupMultiple> getMatchedGroupMultiples() {
- return matchedGroup.multiples();
- }
- /** Returns the option with the specified short name, or {@code null} if no option with that name was matched
- * on the command line.
- * <p>Use {@link OptionSpec#getValue() getValue} on the returned {@code OptionSpec} to get the matched value (or values),
- * converted to the type of the option. Alternatively, use {@link OptionSpec#stringValues() stringValues}
- * to get the matched String values after they were {@linkplain OptionSpec#splitRegex() split} into parts, or
- * {@link OptionSpec#originalStringValues() originalStringValues} to get the original String values that were
- * matched on the command line, before any processing.
- * </p><p>To get the {@linkplain OptionSpec#defaultValue() default value} of an option that was
- * {@linkplain #hasMatchedOption(char) <em>not</em> matched} on the command line, use
- * {@code parseResult.commandSpec().findOption(shortName).getValue()}. </p>
- * @see CommandSpec#findOption(char) */
- public OptionSpec matchedOption(char shortName) { return CommandSpec.findOption(shortName, matchedOptions); }
- /** Returns the option with the specified name, or {@code null} if no option with that name was matched on the command line.
- * <p>Use {@link OptionSpec#getValue() getValue} on the returned {@code OptionSpec} to get the matched value (or values),
- * converted to the type of the option. Alternatively, use {@link OptionSpec#stringValues() stringValues}
- * to get the matched String values after they were {@linkplain OptionSpec#splitRegex() split} into parts, or
- * {@link OptionSpec#originalStringValues() originalStringValues} to get the original String values that were
- * matched on the command line, before any processing.
- * </p><p>To get the {@linkplain OptionSpec#defaultValue() default value} of an option that was
- * {@linkplain #hasMatchedOption(String) <em>not</em> matched} on the command line, use
- * {@code parseResult.commandSpec().findOption(String).getValue()}. </p>
- * @see CommandSpec#findOption(String)
- * @param name used to search the matched options. May be an alias of the option name that was actually specified on the command line.
- * The specified name may include option name prefix characters or not. */
- public OptionSpec matchedOption(String name) { return CommandSpec.findOption(name, matchedOptions); }
- /** Returns the first {@code PositionalParamSpec} that matched an argument at the specified position, or {@code null} if no positional parameters were matched at that position. */
- public PositionalParamSpec matchedPositional(int position) {
- if (matchedPositionalParams.size() <= position || matchedPositionalParams.get(position).isEmpty()) { return null; }
- return matchedPositionalParams.get(position).get(0);
- }
- /** Returns all {@code PositionalParamSpec} objects that matched an argument at the specified position, or an empty list if no positional parameters were matched at that position. */
- public List<PositionalParamSpec> matchedPositionals(int position) {
- if (matchedPositionalParams.size() <= position) { return Collections.emptyList(); }
- return matchedPositionalParams.get(position) == null ? Collections.<PositionalParamSpec>emptyList() : matchedPositionalParams.get(position);
- }
- /** Returns the {@code CommandSpec} for the matched command. */
- public CommandSpec commandSpec() { return commandSpec; }
- /** Returns whether an option whose aliases include the specified short name was matched on the command line.
- * @param shortName used to search the matched options. May be an alias of the option name that was actually specified on the command line. */
- public boolean hasMatchedOption(char shortName) { return matchedOption(shortName) != null; }
- /** Returns whether an option whose aliases include the specified name was matched on the command line.
- * @param name used to search the matched options. May be an alias of the option name that was actually specified on the command line.
- * The specified name may include option name prefix characters or not. */
- public boolean hasMatchedOption(String name) { return matchedOption(name) != null; }
- /** Returns whether the specified option was matched on the command line. */
- public boolean hasMatchedOption(OptionSpec option) { return matchedOptions.contains(option); }
- /** Returns whether a positional parameter was matched at the specified position. */
- public boolean hasMatchedPositional(int position) { return matchedPositional(position) != null; }
- /** Returns whether the specified positional parameter was matched on the command line. */
- public boolean hasMatchedPositional(PositionalParamSpec positional) { return matchedUniquePositionals.contains(positional); }
- /** Returns a list of matched options, in the order they were found on the command line. */
- public List<OptionSpec> matchedOptions() { return Collections.unmodifiableList(matchedOptions); }
- /** Returns a list of matched positional parameters. */
- public List<PositionalParamSpec> matchedPositionals() { return Collections.unmodifiableList(matchedUniquePositionals); }
- /** Returns a list of command line arguments that did not match any options or positional parameters. */
- public List<String> unmatched() { return Collections.unmodifiableList(unmatched); }
- /** Returns the command line arguments that were parsed. */
- public List<String> originalArgs() { return Collections.unmodifiableList(originalArgs); }
- /** If {@link ParserSpec#collectErrors} is {@code true}, returns the list of exceptions that were encountered during parsing, otherwise, returns an empty list.
- * @since 3.2 */
- public List<Exception> errors() { return Collections.unmodifiableList(errors); }
- /** Returns the command line argument value of the option with the specified name, converted to the {@linkplain OptionSpec#type() type} of the option, or the specified default value if no option with the specified name was matched. */
- public <T> T matchedOptionValue(char shortName, T defaultValue) { return matchedOptionValue(matchedOption(shortName), defaultValue); }
- /** Returns the command line argument value of the option with the specified name, converted to the {@linkplain OptionSpec#type() type} of the option, or the specified default value if no option with the specified name was matched. */
- public <T> T matchedOptionValue(String name, T defaultValue) { return matchedOptionValue(matchedOption(name), defaultValue); }
- /** Returns the command line argument value of the specified option, converted to the {@linkplain OptionSpec#type() type} of the option, or the specified default value if the specified option is {@code null}. */
- @SuppressWarnings("unchecked")
- private <T> T matchedOptionValue(OptionSpec option, T defaultValue) { return option == null ? defaultValue : (T) option.getValue(); }
- /** Returns the command line argument value of the positional parameter at the specified position, converted to the {@linkplain PositionalParamSpec#type() type} of the positional parameter, or the specified default value if no positional parameter was matched at that position. */
- public <T> T matchedPositionalValue(int position, T defaultValue) { return matchedPositionalValue(matchedPositional(position), defaultValue); }
- /** Returns the command line argument value of the specified positional parameter, converted to the {@linkplain PositionalParamSpec#type() type} of the positional parameter, or the specified default value if the specified positional parameter is {@code null}. */
- @SuppressWarnings("unchecked")
- private <T> T matchedPositionalValue(PositionalParamSpec positional, T defaultValue) { return positional == null ? defaultValue : (T) positional.getValue(); }
- /** Returns {@code true} if a subcommand was matched on the command line, {@code false} otherwise. */
- public boolean hasSubcommand() { return subcommand != null; }
- /** Returns the {@code ParseResult} for the subcommand of this command that was matched on the command line, or {@code null} if no subcommand was matched. */
- public ParseResult subcommand() { return subcommand; }
- /** Returns {@code true} if one of the options that was matched on the command line is a {@link OptionSpec#usageHelp() usageHelp} option. */
- public boolean isUsageHelpRequested() { return usageHelpRequested; }
- /** Returns {@code true} if one of the options that was matched on the command line is a {@link OptionSpec#versionHelp() versionHelp} option. */
- public boolean isVersionHelpRequested() { return versionHelpRequested; }
- /** Returns this {@code ParseResult} as a list of {@code CommandLine} objects, one for each matched command/subcommand.
- * For backwards compatibility with pre-3.0 methods. */
- public List<CommandLine> asCommandLineList() {
- List<CommandLine> result = new ArrayList<CommandLine>();
- ParseResult pr = this;
- while (pr != null) { result.add(pr.commandSpec().commandLine()); pr = pr.hasSubcommand() ? pr.subcommand() : null; }
- return result;
- }
- /** Builds immutable {@code ParseResult} instances. */
- public static class Builder {
- private final CommandSpec commandSpec;
- private final Set<OptionSpec> options = new LinkedHashSet<OptionSpec>();
- private final Set<PositionalParamSpec> positionals = new LinkedHashSet<PositionalParamSpec>();
- private final List<String> unmatched = new ArrayList<String>();
- private final List<String> originalArgList = new ArrayList<String>();
- private final List<List<PositionalParamSpec>> positionalParams = new ArrayList<List<PositionalParamSpec>>();
- private ParseResult subcommand;
- private boolean usageHelpRequested;
- private boolean versionHelpRequested;
- boolean isInitializingDefaultValues;
- private List<Exception> errors = new ArrayList<Exception>(1);
- private List<Object> nowProcessing;
- private MatchedGroup matchedGroup = new MatchedGroup(null, null);
- private Builder(CommandSpec spec) { commandSpec = Assert.notNull(spec, "commandSpec"); }
- /** Creates and returns a new {@code ParseResult} instance for this builder's configuration. */
- public ParseResult build() {
- return new ParseResult(this);
- }
- private void nowProcessing(ArgSpec spec, Object value) {
- if (nowProcessing != null && !isInitializingDefaultValues) {
- nowProcessing.add(spec.isPositional() ? spec : value);
- }
- }
- /** Adds the specified {@code OptionSpec} or {@code PositionalParamSpec} to the list of options and parameters
- * that were matched on the command line.
- * @param arg the matched {@code OptionSpec} or {@code PositionalParamSpec}
- * @param position the command line position at which the {@code PositionalParamSpec} was matched. Ignored for {@code OptionSpec}s.
- * @return this builder for method chaining */
- public Builder add(ArgSpec arg, int position) {
- if (arg.isOption()) {
- addOption((OptionSpec) arg);
- } else {
- addPositionalParam((PositionalParamSpec) arg, position);
- }
- afterMatchingGroupElement(arg, position);
- return this;
- }
- /** Adds the specified {@code OptionSpec} to the list of options that were matched on the command line. */
- public Builder addOption(OptionSpec option) { if (!isInitializingDefaultValues) {options.add(option);} return this; }
- /** Adds the specified {@code PositionalParamSpec} to the list of parameters that were matched on the command line.
- * @param positionalParam the matched {@code PositionalParamSpec}
- * @param position the command line position at which the {@code PositionalParamSpec} was matched.
- * @return this builder for method chaining */
- public Builder addPositionalParam(PositionalParamSpec positionalParam, int position) {
- if (isInitializingDefaultValues) { return this; }
- positionals.add(positionalParam);
- while (positionalParams.size() <= position) { positionalParams.add(new ArrayList<PositionalParamSpec>()); }
- positionalParams.get(position).add(positionalParam);
- return this;
- }
- /** Adds the specified command line argument to the list of unmatched command line arguments. */
- public Builder addUnmatched(String arg) { unmatched.add(arg); return this; }
- /** Adds all elements of the specified command line arguments stack to the list of unmatched command line arguments. */
- public Builder addUnmatched(Stack<String> args) { while (!args.isEmpty()) { addUnmatched(args.pop()); } return this; }
- /** Sets the specified {@code ParseResult} for a subcommand that was matched on the command line. */
- public Builder subcommand(ParseResult subcommand) { this.subcommand = subcommand; return this; }
- /** Sets the specified command line arguments that were parsed. */
- public Builder originalArgs(String[] originalArgs) { originalArgList.addAll(Arrays.asList(originalArgs)); return this;}
- void addStringValue (ArgSpec argSpec, String value) { if (!isInitializingDefaultValues) { argSpec.stringValues.add(value);} }
- void addOriginalStringValue(ArgSpec argSpec, String value) {
- if (!isInitializingDefaultValues) {
- argSpec.originalStringValues.add(value);
- if (argSpec.group() != null) {
- MatchedGroup matchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
- matchedGroup.multiple().addOriginalStringValue(argSpec, value);
- }
- }
- }
- void addTypedValues(ArgSpec argSpec, int position, Object typedValue) {
- if (!isInitializingDefaultValues) {
- argSpec.typedValues.add(typedValue);
- if (argSpec.group() == null) {
- argSpec.typedValueAtPosition.put(position, typedValue);
- } else {
- MatchedGroup matchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
- matchedGroup.multiple().addMatchedValue(argSpec, position, typedValue, commandSpec.commandLine.tracer);
- }
- }
- }
- public void addError(PicocliException ex) {
- errors.add(Assert.notNull(ex, "exception"));
- }
- void beforeMatchingGroupElement(ArgSpec argSpec) throws Exception {
- ArgGroupSpec group = argSpec.group();
- if (group == null || isInitializingDefaultValues) { return; }
- MatchedGroup foundMatchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
- if (foundMatchedGroup.multiple().matchedMinElements() && argSpec.required()) {
- // we need to create a new multiple; if maxMultiplicity has been reached, we need to add a new MatchedGroup.
- String elementDescription = ArgSpec.describe(argSpec, "=");
- Tracer tracer = commandSpec.commandLine.tracer;
- tracer.info("MatchedGroupMultiple %s is complete: its mandatory elements are all matched. (User object: %s.) %s is required in the group, so it starts a new MatchedGroupMultiple.%n", foundMatchedGroup.multiple(), foundMatchedGroup.group.userObject(), elementDescription);
- foundMatchedGroup.addMultiple(commandSpec.commandLine);
- }
- }
- private void afterMatchingGroupElement(ArgSpec argSpec, int position) {
- // ArgGroupSpec group = argSpec.group();
- // if (group == null || isInitializingDefaultValues) { return; }
- // MatchedGroup matchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
- // promotePartiallyMatchedGroupToMatched(group, matchedGroup, true);
- }
- private void promotePartiallyMatchedGroupToMatched(ArgGroupSpec group, MatchedGroup matchedGroup, boolean allRequired) {
- if (!matchedGroup.matchedFully(allRequired)) { return; }
- // FIXME: before promoting the child group, check to see if the parent is matched, given the child group
- Tracer tracer = commandSpec.commandLine.tracer;
- if (matchedGroup.matchedMaxElements()) {
- tracer.info("Marking matched group %s as complete: max elements reached. User object: %s%n", matchedGroup, matchedGroup.group.userObject());
- matchedGroup.complete(commandSpec.commandLine());
- }
- }
- }
- /** Provides information about an {@link ArgGroup} that was matched on the command line.
- * <p>
- * The {@code ParseResult} may have more than one {@code MatchedGroup} for an {@code ArgGroupSpec}, when the
- * group was matched more often than its maximum {@linkplain ArgGroup#multiplicity() multiplicity}.
- * This is not necessarily a problem: the parser will add a multiple to the {@linkplain MatchedGroup#parentMatchedGroup() parent matched group}
- * until the maximum multiplicity of the parent group is exceeded, in which case parser will add a multiple to the parent's parent group, etc.
- * </p><p>
- * Ultimately, as long as the {@link ParseResult#getMatchedGroupMultiples()} method does not return more than one multiple, the maximum number of elements is not exceeded.
- * </p>
- * @since 4.0 */
- public static class MatchedGroup {
- private final ArgGroupSpec group;
- private MatchedGroup parentMatchedGroup;
- private List<MatchedGroupMultiple> multiples = new ArrayList<MatchedGroupMultiple>();
- MatchedGroup(ArgGroupSpec group, CommandLine cmd) { this.group = group; addMultiple(cmd);}
- /** Returns the {@code ArgGroupSpec} whose matches are captured in this {@code MatchedGroup}. */
- public ArgGroupSpec group() { return group; }
- /** Returns the {@code MatchedGroup} of the parent {@code ArgGroupSpec}, or {@code null} if this group has no parent. */
- public MatchedGroup parentMatchedGroup() { return parentMatchedGroup; }
- /** Returns the list of {@code MatchedGroupMultiple} instances: {@code ArgGroupSpec}s with a multiplicity greater than one may be matched multiple times. */
- public List<MatchedGroupMultiple> multiples() { return Collections.unmodifiableList(multiples); }
- void addMultiple(CommandLine commandLine) {
- Tracer tracer = commandLine == null ? new Tracer() : commandLine.tracer;
- if (group != null && isMaxMultiplicityReached()) {
- tracer.info("Completing MatchedGroup %s: max multiplicity is reached.%n", this);
- complete(commandLine);
- } else {
- if (group != null) {
- tracer.info("Adding multiple to MatchedGroup %s (group=%s %s).%n", this, group == null ? "?" : group.id(), group == null ? "ROOT" : group.synopsis());
- }
- multiples.add(new MatchedGroupMultiple(this));
- if (group == null) { return; }
- }
- group.initUserObject(commandLine);
- }
- void complete(CommandLine commandLine) {
- if (parentMatchedGroup == null) {
- addMultiple(commandLine); // we have no choice but to potentially exceed the max multiplicity of this group...
- } else {
- parentMatchedGroup.addMultiple(commandLine);
- }
- }
- /** Returns the "active" multiple of this MatchedGroup. */
- MatchedGroupMultiple multiple() { return multiples.get(multiples.size() - 1); }
- /** Returns {@code true} if no more {@code MatchedGroupMultiples} can be added to this {@code MatchedGroup}. Each multiple may be a complete or an incomplete match.*/
- boolean isMaxMultiplicityReached() { return multiples.size() >= group.multiplicity.max; }
- /** Returns {@code true} if this {@code MatchedGroup} has at least the minimum number of {@code MatchedGroupMultiples}. Each multiple may be a complete or an incomplete match. */
- boolean isMinMultiplicityReached() { return multiples.size() >= group.multiplicity.min; }
- /** Returns {@code true} if the minimum number of multiples has been matched for the multiplicity of this group,
- * and each multiple has matched at least the {@linkplain MatchedGroupMultiple#matchedMinElements() minimum number of elements}.*/
- boolean matchedMinElements() { return matchedFully(false); }
- /** Returns {@code true} if the maximum number of multiples has been matched for the multiplicity of this group,
- * and the last multiple has {@linkplain MatchedGroupMultiple#matchedMaxElements() matched the maximum number of elements},
- * while all other multiples have matched at least the {@linkplain MatchedGroupMultiple#matchedMinElements() minimum number of elements}.*/
- boolean matchedMaxElements() { return matchedFully(true); }
- private boolean matchedFully(boolean allRequired) {
- for (MatchedGroupMultiple multiple : multiples) {
- boolean actuallyAllRequired = allRequired && multiple == multiple();
- if (!multiple.matchedFully(actuallyAllRequired)) { return false; }
- }
- return allRequired ? isMaxMultiplicityReached() : isMinMultiplicityReached();
- }
- private MatchedGroup findOrCreateMatchingGroup(ArgSpec argSpec, CommandLine commandLine) {
- ArgGroupSpec searchGroup = Assert.notNull(argSpec.group(), "group for " + argSpec);
- MatchedGroup match = this;
- if (searchGroup == match.group()) { return match; }
- List<ArgGroupSpec> keys = new ArrayList<ArgGroupSpec>();
- while (searchGroup != null) {
- keys.add(searchGroup);
- searchGroup = searchGroup.parentGroup();
- }
- Collections.reverse(keys);
- for (ArgGroupSpec key : keys) {
- MatchedGroup sub = match.multiple().matchedSubgroups().get(key);
- if (sub == null) {
- sub = createMatchedGroup(key, match, commandLine);
- }
- match = sub;
- }
- return match;
- }
- private MatchedGroup createMatchedGroup(ArgGroupSpec group, MatchedGroup parent, CommandLine commandLine) {
- MatchedGroup result = new MatchedGroup(group, commandLine);
- result.parentMatchedGroup = parent;
- parent.multiple().matchedSubgroups.put(group, result);
- return result;
- }
- MatchedGroup trim() {
- for (Iterator<MatchedGroupMultiple> iter = multiples.iterator(); iter.hasNext(); ) {
- MatchedGroupMultiple multiple = iter.next();
- if (multiple.isEmpty()) { iter.remove(); }
- for (MatchedGroup sub : multiple.matchedSubgroups.values()) { sub.trim(); }
- }
- return this;
- }
- List<MatchedGroup> findMatchedGroup(ArgGroupSpec group, List<MatchedGroup> result) {
- if (this.group == group) { result.add(this); return result; }
- for (MatchedGroupMultiple multiple : multiples()) {
- for (MatchedGroup mg : multiple.matchedSubgroups.values()) {
- mg.findMatchedGroup(group, result);
- }
- }
- return result;
- }
- @Override public String toString() {
- return toString(new StringBuilder()).toString();
- }
- private StringBuilder toString(StringBuilder result) {
- String prefix = result.length() == 0 ? "={" : "";
- String suffix = result.length() == 0 ? "}" : "";
- if (group != null && result.length() == 0) {
- result.append(group.synopsis());
- }
- result.append(prefix);
- String infix = "";
- for (MatchedGroupMultiple occurrence : multiples) {
- result.append(infix);
- occurrence.toString(result);
- infix = " ";
- }
- return result.append(suffix);
- }
- }
- /** A group's {@linkplain ArgGroup#multiplicity() multiplicity} specifies how many multiples of a group can/must
- * appear on the command line before a group is fully matched. This class models a single "multiple".
- * For example, this group: {@code (-a -b) (-a -b)} requires two multiples of its arguments to fully match.
- * @since 4.0
- */
- public static class MatchedGroupMultiple {
- int position;
- final MatchedGroup container;
- Map<ArgGroupSpec, MatchedGroup> matchedSubgroups = new LinkedHashMap<ArgGroupSpec, MatchedGroup>(2); // preserve order: used in toString()
- Map<ArgSpec, List<Object>> matchedValues = new IdentityHashMap<ArgSpec, List<Object>>(); // identity map for performance
- Map<ArgSpec, List<String>> originalStringValues = new LinkedHashMap<ArgSpec, List<String>>(); // preserve order: used in toString()
- Map<ArgSpec, Map<Integer, List<Object>>> matchedValuesAtPosition = new IdentityHashMap<ArgSpec, Map<Integer, List<Object>>>();
- MatchedGroupMultiple(MatchedGroup container) { this.container = container; }
- /** Returns {@code true} if this multiple has no matched arguments and no matched subgroups. */
- public boolean isEmpty() { return originalStringValues.isEmpty() && matchedSubgroups.isEmpty(); }
- /** Returns the {@code ArgGroupSpec} of the container {@code MatchedGroup} of this multiple. */
- public ArgGroupSpec group() { return container.group; }
- /** Returns the container {@code MatchedGroup} of this multiple. */
- public MatchedGroup container() { return container; }
- /** Returns matches for the subgroups, if any. */
- public Map<ArgGroupSpec, MatchedGroup> matchedSubgroups() { return Collections.unmodifiableMap(matchedSubgroups); }
- int matchCount(ArgSpec argSpec) { return matchedValues.get(argSpec) == null ? 0 : matchedValues.get(argSpec).size(); }
- /** Returns the values matched for the specified argument, converted to the type of the argument. */
- public List<Object> matchedValues(ArgSpec argSpec) { return matchedValues.get(argSpec) == null ? Collections.emptyList() : Collections.unmodifiableList(matchedValues.get(argSpec)); }
- void addOriginalStringValue(ArgSpec argSpec, String value) {
- addValueToListInMap(originalStringValues, argSpec, value);
- }
- void addMatchedValue(ArgSpec argSpec, int matchPosition, Object stronglyTypedValue, Tracer tracer) {
- addValueToListInMap(matchedValues, argSpec, stronglyTypedValue);
- Map<Integer, List<Object>> positionalValues = matchedValuesAtPosition.get(argSpec);
- if (positionalValues == null) {
- positionalValues = new TreeMap<Integer, List<Object>>();
- matchedValuesAtPosition.put(argSpec, positionalValues);
- }
- addValueToListInMap(positionalValues, matchPosition, stronglyTypedValue);
- }
- boolean hasMatchedValueAtPosition(ArgSpec arg, int position) { Map<Integer, List<Object>> atPos = matchedValuesAtPosition.get(arg); return atPos != null && atPos.containsKey(position); }
- /** Returns {@code true} if the minimum number of elements have been matched for this multiple:
- * all required arguments have been matched, and for each subgroup,
- * the {@linkplain MatchedGroup#matchedMinElements() minimum number of elements have been matched}.*/
- boolean matchedMinElements() { return matchedFully(false); }
- /** Returns {@code true} if the maximum number of multiples has been matched for this multiple:
- * all arguments (required or not) have been matched, and for each subgroup,
- * the {@linkplain MatchedGroup#matchedMaxElements() maximum number of elements have been matched}.*/
- boolean matchedMaxElements() { return matchedFully(true); }
- private boolean matchedFully(boolean allRequired) {
- if (group().exclusive()) { return !matchedValues.isEmpty() || hasFullyMatchedSubgroup(allRequired); }
- for (ArgSpec arg : group().args()) {
- if (matchedValues.get(arg) == null && (arg.required() || allRequired)) { return false; }
- }
- for (ArgGroupSpec subgroup : group().subgroups()) {
- MatchedGroup matchedGroup = matchedSubgroups.get(subgroup);
- if (matchedGroup != null) {
- if (!matchedGroup.matchedFully(allRequired)) { return false; }
- } else {
- if (allRequired || subgroup.multiplicity().min > 0) { return false; }
- }
- }
- return true;
- }
- private boolean hasFullyMatchedSubgroup(boolean allRequired) {
- for (MatchedGroup sub : matchedSubgroups.values()) { if (sub.matchedFully(allRequired)) { return true; } }
- return false;
- }
- @Override public String toString() {
- return toString(new StringBuilder()).toString();
- }
- private StringBuilder toString(StringBuilder result) {
- int originalLength = result.length();
- for (ArgSpec arg : originalStringValues.keySet()) {
- List<String> values = originalStringValues.get(arg);
- for (String value : values) {
- if (result.length() != originalLength) { result.append(" "); }
- result.append(ArgSpec.describe(arg, "=", value));
- }
- }
- for (MatchedGroup sub : matchedSubgroups.values()) {
- if (result.length() != originalLength) { result.append(" "); }
- if (originalLength == 0) {
- result.append(sub.toString()); // include synopsis
- } else {
- sub.toString(result); // without synopsis
- }
- }
- return result;
- }
- }
- }
- static <K, T> void addValueToListInMap(Map<K, List<T>> map, K key, T value) {
- List<T> values = map.get(key);
- if (values == null) { values = new ArrayList<T>(); map.put(key, values); }
- values.add(value);
- }
- static <T> List<T> flatList(Collection<? extends Collection<T>> collection) {
- List<T> result = new ArrayList<T>();
- for (Collection<T> sub : collection) { result.addAll(sub); }
- return result;
- }
- private enum LookBehind { SEPARATE, ATTACHED, ATTACHED_WITH_SEPARATOR;
- public boolean isAttached() { return this != LookBehind.SEPARATE; }
- }
- /**
- * Helper class responsible for processing command line arguments.
- */
- private class Interpreter {
- private final Map<Class<?>, ITypeConverter<?>> converterRegistry = new HashMap<Class<?>, ITypeConverter<?>>();
- private boolean isHelpRequested;
- private int position;
- private boolean endOfOptions;
- private ParseResult.Builder parseResultBuilder;
- Interpreter() { registerBuiltInConverters(); }
- private void registerBuiltInConverters() {
- converterRegistry.put(Object.class, new BuiltIn.StringConverter());
- converterRegistry.put(String.class, new BuiltIn.StringConverter());
- converterRegistry.put(StringBuilder.class, new BuiltIn.StringBuilderConverter());
- converterRegistry.put(CharSequence.class, new BuiltIn.CharSequenceConverter());
- converterRegistry.put(Byte.class, new BuiltIn.ByteConverter());
- converterRegistry.put(Byte.TYPE, new BuiltIn.ByteConverter());
- converterRegistry.put(Boolean.class, new BuiltIn.BooleanConverter());
- converterRegistry.put(Boolean.TYPE, new BuiltIn.BooleanConverter());
- converterRegistry.put(Character.class, new BuiltIn.CharacterConverter());
- converterRegistry.put(Character.TYPE, new BuiltIn.CharacterConverter());
- converterRegistry.put(Short.class, new BuiltIn.ShortConverter());
- converterRegistry.put(Short.TYPE, new BuiltIn.ShortConverter());
- converterRegistry.put(Integer.class, new BuiltIn.IntegerConverter());
- converterRegistry.put(Integer.TYPE, new BuiltIn.IntegerConverter());
- converterRegistry.put(Long.class, new BuiltIn.LongConverter());
- converterRegistry.put(Long.TYPE, new BuiltIn.LongConverter());
- converterRegistry.put(Float.class, new BuiltIn.FloatConverter());
- converterRegistry.put(Float.TYPE, new BuiltIn.FloatConverter());
- converterRegistry.put(Double.class, new BuiltIn.DoubleConverter());
- converterRegistry.put(Double.TYPE, new BuiltIn.DoubleConverter());
- converterRegistry.put(File.class, new BuiltIn.FileConverter());
- converterRegistry.put(URI.class, new BuiltIn.URIConverter());
- converterRegistry.put(URL.class, new BuiltIn.URLConverter());
- converterRegistry.put(Date.class, new BuiltIn.ISO8601DateConverter());
- converterRegistry.put(BigDecimal.class, new BuiltIn.BigDecimalConverter());
- converterRegistry.put(BigInteger.class, new BuiltIn.BigIntegerConverter());
- converterRegistry.put(Charset.class, new BuiltIn.CharsetConverter());
- converterRegistry.put(InetAddress.class, new BuiltIn.InetAddressConverter());
- converterRegistry.put(Pattern.class, new BuiltIn.PatternConverter());
- converterRegistry.put(UUID.class, new BuiltIn.UUIDConverter());
- converterRegistry.put(Currency.class, new BuiltIn.CurrencyConverter());
- converterRegistry.put(TimeZone.class, new BuiltIn.TimeZoneConverter());
- converterRegistry.put(ByteOrder.class, new BuiltIn.ByteOrderConverter());
- converterRegistry.put(Class.class, new BuiltIn.ClassConverter());
- converterRegistry.put(NetworkInterface.class, new BuiltIn.NetworkInterfaceConverter());
- BuiltIn.ISO8601TimeConverter.registerIfAvailable(converterRegistry, tracer);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.sql.Connection", "java.sql.DriverManager","getConnection", String.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.sql.Driver", "java.sql.DriverManager","getDriver", String.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.sql.Timestamp", "java.sql.Timestamp","valueOf", String.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Duration", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Instant", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalDate", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalDateTime", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalTime", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.MonthDay", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.OffsetDateTime", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.OffsetTime", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Period", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Year", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.YearMonth", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZonedDateTime", "parse", CharSequence.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZoneId", "of", String.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZoneOffset", "of", String.class);
- BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.nio.file.Path", "java.nio.file.Paths", "get", String.class, String[].class);
- }
- private ParserSpec config() { return commandSpec.parser(); }
- /**
- * Entry point into parsing command line arguments.
- * @param args the command line arguments
- * @return a list with all commands and subcommands initialized by this method
- * @throws ParameterException if the specified command line arguments are invalid
- */
- List<CommandLine> parse(String... args) {
- Assert.notNull(args, "argument array");
- if (tracer.isInfo()) {tracer.info("Picocli version: %s%n", versionString());}
- if (tracer.isInfo()) {tracer.info("Parsing %d command line args %s%n", args.length, Arrays.toString(args));}
- if (tracer.isDebug()){tracer.debug("Parser configuration: %s%n", config());}
- if (tracer.isDebug()){tracer.debug("(ANSI is %s by default: isatty=%s, XTERM=%s, OSTYPE=%s, isWindows=%s, JansiConsoleInstalled=%s, ANSICON=%s, ConEmuANSI=%s, NO_COLOR=%s, CLICOLOR=%s, CLICOLOR_FORCE=%s)%n",
- Help.Ansi.ansiPossible() ? "enabled" : "disabled", Help.Ansi.isTTY(), System.getenv("XTERM"), System.getenv("OSTYPE"), Help.Ansi.isWindows(), Help.Ansi.isJansiConsoleInstalled(), System.getenv("ANSICON"), System.getenv("ConEmuANSI"), System.getenv("NO_COLOR"), System.getenv("CLICOLOR"), System.getenv("CLICOLOR_FORCE"));}
- List<String> expanded = new ArrayList<String>();
- for (String arg : args) { addOrExpand(arg, expanded, new LinkedHashSet<String>()); }
- Stack<String> arguments = new Stack<String>();
- arguments.addAll(reverseList(expanded));
- List<CommandLine> result = new ArrayList<CommandLine>();
- parse(result, arguments, args, new ArrayList<Object>());
- return result;
- }
- private void addOrExpand(String arg, List<String> arguments, Set<String> visited) {
- if (config().expandAtFiles() && !arg.equals("@") && arg.startsWith("@")) {
- arg = arg.substring(1);
- if (arg.startsWith("@")) {
- if (tracer.isInfo()) { tracer.info("Not expanding @-escaped argument %s (trimmed leading '@' char)%n", arg); }
- } else {
- if (tracer.isInfo()) { tracer.info("Expanding argument file @%s%n", arg); }
- expandArgumentFile(arg, arguments, visited);
- return;
- }
- }
- arguments.add(arg);
- }
- private void expandArgumentFile(String fileName, List<String> arguments, Set<String> visited) {
- File file = new File(fileName);
- if (!file.canRead()) {
- if (tracer.isInfo()) {tracer.info("File %s does not exist or cannot be read; treating argument literally%n", fileName);}
- arguments.add("@" + fileName);
- } else if (visited.contains(file.getAbsolutePath())) {
- if (tracer.isInfo()) {tracer.info("Already visited file %s; ignoring...%n", file.getAbsolutePath());}
- } else {
- expandValidArgumentFile(fileName, file, arguments, visited);
- }
- }
- private void expandValidArgumentFile(String fileName, File file, List<String> arguments, Set<String> visited) {
- List<String> result = new ArrayList<String>();
- LineNumberReader reader = null;
- try {
- visited.add(file.getAbsolutePath());
- reader = new LineNumberReader(new FileReader(file));
- if (commandSpec.parser().useSimplifiedAtFiles()) {
- String token;
- while ((token = reader.readLine()) != null) {
- if (token.length() > 0 && !token.trim().startsWith(String.valueOf(commandSpec.parser().atFileCommentChar()))) {
- addOrExpand(token, result, visited);
- }
- }
- } else {
- StreamTokenizer tok = new StreamTokenizer(reader);
- tok.resetSyntax();
- tok.wordChars(' ', 255);
- tok.whitespaceChars(0, ' ');
- tok.quoteChar('"');
- tok.quoteChar('\'');
- if (commandSpec.parser().atFileCommentChar() != null) {
- tok.commentChar(commandSpec.parser().atFileCommentChar());
- }
- while (tok.nextToken() != StreamTokenizer.TT_EOF) {
- addOrExpand(tok.sval, result, visited);
- }
- }
- } catch (Exception ex) {
- throw new InitializationException("Could not read argument file @" + fileName, ex);
- } finally {
- if (reader != null) { try {reader.close();} catch (Exception ignored) {} }
- }
- if (tracer.isInfo()) {tracer.info("Expanded file @%s to arguments %s%n", fileName, result);}
- arguments.addAll(result);
- }
- private void clear() {
- position = 0;
- endOfOptions = false;
- isHelpRequested = false;
- parseResultBuilder = ParseResult.builder(getCommandSpec());
- for (OptionSpec option : getCommandSpec().options()) { clear(option); }
- for (PositionalParamSpec positional : getCommandSpec().positionalParameters()) { clear(positional); }
- }
- private void clear(ArgSpec argSpec) {
- argSpec.resetStringValues();
- argSpec.resetOriginalStringValues();
- argSpec.typedValues.clear();
- argSpec.typedValueAtPosition.clear();
- if (argSpec.group() == null) { argSpec.applyInitialValue(tracer); } // groups do their own initialization
- }
- private void maybeThrow(PicocliException ex) throws PicocliException {
- if (commandSpec.parser().collectErrors) {
- parseResultBuilder.addError(ex);
- } else {
- throw ex;
- }
- }
- private void parse(List<CommandLine> parsedCommands, Stack<String> argumentStack, String[] originalArgs, List<Object> nowProcessing) {
- clear(); // first reset any state in case this CommandLine instance is being reused
- if (tracer.isDebug()) {
- tracer.debug("Initializing %s: %d options, %d positional parameters, %d required, %d groups, %d subcommands.%n",
- commandSpec.toString(), new HashSet<ArgSpec>(commandSpec.optionsMap().values()).size(),
- commandSpec.positionalParameters().size(), commandSpec.requiredArgs().size(),
- commandSpec.argGroups().size(), commandSpec.subcommands().size());
- }
- parsedCommands.add(CommandLine.this);
- List<ArgSpec> required = new ArrayList<ArgSpec>(commandSpec.requiredArgs());
- Set<ArgSpec> initialized = new LinkedHashSet<ArgSpec>();
- Collections.sort(required, new PositionalParametersSorter());
- boolean continueOnError = commandSpec.parser().collectErrors();
- do {
- int stackSize = argumentStack.size();
- try {
- applyDefaultValues(required);
- processArguments(parsedCommands, argumentStack, required, initialized, originalArgs, nowProcessing);
- } catch (ParameterException ex) {
- maybeThrow(ex);
- } catch (Exception ex) {
- int offendingArgIndex = originalArgs.length - argumentStack.size() - 1;
- String arg = offendingArgIndex >= 0 && offendingArgIndex < originalArgs.length ? originalArgs[offendingArgIndex] : "?";
- maybeThrow(ParameterException.create(CommandLine.this, ex, arg, offendingArgIndex, originalArgs));
- }
- if (continueOnError && stackSize == argumentStack.size() && stackSize > 0) {
- parseResultBuilder.unmatched.add(argumentStack.pop());
- }
- } while (!argumentStack.isEmpty() && continueOnError);
- validateConstraints(argumentStack, required, initialized);
- }
- private void validateConstraints(Stack<String> argumentStack, List<ArgSpec> required, Set<ArgSpec> matched) {
- if (!isAnyHelpRequested() && !required.isEmpty()) {
- for (ArgSpec missing : required) {
- Assert.assertTrue(missing.group() == null, "Arguments in a group are not necessarily required for the command");
- if (missing.isOption()) {
- maybeThrow(MissingParameterException.create(CommandLine.this, required, config().separator()));
- } else {
- assertNoMissingParameters(missing, missing.arity(), argumentStack);
- }
- }
- }
- if (!parseResultBuilder.unmatched.isEmpty()) {
- String[] unmatched = parseResultBuilder.unmatched.toArray(new String[0]);
- for (UnmatchedArgsBinding unmatchedArgsBinding : getCommandSpec().unmatchedArgsBindings()) {
- unmatchedArgsBinding.addAll(unmatched.clone());
- }
- if (!isUnmatchedArgumentsAllowed()) { maybeThrow(new UnmatchedArgumentException(CommandLine.this, Collections.unmodifiableList(parseResultBuilder.unmatched))); }
- if (tracer.isInfo()) { tracer.info("Unmatched arguments: %s%n", parseResultBuilder.unmatched); }
- }
- for (ArgGroupSpec group : commandSpec.argGroups()) {
- group.clearValidationResult();
- }
- ParseResult pr = parseResultBuilder.build();
- for (ArgGroupSpec group : commandSpec.argGroups()) {
- group.validateConstraints(pr);
- }
- List<ParseResult.MatchedGroupMultiple> matchedGroupMultiples = pr.getMatchedGroupMultiples();
- if (matchedGroupMultiples.size() > 1) {
- failGroupMultiplicityExceeded(matchedGroupMultiples);
- }
- }
- private void failGroupMultiplicityExceeded(List<ParseResult.MatchedGroupMultiple> matchedGroupMultiples) {
- Map<ArgGroupSpec, List<List<ParseResult.MatchedGroupMultiple>>> multiplesPerGroup = new IdentityHashMap<ArgGroupSpec, List<List<ParseResult.MatchedGroupMultiple>>>();
- String msg = "";
- for (ParseResult.MatchedGroupMultiple multiple : matchedGroupMultiples) {
- if (msg.length() > 0) { msg += " and "; }
- msg += multiple.toString();
- Map<ArgGroupSpec, MatchedGroup> subgroups = multiple.matchedSubgroups();
- for (ArgGroupSpec group : subgroups.keySet()) {
- addValueToListInMap(multiplesPerGroup, group, subgroups.get(group).multiples());
- }
- }
- if (!simplifyErrorMessageForSingleGroup(multiplesPerGroup)) {
- maybeThrow(new MaxValuesExceededException(CommandLine.this, "Error: expected only one match but got " + msg));
- }
- }
- private boolean simplifyErrorMessageForSingleGroup(Map<ArgGroupSpec, List<List<ParseResult.MatchedGroupMultiple>>> multiplesPerGroup) {
- if (multiplesPerGroup.size() == 1) { // all multiples were matches for a single group
- ArgGroupSpec group = multiplesPerGroup.keySet().iterator().next();
- List<ParseResult.MatchedGroupMultiple> flat = flatList(multiplesPerGroup.get(group));
- for (ParseResult.MatchedGroupMultiple multiple : flat) {
- if (!multiple.matchedSubgroups().isEmpty()) { return false; }
- }
- group.validationException = null;
- group.validateMultiples(CommandLine.this, flat);
- if (group.validationException != null) {
- maybeThrow(group.validationException);
- return true;
- }
- }
- return false;
- }
- private void applyDefaultValues(List<ArgSpec> required) throws Exception {
- parseResultBuilder.isInitializingDefaultValues = true;
- for (ArgSpec arg : commandSpec.args()) {
- if (arg.group() == null) {
- if (applyDefault(commandSpec.defaultValueProvider(), arg)) { required.remove(arg); }
- }
- }
- parseResultBuilder.isInitializingDefaultValues = false;
- }
- private boolean applyDefault(IDefaultValueProvider defaultValueProvider, ArgSpec arg) throws Exception {
- // Default value provider return value is only used if provider exists and if value
- // is not null otherwise the original default or initial value are used
- String fromProvider = defaultValueProvider == null ? null : defaultValueProvider.defaultValue(arg);
- String defaultValue = fromProvider == null ? arg.defaultValue() : fromProvider;
- if (defaultValue != null) {
- if (tracer.isDebug()) {tracer.debug("Applying defaultValue (%s) to %s%n", defaultValue, arg);}
- Range arity = arg.arity().min(Math.max(1, arg.arity().min));
- applyOption(arg, LookBehind.SEPARATE, arity, stack(defaultValue), new HashSet<ArgSpec>(), arg.toString);
- }
- return defaultValue != null;
- }
- private Stack<String> stack(String value) {Stack<String> result = new Stack<String>(); result.push(value); return result;}
- private void processArguments(List<CommandLine> parsedCommands,
- Stack<String> args,
- Collection<ArgSpec> required,
- Set<ArgSpec> initialized,
- String[] originalArgs,
- List<Object> nowProcessing) throws Exception {
- // arg must be one of:
- // 1. the "--" double dash separating options from positional arguments
- // 1. a stand-alone flag, like "-v" or "--verbose": no value required, must map to boolean or Boolean field
- // 2. a short option followed by an argument, like "-f file" or "-ffile": may map to any type of field
- // 3. a long option followed by an argument, like "-file out.txt" or "-file=out.txt"
- // 3. one or more remaining arguments without any associated options. Must be the last in the list.
- // 4. a combination of stand-alone options, like "-vxr". Equivalent to "-v -x -r", "-v true -x true -r true"
- // 5. a combination of stand-alone options and one option with an argument, like "-vxrffile"
- parseResultBuilder.originalArgs(originalArgs);
- parseResultBuilder.nowProcessing = nowProcessing;
- String separator = config().separator();
- while (!args.isEmpty()) {
- if (endOfOptions) {
- processRemainderAsPositionalParameters(required, initialized, args);
- return;
- }
- String arg = args.pop();
- if (tracer.isDebug()) {tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse(copy(args)));}
- // Double-dash separates options from positional arguments.
- // If found, then interpret the remaining args as positional parameters.
- if (commandSpec.parser.endOfOptionsDelimiter().equals(arg)) {
- tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n");
- endOfOptions = true;
- processRemainderAsPositionalParameters(required, initialized, args);
- return; // we are done
- }
- // if we find another command, we are done with the current command
- if (commandSpec.subcommands().containsKey(arg)) {
- CommandLine subcommand = commandSpec.subcommands().get(arg);
- nowProcessing.add(subcommand.commandSpec);
- updateHelpRequested(subcommand.commandSpec);
- if (!isAnyHelpRequested() && !required.isEmpty()) { // ensure current command portion is valid
- throw MissingParameterException.create(CommandLine.this, required, separator);
- }
- if (tracer.isDebug()) {tracer.debug("Found subcommand '%s' (%s)%n", arg, subcommand.commandSpec.toString());}
- subcommand.interpreter.parse(parsedCommands, args, originalArgs, nowProcessing);
- parseResultBuilder.subcommand(subcommand.interpreter.parseResultBuilder.build());
- return; // remainder done by the command
- }
- // First try to interpret the argument as a single option (as opposed to a compact group of options).
- // A single option may be without option parameters, like "-v" or "--verbose" (a boolean value),
- // or an option may have one or more option parameters.
- // A parameter may be attached to the option.
- boolean paramAttachedToOption = false;
- int separatorIndex = arg.indexOf(separator);
- if (separatorIndex > 0) {
- String key = arg.substring(0, separatorIndex);
- // be greedy. Consume the whole arg as an option if possible.
- if (commandSpec.optionsMap().containsKey(key) && commandSpec.optionsMap().containsKey(arg)) {
- tracer.warn("Both '%s' and '%s' are valid option names in %s. Using '%s'...%n", arg, key, getCommandName(), arg);
- } else if (commandSpec.optionsMap().containsKey(key)) {
- paramAttachedToOption = true;
- String optionParam = arg.substring(separatorIndex + separator.length());
- args.push(optionParam);
- arg = key;
- if (tracer.isDebug()) {tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);}
- } else {
- if (tracer.isDebug()) {tracer.debug("'%s' contains separator '%s' but '%s' is not a known option%n", arg, separator, key);}
- }
- } else {
- if (tracer.isDebug()) {tracer.debug("'%s' cannot be separated into <option>%s<option-parameter>%n", arg, separator);}
- }
- if (isStandaloneOption(arg)) {
- processStandaloneOption(required, initialized, arg, args, paramAttachedToOption);
- }
- // Compact (single-letter) options can be grouped with other options or with an argument.
- // only single-letter options can be combined with other options or with an argument
- else if (config().posixClusteredShortOptionsAllowed() && arg.length() > 2 && arg.startsWith("-")) {
- if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as clustered short options%n", arg, args);}
- processClusteredShortOptions(required, initialized, arg, args);
- }
- // The argument could not be interpreted as an option: process it as a positional argument
- else {
- args.push(arg);
- if (tracer.isDebug()) {tracer.debug("Could not find option '%s', deciding whether to treat as unmatched option or positional parameter...%n", arg);}
- if (commandSpec.resemblesOption(arg, tracer)) { handleUnmatchedArgument(args); continue; } // #149
- if (tracer.isDebug()) {tracer.debug("No option named '%s' found. Processing as positional parameter%n", arg);}
- processPositionalParameter(required, initialized, args);
- }
- }
- }
- private boolean isStandaloneOption(String arg) {
- return commandSpec.optionsMap().containsKey(arg);
- }
- private void handleUnmatchedArgument(Stack<String> args) throws Exception {
- if (!args.isEmpty()) { handleUnmatchedArgument(args.pop()); }
- if (config().stopAtUnmatched()) {
- // addAll would give args in reverse order
- while (!args.isEmpty()) { handleUnmatchedArgument(args.pop()); }
- }
- }
- private void handleUnmatchedArgument(String arg) {
- parseResultBuilder.unmatched.add(arg);
- }
- private void processRemainderAsPositionalParameters(Collection<ArgSpec> required, Set<ArgSpec> initialized, Stack<String> args) throws Exception {
- while (!args.empty()) {
- processPositionalParameter(required, initialized, args);
- }
- }
- private void processPositionalParameter(Collection<ArgSpec> required, Set<ArgSpec> initialized, Stack<String> args) throws Exception {
- if (tracer.isDebug()) {tracer.debug("Processing next arg as a positional parameter. Command-local position=%d. Remainder=%s%n", position, reverse(copy(args)));}
- if (config().stopAtPositional()) {
- if (!endOfOptions && tracer.isDebug()) {tracer.debug("Parser was configured with stopAtPositional=true, treating remaining arguments as positional parameters.%n");}
- endOfOptions = true;
- }
- int consumedByGroup = 0;
- int argsConsumed = 0;
- int interactiveConsumed = 0;
- int originalNowProcessingSize = parseResultBuilder.nowProcessing.size();
- Map<PositionalParamSpec, Integer> newPositions = new IdentityHashMap<PositionalParamSpec, Integer>();
- for (PositionalParamSpec positionalParam : commandSpec.positionalParameters()) {
- Range indexRange = positionalParam.index();
- int localPosition = getPosition(positionalParam);
- if (positionalParam.group() != null) { // does the positionalParam's index range contain the current position in the currently matching group
- MatchedGroup matchedGroup = parseResultBuilder.matchedGroup.findOrCreateMatchingGroup(positionalParam, commandSpec.commandLine());
- if (!indexRange.contains(localPosition) || (matchedGroup != null && matchedGroup.multiple().hasMatchedValueAtPosition(positionalParam, localPosition))) {
- continue;
- }
- } else {
- if (!indexRange.contains(localPosition) || positionalParam.typedValueAtPosition.get(localPosition) != null) {
- continue;
- }
- }
- Stack<String> argsCopy = copy(args);
- Range arity = positionalParam.arity();
- if (tracer.isDebug()) {tracer.debug("Position %s is in index range %s. Trying to assign args to %s, arity=%s%n", positionDesc(positionalParam), indexRange, positionalParam, arity);}
- if (!assertNoMissingParameters(positionalParam, arity, argsCopy)) { break; } // #389 collectErrors parsing
- int originalSize = argsCopy.size();
- int actuallyConsumed = applyOption(positionalParam, LookBehind.SEPARATE, arity, argsCopy, initialized, "args[" + indexRange + "] at position " + localPosition);
- int count = originalSize - argsCopy.size();
- if (count > 0 || actuallyConsumed > 0) {
- required.remove(positionalParam);
- if (positionalParam.interactive()) { interactiveConsumed++; }
- }
- if (positionalParam.group() == null) { // don't update the command-level position for group args
- argsConsumed = Math.max(argsConsumed, count);
- } else {
- newPositions.put(positionalParam, localPosition + count);
- consumedByGroup = Math.max(consumedByGroup, count);
- }
- while (parseResultBuilder.nowProcessing.size() > originalNowProcessingSize + count) {
- parseResultBuilder.nowProcessing.remove(parseResultBuilder.nowProcessing.size() - 1);
- }
- }
- // remove processed args from the stack
- int maxConsumed = Math.max(consumedByGroup, argsConsumed);
- for (int i = 0; i < maxConsumed; i++) { args.pop(); }
- position += argsConsumed + interactiveConsumed;
- if (tracer.isDebug()) {tracer.debug("Consumed %d arguments and %d interactive values, moving command-local position to index %d.%n", argsConsumed, interactiveConsumed, position);}
- for (PositionalParamSpec positional : newPositions.keySet()) {
- MatchedGroup inProgress = parseResultBuilder.matchedGroup.findOrCreateMatchingGroup(positional, commandSpec.commandLine());
- if (inProgress != null) {
- inProgress.multiple().position = newPositions.get(positional);
- if (tracer.isDebug()) {tracer.debug("Updated group position to %s for group %s.%n", inProgress.multiple().position, inProgress);}
- }
- }
- if (consumedByGroup == 0 && argsConsumed == 0 && interactiveConsumed == 0 && !args.isEmpty()) {
- handleUnmatchedArgument(args);
- }
- }
- private void processStandaloneOption(Collection<ArgSpec> required,
- Set<ArgSpec> initialized,
- String arg,
- Stack<String> args,
- boolean paramAttachedToKey) throws Exception {
- ArgSpec argSpec = commandSpec.optionsMap().get(arg);
- required.remove(argSpec);
- Range arity = argSpec.arity();
- if (paramAttachedToKey) {
- arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
- }
- LookBehind lookBehind = paramAttachedToKey ? LookBehind.ATTACHED_WITH_SEPARATOR : LookBehind.SEPARATE;
- if (tracer.isDebug()) {tracer.debug("Found option named '%s': %s, arity=%s%n", arg, argSpec, arity);}
- parseResultBuilder.nowProcessing.add(argSpec);
- applyOption(argSpec, lookBehind, arity, args, initialized, "option " + arg);
- }
- private void processClusteredShortOptions(Collection<ArgSpec> required,
- Set<ArgSpec> initialized,
- String arg,
- Stack<String> args) throws Exception {
- String prefix = arg.substring(0, 1);
- String cluster = arg.substring(1);
- boolean paramAttachedToOption = true;
- boolean first = true;
- do {
- if (cluster.length() > 0 && commandSpec.posixOptionsMap().containsKey(cluster.charAt(0))) {
- ArgSpec argSpec = commandSpec.posixOptionsMap().get(cluster.charAt(0));
- Range arity = argSpec.arity();
- String argDescription = "option " + prefix + cluster.charAt(0);
- if (tracer.isDebug()) {tracer.debug("Found option '%s%s' in %s: %s, arity=%s%n", prefix, cluster.charAt(0), arg,
- argSpec, arity);}
- required.remove(argSpec);
- cluster = cluster.substring(1);
- paramAttachedToOption = cluster.length() > 0;
- LookBehind lookBehind = paramAttachedToOption ? LookBehind.ATTACHED : LookBehind.SEPARATE;
- if (cluster.startsWith(config().separator())) {// attached with separator, like -f=FILE or -v=true
- lookBehind = LookBehind.ATTACHED_WITH_SEPARATOR;
- cluster = cluster.substring(config().separator().length());
- arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
- }
- if (arity.min > 0 && !empty(cluster)) {
- if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as option parameter%n", cluster);}
- }
- // arity may be >= 1, or
- // arity <= 0 && !cluster.startsWith(separator)
- // e.g., boolean @Option("-v", arity=0, varargs=true); arg "-rvTRUE", remainder cluster="TRUE"
- if (!empty(cluster)) {
- args.push(cluster); // interpret remainder as option parameter (CAUTION: may be empty string!)
- }
- if (first) {
- parseResultBuilder.nowProcessing.add(argSpec);
- first = false;
- } else {
- parseResultBuilder.nowProcessing.set(parseResultBuilder.nowProcessing.size() - 1, argSpec); // replace
- }
- int argCount = args.size();
- int consumed = applyOption(argSpec, lookBehind, arity, args, initialized, argDescription);
- // if cluster was consumed as a parameter or if this field was the last in the cluster we're done; otherwise continue do-while loop
- if (empty(cluster) || args.isEmpty() || args.size() < argCount) {
- return;
- }
- cluster = args.pop();
- } else { // cluster is empty || cluster.charAt(0) is not a short option key
- if (cluster.length() == 0) { // we finished parsing a group of short options like -rxv
- return; // return normally and parse the next arg
- }
- // We get here when the remainder of the cluster group is neither an option,
- // nor a parameter that the last option could consume.
- if (arg.endsWith(cluster)) {
- args.push(paramAttachedToOption ? prefix + cluster : cluster);
- if (args.peek().equals(arg)) { // #149 be consistent between unmatched short and long options
- if (tracer.isDebug()) {tracer.debug("Could not match any short options in %s, deciding whether to treat as unmatched option or positional parameter...%n", arg);}
- if (commandSpec.resemblesOption(arg, tracer)) { handleUnmatchedArgument(args); return; } // #149
- processPositionalParameter(required, initialized, args);
- return;
- }
- // remainder was part of a clustered group that could not be completely parsed
- if (tracer.isDebug()) {tracer.debug("No option found for %s in %s%n", cluster, arg);}
- String tmp = args.pop();
- tmp = tmp + " (while processing option: '" + arg + "')";
- args.push(tmp);
- handleUnmatchedArgument(args);
- } else {
- args.push(cluster);
- if (tracer.isDebug()) {tracer.debug("%s is not an option parameter for %s%n", cluster, arg);}
- processPositionalParameter(required, initialized, args);
- }
- return;
- }
- } while (true);
- }
- private int applyOption(ArgSpec argSpec,
- LookBehind lookBehind,
- Range arity,
- Stack<String> args,
- Set<ArgSpec> initialized,
- String argDescription) throws Exception {
- updateHelpRequested(argSpec);
- boolean consumeOnlyOne = commandSpec.parser().aritySatisfiedByAttachedOptionParam() && lookBehind.isAttached();
- Stack<String> workingStack = args;
- if (consumeOnlyOne) {
- workingStack = args.isEmpty() ? args : stack(args.pop());
- } else {
- if (!assertNoMissingParameters(argSpec, arity, args)) { return 0; } // #389 collectErrors parsing
- }
- if (argSpec.interactive()) {
- String name = argSpec.isOption() ? ((OptionSpec) argSpec).longestName() : "position " + position;
- String prompt = String.format("Enter value for %s (%s): ", name, str(argSpec.renderedDescription(), 0));
- if (tracer.isDebug()) {tracer.debug("Reading value for %s from console...%n", name);}
- char[] value = readPassword(prompt);
- if (tracer.isDebug()) {tracer.debug("User entered '%s' for %s.%n", value, name);}
- workingStack.push(new String(value));
- }
- parseResultBuilder.beforeMatchingGroupElement(argSpec);
- int result;
- if (argSpec.type().isArray()) {
- result = applyValuesToArrayField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
- } else if (Collection.class.isAssignableFrom(argSpec.type())) {
- result = applyValuesToCollectionField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
- } else if (Map.class.isAssignableFrom(argSpec.type())) {
- result = applyValuesToMapField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
- } else {
- result = applyValueToSingleValuedField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
- }
- if (workingStack != args && !workingStack.isEmpty()) {
- args.push(workingStack.pop());
- Assert.assertTrue(workingStack.isEmpty(), "Working stack should be empty but was " + new ArrayList<String>(workingStack));
- }
- return result;
- }
- private int applyValueToSingleValuedField(ArgSpec argSpec,
- LookBehind lookBehind,
- Range derivedArity,
- Stack<String> args,
- Set<ArgSpec> initialized,
- String argDescription) throws Exception {
- boolean noMoreValues = args.isEmpty();
- String value = args.isEmpty() ? null : trim(args.pop()); // unquote the value
- Range arity = argSpec.arity().isUnspecified ? derivedArity : argSpec.arity(); // #509
- if (arity.max == 0 && !arity.isUnspecified && lookBehind == LookBehind.ATTACHED_WITH_SEPARATOR) { // #509
- throw new MaxValuesExceededException(CommandLine.this, optionDescription("", argSpec, 0) +
- " should be specified without '" + value + "' parameter");
- }
- int result = arity.min; // the number or args we need to consume
- Class<?> cls = argSpec.auxiliaryTypes()[0]; // field may be interface/abstract type, use annotation to get concrete type
- if (arity.min <= 0) { // value may be optional
- // special logic for booleans: BooleanConverter accepts only "true" or "false".
- if (cls == Boolean.class || cls == Boolean.TYPE) {
- // boolean option with arity = 0..1 or 0..*: value MAY be a param
- if (arity.max > 0 && ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) {
- result = 1; // if it is a varargs we only consume 1 argument if it is a boolean value
- if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, value); }
- } else if (lookBehind != LookBehind.ATTACHED_WITH_SEPARATOR) { // if attached, try converting the value to boolean (and fail if invalid value)
- // it's okay to ignore value if not attached to option
- if (value != null) {
- args.push(value); // we don't consume the value
- }
- if (commandSpec.parser().toggleBooleanFlags()) {
- Boolean currentValue = (Boolean) argSpec.getValue();
- value = String.valueOf(currentValue == null || !currentValue); // #147 toggle existing boolean value
- } else {
- value = "true";
- }
- }
- } else { // non-boolean option with optional value #325, #279
- if (isOption(value)) {
- args.push(value); // we don't consume the value
- value = "";
- } else if (value == null) {
- value = "";
- } else {
- if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, value); }
- }
- }
- } else {
- if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, value); }
- }
- if (noMoreValues && value == null) {
- return 0;
- }
- ITypeConverter<?> converter = getTypeConverter(cls, argSpec, 0);
- Object newValue = tryConvert(argSpec, -1, converter, value, cls);
- Object oldValue = argSpec.getValue();
- String traceMessage = "Setting %s to '%3$s' (was '%2$s') for %4$s%n";
- if (argSpec.group() == null && initialized.contains(argSpec)) {
- if (!isOverwrittenOptionsAllowed()) {
- throw new OverwrittenOptionException(CommandLine.this, argSpec, optionDescription("", argSpec, 0) + " should be specified only once");
- }
- traceMessage = "Overwriting %s value '%s' with '%s' for %s%n";
- }
- initialized.add(argSpec);
- if (tracer.isInfo()) { tracer.info(traceMessage, argSpec.toString(), String.valueOf(oldValue), String.valueOf(newValue), argDescription); }
- int pos = getPosition(argSpec);
- argSpec.setValue(newValue);
- parseResultBuilder.addOriginalStringValue(argSpec, value);// #279 track empty string value if no command line argument was consumed
- parseResultBuilder.addStringValue(argSpec, value);
- parseResultBuilder.addTypedValues(argSpec, pos, newValue);
- parseResultBuilder.add(argSpec, pos);
- return result;
- }
- private int applyValuesToMapField(ArgSpec argSpec,
- LookBehind lookBehind,
- Range arity,
- Stack<String> args,
- Set<ArgSpec> initialized,
- String argDescription) throws Exception {
- Class<?>[] classes = argSpec.auxiliaryTypes();
- if (classes.length < 2) { throw new ParameterException(CommandLine.this, argSpec.toString() + " needs two types (one for the map key, one for the value) but only has " + classes.length + " types configured.",argSpec, null); }
- ITypeConverter<?> keyConverter = getTypeConverter(classes[0], argSpec, 0);
- ITypeConverter<?> valueConverter = getTypeConverter(classes[1], argSpec, 1);
- @SuppressWarnings("unchecked") Map<Object, Object> map = (Map<Object, Object>) argSpec.getValue();
- if (map == null || (!map.isEmpty() && !initialized.contains(argSpec))) {
- tracer.debug("Initializing binding for %s with empty %s%n", optionDescription("", argSpec, 0), argSpec.type().getSimpleName());
- map = createMap(argSpec.type()); // map class
- argSpec.setValue(map);
- }
- initialized.add(argSpec);
- int originalSize = map.size();
- int pos = getPosition(argSpec);
- consumeMapArguments(argSpec, lookBehind, arity, args, classes, keyConverter, valueConverter, map, argDescription);
- parseResultBuilder.add(argSpec, pos);
- argSpec.setValue(map);
- return map.size() - originalSize;
- }
- private void consumeMapArguments(ArgSpec argSpec,
- LookBehind lookBehind,
- Range arity,
- Stack<String> args,
- Class<?>[] classes,
- ITypeConverter<?> keyConverter,
- ITypeConverter<?> valueConverter,
- Map<Object, Object> result,
- String argDescription) throws Exception {
- // don't modify Interpreter.position: same position may be consumed by multiple ArgSpec objects
- int currentPosition = getPosition(argSpec);
- // first do the arity.min mandatory parameters
- int initialSize = argSpec.stringValues().size();
- int consumed = consumedCountMap(0, initialSize, argSpec);
- for (int i = 0; consumed < arity.min && !args.isEmpty(); i++) {
- Map<Object, Object> typedValuesAtPosition = new LinkedHashMap<Object, Object>();
- parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
- assertNoMissingMandatoryParameter(argSpec, args, i, arity);
- consumeOneMapArgument(argSpec, lookBehind, arity, consumed, args.pop(), classes, keyConverter, valueConverter, typedValuesAtPosition, i, argDescription);
- result.putAll(typedValuesAtPosition);
- consumed = consumedCountMap(i + 1, initialSize, argSpec);
- lookBehind = LookBehind.SEPARATE;
- }
- // now process the varargs if any
- for (int i = consumed; consumed < arity.max && !args.isEmpty(); i++) {
- if (!varargCanConsumeNextValue(argSpec, args.peek())) { break; }
- Map<Object, Object> typedValuesAtPosition = new LinkedHashMap<Object, Object>();
- parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
- if (!canConsumeOneMapArgument(argSpec, arity, consumed, args.peek(), classes, keyConverter, valueConverter, argDescription)) {
- break; // leave empty map at argSpec.typedValueAtPosition[currentPosition] so we won't try to consume that position again
- }
- consumeOneMapArgument(argSpec, lookBehind, arity, consumed, args.pop(), classes, keyConverter, valueConverter, typedValuesAtPosition, i, argDescription);
- result.putAll(typedValuesAtPosition);
- consumed = consumedCountMap(i + 1, initialSize, argSpec);
- lookBehind = LookBehind.SEPARATE;
- }
- }
- private void consumeOneMapArgument(ArgSpec argSpec,
- LookBehind lookBehind,
- Range arity, int consumed,
- String arg,
- Class<?>[] classes,
- ITypeConverter<?> keyConverter, ITypeConverter<?> valueConverter,
- Map<Object, Object> result,
- int index,
- String argDescription) throws Exception {
- if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, arg); }
- String raw = trim(arg);
- String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
- for (String value : values) {
- String[] keyValue = splitKeyValue(argSpec, value);
- Object mapKey = tryConvert(argSpec, index, keyConverter, keyValue[0], classes[0]);
- Object mapValue = tryConvert(argSpec, index, valueConverter, keyValue[1], classes[1]);
- result.put(mapKey, mapValue);
- if (tracer.isInfo()) { tracer.info("Putting [%s : %s] in %s<%s, %s> %s for %s%n", String.valueOf(mapKey), String.valueOf(mapValue),
- result.getClass().getSimpleName(), classes[0].getSimpleName(), classes[1].getSimpleName(), argSpec.toString(), argDescription); }
- parseResultBuilder.addStringValue(argSpec, keyValue[0]);
- parseResultBuilder.addStringValue(argSpec, keyValue[1]);
- }
- parseResultBuilder.addOriginalStringValue(argSpec, raw);
- }
- private boolean canConsumeOneMapArgument(ArgSpec argSpec, Range arity, int consumed,
- String raw, Class<?>[] classes,
- ITypeConverter<?> keyConverter, ITypeConverter<?> valueConverter,
- String argDescription) {
- String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
- try {
- for (String value : values) {
- String[] keyValue = splitKeyValue(argSpec, value);
- tryConvert(argSpec, -1, keyConverter, keyValue[0], classes[0]);
- tryConvert(argSpec, -1, valueConverter, keyValue[1], classes[1]);
- }
- return true;
- } catch (PicocliException ex) {
- tracer.debug("$s cannot be assigned to %s: type conversion fails: %s.%n", raw, argDescription, ex.getMessage());
- return false;
- }
- }
- private String[] splitKeyValue(ArgSpec argSpec, String value) {
- String[] keyValue = ArgSpec.splitRespectingQuotedStrings(value, 2, config(), argSpec, "=");
- if (keyValue.length < 2) {
- String splitRegex = argSpec.splitRegex();
- if (splitRegex.length() == 0) {
- throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("",
- argSpec, 0) + " should be in KEY=VALUE format but was " + value, argSpec, value);
- } else {
- throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("",
- argSpec, 0) + " should be in KEY=VALUE[" + splitRegex + "KEY=VALUE]... format but was " + value, argSpec, value);
- }
- }
- return keyValue;
- }
- private void assertNoMissingMandatoryParameter(ArgSpec argSpec, Stack<String> args, int i, Range arity) {
- if (!varargCanConsumeNextValue(argSpec, args.peek())) {
- String desc = arity.min > 1 ? (i + 1) + " (of " + arity.min + " mandatory parameters) " : "";
- throw new MissingParameterException(CommandLine.this, argSpec, "Expected parameter " + desc + "for " + optionDescription("", argSpec, -1) + " but found '" + args.peek() + "'");
- }
- }
- private int applyValuesToArrayField(ArgSpec argSpec,
- LookBehind lookBehind,
- Range arity,
- Stack<String> args,
- Set<ArgSpec> initialized,
- String argDescription) throws Exception {
- Object existing = argSpec.getValue();
- int length = existing == null ? 0 : Array.getLength(existing);
- Class<?> type = argSpec.auxiliaryTypes()[0];
- int pos = getPosition(argSpec);
- List<Object> converted = consumeArguments(argSpec, lookBehind, arity, args, type, argDescription);
- List<Object> newValues = new ArrayList<Object>();
- if (initialized.contains(argSpec)) { // existing values are default values if initialized does NOT contain argsSpec
- for (int i = 0; i < length; i++) {
- newValues.add(Array.get(existing, i)); // keep non-default values
- }
- }
- initialized.add(argSpec);
- for (Object obj : converted) {
- if (obj instanceof Collection<?>) {
- newValues.addAll((Collection<?>) obj);
- } else {
- newValues.add(obj);
- }
- }
- Object array = Array.newInstance(type, newValues.size());
- for (int i = 0; i < newValues.size(); i++) {
- Array.set(array, i, newValues.get(i));
- }
- argSpec.setValue(array);
- parseResultBuilder.add(argSpec, pos);
- return converted.size(); // return how many args were consumed
- }
- @SuppressWarnings("unchecked")
- private int applyValuesToCollectionField(ArgSpec argSpec,
- LookBehind lookBehind,
- Range arity,
- Stack<String> args,
- Set<ArgSpec> initialized,
- String argDescription) throws Exception {
- Collection<Object> collection = (Collection<Object>) argSpec.getValue();
- Class<?> type = argSpec.auxiliaryTypes()[0];
- int pos = getPosition(argSpec);
- List<Object> converted = consumeArguments(argSpec, lookBehind, arity, args, type, argDescription);
- if (collection == null || (!collection.isEmpty() && !initialized.contains(argSpec))) {
- tracer.debug("Initializing binding for %s with empty %s%n", optionDescription("", argSpec, 0), argSpec.type().getSimpleName());
- collection = createCollection(argSpec.type(), type); // collection type, element type
- argSpec.setValue(collection);
- }
- initialized.add(argSpec);
- for (Object element : converted) {
- if (element instanceof Collection<?>) {
- collection.addAll((Collection<?>) element);
- } else {
- collection.add(element);
- }
- }
- parseResultBuilder.add(argSpec, pos);
- argSpec.setValue(collection);
- return converted.size();
- }
- private List<Object> consumeArguments(ArgSpec argSpec,
- LookBehind lookBehind,
- Range arity,
- Stack<String> args,
- Class<?> type,
- String argDescription) throws Exception {
- List<Object> result = new ArrayList<Object>();
- // don't modify Interpreter.position: same position may be consumed by multiple ArgSpec objects
- int currentPosition = getPosition(argSpec);
- // first do the arity.min mandatory parameters
- int initialSize = argSpec.stringValues().size();
- int consumed = consumedCount(0, initialSize, argSpec);
- for (int i = 0; consumed < arity.min && !args.isEmpty(); i++) {
- List<Object> typedValuesAtPosition = new ArrayList<Object>();
- parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
- assertNoMissingMandatoryParameter(argSpec, args, i, arity);
- consumeOneArgument(argSpec, lookBehind, arity, consumed, args.pop(), type, typedValuesAtPosition, i, argDescription);
- result.addAll(typedValuesAtPosition);
- consumed = consumedCount(i + 1, initialSize, argSpec);
- lookBehind = LookBehind.SEPARATE;
- }
- // now process the varargs if any
- for (int i = consumed; consumed < arity.max && !args.isEmpty(); i++) {
- if (!varargCanConsumeNextValue(argSpec, args.peek())) { break; }
- List<Object> typedValuesAtPosition = new ArrayList<Object>();
- parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
- if (!canConsumeOneArgument(argSpec, arity, consumed, args.peek(), type, argDescription)) {
- break; // leave empty list at argSpec.typedValueAtPosition[currentPosition] so we won't try to consume that position again
- }
- consumeOneArgument(argSpec, lookBehind, arity, consumed, args.pop(), type, typedValuesAtPosition, i, argDescription);
- result.addAll(typedValuesAtPosition);
- consumed = consumedCount(i + 1, initialSize, argSpec);
- lookBehind = LookBehind.SEPARATE;
- }
- if (result.isEmpty() && arity.min == 0 && arity.max <= 1 && isBoolean(type)) {
- return Arrays.asList((Object) Boolean.TRUE);
- }
- return result;
- }
- private int consumedCount(int i, int initialSize, ArgSpec arg) {
- return commandSpec.parser().splitFirst() ? arg.stringValues().size() - initialSize : i;
- }
- private int consumedCountMap(int i, int initialSize, ArgSpec arg) {
- return commandSpec.parser().splitFirst() ? (arg.stringValues().size() - initialSize) / 2 : i;
- }
- private int consumeOneArgument(ArgSpec argSpec,
- LookBehind lookBehind,
- Range arity,
- int consumed,
- String arg,
- Class<?> type,
- List<Object> result,
- int index,
- String argDescription) {
- if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, arg); }
- String raw = trim(arg);
- String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
- ITypeConverter<?> converter = getTypeConverter(type, argSpec, 0);
- for (int j = 0; j < values.length; j++) {
- Object stronglyTypedValue = tryConvert(argSpec, index, converter, values[j], type);
- result.add(stronglyTypedValue);
- if (tracer.isInfo()) {
- tracer.info("Adding [%s] to %s for %s%n", String.valueOf(result.get(result.size() - 1)), argSpec.toString(), argDescription);
- }
- parseResultBuilder.addStringValue(argSpec, values[j]);
- }
- parseResultBuilder.addOriginalStringValue(argSpec, raw);
- return ++index;
- }
- private boolean canConsumeOneArgument(ArgSpec argSpec, Range arity, int consumed, String arg, Class<?> type, String argDescription) {
- ITypeConverter<?> converter = getTypeConverter(type, argSpec, 0);
- try {
- String[] values = argSpec.splitValue(trim(arg), commandSpec.parser(), arity, consumed);
- // if (!argSpec.acceptsValues(values.length, commandSpec.parser())) {
- // tracer.debug("$s would split into %s values but %s cannot accept that many values.%n", arg, values.length, argDescription);
- // return false;
- // }
- for (String value : values) {
- tryConvert(argSpec, -1, converter, value, type);
- }
- return true;
- } catch (PicocliException ex) {
- tracer.debug("$s cannot be assigned to %s: type conversion fails: %s.%n", arg, argDescription, ex.getMessage());
- return false;
- }
- }
- /** Returns whether the next argument can be assigned to a vararg option/positional parameter.
- * <p>
- * Usually, we stop if we encounter '--', a command, or another option.
- * However, if end-of-options has been reached, positional parameters may consume all remaining arguments. </p>*/
- private boolean varargCanConsumeNextValue(ArgSpec argSpec, String nextValue) {
- if (endOfOptions && argSpec.isPositional()) { return true; }
- boolean isCommand = commandSpec.subcommands().containsKey(nextValue);
- return !isCommand && !isOption(nextValue);
- }
- /**
- * Called when parsing varargs parameters for a multi-value option.
- * When an option is encountered, the remainder should not be interpreted as vararg elements.
- * @param arg the string to determine whether it is an option or not
- * @return true if it is an option, false otherwise
- */
- private boolean isOption(String arg) {
- if (arg == null) { return false; }
- if ("--".equals(arg)) { return true; }
- // not just arg prefix: we may be in the middle of parsing -xrvfFILE
- if (commandSpec.optionsMap().containsKey(arg)) { // -v or -f or --file (not attached to param or other option)
- return true;
- }
- int separatorIndex = arg.indexOf(config().separator());
- if (separatorIndex > 0) { // -f=FILE or --file==FILE (attached to param via separator)
- if (commandSpec.optionsMap().containsKey(arg.substring(0, separatorIndex))) {
- return true;
- }
- }
- return (arg.length() > 2 && arg.startsWith("-") && commandSpec.posixOptionsMap().containsKey(arg.charAt(1)));
- }
- private Object tryConvert(ArgSpec argSpec, int index, ITypeConverter<?> converter, String value, Class<?> type)
- throws ParameterException {
- try {
- return converter.convert(value);
- } catch (TypeConversionException ex) {
- String msg = String.format("Invalid value for %s: %s", optionDescription("", argSpec, index), ex.getMessage());
- throw new ParameterException(CommandLine.this, msg, argSpec, value);
- } catch (Exception other) {
- String desc = optionDescription("", argSpec, index);
- String msg = String.format("Invalid value for %s: cannot convert '%s' to %s (%s)", desc, value, type.getSimpleName(), other);
- throw new ParameterException(CommandLine.this, msg, other, argSpec, value);
- }
- }
- private String optionDescription(String prefix, ArgSpec argSpec, int index) {
- String desc = "";
- if (argSpec.isOption()) {
- desc = prefix + "option '" + ((OptionSpec) argSpec).longestName() + "'";
- if (index >= 0) {
- if (argSpec.arity().max > 1) {
- desc += " at index " + index;
- }
- desc += " (" + argSpec.paramLabel() + ")";
- }
- } else {
- desc = prefix + "positional parameter at index " + ((PositionalParamSpec) argSpec).index() + " (" + argSpec.paramLabel() + ")";
- }
- return desc;
- }
- private boolean isAnyHelpRequested() { return isHelpRequested || parseResultBuilder.versionHelpRequested || parseResultBuilder.usageHelpRequested; }
- private void updateHelpRequested(CommandSpec command) {
- isHelpRequested |= command.helpCommand();
- }
- private void updateHelpRequested(ArgSpec argSpec) {
- if (!parseResultBuilder.isInitializingDefaultValues && argSpec.isOption()) {
- OptionSpec option = (OptionSpec) argSpec;
- isHelpRequested |= is(argSpec, "help", option.help());
- parseResultBuilder.versionHelpRequested |= is(argSpec, "versionHelp", option.versionHelp());
- parseResultBuilder.usageHelpRequested |= is(argSpec, "usageHelp", option.usageHelp());
- }
- }
- private boolean is(ArgSpec p, String attribute, boolean value) {
- if (value) { if (tracer.isInfo()) {tracer.info("%s has '%s' annotation: not validating required fields%n", p.toString(), attribute); }}
- return value;
- }
- @SuppressWarnings("unchecked")
- private Collection<Object> createCollection(Class<?> collectionClass, Class<?> elementType) throws Exception {
- if (EnumSet.class.isAssignableFrom(collectionClass) && Enum.class.isAssignableFrom(elementType)) {
- Object enumSet = EnumSet.noneOf((Class<Enum>) elementType);
- return (Collection<Object>) enumSet;
- }
- // custom Collection implementation class must have default constructor
- return (Collection<Object>) factory.create(collectionClass);
- }
- @SuppressWarnings("unchecked") private Map<Object, Object> createMap(Class<?> mapClass) throws Exception {
- return (Map<Object, Object>) factory.create(mapClass);
- }
- private ITypeConverter<?> getTypeConverter(final Class<?> type, ArgSpec argSpec, int index) {
- if (argSpec.converters().length > index) { return argSpec.converters()[index]; }
- if (converterRegistry.containsKey(type)) { return converterRegistry.get(type); }
- if (type.isEnum()) {
- return new ITypeConverter<Object>() {
- @SuppressWarnings("unchecked")
- public Object convert(String value) throws Exception {
- String sensitivity = "case-sensitive";
- if (commandSpec.parser().caseInsensitiveEnumValuesAllowed()) {
- String upper = value.toUpperCase();
- for (Object enumConstant : type.getEnumConstants()) {
- if (upper.equals(String.valueOf(enumConstant).toUpperCase())) { return enumConstant; }
- }
- sensitivity = "case-insensitive";
- }
- try { return Enum.valueOf((Class<Enum>) type, value); }
- catch (Exception ex) {
- Enum<?>[] constants = ((Class<Enum<?>>) type).getEnumConstants();
- String[] names = new String[constants.length];
- for (int i = 0; i < names.length; i++) { names[i] = constants[i].name(); }
- throw new TypeConversionException(
- String.format("expected one of %s (%s) but was '%s'", Arrays.asList(names), sensitivity, value)); }
- }
- };
- }
- throw new MissingTypeConverterException(CommandLine.this, "No TypeConverter registered for " + type.getName() + " of " + argSpec);
- }
- private boolean assertNoMissingParameters(ArgSpec argSpec, Range arity, Stack<String> args) {
- if (argSpec.interactive()) { return true; }
- int available = args.size();
- if (available > 0 && commandSpec.parser().splitFirst() && argSpec.splitRegex().length() > 0) {
- available += argSpec.splitValue(args.peek(), commandSpec.parser(), arity, 0).length - 1;
- }
- if (arity.min > available) {
- if (arity.min == 1) {
- if (argSpec.isOption()) {
- maybeThrow(new MissingParameterException(CommandLine.this, argSpec, "Missing required parameter for " +
- optionDescription("", argSpec, 0)));
- return false;
- }
- Range indexRange = ((PositionalParamSpec) argSpec).index();
- String sep = "";
- String names = ": ";
- int count = 0;
- List<PositionalParamSpec> positionalParameters = commandSpec.positionalParameters();
- for (int i = indexRange.min; i < positionalParameters.size(); i++) {
- if (positionalParameters.get(i).arity().min > 0) {
- names += sep + positionalParameters.get(i).paramLabel();
- sep = ", ";
- count++;
- }
- }
- String msg = "Missing required parameter";
- Range paramArity = argSpec.arity();
- if (count > 1 || arity.min - available > 1) {
- msg += "s";
- }
- maybeThrow(new MissingParameterException(CommandLine.this, argSpec, msg + names));
- } else if (args.isEmpty()) {
- maybeThrow(new MissingParameterException(CommandLine.this, argSpec, optionDescription("", argSpec, 0) +
- " requires at least " + arity.min + " values, but none were specified."));
- } else {
- maybeThrow(new MissingParameterException(CommandLine.this, argSpec, optionDescription("", argSpec, 0) +
- " requires at least " + arity.min + " values, but only " + available + " were specified: " + reverse(args)));
- }
- return false;
- }
- return true;
- }
- private String trim(String value) {
- return unquote(value);
- }
- private String unquote(String value) {
- if (!commandSpec.parser().trimQuotes()) { return value; }
- return value == null
- ? null
- : (value.length() > 1 && value.startsWith("\"") && value.endsWith("\""))
- ? value.substring(1, value.length() - 1)
- : value;
- }
- char[] readPassword(String prompt) {
- try {
- Object console = System.class.getDeclaredMethod("console").invoke(null);
- Method method = Class.forName("java.io.Console").getDeclaredMethod("readPassword", String.class, Object[].class);
- return (char[]) method.invoke(console, prompt, new Object[0]);
- } catch (Exception e) {
- System.out.print(prompt);
- InputStreamReader isr = new InputStreamReader(System.in);
- BufferedReader in = new BufferedReader(isr);
- try {
- return in.readLine().toCharArray();
- } catch (IOException ex2) {
- throw new IllegalStateException(ex2);
- }
- }
- }
- int getPosition(ArgSpec arg) {
- if (arg.group() == null) { return position; }
- MatchedGroup matchedGroup = parseResultBuilder.matchedGroup.findOrCreateMatchingGroup(arg, commandSpec.commandLine());
- return matchedGroup == null ? 0 : matchedGroup.multiple().position;
- }
- String positionDesc(ArgSpec arg) {
- int pos = getPosition(arg);
- return (arg.group() == null) ? pos + " (command-local)" : pos + " (in group " + arg.group().synopsis() + ")";
- }
- }
- private static class PositionalParametersSorter implements Comparator<ArgSpec> {
- private static final Range OPTION_INDEX = new Range(0, 0, false, true, "0");
- public int compare(ArgSpec p1, ArgSpec p2) {
- int result = index(p1).compareTo(index(p2));
- return (result == 0) ? p1.arity().compareTo(p2.arity()) : result;
- }
- private Range index(ArgSpec arg) { return arg.isOption() ? OPTION_INDEX : ((PositionalParamSpec) arg).index(); }
- }
- /**
- * Inner class to group the built-in {@link ITypeConverter} implementations.
- */
- private static class BuiltIn {
- static class StringConverter implements ITypeConverter<String> {
- public String convert(String value) { return value; }
- }
- static class StringBuilderConverter implements ITypeConverter<StringBuilder> {
- public StringBuilder convert(String value) { return new StringBuilder(value); }
- }
- static class CharSequenceConverter implements ITypeConverter<CharSequence> {
- public String convert(String value) { return value; }
- }
- /** Converts {@code "true"} or {@code "false"} to a {@code Boolean}. Other values result in a ParameterException.*/
- static class BooleanConverter implements ITypeConverter<Boolean> {
- public Boolean convert(String value) {
- if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
- return Boolean.parseBoolean(value);
- } else {
- throw new TypeConversionException("'" + value + "' is not a boolean");
- }
- }
- }
- static class CharacterConverter implements ITypeConverter<Character> {
- public Character convert(String value) {
- if (value.length() > 1) {
- throw new TypeConversionException("'" + value + "' is not a single character");
- }
- return value.charAt(0);
- }
- }
- private static TypeConversionException fail(String value, Class<?> c) { return fail(value, c, "'%s' is not a %s"); }
- private static TypeConversionException fail(String value, Class<?> c, String template) {
- return new TypeConversionException(String.format(template, value, c.getSimpleName()));
- }
- /** Converts text to a {@code Byte} by delegating to {@link Byte#valueOf(String)}.*/
- static class ByteConverter implements ITypeConverter<Byte> {
- public Byte convert(String value) { try {return Byte.valueOf(value);} catch (Exception ex) {throw fail(value, Byte.TYPE);} }
- }
- /** Converts text to a {@code Short} by delegating to {@link Short#valueOf(String)}.*/
- static class ShortConverter implements ITypeConverter<Short> {
- public Short convert(String value) { try {return Short.valueOf(value);} catch (Exception ex) {throw fail(value, Short.TYPE);} }
- }
- /** Converts text to an {@code Integer} by delegating to {@link Integer#valueOf(String)}.*/
- static class IntegerConverter implements ITypeConverter<Integer> {
- public Integer convert(String value) { try {return Integer.valueOf(value);} catch (Exception ex) {throw fail(value, Integer.TYPE, "'%s' is not an %s");} }
- }
- /** Converts text to a {@code Long} by delegating to {@link Long#valueOf(String)}.*/
- static class LongConverter implements ITypeConverter<Long> {
- public Long convert(String value) { try {return Long.valueOf(value);} catch (Exception ex) {throw fail(value, Long.TYPE);} }
- }
- static class FloatConverter implements ITypeConverter<Float> {
- public Float convert(String value) { try {return Float.valueOf(value);} catch (Exception ex) {throw fail(value, Float.TYPE);} }
- }
- static class DoubleConverter implements ITypeConverter<Double> {
- public Double convert(String value) { try {return Double.valueOf(value);} catch (Exception ex) {throw fail(value, Double.TYPE);} }
- }
- static class FileConverter implements ITypeConverter<File> {
- public File convert(String value) { return new File(value); }
- }
- static class URLConverter implements ITypeConverter<URL> {
- public URL convert(String value) throws MalformedURLException { return new URL(value); }
- }
- static class URIConverter implements ITypeConverter<URI> {
- public URI convert(String value) throws URISyntaxException { return new URI(value); }
- }
- /** Converts text in {@code yyyy-mm-dd} format to a {@code java.util.Date}. ParameterException on failure. */
- static class ISO8601DateConverter implements ITypeConverter<Date> {
- public Date convert(String value) {
- try {
- return new SimpleDateFormat("yyyy-MM-dd").parse(value);
- } catch (ParseException e) {
- throw new TypeConversionException("'" + value + "' is not a yyyy-MM-dd date");
- }
- }
- }
- /** Converts text in any of the following formats to a {@code java.sql.Time}: {@code HH:mm}, {@code HH:mm:ss},
- * {@code HH:mm:ss.SSS}, {@code HH:mm:ss,SSS}. Other formats result in a ParameterException. */
- static class ISO8601TimeConverter implements ITypeConverter<Object> {
- // Implementation note: use reflection so that picocli only requires the java.base module in Java 9.
- private static /*final*/ String FQCN = "java.sql.Time"; // non-final for testing
- public Object convert(String value) {
- try {
- if (value.length() <= 5) {
- return createTime(new SimpleDateFormat("HH:mm").parse(value).getTime());
- } else if (value.length() <= 8) {
- return createTime(new SimpleDateFormat("HH:mm:ss").parse(value).getTime());
- } else if (value.length() <= 12) {
- try {
- return createTime(new SimpleDateFormat("HH:mm:ss.SSS").parse(value).getTime());
- } catch (ParseException e2) {
- return createTime(new SimpleDateFormat("HH:mm:ss,SSS").parse(value).getTime());
- }
- }
- } catch (ParseException ignored) {
- // ignored because we throw a ParameterException below
- }
- throw new TypeConversionException("'" + value + "' is not a HH:mm[:ss[.SSS]] time");
- }
- private Object createTime(long epochMillis) {
- try {
- Class<?> timeClass = Class.forName(FQCN);
- Constructor<?> constructor = timeClass.getDeclaredConstructor(long.class);
- return constructor.newInstance(epochMillis);
- } catch (Exception e) {
- throw new TypeConversionException("Unable to create new java.sql.Time with long value " + epochMillis + ": " + e.getMessage());
- }
- }
- public static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer) {
- if (excluded(FQCN, tracer)) { return; }
- try {
- registry.put(Class.forName(FQCN), new ISO8601TimeConverter());
- } catch (Exception e) {
- if (!traced.contains(FQCN)) {
- tracer.debug("Could not register converter for %s: %s%n", FQCN, e.toString());
- }
- traced.add(FQCN);
- }
- }
- }
- static class BigDecimalConverter implements ITypeConverter<BigDecimal> {
- public BigDecimal convert(String value) { return new BigDecimal(value); }
- }
- static class BigIntegerConverter implements ITypeConverter<BigInteger> {
- public BigInteger convert(String value) { return new BigInteger(value); }
- }
- static class CharsetConverter implements ITypeConverter<Charset> {
- public Charset convert(String s) { return Charset.forName(s); }
- }
- /** Converts text to a {@code InetAddress} by delegating to {@link InetAddress#getByName(String)}. */
- static class InetAddressConverter implements ITypeConverter<InetAddress> {
- public InetAddress convert(String s) throws Exception { return InetAddress.getByName(s); }
- }
- static class PatternConverter implements ITypeConverter<Pattern> {
- public Pattern convert(String s) { return Pattern.compile(s); }
- }
- static class UUIDConverter implements ITypeConverter<UUID> {
- public UUID convert(String s) throws Exception { return UUID.fromString(s); }
- }
- static class CurrencyConverter implements ITypeConverter<Currency> {
- public Currency convert(String s) throws Exception { return Currency.getInstance(s); }
- }
- static class TimeZoneConverter implements ITypeConverter<TimeZone> {
- public TimeZone convert(String s) throws Exception { return TimeZone.getTimeZone(s); }
- }
- static class ByteOrderConverter implements ITypeConverter<ByteOrder> {
- public ByteOrder convert(String s) throws Exception {
- if (s.equalsIgnoreCase(ByteOrder.BIG_ENDIAN.toString())) { return ByteOrder.BIG_ENDIAN; }
- if (s.equalsIgnoreCase(ByteOrder.LITTLE_ENDIAN.toString())) { return ByteOrder.LITTLE_ENDIAN; }
- throw new TypeConversionException("'" + s + "' is not a valid ByteOrder");
- }
- }
- static class ClassConverter implements ITypeConverter<Class<?>> {
- public Class<?> convert(String s) throws Exception { return Class.forName(s); }
- }
- static class NetworkInterfaceConverter implements ITypeConverter<NetworkInterface> {
- public NetworkInterface convert(String s) throws Exception {
- try {
- InetAddress addr = new InetAddressConverter().convert(s);
- return NetworkInterface.getByInetAddress(addr);
- } catch (Exception ex) {
- try { return NetworkInterface.getByName(s);
- } catch (Exception ex2) {
- throw new TypeConversionException("'" + s + "' is not an InetAddress or NetworkInterface name");
- }
- }
- }
- }
- static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer, String fqcn, String factoryMethodName, Class<?>... paramTypes) {
- registerIfAvailable(registry, tracer, fqcn, fqcn, factoryMethodName, paramTypes);
- }
- static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer, String fqcn, String factoryClass, String factoryMethodName, Class<?>... paramTypes) {
- if (excluded(fqcn, tracer)) { return; }
- try {
- Class<?> cls = Class.forName(fqcn);
- Class<?> factory = Class.forName(factoryClass);
- Method method = factory.getDeclaredMethod(factoryMethodName, paramTypes);
- registry.put(cls, new ReflectionConverter(method, paramTypes));
- } catch (Exception e) {
- if (!traced.contains(fqcn)) {
- tracer.debug("Could not register converter for %s: %s%n", fqcn, e.toString());
- }
- traced.add(fqcn);
- }
- }
- static boolean excluded(String fqcn, Tracer tracer) {
- String[] excludes = System.getProperty("picocli.converters.excludes", "").split(",");
- for (String regex : excludes) {
- if (fqcn.matches(regex)) {
- tracer.debug("BuiltIn type converter for %s is not loaded: (picocli.converters.excludes=%s)%n", fqcn, System.getProperty("picocli.converters.excludes"));
- return true;
- }
- }
- return false;
- }
- static Set<String> traced = new HashSet<String>();
- static class ReflectionConverter implements ITypeConverter<Object> {
- private final Method method;
- private Class<?>[] paramTypes;
- public ReflectionConverter(Method method, Class<?>... paramTypes) {
- this.method = Assert.notNull(method, "method");
- this.paramTypes = Assert.notNull(paramTypes, "paramTypes");
- }
- public Object convert(String s) {
- try {
- if (paramTypes.length > 1) {
- return method.invoke(null, s, new String[0]);
- } else {
- return method.invoke(null, s);
- }
- } catch (InvocationTargetException e) {
- throw new TypeConversionException(String.format("cannot convert '%s' to %s (%s)", s, method.getReturnType(), e.getTargetException()));
- } catch (Exception e) {
- throw new TypeConversionException(String.format("Internal error converting '%s' to %s (%s)", s, method.getReturnType(), e));
- }
- }
- }
- private BuiltIn() {} // private constructor: never instantiate
- }
- static class AutoHelpMixin {
- private static final String KEY = "mixinStandardHelpOptions";
- @Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "mixinStandardHelpOptions.help",
- description = "Show this help message and exit.")
- private boolean helpRequested;
- @Option(names = {"-V", "--version"}, versionHelp = true, descriptionKey = "mixinStandardHelpOptions.version",
- description = "Print version information and exit.")
- private boolean versionRequested;
- }
- /** Help command that can be installed as a subcommand on all application commands. When invoked with a subcommand
- * argument, it prints usage help for the specified subcommand. For example:<pre>
- *
- * // print help for subcommand
- * command help subcommand
- * </pre><p>
- * When invoked without additional parameters, it prints usage help for the parent command. For example:
- * </p><pre>
- *
- * // print help for command
- * command help
- * </pre>
- * For {@linkplain Messages internationalization}: this command has a {@code --help} option with {@code descriptionKey = "helpCommand.help"},
- * and a {@code COMMAND} positional parameter with {@code descriptionKey = "helpCommand.command"}.
- * @since 3.0
- */
- @Command(name = "help", header = "Displays help information about the specified command",
- synopsisHeading = "%nUsage: ", helpCommand = true,
- description = {"%nWhen no COMMAND is given, the usage help for the main command is displayed.",
- "If a COMMAND is specified, the help for that command is shown.%n"})
- public static final class HelpCommand implements IHelpCommandInitializable, Runnable {
- @Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "helpCommand.help",
- description = "Show usage help for the help command and exit.")
- private boolean helpRequested;
- @Parameters(paramLabel = "COMMAND", descriptionKey = "helpCommand.command",
- description = "The COMMAND to display the usage help message for.")
- private String[] commands = new String[0];
- private CommandLine self;
- private PrintStream out;
- private PrintStream err;
- private Help.Ansi ansi;
- /** Invokes {@link #usage(PrintStream, Help.Ansi) usage} for the specified command, or for the parent command. */
- public void run() {
- CommandLine parent = self == null ? null : self.getParent();
- if (parent == null) { return; }
- if (commands.length > 0) {
- CommandLine subcommand = parent.getSubcommands().get(commands[0]);
- if (subcommand != null) {
- subcommand.usage(out, ansi);
- } else {
- throw new ParameterException(parent, "Unknown subcommand '" + commands[0] + "'.", null, commands[0]);
- }
- } else {
- parent.usage(out, ansi);
- }
- }
- /** {@inheritDoc} */
- public void init(CommandLine helpCommandLine, Help.Ansi ansi, PrintStream out, PrintStream err) {
- this.self = Assert.notNull(helpCommandLine, "helpCommandLine");
- this.ansi = Assert.notNull(ansi, "ansi");
- this.out = Assert.notNull(out, "out");
- this.err = Assert.notNull(err, "err");
- }
- }
- /** Help commands that provide usage help for other commands can implement this interface to be initialized with the information they need.
- * <p>The {@link #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) CommandLine::printHelpIfRequested} method calls the
- * {@link #init(CommandLine, picocli.CommandLine.Help.Ansi, PrintStream, PrintStream) init} method on commands marked as {@link Command#helpCommand() helpCommand}
- * before the help command's {@code run} or {@code call} method is called.</p>
- * <p><b>Implementation note:</b></p><p>
- * If an error occurs in the {@code run} or {@code call} method while processing the help request, it is recommended custom Help
- * commands throw a {@link ParameterException ParameterException} with a reference to the parent command. The {@link DefaultExceptionHandler DefaultExceptionHandler} will print
- * the error message and the usage for the parent command, and will terminate with the exit code of the exception handler if one was set.
- * </p>
- * @since 3.0 */
- public static interface IHelpCommandInitializable {
- /** Initializes this object with the information needed to implement a help command that provides usage help for other commands.
- * @param helpCommandLine the {@code CommandLine} object associated with this help command. Implementors can use
- * this to walk the command hierarchy and get access to the help command's parent and sibling commands.
- * @param ansi whether to use Ansi colors or not
- * @param out the stream to print the usage help message to
- * @param err the error stream to print any diagnostic messages to, in addition to the output from the exception handler
- */
- void init(CommandLine helpCommandLine, Help.Ansi ansi, PrintStream out, PrintStream err);
- }
- /**
- * Renders a section of the usage help message. The usage help message can be customized:
- * use the {@link #setHelpSectionKeys(List)} and {@link #setHelpSectionMap(Map)} to change the order of sections,
- * delete standard sections, add custom sections or replace the renderer of a standard sections with a custom one.
- * <p>
- * This gives complete freedom on how a usage help message section is rendered, but it also means that the section renderer
- * is responsible for all aspects of rendering the section, including layout and emitting ANSI escape codes.
- * The {@link Help.TextTable} and {@link Help.Ansi.Text} classes, and the {@link CommandLine.Help.Ansi#string(String)} and {@link CommandLine.Help.Ansi#text(String)} methods may be useful.
- * </p>
- * @see UsageMessageSpec
- * @since 3.9
- */
- public interface IHelpSectionRenderer {
- /**
- * Renders a section of the usage help, like header heading, header, synopsis heading,
- * synopsis, description heading, description, etc.
- * @param help the {@code Help} instance for which to render a section
- * @return the text for this section; may contain {@linkplain Help.Ansi ANSI} escape codes
- * @since 3.9
- */
- String render(Help help);
- }
- /**
- * A collection of methods and inner classes that provide fine-grained control over the contents and layout of
- * the usage help message to display to end users when help is requested or invalid input values were specified.
- * <h2>Class Diagram of the CommandLine.Help API</h2>
- * <p>
- * <img src="doc-files/class-diagram-help-api.png" alt="Class Diagram of the CommandLine.Help API">
- * </p>
- * <h2>Layered API</h2>
- * <p>The {@link Command} annotation and the {@link UsageMessageSpec} programmatic API equivalent
- * provide the easiest way to configure the usage help message. See
- * the <a href="https://remkop.github.io/picocli/index.html#_usage_help">Manual</a> for details.</p>
- * <p>This Help class provides high-level functions to create sections of the usage help message and headings
- * for these sections. Instead of calling the {@link CommandLine#usage(PrintStream, CommandLine.Help.ColorScheme)}
- * method, application authors may want to create a custom usage help message by reorganizing sections in a
- * different order and/or adding custom sections.</p>
- * <p>Finally, the Help class contains inner classes and interfaces that can be used to create custom help messages.</p>
- * <h3>IOptionRenderer and IParameterRenderer</h3>
- * <p>Renders a field annotated with {@link Option} or {@link Parameters} to an array of {@link Text} values.
- * By default, these values are</p><ul>
- * <li>mandatory marker character (if the option/parameter is {@link Option#required() required})</li>
- * <li>short option name (empty for parameters)</li>
- * <li>comma or empty (empty for parameters)</li>
- * <li>long option names (the parameter {@link IParamLabelRenderer label} for parameters)</li>
- * <li>description</li>
- * </ul>
- * <p>Other components rely on this ordering.</p>
- * <h3>Layout</h3>
- * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
- * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
- * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
- * <h3>TextTable</h3>
- * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
- * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
- * longer than the column's width.</p>
- * <h3>Text</h3>
- * <p>Encapsulates rich text with styles and colors in a way that other components like {@link TextTable} are
- * unaware of the embedded ANSI escape codes.</p>
- */
- public static class Help {
- /** Constant String holding the default program name, value defined in {@link CommandSpec#DEFAULT_COMMAND_NAME}. */
- protected static final String DEFAULT_COMMAND_NAME = CommandSpec.DEFAULT_COMMAND_NAME;
- /** Constant String holding the default string that separates options from option parameters, value defined in {@link ParserSpec#DEFAULT_SEPARATOR}. */
- protected static final String DEFAULT_SEPARATOR = ParserSpec.DEFAULT_SEPARATOR;
- private final static int defaultOptionsColumnWidth = 24;
- private final CommandSpec commandSpec;
- private final ColorScheme colorScheme;
- private final Map<String, Help> commands = new LinkedHashMap<String, Help>();
- private List<String> aliases = Collections.emptyList();
- private IParamLabelRenderer parameterLabelRenderer;
- /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
- * on the specified class and superclasses.
- * @param command the annotated object to create usage help for */
- public Help(Object command) {
- this(command, Ansi.AUTO);
- }
- /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
- * on the specified class and superclasses.
- * @param command the annotated object to create usage help for
- * @param ansi whether to emit ANSI escape codes or not */
- public Help(Object command, Ansi ansi) {
- this(command, defaultColorScheme(ansi));
- }
- /** Constructs a new {@code Help} instance with the specified color scheme, initialized from annotatations
- * on the specified class and superclasses.
- * @param command the annotated object to create usage help for
- * @param colorScheme the color scheme to use
- * @deprecated use {@link picocli.CommandLine.Help#Help(picocli.CommandLine.Model.CommandSpec, picocli.CommandLine.Help.ColorScheme)} */
- @Deprecated public Help(Object command, ColorScheme colorScheme) {
- this(CommandSpec.forAnnotatedObject(command, new DefaultFactory()), colorScheme);
- }
- /** Constructs a new {@code Help} instance with the specified color scheme, initialized from annotatations
- * on the specified class and superclasses.
- * @param commandSpec the command model to create usage help for
- * @param colorScheme the color scheme to use */
- public Help(CommandSpec commandSpec, ColorScheme colorScheme) {
- this.commandSpec = Assert.notNull(commandSpec, "commandSpec");
- this.aliases = new ArrayList<String>(Arrays.asList(commandSpec.aliases()));
- this.aliases.add(0, commandSpec.name());
- this.colorScheme = Assert.notNull(colorScheme, "colorScheme").applySystemProperties();
- parameterLabelRenderer = createDefaultParamLabelRenderer(); // uses help separator
- this.addAllSubcommands(commandSpec.subcommands());
- }
- Help withCommandNames(List<String> aliases) { this.aliases = aliases; return this; }
- /** Returns the {@code CommandSpec} model that this Help was constructed with.
- * @since 3.9 */
- public CommandSpec commandSpec() { return commandSpec; }
- /** Returns the {@code ColorScheme} model that this Help was constructed with.
- * @since 3.0 */
- public ColorScheme colorScheme() { return colorScheme; }
- /** Returns the {@code IHelpFactory} that this Help was constructed with.
- * @since 3.9 */
- private IHelpFactory getHelpFactory() { return commandSpec.usageMessage().helpFactory(); }
- /** Returns the map of subcommand {@code Help} instances for this command Help.
- * @since 3.9 */
- protected Map<String, Help> subcommands() { return Collections.unmodifiableMap(commands); }
- /** Returns the list of aliases for the command in this Help.
- * @since 3.9 */
- protected List<String> aliases() { return Collections.unmodifiableList(aliases); }
- /** Option and positional parameter value label renderer used for the synopsis line(s) and the option list.
- * By default initialized to the result of {@link #createDefaultParamLabelRenderer()}, which takes a snapshot
- * of the {@link ParserSpec#separator()} at construction time. If the separator is modified after Help construction, you
- * may need to re-initialize this field by calling {@link #createDefaultParamLabelRenderer()} again. */
- public IParamLabelRenderer parameterLabelRenderer() {return parameterLabelRenderer;}
- /** Registers all specified subcommands with this Help.
- * @param commands maps the command names to the associated CommandLine object
- * @return this Help instance (for method chaining)
- * @see CommandLine#getSubcommands()
- */
- public Help addAllSubcommands(Map<String, CommandLine> commands) {
- if (commands != null) {
- // first collect aliases
- Map<CommandLine, List<String>> done = new IdentityHashMap<CommandLine, List<String>>();
- for (CommandLine cmd : commands.values()) {
- if (!done.containsKey(cmd)) {
- done.put(cmd, new ArrayList<String>(Arrays.asList(cmd.commandSpec.aliases())));
- }
- }
- // then loop over all names that the command was registered with and add this name to the front of the list (if it isn't already in the list)
- for (Map.Entry<String, CommandLine> entry : commands.entrySet()) {
- List<String> aliases = done.get(entry.getValue());
- if (!aliases.contains(entry.getKey())) { aliases.add(0, entry.getKey()); }
- }
- // The aliases list for each command now has at least one entry, with the main name at the front.
- // Now we loop over the commands in the order that they were registered on their parent command.
- for (Map.Entry<String, CommandLine> entry : commands.entrySet()) {
- // not registering hidden commands is easier than suppressing display in Help.commandList():
- // if all subcommands are hidden, help should not show command list header
- if (!entry.getValue().getCommandSpec().usageMessage().hidden()) {
- List<String> aliases = done.remove(entry.getValue());
- if (aliases != null) { // otherwise we already processed this command by another alias
- addSubcommand(aliases, entry.getValue());
- }
- }
- }
- }
- return this;
- }
- /** Registers the specified subcommand with this Help.
- * @param commandNames the name and aliases of the subcommand to display in the usage message
- * @param commandLine the {@code CommandLine} object to get more information from
- * @return this Help instance (for method chaining) */
- Help addSubcommand(List<String> commandNames, CommandLine commandLine) {
- String all = commandNames.toString();
- commands.put(all.substring(1, all.length() - 1), getHelpFactory().create(commandLine.commandSpec, colorScheme).withCommandNames(commandNames));
- return this;
- }
- /** Registers the specified subcommand with this Help.
- * @param commandName the name of the subcommand to display in the usage message
- * @param command the {@code CommandSpec} or {@code @Command} annotated object to get more information from
- * @return this Help instance (for method chaining)
- * @deprecated
- */
- @Deprecated public Help addSubcommand(String commandName, Object command) {
- commands.put(commandName,
- getHelpFactory().create(CommandSpec.forAnnotatedObject(command, commandSpec.commandLine().factory), defaultColorScheme(Ansi.AUTO)));
- return this;
- }
- List<OptionSpec> options() { return commandSpec.options(); }
- List<PositionalParamSpec> positionalParameters() { return commandSpec.positionalParameters(); }
- String commandName() { return commandSpec.name(); }
- /** Returns a synopsis for the command without reserving space for the synopsis heading.
- * @return a synopsis
- * @see #abbreviatedSynopsis()
- * @see #detailedSynopsis(Comparator, boolean)
- * @deprecated use {@link #synopsis(int)} instead
- */
- @Deprecated public String synopsis() { return synopsis(0); }
- /**
- * Returns a synopsis for the command, reserving the specified space for the synopsis heading.
- * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
- * @return a synopsis
- * @see #abbreviatedSynopsis()
- * @see #detailedSynopsis(Comparator, boolean)
- * @see #synopsisHeading
- */
- public String synopsis(int synopsisHeadingLength) {
- if (!empty(commandSpec.usageMessage().customSynopsis())) { return customSynopsis(); }
- return commandSpec.usageMessage().abbreviateSynopsis() ? abbreviatedSynopsis()
- : detailedSynopsis(synopsisHeadingLength, createShortOptionArityAndNameComparator(), true);
- }
- /** Generates a generic synopsis like {@code <command name> [OPTIONS] [PARAM1 [PARAM2]...]}, omitting parts
- * that don't apply to the command (e.g., does not show [OPTIONS] if the command has no options).
- * @return a generic synopsis */
- public String abbreviatedSynopsis() {
- StringBuilder sb = new StringBuilder();
- if (!commandSpec.optionsMap().isEmpty()) { // only show if annotated object actually has options
- sb.append(" [OPTIONS]");
- }
- // sb.append(" [--] "); // implied
- for (PositionalParamSpec positionalParam : commandSpec.positionalParameters()) {
- if (!positionalParam.hidden()) {
- sb.append(' ').append(parameterLabelRenderer().renderParameterLabel(positionalParam, ansi(), colorScheme.parameterStyles));
- }
- }
- // only show if object has subcommands
- if (!commandSpec.subcommands().isEmpty()) {
- sb.append(" [COMMAND]");
- }
- return colorScheme.commandText(commandSpec.qualifiedName()).toString()
- + (sb.toString()) + System.getProperty("line.separator");
- }
- /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
- * showing optional options and parameters in square brackets ({@code [ ]}).
- * @param optionSort comparator to sort options or {@code null} if options should not be sorted
- * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
- * @return a detailed synopsis
- * @deprecated use {@link #detailedSynopsis(int, Comparator, boolean)} instead. */
- @Deprecated public String detailedSynopsis(Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
- return detailedSynopsis(0, optionSort, clusterBooleanOptions);
- }
- /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
- * showing optional options and parameters in square brackets ({@code [ ]}).
- * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
- * @param optionSort comparator to sort options or {@code null} if options should not be sorted
- * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
- * @return a detailed synopsis
- * @since 3.0 */
- public String detailedSynopsis(int synopsisHeadingLength, Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
- Set<ArgSpec> argsInGroups = new HashSet<ArgSpec>();
- Text groupsText = createDetailedSynopsisGroupsText(argsInGroups);
- Text optionText = createDetailedSynopsisOptionsText(argsInGroups, optionSort, clusterBooleanOptions);
- Text positionalParamText = createDetailedSynopsisPositionalsText(argsInGroups);
- Text commandText = createDetailedSynopsisCommandText();
- Text text = groupsText.concat(optionText).concat(positionalParamText).concat(commandText);
- return insertSynopsisCommandName(synopsisHeadingLength, text);
- }
- /** Returns a Text object containing a partial detailed synopsis showing only the options and positional parameters in
- * the specified {@linkplain ArgGroup#validate() validating} {@linkplain ArgGroup groups}, starting with a {@code " "} space.
- * @param outparam_groupArgs all options and positional parameters in the groups this method generates a synopsis for;
- * these options and positional parameters should be excluded from appearing elsewhere in the synopsis
- * @return the formatted groups synopsis elements, starting with a {@code " "} space, or an empty Text if this command has no validating groups
- * @since 4.0 */
- protected Text createDetailedSynopsisGroupsText(Set<ArgSpec> outparam_groupArgs) {
- Set<ArgGroupSpec> remove = new HashSet<ArgGroupSpec>();
- List<ArgGroupSpec> groups = new ArrayList<ArgGroupSpec>(commandSpec().argGroups());
- for (ArgGroupSpec group : groups) {
- if (group.validate()) {
- // remove subgroups
- remove.addAll(group.subgroups());
- // exclude options and positional parameters in this group
- outparam_groupArgs.addAll(group.args());
- // exclude options and positional parameters in the subgroups
- for (ArgGroupSpec subgroup : group.subgroups()) {
- outparam_groupArgs.addAll(subgroup.args());
- }
- } else {
- remove.add(group); // non-validating groups should not impact synopsis
- }
- }
- groups.removeAll(remove);
- Text groupText = ansi().new Text(0);
- for (ArgGroupSpec group : groups) {
- groupText = groupText.concat(" ").concat(group.synopsisText(colorScheme()));
- }
- return groupText;
- }
- /** Returns a Text object containing a partial detailed synopsis showing only the options, starting with a {@code " "} space.
- * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}).
- * @param done the list of options and positional parameters for which a synopsis was already generated. Options in this set should be excluded.
- * @param optionSort comparator to sort options or {@code null} if options should not be sorted
- * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
- * @return the formatted options, starting with a {@code " "} space, or an empty Text if this command has no named options
- * @since 3.9 */
- protected Text createDetailedSynopsisOptionsText(Collection<ArgSpec> done, Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
- Text optionText = ansi().new Text(0);
- List<OptionSpec> options = new ArrayList<OptionSpec>(commandSpec.options()); // iterate in declaration order
- if (optionSort != null) {
- Collections.sort(options, optionSort);// iterate in specified sort order
- }
- options.removeAll(done);
- if (clusterBooleanOptions) { // cluster all short boolean options into a single string
- List<OptionSpec> booleanOptions = new ArrayList<OptionSpec>();
- StringBuilder clusteredRequired = new StringBuilder("-");
- StringBuilder clusteredOptional = new StringBuilder("-");
- for (OptionSpec option : options) {
- if (option.hidden()) { continue; }
- boolean isFlagOption = option.typeInfo().isBoolean();
- if (isFlagOption && option.arity().max <= 0) { // #612 consider arity: boolean options may require a parameter
- String shortestName = option.shortestName();
- if (shortestName.length() == 2 && shortestName.startsWith("-")) {
- booleanOptions.add(option);
- if (option.required()) {
- clusteredRequired.append(shortestName.substring(1));
- } else {
- clusteredOptional.append(shortestName.substring(1));
- }
- }
- }
- }
- options.removeAll(booleanOptions);
- if (clusteredRequired.length() > 1) { // initial length was 1
- optionText = optionText.concat(" ").concat(colorScheme.optionText(clusteredRequired.toString()));
- }
- if (clusteredOptional.length() > 1) { // initial length was 1
- optionText = optionText.concat(" [").concat(colorScheme.optionText(clusteredOptional.toString())).concat("]");
- }
- }
- for (OptionSpec option : options) {
- if (!option.hidden()) {
- Text name = colorScheme.optionText(option.shortestName());
- Text param = parameterLabelRenderer().renderParameterLabel(option, colorScheme.ansi(), colorScheme.optionParamStyles);
- if (option.required()) { // e.g., -x=VAL
- optionText = optionText.concat(" ").concat(name).concat(param).concat("");
- if (option.isMultiValue()) { // e.g., -x=VAL [-x=VAL]...
- optionText = optionText.concat(" [").concat(name).concat(param).concat("]...");
- }
- } else {
- optionText = optionText.concat(" [").concat(name).concat(param).concat("]");
- if (option.isMultiValue()) { // add ellipsis to show option is repeatable
- optionText = optionText.concat("...");
- }
- }
- }
- }
- return optionText;
- }
- /** Returns a Text object containing a partial detailed synopsis showing only the positional parameters, starting with a {@code " "} space.
- * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}).
- * @param done the list of options and positional parameters for which a synopsis was already generated. Positional parameters in this set should be excluded.
- * @return the formatted positional parameters, starting with a {@code " "} space, or an empty Text if this command has no positional parameters
- * @since 3.9 */
- protected Text createDetailedSynopsisPositionalsText(Collection<ArgSpec> done) {
- Text positionalParamText = ansi().new Text(0);
- List<PositionalParamSpec> positionals = new ArrayList<PositionalParamSpec>(commandSpec.positionalParameters()); // iterate in declaration order
- positionals.removeAll(done);
- for (PositionalParamSpec positionalParam : positionals) {
- if (!positionalParam.hidden()) {
- positionalParamText = positionalParamText.concat(" ");
- Text label = parameterLabelRenderer().renderParameterLabel(positionalParam, colorScheme.ansi(), colorScheme.parameterStyles);
- positionalParamText = positionalParamText.concat(label);
- }
- }
- return positionalParamText;
- }
- /** Returns a Text object containing a partial detailed synopsis showing only the subcommands, starting with a {@code " "} space.
- * Follows the unix convention of showing optional elements in square brackets ({@code [ ]}).
- * @return this implementation returns a hard-coded string {@code " [COMMAND]"} if this command has subcommands, an empty Text otherwise
- * @since 3.9 */
- protected Text createDetailedSynopsisCommandText() {
- Text commandText = ansi().new Text(0);
- if (!commandSpec.subcommands().isEmpty()){
- commandText = commandText.concat(" [")
- .concat("COMMAND")
- .concat("]");
- }
- return commandText;
- }
- /**
- * Returns the detailed synopsis text by inserting the command name before the specified text with options and positional parameters details.
- * @param synopsisHeadingLength length of the synopsis heading string to be displayed on the same line as the first synopsis line.
- * For example, if the synopsis heading is {@code "Usage: "}, this value is 7.
- * @param optionsAndPositionalsAndCommandsDetails formatted string with options, positional parameters and subcommands.
- * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}).
- * @return the detailed synopsis text, in multiple lines if the length exceeds the usage width
- */
- protected String insertSynopsisCommandName(int synopsisHeadingLength, Text optionsAndPositionalsAndCommandsDetails) {
- // Fix for #142: first line of synopsis overshoots max. characters
- String commandName = commandSpec.qualifiedName();
- int firstColumnLength = commandName.length() + synopsisHeadingLength;
- // synopsis heading ("Usage: ") may be on the same line, so adjust column width
- TextTable textTable = TextTable.forColumnWidths(ansi(), firstColumnLength, width() - firstColumnLength);
- textTable.indentWrappedLines = 1; // don't worry about first line: options (2nd column) always start with a space
- // right-adjust the command name by length of synopsis heading
- Text PADDING = Ansi.OFF.new Text(stringOf('X', synopsisHeadingLength));
- textTable.addRowValues(PADDING.concat(colorScheme.commandText(commandName)), optionsAndPositionalsAndCommandsDetails);
- return textTable.toString().substring(synopsisHeadingLength); // cut off leading synopsis heading spaces
- }
- /** Returns the number of characters the synopsis heading will take on the same line as the synopsis.
- * @return the number of characters the synopsis heading will take on the same line as the synopsis.
- * @see #detailedSynopsis(int, Comparator, boolean)
- */
- public int synopsisHeadingLength() {
- String[] lines = Ansi.OFF.new Text(commandSpec.usageMessage().synopsisHeading()).toString().split("\\r?\\n|\\r|%n", -1);
- return lines[lines.length - 1].length();
- }
- /**
- * <p>Returns a description of the {@linkplain Option options} supported by the application.
- * This implementation {@linkplain #createShortOptionNameComparator() sorts options alphabetically}, and shows
- * only the {@linkplain Option#hidden() non-hidden} options in a {@linkplain TextTable tabular format}
- * using the {@linkplain #createDefaultOptionRenderer() default renderer} and {@linkplain Layout default layout}.</p>
- * @return the fully formatted option list
- * @see #optionList(Layout, Comparator, IParamLabelRenderer)
- */
- public String optionList() {
- Comparator<OptionSpec> sortOrder = commandSpec.usageMessage().sortOptions()
- ? createShortOptionNameComparator()
- : createOrderComparatorIfNecessary(commandSpec.options());
- return optionList(createLayout(calcLongOptionColumnWidth()), sortOrder, parameterLabelRenderer());
- }
- private static Comparator<OptionSpec> createOrderComparatorIfNecessary(List<OptionSpec> options) {
- for (OptionSpec option : options) { if (option.order() != OptionSpec.DEFAULT_ORDER) { return createOrderComparator(); } }
- return null;
- }
- private int calcLongOptionColumnWidth() {
- int max = 0;
- IOptionRenderer optionRenderer = new DefaultOptionRenderer(false, " ");
- for (OptionSpec option : commandSpec.options()) {
- Text[][] values = optionRenderer.render(option, parameterLabelRenderer(), colorScheme);
- int len = values[0][3].length;
- if (len < Help.defaultOptionsColumnWidth - 3) { max = Math.max(max, len); }
- }
- IParameterRenderer paramRenderer = new DefaultParameterRenderer(false, " ");
- for (PositionalParamSpec positional : commandSpec.positionalParameters()) {
- Text[][] values = paramRenderer.render(positional, parameterLabelRenderer(), colorScheme);
- int len = values[0][3].length;
- if (len < Help.defaultOptionsColumnWidth - 3) { max = Math.max(max, len); }
- }
- return max + 3;
- }
- /** Sorts all {@code Options} with the specified {@code comparator} (if the comparator is non-{@code null}),
- * then {@linkplain Layout#addOption(CommandLine.Model.OptionSpec, CommandLine.Help.IParamLabelRenderer) adds} all non-hidden options to the
- * specified TextTable and returns the result of TextTable.toString().
- * @param layout responsible for rendering the option list
- * @param valueLabelRenderer used for options with a parameter
- * @return the fully formatted option list
- * @since 3.0 */
- public String optionList(Layout layout, Comparator<OptionSpec> optionSort, IParamLabelRenderer valueLabelRenderer) {
- List<OptionSpec> options = new ArrayList<OptionSpec>(commandSpec.options()); // options are stored in order of declaration
- if (optionSort != null) {
- Collections.sort(options, optionSort); // default: sort options ABC
- }
- List<ArgGroupSpec> groups = optionListGroups();
- for (ArgGroupSpec group : groups) { options.removeAll(group.options()); }
- StringBuilder sb = new StringBuilder();
- layout.addOptions(options, valueLabelRenderer);
- sb.append(layout.toString());
- int longOptionColumnWidth = calcLongOptionColumnWidth();
- Collections.sort(groups, new SortByOrder<ArgGroupSpec>());
- for (ArgGroupSpec group : groups) {
- sb.append(heading(ansi(), width(), group.heading()));
- Layout groupLayout = createLayout(longOptionColumnWidth);
- groupLayout.addPositionalParameters(group.positionalParameters(), valueLabelRenderer);
- List<OptionSpec> groupOptions = new ArrayList<OptionSpec>(group.options());
- if (optionSort != null) {
- Collections.sort(groupOptions, optionSort);
- }
- groupLayout.addOptions(groupOptions, valueLabelRenderer);
- sb.append(groupLayout);
- }
- return sb.toString();
- }
- /** Returns the list of {@code ArgGroupSpec}s with a non-{@code null} heading. */
- private List<ArgGroupSpec> optionListGroups() {
- List<ArgGroupSpec> result = new ArrayList<ArgGroupSpec>();
- optionListGroups(commandSpec.argGroups(), result);
- return result;
- }
- private static void optionListGroups(List<ArgGroupSpec> groups, List<ArgGroupSpec> result) {
- for (ArgGroupSpec group : groups) {
- optionListGroups(group.subgroups(), result);
- if (group.heading() != null) { result.add(group); }
- }
- }
- /**
- * Returns the section of the usage help message that lists the parameters with their descriptions.
- * @return the section of the usage help message that lists the parameters
- */
- public String parameterList() {
- return parameterList(createLayout(calcLongOptionColumnWidth()), parameterLabelRenderer());
- }
- /**
- * Returns the section of the usage help message that lists the parameters with their descriptions.
- * @param layout the layout to use
- * @param paramLabelRenderer for rendering parameter names
- * @return the section of the usage help message that lists the parameters
- */
- public String parameterList(Layout layout, IParamLabelRenderer paramLabelRenderer) {
- List<PositionalParamSpec> positionals = new ArrayList<PositionalParamSpec>(commandSpec.positionalParameters());
- List<ArgGroupSpec> groups = optionListGroups();
- for (ArgGroupSpec group : groups) { positionals.removeAll(group.positionalParameters()); }
- layout.addPositionalParameters(positionals, paramLabelRenderer);
- return layout.toString();
- }
- private static String heading(Ansi ansi, int usageWidth, String values, Object... params) {
- StringBuilder sb = join(ansi, usageWidth, new String[] {values}, new StringBuilder(), params);
- return trimLineSeparator(sb.toString()) + new String(spaces(countTrailingSpaces(values)));
- }
- static String trimLineSeparator(String result) {
- return result.endsWith(System.getProperty("line.separator"))
- ? result.substring(0, result.length() - System.getProperty("line.separator").length()) : result;
- }
- private static char[] spaces(int length) { char[] result = new char[length]; Arrays.fill(result, ' '); return result; }
- private static int countTrailingSpaces(String str) {
- if (str == null) {return 0;}
- int trailingSpaces = 0;
- for (int i = str.length() - 1; i >= 0 && str.charAt(i) == ' '; i--) { trailingSpaces++; }
- return trailingSpaces;
- }
- /** Formats each of the specified values and appends it to the specified StringBuilder.
- * @param ansi whether the result should contain ANSI escape codes or not
- * @param usageHelpWidth the width of the usage help message
- * @param values the values to format and append to the StringBuilder
- * @param sb the StringBuilder to collect the formatted strings
- * @param params the parameters to pass to the format method when formatting each value
- * @return the specified StringBuilder */
- public static StringBuilder join(Ansi ansi, int usageHelpWidth, String[] values, StringBuilder sb, Object... params) {
- if (values != null) {
- TextTable table = TextTable.forColumnWidths(ansi, usageHelpWidth);
- table.indentWrappedLines = 0;
- for (String summaryLine : values) {
- Text[] lines = ansi.new Text(format(summaryLine, params)).splitLines();
- for (Text line : lines) { table.addRowValues(line); }
- }
- table.toString(sb);
- }
- return sb;
- }
- private int width() { return commandSpec.usageMessage().width(); }
- /** Returns command custom synopsis as a string. A custom synopsis can be zero or more lines, and can be
- * specified declaratively with the {@link Command#customSynopsis()} annotation attribute or programmatically
- * by setting the Help instance's {@link Help#customSynopsis} field.
- * @param params Arguments referenced by the format specifiers in the synopsis strings
- * @return the custom synopsis lines combined into a single String (which may be empty)
- */
- public String customSynopsis(Object... params) {
- return join(ansi(), width(), commandSpec.usageMessage().customSynopsis(), new StringBuilder(), params).toString();
- }
- /** Returns command description text as a string. Description text can be zero or more lines, and can be specified
- * declaratively with the {@link Command#description()} annotation attribute or programmatically by
- * setting the Help instance's {@link Help#description} field.
- * @param params Arguments referenced by the format specifiers in the description strings
- * @return the description lines combined into a single String (which may be empty)
- */
- public String description(Object... params) {
- return join(ansi(), width(), commandSpec.usageMessage().description(), new StringBuilder(), params).toString();
- }
- /** Returns the command header text as a string. Header text can be zero or more lines, and can be specified
- * declaratively with the {@link Command#header()} annotation attribute or programmatically by
- * setting the Help instance's {@link Help#header} field.
- * @param params Arguments referenced by the format specifiers in the header strings
- * @return the header lines combined into a single String (which may be empty)
- */
- public String header(Object... params) {
- return join(ansi(), width(), commandSpec.usageMessage().header(), new StringBuilder(), params).toString();
- }
- /** Returns command footer text as a string. Footer text can be zero or more lines, and can be specified
- * declaratively with the {@link Command#footer()} annotation attribute or programmatically by
- * setting the Help instance's {@link Help#footer} field.
- * @param params Arguments referenced by the format specifiers in the footer strings
- * @return the footer lines combined into a single String (which may be empty)
- */
- public String footer(Object... params) {
- return join(ansi(), width(), commandSpec.usageMessage().footer(), new StringBuilder(), params).toString();
- }
- /** Returns the text displayed before the header text; the result of {@code String.format(headerHeading, params)}.
- * @param params the parameters to use to format the header heading
- * @return the formatted header heading */
- public String headerHeading(Object... params) {
- return heading(ansi(), width(), commandSpec.usageMessage().headerHeading(), params);
- }
- /** Returns the text displayed before the synopsis text; the result of {@code String.format(synopsisHeading, params)}.
- * @param params the parameters to use to format the synopsis heading
- * @return the formatted synopsis heading */
- public String synopsisHeading(Object... params) {
- return heading(ansi(), width(), commandSpec.usageMessage().synopsisHeading(), params);
- }
- /** Returns the text displayed before the description text; an empty string if there is no description,
- * otherwise the result of {@code String.format(descriptionHeading, params)}.
- * @param params the parameters to use to format the description heading
- * @return the formatted description heading */
- public String descriptionHeading(Object... params) {
- return empty(commandSpec.usageMessage().descriptionHeading()) ? "" : heading(ansi(), width(), commandSpec.usageMessage().descriptionHeading(), params);
- }
- /** Returns the text displayed before the positional parameter list; an empty string if there are no positional
- * parameters, otherwise the result of {@code String.format(parameterListHeading, params)}.
- * @param params the parameters to use to format the parameter list heading
- * @return the formatted parameter list heading */
- public String parameterListHeading(Object... params) {
- return commandSpec.positionalParameters().isEmpty() ? "" : heading(ansi(), width(), commandSpec.usageMessage().parameterListHeading(), params);
- }
- /** Returns the text displayed before the option list; an empty string if there are no options,
- * otherwise the result of {@code String.format(optionListHeading, params)}.
- * @param params the parameters to use to format the option list heading
- * @return the formatted option list heading */
- public String optionListHeading(Object... params) {
- return commandSpec.optionsMap().isEmpty() ? "" : heading(ansi(), width(), commandSpec.usageMessage().optionListHeading(), params);
- }
- /** Returns the text displayed before the command list; an empty string if there are no commands,
- * otherwise the result of {@code String.format(commandListHeading, params)}.
- * @param params the parameters to use to format the command list heading
- * @return the formatted command list heading */
- public String commandListHeading(Object... params) {
- return commands.isEmpty() ? "" : heading(ansi(), width(), commandSpec.usageMessage().commandListHeading(), params);
- }
- /** Returns the text displayed before the footer text; the result of {@code String.format(footerHeading, params)}.
- * @param params the parameters to use to format the footer heading
- * @return the formatted footer heading */
- public String footerHeading(Object... params) {
- return heading(ansi(), width(), commandSpec.usageMessage().footerHeading(), params);
- }
- /** Returns a 2-column list with command names and the first line of their header or (if absent) description.
- * @return a usage help section describing the added commands */
- public String commandList() {
- if (subcommands().isEmpty()) { return ""; }
- int commandLength = maxLength(subcommands().keySet());
- Help.TextTable textTable = Help.TextTable.forColumns(ansi(),
- new Help.Column(commandLength + 2, 2, Help.Column.Overflow.SPAN),
- new Help.Column(width() - (commandLength + 2), 2, Help.Column.Overflow.WRAP));
- for (Map.Entry<String, Help> entry : subcommands().entrySet()) {
- Help help = entry.getValue();
- UsageMessageSpec usage = help.commandSpec().usageMessage();
- String header = !empty(usage.header())
- ? usage.header()[0]
- : (!empty(usage.description()) ? usage.description()[0] : "");
- Text[] lines = ansi().text(format(header)).splitLines();
- for (int i = 0; i < lines.length; i++) {
- textTable.addRowValues(i == 0 ? help.commandNamesText(", ") : Ansi.EMPTY_TEXT, lines[i]);
- }
- }
- return textTable.toString();
- }
- private static int maxLength(Collection<String> any) {
- List<String> strings = new ArrayList<String>(any);
- Collections.sort(strings, Collections.reverseOrder(Help.shortestFirst()));
- return strings.get(0).length();
- }
- /** Returns a {@code Text} object containing the command name and all aliases, separated with the specified separator.
- * Command names will use the {@link ColorScheme#commandText(String) command style} for the color scheme of this Help.
- * @since 3.9 */
- public Text commandNamesText(String separator) {
- Text result = colorScheme().commandText(aliases().get(0));
- for (int i = 1; i < aliases().size(); i++) {
- result = result.concat(separator).concat(colorScheme().commandText(aliases().get(i)));
- }
- return result;
- }
- private static String join(String[] names, int offset, int length, String separator) {
- if (names == null) { return ""; }
- StringBuilder result = new StringBuilder();
- for (int i = offset; i < offset + length; i++) {
- result.append((i > offset) ? separator : "").append(names[i]);
- }
- return result.toString();
- }
- private static String stringOf(char chr, int length) {
- char[] buff = new char[length];
- Arrays.fill(buff, chr);
- return new String(buff);
- }
- /** Returns a {@code Layout} instance configured with the user preferences captured in this Help instance.
- * @return a Layout */
- public Layout createDefaultLayout() {
- return createLayout(Help.defaultOptionsColumnWidth);
- }
- private Layout createLayout(int longOptionsColumnWidth) {
- return new Layout(colorScheme, TextTable.forDefaultColumns(colorScheme.ansi(), longOptionsColumnWidth, width()), createDefaultOptionRenderer(), createDefaultParameterRenderer());
- }
- /** Returns a new default OptionRenderer which converts {@link OptionSpec Options} to five columns of text to match
- * the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
- * <ol>
- * <li>the required option marker</li>
- * <li>2-character short option name (or empty string if no short option exists)</li>
- * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
- * <li>comma-separated string with long option name(s)</li>
- * <li>first element of the {@link OptionSpec#description()} array</li>
- * </ol>
- * <p>Following this, there will be one row for each of the remaining elements of the {@link
- * OptionSpec#description()} array, and these rows look like {@code {"", "", "", "", option.description()[i]}}.</p>
- * <p>If configured, this option renderer adds an additional row to display the default field value.</p>
- * @return a new default OptionRenderer
- */
- public IOptionRenderer createDefaultOptionRenderer() {
- return new DefaultOptionRenderer(commandSpec.usageMessage.showDefaultValues(), "" +commandSpec.usageMessage().requiredOptionMarker());
- }
- /** Returns a new minimal OptionRenderer which converts {@link OptionSpec Options} to a single row with two columns
- * of text: an option name and a description. If multiple names or descriptions exist, the first value is used.
- * @return a new minimal OptionRenderer */
- public static IOptionRenderer createMinimalOptionRenderer() {
- return new MinimalOptionRenderer();
- }
- /** Returns a new default ParameterRenderer which converts {@linkplain PositionalParamSpec positional parameters} to four columns of
- * text to match the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
- * <ol>
- * <li>empty string </li>
- * <li>empty string </li>
- * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
- * <li>first element of the {@link PositionalParamSpec#description()} array</li>
- * </ol>
- * <p>Following this, there will be one row for each of the remaining elements of the {@link
- * PositionalParamSpec#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
- * <p>If configured, this parameter renderer adds an additional row to display the default field value.</p>
- * @return a new default ParameterRenderer
- */
- public IParameterRenderer createDefaultParameterRenderer() {
- return new DefaultParameterRenderer(commandSpec.usageMessage.showDefaultValues(), "" + commandSpec.usageMessage().requiredOptionMarker());
- }
- /** Returns a new minimal ParameterRenderer which converts {@linkplain PositionalParamSpec positional parameters}
- * to a single row with two columns of text: an option name and a description. If multiple descriptions exist, the first value is used.
- * @return a new minimal ParameterRenderer */
- public static IParameterRenderer createMinimalParameterRenderer() {
- return new MinimalParameterRenderer();
- }
- /** Returns a value renderer that returns the {@code paramLabel} if defined or the field name otherwise.
- * @return a new minimal ParamLabelRenderer */
- public static IParamLabelRenderer createMinimalParamLabelRenderer() {
- return new IParamLabelRenderer() {
- public Text renderParameterLabel(ArgSpec argSpec, Ansi ansi, List<IStyle> styles) {
- return ansi.apply(argSpec.paramLabel(), styles);
- }
- public String separator() { return ""; }
- };
- }
- /** Returns a new default param label renderer that separates option parameters from their option name
- * with the specified separator string, and, unless {@link ArgSpec#hideParamSyntax()} is true,
- * surrounds optional parameters with {@code '['} and {@code ']'}
- * characters and uses ellipses ("...") to indicate that any number of a parameter are allowed.
- * @return a new default ParamLabelRenderer
- */
- public IParamLabelRenderer createDefaultParamLabelRenderer() {
- return new DefaultParamLabelRenderer(commandSpec);
- }
- /** Sorts {@link OptionSpec OptionSpecs} by their option name in case-insensitive alphabetic order. If an
- * option has multiple names, the shortest name is used for the sorting. Help options follow non-help options.
- * @return a comparator that sorts OptionSpecs by their option name in case-insensitive alphabetic order */
- public static Comparator<OptionSpec> createShortOptionNameComparator() {
- return new SortByShortestOptionNameAlphabetically();
- }
- /** Sorts {@link OptionSpec OptionSpecs} by their option {@linkplain Range#max max arity} first, by
- * {@linkplain Range#min min arity} next, and by {@linkplain #createShortOptionNameComparator() option name} last.
- * @return a comparator that sorts OptionSpecs by arity first, then their option name */
- public static Comparator<OptionSpec> createShortOptionArityAndNameComparator() {
- return new SortByOptionArityAndNameAlphabetically();
- }
- /** Sorts short strings before longer strings.
- * @return a comparators that sorts short strings before longer strings */
- public static Comparator<String> shortestFirst() { return new ShortestFirst(); }
- /** Sorts {@link OptionSpec options} by their option {@linkplain IOrdered#order() order}, lowest first, highest last.
- * @return a comparator that sorts OptionSpecs by their order
- * @since 3.9*/
- static Comparator<OptionSpec> createOrderComparator() {
- return new SortByOrder<OptionSpec>();
- }
- /** Returns whether ANSI escape codes are enabled or not.
- * @return whether ANSI escape codes are enabled or not
- */
- public Ansi ansi() { return colorScheme.ansi; }
- /** Controls the visibility of certain aspects of the usage help message. */
- public enum Visibility { ALWAYS, NEVER, ON_DEMAND }
- /** When customizing online help for {@link OptionSpec Option} details, a custom {@code IOptionRenderer} can be
- * used to create textual representation of an Option in a tabular format: one or more rows, each containing
- * one or more columns. The {@link Layout Layout} is responsible for placing these text values in the
- * {@link TextTable TextTable}. */
- public interface IOptionRenderer {
- /**
- * Returns a text representation of the specified option and its parameter(s) if any.
- * @param option the command line option to show online usage help for
- * @param parameterLabelRenderer responsible for rendering option parameters to text
- * @param scheme color scheme for applying ansi color styles to options and option parameters
- * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
- * @since 3.0
- */
- Text[][] render(OptionSpec option, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
- }
- /** The DefaultOptionRenderer converts {@link OptionSpec Options} to five columns of text to match the default
- * {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
- * <ol>
- * <li>the required option marker (if the option is required)</li>
- * <li>2-character short option name (or empty string if no short option exists)</li>
- * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
- * <li>comma-separated string with long option name(s)</li>
- * <li>first element of the {@link OptionSpec#description()} array</li>
- * </ol>
- * <p>Following this, there will be one row for each of the remaining elements of the {@link
- * OptionSpec#description()} array, and these rows look like {@code {"", "", "", option.description()[i]}}.</p>
- */
- static class DefaultOptionRenderer implements IOptionRenderer {
- private String requiredMarker = " ";
- private boolean showDefaultValues;
- private String sep;
- public DefaultOptionRenderer(boolean showDefaultValues, String requiredMarker) {
- this.showDefaultValues = showDefaultValues;
- this.requiredMarker = Assert.notNull(requiredMarker, "requiredMarker");
- }
- public Text[][] render(OptionSpec option, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) {
- String[] names = ShortestFirst.sort(option.names());
- int shortOptionCount = names[0].length() == 2 ? 1 : 0;
- String shortOption = shortOptionCount > 0 ? names[0] : "";
- sep = shortOptionCount > 0 && names.length > 1 ? "," : "";
- String longOption = join(names, shortOptionCount, names.length - shortOptionCount, ", ");
- Text longOptionText = createLongOptionText(option, paramLabelRenderer, scheme, longOption);
- String requiredOption = option.required() ? requiredMarker : "";
- return renderDescriptionLines(option, scheme, requiredOption, shortOption, longOptionText);
- }
- private Text createLongOptionText(OptionSpec option, IParamLabelRenderer renderer, ColorScheme scheme, String longOption) {
- Text paramLabelText = renderer.renderParameterLabel(option, scheme.ansi(), scheme.optionParamStyles);
- // if no long option, fill in the space between the short option name and the param label value
- if (paramLabelText.length > 0 && longOption.length() == 0) {
- sep = renderer.separator();
- // #181 paramLabelText may be =LABEL or [=LABEL...]
- int sepStart = paramLabelText.plainString().indexOf(sep);
- Text prefix = paramLabelText.substring(0, sepStart);
- paramLabelText = prefix.concat(paramLabelText.substring(sepStart + sep.length()));
- }
- Text longOptionText = scheme.optionText(longOption);
- longOptionText = longOptionText.concat(paramLabelText);
- return longOptionText;
- }
- private Text[][] renderDescriptionLines(OptionSpec option,
- ColorScheme scheme,
- String requiredOption,
- String shortOption,
- Text longOptionText) {
- Text EMPTY = Ansi.EMPTY_TEXT;
- boolean[] showDefault = {option.internalShowDefaultValue(showDefaultValues)};
- List<Text[]> result = new ArrayList<Text[]>();
- String[] description = option.renderedDescription();
- Text[] descriptionFirstLines = createDescriptionFirstLines(scheme, option, description, showDefault);
- result.add(new Text[] { scheme.optionText(requiredOption), scheme.optionText(shortOption),
- scheme.ansi().new Text(sep), longOptionText, descriptionFirstLines[0] });
- for (int i = 1; i < descriptionFirstLines.length; i++) {
- result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] });
- }
- for (int i = 1; i < description.length; i++) {
- Text[] descriptionNextLines = scheme.ansi().new Text(description[i]).splitLines();
- for (Text line : descriptionNextLines) {
- result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line });
- }
- }
- if (showDefault[0]) { addTrailingDefaultLine(result, option, scheme); }
- return result.toArray(new Text[result.size()][]);
- }
- }
- /** The MinimalOptionRenderer converts {@link OptionSpec Options} to a single row with two columns of text: an
- * option name and a description. If multiple names or description lines exist, the first value is used. */
- static class MinimalOptionRenderer implements IOptionRenderer {
- public Text[][] render(OptionSpec option, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme) {
- Text optionText = scheme.optionText(option.names()[0]);
- Text paramLabelText = parameterLabelRenderer.renderParameterLabel(option, scheme.ansi(), scheme.optionParamStyles);
- optionText = optionText.concat(paramLabelText);
- return new Text[][] {{ optionText,
- scheme.ansi().new Text(option.description().length == 0 ? "" : option.description()[0]) }};
- }
- }
- /** The MinimalParameterRenderer converts {@linkplain PositionalParamSpec positional parameters} to a single row with two columns of
- * text: the parameters label and a description. If multiple description lines exist, the first value is used. */
- static class MinimalParameterRenderer implements IParameterRenderer {
- public Text[][] render(PositionalParamSpec param, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme) {
- return new Text[][] {{ parameterLabelRenderer.renderParameterLabel(param, scheme.ansi(), scheme.parameterStyles),
- scheme.ansi().new Text(param.description().length == 0 ? "" : param.description()[0]) }};
- }
- }
- /** When customizing online help for {@linkplain PositionalParamSpec positional parameters} details, a custom {@code IParameterRenderer}
- * can be used to create textual representation of a Parameters field in a tabular format: one or more rows,
- * each containing one or more columns. The {@link Layout Layout} is responsible for placing these text
- * values in the {@link TextTable TextTable}. */
- public interface IParameterRenderer {
- /**
- * Returns a text representation of the specified positional parameter.
- * @param param the positional parameter to show online usage help for
- * @param parameterLabelRenderer responsible for rendering parameter labels to text
- * @param scheme color scheme for applying ansi color styles to positional parameters
- * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
- * @since 3.0
- */
- Text[][] render(PositionalParamSpec param, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
- }
- /** The DefaultParameterRenderer converts {@linkplain PositionalParamSpec positional parameters} to five columns of text to match the
- * default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
- * <ol>
- * <li>the required option marker (if the parameter's arity is to have at least one value)</li>
- * <li>empty string </li>
- * <li>empty string </li>
- * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
- * <li>first element of the {@link PositionalParamSpec#description()} array</li>
- * </ol>
- * <p>Following this, there will be one row for each of the remaining elements of the {@link
- * PositionalParamSpec#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
- */
- static class DefaultParameterRenderer implements IParameterRenderer {
- private String requiredMarker = " ";
- private boolean showDefaultValues;
- public DefaultParameterRenderer(boolean showDefaultValues, String requiredMarker) {
- this.showDefaultValues = showDefaultValues;
- this.requiredMarker = Assert.notNull(requiredMarker, "requiredMarker");
- }
- public Text[][] render(PositionalParamSpec param, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) {
- Text label = paramLabelRenderer.renderParameterLabel(param, scheme.ansi(), scheme.parameterStyles);
- Text requiredParameter = scheme.parameterText(param.arity().min > 0 ? requiredMarker : "");
- Text EMPTY = Ansi.EMPTY_TEXT;
- boolean[] showDefault = {param.internalShowDefaultValue(showDefaultValues)};
- List<Text[]> result = new ArrayList<Text[]>();
- String[] description = param.renderedDescription();
- Text[] descriptionFirstLines = createDescriptionFirstLines(scheme, param, description, showDefault);
- result.add(new Text[] { requiredParameter, EMPTY, EMPTY, label, descriptionFirstLines[0] });
- for (int i = 1; i < descriptionFirstLines.length; i++) {
- result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] });
- }
- for (int i = 1; i < description.length; i++) {
- Text[] descriptionNextLines = scheme.ansi().new Text(description[i]).splitLines();
- for (Text line : descriptionNextLines) {
- result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line });
- }
- }
- if (showDefault[0]) { addTrailingDefaultLine(result, param, scheme); }
- return result.toArray(new Text[result.size()][]);
- }
- }
- private static void addTrailingDefaultLine(List<Text[]> result, ArgSpec arg, ColorScheme scheme) {
- Text EMPTY = Ansi.EMPTY_TEXT;
- result.add(new Text[]{EMPTY, EMPTY, EMPTY, EMPTY, scheme.ansi().new Text(" Default: " + arg.defaultValueString())});
- }
- private static Text[] createDescriptionFirstLines(ColorScheme scheme, ArgSpec arg, String[] description, boolean[] showDefault) {
- Text[] result = scheme.ansi().new Text(str(description, 0)).splitLines();
- if (result.length == 0 || (result.length == 1 && result[0].plain.length() == 0)) {
- if (showDefault[0]) {
- result = new Text[]{scheme.ansi().new Text(" Default: " + arg.defaultValueString())};
- showDefault[0] = false; // don't show the default value twice
- } else {
- result = new Text[]{ Ansi.EMPTY_TEXT };
- }
- }
- return result;
- }
- /** When customizing online usage help for an option parameter or a positional parameter, a custom
- * {@code IParamLabelRenderer} can be used to render the parameter name or label to a String. */
- public interface IParamLabelRenderer {
- /** Returns a text rendering of the option parameter or positional parameter; returns an empty string
- * {@code ""} if the option is a boolean and does not take a parameter.
- * @param argSpec the named or positional parameter with a parameter label
- * @param ansi determines whether ANSI escape codes should be emitted or not
- * @param styles the styles to apply to the parameter label
- * @return a text rendering of the Option parameter or positional parameter
- * @since 3.0 */
- Text renderParameterLabel(ArgSpec argSpec, Ansi ansi, List<IStyle> styles);
- /** Returns the separator between option name and param label.
- * @return the separator between option name and param label */
- String separator();
- }
- /**
- * DefaultParamLabelRenderer separates option parameters from their {@linkplain OptionSpec option names} with a
- * {@linkplain CommandLine.Model.ParserSpec#separator() separator} string, and, unless
- * {@link ArgSpec#hideParamSyntax()} is true, surrounds optional values with {@code '['} and {@code ']'} characters
- * and uses ellipses ("...") to indicate that any number of values is allowed for options or parameters with variable arity.
- */
- static class DefaultParamLabelRenderer implements IParamLabelRenderer {
- private final CommandSpec commandSpec;
- /** Constructs a new DefaultParamLabelRenderer with the specified separator string. */
- public DefaultParamLabelRenderer(CommandSpec commandSpec) {
- this.commandSpec = Assert.notNull(commandSpec, "commandSpec");
- }
- public String separator() { return commandSpec.parser().separator(); }
- public Text renderParameterLabel(ArgSpec argSpec, Ansi ansi, List<IStyle> styles) {
- Range capacity = argSpec.isOption() ? argSpec.arity() : ((PositionalParamSpec)argSpec).capacity();
- if (capacity.max == 0) { return ansi.new Text(""); }
- if (argSpec.hideParamSyntax()) { return ansi.apply((argSpec.isOption() ? separator() : "") + argSpec.paramLabel(), styles); }
- Text paramName = ansi.apply(argSpec.paramLabel(), styles);
- String split = argSpec.splitRegex();
- String mandatorySep = empty(split) ? " " : split;
- String optionalSep = empty(split) ? " [" : "[" + split;
- boolean unlimitedSplit = !empty(split) && !commandSpec.parser().limitSplit();
- boolean limitedSplit = !empty(split) && commandSpec.parser().limitSplit();
- Text repeating = paramName;
- int paramCount = 1;
- if (unlimitedSplit) {
- repeating = paramName.concat("[" + split).concat(paramName).concat("...]");
- paramCount++;
- mandatorySep = " ";
- optionalSep = " [";
- }
- Text result = repeating;
- int done = 1;
- for (; done < capacity.min; done++) {
- result = result.concat(mandatorySep).concat(repeating); // " PARAM" or ",PARAM"
- paramCount += paramCount;
- }
- if (!capacity.isVariable) {
- for (int i = done; i < capacity.max; i++) {
- result = result.concat(optionalSep).concat(paramName); // " [PARAM" or "[,PARAM"
- paramCount++;
- }
- for (int i = done; i < capacity.max; i++) {
- result = result.concat("]");
- }
- }
- // show an extra trailing "[,PARAM]" if split and either max=* or splitting is not restricted to max
- boolean effectivelyVariable = capacity.isVariable || (limitedSplit && paramCount == 1);
- if (limitedSplit && effectivelyVariable && paramCount == 1) {
- result = result.concat(optionalSep).concat(repeating).concat("]"); // PARAM[,PARAM]...
- }
- if (effectivelyVariable) {
- if (!argSpec.arity().isVariable && argSpec.arity().min > 1) {
- result = ansi.new Text("(").concat(result).concat(")"); // repeating group
- }
- result = result.concat("..."); // PARAM...
- }
- String optionSeparator = argSpec.isOption() ? separator() : "";
- if (capacity.min == 0) { // optional
- String sep2 = empty(optionSeparator.trim()) ? optionSeparator + "[" : "[" + optionSeparator;
- result = ansi.new Text(sep2).concat(result).concat("]");
- } else {
- result = ansi.new Text(optionSeparator).concat(result);
- }
- return result;
- }
- }
- /** Use a Layout to format usage help text for options and parameters in tabular format.
- * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
- * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
- * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
- * <p>Customize by overriding the {@link #layout(CommandLine.Model.ArgSpec, CommandLine.Help.Ansi.Text[][])} method.</p>
- * @see IOptionRenderer rendering options to text
- * @see IParameterRenderer rendering parameters to text
- * @see TextTable showing values in a tabular format
- */
- public static class Layout {
- protected final ColorScheme colorScheme;
- protected final TextTable table;
- protected IOptionRenderer optionRenderer;
- protected IParameterRenderer parameterRenderer;
- /** Constructs a Layout with the specified color scheme, a new default TextTable, the
- * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
- * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
- * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message */
- public Layout(ColorScheme colorScheme, int tableWidth) { this(colorScheme, TextTable.forDefaultColumns(colorScheme.ansi(), tableWidth)); }
- /** Constructs a Layout with the specified color scheme, the specified TextTable, the
- * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
- * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
- * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
- * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
- public Layout(ColorScheme colorScheme, TextTable textTable) {
- this(colorScheme, textTable, new DefaultOptionRenderer(false, " "), new DefaultParameterRenderer(false, " "));
- }
- /** Constructs a Layout with the specified color scheme, the specified TextTable, the
- * specified option renderer and the specified parameter renderer.
- * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
- * @param optionRenderer the object responsible for rendering Options to Text
- * @param parameterRenderer the object responsible for rendering Parameters to Text
- * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
- public Layout(ColorScheme colorScheme, TextTable textTable, IOptionRenderer optionRenderer, IParameterRenderer parameterRenderer) {
- this.colorScheme = Assert.notNull(colorScheme, "colorScheme");
- this.table = Assert.notNull(textTable, "textTable");
- this.optionRenderer = Assert.notNull(optionRenderer, "optionRenderer");
- this.parameterRenderer = Assert.notNull(parameterRenderer, "parameterRenderer");
- }
- /**
- * Copies the specified text values into the correct cells in the {@link TextTable}. This implementation
- * delegates to {@link TextTable#addRowValues(CommandLine.Help.Ansi.Text...)} for each row of values.
- * <p>Subclasses may override.</p>
- * @param argSpec the Option or Parameters
- * @param cellValues the text values representing the Option/Parameters, to be displayed in tabular form
- * @since 3.0 */
- public void layout(ArgSpec argSpec, Text[][] cellValues) {
- for (Text[] oneRow : cellValues) {
- table.addRowValues(oneRow);
- }
- }
- /** Calls {@link #addOption(CommandLine.Model.OptionSpec, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Options in the list.
- * @param options options to add usage descriptions for
- * @param paramLabelRenderer object that knows how to render option parameters
- * @since 3.0 */
- public void addOptions(List<OptionSpec> options, IParamLabelRenderer paramLabelRenderer) {
- for (OptionSpec option : options) {
- if (!option.hidden()) {
- addOption(option, paramLabelRenderer);
- }
- }
- }
- /**
- * Delegates to the {@link #optionRenderer option renderer} of this layout to obtain
- * text values for the specified {@link OptionSpec}, and then calls the {@link #layout(CommandLine.Model.ArgSpec, CommandLine.Help.Ansi.Text[][])}
- * method to write these text values into the correct cells in the TextTable.
- * @param option the option argument
- * @param paramLabelRenderer knows how to render option parameters
- * @since 3.0 */
- public void addOption(OptionSpec option, IParamLabelRenderer paramLabelRenderer) {
- Text[][] values = optionRenderer.render(option, paramLabelRenderer, colorScheme);
- layout(option, values);
- }
- /** Calls {@link #addPositionalParameter(CommandLine.Model.PositionalParamSpec, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Parameters in the list.
- * @param params positional parameters to add usage descriptions for
- * @param paramLabelRenderer knows how to render option parameters
- * @since 3.0 */
- public void addPositionalParameters(List<PositionalParamSpec> params, IParamLabelRenderer paramLabelRenderer) {
- for (PositionalParamSpec param : params) {
- if (!param.hidden()) {
- addPositionalParameter(param, paramLabelRenderer);
- }
- }
- }
- /**
- * Delegates to the {@link #parameterRenderer parameter renderer} of this layout
- * to obtain text values for the specified {@linkplain PositionalParamSpec positional parameter}, and then calls
- * {@link #layout(CommandLine.Model.ArgSpec, CommandLine.Help.Ansi.Text[][])} to write these text values into the correct cells in the TextTable.
- * @param param the positional parameter
- * @param paramLabelRenderer knows how to render option parameters
- * @since 3.0 */
- public void addPositionalParameter(PositionalParamSpec param, IParamLabelRenderer paramLabelRenderer) {
- Text[][] values = parameterRenderer.render(param, paramLabelRenderer, colorScheme);
- layout(param, values);
- }
- /** Returns the section of the usage help message accumulated in the TextTable owned by this layout. */
- @Override public String toString() { return table.toString(); }
- }
- /** Sorts short strings before longer strings. */
- static class ShortestFirst implements Comparator<String> {
- public int compare(String o1, String o2) {
- return o1.length() - o2.length();
- }
- /** Sorts the specified array of Strings shortest-first and returns it. */
- public static String[] sort(String[] names) {
- Arrays.sort(names, new ShortestFirst());
- return names;
- }
- /** Sorts the specified array of Strings longest-first and returns it. */
- public static String[] longestFirst(String[] names) {
- Arrays.sort(names, Collections.reverseOrder(new ShortestFirst()));
- return names;
- }
- }
- /** Sorts {@code OptionSpec} instances by their name in case-insensitive alphabetic order. If an option has
- * multiple names, the shortest name is used for the sorting. Help options follow non-help options. */
- static class SortByShortestOptionNameAlphabetically implements Comparator<OptionSpec> {
- public int compare(OptionSpec o1, OptionSpec o2) {
- if (o1 == null) { return 1; } else if (o2 == null) { return -1; } // options before params
- String[] names1 = ShortestFirst.sort(o1.names());
- String[] names2 = ShortestFirst.sort(o2.names());
- int result = names1[0].toUpperCase().compareTo(names2[0].toUpperCase()); // case insensitive sort
- result = result == 0 ? -names1[0].compareTo(names2[0]) : result; // lower case before upper case
- return o1.help() == o2.help() ? result : o2.help() ? -1 : 1; // help options come last
- }
- }
- /** Sorts {@code OptionSpec} instances by their max arity first, then their min arity, then delegates to super class. */
- static class SortByOptionArityAndNameAlphabetically extends SortByShortestOptionNameAlphabetically {
- public int compare(OptionSpec o1, OptionSpec o2) {
- Range arity1 = o1.arity();
- Range arity2 = o2.arity();
- int result = arity1.max - arity2.max;
- if (result == 0) {
- result = arity1.min - arity2.min;
- }
- if (result == 0) { // arity is same
- if (o1.isMultiValue() && !o2.isMultiValue()) { result = 1; } // f1 > f2
- if (!o1.isMultiValue() && o2.isMultiValue()) { result = -1; } // f1 < f2
- }
- return result == 0 ? super.compare(o1, o2) : result;
- }
- }
- static class SortByOrder<T extends IOrdered> implements Comparator<T> {
- public int compare(T o1, T o2) {
- return Integer.signum(o1.order() - o2.order());
- }
- }
- /**
- * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
- * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
- * longer than the column's width.</p>
- */
- public static class TextTable {
- /**
- * Helper class to index positions in a {@code Help.TextTable}.
- * @since 2.0
- */
- public static class Cell {
- /** Table column index (zero based). */
- public final int column;
- /** Table row index (zero based). */
- public final int row;
- /** Constructs a new Cell with the specified coordinates in the table.
- * @param column the zero-based table column
- * @param row the zero-based table row */
- public Cell(int column, int row) { this.column = column; this.row = row; }
- }
- private static final int OPTION_SEPARATOR_COLUMN = 2;
- private static final int LONG_OPTION_COLUMN = 3;
- /** The column definitions of this table. */
- private final Column[] columns;
- /** The {@code char[]} slots of the {@code TextTable} to copy text values into. */
- protected final List<Text> columnValues = new ArrayList<Text>();
- /** By default, indent wrapped lines by 2 spaces. */
- public int indentWrappedLines = 2;
- private final Ansi ansi;
- private final int tableWidth;
- /** Constructs a TextTable with five columns as follows:
- * <ol>
- * <li>required option/parameter marker (width: 2, indent: 0, TRUNCATE on overflow)</li>
- * <li>short option name (width: 2, indent: 0, TRUNCATE on overflow)</li>
- * <li>comma separator (width: 1, indent: 0, TRUNCATE on overflow)</li>
- * <li>long option name(s) (width: 24, indent: 1, SPAN multiple columns on overflow)</li>
- * <li>description line(s) (width: 51, indent: 1, WRAP to next row on overflow)</li>
- * </ol>
- * @param ansi whether to emit ANSI escape codes or not
- * @param usageHelpWidth the total width of the columns combined
- */
- public static TextTable forDefaultColumns(Ansi ansi, int usageHelpWidth) {
- return forDefaultColumns(ansi, defaultOptionsColumnWidth, usageHelpWidth);
- }
- /** Constructs a TextTable with five columns as follows:
- * <ol>
- * <li>required option/parameter marker (width: 2, indent: 0, TRUNCATE on overflow)</li>
- * <li>short option name (width: 2, indent: 0, TRUNCATE on overflow)</li>
- * <li>comma separator (width: 1, indent: 0, TRUNCATE on overflow)</li>
- * <li>long option name(s) (width: 24, indent: 1, SPAN multiple columns on overflow)</li>
- * <li>description line(s) (width: 51, indent: 1, WRAP to next row on overflow)</li>
- * </ol>
- * @param ansi whether to emit ANSI escape codes or not
- * @param longOptionsColumnWidth the width of the long options column
- * @param usageHelpWidth the total width of the columns combined
- */
- public static TextTable forDefaultColumns(Ansi ansi, int longOptionsColumnWidth, int usageHelpWidth) {
- // "* -c, --create Creates a ...."
- return forColumns(ansi,
- new Column(2, 0, TRUNCATE), // "*"
- new Column(2, 0, TRUNCATE), // "-c"
- new Column(1, 0, TRUNCATE), // ","
- new Column(longOptionsColumnWidth, 1, SPAN), // " --create"
- new Column(usageHelpWidth - longOptionsColumnWidth, 1, WRAP)); // " Creates a ..."
- }
- /** Constructs a new TextTable with columns with the specified width, all SPANning multiple columns on
- * overflow except the last column which WRAPS to the next row.
- * @param ansi whether to emit ANSI escape codes or not
- * @param columnWidths the width of each table column (all columns have zero indent)
- */
- public static TextTable forColumnWidths(Ansi ansi, int... columnWidths) {
- Column[] columns = new Column[columnWidths.length];
- for (int i = 0; i < columnWidths.length; i++) {
- columns[i] = new Column(columnWidths[i], 0, i == columnWidths.length - 1 ? WRAP : SPAN);
- }
- return new TextTable(ansi, columns);
- }
- /** Constructs a {@code TextTable} with the specified columns.
- * @param ansi whether to emit ANSI escape codes or not
- * @param columns columns to construct this TextTable with */
- public static TextTable forColumns(Ansi ansi, Column... columns) { return new TextTable(ansi, columns); }
- protected TextTable(Ansi ansi, Column[] columns) {
- this.ansi = Assert.notNull(ansi, "ansi");
- this.columns = Assert.notNull(columns, "columns").clone();
- if (columns.length == 0) { throw new IllegalArgumentException("At least one column is required"); }
- int totalWidth = 0;
- for (Column col : columns) { totalWidth += col.width; }
- tableWidth = totalWidth;
- }
- /** The column definitions of this table. */
- public Column[] columns() { return columns.clone(); }
- /** Returns the {@code Text} slot at the specified row and column to write a text value into.
- * @param row the row of the cell whose Text to return
- * @param col the column of the cell whose Text to return
- * @return the Text object at the specified row and column
- * @since 2.0 */
- public Text textAt(int row, int col) { return columnValues.get(col + (row * columns.length)); }
- /** Returns the {@code Text} slot at the specified row and column to write a text value into.
- * @param row the row of the cell whose Text to return
- * @param col the column of the cell whose Text to return
- * @return the Text object at the specified row and column
- * @deprecated use {@link #textAt(int, int)} instead */
- @Deprecated public Text cellAt(int row, int col) { return textAt(row, col); }
- /** Returns the current number of rows of this {@code TextTable}.
- * @return the current number of rows in this TextTable */
- public int rowCount() { return columnValues.size() / columns.length; }
- /** Adds the required {@code char[]} slots for a new row to the {@link #columnValues} field. */
- public void addEmptyRow() {
- for (int i = 0; i < columns.length; i++) {
- columnValues.add(ansi.new Text(columns[i].width));
- }
- }
- /** Delegates to {@link #addRowValues(CommandLine.Help.Ansi.Text...)}.
- * @param values the text values to display in each column of the current row */
- public void addRowValues(String... values) {
- Text[] array = new Text[values.length];
- for (int i = 0; i < array.length; i++) {
- array[i] = values[i] == null ? Ansi.EMPTY_TEXT : ansi.new Text(values[i]);
- }
- addRowValues(array);
- }
- /**
- * Adds a new {@linkplain TextTable#addEmptyRow() empty row}, then calls {@link
- * TextTable#putValue(int, int, CommandLine.Help.Ansi.Text) putValue} for each of the specified values, adding more empty rows
- * if the return value indicates that the value spanned multiple columns or was wrapped to multiple rows.
- * @param values the values to write into a new row in this TextTable
- * @throws IllegalArgumentException if the number of values exceeds the number of Columns in this table
- */
- public void addRowValues(Text... values) {
- if (values.length > columns.length) {
- throw new IllegalArgumentException(values.length + " values don't fit in " +
- columns.length + " columns");
- }
- addEmptyRow();
- int oldIndent = unindent(values);
- for (int col = 0; col < values.length; col++) {
- int row = rowCount() - 1;// write to last row: previous value may have wrapped to next row
- Cell cell = putValue(row, col, values[col]);
- // add row if a value spanned/wrapped and there are still remaining values
- if ((cell.row != row || cell.column != col) && col != values.length - 1) {
- addEmptyRow();
- }
- }
- reindent(oldIndent);
- }
- private int unindent(Text[] values) {
- if (columns.length <= LONG_OPTION_COLUMN) { return 0; }
- int oldIndent = columns[LONG_OPTION_COLUMN].indent;
- if ("=".equals(values[OPTION_SEPARATOR_COLUMN].toString())) {
- columns[LONG_OPTION_COLUMN].indent = 0;
- }
- return oldIndent;
- }
- private void reindent(int oldIndent) {
- if (columns.length <= LONG_OPTION_COLUMN) { return; }
- columns[LONG_OPTION_COLUMN].indent = oldIndent;
- }
- /**
- * Writes the specified value into the cell at the specified row and column and returns the last row and
- * column written to. Depending on the Column's {@link Column#overflow Overflow} policy, the value may span
- * multiple columns or wrap to multiple rows when larger than the column width.
- * @param row the target row in the table
- * @param col the target column in the table to write to
- * @param value the value to write
- * @return a Cell indicating the position in the table that was last written to (since 2.0)
- * @throws IllegalArgumentException if the specified row exceeds the table's {@linkplain
- * TextTable#rowCount() row count}
- * @since 2.0 (previous versions returned a {@code java.awt.Point} object)
- */
- public Cell putValue(int row, int col, Text value) {
- if (row > rowCount() - 1) {
- throw new IllegalArgumentException("Cannot write to row " + row + ": rowCount=" + rowCount());
- }
- if (value == null || value.plain.length() == 0) { return new Cell(col, row); }
- Column column = columns[col];
- int indent = column.indent;
- switch (column.overflow) {
- case TRUNCATE:
- copy(value, textAt(row, col), indent);
- return new Cell(col, row);
- case SPAN:
- int startColumn = col;
- do {
- boolean lastColumn = col == columns.length - 1;
- int charsWritten = lastColumn
- ? copy(BreakIterator.getLineInstance(), value, textAt(row, col), indent)
- : copy(value, textAt(row, col), indent);
- value = value.substring(charsWritten);
- indent = 0;
- if (value.length > 0) { // value did not fit in column
- ++col; // write remainder of value in next column
- }
- if (value.length > 0 && col >= columns.length) { // we filled up all columns on this row
- addEmptyRow();
- row++;
- col = startColumn;
- indent = column.indent + indentWrappedLines;
- }
- } while (value.length > 0);
- return new Cell(col, row);
- case WRAP:
- BreakIterator lineBreakIterator = BreakIterator.getLineInstance();
- do {
- int charsWritten = copy(lineBreakIterator, value, textAt(row, col), indent);
- value = value.substring(charsWritten);
- indent = column.indent + indentWrappedLines;
- if (value.length > 0) { // value did not fit in column
- ++row; // write remainder of value in next row
- addEmptyRow();
- }
- } while (value.length > 0);
- return new Cell(col, row);
- }
- throw new IllegalStateException(column.overflow.toString());
- }
- private static int length(Text str) {
- return str.length; // TODO count some characters as double length
- }
- private int copy(BreakIterator line, Text text, Text columnValue, int offset) {
- // Deceive the BreakIterator to ensure no line breaks after '-' character
- line.setText(text.plainString().replace("-", "\u00ff"));
- int done = 0;
- for (int start = line.first(), end = line.next(); end != BreakIterator.DONE; start = end, end = line.next()) {
- Text word = text.substring(start, end); //.replace("\u00ff", "-"); // not needed
- if (columnValue.maxLength >= offset + done + length(word)) {
- done += copy(word, columnValue, offset + done); // TODO messages length
- } else {
- break;
- }
- }
- if (done == 0 && length(text) + offset > columnValue.maxLength) {
- // The value is a single word that is too big to be written to the column. Write as much as we can.
- done = copy(text, columnValue, offset);
- }
- return done;
- }
- private static int copy(Text value, Text destination, int offset) {
- int length = Math.min(value.length, destination.maxLength - offset);
- value.getStyledChars(value.from, length, destination, offset);
- return length;
- }
- /** Copies the text representation that we built up from the options into the specified StringBuilder.
- * @param text the StringBuilder to write into
- * @return the specified StringBuilder object (to allow method chaining and a more fluid API) */
- public StringBuilder toString(StringBuilder text) {
- int columnCount = this.columns.length;
- StringBuilder row = new StringBuilder(tableWidth);
- for (int i = 0; i < columnValues.size(); i++) {
- Text column = columnValues.get(i);
- row.append(column.toString());
- row.append(new String(spaces(columns[i % columnCount].width - column.length)));
- if (i % columnCount == columnCount - 1) {
- int lastChar = row.length() - 1;
- while (lastChar >= 0 && row.charAt(lastChar) == ' ') {lastChar--;} // rtrim
- row.setLength(lastChar + 1);
- text.append(row.toString()).append(System.getProperty("line.separator"));
- row.setLength(0);
- }
- }
- return text;
- }
- public String toString() { return toString(new StringBuilder()).toString(); }
- }
- /** Columns define the width, indent (leading number of spaces in a column before the value) and
- * {@linkplain Overflow Overflow} policy of a column in a {@linkplain TextTable TextTable}. */
- public static class Column {
- /** Policy for handling text that is longer than the column width:
- * span multiple columns, wrap to the next row, or simply truncate the portion that doesn't fit. */
- public enum Overflow { TRUNCATE, SPAN, WRAP }
- /** Column width in characters */
- public final int width;
- /** Indent (number of empty spaces at the start of the column preceding the text value) */
- public int indent;
- /** Policy that determines how to handle values larger than the column width. */
- public final Overflow overflow;
- public Column(int width, int indent, Overflow overflow) {
- this.width = width;
- this.indent = indent;
- this.overflow = Assert.notNull(overflow, "overflow");
- }
- }
- /** All usage help message are generated with a color scheme that assigns certain styles and colors to common
- * parts of a usage message: the command name, options, positional parameters and option parameters.
- * Users may customize these styles by creating Help with a custom color scheme.
- * <p>Note that these options and styles may not be rendered if ANSI escape codes are not
- * {@linkplain Ansi#enabled() enabled}.</p>
- * @see Help#defaultColorScheme(Ansi)
- */
- public static class ColorScheme {
- public final List<IStyle> commandStyles = new ArrayList<IStyle>();
- public final List<IStyle> optionStyles = new ArrayList<IStyle>();
- public final List<IStyle> parameterStyles = new ArrayList<IStyle>();
- public final List<IStyle> optionParamStyles = new ArrayList<IStyle>();
- private final Ansi ansi;
- /** Constructs a new empty ColorScheme with {@link Help.Ansi#AUTO}. */
- public ColorScheme() { this(Ansi.AUTO); }
- /** Constructs a new empty ColorScheme with the specified Ansi enabled mode.
- * @see Help#defaultColorScheme(Ansi)
- * @param ansi whether to emit ANSI escape codes or not
- */
- public ColorScheme(Ansi ansi) {this.ansi = Assert.notNull(ansi, "ansi"); }
- /** Adds the specified styles to the registered styles for commands in this color scheme and returns this color scheme.
- * @param styles the styles to add to the registered styles for commands in this color scheme
- * @return this color scheme to enable method chaining for a more fluent API */
- public ColorScheme commands(IStyle... styles) { return addAll(commandStyles, styles); }
- /** Adds the specified styles to the registered styles for options in this color scheme and returns this color scheme.
- * @param styles the styles to add to registered the styles for options in this color scheme
- * @return this color scheme to enable method chaining for a more fluent API */
- public ColorScheme options(IStyle... styles) { return addAll(optionStyles, styles);}
- /** Adds the specified styles to the registered styles for positional parameters in this color scheme and returns this color scheme.
- * @param styles the styles to add to registered the styles for parameters in this color scheme
- * @return this color scheme to enable method chaining for a more fluent API */
- public ColorScheme parameters(IStyle... styles) { return addAll(parameterStyles, styles);}
- /** Adds the specified styles to the registered styles for option parameters in this color scheme and returns this color scheme.
- * @param styles the styles to add to the registered styles for option parameters in this color scheme
- * @return this color scheme to enable method chaining for a more fluent API */
- public ColorScheme optionParams(IStyle... styles) { return addAll(optionParamStyles, styles);}
- /** Returns a Text with all command styles applied to the specified command string.
- * @param command the command string to apply the registered command styles to
- * @return a Text with all command styles applied to the specified command string */
- public Ansi.Text commandText(String command) { return ansi().apply(command, commandStyles); }
- /** Returns a Text with all option styles applied to the specified option string.
- * @param option the option string to apply the registered option styles to
- * @return a Text with all option styles applied to the specified option string */
- public Ansi.Text optionText(String option) { return ansi().apply(option, optionStyles); }
- /** Returns a Text with all parameter styles applied to the specified parameter string.
- * @param parameter the parameter string to apply the registered parameter styles to
- * @return a Text with all parameter styles applied to the specified parameter string */
- public Ansi.Text parameterText(String parameter) { return ansi().apply(parameter, parameterStyles); }
- /** Returns a Text with all optionParam styles applied to the specified optionParam string.
- * @param optionParam the option parameter string to apply the registered option parameter styles to
- * @return a Text with all option parameter styles applied to the specified option parameter string */
- public Ansi.Text optionParamText(String optionParam) { return ansi().apply(optionParam, optionParamStyles); }
- /** Replaces colors and styles in this scheme with ones specified in system properties, and returns this scheme.
- * Supported property names:<ul>
- * <li>{@code picocli.color.commands}</li>
- * <li>{@code picocli.color.options}</li>
- * <li>{@code picocli.color.parameters}</li>
- * <li>{@code picocli.color.optionParams}</li>
- * </ul><p>Property values can be anything that {@link Help.Ansi.Style#parse(String)} can handle.</p>
- * @return this ColorScheme
- */
- public ColorScheme applySystemProperties() {
- replace(commandStyles, System.getProperty("picocli.color.commands"));
- replace(optionStyles, System.getProperty("picocli.color.options"));
- replace(parameterStyles, System.getProperty("picocli.color.parameters"));
- replace(optionParamStyles, System.getProperty("picocli.color.optionParams"));
- return this;
- }
- private void replace(List<IStyle> styles, String property) {
- if (property != null) {
- styles.clear();
- addAll(styles, Style.parse(property));
- }
- }
- private ColorScheme addAll(List<IStyle> styles, IStyle... add) {
- styles.addAll(Arrays.asList(add));
- return this;
- }
- public Ansi ansi() { return ansi; }
- }
- /** Creates and returns a new {@link ColorScheme} initialized with picocli default values: commands are bold,
- * options and parameters use a yellow foreground, and option parameters use italic.
- * @param ansi whether the usage help message should contain ANSI escape codes or not
- * @return a new default color scheme
- */
- public static ColorScheme defaultColorScheme(Ansi ansi) {
- return new ColorScheme(ansi)
- .commands(Style.bold)
- .options(Style.fg_yellow)
- .parameters(Style.fg_yellow)
- .optionParams(Style.italic);
- }
- /** Provides methods and inner classes to support using ANSI escape codes in usage help messages. */
- public enum Ansi {
- /** Only emit ANSI escape codes if the platform supports it and system property {@code "picocli.ansi"}
- * is not set to any value other than {@code "true"} (case insensitive). */
- AUTO,
- /** Forced ON: always emit ANSI escape code regardless of the platform. */
- ON,
- /** Forced OFF: never emit ANSI escape code regardless of the platform. */
- OFF;
- static Text EMPTY_TEXT = OFF.new Text(0);
- static Boolean tty;
- static boolean isTTY() {
- if (tty == null) { tty = calcTTY(); }
- return tty;
- }
- static final boolean isWindows() { return System.getProperty("os.name").startsWith("Windows"); }
- static final boolean isXterm() { return System.getenv("TERM") != null && System.getenv("TERM").startsWith("xterm"); }
- // null on Windows unless on Cygwin or MSYS
- static final boolean hasOsType() { return System.getenv("OSTYPE") != null; }
- // see Jan Niklas Hasse's https://bixense.com/clicolors/ proposal
- // https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable
- static final boolean hintDisabled() { return "0".equals(System.getenv("CLICOLOR"))
- || "OFF".equals(System.getenv("ConEmuANSI")); }
- /** https://github.com/adoxa/ansicon/blob/master/readme.txt,
- * Jan Niklas Hasse's https://bixense.com/clicolors/ proposal,
- * https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable */
- static final boolean hintEnabled() { return System.getenv("ANSICON") != null
- || "1".equals(System.getenv("CLICOLOR"))
- || "ON".equals(System.getenv("ConEmuANSI")); }
- /** https://no-color.org/ */
- static final boolean forceDisabled() { return System.getenv("NO_COLOR") != null; }
- /** Jan Niklas Hasse's https://bixense.com/clicolors/ proposal */
- static final boolean forceEnabled() { return System.getenv("CLICOLOR_FORCE") != null
- && !"0".equals(System.getenv("CLICOLOR_FORCE"));}
- /** http://stackoverflow.com/questions/1403772/how-can-i-check-if-a-java-programs-input-output-streams-are-connected-to-a-term */
- static boolean calcTTY() {
- try { return System.class.getDeclaredMethod("console").invoke(null) != null; }
- catch (Throwable reflectionFailed) { return true; }
- }
- /** Cygwin and MSYS use pseudo-tty and console is always null... */
- static boolean isPseudoTTY() { return isWindows() && (isXterm() || hasOsType()); }
- static boolean ansiPossible() {
- if (forceDisabled()) { return false; }
- if (forceEnabled()) { return true; }
- if (isWindows() && isJansiConsoleInstalled()) { return true; } // #630 JVM crash loading jansi.AnsiConsole on Linux
- if (hintDisabled()) { return false; }
- if (!isTTY() && !isPseudoTTY()) { return false; }
- return hintEnabled() || !isWindows() || isXterm() || hasOsType();
- }
- static boolean isJansiConsoleInstalled() {
- try {
- Class<?> ansiConsole = Class.forName("org.fusesource.jansi.AnsiConsole");
- Field out = ansiConsole.getField("out");
- return out.get(null) == System.out;
- } catch (Exception reflectionFailed) {
- return false;
- }
- }
- /** Returns {@code true} if ANSI escape codes should be emitted, {@code false} otherwise.
- * @return ON: {@code true}, OFF: {@code false}, AUTO: if system property {@code "picocli.ansi"} is
- * defined then return its boolean value, otherwise return whether the platform supports ANSI escape codes */
- public boolean enabled() {
- if (this == ON) { return true; }
- if (this == OFF) { return false; }
- String ansi = System.getProperty("picocli.ansi");
- boolean auto = ansi == null || "AUTO".equalsIgnoreCase(ansi);
- return auto ? ansiPossible() : Boolean.getBoolean("picocli.ansi");
- }
- /**
- * Returns a new Text object for this Ansi mode, encapsulating the specified string
- * which may contain markup like {@code @|bg(red),white,underline some text|@}.
- * <p>
- * Calling {@code toString()} on the returned Text will either include ANSI escape codes
- * (if this Ansi mode is ON), or suppress ANSI escape codes (if this Ansi mode is OFF).
- * <p>
- * Equivalent to {@code this.new Text(stringWithMarkup)}.
- * @since 3.4 */
- public Text text(String stringWithMarkup) { return this.new Text(stringWithMarkup); }
- /**
- * Returns a String where any markup like
- * {@code @|bg(red),white,underline some text|@} is converted to ANSI escape codes
- * if this Ansi is ON, or suppressed if this Ansi is OFF.
- * <p>
- * Equivalent to {@code this.new Text(stringWithMarkup).toString()}.
- * @since 3.4 */
- public String string(String stringWithMarkup) { return this.new Text(stringWithMarkup).toString(); }
- /** Returns Ansi.ON if the specified {@code enabled} flag is true, Ansi.OFF otherwise.
- * @since 3.4 */
- public static Ansi valueOf(boolean enabled) {return enabled ? ON : OFF; }
- /** Defines the interface for an ANSI escape sequence. */
- public interface IStyle {
- /** The Control Sequence Introducer (CSI) escape sequence {@value}. */
- String CSI = "\u001B[";
- /** Returns the ANSI escape code for turning this style on.
- * @return the ANSI escape code for turning this style on */
- String on();
- /** Returns the ANSI escape code for turning this style off.
- * @return the ANSI escape code for turning this style off */
- String off();
- }
- /**
- * A set of pre-defined ANSI escape code styles and colors, and a set of convenience methods for parsing
- * text with embedded markup style names, as well as convenience methods for converting
- * styles to strings with embedded escape codes.
- */
- public enum Style implements IStyle {
- reset(0, 0), bold(1, 21), faint(2, 22), italic(3, 23), underline(4, 24), blink(5, 25), reverse(7, 27),
- fg_black(30, 39), fg_red(31, 39), fg_green(32, 39), fg_yellow(33, 39), fg_blue(34, 39), fg_magenta(35, 39), fg_cyan(36, 39), fg_white(37, 39),
- bg_black(40, 49), bg_red(41, 49), bg_green(42, 49), bg_yellow(43, 49), bg_blue(44, 49), bg_magenta(45, 49), bg_cyan(46, 49), bg_white(47, 49),
- ;
- private final int startCode;
- private final int endCode;
- Style(int startCode, int endCode) {this.startCode = startCode; this.endCode = endCode; }
- public String on() { return CSI + startCode + "m"; }
- public String off() { return CSI + endCode + "m"; }
- /** Returns the concatenated ANSI escape codes for turning all specified styles on.
- * @param styles the styles to generate ANSI escape codes for
- * @return the concatenated ANSI escape codes for turning all specified styles on */
- public static String on(IStyle... styles) {
- StringBuilder result = new StringBuilder();
- for (IStyle style : styles) {
- result.append(style.on());
- }
- return result.toString();
- }
- /** Returns the concatenated ANSI escape codes for turning all specified styles off.
- * @param styles the styles to generate ANSI escape codes for
- * @return the concatenated ANSI escape codes for turning all specified styles off */
- public static String off(IStyle... styles) {
- StringBuilder result = new StringBuilder();
- for (IStyle style : styles) {
- result.append(style.off());
- }
- return result.toString();
- }
- /** Parses the specified style markup and returns the associated style.
- * The markup may be one of the Style enum value names, or it may be one of the Style enum value
- * names when {@code "fg_"} is prepended, or it may be one of the indexed colors in the 256 color palette.
- * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "fg_blue"},
- * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color)
- * @return the IStyle for the specified converter
- */
- public static IStyle fg(String str) {
- try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
- try { return Style.valueOf("fg_" + str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
- return new Palette256Color(true, str);
- }
- /** Parses the specified style markup and returns the associated style.
- * The markup may be one of the Style enum value names, or it may be one of the Style enum value
- * names when {@code "bg_"} is prepended, or it may be one of the indexed colors in the 256 color palette.
- * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "bg_blue"},
- * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color)
- * @return the IStyle for the specified converter
- */
- public static IStyle bg(String str) {
- try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
- try { return Style.valueOf("bg_" + str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
- return new Palette256Color(false, str);
- }
- /** Parses the specified comma-separated sequence of style descriptors and returns the associated
- * styles. For each markup, strings starting with {@code "bg("} are delegated to
- * {@link #bg(String)}, others are delegated to {@link #bg(String)}.
- * @param commaSeparatedCodes one or more descriptors, e.g. {@code "bg(blue),underline,red"}
- * @return an array with all styles for the specified descriptors
- */
- public static IStyle[] parse(String commaSeparatedCodes) {
- String[] codes = commaSeparatedCodes.split(",");
- IStyle[] styles = new IStyle[codes.length];
- for(int i = 0; i < codes.length; ++i) {
- if (codes[i].toLowerCase(ENGLISH).startsWith("fg(")) {
- int end = codes[i].indexOf(')');
- styles[i] = Style.fg(codes[i].substring(3, end < 0 ? codes[i].length() : end));
- } else if (codes[i].toLowerCase(ENGLISH).startsWith("bg(")) {
- int end = codes[i].indexOf(')');
- styles[i] = Style.bg(codes[i].substring(3, end < 0 ? codes[i].length() : end));
- } else {
- styles[i] = Style.fg(codes[i]);
- }
- }
- return styles;
- }
- }
- /** Defines a palette map of 216 colors: 6 * 6 * 6 cube (216 colors):
- * 16 + 36 * r + 6 * g + b (0 <= r, g, b <= 5). */
- static class Palette256Color implements IStyle {
- private final int fgbg;
- private final int color;
- Palette256Color(boolean foreground, String color) {
- this.fgbg = foreground ? 38 : 48;
- String[] rgb = color.split(";");
- if (rgb.length == 3) {
- this.color = 16 + 36 * Integer.decode(rgb[0]) + 6 * Integer.decode(rgb[1]) + Integer.decode(rgb[2]);
- } else {
- this.color = Integer.decode(color);
- }
- }
- public String on() { return String.format(CSI + "%d;5;%dm", fgbg, color); }
- public String off() { return CSI + (fgbg + 1) + "m"; }
- }
- private static class StyledSection {
- int startIndex, length;
- String startStyles, endStyles;
- StyledSection(int start, int len, String style1, String style2) {
- startIndex = start; length = len; startStyles = style1; endStyles = style2;
- }
- StyledSection withStartIndex(int newStart) {
- return new StyledSection(newStart, length, startStyles, endStyles);
- }
- }
- /**
- * Returns a new Text object where all the specified styles are applied to the full length of the
- * specified plain text.
- * @param plainText the string to apply all styles to. Must not contain markup!
- * @param styles the styles to apply to the full plain text
- * @return a new Text object
- */
- public Text apply(String plainText, List<IStyle> styles) {
- if (plainText.length() == 0) { return new Text(0); }
- Text result = new Text(plainText.length());
- IStyle[] all = styles.toArray(new IStyle[styles.size()]);
- result.sections.add(new StyledSection(
- 0, plainText.length(), Style.on(all), Style.off(reverse(all)) + Style.reset.off()));
- result.plain.append(plainText);
- result.length = result.plain.length();
- return result;
- }
- private static <T> T[] reverse(T[] all) {
- for (int i = 0; i < all.length / 2; i++) {
- T temp = all[i];
- all[i] = all[all.length - i - 1];
- all[all.length - i - 1] = temp;
- }
- return all;
- }
- /** Encapsulates rich text with styles and colors. Text objects may be constructed with Strings containing
- * markup like {@code @|bg(red),white,underline some text|@}, and this class converts the markup to ANSI
- * escape codes.
- * <p>
- * Internally keeps both an enriched and a plain text representation to allow layout components to calculate
- * text width while remaining unaware of the embedded ANSI escape codes.</p> */
- public class Text implements Cloneable {
- private final int maxLength;
- private int from;
- private int length;
- private StringBuilder plain = new StringBuilder();
- private List<StyledSection> sections = new ArrayList<StyledSection>();
- /** Constructs a Text with the specified max length (for use in a TextTable Column).
- * @param maxLength max length of this text */
- public Text(int maxLength) { this.maxLength = maxLength; }
- /** Copy constructor.
- * @since 3.9 */
- public Text(Text other) {
- this.maxLength = other.maxLength;
- this.from = other.from;
- this.length = other.length;
- this.plain = new StringBuilder(other.plain);
- this.sections = new ArrayList<StyledSection>(other.sections);
- }
- /**
- * Constructs a Text with the specified String, which may contain markup like
- * {@code @|bg(red),white,underline some text|@}.
- * @param input the string with markup to parse
- */
- public Text(String input) {
- maxLength = -1;
- plain.setLength(0);
- int i = 0;
- while (true) {
- int j = input.indexOf("@|", i);
- if (j == -1) {
- if (i == 0) {
- plain.append(input);
- length = plain.length();
- return;
- }
- plain.append(input.substring(i, input.length()));
- length = plain.length();
- return;
- }
- plain.append(input.substring(i, j));
- int k = input.indexOf("|@", j);
- if (k == -1) {
- plain.append(input);
- length = plain.length();
- return;
- }
- j += 2;
- String spec = input.substring(j, k);
- String[] items = spec.split(" ", 2);
- if (items.length == 1) {
- plain.append(input);
- length = plain.length();
- return;
- }
- IStyle[] styles = Style.parse(items[0]);
- addStyledSection(plain.length(), items[1].length(),
- Style.on(styles), Style.off(reverse(styles)) + Style.reset.off());
- plain.append(items[1]);
- i = k + 2;
- }
- }
- private void addStyledSection(int start, int length, String startStyle, String endStyle) {
- sections.add(new StyledSection(start, length, startStyle, endStyle));
- }
- public Object clone() { return new Text(this); }
- public Text[] splitLines() {
- List<Text> result = new ArrayList<Text>();
- int start = 0, end = 0;
- for (int i = 0; i < plain.length(); i++, end = i) {
- char c = plain.charAt(i);
- boolean eol = c == '\n';
- if (c == '\r' && i + 1 < plain.length() && plain.charAt(i + 1) == '\n') { eol = true; i++; } // \r\n
- eol |= c == '\r';
- if (eol) {
- result.add(this.substring(start, end));
- start = i + 1;
- }
- }
- // add remainder (may be empty string)
- result.add(this.substring(start, plain.length()));
- return result.toArray(new Text[result.size()]);
- }
- /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance!
- * @param start index in the plain text where to start the substring
- * @return a new Text instance that is a substring of this Text */
- public Text substring(int start) {
- return substring(start, length);
- }
- /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance!
- * @param start index in the plain text where to start the substring
- * @param end index in the plain text where to end the substring
- * @return a new Text instance that is a substring of this Text */
- public Text substring(int start, int end) {
- Text result = (Text) clone();
- result.from = from + start;
- result.length = end - start;
- return result;
- }
- /** @deprecated use {@link #concat(String)} instead */
- @Deprecated public Text append(String string) { return concat(string); }
- /** @deprecated use {@link #concat(Text)} instead */
- @Deprecated public Text append(Text text) { return concat(text); }
- /** Returns a copy of this {@code Text} instance with the specified text concatenated to the end. Does not modify this instance!
- * @param string the text to concatenate to the end of this Text
- * @return a new Text instance
- * @since 3.0 */
- public Text concat(String string) { return concat(new Text(string)); }
- /** Returns a copy of this {@code Text} instance with the specified text concatenated to the end. Does not modify this instance!
- * @param other the text to concatenate to the end of this Text
- * @return a new Text instance
- * @since 3.0 */
- public Text concat(Text other) {
- Text result = (Text) clone();
- result.plain = new StringBuilder(plain.toString().substring(from, from + length));
- result.from = 0;
- result.sections = new ArrayList<StyledSection>();
- for (StyledSection section : sections) {
- result.sections.add(section.withStartIndex(section.startIndex - from));
- }
- result.plain.append(other.plain.toString().substring(other.from, other.from + other.length));
- for (StyledSection section : other.sections) {
- int index = result.length + section.startIndex - other.from;
- result.sections.add(section.withStartIndex(index));
- }
- result.length = result.plain.length();
- return result;
- }
- /**
- * Copies the specified substring of this Text into the specified destination, preserving the markup.
- * @param from start of the substring
- * @param length length of the substring
- * @param destination destination Text to modify
- * @param offset indentation (padding)
- */
- public void getStyledChars(int from, int length, Text destination, int offset) {
- if (destination.length < offset) {
- for (int i = destination.length; i < offset; i++) {
- destination.plain.append(' ');
- }
- destination.length = offset;
- }
- for (StyledSection section : sections) {
- destination.sections.add(section.withStartIndex(section.startIndex - from + destination.length));
- }
- destination.plain.append(plain.toString().substring(from, from + length));
- destination.length = destination.plain.length();
- }
- /** Returns the plain text without any formatting.
- * @return the plain text without any formatting */
- public String plainString() { return plain.toString().substring(from, from + length); }
- public boolean equals(Object obj) { return toString().equals(String.valueOf(obj)); }
- public int hashCode() { return toString().hashCode(); }
- /** Returns a String representation of the text with ANSI escape codes embedded, unless ANSI is
- * {@linkplain Ansi#enabled()} not enabled}, in which case the plain text is returned.
- * @return a String representation of the text with ANSI escape codes embedded (if enabled) */
- public String toString() {
- if (!Ansi.this.enabled()) {
- return plain.toString().substring(from, from + length);
- }
- if (length == 0) { return ""; }
- StringBuilder sb = new StringBuilder(plain.length() + 20 * sections.size());
- StyledSection current = null;
- int end = Math.min(from + length, plain.length());
- for (int i = from; i < end; i++) {
- StyledSection section = findSectionContaining(i);
- if (section != current) {
- if (current != null) { sb.append(current.endStyles); }
- if (section != null) { sb.append(section.startStyles); }
- current = section;
- }
- sb.append(plain.charAt(i));
- }
- if (current != null) { sb.append(current.endStyles); }
- return sb.toString();
- }
- private StyledSection findSectionContaining(int index) {
- for (StyledSection section : sections) {
- if (index >= section.startIndex && index < section.startIndex + section.length) {
- return section;
- }
- }
- return null;
- }
- }
- }
- }
- /**
- * Utility class providing some defensive coding convenience methods.
- */
- private static final class Assert {
- /**
- * Throws a NullPointerException if the specified object is null.
- * @param object the object to verify
- * @param description error message
- * @param <T> type of the object to check
- * @return the verified object
- */
- static <T> T notNull(T object, String description) {
- if (object == null) {
- throw new NullPointerException(description);
- }
- return object;
- }
- static boolean equals(Object obj1, Object obj2) { return obj1 == null ? obj2 == null : obj1.equals(obj2); }
- static int hashCode(Object obj) {return obj == null ? 0 : obj.hashCode(); }
- static int hashCode(boolean bool) {return bool ? 1 : 0; }
- static void assertTrue(boolean condition, String message) {
- if (!condition) throw new IllegalStateException(message);
- }
- private Assert() {} // private constructor: never instantiate
- }
- private enum TraceLevel { OFF, WARN, INFO, DEBUG;
- public boolean isEnabled(TraceLevel other) { return ordinal() >= other.ordinal(); }
- private void print(Tracer tracer, String msg, Object... params) {
- if (tracer.level.isEnabled(this)) { tracer.stream.printf(prefix(msg), params); }
- }
- private String prefix(String msg) { return "[picocli " + this + "] " + msg; }
- static TraceLevel lookup(String key) { return key == null ? WARN : empty(key) || "true".equalsIgnoreCase(key) ? INFO : valueOf(key); }
- }
- static class Tracer {
- TraceLevel level = TraceLevel.lookup(System.getProperty("picocli.trace"));
- PrintStream stream = System.err;
- void warn (String msg, Object... params) { TraceLevel.WARN.print(this, msg, params); }
- void info (String msg, Object... params) { TraceLevel.INFO.print(this, msg, params); }
- void debug(String msg, Object... params) { TraceLevel.DEBUG.print(this, msg, params); }
- boolean isWarn() { return level.isEnabled(TraceLevel.WARN); }
- boolean isInfo() { return level.isEnabled(TraceLevel.INFO); }
- boolean isDebug() { return level.isEnabled(TraceLevel.DEBUG); }
- }
- /**
- * Uses cosine similarity to find matches from a candidate set for a specified input.
- * Based on code from http://www.nearinfinity.com/blogs/seth_schroeder/groovy_cosine_similarity_in_grails.html
- *
- * @author Burt Beckwith
- */
- private static class CosineSimilarity {
- static List<String> mostSimilar(String pattern, Iterable<String> candidates) { return mostSimilar(pattern, candidates, 0); }
- static List<String> mostSimilar(String pattern, Iterable<String> candidates, double threshold) {
- pattern = pattern.toLowerCase();
- SortedMap<Double, String> sorted = new TreeMap<Double, String>();
- for (String candidate : candidates) {
- double score = similarity(pattern, candidate.toLowerCase(), 2);
- if (score > threshold) { sorted.put(score, candidate); }
- }
- return reverseList(new ArrayList<String>(sorted.values()));
- }
- private static double similarity(String sequence1, String sequence2, int degree) {
- Map<String, Integer> m1 = countNgramFrequency(sequence1, degree);
- Map<String, Integer> m2 = countNgramFrequency(sequence2, degree);
- return dotProduct(m1, m2) / Math.sqrt(dotProduct(m1, m1) * dotProduct(m2, m2));
- }
- private static Map<String, Integer> countNgramFrequency(String sequence, int degree) {
- Map<String, Integer> m = new HashMap<String, Integer>();
- for (int i = 0; i + degree <= sequence.length(); i++) {
- String gram = sequence.substring(i, i + degree);
- m.put(gram, 1 + (m.containsKey(gram) ? m.get(gram) : 0));
- }
- return m;
- }
- private static double dotProduct(Map<String, Integer> m1, Map<String, Integer> m2) {
- double result = 0;
- for (String key : m1.keySet()) { result += m1.get(key) * (m2.containsKey(key) ? m2.get(key) : 0); }
- return result;
- }
- }
- /** Base class of all exceptions thrown by {@code picocli.CommandLine}.
- * <h2>Class Diagram of the Picocli Exceptions</h2>
- * <p>
- * <img src="doc-files/class-diagram-exceptions.png" alt="Class Diagram of the Picocli Exceptions">
- * </p>
- * @since 2.0 */
- public static class PicocliException extends RuntimeException {
- private static final long serialVersionUID = -2574128880125050818L;
- public PicocliException(String msg) { super(msg); }
- public PicocliException(String msg, Throwable t) { super(msg, t); }
- }
- /** Exception indicating a problem during {@code CommandLine} initialization.
- * @since 2.0 */
- public static class InitializationException extends PicocliException {
- private static final long serialVersionUID = 8423014001666638895L;
- public InitializationException(String msg) { super(msg); }
- public InitializationException(String msg, Exception ex) { super(msg, ex); }
- }
- /** Exception indicating a problem while invoking a command or subcommand.
- * @since 2.0 */
- public static class ExecutionException extends PicocliException {
- private static final long serialVersionUID = 7764539594267007998L;
- private final CommandLine commandLine;
- public ExecutionException(CommandLine commandLine, String msg) {
- super(msg);
- this.commandLine = Assert.notNull(commandLine, "commandLine");
- }
- public ExecutionException(CommandLine commandLine, String msg, Throwable t) {
- super(msg, t);
- this.commandLine = Assert.notNull(commandLine, "commandLine");
- }
- /** Returns the {@code CommandLine} object for the (sub)command that could not be invoked.
- * @return the {@code CommandLine} object for the (sub)command where invocation failed.
- */
- public CommandLine getCommandLine() { return commandLine; }
- }
- /** Exception thrown by {@link ITypeConverter} implementations to indicate a String could not be converted. */
- public static class TypeConversionException extends PicocliException {
- private static final long serialVersionUID = 4251973913816346114L;
- public TypeConversionException(String msg) { super(msg); }
- }
- /** Exception indicating something went wrong while parsing command line options. */
- public static class ParameterException extends PicocliException {
- private static final long serialVersionUID = 1477112829129763139L;
- private final CommandLine commandLine;
- private ArgSpec argSpec = null;
- private String value = null;
- /** Constructs a new ParameterException with the specified CommandLine and error message.
- * @param commandLine the command or subcommand whose input was invalid
- * @param msg describes the problem
- * @since 2.0 */
- public ParameterException(CommandLine commandLine, String msg) {
- super(msg);
- this.commandLine = Assert.notNull(commandLine, "commandLine");
- }
- /** Constructs a new ParameterException with the specified CommandLine and error message.
- * @param commandLine the command or subcommand whose input was invalid
- * @param msg describes the problem
- * @param t the throwable that caused this ParameterException
- * @since 2.0 */
- public ParameterException(CommandLine commandLine, String msg, Throwable t) {
- super(msg, t);
- this.commandLine = Assert.notNull(commandLine, "commandLine");
- }
- /** Constructs a new ParameterException with the specified CommandLine and error message.
- * @param commandLine the command or subcommand whose input was invalid
- * @param msg describes the problem
- * @param t the throwable that caused this ParameterException
- * @param argSpec the argSpec that caused this ParameterException
- * @param value the value that caused this ParameterException
- * @since 3.2 */
- public ParameterException(CommandLine commandLine, String msg, Throwable t, ArgSpec argSpec, String value) {
- super(msg, t);
- this.commandLine = Assert.notNull(commandLine, "commandLine");
- if (argSpec == null && value == null) { throw new IllegalArgumentException("ArgSpec and value cannot both be null"); }
- this.argSpec = argSpec;
- this.value = value;
- }
- /** Constructs a new ParameterException with the specified CommandLine and error message.
- * @param commandLine the command or subcommand whose input was invalid
- * @param msg describes the problem
- * @param argSpec the argSpec that caused this ParameterException
- * @param value the value that caused this ParameterException
- * @since 3.2 */
- public ParameterException(CommandLine commandLine, String msg, ArgSpec argSpec, String value) {
- super(msg);
- this.commandLine = Assert.notNull(commandLine, "commandLine");
- if (argSpec == null && value == null) { throw new IllegalArgumentException("ArgSpec and value cannot both be null"); }
- this.argSpec = argSpec;
- this.value = value;
- }
- /** Returns the {@code CommandLine} object for the (sub)command whose input could not be parsed.
- * @return the {@code CommandLine} object for the (sub)command where parsing failed.
- * @since 2.0
- */
- public CommandLine getCommandLine() { return commandLine; }
- /** Returns the {@code ArgSpec} object for the (sub)command whose input could not be parsed.
- * @return the {@code ArgSpec} object for the (sub)command where parsing failed.
- * @since 3.2
- */
- public ArgSpec getArgSpec() { return argSpec; }
- /** Returns the {@code String} value for the (sub)command whose input could not be parsed.
- * @return the {@code String} value for the (sub)command where parsing failed.
- * @since 3.2
- */
- public String getValue() { return value; }
- private static ParameterException create(CommandLine cmd, Exception ex, String arg, int i, String[] args) {
- String msg = ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage()
- + " while processing argument at or before arg[" + i + "] '" + arg + "' in " + Arrays.toString(args) + ": " + ex.toString();
- return new ParameterException(cmd, msg, ex, null, arg);
- }
- }
- /**
- * Exception indicating that a required parameter was not specified.
- */
- public static class MissingParameterException extends ParameterException {
- private static final long serialVersionUID = 5075678535706338753L;
- private final List<ArgSpec> missing;
- public MissingParameterException(CommandLine commandLine, ArgSpec missing, String msg) { this(commandLine, Arrays.asList(missing), msg); }
- public MissingParameterException(CommandLine commandLine, Collection<ArgSpec> missing, String msg) {
- super(commandLine, msg);
- this.missing = Collections.unmodifiableList(new ArrayList<ArgSpec>(missing));
- }
- public List<ArgSpec> getMissing() { return missing; }
- private static MissingParameterException create(CommandLine cmd, Collection<ArgSpec> missing, String separator) {
- if (missing.size() == 1) {
- return new MissingParameterException(cmd, missing, "Missing required option '"
- + ArgSpec.describe(missing.iterator().next(), separator) + "'");
- }
- List<String> names = new ArrayList<String>(missing.size());
- for (ArgSpec argSpec : missing) {
- names.add(ArgSpec.describe(argSpec, separator));
- }
- return new MissingParameterException(cmd, missing, "Missing required options " + names.toString());
- }
- }
- /** Exception indicating that the user input included multiple arguments from a mutually exclusive group.
- * @since 4.0 */
- public static class MutuallyExclusiveArgsException extends ParameterException {
- private static final long serialVersionUID = -5557715106221420986L;
- public MutuallyExclusiveArgsException(CommandLine commandLine, String msg) { super(commandLine, msg); }
- }
- /** Exception indicating that multiple named elements have incorrectly used the same name.
- * @since 4.0 */
- public static class DuplicateNameException extends InitializationException {
- private static final long serialVersionUID = -4126747467955626054L;
- public DuplicateNameException(String msg) { super(msg); }
- }
- /**
- * Exception indicating that multiple fields have been annotated with the same Option name.
- */
- public static class DuplicateOptionAnnotationsException extends DuplicateNameException {
- private static final long serialVersionUID = -3355128012575075641L;
- public DuplicateOptionAnnotationsException(String msg) { super(msg); }
- private static DuplicateOptionAnnotationsException create(String name, ArgSpec argSpec1, ArgSpec argSpec2) {
- return new DuplicateOptionAnnotationsException("Option name '" + name + "' is used by both " +
- argSpec1.toString() + " and " + argSpec2.toString());
- }
- }
- /** Exception indicating that there was a gap in the indices of the fields annotated with {@link Parameters}. */
- public static class ParameterIndexGapException extends InitializationException {
- private static final long serialVersionUID = -1520981133257618319L;
- public ParameterIndexGapException(String msg) { super(msg); }
- }
- /** Exception indicating that a command line argument could not be mapped to any of the fields annotated with
- * {@link Option} or {@link Parameters}. */
- public static class UnmatchedArgumentException extends ParameterException {
- private static final long serialVersionUID = -8700426380701452440L;
- private List<String> unmatched = Collections.<String>emptyList();
- public UnmatchedArgumentException(CommandLine commandLine, String msg) { super(commandLine, msg); }
- public UnmatchedArgumentException(CommandLine commandLine, Stack<String> args) { this(commandLine, new ArrayList<String>(reverse(args))); }
- public UnmatchedArgumentException(CommandLine commandLine, List<String> args) {
- this(commandLine, describe(args, commandLine) + (args.size() == 1 ? ": " : "s: ") + str(args));
- unmatched = args == null ? Collections.<String>emptyList() : args;
- }
- /** Returns {@code true} and prints suggested solutions to the specified stream if such solutions exist, otherwise returns {@code false}.
- * @since 3.3.0 */
- public static boolean printSuggestions(ParameterException ex, PrintStream out) {
- return ex instanceof UnmatchedArgumentException && ((UnmatchedArgumentException) ex).printSuggestions(out);
- }
- /** Returns the unmatched command line arguments.
- * @since 3.3.0 */
- public List<String> getUnmatched() { return stripErrorMessage(unmatched); }
- static List<String> stripErrorMessage(List<String> unmatched) {
- List<String> result = new ArrayList<String>();
- for (String s : unmatched) {
- if (s == null) { result.add(null); }
- int pos = s.indexOf(" (while processing option:");
- result.add(pos < 0 ? s : s.substring(0, pos));
- }
- return Collections.unmodifiableList(result);
- }
- /** Returns {@code true} if the first unmatched command line arguments resembles an option, {@code false} otherwise.
- * @since 3.3.0 */
- public boolean isUnknownOption() { return isUnknownOption(unmatched, getCommandLine()); }
- /** Returns {@code true} and prints suggested solutions to the specified stream if such solutions exist, otherwise returns {@code false}.
- * @since 3.3.0 */
- public boolean printSuggestions(PrintStream out) {
- List<String> suggestions = getSuggestions();
- if (!suggestions.isEmpty()) {
- out.println(isUnknownOption()
- ? "Possible solutions: " + str(suggestions)
- : "Did you mean: " + str(suggestions).replace(", ", " or ") + "?");
- }
- return !suggestions.isEmpty();
- }
- /** Returns suggested solutions if such solutions exist, otherwise returns an empty list.
- * @since 3.3.0 */
- public List<String> getSuggestions() {
- if (unmatched == null || unmatched.isEmpty()) { return Collections.emptyList(); }
- String arg = unmatched.get(0);
- String stripped = CommandSpec.stripPrefix(arg);
- CommandSpec spec = getCommandLine().getCommandSpec();
- if (spec.resemblesOption(arg, null)) {
- return spec.findOptionNamesWithPrefix(stripped.substring(0, Math.min(2, stripped.length())));
- } else if (!spec.subcommands().isEmpty()) {
- List<String> mostSimilar = CosineSimilarity.mostSimilar(arg, spec.subcommands().keySet());
- return mostSimilar.subList(0, Math.min(3, mostSimilar.size()));
- }
- return Collections.emptyList();
- }
- private static boolean isUnknownOption(List<String> unmatch, CommandLine cmd) {
- return unmatch != null && !unmatch.isEmpty() && cmd.getCommandSpec().resemblesOption(unmatch.get(0), null);
- }
- private static String describe(List<String> unmatch, CommandLine cmd) {
- return isUnknownOption(unmatch, cmd) ? "Unknown option" : "Unmatched argument";
- }
- static String str(List<String> list) {
- String s = list.toString();
- return s.substring(0, s.length() - 1).substring(1);
- }
- }
- /** Exception indicating that more values were specified for an option or parameter than its {@link Option#arity() arity} allows. */
- public static class MaxValuesExceededException extends ParameterException {
- private static final long serialVersionUID = 6536145439570100641L;
- public MaxValuesExceededException(CommandLine commandLine, String msg) { super(commandLine, msg); }
- }
- /** Exception indicating that an option for a single-value option field has been specified multiple times on the command line. */
- public static class OverwrittenOptionException extends ParameterException {
- private static final long serialVersionUID = 1338029208271055776L;
- private final ArgSpec overwrittenArg;
- public OverwrittenOptionException(CommandLine commandLine, ArgSpec overwritten, String msg) {
- super(commandLine, msg);
- overwrittenArg = overwritten;
- }
- /** Returns the {@link ArgSpec} for the option which was being overwritten.
- * @since 3.8 */
- public ArgSpec getOverwritten() { return overwrittenArg; }
- }
- /**
- * Exception indicating that an annotated field had a type for which no {@link ITypeConverter} was
- * {@linkplain #registerConverter(Class, ITypeConverter) registered}.
- */
- public static class MissingTypeConverterException extends ParameterException {
- private static final long serialVersionUID = -6050931703233083760L;
- public MissingTypeConverterException(CommandLine commandLine, String msg) { super(commandLine, msg); }
- }
- }
|