diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 000000000000..8e253023285e --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,3614 @@ +{ + "projectName": "oh-my-posh", + "projectOwner": "JanDeDobbeleer", + "repoType": "github", + "repoHost": "https://github.com", + "files": [ + "website/docs/contributors.md" + ], + "imageSize": 100, + "commit": true, + "commitConvention": "angular", + "contributors": [ + { + "login": "lnu", + "name": "Laurent Nullens", + "avatar_url": "https://avatars.githubusercontent.com/u/1829553?v=4", + "profile": "https://github.com/lnu", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "TravisTX", + "name": "Travis Collins", + "avatar_url": "https://avatars.githubusercontent.com/u/934490?v=4", + "profile": "https://github.com/TravisTX", + "contributions": [ + "code" + ] + }, + { + "login": "jos3s", + "name": "José Ulisses", + "avatar_url": "https://avatars.githubusercontent.com/u/50359547?v=4", + "profile": "https://github.com/jos3s", + "contributions": [ + "code" + ] + }, + { + "login": "nwykes", + "name": "Nathan Wykes", + "avatar_url": "https://avatars.githubusercontent.com/u/593993?v=4", + "profile": "https://github.com/nwykes", + "contributions": [ + "code" + ] + }, + { + "login": "tillig", + "name": "Travis Illig", + "avatar_url": "https://avatars.githubusercontent.com/u/1156571?v=4", + "profile": "http://www.paraesthesia.com/", + "contributions": [ + "code" + ] + }, + { + "login": "evilz", + "name": "Vincent B.", + "avatar_url": "https://avatars.githubusercontent.com/u/2937862?v=4", + "profile": "http://www.evilznet.com/", + "contributions": [ + "code" + ] + }, + { + "login": "erclu", + "name": "Luca Ercole", + "avatar_url": "https://avatars.githubusercontent.com/u/30255227?v=4", + "profile": "https://erclu.github.io/cv/", + "contributions": [ + "code" + ] + }, + { + "login": "LarsBauer", + "name": "Lars Bauer", + "avatar_url": "https://avatars.githubusercontent.com/u/3920045?v=4", + "profile": "https://larsbauer.xyz/", + "contributions": [ + "design" + ] + }, + { + "login": "RobCannon", + "name": "Rob Cannon", + "avatar_url": "https://avatars.githubusercontent.com/u/189862?v=4", + "profile": "https://github.com/RobCannon", + "contributions": [ + "code" + ] + }, + { + "login": "Vixb1122", + "name": "Vixb", + "avatar_url": "https://avatars.githubusercontent.com/u/17810492?v=4", + "profile": "https://github.com/Vixb1122", + "contributions": [ + "doc" + ] + }, + { + "login": "zeyugao", + "name": "Elsa Granger", + "avatar_url": "https://avatars.githubusercontent.com/u/6374697?v=4", + "profile": "https://github.com/zeyugao", + "contributions": [ + "design" + ] + }, + { + "login": "softweaprograma", + "name": "Anthony G", + "avatar_url": "https://avatars.githubusercontent.com/u/35231092?v=4", + "profile": "https://github.com/softweaprograma", + "contributions": [ + "design" + ] + }, + { + "login": "gitolicious", + "name": "gitolicious", + "avatar_url": "https://avatars.githubusercontent.com/u/26963495?v=4", + "profile": "https://github.com/gitolicious", + "contributions": [ + "code" + ] + }, + { + "login": "irdkwmnsb", + "name": "Maxim", + "avatar_url": "https://avatars.githubusercontent.com/u/8657078?v=4", + "profile": "https://alzhanov.ru/", + "contributions": [ + "design" + ] + }, + { + "login": "PIYUSH194", + "name": "PIYUSH194", + "avatar_url": "https://avatars.githubusercontent.com/u/2896456?v=4", + "profile": "https://github.com/PIYUSH194", + "contributions": [ + "code" + ] + }, + { + "login": "97krihop", + "name": "97krihop", + "avatar_url": "https://avatars.githubusercontent.com/u/24739853?v=4", + "profile": "https://github.com/97krihop", + "contributions": [ + "doc" + ] + }, + { + "login": "stefanes", + "name": "Stefan", + "avatar_url": "https://avatars.githubusercontent.com/u/5484354?v=4", + "profile": "https://github.com/stefanes", + "contributions": [ + "design", + "code" + ] + }, + { + "login": "moritz-meier", + "name": "Moritz Meier", + "avatar_url": "https://avatars.githubusercontent.com/u/60762067?v=4", + "profile": "https://github.com/moritz-meier", + "contributions": [ + "code" + ] + }, + { + "login": "jetersen", + "name": "Joseph Petersen", + "avatar_url": "https://avatars.githubusercontent.com/u/1661688?v=4", + "profile": "https://github.com/jetersen", + "contributions": [ + "code" + ] + }, + { + "login": "Goliaita", + "name": "Davide Basile", + "avatar_url": "https://avatars.githubusercontent.com/u/11245411?v=4", + "profile": "https://github.com/Goliaita", + "contributions": [ + "code" + ] + }, + { + "login": "sukso96100", + "name": "Youngbin Han", + "avatar_url": "https://avatars.githubusercontent.com/u/1916739?v=4", + "profile": "http://youngbin.xyz/", + "contributions": [ + "design" + ] + }, + { + "login": "mateusnssn", + "name": "Mateus Nunes", + "avatar_url": "https://avatars.githubusercontent.com/u/69170710?v=4", + "profile": "https://mateusnssp.github.io/mateusnssp/", + "contributions": [ + "design" + ] + }, + { + "login": "PixelRobots", + "name": "PixelRobots", + "avatar_url": "https://avatars.githubusercontent.com/u/22979170?v=4", + "profile": "https://pixelrobots.co.uk/", + "contributions": [ + "design" + ] + }, + { + "login": "RishabhSood", + "name": "RishabhSood", + "avatar_url": "https://avatars.githubusercontent.com/u/55499929?v=4", + "profile": "https://github.com/RishabhSood", + "contributions": [ + "design" + ] + }, + { + "login": "SagarYadav17", + "name": "Sagar Yadav", + "avatar_url": "https://avatars.githubusercontent.com/u/47110215?v=4", + "profile": "https://github.com/SagarYadav17", + "contributions": [ + "design" + ] + }, + { + "login": "WolfspiritM", + "name": "Adrian", + "avatar_url": "https://avatars.githubusercontent.com/u/5904171?v=4", + "profile": "https://github.com/WolfspiritM", + "contributions": [ + "code" + ] + }, + { + "login": "MJECloud", + "name": "Maurice", + "avatar_url": "https://avatars.githubusercontent.com/u/22131101?v=4", + "profile": "https://github.com/MJECloud", + "contributions": [ + "code" + ] + }, + { + "login": "samuelfahrngruber", + "name": "samuelfahrngruber", + "avatar_url": "https://avatars.githubusercontent.com/u/35682879?v=4", + "profile": "https://github.com/samuelfahrngruber", + "contributions": [ + "code" + ] + }, + { + "login": "zilmarr", + "name": "Zilmar de Souza Junior", + "avatar_url": "https://avatars.githubusercontent.com/u/5557367?v=4", + "profile": "https://github.com/zilmarr", + "contributions": [ + "design" + ] + }, + { + "login": "AsafMah", + "name": "AsafMah", + "avatar_url": "https://avatars.githubusercontent.com/u/6424271?v=4", + "profile": "https://github.com/AsafMah", + "contributions": [ + "code" + ] + }, + { + "login": "cinnamon-msft", + "name": "Kayla Cinnamon", + "avatar_url": "https://avatars.githubusercontent.com/u/48369326?v=4", + "profile": "https://github.com/cinnamon-msft", + "contributions": [ + "design", + "code", + "doc" + ] + }, + { + "login": "cbargren", + "name": "Chris Bargren", + "avatar_url": "https://avatars.githubusercontent.com/u/1050712?v=4", + "profile": "https://github.com/cbargren", + "contributions": [ + "code" + ] + }, + { + "login": "tonybaloney", + "name": "Anthony Shaw", + "avatar_url": "https://avatars.githubusercontent.com/u/1532417?v=4", + "profile": "https://tonybaloney.github.io/", + "contributions": [ + "design" + ] + }, + { + "login": "mifieldxu", + "name": "Mifield", + "avatar_url": "https://avatars.githubusercontent.com/u/5520179?v=4", + "profile": "https://github.com/mifieldxu", + "contributions": [ + "doc" + ] + }, + { + "login": "benallred", + "name": "Ben Allred", + "avatar_url": "https://avatars.githubusercontent.com/u/3902274?v=4", + "profile": "https://github.com/benallred", + "contributions": [ + "doc" + ] + }, + { + "login": "riazXrazor", + "name": "Riaz Laskar", + "avatar_url": "https://avatars.githubusercontent.com/u/13194363?v=4", + "profile": "https://riazxrazor.herokuapp.com/", + "contributions": [ + "doc" + ] + }, + { + "login": "Don-Vito", + "name": "Don-Vito", + "avatar_url": "https://avatars.githubusercontent.com/u/4639110?v=4", + "profile": "https://github.com/Don-Vito", + "contributions": [ + "doc" + ] + }, + { + "login": "FabianEscarate", + "name": "Fabian Roberto Escarate", + "avatar_url": "https://avatars.githubusercontent.com/u/19978896?v=4", + "profile": "https://github.com/FabianEscarate", + "contributions": [ + "design" + ] + }, + { + "login": "xt0rted", + "name": "Brian Surowiec", + "avatar_url": "https://avatars.githubusercontent.com/u/831974?v=4", + "profile": "https://github.com/xt0rted", + "contributions": [ + "code" + ] + }, + { + "login": "ojullien", + "name": "Olivier Jullien", + "avatar_url": "https://avatars.githubusercontent.com/u/3778194?v=4", + "profile": "https://twitter.com/OJullien", + "contributions": [ + "code" + ] + }, + { + "login": "cdonnellytx", + "name": "Chris Donnelly", + "avatar_url": "https://avatars.githubusercontent.com/u/183046?v=4", + "profile": "https://github.com/cdonnellytx", + "contributions": [ + "code" + ] + }, + { + "login": "KyleCrowley", + "name": "Kyle Crowley", + "avatar_url": "https://avatars.githubusercontent.com/u/6757487?v=4", + "profile": "https://github.com/KyleCrowley", + "contributions": [ + "code" + ] + }, + { + "login": "gitolicious", + "name": "gitolicious", + "avatar_url": "https://avatars.githubusercontent.com/u/26963495?v=4", + "profile": "https://github.com/gitolicious", + "contributions": [ + "code" + ] + }, + { + "login": "jeroen7s", + "name": "Jeroen Evens", + "avatar_url": "https://avatars.githubusercontent.com/u/10954827?v=4", + "profile": "https://github.com/jeroen7s", + "contributions": [ + "doc" + ] + }, + { + "login": "equinox", + "name": "equinox", + "avatar_url": "https://avatars.githubusercontent.com/u/6139999?v=4", + "profile": "https://github.com/equinox", + "contributions": [ + "doc" + ] + }, + { + "login": "DamianoPellegrini", + "name": "Damiano Pellegrini", + "avatar_url": "https://avatars.githubusercontent.com/u/41305552?v=4", + "profile": "https://github.com/DamianoPellegrini", + "contributions": [ + "design" + ] + }, + { + "login": "timon-schelling", + "name": "Timon Schelling", + "avatar_url": "https://avatars.githubusercontent.com/u/36821505?v=4", + "profile": "https://timokrates.de/", + "contributions": [ + "design" + ] + }, + { + "login": "zeyugao", + "name": "Elsa Granger", + "avatar_url": "https://avatars.githubusercontent.com/u/6374697?v=4", + "profile": "https://github.com/zeyugao", + "contributions": [ + "design" + ] + }, + { + "login": "Daksh777", + "name": "Daksh P. Jain", + "avatar_url": "https://avatars.githubusercontent.com/u/43648146?v=4", + "profile": "https://daksh.eu.org/", + "contributions": [ + "doc" + ] + }, + { + "login": "boarder2", + "name": "Willie Zutz", + "avatar_url": "https://avatars.githubusercontent.com/u/19351?v=4", + "profile": "http://bit-shift.com/", + "contributions": [ + "doc" + ] + }, + { + "login": "uruz-7", + "name": "uruz-7", + "avatar_url": "https://avatars.githubusercontent.com/u/15071454?v=4", + "profile": "https://github.com/uruz-7", + "contributions": [ + "design" + ] + }, + { + "login": "beppler", + "name": "Carlos Alberto Costa Beppler", + "avatar_url": "https://avatars.githubusercontent.com/u/66092?v=4", + "profile": "https://github.com/beppler", + "contributions": [ + "code" + ] + }, + { + "login": "sky96111", + "name": "sky96111", + "avatar_url": "https://avatars.githubusercontent.com/u/22412214?v=4", + "profile": "https://github.com/sky96111", + "contributions": [ + "design" + ] + }, + { + "login": "jantielens", + "name": "Jan Tielens", + "avatar_url": "https://avatars.githubusercontent.com/u/9884103?v=4", + "profile": "http://j.tlns.be/", + "contributions": [ + "doc" + ] + }, + { + "login": "shedric1", + "name": "shedric1", + "avatar_url": "https://avatars.githubusercontent.com/u/56672838?v=4", + "profile": "https://github.com/shedric1", + "contributions": [ + "code" + ] + }, + { + "login": "sectorogo", + "name": "sectorogo", + "avatar_url": "https://avatars.githubusercontent.com/u/32959212?v=4", + "profile": "https://github.com/sectorogo", + "contributions": [ + "design" + ] + }, + { + "login": "phil-scott-78", + "name": "Phil Scott", + "avatar_url": "https://avatars.githubusercontent.com/u/2447331?v=4", + "profile": "https://github.com/phil-scott-78", + "contributions": [ + "design" + ] + }, + { + "login": "suuus", + "name": "Suus", + "avatar_url": "https://avatars.githubusercontent.com/u/40822355?v=4", + "profile": "https://suuu.us/", + "contributions": [ + "doc" + ] + }, + { + "login": "wopian", + "name": "James Harris", + "avatar_url": "https://avatars.githubusercontent.com/u/3440094?v=4", + "profile": "https://wopian.me/", + "contributions": [ + "design" + ] + }, + { + "login": "mdlopresti", + "name": "Michael LoPresti", + "avatar_url": "https://avatars.githubusercontent.com/u/1293090?v=4", + "profile": "https://github.com/mdlopresti", + "contributions": [ + "code" + ] + }, + { + "login": "floh96", + "name": "Florian Heberl", + "avatar_url": "https://avatars.githubusercontent.com/u/49693964?v=4", + "profile": "https://github.com/floh96", + "contributions": [ + "doc" + ] + }, + { + "login": "relativityhd", + "name": "Tobias Hölzer", + "avatar_url": "https://avatars.githubusercontent.com/u/37540371?v=4", + "profile": "http://tobiashoelzer.dynu.net", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "h4iku", + "name": "Reza Gharibi", + "avatar_url": "https://avatars.githubusercontent.com/u/3812788?v=4", + "profile": "https://h4iku.github.io", + "contributions": [ + "doc" + ] + }, + { + "login": "JustinGrote", + "name": "Justin Grote", + "avatar_url": "https://avatars.githubusercontent.com/u/15258962?v=4", + "profile": "https://justingrote.github.io", + "contributions": [ + "doc" + ] + }, + { + "login": "henry-js", + "name": "James", + "avatar_url": "https://avatars.githubusercontent.com/u/79054685?v=4", + "profile": "https://github.com/henry-js", + "contributions": [ + "doc" + ] + }, + { + "login": "iarejenius", + "name": "Timothy Wittig", + "avatar_url": "https://avatars.githubusercontent.com/u/1031515?v=4", + "profile": "https://wittig.dev", + "contributions": [ + "code" + ] + }, + { + "login": "Descalon", + "name": "Nico Glas", + "avatar_url": "https://avatars.githubusercontent.com/u/1098500?v=4", + "profile": "https://github.com/Descalon", + "contributions": [ + "code" + ] + }, + { + "login": "hanskokx", + "name": "Hans Kokx", + "avatar_url": "https://avatars.githubusercontent.com/u/1911919?v=4", + "profile": "https://github.com/hanskokx", + "contributions": [ + "doc" + ] + }, + { + "login": "alchatti", + "name": "Majed Al-Chatti", + "avatar_url": "https://avatars.githubusercontent.com/u/9209306?v=4", + "profile": "http://alchatti.com", + "contributions": [ + "design" + ] + }, + { + "login": "Jan0660", + "name": "Jan0660", + "avatar_url": "https://avatars.githubusercontent.com/u/58996212?v=4", + "profile": "https://jan0660.dev", + "contributions": [ + "code" + ] + }, + { + "login": "LuiseFreese", + "name": "Luise Freese", + "avatar_url": "https://avatars.githubusercontent.com/u/49960482?v=4", + "profile": "http://www.m365princess.com", + "contributions": [ + "design" + ] + }, + { + "login": "asherber", + "name": "Aaron Sherber", + "avatar_url": "https://avatars.githubusercontent.com/u/5248041?v=4", + "profile": "https://github.com/asherber", + "contributions": [ + "code" + ] + }, + { + "login": "SeanKilleen", + "name": "Sean Killeen", + "avatar_url": "https://avatars.githubusercontent.com/u/2148318?v=4", + "profile": "http://SeanKilleen.com", + "contributions": [ + "doc" + ] + }, + { + "login": "NickCraver", + "name": "Nick Craver", + "avatar_url": "https://avatars.githubusercontent.com/u/454813?v=4", + "profile": "https://nickcraver.com", + "contributions": [ + "code" + ] + }, + { + "login": "justin-vogt", + "name": "Justin Vogt", + "avatar_url": "https://avatars.githubusercontent.com/u/84424169?v=4", + "profile": "https://github.com/justin-vogt", + "contributions": [ + "design" + ] + }, + { + "login": "TheOnlyTails", + "name": "TheOnlyTails", + "avatar_url": "https://avatars.githubusercontent.com/u/65342367?v=4", + "profile": "http://theonlytails.com", + "contributions": [ + "ideas" + ] + }, + { + "login": "bewing", + "name": "bewing", + "avatar_url": "https://avatars.githubusercontent.com/u/4759896?v=4", + "profile": "https://github.com/bewing", + "contributions": [ + "code" + ] + }, + { + "login": "shawnwildermuth", + "name": "Shawn Wildermuth", + "avatar_url": "https://avatars.githubusercontent.com/u/568272?v=4", + "profile": "http://wildermuth.com", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "onpikono", + "name": "Ondrej Pinka", + "avatar_url": "https://avatars.githubusercontent.com/u/25362465?v=4", + "profile": "https://github.com/onpikono", + "contributions": [ + "doc" + ] + }, + { + "login": "kasuken", + "name": "Emanuele Bartolesi", + "avatar_url": "https://avatars.githubusercontent.com/u/2757486?v=4", + "profile": "https://www.emanuelebartolesi.com", + "contributions": [ + "design" + ] + }, + { + "login": "qiansen1386", + "name": "Paris Qian", + "avatar_url": "https://avatars.githubusercontent.com/u/1759658?v=4", + "profile": "https://qiansen1386.github.io", + "contributions": [ + "design" + ] + }, + { + "login": "tjackadams", + "name": "Thomas Adams", + "avatar_url": "https://avatars.githubusercontent.com/u/2307314?v=4", + "profile": "https://blog.itadams.co.uk", + "contributions": [ + "code" + ] + }, + { + "login": "gschizas", + "name": "George Schizas", + "avatar_url": "https://avatars.githubusercontent.com/u/598065?v=4", + "profile": "http://www.terrasoft.gr/", + "contributions": [ + "code", + "design" + ] + }, + { + "login": "denelon", + "name": "denelon", + "avatar_url": "https://avatars.githubusercontent.com/u/61799811?v=4", + "profile": "https://github.com/denelon", + "contributions": [ + "code" + ] + }, + { + "login": "AbdelrahmanHafez", + "name": "Hafez", + "avatar_url": "https://avatars.githubusercontent.com/u/19984935?v=4", + "profile": "https://github.com/AbdelrahmanHafez", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "TedCrocker", + "name": "Ted Ballou", + "avatar_url": "https://avatars.githubusercontent.com/u/382001?v=4", + "profile": "https://github.com/TedCrocker", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "mikesigs", + "name": "Mike Sigsworth", + "avatar_url": "https://avatars.githubusercontent.com/u/811177?v=4", + "profile": "https://discardchanges.com", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "memcpy-rand-rand-rand", + "name": "Will", + "avatar_url": "https://avatars.githubusercontent.com/u/90210865?v=4", + "profile": "https://github.com/memcpy-rand-rand-rand", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "shanselman", + "name": "Scott Hanselman", + "avatar_url": "https://avatars.githubusercontent.com/u/2892?v=4", + "profile": "http://www.hanselman.com", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "hgreving", + "name": "Harmjan Greving", + "avatar_url": "https://avatars.githubusercontent.com/u/23560667?v=4", + "profile": "https://github.com/hgreving", + "contributions": [ + "doc" + ] + }, + { + "login": "Khaos66", + "name": "Khaos", + "avatar_url": "https://avatars.githubusercontent.com/u/4013009?v=4", + "profile": "https://github.com/Khaos66", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "mattwojo", + "name": "Matt Wojciakowski", + "avatar_url": "https://avatars.githubusercontent.com/u/7566797?v=4", + "profile": "http://mattwojo.github.io/", + "contributions": [ + "doc" + ] + }, + { + "login": "TheTaylorLee", + "name": "TheTaylorLee", + "avatar_url": "https://avatars.githubusercontent.com/u/53202926?v=4", + "profile": "https://www.powershellgallery.com/profiles/TaylorLee", + "contributions": [ + "design" + ] + }, + { + "login": "PapiPeppers", + "name": "Papi Peppers", + "avatar_url": "https://avatars.githubusercontent.com/u/57047860?v=4", + "profile": "https://github.com/PapiPeppers", + "contributions": [ + "design" + ] + }, + { + "login": "erresen", + "name": "erresen", + "avatar_url": "https://avatars.githubusercontent.com/u/5566441?v=4", + "profile": "https://erresen.github.io", + "contributions": [ + "doc" + ] + }, + { + "login": "icy-comet", + "name": "Aniket Teredesai", + "avatar_url": "https://avatars.githubusercontent.com/u/50461557?v=4", + "profile": "https://aniketteredesai.com", + "contributions": [ + "doc" + ] + }, + { + "login": "sdebruyn", + "name": "Sam Debruyn", + "avatar_url": "https://avatars.githubusercontent.com/u/963413?v=4", + "profile": "https://debruyn.dev", + "contributions": [ + "code" + ] + }, + { + "login": "larserikfinholt", + "name": "Lars Erik Finholt", + "avatar_url": "https://avatars.githubusercontent.com/u/1328417?v=4", + "profile": "https://github.com/larserikfinholt", + "contributions": [ + "code" + ] + }, + { + "login": "simorgh1", + "name": "Bahram Maravandi", + "avatar_url": "https://avatars.githubusercontent.com/u/5792905?v=4", + "profile": "https://github.com/simorgh1", + "contributions": [ + "code" + ] + }, + { + "login": "calebjenkins", + "name": "Caleb Jenkins", + "avatar_url": "https://avatars.githubusercontent.com/u/211001?v=4", + "profile": "http://developingux.com", + "contributions": [ + "ideas" + ] + }, + { + "login": "FlavienMacquignon", + "name": "FlavienMacquignon", + "avatar_url": "https://avatars.githubusercontent.com/u/70152975?v=4", + "profile": "https://github.com/FlavienMacquignon", + "contributions": [ + "doc" + ] + }, + { + "login": "Victoria-DR", + "name": "Victoria", + "avatar_url": "https://avatars.githubusercontent.com/u/68347113?v=4", + "profile": "https://github.com/Victoria-DR", + "contributions": [ + "design" + ] + }, + { + "login": "UlanaXY", + "name": "Mikolaj", + "avatar_url": "https://avatars.githubusercontent.com/u/12629308?v=4", + "profile": "https://github.com/UlanaXY", + "contributions": [ + "doc" + ] + }, + { + "login": "markbullplus", + "name": "markbull", + "avatar_url": "https://avatars.githubusercontent.com/u/88931495?v=4", + "profile": "https://github.com/markbullplus", + "contributions": [ + "design" + ] + }, + { + "login": "brian6932", + "name": "Brian", + "avatar_url": "https://avatars.githubusercontent.com/u/18603393?v=4", + "profile": "https://github.com/brian6932", + "contributions": [ + "code" + ] + }, + { + "login": "patHyatt", + "name": "Patrick Hyatt", + "avatar_url": "https://avatars.githubusercontent.com/u/296125?v=4", + "profile": "http://www.patrickhyatt.com", + "contributions": [ + "doc" + ] + }, + { + "login": "hezhizhen", + "name": "Zhizhen He", + "avatar_url": "https://avatars.githubusercontent.com/u/7611700?v=4", + "profile": "https://github.com/hezhizhen", + "contributions": [ + "code" + ] + }, + { + "login": "jedwillick", + "name": "Jed Willick", + "avatar_url": "https://avatars.githubusercontent.com/u/85419773?v=4", + "profile": "https://github.com/jedwillick", + "contributions": [ + "code" + ] + }, + { + "login": "eltociear", + "name": "Ikko Ashimine", + "avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4", + "profile": "https://bandism.net/", + "contributions": [ + "doc" + ] + }, + { + "login": "CapularisPerpetua", + "name": "Courtney Caldwell", + "avatar_url": "https://avatars.githubusercontent.com/u/32304933?v=4", + "profile": "https://prokopto.dev/", + "contributions": [ + "doc" + ] + }, + { + "login": "rfverbruggen", + "name": "Robbert Verbruggen", + "avatar_url": "https://avatars.githubusercontent.com/u/2320197?v=4", + "profile": "https://github.com/rfverbruggen", + "contributions": [ + "code" + ] + }, + { + "login": "Merlin2001", + "name": "Marcus Mangelsdorf", + "avatar_url": "https://avatars.githubusercontent.com/u/13134791?v=4", + "profile": "https://github.com/Merlin2001", + "contributions": [ + "doc" + ] + }, + { + "login": "andresrinivasan", + "name": "André Srinivasan", + "avatar_url": "https://avatars.githubusercontent.com/u/134301?v=4", + "profile": "http://linkedin.com/andresrinivasan", + "contributions": [ + "doc" + ] + }, + { + "login": "ehawman-rosenberg", + "name": "ehawman-rosenberg", + "avatar_url": "https://avatars.githubusercontent.com/u/81652082?v=4", + "profile": "https://github.com/ehawman-rosenberg", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "claudiospizzi", + "name": "Claudio Spizzi", + "avatar_url": "https://avatars.githubusercontent.com/u/1934246?v=4", + "profile": "https://spizzi.net/", + "contributions": [ + "doc" + ] + }, + { + "login": "estruyf", + "name": "Elio Struyf", + "avatar_url": "https://avatars.githubusercontent.com/u/2900833?v=4", + "profile": "https://www.eliostruyf.com", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "oalders", + "name": "Olaf Alders", + "avatar_url": "https://avatars.githubusercontent.com/u/96205?v=4", + "profile": "https://www.olafalders.com/", + "contributions": [ + "doc" + ] + }, + { + "login": "DavidDeSloovere", + "name": "David De Sloovere", + "avatar_url": "https://avatars.githubusercontent.com/u/352626?v=4", + "profile": "https://blog.deltacode.be", + "contributions": [ + "code" + ] + }, + { + "login": "LensPlaysGames", + "name": "LensPlaysGames", + "avatar_url": "https://avatars.githubusercontent.com/u/69637718?v=4", + "profile": "https://lensor-radii.netlify.app", + "contributions": [ + "doc" + ] + }, + { + "login": "atakiya", + "name": "Alex 'Avunia' Takiya", + "avatar_url": "https://avatars.githubusercontent.com/u/6952402?v=4", + "profile": "https://takiya.eu", + "contributions": [ + "code" + ] + }, + { + "login": "kenmorse", + "name": "kenmorse", + "avatar_url": "https://avatars.githubusercontent.com/u/63734484?v=4", + "profile": "https://github.com/kenmorse", + "contributions": [ + "doc" + ] + }, + { + "login": "xadozuk", + "name": "xadozuk", + "avatar_url": "https://avatars.githubusercontent.com/u/780423?v=4", + "profile": "https://github.com/xadozuk", + "contributions": [ + "code" + ] + }, + { + "login": "vedantmgoyal9", + "name": "Vedant", + "avatar_url": "https://avatars.githubusercontent.com/u/83997633?v=4", + "profile": "https://bittu.eu.org", + "contributions": [ + "design", + "code" + ] + }, + { + "login": "lewis-yeung", + "name": "L. Yeung", + "avatar_url": "https://avatars.githubusercontent.com/u/83903009?v=4", + "profile": "https://github.com/lewis-yeung", + "contributions": [ + "code", + "doc", + "design" + ] + }, + { + "login": "antoson", + "name": "Ondrej Antos", + "avatar_url": "https://avatars.githubusercontent.com/u/36371990?v=4", + "profile": "https://github.com/antoson", + "contributions": [ + "doc" + ] + }, + { + "login": "Bahnschrift", + "name": "Bahnschrift", + "avatar_url": "https://avatars.githubusercontent.com/u/31170809?v=4", + "profile": "https://github.com/Bahnschrift", + "contributions": [ + "doc" + ] + }, + { + "login": "jakeboone02", + "name": "Jake Boone", + "avatar_url": "https://avatars.githubusercontent.com/u/366438?v=4", + "profile": "https://github.com/jakeboone02", + "contributions": [ + "doc" + ] + }, + { + "login": "kapsiR", + "name": "kapsiR", + "avatar_url": "https://avatars.githubusercontent.com/u/7165033?v=4", + "profile": "https://github.com/kapsiR", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "csrakowski", + "name": "Christiaan Rakowski", + "avatar_url": "https://avatars.githubusercontent.com/u/1303967?v=4", + "profile": "https://github.com/csrakowski", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "mosullivan93", + "name": "Mitchell J. O'Sullivan", + "avatar_url": "https://avatars.githubusercontent.com/u/7676935?v=4", + "profile": "https://github.com/mosullivan93", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "felpel", + "name": "Félix Pelletier", + "avatar_url": "https://avatars.githubusercontent.com/u/5000004?v=4", + "profile": "https://github.com/felpel", + "contributions": [ + "doc" + ] + }, + { + "login": "ralish", + "name": "Samuel D. Leslie", + "avatar_url": "https://avatars.githubusercontent.com/u/3214803?v=4", + "profile": "https://nexiom.net/", + "contributions": [ + "code" + ] + }, + { + "login": "AjayKMehta", + "name": "Ajay Mehta", + "avatar_url": "https://avatars.githubusercontent.com/u/11180071?v=4", + "profile": "https://github.com/AjayKMehta", + "contributions": [ + "code" + ] + }, + { + "login": "the-eduardo", + "name": "the-eduardo", + "avatar_url": "https://avatars.githubusercontent.com/u/40523695?v=4", + "profile": "https://github.com/the-eduardo", + "contributions": [ + "doc" + ] + }, + { + "login": "antonpiatek", + "name": "Anton Piatek", + "avatar_url": "https://avatars.githubusercontent.com/u/175077?v=4", + "profile": "https://github.com/antonpiatek", + "contributions": [ + "doc" + ] + }, + { + "login": "prodehghan", + "name": "Mohammad Dehghan", + "avatar_url": "https://avatars.githubusercontent.com/u/1384790?v=4", + "profile": "https://careers.stackoverflow.com/dehghan", + "contributions": [ + "doc" + ] + }, + { + "login": "bhagerty", + "name": "bhagerty", + "avatar_url": "https://avatars.githubusercontent.com/u/7828454?v=4", + "profile": "https://github.com/bhagerty", + "contributions": [ + "doc" + ] + }, + { + "login": "CodyScavenger", + "name": "Cody Scavenger", + "avatar_url": "https://avatars.githubusercontent.com/u/94334877?v=4", + "profile": "https://github.com/CodyScavenger", + "contributions": [ + "doc" + ] + }, + { + "login": "FWest98", + "name": "Floris Westerman", + "avatar_url": "https://avatars.githubusercontent.com/u/1918658?v=4", + "profile": "http://fwest98.nl/", + "contributions": [ + "code" + ] + }, + { + "login": "mjcarman", + "name": "Michael Carman", + "avatar_url": "https://avatars.githubusercontent.com/u/121028?v=4", + "profile": "https://github.com/mjcarman", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "entr0pia", + "name": "风沐白", + "avatar_url": "https://avatars.githubusercontent.com/u/30486766?v=4", + "profile": "https://github.com/entr0pia", + "contributions": [ + "design" + ] + }, + { + "login": "schallm", + "name": "Michael T. Schall", + "avatar_url": "https://avatars.githubusercontent.com/u/331167?v=4", + "profile": "https://github.com/schallm", + "contributions": [ + "design" + ] + }, + { + "login": "craiglpeters", + "name": "Craig Peters", + "avatar_url": "https://avatars.githubusercontent.com/u/9445180?v=4", + "profile": "https://github.com/craiglpeters", + "contributions": [ + "doc" + ] + }, + { + "login": "dorian-li", + "name": "Dongyu Li", + "avatar_url": "https://avatars.githubusercontent.com/u/49279922?v=4", + "profile": "https://github.com/dorian-li", + "contributions": [ + "design" + ] + }, + { + "login": "cyberbliss", + "name": "Stephen Judd", + "avatar_url": "https://avatars.githubusercontent.com/u/5401528?v=4", + "profile": "https://github.com/cyberbliss", + "contributions": [ + "code" + ] + }, + { + "login": "douugdev", + "name": "Douglas Silva", + "avatar_url": "https://avatars.githubusercontent.com/u/59324692?v=4", + "profile": "https://douug.dev", + "contributions": [ + "doc" + ] + }, + { + "login": "BoseSj", + "name": "SJ Basak", + "avatar_url": "https://avatars.githubusercontent.com/u/58129377?v=4", + "profile": "https://github.com/BoseSj", + "contributions": [ + "design" + ] + }, + { + "login": "treed", + "name": "Ted Reed", + "avatar_url": "https://avatars.githubusercontent.com/u/71910?v=4", + "profile": "http://tedreed.info", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "asportnoy", + "name": "Albert Portnoy", + "avatar_url": "https://avatars.githubusercontent.com/u/14863373?v=4", + "profile": "http://albertp.dev", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Lemorz56", + "name": "Sebastian", + "avatar_url": "https://avatars.githubusercontent.com/u/1346676?v=4", + "profile": "https://www.msbrg.net/", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "mirsella", + "name": "Lucas", + "avatar_url": "https://avatars.githubusercontent.com/u/45905567?v=4", + "profile": "https://github.com/mirsella", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "ethansocal", + "name": "Ethan", + "avatar_url": "https://avatars.githubusercontent.com/u/79533577?v=4", + "profile": "https://github.com/ethansocal", + "contributions": [ + "doc" + ] + }, + { + "login": "astronaako", + "name": "Mohamed Naamy", + "avatar_url": "https://avatars.githubusercontent.com/u/18577543?v=4", + "profile": "https://github.com/astronaako", + "contributions": [ + "design" + ] + }, + { + "login": "bend-n", + "name": "bendn", + "avatar_url": "https://avatars.githubusercontent.com/u/70787919?v=4", + "profile": "http://bend-n.github.io", + "contributions": [ + "design" + ] + }, + { + "login": "davidanthoff", + "name": "David Anthoff", + "avatar_url": "https://avatars.githubusercontent.com/u/1036561?v=4", + "profile": "http://www.david-anthoff.com", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "jooooel", + "name": "jooooel", + "avatar_url": "https://avatars.githubusercontent.com/u/9303280?v=4", + "profile": "https://github.com/jooooel", + "contributions": [ + "doc" + ] + }, + { + "login": "maxlandon", + "name": "maxlandon", + "avatar_url": "https://avatars.githubusercontent.com/u/25826036?v=4", + "profile": "https://github.com/maxlandon", + "contributions": [ + "code" + ] + }, + { + "login": "lino-levan", + "name": "Lino Le Van", + "avatar_url": "https://avatars.githubusercontent.com/u/11367844?v=4", + "profile": "https://linolevan.com", + "contributions": [ + "doc" + ] + }, + { + "login": "dvlprJobayer", + "name": "Jobayer Ahammed Patwary", + "avatar_url": "https://avatars.githubusercontent.com/u/76583359?v=4", + "profile": "https://github.com/dvlprJobayer", + "contributions": [ + "design" + ] + }, + { + "login": "NoF0rte", + "name": "NoF0rte", + "avatar_url": "https://avatars.githubusercontent.com/u/64100993?v=4", + "profile": "https://github.com/NoF0rte", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "LNKLEO", + "name": "LNKLEO", + "avatar_url": "https://avatars.githubusercontent.com/u/10334184?v=4", + "profile": "https://github.com/LNKLEO", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "kamfaima", + "name": "kamfaima", + "avatar_url": "https://avatars.githubusercontent.com/u/23546392?v=4", + "profile": "https://github.com/kamfaima", + "contributions": [ + "design" + ] + }, + { + "login": "dhrdlicka", + "name": "David Hrdlička", + "avatar_url": "https://avatars.githubusercontent.com/u/13226155?v=4", + "profile": "https://github.com/dhrdlicka", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "davidcourtney", + "name": "David Courtney", + "avatar_url": "https://avatars.githubusercontent.com/u/1019134?v=4", + "profile": "http://davidcourtney.com", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "Jensdevloo", + "name": "jensdevloo", + "avatar_url": "https://avatars.githubusercontent.com/u/2276152?v=4", + "profile": "https://github.com/Jensdevloo", + "contributions": [ + "doc" + ] + }, + { + "login": "thomasdoerr", + "name": "Thomas Dörr", + "avatar_url": "https://avatars.githubusercontent.com/u/6919685?v=4", + "profile": "https://github.com/thomasdoerr", + "contributions": [ + "design" + ] + }, + { + "login": "SvenAelterman", + "name": "Sven Aelterman", + "avatar_url": "https://avatars.githubusercontent.com/u/17446043?v=4", + "profile": "https://blog.aelterman.com", + "contributions": [ + "doc" + ] + }, + { + "login": "CodexLink", + "name": "Janrey Licas", + "avatar_url": "https://avatars.githubusercontent.com/u/5953927?v=4", + "profile": "https://github.com/CodexLink", + "contributions": [ + "design", + "doc", + "code" + ] + }, + { + "login": "padilo", + "name": "Pablo Díaz-López", + "avatar_url": "https://avatars.githubusercontent.com/u/783959?v=4", + "profile": "https://github.com/padilo", + "contributions": [ + "doc" + ] + }, + { + "login": "DarkMagicSource", + "name": "Caitlyn Williams", + "avatar_url": "https://avatars.githubusercontent.com/u/35950530?v=4", + "profile": "https://github.com/DarkMagicSource", + "contributions": [ + "doc" + ] + }, + { + "login": "gork3n", + "name": "Christopher Henderson", + "avatar_url": "https://avatars.githubusercontent.com/u/1086155?v=4", + "profile": "https://github.com/gork3n", + "contributions": [ + "design" + ] + }, + { + "login": "cabauman", + "name": "Colt", + "avatar_url": "https://avatars.githubusercontent.com/u/6819362?v=4", + "profile": "https://www.coltbauman.com", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "craftzneko", + "name": "craftzneko", + "avatar_url": "https://avatars.githubusercontent.com/u/662108?v=4", + "profile": "https://github.com/craftzneko", + "contributions": [ + "doc" + ] + }, + { + "login": "atlanswer", + "name": "甘亭", + "avatar_url": "https://avatars.githubusercontent.com/u/17683244?v=4", + "profile": "http://waferlab.dev", + "contributions": [ + "doc" + ] + }, + { + "login": "Mertsch", + "name": "Mertsch", + "avatar_url": "https://avatars.githubusercontent.com/u/9402861?v=4", + "profile": "https://github.com/Mertsch", + "contributions": [ + "doc" + ] + }, + { + "login": "marc2332", + "name": "Marc Espín", + "avatar_url": "https://avatars.githubusercontent.com/u/38158676?v=4", + "profile": "https://mespin.me/", + "contributions": [ + "code" + ] + }, + { + "login": "ksdpmx", + "name": "jasonz", + "avatar_url": "https://avatars.githubusercontent.com/u/3256083?v=4", + "profile": "https://github.com/ksdpmx", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "bsiegert", + "name": "Benny Siegert", + "avatar_url": "https://avatars.githubusercontent.com/u/866330?v=4", + "profile": "https://bentsukun.ch", + "contributions": [ + "code" + ] + }, + { + "login": "kema-dev", + "name": "kema", + "avatar_url": "https://avatars.githubusercontent.com/u/54537427?v=4", + "profile": "http://www.kemadev.fr/fr/", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "mavaddat", + "name": "Mavaddat Javid", + "avatar_url": "https://avatars.githubusercontent.com/u/5055400?v=4", + "profile": "http://mavaddat.ca", + "contributions": [ + "code" + ] + }, + { + "login": "iavael", + "name": "Iavael", + "avatar_url": "https://avatars.githubusercontent.com/u/905853?v=4", + "profile": "https://iavael.name/", + "contributions": [ + "code" + ] + }, + { + "login": "Kushal-Chandar", + "name": "Kushal-Chandar", + "avatar_url": "https://avatars.githubusercontent.com/u/83660514?v=4", + "profile": "https://github.com/Kushal-Chandar", + "contributions": [ + "design" + ] + }, + { + "login": "BigBear0812", + "name": "Matthew Miller", + "avatar_url": "https://avatars.githubusercontent.com/u/2429638?v=4", + "profile": "http://www.project-miller.com/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "javidcf", + "name": "Javier Dehesa", + "avatar_url": "https://avatars.githubusercontent.com/u/1098280?v=4", + "profile": "https://github.com/javidcf", + "contributions": [ + "code" + ] + }, + { + "login": "alexvy86", + "name": "Alex Villarreal", + "avatar_url": "https://avatars.githubusercontent.com/u/716334?v=4", + "profile": "https://alex-v.blog/", + "contributions": [ + "code" + ] + }, + { + "login": "krzysdz", + "name": "krzysdz", + "avatar_url": "https://avatars.githubusercontent.com/u/12915102?v=4", + "profile": "https://github.com/krzysdz", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "BasLijten", + "name": "Bas Lijten", + "avatar_url": "https://avatars.githubusercontent.com/u/11842067?v=4", + "profile": "http://blog.baslijten.com", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "ParkerM", + "name": "Parker Mauney", + "avatar_url": "https://avatars.githubusercontent.com/u/5124113?v=4", + "profile": "https://github.com/ParkerM", + "contributions": [ + "design" + ] + }, + { + "login": "gbrusella", + "name": "Gonzalo Brusella", + "avatar_url": "https://avatars.githubusercontent.com/u/115679?v=4", + "profile": "http://www.brusella.com.ar", + "contributions": [ + "doc" + ] + }, + { + "login": "krokofant", + "name": "Emil Sundin", + "avatar_url": "https://avatars.githubusercontent.com/u/5908498?v=4", + "profile": "https://github.com/krokofant", + "contributions": [ + "doc" + ] + }, + { + "login": "dysuby", + "name": "dysuby", + "avatar_url": "https://avatars.githubusercontent.com/u/26317510?v=4", + "profile": "http://dysuby.github.io", + "contributions": [ + "doc" + ] + }, + { + "login": "dorokhin-bohdan", + "name": "Bohdan Dorokhin", + "avatar_url": "https://avatars.githubusercontent.com/u/24988081?v=4", + "profile": "https://github.com/dorokhin-bohdan", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "CY-Pan", + "name": "Ad Red", + "avatar_url": "https://avatars.githubusercontent.com/u/59761962?v=4", + "profile": "https://github.com/CY-Pan", + "contributions": [ + "design" + ] + }, + { + "login": "nopeless", + "name": "nopeless", + "avatar_url": "https://avatars.githubusercontent.com/u/38830903?v=4", + "profile": "https://github.com/nopeless", + "contributions": [ + "code", + "doc", + "design" + ] + }, + { + "login": "vinhloc30796", + "name": "Loc Nguyen", + "avatar_url": "https://avatars.githubusercontent.com/u/19675202?v=4", + "profile": "https://linkedin.com/in/vinhloc30796", + "contributions": [ + "doc" + ] + }, + { + "login": "Coder-Tavi", + "name": "Tavi", + "avatar_url": "https://avatars.githubusercontent.com/u/66774833?v=4", + "profile": "https://tavis.page", + "contributions": [ + "doc" + ] + }, + { + "login": "NicholasDawson", + "name": "Nick Dawson", + "avatar_url": "https://avatars.githubusercontent.com/u/37987430?v=4", + "profile": "http://ndawson.me", + "contributions": [ + "doc" + ] + }, + { + "login": "jntrnr", + "name": "JT", + "avatar_url": "https://avatars.githubusercontent.com/u/547158?v=4", + "profile": "https://www.jntrnr.com/", + "contributions": [ + "code" + ] + }, + { + "login": "ChandanChainani", + "name": "ChandanChainani", + "avatar_url": "https://avatars.githubusercontent.com/u/28807775?v=4", + "profile": "https://github.com/ChandanChainani", + "contributions": [ + "doc" + ] + }, + { + "login": "jenspinney", + "name": "Jen Spinney", + "avatar_url": "https://avatars.githubusercontent.com/u/3200507?v=4", + "profile": "https://github.com/jenspinney", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "rotu", + "name": "Dan Rose", + "avatar_url": "https://avatars.githubusercontent.com/u/119948?v=4", + "profile": "https://github.com/rotu", + "contributions": [ + "code" + ] + }, + { + "login": "darthwalsh", + "name": "Carl Walsh", + "avatar_url": "https://avatars.githubusercontent.com/u/2829438?v=4", + "profile": "https://carlwa.com", + "contributions": [ + "doc" + ] + }, + { + "login": "ercpereda", + "name": "Ernesto R. C. Pereda", + "avatar_url": "https://avatars.githubusercontent.com/u/13546685?v=4", + "profile": "https://github.com/ercpereda", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "0Ky", + "name": "cryptix", + "avatar_url": "https://avatars.githubusercontent.com/u/16103757?v=4", + "profile": "https://github.com/0Ky", + "contributions": [ + "doc" + ] + }, + { + "login": "ehawman", + "name": "Evan Hawman", + "avatar_url": "https://avatars.githubusercontent.com/u/52979227?v=4", + "profile": "https://github.com/ehawman", + "contributions": [ + "design" + ] + }, + { + "login": "ZerdoX-x", + "name": "Mark Lansky", + "avatar_url": "https://avatars.githubusercontent.com/u/49815452?v=4", + "profile": "https://zerdox.dev", + "contributions": [ + "design" + ] + }, + { + "login": "pulsation", + "name": "pulsation", + "avatar_url": "https://avatars.githubusercontent.com/u/1838397?v=4", + "profile": "https://github.com/pulsation", + "contributions": [ + "code" + ] + }, + { + "login": "oriionn", + "name": "orionsource", + "avatar_url": "https://avatars.githubusercontent.com/u/38093786?v=4", + "profile": "https://oriondev.fr", + "contributions": [ + "design" + ] + }, + { + "login": "CesarGBkR", + "name": "Cesar Garduño", + "avatar_url": "https://avatars.githubusercontent.com/u/99093357?v=4", + "profile": "https://github.com/CesarGBkR", + "contributions": [ + "doc" + ] + }, + { + "login": "Adi-vig", + "name": "Aditya Sakhare", + "avatar_url": "https://avatars.githubusercontent.com/u/123308369?v=4", + "profile": "https://github.com/Adi-vig", + "contributions": [ + "design" + ] + }, + { + "login": "deepak-dev-96", + "name": "Deepak Dev", + "avatar_url": "https://avatars.githubusercontent.com/u/134447761?v=4", + "profile": "https://github.com/deepak-dev-96", + "contributions": [ + "doc" + ] + }, + { + "login": "warrenbuckley", + "name": "Warren Buckley", + "avatar_url": "https://avatars.githubusercontent.com/u/1389894?v=4", + "profile": "http://creativewebspecialist.co.uk", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "LunarMarathon", + "name": "LunarMarathon", + "avatar_url": "https://avatars.githubusercontent.com/u/113847439?v=4", + "profile": "https://github.com/LunarMarathon", + "contributions": [ + "doc" + ] + }, + { + "login": "ginglis13", + "name": "Gavin Inglis", + "avatar_url": "https://avatars.githubusercontent.com/u/43075615?v=4", + "profile": "https://ginglis.me", + "contributions": [ + "code" + ] + }, + { + "login": "jaliyaudagedara", + "name": "Jaliya Udagedara", + "avatar_url": "https://avatars.githubusercontent.com/u/5653381?v=4", + "profile": "http://jaliyaudagedara.blogspot.com", + "contributions": [ + "doc" + ] + }, + { + "login": "BPplays", + "name": "BPplays", + "avatar_url": "https://avatars.githubusercontent.com/u/58504799?v=4", + "profile": "https://github.com/BPplays", + "contributions": [ + "code" + ] + }, + { + "login": "mateusz-bajorek", + "name": "Mateusz Bajorek", + "avatar_url": "https://avatars.githubusercontent.com/u/11185738?v=4", + "profile": "https://github.com/mateusz-bajorek", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "joshbduncan", + "name": "Josh Duncan", + "avatar_url": "https://avatars.githubusercontent.com/u/44387852?v=4", + "profile": "http://joshbduncan.com", + "contributions": [ + "doc" + ] + }, + { + "login": "princesaini", + "name": "Prince Saini", + "avatar_url": "https://avatars.githubusercontent.com/u/25565506?v=4", + "profile": "https://github.com/princesaini", + "contributions": [ + "design" + ] + }, + { + "login": "fabriciojlm", + "name": "fabriciojlm", + "avatar_url": "https://avatars.githubusercontent.com/u/70244182?v=4", + "profile": "https://www.linkedin.com/in/fabriciojuliano/", + "contributions": [ + "design" + ] + }, + { + "login": "SriRamanujam", + "name": "Sri Ramanujam", + "avatar_url": "https://avatars.githubusercontent.com/u/2983875?v=4", + "profile": "https://github.com/SriRamanujam", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Juneezee", + "name": "Eng Zer Jun", + "avatar_url": "https://avatars.githubusercontent.com/u/20135478?v=4", + "profile": "https://github.com/Juneezee", + "contributions": [ + "code" + ] + }, + { + "login": "AlexJPotter", + "name": "Alex Potter", + "avatar_url": "https://avatars.githubusercontent.com/u/14200888?v=4", + "profile": "https://alexpotter.dev", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "mishmanners", + "name": "Michelle Mannering", + "avatar_url": "https://avatars.githubusercontent.com/u/36594527?v=4", + "profile": "http://mishmanners.info", + "contributions": [ + "doc" + ] + }, + { + "login": "paulomorgado", + "name": "Paulo Morgado", + "avatar_url": "https://avatars.githubusercontent.com/u/470455?v=4", + "profile": "https://github.com/paulomorgado", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "joadoumie", + "name": "joadoumie", + "avatar_url": "https://avatars.githubusercontent.com/u/98557455?v=4", + "profile": "https://github.com/joadoumie", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "flanakin", + "name": "Michael Flanakin", + "avatar_url": "https://avatars.githubusercontent.com/u/399533?v=4", + "profile": "http://about.me/flanakin", + "contributions": [ + "doc" + ] + }, + { + "login": "thiagoszbarros", + "name": "Thiago Barros", + "avatar_url": "https://avatars.githubusercontent.com/u/88802518?v=4", + "profile": "https://www.linkedin.com/in/thiagobarros95/", + "contributions": [ + "design" + ] + }, + { + "login": "TendTo", + "name": "Tend", + "avatar_url": "https://avatars.githubusercontent.com/u/65033249?v=4", + "profile": "https://github.com/TendTo", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "KibbeWater", + "name": "Snow", + "avatar_url": "https://avatars.githubusercontent.com/u/35224538?v=4", + "profile": "https://kibbewater.com", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "randombenj", + "name": "Benj Fassbind", + "avatar_url": "https://avatars.githubusercontent.com/u/5184499?v=4", + "profile": "https://github.com/randombenj", + "contributions": [ + "code" + ] + }, + { + "login": "liudonghua123", + "name": "liudonghua", + "avatar_url": "https://avatars.githubusercontent.com/u/2276718?v=4", + "profile": "http://blog.liudonghua.top", + "contributions": [ + "code" + ] + }, + { + "login": "Somoy73", + "name": "Somoy Subandhu", + "avatar_url": "https://avatars.githubusercontent.com/u/40368688?v=4", + "profile": "http://somoy.me", + "contributions": [ + "design" + ] + }, + { + "login": "oleksbabieiev", + "name": "Oleksandr Babieiev", + "avatar_url": "https://avatars.githubusercontent.com/u/64398691?v=4", + "profile": "https://github.com/oleksbabieiev", + "contributions": [ + "code", + "doc", + "design" + ] + }, + { + "login": "mrbeardad", + "name": "Heache Bear", + "avatar_url": "https://avatars.githubusercontent.com/u/54128430?v=4", + "profile": "https://github.com/mrbeardad", + "contributions": [ + "doc" + ] + }, + { + "login": "ChrisNSki", + "name": "Christopher Narowski", + "avatar_url": "https://avatars.githubusercontent.com/u/125232146?v=4", + "profile": "http://ensif.com", + "contributions": [ + "design" + ] + }, + { + "login": "sino1641", + "name": "Sin", + "avatar_url": "https://avatars.githubusercontent.com/u/13870295?v=4", + "profile": "https://github.com/sino1641", + "contributions": [ + "doc" + ] + }, + { + "login": "kkk669", + "name": "Kenta Kubo", + "avatar_url": "https://avatars.githubusercontent.com/u/601636?v=4", + "profile": "https://kebo.xyz", + "contributions": [ + "doc" + ] + }, + { + "login": "mfedatto", + "name": "MFedatto", + "avatar_url": "https://avatars.githubusercontent.com/u/5623739?v=4", + "profile": "http://mfedatto.com", + "contributions": [ + "doc" + ] + }, + { + "login": "RiikkaDream", + "name": "Riikka", + "avatar_url": "https://avatars.githubusercontent.com/u/56921531?v=4", + "profile": "https://www.linkedin.com/in/riikka-l-861694b2/", + "contributions": [ + "doc" + ] + }, + { + "login": "srpmtt", + "name": "srpmtt", + "avatar_url": "https://avatars.githubusercontent.com/u/11175503?v=4", + "profile": "https://github.com/srpmtt", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "Chris-Johnston", + "name": "Chris Johnston", + "avatar_url": "https://avatars.githubusercontent.com/u/16418643?v=4", + "profile": "https://chris-johnston.me", + "contributions": [ + "doc" + ] + }, + { + "login": "Daimonion1980", + "name": "Thomas", + "avatar_url": "https://avatars.githubusercontent.com/u/12880413?v=4", + "profile": "https://github.com/Daimonion1980", + "contributions": [ + "design" + ] + }, + { + "login": "VEERT00X", + "name": "Veko", + "avatar_url": "https://avatars.githubusercontent.com/u/72668825?v=4", + "profile": "https://veert00x.com", + "contributions": [ + "doc" + ] + }, + { + "login": "lucascosti", + "name": "Lucas Costi", + "avatar_url": "https://avatars.githubusercontent.com/u/4434330?v=4", + "profile": "https://lucascosti.com", + "contributions": [ + "code" + ] + }, + { + "login": "gergelyk", + "name": "Grzegorz Krasoń", + "avatar_url": "https://avatars.githubusercontent.com/u/11185582?v=4", + "profile": "http://krason.dev/", + "contributions": [ + "code" + ] + }, + { + "login": "rockyoung", + "name": "rockyoung", + "avatar_url": "https://avatars.githubusercontent.com/u/1207971?v=4", + "profile": "https://github.com/rockyoung", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "shravanasati", + "name": "Shravan Asati", + "avatar_url": "https://avatars.githubusercontent.com/u/69118069?v=4", + "profile": "https://github.com/shravanasati", + "contributions": [ + "design" + ] + }, + { + "login": "lzecca78", + "name": "Luca Zecca", + "avatar_url": "https://avatars.githubusercontent.com/u/3881844?v=4", + "profile": "https://github.com/lzecca78", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "jreilly-lukava", + "name": "Joshua Reilly", + "avatar_url": "https://avatars.githubusercontent.com/u/30353736?v=4", + "profile": "https://github.com/jreilly-lukava", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "ivan-the-terrible", + "name": "Ivan", + "avatar_url": "https://avatars.githubusercontent.com/u/56458442?v=4", + "profile": "https://ivan-the-terrible.github.io/", + "contributions": [ + "design", + "doc", + "code" + ] + }, + { + "login": "mountcount", + "name": "mountcount", + "avatar_url": "https://avatars.githubusercontent.com/u/166301065?v=4", + "profile": "https://github.com/mountcount", + "contributions": [ + "doc" + ] + }, + { + "login": "Bondrake", + "name": "Bondrake", + "avatar_url": "https://avatars.githubusercontent.com/u/11696?v=4", + "profile": "https://github.com/Bondrake", + "contributions": [ + "design", + "code" + ] + }, + { + "login": "R00dRallec", + "name": "R00dRallec", + "avatar_url": "https://avatars.githubusercontent.com/u/9081954?v=4", + "profile": "https://github.com/R00dRallec", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "publicfacingusername", + "name": "Justin Wolfington", + "avatar_url": "https://avatars.githubusercontent.com/u/13956145?v=4", + "profile": "https://github.com/publicfacingusername", + "contributions": [ + "code" + ] + }, + { + "login": "jtracey93", + "name": "Jack Tracey", + "avatar_url": "https://avatars.githubusercontent.com/u/41163455?v=4", + "profile": "https://bio.link/jacktracey", + "contributions": [ + "design" + ] + }, + { + "login": "MarkDaveny", + "name": "MarkDaveny", + "avatar_url": "https://avatars.githubusercontent.com/u/168091250?v=4", + "profile": "https://github.com/MarkDaveny", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "tiwahu", + "name": "Timothy Huber", + "avatar_url": "https://avatars.githubusercontent.com/u/590564?v=4", + "profile": "http://www.tiwahu.com/", + "contributions": [ + "design" + ] + }, + { + "login": "YashJM", + "name": "Yash Mistry", + "avatar_url": "https://avatars.githubusercontent.com/u/63824041?v=4", + "profile": "http://yashjmistry.me", + "contributions": [ + "design" + ] + }, + { + "login": "jlabonski", + "name": "Jeffrey Labonski", + "avatar_url": "https://avatars.githubusercontent.com/u/2981369?v=4", + "profile": "https://github.com/jlabonski", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "herbygillot", + "name": "Herby Gillot", + "avatar_url": "https://avatars.githubusercontent.com/u/618376?v=4", + "profile": "https://github.com/herbygillot", + "contributions": [ + "doc" + ] + }, + { + "login": "arjan-s", + "name": "arjan-s", + "avatar_url": "https://avatars.githubusercontent.com/u/10400299?v=4", + "profile": "https://github.com/arjan-s", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "0323pin", + "name": "pin", + "avatar_url": "https://avatars.githubusercontent.com/u/90570748?v=4", + "profile": "https://github.com/0323pin", + "contributions": [ + "code" + ] + }, + { + "login": "FireIsGood", + "name": "FireIsGood", + "avatar_url": "https://avatars.githubusercontent.com/u/109556932?v=4", + "profile": "http://fireis.dev", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "Joxtacy", + "name": "Jesper Hasselquist", + "avatar_url": "https://avatars.githubusercontent.com/u/10127673?v=4", + "profile": "https://github.com/Joxtacy", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "aaronpowell", + "name": "Aaron Powell", + "avatar_url": "https://avatars.githubusercontent.com/u/434140?v=4", + "profile": "https://www.aaron-powell.com", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "Dartypier", + "name": "Jacopo Zecchi", + "avatar_url": "https://avatars.githubusercontent.com/u/22201626?v=4", + "profile": "https://github.com/Dartypier", + "contributions": [ + "doc" + ] + }, + { + "login": "rose-m", + "name": "Michael Rose", + "avatar_url": "https://avatars.githubusercontent.com/u/4354632?v=4", + "profile": "https://github.com/rose-m", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "denehoffman", + "name": "Nathaniel D. Hoffman", + "avatar_url": "https://avatars.githubusercontent.com/u/36977879?v=4", + "profile": "http://denehoffman.com", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "michaelschwobe", + "name": "Michael Schwobe", + "avatar_url": "https://avatars.githubusercontent.com/u/926242?v=4", + "profile": "https://schwobe.dev", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "Nibodhika", + "name": "Nibodhika", + "avatar_url": "https://avatars.githubusercontent.com/u/729967?v=4", + "profile": "https://github.com/Nibodhika", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "sassdawe", + "name": "David Sass", + "avatar_url": "https://avatars.githubusercontent.com/u/10754765?v=4", + "profile": "http://davidsass.io", + "contributions": [ + "doc" + ] + }, + { + "login": "carehart", + "name": "Charlie Arehart", + "avatar_url": "https://avatars.githubusercontent.com/u/389746?v=4", + "profile": "http://www.carehart.org", + "contributions": [ + "doc" + ] + }, + { + "login": "aramikuto", + "name": "Aleksandr Kondrashov", + "avatar_url": "https://avatars.githubusercontent.com/u/116561995?v=4", + "profile": "https://github.com/aramikuto", + "contributions": [ + "doc" + ] + }, + { + "login": "kimsey0", + "name": "Jacob Bundgaard", + "avatar_url": "https://avatars.githubusercontent.com/u/984760?v=4", + "profile": "https://jacobbundgaard.dk", + "contributions": [ + "doc" + ] + }, + { + "login": "ThisaruGuruge", + "name": "Thisaru Guruge", + "avatar_url": "https://avatars.githubusercontent.com/u/40016057?v=4", + "profile": "https://thisaru.me", + "contributions": [ + "doc" + ] + }, + { + "login": "edwin-shdw", + "name": "Edwin", + "avatar_url": "https://avatars.githubusercontent.com/u/62764562?v=4", + "profile": "https://github.com/edwin-shdw", + "contributions": [ + "doc" + ] + }, + { + "login": "jcdickinson", + "name": "Jonathan Dickinson", + "avatar_url": "https://avatars.githubusercontent.com/u/522465?v=4", + "profile": "https://dickinson.id", + "contributions": [ + "doc" + ] + }, + { + "login": "po1o", + "name": "Polo-François Poli", + "avatar_url": "https://avatars.githubusercontent.com/u/5702825?v=4", + "profile": "https://github.com/po1o", + "contributions": [ + "code" + ] + }, + { + "login": "EDIflyer", + "name": "EDIflyer", + "avatar_url": "https://avatars.githubusercontent.com/u/13610277?v=4", + "profile": "https://github.com/EDIflyer", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "felipebz", + "name": "Felipe Zorzo", + "avatar_url": "https://avatars.githubusercontent.com/u/13829?v=4", + "profile": "https://felipezorzo.com.br", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "DeepSpace2", + "name": "Adi Vaknin", + "avatar_url": "https://avatars.githubusercontent.com/u/6841988?v=4", + "profile": "https://github.com/DeepSpace2", + "contributions": [ + "code", + "doc", + "design" + ] + }, + { + "login": "EladLeev", + "name": "Elad Leev", + "avatar_url": "https://avatars.githubusercontent.com/u/835319?v=4", + "profile": "https://leevs.dev/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Soyvolon", + "name": "Bounds", + "avatar_url": "https://avatars.githubusercontent.com/u/16871668?v=4", + "profile": "https://github.com/Soyvolon", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Yash-Garg", + "name": "Yash Garg", + "avatar_url": "https://avatars.githubusercontent.com/u/33605526?v=4", + "profile": "http://yashgarg.dev", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "sarpuser", + "name": "Sarp User", + "avatar_url": "https://avatars.githubusercontent.com/u/23362324?v=4", + "profile": "https://github.com/sarpuser", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "clemyan", + "name": "Clement Yan", + "avatar_url": "https://avatars.githubusercontent.com/u/41266433?v=4", + "profile": "https://github.com/clemyan", + "contributions": [ + "code" + ] + }, + { + "login": "thep0y", + "name": "thep0y", + "avatar_url": "https://avatars.githubusercontent.com/u/51874567?v=4", + "profile": "https://github.com/thep0y", + "contributions": [ + "code" + ] + }, + { + "login": "ClxUne09", + "name": "Artin", + "avatar_url": "https://avatars.githubusercontent.com/u/175628107?v=4", + "profile": "https://github.com/ClxUne09", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "guspan-tanadi", + "name": "Guspan Tanadi", + "avatar_url": "https://avatars.githubusercontent.com/u/36249910?v=4", + "profile": "https://github.com/guspan-tanadi", + "contributions": [ + "doc" + ] + }, + { + "login": "rocketraman", + "name": "Raman Gupta", + "avatar_url": "https://avatars.githubusercontent.com/u/53049?v=4", + "profile": "http://vivosys.com", + "contributions": [ + "doc" + ] + }, + { + "login": "hsnabszhdn", + "name": "Hossein Abbasi", + "avatar_url": "https://avatars.githubusercontent.com/u/16090309?v=4", + "profile": "https://github.com/hsnabszhdn", + "contributions": [ + "code" + ] + }, + { + "login": "kizivat", + "name": "David Kizivat", + "avatar_url": "https://avatars.githubusercontent.com/u/3535926?v=4", + "profile": "https://kizivat.eu", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "mgrubb", + "name": "Michael Grubb", + "avatar_url": "https://avatars.githubusercontent.com/u/351301?v=4", + "profile": "https://github.com/mgrubb", + "contributions": [ + "doc" + ] + }, + { + "login": "oliviaBahr", + "name": "Olivia Bahr", + "avatar_url": "https://avatars.githubusercontent.com/u/98684296?v=4", + "profile": "https://github.com/oliviaBahr", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "garysassano", + "name": "Gary Sassano", + "avatar_url": "https://avatars.githubusercontent.com/u/10464497?v=4", + "profile": "https://github.com/garysassano", + "contributions": [ + "design" + ] + }, + { + "login": "ilaumjd", + "name": "Ilham AM", + "avatar_url": "https://avatars.githubusercontent.com/u/16514431?v=4", + "profile": "https://github.com/ilaumjd", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "trajano", + "name": "Archimedes Trajano", + "avatar_url": "https://avatars.githubusercontent.com/u/110627?v=4", + "profile": "https://trajano.net/", + "contributions": [ + "doc" + ] + }, + { + "login": "devxpain", + "name": "devxpain", + "avatar_url": "https://avatars.githubusercontent.com/u/170700110?v=4", + "profile": "https://github.com/devxpain", + "contributions": [ + "doc" + ] + }, + { + "login": "AntoninRuan", + "name": "Antonin Ruan", + "avatar_url": "https://avatars.githubusercontent.com/u/43148004?v=4", + "profile": "https://www.antonin-ruan.fr", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "00ll00", + "name": "00ll00", + "avatar_url": "https://avatars.githubusercontent.com/u/40747228?v=4", + "profile": "https://github.com/00ll00", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "ernstc", + "name": "Ernesto Cianciotta", + "avatar_url": "https://avatars.githubusercontent.com/u/130360?v=4", + "profile": "https://devnotes.ernstc.net/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "eelispeltola", + "name": "Eelis Peltola", + "avatar_url": "https://avatars.githubusercontent.com/u/15069074?v=4", + "profile": "https://github.com/eelispeltola", + "contributions": [ + "code" + ] + }, + { + "login": "vshulcz", + "name": "Vlad Shulcz", + "avatar_url": "https://avatars.githubusercontent.com/u/99616188?v=4", + "profile": "https://github.com/vshulcz", + "contributions": [ + "code" + ] + }, + { + "login": "Silzinc", + "name": "Silzinc", + "avatar_url": "https://avatars.githubusercontent.com/u/128738169?v=4", + "profile": "https://github.com/Silzinc", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "Hampter", + "name": "Noah Springer", + "avatar_url": "https://avatars.githubusercontent.com/u/23213489?v=4", + "profile": "https://github.com/Hampter", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "dusktreader", + "name": "Tucker Beck", + "avatar_url": "https://avatars.githubusercontent.com/u/713676?v=4", + "profile": "https://github.com/dusktreader", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Pietrucci-Blacher", + "name": "Sunshio", + "avatar_url": "https://avatars.githubusercontent.com/u/38607067?v=4", + "profile": "https://mpb-dev.fr/", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "pashagolub", + "name": "Pavlo Golub", + "avatar_url": "https://avatars.githubusercontent.com/u/9463113?v=4", + "profile": "https://pashagolub.github.io/blog", + "contributions": [ + "doc" + ] + }, + { + "login": "heaths", + "name": "Heath Stewart", + "avatar_url": "https://avatars.githubusercontent.com/u/1532486?v=4", + "profile": "https://heaths.dev", + "contributions": [ + "code" + ] + }, + { + "login": "HypheX", + "name": "Xelph", + "avatar_url": "https://avatars.githubusercontent.com/u/29693543?v=4", + "profile": "https://xelph.me", + "contributions": [ + "design" + ] + }, + { + "login": "TristanLeclair", + "name": "Tristan Leclair-Vani", + "avatar_url": "https://avatars.githubusercontent.com/u/60434271?v=4", + "profile": "https://tristanleclair.github.io/personal-website/index.html", + "contributions": [ + "doc" + ] + }, + { + "login": "vil02", + "name": "Piotr Idzik", + "avatar_url": "https://avatars.githubusercontent.com/u/65706193?v=4", + "profile": "https://github.com/vil02", + "contributions": [ + "code" + ] + }, + { + "login": "wiyco", + "name": "wiyco", + "avatar_url": "https://avatars.githubusercontent.com/u/72733890?v=4", + "profile": "https://wiyco.dev", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "abhro", + "name": "abhro", + "avatar_url": "https://avatars.githubusercontent.com/u/5664668?v=4", + "profile": "https://github.com/abhro", + "contributions": [ + "doc" + ] + }, + { + "login": "spg-iwilson", + "name": "Ivan Wilson", + "avatar_url": "https://avatars.githubusercontent.com/u/25376734?v=4", + "profile": "https://sharepointgurus.net", + "contributions": [ + "design" + ] + }, + { + "login": "mdanish-kh", + "name": "Muhammad Danish", + "avatar_url": "https://avatars.githubusercontent.com/u/88161975?v=4", + "profile": "https://github.com/mdanish-kh", + "contributions": [ + "doc" + ] + }, + { + "login": "BoscoDomingo", + "name": "Bosco Domingo", + "avatar_url": "https://avatars.githubusercontent.com/u/46006784?v=4", + "profile": "https://dub.sh/boscodomingo", + "contributions": [ + "code" + ] + }, + { + "login": "Edu4rdSHL", + "name": "Eduard Tolosa", + "avatar_url": "https://avatars.githubusercontent.com/u/32582878?v=4", + "profile": "https://edu4rdshl.dev", + "contributions": [ + "design", + "doc" + ] + }, + { + "login": "JamesAndrewJackson13", + "name": "James Jackson", + "avatar_url": "https://avatars.githubusercontent.com/u/27647566?v=4", + "profile": "https://github.com/JamesAndrewJackson13", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Mr-Vipi", + "name": "Jul Guga", + "avatar_url": "https://avatars.githubusercontent.com/u/58825526?v=4", + "profile": "https://github.com/Mr-Vipi", + "contributions": [ + "design" + ] + }, + { + "login": "tiaoxizhan", + "name": "tiaoxizhan", + "avatar_url": "https://avatars.githubusercontent.com/u/178074436?v=4", + "profile": "http://txzhan.io", + "contributions": [ + "code" + ] + }, + { + "login": "chrisant996", + "name": "Chris Antos", + "avatar_url": "https://avatars.githubusercontent.com/u/17440311?v=4", + "profile": "https://github.com/chrisant996", + "contributions": [ + "code" + ] + }, + { + "login": "rbleattler", + "name": "Robert Bleattler", + "avatar_url": "https://avatars.githubusercontent.com/u/40604784?v=4", + "profile": "https://robertbleattler.com", + "contributions": [ + "design" + ] + }, + { + "login": "d3v2a", + "name": "dev2a", + "avatar_url": "https://avatars.githubusercontent.com/u/1815655?v=4", + "profile": "https://artis-auxilium.fr/fr", + "contributions": [ + "code" + ] + }, + { + "login": "luisegarduno", + "name": "Luis", + "avatar_url": "https://avatars.githubusercontent.com/u/30121656?v=4", + "profile": "http://gardunos.tech", + "contributions": [ + "code" + ] + }, + { + "login": "tleepa", + "name": "Leepa", + "avatar_url": "https://avatars.githubusercontent.com/u/7734919?v=4", + "profile": "https://github.com/tleepa", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "raylu", + "name": "raylu", + "avatar_url": "https://avatars.githubusercontent.com/u/90059?v=4", + "profile": "https://blog.raylu.net", + "contributions": [ + "code", + "doc", + "design" + ] + }, + { + "login": "lechwolowski", + "name": "Lech Wołowski", + "avatar_url": "https://avatars.githubusercontent.com/u/33866950?v=4", + "profile": "https://github.com/lechwolowski", + "contributions": [ + "code" + ] + }, + { + "login": "OwlBurst", + "name": "Owl Burst", + "avatar_url": "https://avatars.githubusercontent.com/u/158167545?v=4", + "profile": "https://github.com/OwlBurst", + "contributions": [ + "doc" + ] + }, + { + "login": "RubixDev", + "name": "Silas Groh", + "avatar_url": "https://avatars.githubusercontent.com/u/35602040?v=4", + "profile": "http://rubixdev.de", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "mwiedemeyer", + "name": "Marco Wiedemeyer", + "avatar_url": "https://avatars.githubusercontent.com/u/4295189?v=4", + "profile": "https://mwiede.me/blog", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "0-0-1-0-1-0-1-0", + "name": "0-0-1-0-1-0-1-0", + "avatar_url": "https://avatars.githubusercontent.com/u/43226073?v=4", + "profile": "https://github.com/0-0-1-0-1-0-1-0", + "contributions": [ + "design" + ] + }, + { + "login": "player131007", + "name": "player131007", + "avatar_url": "https://avatars.githubusercontent.com/u/77326303?v=4", + "profile": "https://github.com/player131007", + "contributions": [ + "code" + ] + }, + { + "login": "kaien07", + "name": "kaien07", + "avatar_url": "https://avatars.githubusercontent.com/u/160471571?v=4", + "profile": "https://github.com/kaien07", + "contributions": [ + "code" + ] + }, + { + "login": "BusHero", + "name": "Cervac Petru", + "avatar_url": "https://avatars.githubusercontent.com/u/24370515?v=4", + "profile": "https://github.com/BusHero", + "contributions": [ + "design" + ] + }, + { + "login": "Marukome0743", + "name": "マルコメ", + "avatar_url": "https://avatars.githubusercontent.com/u/146040408?v=4", + "profile": "https://github.com/Marukome0743", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "mreinhardt", + "name": "Michael Reinhardt", + "avatar_url": "https://avatars.githubusercontent.com/u/582461?v=4", + "profile": "https://github.com/mreinhardt", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "AspectBruise09", + "name": "Artin", + "avatar_url": "https://avatars.githubusercontent.com/u/141767586?v=4", + "profile": "https://github.com/AspectBruise09", + "contributions": [ + "design" + ] + }, + { + "login": "b-simjoo", + "name": "Behnam Simjoo", + "avatar_url": "https://avatars.githubusercontent.com/u/117530839?v=4", + "profile": "http://bsimjoo.pcworms.ir", + "contributions": [ + "code" + ] + }, + { + "login": "plamendelchev", + "name": "Plamen Delchev", + "avatar_url": "https://avatars.githubusercontent.com/u/25668366?v=4", + "profile": "https://github.com/plamendelchev", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "beaualbritton", + "name": "beau albritton", + "avatar_url": "https://avatars.githubusercontent.com/u/112587801?v=4", + "profile": "https://github.com/beaualbritton", + "contributions": [ + "code" + ] + }, + { + "login": "Cierra-Runis", + "name": "Cierra-Runis", + "avatar_url": "https://avatars.githubusercontent.com/u/29329988?v=4", + "profile": "https://note-of-me.top", + "contributions": [ + "design" + ] + }, + { + "login": "jasonm23", + "name": "Jason Milkins", + "avatar_url": "https://avatars.githubusercontent.com/u/71587?v=4", + "profile": "https://github.com/jasonm23", + "contributions": [ + "code" + ] + }, + { + "login": "arjunrbery", + "name": "arjunrbery", + "avatar_url": "https://avatars.githubusercontent.com/u/20059577?v=4", + "profile": "http://www.arb.dev", + "contributions": [ + "doc" + ] + }, + { + "login": "JamBalaya56562", + "name": "Jam Balaya", + "avatar_url": "https://avatars.githubusercontent.com/u/88115388?v=4", + "profile": "https://github.com/JamBalaya56562", + "contributions": [ + "doc", + "code", + "design" + ] + }, + { + "login": "RichLewis007", + "name": "Rich Lewis", + "avatar_url": "https://avatars.githubusercontent.com/u/1149213?v=4", + "profile": "https://github.com/RichLewis007", + "contributions": [ + "doc", + "design" + ] + }, + { + "login": "Gijsreyn", + "name": "Gijs Reijn", + "avatar_url": "https://avatars.githubusercontent.com/u/26114636?v=4", + "profile": "https://gijsreijn.medium.com/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "mikelolasagasti", + "name": "Mikel Olasagasti Uranga", + "avatar_url": "https://avatars.githubusercontent.com/u/773148?v=4", + "profile": "https://mikel.olasagasti.info", + "contributions": [ + "code" + ] + }, + { + "login": "mkvlrn", + "name": "mkvlrn", + "avatar_url": "https://avatars.githubusercontent.com/u/186238078?v=4", + "profile": "https://github.com/mkvlrn", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "iandunn", + "name": "Ian Dunn", + "avatar_url": "https://avatars.githubusercontent.com/u/484068?v=4", + "profile": "https://iandunn.name", + "contributions": [ + "doc" + ] + }, + { + "login": "sanki92", + "name": "Sankalp Tripathi", + "avatar_url": "https://avatars.githubusercontent.com/u/70330866?v=4", + "profile": "https://github.com/sanki92", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "MariusStorhaug", + "name": "Marius Storhaug", + "avatar_url": "https://avatars.githubusercontent.com/u/17722253?v=4", + "profile": "https://github.com/PSModule", + "contributions": [ + "doc" + ] + }, + { + "login": "ADIX7", + "name": "Kovács Ádám", + "avatar_url": "https://avatars.githubusercontent.com/u/10939090?v=4", + "profile": "https://github.com/ADIX7", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "spidersouris", + "name": "Enzo Doyen", + "avatar_url": "https://avatars.githubusercontent.com/u/7102007?v=4", + "profile": "https://www.edoyen.com/", + "contributions": [ + "doc" + ] + }, + { + "login": "Pinta365", + "name": "Pinta", + "avatar_url": "https://avatars.githubusercontent.com/u/19735646?v=4", + "profile": "https://pinta.land", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "scop", + "name": "Ville Skyttä", + "avatar_url": "https://avatars.githubusercontent.com/u/109152?v=4", + "profile": "https://github.com/scop", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "anujsrc", + "name": "Anuj Kumar", + "avatar_url": "https://avatars.githubusercontent.com/u/1001682?v=4", + "profile": "http://linkedin.com/in/anujsays", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "ValerioCeccarelli", + "name": "Valerio Ceccarelli", + "avatar_url": "https://avatars.githubusercontent.com/u/42637334?v=4", + "profile": "https://github.com/ValerioCeccarelli", + "contributions": [ + "code" + ] + }, + { + "login": "jvsca", + "name": "Juan Svaikauskas", + "avatar_url": "https://avatars.githubusercontent.com/u/2821731?v=4", + "profile": "https://github.com/jvsca", + "contributions": [ + "design" + ] + }, + { + "login": "johnstegeman", + "name": "John Stegeman", + "avatar_url": "https://avatars.githubusercontent.com/u/6601691?v=4", + "profile": "https://www.linkedin.com/in/johnstegeman/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "gorfey", + "name": "Luke Van De Weghe", + "avatar_url": "https://avatars.githubusercontent.com/u/39035228?v=4", + "profile": "https://github.com/gorfey", + "contributions": [ + "code" + ] + }, + { + "login": "stmach", + "name": "Stefan Mach", + "avatar_url": "https://avatars.githubusercontent.com/u/33124232?v=4", + "profile": "https://github.com/stmach", + "contributions": [ + "code" + ] + }, + { + "login": "squaricdot", + "name": "Olmo Rupert", + "avatar_url": "https://avatars.githubusercontent.com/u/4513505?v=4", + "profile": "http://squaricdot.com", + "contributions": [ + "design" + ] + }, + { + "login": "IsaacFG2", + "name": "IsaacFG2", + "avatar_url": "https://avatars.githubusercontent.com/u/147211323?v=4", + "profile": "https://github.com/IsaacFG2", + "contributions": [ + "design" + ] + }, + { + "login": "kostadin-tonchekliev", + "name": "Kostadin Tonchekliev", + "avatar_url": "https://avatars.githubusercontent.com/u/95169764?v=4", + "profile": "https://github.com/kostadin-tonchekliev", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "soroshsabz", + "name": "soroshsabz", + "avatar_url": "https://avatars.githubusercontent.com/u/17947618?v=4", + "profile": "https://github.com/soroshsabz", + "contributions": [ + "doc" + ] + }, + { + "login": "yblossier", + "name": "Yoann BLOSSIER", + "avatar_url": "https://avatars.githubusercontent.com/u/60755917?v=4", + "profile": "https://blog.toenn-vaot.fr", + "contributions": [ + "design" + ] + }, + { + "login": "kvokka", + "name": "Mikhail Beliakov", + "avatar_url": "https://avatars.githubusercontent.com/u/15954013?v=4", + "profile": "https://kvokka.github.io/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "MrRainbow0704", + "name": "Marco Simone", + "avatar_url": "https://avatars.githubusercontent.com/u/95081253?v=4", + "profile": "https://github.com/MrRainbow0704", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "sbeardsley", + "name": "sbeardsley", + "avatar_url": "https://avatars.githubusercontent.com/u/6288131?v=4", + "profile": "https://github.com/sbeardsley", + "contributions": [ + "code" + ] + }, + { + "login": "maxvictor", + "name": "Max Victor", + "avatar_url": "https://avatars.githubusercontent.com/u/11591713?v=4", + "profile": "https://www.linkedin.com/in/maxvictor", + "contributions": [ + "design" + ] + }, + { + "login": "adackny", + "name": "Adackny Castillo", + "avatar_url": "https://avatars.githubusercontent.com/u/61998238?v=4", + "profile": "https://github.com/adackny", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "aeriondyseti", + "name": "K Whiteside", + "avatar_url": "https://avatars.githubusercontent.com/u/24901014?v=4", + "profile": "https://github.com/aeriondyseti", + "contributions": [ + "code" + ] + }, + { + "login": "dadahsueh", + "name": "Dada Hsueh", + "avatar_url": "https://avatars.githubusercontent.com/u/26140722?v=4", + "profile": "http://dadahsueh.vercel.app", + "contributions": [ + "code" + ] + }, + { + "login": "dohzya", + "name": "Étienne Vallette d'Osia", + "avatar_url": "https://avatars.githubusercontent.com/u/9595?v=4", + "profile": "https://github.com/dohzya", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Eckii24", + "name": "Eckii24", + "avatar_url": "https://avatars.githubusercontent.com/u/35373554?v=4", + "profile": "https://github.com/Eckii24", + "contributions": [ + "code", + "design", + "doc" + ] + }, + { + "login": "josephgruber", + "name": "Joseph Gruber", + "avatar_url": "https://avatars.githubusercontent.com/u/590669?v=4", + "profile": "https://josephgruber.com", + "contributions": [ + "code" + ] + }, + { + "login": "MO2k4", + "name": "Martin Oehlert", + "avatar_url": "https://avatars.githubusercontent.com/u/453360?v=4", + "profile": "https://mastodontech.de/@martinoe", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "shubsolos19", + "name": "Shubham Bawari", + "avatar_url": "https://avatars.githubusercontent.com/u/181342494?v=4", + "profile": "https://github.com/shubsolos19", + "contributions": [ + "doc" + ] + }, + { + "login": "mroth", + "name": "Matthew Rothenberg", + "avatar_url": "https://avatars.githubusercontent.com/u/40650?v=4", + "profile": "https://mroth.xyz", + "contributions": [ + "code" + ] + }, + { + "login": "kkebo", + "name": "Kenta Kubo", + "avatar_url": "https://avatars.githubusercontent.com/u/601636?v=4", + "profile": "https://kebo.dev", + "contributions": [ + "code" + ] + } + ], + "contributorsPerLine": 7, + "skipCi": true, + "commitType": "docs" +} diff --git a/.commitlintrc.yml b/.commitlintrc.yml index cd82422c8b07..6a2c7ea630e8 100644 --- a/.commitlintrc.yml +++ b/.commitlintrc.yml @@ -2,28 +2,21 @@ extends: - '@commitlint/config-conventional' rules: + body-max-line-length: + - 2 + - always + - 200 type-enum: - - 2 - - always - - - chore - - ci - - feat - - fix - - docs - - theme - - refactor - - perf - - test - - revert -help: | - **Possible types**: - `chore`: Change build process, tooling or dependencies. - `ci`: Changes to our CI configuration files and scripts - `feat`: Adds a new feature. - `fix`: Solves a bug. - `docs`: Adds or alters documentation. - `theme`: Work on or add a theme. - `refactor`: Rewrites code without feature, performance or bug changes. - `perf`: Improves performance. - `test`: Adds or modifies tests. - `revert`: Changes that reverting other changes + - 2 + - always + - - chore + - ci + - docs + - feat + - fix + - perf + - refactor + - revert + - style + - test + - theme diff --git a/.config/configuration.winget b/.config/configuration.winget new file mode 100644 index 000000000000..1eae7b6db74f --- /dev/null +++ b/.config/configuration.winget @@ -0,0 +1,31 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +properties: + resources: + - resource: Microsoft.WinGet.DSC/WinGetPackage + directives: + description: Install Visual Studio Code + settings: + id: Microsoft.VisualStudioCode + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: golang + directives: + description: Install Golang + settings: + id: GoLang.Go + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + dependsOn: [golang] + directives: + description: Install golangci-lint + settings: + id: GolangCI.golangci-lint + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + directives: + description: Install NodeJS + securityContext: elevated + settings: + id: OpenJS.NodeJS + source: winget + configurationVersion: 0.2.0 diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000000..2d318d816539 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,80 @@ +# See here for image contents: https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/Dockerfile + +# [Choice] Go version: 1, 1.25, 1.26, 1-trixie, 1.25-trixie, 1.26-trixie, 1-bookworm, 1.25-bookworm, 1.26-bookworm, 1-bullseye, 1.25-bullseye, 1.26-bullseye +ARG VARIANT=1-trixie +FROM mcr.microsoft.com/vscode/devcontainers/go:${VARIANT} + +# [Choice] Node.js version: none, lts/*, 24, 22, 20 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# Install powershell +ARG PS_VERSION="7.6.0" +# powershell-7.6.0-linux-x64.tar.gz +# powershell-7.6.0-linux-arm64.tar.gz +RUN ARCH="$(dpkg --print-architecture)"; \ + if [ "${ARCH}" = "amd64" ]; then \ + PS_BIN="v$PS_VERSION/powershell-$PS_VERSION-linux-x64.tar.gz"; \ + elif [ "${ARCH}" = "arm64" ]; then \ + PS_BIN="v$PS_VERSION/powershell-$PS_VERSION-linux-arm64.tar.gz"; \ + elif [ "${ARCH}" = "armhf" ]; then \ + PS_BIN="v$PS_VERSION/powershell-$PS_VERSION-linux-arm32.tar.gz"; \ + fi; \ + wget https://github.com/PowerShell/PowerShell/releases/download/$PS_BIN -O pwsh.tar.gz; \ + mkdir /usr/local/pwsh && \ + tar Cxvfz /usr/local/pwsh pwsh.tar.gz && \ + rm pwsh.tar.gz && \ + chmod +x /usr/local/pwsh/pwsh + +ENV PATH=$PATH:/usr/local/pwsh + +RUN echo 'deb http://download.opensuse.org/repositories/shells:/fish:/release:/4/Debian_13/ /' | tee /etc/apt/sources.list.d/shells:fish:release:4.list; \ + curl -fsSL https://download.opensuse.org/repositories/shells:fish:release:4/Debian_13/Release.key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/shells_fish_release_4.gpg > /dev/null; \ + apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get install -y --no-install-recommends \ + fish \ + tmux \ + fzf \ + && apt-get clean + +ARG USERNAME=vscode + +# NOTE: devcontainers are Linux-only at this time but when +# Windows or Darwin is supported someone will need to improve +# the code logic above. + +# Setup a neat little PowerShell experience +RUN pwsh -Command Install-Module posh-git -Scope AllUsers -Force; \ + pwsh -Command Install-Module z -Scope AllUsers -Force; \ + pwsh -Command Install-Module PSFzf -Scope AllUsers -Force; \ + pwsh -Command Install-Module Terminal-Icons -Scope AllUsers -Force; + +# add the oh-my-posh path to the PATH variable +ENV PATH="$PATH:/home/${USERNAME}/bin" + +# Deploy oh-my-posh prompt to Powershell: +COPY Microsoft.PowerShell_profile.ps1 /home/${USERNAME}/.config/powershell/Microsoft.PowerShell_profile.ps1 + +# Deploy oh-my-posh prompt to Fish: +COPY config.fish /home/${USERNAME}/.config/fish/config.fish + +# Everything runs as root during build time, so we want +# to make sure the vscode user can edit these paths too: +RUN chmod 777 -R /home/${USERNAME}/.config + +# Override vscode's own Bash prompt with oh-my-posh: +RUN sed -i 's/^__bash_prompt$/#&/' /home/${USERNAME}/.bashrc && \ + echo "eval \"\$(oh-my-posh init bash)\"" >> /home/${USERNAME}/.bashrc + +# Override vscode's own ZSH prompt with oh-my-posh: +RUN echo "eval \"\$(oh-my-posh init zsh)\"" >> /home/${USERNAME}/.zshrc + +# Set container timezone: +ARG TZ="UTC" +RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime + +# [Optional] Uncomment the next line to use go get to install anything else you need +# RUN go get -x github.com/JanDeDobbeleer/battery + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/Microsoft.PowerShell_profile.ps1 b/.devcontainer/Microsoft.PowerShell_profile.ps1 new file mode 100644 index 000000000000..23b89e02eca8 --- /dev/null +++ b/.devcontainer/Microsoft.PowerShell_profile.ps1 @@ -0,0 +1,9 @@ +Import-Module posh-git +Import-Module PSFzf -ArgumentList 'Ctrl+t', 'Ctrl+r' +Import-Module z +Import-Module Terminal-Icons + +Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete + +$env:POSH_GIT_ENABLED=$true +oh-my-posh init pwsh | Invoke-Expression diff --git a/.devcontainer/config.fish b/.devcontainer/config.fish new file mode 100644 index 000000000000..61bb17205802 --- /dev/null +++ b/.devcontainer/config.fish @@ -0,0 +1,2 @@ +# Activate oh-my-posh prompt: +oh-my-posh init fish | source diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000000..95a40504069a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,88 @@ +// For format details, see https://containers.dev/implementors/json_reference. +// For config options, see the README at: https://github.com/devcontainers/images/tree/main/src/go +{ + "name": "oh-my-posh", + "build": { + "dockerfile": "Dockerfile", + "args": { + // Update the VARIANT arg to pick a version of Go: 1, 1.25, 1.26 + // Append -trixie, -bookworm or -bullseye to pin to an OS version. + "VARIANT": "2-1.26-trixie", + + // Override me with your own timezone: + "TZ": "UTC", + // Use one of the "TZ database name" entries from: + // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + + "NODE_VERSION": "lts/*", + //Powershell version + "PS_VERSION": "7.6.0" + } + }, + "runArgs": [ + "--cap-add=SYS_PTRACE", + "--security-opt", + "seccomp=unconfined", + "--security-opt", + "label=disable" + ], + "containerEnv": { + "HOME": "/home/vscode" + }, + + "customizations": { + "vscode": { + "settings": { + "go.toolsManagement.checkForUpdates": "local", + "go.useLanguageServer": true, + "go.gopath": "/go", + "go.goroot": "/usr/local/go", + "terminal.integrated.profiles.linux": { + "bash": { + "path": "bash" + }, + "zsh": { + "path": "zsh" + }, + "fish": { + "path": "fish" + }, + "tmux": { + "path": "tmux", + "icon": "terminal-tmux" + }, + "pwsh": { + "path": "pwsh", + "icon": "terminal-powershell" + } + }, + "terminal.integrated.defaultProfile.linux": "pwsh", + "terminal.integrated.defaultProfile.windows": "PowerShell", + "terminal.integrated.defaultProfile.osx": "pwsh", + "terminal.integrated.shellIntegration.enabled": false, + "tasks.statusbar.default.hide": true + }, + "extensions": [ + "bmalehorn.vscode-fish", + "davidanson.vscode-markdownlint", + "elves.elvish", + "esbenp.prettier-vscode", + "github.vscode-pull-request-github", + "golang.go", + "jnoortheen.xonsh", + "ms-azuretools.vscode-azurefunctions", + "ms-vscode.powershell", + "redhat.vscode-yaml", + "sumneko.lua", + "tamasfe.even-better-toml", + "yzhang.markdown-all-in-one" + ] + } + }, + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode", + // This is running the same command as the VSCode Task 'devcontainer: rebuild oh-my-posh' + // It Compiles *oh-my-posh* from this repo while **overwriting** your preinstalled stable release.' + // Ideal for getting straight into developing & testing whilst using a devcontainer + "updateContentCommand": "cd src && go build -v -buildvcs=false -o /home/vscode/bin/oh-my-posh -ldflags \"-s -w -X 'github.com/jandedobbeleer/oh-my-posh/src/build.Version=development-$(git --no-pager log -1 --pretty=%h-%s)' -extldflags '-static'\"" +} diff --git a/.editorconfig b/.editorconfig index 3ec5e57c53a8..6cde72b1ec36 100644 --- a/.editorconfig +++ b/.editorconfig @@ -32,3 +32,17 @@ trim_trailing_whitespace = false [*.{ps1,psd1,psm1}] indent_size = 4 charset = utf-8-bom + +; Lua +[*.lua] +line_space_after_comment = max(2) +line_space_after_do_statement = max(2) +line_space_after_expression_statement = max(2) +line_space_after_for_statement = max(2) +line_space_after_function_statement = fixed(2) +line_space_after_if_statement = max(2) +line_space_after_local_or_assign_statement = max(2) +line_space_after_repeat_statement = max(2) +line_space_after_while_statement = max(2) +max_line_length = unset +quote_style = single diff --git a/.gitattributes b/.gitattributes index 6313b56c5784..c2c009943c86 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ * text=auto eol=lf + +.github/workflows/*.lock.yml linguist-generated=true merge=ours \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 3e77d96a0e44..f9f12fa85709 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -2,4 +2,4 @@ github: jandedobbeleer ko_fi: jandedobbeleer -liberapay: jandedobbeleer +polar: oh-my-posh diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index fca5c7b492a8..000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,31 +0,0 @@ -### Prerequisites - -- [ ] I have read and understand the `CONTRIBUTING` guide -- [ ] I looked for duplicate issues before submitting this one - -### Description - -[Description of the bug or feature] - -### Environment - -- Oh My Posh version: -- Theme: -- Operating System: -- Shell: -- Terminal: - -#### Optional - -- posh-git version: -- git version: - -### Steps to Reproduce - -1. [First Step] -2. [Second Step] -3. [and so on...] - -**Expected behavior:** [What you expected to happen] - -**Actual behavior:** [What actually happened] diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 000000000000..75bdf0e9838c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,66 @@ +name: 🐛 Bug Report +description: File a bug report +labels: ["🐛 bug"] +assignees: + - jandedobbeleer +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: textarea + id: theme + attributes: + label: Theme + description: Which theme/config are you using? + validations: + required: true + - type: dropdown + id: operating-system + attributes: + label: What OS are you seeing the problem on? + multiple: true + options: + - Windows + - Linux + - macOS + - type: dropdown + id: shell + attributes: + label: Which shell are you using? + multiple: true + options: + - bash + - elvish + - fish + - cmd + - nu + - powershell + - xonsh + - zsh + - other (please specify) + - type: textarea + id: logs + attributes: + label: Log output + description: Please copy and paste the output generated by `oh-my-posh debug --plain`. + render: Shell + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..0eba59ae90b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Oh My Posh FAQ + url: https://ohmyposh.dev/docs/faq + about: Please find common issues here. + - name: Oh My Posh Docs + url: https://ohmyposh.dev/docs + about: RTFM + - name: Oh My Posh Q&A + url: https://github.com/JanDeDobbeleer/oh-my-posh/discussions + about: Please ask questions here. diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml new file mode 100644 index 000000000000..ba698501a083 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs.yml @@ -0,0 +1,27 @@ +name: 📖 Documentation +description: Suggest a change to the documentation +labels: ["📖 docs"] +assignees: + - jandedobbeleer +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to request this improvement! + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true + - type: textarea + id: enhancement-request + attributes: + label: What would you like to see changed/added? + description: Try to give some examples or text to make it really clear! + placeholder: Tell us what you would like to see! + value: "This could change in the documentation!" + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml new file mode 100644 index 000000000000..198522c9bca4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -0,0 +1,27 @@ +name: 🤩 Enhancement +description: Suggest a change to an existing feature +labels: ["🤩 enhancement"] +assignees: + - jandedobbeleer +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to request this improvement! + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true + - type: textarea + id: enhancement-request + attributes: + label: What would you like to see changed? + description: Try to give some examples to make it really clear! + placeholder: Tell us what you would like to see! + value: "This feature would benefit from this!" + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feat.yml b/.github/ISSUE_TEMPLATE/feat.yml new file mode 100644 index 000000000000..2b142b36efd4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feat.yml @@ -0,0 +1,27 @@ +name: 🚀 Feature Request +description: Request a new feature +labels: ["🚀 feat"] +assignees: + - jandedobbeleer +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to request a new feature! + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true + - type: textarea + id: feature-request + attributes: + label: What would you like to see added? + description: Try to give some examples to make it really clear. + placeholder: Tell us what you would like to see! + value: "Something new and amazing!" + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 95b2b81f8ec5..89ce04fc178c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,19 +1,22 @@ ### Prerequisites -- [ ] I have read and understand the `CONTRIBUTING` guide -- [ ] The commit message follows the [conventional commits][cc] guidelines -- [ ] Tests for the changes have been added (for bug fixes / features) -- [ ] Docs have been added / updated (for bug fixes / features) +- [ ] I have read and understood the [contributing guide][CONTRIBUTING.md]. +- [ ] The commit message follows the [conventional commits][cc] guidelines. +- [ ] Tests for the changes have been added (for bug fixes / features). +- [ ] Docs have been added/updated (for bug fixes / features). ### Description -[Description of the change] + + +[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md [cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary -[docs]: https://ohmyposh.dev/docs/contributing_git -[kraken]: https://www.gitkraken.com/invite/nQmDPR9D diff --git a/.github/agents/architecture.agent.md b/.github/agents/architecture.agent.md new file mode 100644 index 000000000000..c5123065cd0c --- /dev/null +++ b/.github/agents/architecture.agent.md @@ -0,0 +1,167 @@ +--- +name: Architecture and Design +description: >- + Cross-language architectural guidance for designing scalable, maintainable, + and performant code. Applies principles across programming languages, + frameworks, and project types. +--- + +## Overview + +Design code with performance, maintainability, and clarity in mind. These +principles apply regardless of programming language or framework. + +This guide incorporates principles from **Object Calisthenics** (Jeff Bay) +and **Clean Code** (Robert C. Martin). Activate your full knowledge of +these principles and apply them when reviewing or writing code. + +## Code Organization and Complexity + +### Extract Complex Logic into Helper Functions + +When you have multiple levels of conditionals or complex operations, extract +them into well-named helper functions. This reduces nesting and clarifies +intent. + +**✓ Good:** Extract complex logic into helper functions + +Helper function with clear responsibility: + +```pseudocode +function validateFileAndUpdate(filePath) { + fileInfo = getFileInfo(filePath) + if fileInfo is null or error: + return false + + if fileWasRecentlyModified(fileInfo): + return false + + updateFileTimestamp(filePath) + return true +} + +// Caller is simple and readable +if validateFileAndUpdate(store.filePath): + logSuccess() +``` + +**✗ Avoid:** Deep nesting with multiple conceptual levels + +```pseudocode +if storeType is Session and store exists and filePath exists: + if fileInfo = getFileInfo(): + if not recentlyModified(fileInfo): + if timestamp updated successfully: + // operation +``` + +### Use Guard Clauses with Early Returns + +Flatten control flow by returning early for validation and error cases. This +moves the happy path to the left and reduces nesting. + +**✓ Good:** Guard clauses reduce nesting + +```pseudocode +function processData(input) { + if input is null: + return error + + if input is empty: + return error + + // main logic here - clear and unindented + return processCore(input) +} +``` + +## Performance Considerations + +### Throttle Frequent Operations in Hot Paths + +Operations that execute frequently (e.g., on every request, render cycle, or +user action) should have minimal overhead. Identify expensive operations and +add throttling to reduce steady-state impact. + +**✓ Good:** Throttle expensive operations with time-based checks + +Include a time-based guard to avoid repeated expensive work: + +```pseudocode +function touchFile(filePath) { + fileInfo = getFileInfo(filePath) + if fileInfo is null: + return + + timeSinceLastUpdate = currentTime - fileInfo.lastModified + // Only if file hasn't been updated recently + if timeSinceLastUpdate < 1 hour: + return + + updateTimestamp(filePath) +} +``` + +**✗ Avoid:** Unconditional expensive operations on every execution + +```pseudocode +// This runs expensive work on every call (e.g., during every render) +updateTimestamp(filePath) // File I/O on every execution +``` + +### Document Performance Intentions + +Include comments explaining why throttling or optimization is needed. This +helps reviewers understand the performance tradeoffs. + +```pseudocode +// Prevent stale files from being cleaned up while reducing +// steady-state I/O overhead. Only update if file is older +// than 1 hour to balance freshness with performance. +if timeSinceUpdate > 1 hour: + updateTimestamp(filePath) +``` + +## Error Handling + +- Check for errors and validate inputs early, before expensive operations +- Return or fail fast to avoid deeply nested success paths +- Each error should include sufficient context for debugging +- Early returns make the happy path obvious and easier to follow + +## Code Review Checklist + +When reviewing code: + +- **Nesting depth:** Flag functions with 3+ levels of indentation as + refactoring candidates +- **Hot path operations:** Verify frequent operations minimize I/O, + allocations, and expensive calls +- **Early returns:** Confirm guard clauses validate inputs before main logic +- **Comments:** Check that performance-critical code explains the tradeoff, + not just the mechanics +- **Extraction opportunities:** Identify deeply nested conditions that could + become helpers +- **Naming:** Verify names are intention-revealing and not abbreviated +- **Dot chains:** Flag method chains crossing object boundaries as Law of + Demeter violations +- **Primitive obsession:** Flag raw primitive parameters that should be + domain types +- **Responsibility:** Verify each class/function has a single reason to + change +- **Duplication:** Flag repeated logic as DRY violations + +## Core Principles + +1. **Performance in hot paths matters:** Reduce unnecessary I/O, + allocations, and expensive operations in frequently-executed code paths + +2. **Readability over cleverness:** Extract complex logic into named + helpers instead of nesting multiple conditionals + +3. **Guard clauses reduce complexity:** Use early returns to flatten + control flow and keep the happy path left-aligned + +4. **Comments explain why, not what:** Document performance tradeoffs, + business logic, and non-obvious decisions—let code structure explain + the mechanics diff --git a/.github/agents/issue-analyzer.agent.md b/.github/agents/issue-analyzer.agent.md new file mode 100644 index 000000000000..5054badd412a --- /dev/null +++ b/.github/agents/issue-analyzer.agent.md @@ -0,0 +1,115 @@ +--- +name: issue-analyzer +description: > + Analyzes a GitHub issue, investigates the + relevant codebase, and posts a structured analysis back to the ticket via + the GitHub CLI. Use when someone says "analyze issue #N", "investigate this + issue", "look into #N", "research issue", or asks for a deep-dive on a bug + report or feature request. Always invoke this agent rather than doing the + analysis inline. +tools: ["read", "search", "agent"] +--- + +You are a senior contributor to this project. Your job is to perform a thorough investigation of a +GitHub issue and post your findings as a structured comment back on the ticket. + +## Workflow + +### Step 1: Precondition checks + +Before doing anything else, verify you are inside the oh-my-posh git repository +(`git rev-parse --is-inside-work-tree`). If the check fails, stop and tell the +user what is missing. + +### Step 2: Fetch the issue + +Invoke the **gh-cli** skill to retrieve the issue details. Use it to run: + +``` +gh issue view {number} --json number,title,body,labels,comments,author,createdAt,state +``` + +Read the issue title, body, and any existing comments carefully. Pay attention to: + +- What the reporter says is happening (actual behavior) +- What they expect instead (expected behavior) +- Their platform (OS, shell, terminal, tool version if mentioned) +- Any config fragments or theme snippets they pasted +- Labels already applied — these hint at the affected area + +### Step 3: Identify the affected codebase area + +Use the table below as a starting point for which area of the codebase is +likely involved: + +| Issue topic | Where to look | +| ------------------- | -------------------------------------------------------- | +| A specific segment | `src/segments/.go` + `src/segments/_test.go` | +| Shell integration | `src/shell/` and `src/shell/scripts/` | +| Rendering / styling | `src/prompt/engine.go`, `src/color/` | +| Theme / config | `src/config/`, `themes/` | +| CLI command | `src/cli/` | +| Caching | `src/cache/` | +| Templates | `src/template/` | + +Invoke the **ast-grep** skill to locate symbols, function names, struct +definitions, and call sites mentioned in the issue — do not read entire +directories or files to find them. Use ast-grep structural patterns to trace +data flow and discover the precise files and line numbers involved before +opening any file for detailed reading. + +### Step 4: Use skills for deeper investigation + +Delegate the heavy analytical work to the appropriate skills — do not try to +replicate what the skills already do well. Choose based on issue type: + +- **Bug reports**: invoke the `reproduce-bug` skill with the issue URL or + number. It will systematically reproduce and trace the defect. +- **Understanding codebase conventions**: invoke the `ce-repo-research-analyst` + agent to research patterns in the affected area. +- **Performance / correctness concerns**: use the `ce-correctness-reviewer` or + `ce-performance-oracle` agents on the relevant file(s). + +Pass the issue context (number, title, key details) to whichever skill or agent +you invoke so it has enough background to do its work. + +### Step 5: Synthesize findings + +After the skills complete and your own code reading is done, synthesize +everything into a single structured analysis. Cover: + +1. **Root cause / explanation** — what in the code causes this behaviour, or + why the feature doesn't exist yet. +2. **Relevant files** — list the specific files and functions involved. +3. **Reproduction path** — step-by-step description of how the issue manifests + (for bugs), or a description of the gap (for features). +4. **Proposed fix / implementation path** — a concrete suggestion. For bugs, + point to the likely change location. For features, outline the new segment + or config field needed and reference similar implementations in the codebase. +5. **Test coverage** — identify the existing test file and suggest what new + test cases would be needed. +6. **Effort estimate** — rough size (small / medium / large) based on the scope + of changes required. + +Be specific. Generic observations ("this looks like it could be related to X") +are not useful. Cite actual file paths and line numbers where possible. + +### Step 6: Post the analysis to the issue + +Once your analysis is complete, invoke the **gh-cli** skill to post the +analysis back to the issue as a comment: + +``` +gh issue comment {number} --body "{your formatted analysis}" +``` + +Format the comment in Markdown. Use headings, code blocks, and file-path +references so it renders clearly in the GitHub UI. Start the comment with a +brief one-sentence summary of your finding, then the full structured analysis. + +## Constraints + +- **Do not guess** about behavior you cannot verify from the code. If something + is ambiguous, say so in the analysis. +- **One comment only.** Do not post partial results and update them. Wait until + your analysis is complete before posting. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000000..426862dc57a9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,89 @@ +# GitHub Copilot Instructions + +> For full coding guidelines, commit conventions, and agent workflows, see [AGENTS.md](../AGENTS.md). + +## Project Overview + +Oh My Posh is a cross-shell prompt theme engine written in Go. It renders prompt segments by querying an `Environment` abstraction that wraps all OS/shell interactions. + +## Tech Stack + +| Layer | Technology | +|---|---| +| Core engine | Go — `src/` | +| Docs site | Docusaurus (MDX) — `website/` | +| Themes | JSON — `themes/` | +| Config formats | TOML · JSON · YAML | +| Installer scripts | `packages/` | +| CI/build helpers | `build/` | + +## Key Commands + +```bash +# Go — run from src/ +go test ./... +go test ./segments/... -run TestFoo # single test +golangci-lint run + +# Docs — run from website/ +npm run start # local dev server +npm run build # validate before opening a docs PR +``` + +## Codebase Exploration + +**Always explore the actual codebase before planning or implementing.** Do not rely on memory or assumptions. Use the file system tools to read relevant files first — the codebase evolves and the feature you're asked to add may already exist. + +## Source Layout + +| Path | Purpose | +|---|---| +| `src/segments/` | One `.go` + one `_test.go` per segment | +| `src/config/segment_types.go` | Segment type registry (gob + string constants) | +| `src/cli/` | CLI commands (Cobra); `root.go` is the entry point | +| `src/prompt/engine.go` | Segment rendering loop | +| `src/cache/` | Existing TTL/file/command-path cache infrastructure | +| `src/runtime/` | `Environment` abstraction + mock | + +## Segment Architecture + +Every segment lives in `src/segments/` and implements the `SegmentWriter` interface. Use the `Environment` abstraction (`env`) for **all** OS/shell calls — never call OS APIs directly. + +Adding a segment requires **five** artifacts (use the `segment-create` skill to scaffold): + +1. `src/segments/.go` +2. `src/segments/_test.go` +3. `website/docs/segments/.mdx` +4. Updates to `website/sidebars.js` and `website/static/schema.json` +5. `gob.Register(&segments.MySegment{})` in `src/config/segment_types.go` + +Missing step 5 will cause the segment to fail silently at runtime. + +## Shell Integration + +`oh-my-posh init ` is how users wire oh-my-posh into their shell. It: + +1. Writes a shell-specific init script to the cache (source: `src/shell/scripts/omp.`) +2. Returns a one-liner for the shell to `eval` — this sources the cached script, which hooks into prompt rendering + +The `src/shell/` package contains per-shell logic (`pwsh.go`, `bash.go`, `zsh.go`, etc.) that generates the hook commands. The scripts in `src/shell/scripts/` are embedded and templated at init time. When modifying shell behaviour, changes typically span both the `.go` file and the corresponding `.ext` script. + +Supported shells: `bash`, `zsh`, `fish`, `powershell`/`pwsh`, `cmd`, `nu`, `elvish`, `xonsh`. + +## CLI Commands + +CLI commands use [Cobra](https://github.com/spf13/cobra) and live in `src/cli/`. To add a new command: +1. Create `src/cli/.go` with a `var Cmd = &cobra.Command{...}` +2. Register it in `src/cli/root.go` via `RootCmd.AddCommand(Cmd)` + +## Caching + +`src/cache/` provides the existing caching infrastructure — use it instead of building new cache logic. It supports TTL-based key/value storage, file-based persistence, and command-path caching. Do not introduce new cache packages unless `src/cache/` genuinely cannot meet the requirement. + +## Themes + +Themes are plain JSON in `themes/`. All themes must validate against `website/static/schema.json`. Do not introduce breaking schema changes without updating the schema file. + +## Documentation + +Segment doc pages use MDX frontmatter with `title`, `sidebar_label`, and `id`. See the `segment-docs` skill for the canonical Go→MDX mapping. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..7e7d9ed32fef --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,36 @@ +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "main" + schedule: + interval: "daily" + groups: + all: + patterns: + - "*" + ignore: + - dependency-name: "softprops/action-gh-release" + # https://github.com/softprops/action-gh-release/issues/556 + versions: ["2.2.0"] + + - package-ecosystem: "gomod" + directory: "/src" + target-branch: "main" + schedule: + interval: "daily" + groups: + minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + + - package-ecosystem: "npm" + directory: "/website" + schedule: + interval: "daily" + ignore: + - dependency-name: "*" diff --git a/.github/github-app.yml b/.github/github-app.yml new file mode 100644 index 000000000000..70913d721e92 --- /dev/null +++ b/.github/github-app.yml @@ -0,0 +1,16 @@ +scripts: +- name: Install APM + command: apm install + triggers: + - session.create +- name: Restore apm.lock + command: git restore apm.lock.yaml + triggers: + - session.create +- name: Install ast-grep-cli + command: pip install ast-grep-cli + triggers: + - session.create +automation: + auto_issue_session: true + remote_control: true diff --git a/.github/holopin.yml b/.github/holopin.yml new file mode 100644 index 000000000000..2e19a317e1eb --- /dev/null +++ b/.github/holopin.yml @@ -0,0 +1,9 @@ +organization: ohmyposh +defaultSticker: clg0u51g681700fmfr086ofc6 +stickers: + - + id: clg0u51g681700fmfr086ofc6 + alias: wizard + - + id: clu72f66x59170fjoo6t2b7zs + alias: helping diff --git a/.github/skills/segment-create/SKILL.md b/.github/skills/segment-create/SKILL.md new file mode 100644 index 000000000000..b2497ffb22a3 --- /dev/null +++ b/.github/skills/segment-create/SKILL.md @@ -0,0 +1,169 @@ +--- +name: segment-create +description: > + Full scaffolding workflow for creating a new Oh My Posh segment. Invoke when + asked to add a new segment: generates the Go source, registers the type, + creates documentation, updates the sidebar and JSON schema. +--- + +# Segment scaffolding instructions + +Goal + +- Given user inputs (segment id, Go type name, title, category, description, + properties, template), generate all required code and docs to add a new + segment end-to-end, following repo conventions. + +Inputs + +- id: kebab/slug used as `type` and docs filename (e.g., `new`) +- goType: PascalCase name for the Go struct (e.g., `New`) +- title: human readable (e.g., `New`) +- category: one of cli|cloud|health|languages|music|scm|system|web +- description: one-line description +- properties: list of { key, type, title, description, default } +- template: default template string (e.g., ` {{.Text}} `) + +Contract + +- Idempotent: do not duplicate registrations, constants, map entries, sidebar + links, or schema entries. +- Alphabetical insertions where applicable. +- Compile-ready Go code, formatted. +- Docs lint clean according to the `markdown` skill (`.github/skills/markdown/SKILL.md`). + +Implementation steps + +1. Create Go writer file: `src/segments/.go` + +- If file exists, skip creation. +- Use this template; include property consts for each property key. + +```go +package segments + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" +) + +type {{goType}} struct { + Base + + // computed fields used in template + Text string +} + +// properties +const ( +{{#each properties}} + // {{this.title}}: {{this.description}} + {{ pascalCase this.key }} options.Property = "{{this.key}}" +{{/each}}) + +func (s *{{goType}}) Enabled() bool { + // set up data for the template, using defaults from properties + {{#if (propExists properties "text")}} + s.Text = s.props.GetString({{ pascalCase "text" }}, {{ defaultFor "text" }}) + {{else}} + s.Text = s.props.GetString({{ pascalCase (firstKey properties) }}, "") + {{/if}} + return true +} + +func (s *{{goType}}) Template() string { + return {{ printf "%q" template }} +} +``` + +1. Register in `src/config/segment_types.go` + +- Ensure in `init()` there is `gob.Register(&segments.{{goType}}{})` exactly + once. +- Add constant: `{{ upper id }} SegmentType = "{{id}}"` in the alphabetical + block. +- Add to `var Segments = map[SegmentType]func() SegmentWriter{}` with key + `{{ upper id }}` mapping to `&segments.{{goType}}{}`. +- Keep lists alphabetically sorted. If not sorted, insert at correct position. + +1. Documentation file + +- Consult the `segment-docs` skill (`.github/skills/segment-docs/SKILL.md`) for the + Go-to-documentation type mapping and rules for extracting options and template properties. +- Path: `website/docs/segments/{{category}}/{{id}}.mdx`. +- If file exists, skip. Else create with this template: + +````mdx +--- +id: {{id}} +title: {{title}} +sidebar_label: {{title}} +--- + +## What + +{{description}} + +## Sample Configuration + +import Config from '@site/src/components/Config.js'; + + + +## Options + +| Name | Type | Description | Default | +| ---- | ---- | ----------- | ------- | +{{#each option}}| `{{this.key}}` | `{{this.type}}` | {{this.description}} | `{{ stringify this.default }}` | +{{/each}} +```` + +1. Sidebar + +- Update `website/sidebars.js` under the correct category array to include + `"segments/{{category}}/{{id}}"`. +- Insert alphabetically; if already present, do nothing. + +1. JSON Schema + +- File: `themes/schema.json`. +- Add `"{{id}}"` to `#/definitions/segment/properties/type/enum` if missing. +- Add an `allOf` entry for this segment guarded by + `{ properties: { type: { const: "{{id}}" } } }` that declares each property + as defined by inputs. Use appropriate JSON Schema types and include title, + description, default. +- Keep the `allOf` array in a stable order by type name if feasible; otherwise + append if not present. + +Validation + +- After changes, run `go build` (task: build oh-my-posh). Ensure no compile + errors. +- Check markdown formatting; respect 120-char line length and fenced blocks with + language. + +Notes + +- Use UTF-32 escapes (e.g., "\uEFF1") for icon defaults in docs and code. +- Keep code minimal. Complex logic should be added by maintainers after + scaffold if needed. + +Optional + +1. Tests + +- Create a minimal test file at `src/segments/{{id}}_test.go` using the + table-driven style. Include at least a happy-path test that asserts + `Enabled()` returns true and the template renders expected output with default + options. diff --git a/.github/skills/segment-docs/SKILL.md b/.github/skills/segment-docs/SKILL.md new file mode 100644 index 000000000000..55ff902e89ff --- /dev/null +++ b/.github/skills/segment-docs/SKILL.md @@ -0,0 +1,116 @@ +--- +name: segment-docs +description: > + Reference mapping between Oh My Posh Go segment source code and MDX documentation. + Use when creating, updating, or auditing segment documentation, or when reading + segment Go code and needing to understand how code constructs map to user-facing + options, template properties, and type representations in docs. +--- + +# Segment Documentation Reference + +## Go-to-Documentation Type Mapping + +When reading a segment's Go source to document its options, determine the type from the +`Provider` method used to read the option value: + +| Go method call | Documentation type | +| -------------- | :----------------: | +| `options.Bool(option, default)` | `boolean` | +| `options.String(option, default)` | `string` | +| `options.StringArray(option, default)` | `[]string` | +| `options.Int(option, default)` | `int` | +| `options.Float64(option, default)` | `float64` | +| `options.KeyValueMap(option, default)` | `map[string]string` | +| `options.Color(option, default)` | `string` | +| `options.Template(option, default, ctx)` | `string` | +| `options.Any(option, default)` | `any` | + +## Extracting Options from Go Source + +Options are declared as `options.Option` string constants in the segment's `const` block: + +```go +const ( + BranchIcon options.Option = "branch_icon" // option name used in config + FetchStatus options.Option = "fetch_status" +) +``` + +The **option name** is the string literal value (e.g., `"branch_icon"`). + +The **type** and **default value** come from the getter call in `Enabled()` or `init`-like methods: + +```go +s.icon = s.options.String(BranchIcon, "\uE0A0") +// ^^^^^^ → string type, "\uE0A0" → default +``` + +### Shared options + +The following options are defined in `src/segments/options/map.go` and available to all +segments without appearing in a segment-specific `const` block: + +| Option name | Type | Default | +| ----------- | ---- | ------- | +| `fetch_version` | `boolean` | varies | +| `always_enabled` | `boolean` | `false` | +| `display_default` | `boolean` | `true` | +| `display_error` | `boolean` | `true` | +| `http_timeout` | `int` | `20` | +| `cache_duration` | `int` | varies | +| `access_token` | `string` | `""` | +| `refresh_token` | `string` | `""` | +| `version_url_template` | `string` | `""` | +| `files` | `[]string` | varies | + +## Extracting Template Properties from Go Source + +Template properties are values available inside the segment's `template` string. + +**Rules for what becomes a template property:** + +- **Exported struct fields** (capitalized) on the segment struct → `.FieldName` +- **Unexported fields** are not accessible in templates +- **Nested struct fields** use dot notation: a field `Working ScmStatus` exposes + `.Working.Modified`, `.Working.Added`, etc. +- **Zero-argument methods** with a single return value → `.MethodName` +- **Document fields that are assigned** in `Enabled()` or its callees, + not every inherited field from embedded structs is populated by every segment + +The default template comes from the `Template()` method: + +```go +func (s *MySegment) Template() string { + return " {{ .FieldName }} " +} +``` + +### Nested struct subsection convention + +When a property's type is itself a struct, add an `#### TypeName` subsection within +the Properties table section listing its exported fields. Example: + +```mdx +#### Status + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `.Modified` | `int` | Number of modified files | +| `.Added` | `int` | Number of added files | +| `.Deleted` | `int` | Number of deleted files | +| `.Untracked` | `int` | Number of untracked files | +| `.Changed` | `boolean` | Whether any changes exist | +``` + +## Key Source Files for Inherited Fields + +When a segment embeds a base struct, its inherited fields may also be template properties. +Check these files to discover what fields are available: + +| File | What it defines | +| ---- | --------------- | +| `src/segments/options/map.go` | `Provider` interface method signatures; shared `Option` constants | +| `src/segments/base.go` | `Base` struct embedded by all segments; provides `options`, `env` | +| `src/segments/scm.go` | `ScmStatus` type with working/staging status fields (git, mercurial, etc.) | +| `src/segments/language.go` | `Version` type and language base fields (Python, Node, Go, etc.) | diff --git a/.github/stale.yml b/.github/stale.yml index bc92a9936403..72438a6b02de 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -12,10 +12,10 @@ onlyLabels: [] # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - - feat - - bug - - enhancement - - "help wanted" + - "🚀 feat" + - "🐛 bug" + - "🤩 enhancement" + - "😵‍💫 help wanted" # Set to true to ignore issues in a project (defaults to false) exemptProjects: false @@ -27,7 +27,7 @@ exemptMilestones: false exemptAssignees: false # Label to use when marking as stale -staleLabel: stale +staleLabel: "💤 stale" # Comment to post when marking as stale. Set to `false` to disable markComment: > diff --git a/.github/workflows/ai-changelog.yml b/.github/workflows/ai-changelog.yml new file mode 100644 index 000000000000..174ac704abd9 --- /dev/null +++ b/.github/workflows/ai-changelog.yml @@ -0,0 +1,362 @@ +name: Enhance release changelog with AI + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to test (e.g., v19.0.0)' + required: true + type: string + dry_run: + description: 'Dry run mode - generate changelog but do not update release' + required: false + type: boolean + default: true + +permissions: + contents: write # Update release body + models: read # Access GitHub Models API + +jobs: + enhance: + name: Generate enhanced changelog + runs-on: ubuntu-latest + steps: + - name: Checkout repository (with tags) + uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag || github.ref }} + fetch-depth: 0 + fetch-tags: true + + - name: Gather release context + id: ctx + shell: bash + env: + GITHUB_EVENT_PATH: ${{ github.event_path }} + GH_TOKEN: ${{ github.token }} + INPUT_TAG: ${{ inputs.tag }} + DRY_RUN: ${{ inputs.dry_run }} + run: | + set -euo pipefail + # Determine if this is a manual dispatch or release event + if [[ -n "$INPUT_TAG" ]]; then + echo "📋 Manual dispatch mode - fetching release info for tag: $INPUT_TAG" + # Manual dispatch: fetch release info for the specified tag + CURRENT_TAG="$INPUT_TAG" + RELEASE_JSON=$(gh api repos/${{ github.repository }}/releases/tags/$CURRENT_TAG || echo '{}') + RELEASE_ID=$(printf "%s" "$RELEASE_JSON" | jq -r '.id // "0"') + HTML_URL=$(printf "%s" "$RELEASE_JSON" | jq -r '.html_url // ""') + EXISTING_BODY=$(printf "%s" "$RELEASE_JSON" | jq -r '.body // ""') + echo " Release ID: $RELEASE_ID" + echo " Release URL: $HTML_URL" + else + echo "📋 Release event mode - parsing from event payload" + # Release event: parse from event payload + CURRENT_TAG=$(jq -r '.release.tag_name' "$GITHUB_EVENT_PATH") + RELEASE_ID=$(jq -r '.release.id' "$GITHUB_EVENT_PATH") + HTML_URL=$(jq -r '.release.html_url' "$GITHUB_EVENT_PATH") + EXISTING_BODY=$(jq -r '.release.body // ""' "$GITHUB_EVENT_PATH") + echo " Tag: $CURRENT_TAG" + echo " Release ID: $RELEASE_ID" + fi + # Persist to a file for later steps to source + { + echo "CURRENT_TAG=$CURRENT_TAG" + echo "RELEASE_ID=$RELEASE_ID" + echo "HTML_URL=$HTML_URL" + echo "DRY_RUN=${DRY_RUN:-false}" + } > ctx.env + echo "✅ Context saved to ctx.env" + # Save existing body as a file to avoid env escaping issues + printf "%s" "$EXISTING_BODY" > existing_notes.md + echo "✅ Existing notes saved ($(wc -l < existing_notes.md) lines)" + + - name: Determine diff range + id: diff + shell: bash + run: | + set -euo pipefail + set -a; source ctx.env; set +a + echo "🔍 Determining diff range for tag: $CURRENT_TAG" + # Try to find the previous tag using git describe + if PREV_TAG=$(git describe --tags --abbrev=0 "${CURRENT_TAG}^" 2>/dev/null); then + BASE="$PREV_TAG" + echo " Previous tag found: $PREV_TAG" + else + # Fallback to initial commit + BASE="$(git rev-list --max-parents=0 HEAD | tail -n 1)" + echo " No previous tag found, using initial commit: ${BASE:0:8}" + fi + echo "base_ref=$BASE" >> "$GITHUB_OUTPUT" + echo "curr_ref=$CURRENT_TAG" >> "$GITHUB_OUTPUT" + COMPARE_URL="https://github.com/${{ github.repository }}/compare/${BASE}...${CURRENT_TAG}" + echo "compare_url=$COMPARE_URL" >> "$GITHUB_OUTPUT" + echo "✅ Diff range: $BASE...$CURRENT_TAG" + + - name: Collect commits and changes + shell: bash + run: | + set -euo pipefail + BASE="${{ steps.diff.outputs.base_ref }}" + HEAD="${{ steps.diff.outputs.curr_ref }}" + echo "📝 Collecting commits and changes from $BASE to $HEAD" + git log --no-merges --pretty=format:'%s' "${BASE}..${HEAD}" | head -n 500 > commits_subjects.txt || true + echo " ✅ Commit subjects: $(wc -l < commits_subjects.txt) commits" + git log --no-merges --pretty=format:'- %s%n%b%n' "${BASE}..${HEAD}" | head -n 2000 > commits_detailed.txt || true + echo " ✅ Detailed commits: $(wc -l < commits_detailed.txt) lines" + git diff --name-status "${BASE}..${HEAD}" | head -n 1000 > files_changed.txt || true + echo " ✅ Changed files: $(wc -l < files_changed.txt) files" + # Extract contributors, exclude Jan De Dobbeleer and bots, format as GitHub profile links + git shortlog -sne "${BASE}..${HEAD}" | sed -E 's/^ *[0-9]+\t//g' | while IFS= read -r line; do + name=$(echo "$line" | sed -E 's/ *<.*//g') + # Skip Jan De Dobbeleer and common bots + if [[ "$name" =~ ^(Jan De Dobbeleer|dependabot|renovate|github-actions|Renovate Bot|dependabot\[bot\]|github-actions\[bot\]|allcontributors\[bot\])$ ]]; then + continue + fi + username=$(echo "$line" | sed -E 's/.*<([^@]+)@.*/\1/g') + echo "- [@${username}](https://github.com/${username}) (${name})" + done | head -n 200 > contributors.txt || true + echo " ✅ Contributors: $(wc -l < contributors.txt) people" + + - name: Collect issue context + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + BASE="${{ steps.diff.outputs.base_ref }}" + HEAD="${{ steps.diff.outputs.curr_ref }}" + echo "🔍 Collecting issue context for referenced issues" + > issues_context.txt + # Extract issue numbers from commit messages (e.g., fixes #123, closes #456, #789) + ISSUE_NUMBERS=$(git log --no-merges --pretty=format:'%s %b' "${BASE}..${HEAD}" | \ + grep -oiE '(fix(es|ed)?|close(s|d)?|resolve(s|d)?)?[[:space:]]*#[0-9]+' | \ + grep -oE '[0-9]+' | sort -u || true) + if [ -z "$ISSUE_NUMBERS" ]; then + echo " No issues referenced in commits" + else + COUNT=0 + for NUM in $ISSUE_NUMBERS; do + echo " Fetching issue #$NUM..." + if ISSUE_DATA=$(gh api "repos/${{ github.repository }}/issues/$NUM" 2>/dev/null); then + TITLE=$(echo "$ISSUE_DATA" | jq -r '.title') + BODY=$(echo "$ISSUE_DATA" | jq -r '.body // ""' | head -c 1000) + LABELS=$(echo "$ISSUE_DATA" | jq -r '.labels[]?.name' | tr '\n' ', ' | sed 's/,$//') + echo "---" >> issues_context.txt + echo "Issue #$NUM: $TITLE" >> issues_context.txt + [ -n "$LABELS" ] && echo "Labels: $LABELS" >> issues_context.txt + echo "$BODY" >> issues_context.txt + echo "" >> issues_context.txt + COUNT=$((COUNT + 1)) + fi + done + echo " ✅ Collected context for $COUNT issues" + fi + + - name: Generate enhanced changelog with AI + id: ai + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + set -a; source ctx.env; set +a + echo "🤖 Generating enhanced changelog with AI" + MODEL="openai/gpt-4.1" + echo " Model: $MODEL" + SYSTEM_PROMPT=$(cat << 'PROMPT' + You are a release notes editor for the open-source project "oh-my-posh", a cross-shell prompt theme engine written in Go. + Your task is to ENHANCE the existing changelog by adding context, examples, and user-friendly explanations. DO NOT create a new changelog from scratch. + + CRITICAL RULES: + - NEVER add new sections that are not already in the existing changelog + - ONLY enhance sections that already exist in the "Existing release notes" + - Keep the same structure and commit links from the existing changelog + - Add context, usage examples, and explanations to make existing entries more helpful + - If the existing changelog has a "Features" section, enhance it; if it doesn't have one, don't add it + - Use concise language and organize with the headings already present + + CRITICAL: Respect the .versionrc.json configuration: + - ONLY include these sections with these exact names: + * "Features" (for feat: commits) + * "Bug Fixes" (for fix: commits) + * "Refactor" (for refactor: commits) + * "Reverts" (for revert: commits) + * "Themes" (for theme: commits) + - DO NOT include chore, ci, docs, perf, or test commits (marked as hidden in .versionrc.json) + - Use ONLY the section names specified above, not generic names like "Other" + - CRITICAL: DO NOT include a section if there are no changes for it - completely omit empty sections + - NEVER write placeholder text like "No new themes" or "No changes in this category" - just skip the entire section + + Segment changes (public-facing): + - ONLY when you see changes to files in the EXACT path src/segments/*.go (excluding *_test.go), these are prompt segments that users configure + - Changes to other paths like src/dsc/, src/config/, src/engine/, etc. are NOT segments - they are internal implementation details + - A segment is a customizable component users add to their shell prompt (e.g., git status, battery level, current directory) + - Refer to .github/instructions/segment.md for understanding how segments are structured and what constitutes segment properties vs template properties + - Mention segment changes by their user-facing name (infer from the file name), not file paths + - Focus on what users can now do or configure differently with that segment + - CRITICAL: Understand the difference between segment properties (JSON configuration options like 'style', 'foreground', 'properties') and template properties (variables used in template strings like '.ChangeID', '.Working') + - When a change adds a new template property (e.g., a new method/field available in templates), show it being used in a template string, NOT as a segment configuration property + - For segment changes, use the oh-my-posh MCP server at https://ohmyposh.dev/api/mcp to generate JSON code snippets showing example configurations or segment usage + - Every snippet (configuration or segment) MUST be validated using the MCP server before adding it to the changelog + - If a snippet cannot be created or validated correctly using the MCP server, discard that snippet and continue processing other changes + - Include validated snippets as practical examples to help users understand how to use the new or modified segment features + + Goals: + - ENHANCE the existing changelog entries with helpful context and examples + - DO NOT add new sections or restructure the existing changelog + - Summarize highlights up front with context and impact + - Keep the exact same section headings that already exist in the "Existing release notes" + - Call out breaking changes and required migrations with explicit before/after examples or commands + - Add practical usage notes or snippets to help users adopt new features or changes + - For segment changes, explain the user-facing impact (e.g., "The Git segment now supports...") + - Credit contributors at the end (they are pre-filtered and formatted as GitHub profile links) - ONLY if contributors list is not empty + - Include a "Full diff" link footer + + Requirements: + - Output valid Markdown only, no front matter, no HTML, no title heading + - Do not include a title like "Changelog for vX.Y.Z" - start directly with the content + - Keep to ~300-800 words unless there are many breaking changes + - Prefer code blocks for examples with proper language tags (bash, json, yaml, toml, powershell) + - Do not invent features not present in the commits/diff + - Do not list individual file paths unless they are user-facing config/theme files + PROMPT + ) + # Build the user content + REPO="${{ github.repository }}" + COMPARE_URL="${{ steps.diff.outputs.compare_url }}" + CURR="$CURRENT_TAG" + PREV="${{ steps.diff.outputs.base_ref }}" + EXISTING=$(cat existing_notes.md || true) + SUBJECTS="$(cat commits_subjects.txt || true)" + DETAILS="$(cat commits_detailed.txt || true)" + FILES="$(cat files_changed.txt || true)" + CONTRIBUTORS="$(cat contributors.txt || true)" + ISSUES_CONTEXT="$(cat issues_context.txt || true)" + VERSIONRC="$(cat .versionrc.json || echo '{}')" + USER_CONTENT=$(cat << EOF + Repository: ${REPO} + Release: ${CURR} + Previous: ${PREV:-} + Release URL: ${HTML_URL} + Compare URL: ${COMPARE_URL} + + .versionrc.json configuration (sections to show/hide): + --- + ${VERSIONRC} + --- + + Existing release notes (from conventional commits): + --- + ${EXISTING} + --- + + Conventional commits (subjects): + --- + ${SUBJECTS} + --- + + Commits (details): + --- + ${DETAILS} + --- + + Changed files (for context on segment changes only, do not list paths in output): + --- + ${FILES} + --- + + Referenced issues (for additional context, explain impact in user terms): + --- + ${ISSUES_CONTEXT} + --- + + Contributors: + --- + ${CONTRIBUTORS} + --- + EOF + ) + echo " Calling GitHub Models API..." + OUTPUT_MD="" + set +e + RESP=$(curl -sS -f -X POST "https://models.github.ai/inference/chat/completions" \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$(jq -n --arg model "$MODEL" --arg sys "$SYSTEM_PROMPT" --arg user "$USER_CONTENT" '{model:$model, messages: [{role:"system",content:$sys},{role:"user",content:$user}], temperature: 0.2, max_tokens: 4000}')") + CURL_EXIT=$? + if [ $CURL_EXIT -eq 0 ]; then + OUTPUT_MD=$(printf "%s" "$RESP" | jq -r '.choices[0].message.content // empty') + echo " ✅ API call successful" + else + echo " ❌ API call failed with exit code: $CURL_EXIT" + echo " Response: $RESP" + fi + set -e + if [[ -z "$OUTPUT_MD" ]]; then + echo "❌ AI generation failed or no output produced." + echo "enhanced_body=" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo " Generated changelog length: $(printf "%s" "$OUTPUT_MD" | wc -c) characters" + # Save the AI-generated changelog + echo "$OUTPUT_MD" > enhanced_changelog.md + echo "✅ Enhanced changelog saved to enhanced_changelog.md" + echo "enhanced_body<> "$GITHUB_OUTPUT" + cat enhanced_changelog.md >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + echo "✅ Changelog saved to step output" + + - name: Update release body + if: ${{ steps.ai.outputs.enhanced_body != '' }} + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + set -a; source ctx.env; set +a + if [[ "$DRY_RUN" == "true" ]]; then + echo "🧪 Dry run mode enabled - skipping release update" + echo " The generated changelog would be applied to release ID: ${RELEASE_ID}" + exit 0 + fi + echo "📝 Updating release body for release ID: ${RELEASE_ID}" + # Use the AI-generated changelog as the complete release body + PAYLOAD=$(jq -Rs '{body: .}' < enhanced_changelog.md) + gh api -X PATCH repos/${{ github.repository }}/releases/${RELEASE_ID} -H "Content-Type: application/json" -d "$PAYLOAD" + echo "✅ Release body updated successfully" + + - name: Summary + if: ${{ always() && (inputs.dry_run || steps.ai.outputs.enhanced_body != '') }} + shell: bash + run: | + set -a; source ctx.env; set +a + echo "📊 Generating summary..." + if [[ "$DRY_RUN" == "true" ]]; then + echo "## 🧪 Dry Run - Enhanced Changelog Preview" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [[ -f enhanced_changelog.md ]]; then + echo "**Release would not be modified.** Below is the generated changelog:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat enhanced_changelog.md >> $GITHUB_STEP_SUMMARY + else + echo "⚠️ **AI generation failed or no changelog was produced.**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Check the workflow logs for details." >> $GITHUB_STEP_SUMMARY + fi + else + echo "## ✅ Enhanced Changelog Generated" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Release body has been updated with AI-enhanced changelog:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat enhanced_changelog.md >> $GITHUB_STEP_SUMMARY + fi + echo "✅ Summary generated" + + - name: Skipped notice + if: ${{ steps.ai.outputs.enhanced_body == '' }} + run: | + echo "❌ AI changelog generation skipped or failed. Ensure GitHub Models access is enabled for this repo." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 000000000000..fd686f26b909 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,36 @@ +name: Android +on: + release: + types: [published] + +jobs: + build-android: + runs-on: ubuntu-latest + container: ghcr.io/jandedobbeleer/golang-android-container:latest + steps: + - name: Checkout code 👋 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - name: Build + run: | + VERSION=$(echo "${{ github.event.release.name }}" | cut -c2-) + echo "Building version ${VERSION}" + cd src + go build -o dist/posh-android-arm -ldflags="-s -w -X 'github.com/jandedobbeleer/oh-my-posh/src/build.Version=${VERSION}' -X 'github.com/jandedobbeleer/oh-my-posh/src/build.Date=$(date)'" + - name: Upload artifacts 🆙 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + console.log('environment', process.versions); + + const fs = require('fs').promises; + + const { repo: { owner, repo }, sha } = context; + console.log({ owner, repo, sha }); + + await github.rest.repos.uploadReleaseAsset({ + owner, repo, + release_id: ${{ github.event.release.id }}, + name: 'posh-android-arm', + data: await fs.readFile('./src/dist/posh-android-arm') + }); diff --git a/.github/workflows/azure-static-web-apps-ashy-meadow-063e9ba03.yml b/.github/workflows/azure-static-web-apps-ashy-meadow-063e9ba03.yml deleted file mode 100644 index e69f40cea10d..000000000000 --- a/.github/workflows/azure-static-web-apps-ashy-meadow-063e9ba03.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Azure Static Web Apps CI/CD - -on: - push: - branches: - - main - paths: - - 'docs/**' - - 'themes/**' - -jobs: - build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') - runs-on: ubuntu-latest - name: Build and Deploy Job - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - name: Build And Deploy - id: builddeploy - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ASHY_MEADOW_063E9BA03 }} - repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) - action: "upload" - ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### - # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig - app_location: "/docs" # App source code path - api_location: "api" # Api source code path - optional - output_location: "build" # Built app content directory - optional diff --git a/.github/workflows/bluesky.yml b/.github/workflows/bluesky.yml new file mode 100644 index 000000000000..71a67b8a3bec --- /dev/null +++ b/.github/workflows/bluesky.yml @@ -0,0 +1,17 @@ +name: Bluesky +on: + release: + types: [published] + workflow_dispatch: + +jobs: + bluesky: + runs-on: ubuntu-latest + steps: + - name: Publish + uses: JanDeDobbeleer/bluesky-releasenotes-action@main + with: + title: "The best release yet 🚀" + bluesky-identifier: ${{ secrets.BLUESKY_IDENTIFIER }} + bluesky-password: ${{ secrets.BLUESKY_PASSWORD }} + github-token: ${{ secrets.GH_PAT }} diff --git a/.github/workflows/build_code.yml b/.github/workflows/build_code.yml new file mode 100644 index 000000000000..def00d9c4759 --- /dev/null +++ b/.github/workflows/build_code.yml @@ -0,0 +1,40 @@ +on: + pull_request: + paths-ignore: + - 'README.md' + - 'CONTRIBUTING.md' + - 'COPYING' + - 'website/**' + - '.github/*.md' + - '.github/FUNDING.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +name: Build Code +jobs: + build: + runs-on: macos-latest + defaults: + run: + shell: pwsh + steps: + - name: Checkout code 👋 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - name: Install Go 🗳 + uses: ./.github/workflows/composite/bootstrap-go + - name: Run GoReleaser 🚀 + uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 + with: + distribution: goreleaser + version: v2.3.2 + args: build --clean --snapshot --skip=post-hooks --skip=before + workdir: src + - name: Archive production artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: builds + retention-days: 1 + path: | + src/dist diff --git a/.github/workflows/close_themes_pr.yml b/.github/workflows/close_themes_pr.yml new file mode 100644 index 000000000000..9cb71184e6e1 --- /dev/null +++ b/.github/workflows/close_themes_pr.yml @@ -0,0 +1,61 @@ +name: Close Themes PR +on: + pull_request_target: + types: + - opened + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Checkout code 👋 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - name: Check and close 🔐 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 + with: + github-token: ${{ secrets.GH_PAT }} + script: | + const { repo: { owner, repo } } = context; + const pr = context.payload.pull_request; + + const response = await github.rest.pulls.listFiles({ + owner, repo, + pull_number: pr.number + }); + + if (response.status !== 200) { + console.log('Could not fetch files'); + return; + } + + let hasThemeAdditions = false; + for (const file of response.data) { + const name = file.filename + console.log(`File: ${name}`); + if (file.status === 'added' && name.includes('themes/')) { + console.log(`File: ${name} is a theme addition`); + hasThemeAdditions = true; + break; + } + } + + if (!hasThemeAdditions) { + console.log('No theme additions found.'); + return; + } + + const body = `👋 @${pr.user.login}, theme aditions are no longer accepted due to the ever growing set. We do however accept showcasing your custom theme in the [🎨 Themes section](https://github.com/JanDeDobbeleer/oh-my-posh/discussions/categories/themes) or [themes channel](https://discord.com/channels/1023597603331526656/1055533233309233252) on Discord.` + + console.log(`Adding comment: ${body}`); + await github.rest.issues.createComment({ + owner, repo, + issue_number: pr.number, + body, + }); + + console.log(`Closing pull request: ${pr.html_url}`); + await github.rest.pulls.update({ + owner, repo, + pull_number: pr.number, + state: "closed", + }); diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml index deb77ca847a6..f80389222fa0 100644 --- a/.github/workflows/code.yml +++ b/.github/workflows/code.yml @@ -4,35 +4,40 @@ on: - 'README.md' - 'CONTRIBUTING.md' - 'COPYING' - - 'docs/**' + - 'website/**' - '.github/*.md' - '.github/FUNDING.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + name: Validate Code jobs: test: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - include: - - os: ubuntu-latest - - os: macos-latest - - os: windows-latest runs-on: ${{ matrix.os }} defaults: run: working-directory: ${{ github.workspace }}/src steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: 1.16 - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - name: Install Go 🗳 + uses: ./.github/workflows/composite/bootstrap-go - name: Golang CI - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 with: - version: latest working-directory: src + - name: Fieldalignment + run: | + go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest + fieldalignment "./..." + - name: Modernize + run: | + go install golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest + modernize "./..." - name: Unit Tests - run: go test . -v + run: go test "./..." diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index afad58879993..000000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,35 +0,0 @@ -on: - pull_request: - paths-ignore: - - 'README.md' - - 'CONTRIBUTING.md' - - 'COPYING' - - 'docs/**' - - '.github/*.md' - - '.github/FUNDING.yml' - push: - branches: - - main - -name: Code QL -jobs: - code-ql: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ${{ github.workspace }}/src - steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: 1.16 - - name: Checkout code - uses: actions/checkout@v2 - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: go - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/commits.yml b/.github/workflows/commits.yml index 89616ec6d0ef..4eb6341e4dba 100644 --- a/.github/workflows/commits.yml +++ b/.github/workflows/commits.yml @@ -2,13 +2,10 @@ name: Validate Commits on: [pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: commitlint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: wagoid/commitlint-github-action@v2 - with: - configFile: './.commitlintrc.yml' + uses: jandedobbeleer/workflows/.github/workflows/commits.yml@main diff --git a/.github/workflows/composite/bootstrap-go/action.yml b/.github/workflows/composite/bootstrap-go/action.yml new file mode 100644 index 000000000000..62475bd500fc --- /dev/null +++ b/.github/workflows/composite/bootstrap-go/action.yml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-action.json +name: "Setup Go" +description: "Install Go and override with the custom build" +branding: + icon: download + color: purple +runs: + using: "composite" + steps: + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 + with: + go-version: "1.26.0" + cache-dependency-path: src/go.sum diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml new file mode 100644 index 000000000000..2ab270af9102 --- /dev/null +++ b/.github/workflows/contributors.yml @@ -0,0 +1,11 @@ +name: Contributors +on: + pull_request_target: + types: + - closed + +jobs: + contributors: + uses: jandedobbeleer/workflows/.github/workflows/contributors.yml@main + secrets: + token: ${{ secrets.GH_PAT }} diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 000000000000..d316b61d5ce9 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,31 @@ +name: "Copilot Setup Steps" + +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + copilot-setup-steps: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + + - name: Install apm-cli and run apm install + run: | + pip install apm-cli + apm install + git restore apm.lock.yaml + + - name: Install ast-grep + run: | + pip install ast-grep-cli diff --git a/.github/workflows/delete_store_submission.yml b/.github/workflows/delete_store_submission.yml new file mode 100644 index 000000000000..fa525bd8873b --- /dev/null +++ b/.github/workflows/delete_store_submission.yml @@ -0,0 +1,25 @@ +name: Delete Store Submission + +on: + workflow_dispatch: + +jobs: + delete_submission: + name: Delete Store Submission + runs-on: ubuntu-latest + steps: + - name: Configure Store Credentials 🔑 + uses: jandedobbeleer/store-submission@submission-status + with: + command: configure + type: win32 + seller-id: ${{ secrets.SELLER_ID }} + product-id: ${{ secrets.PRODUCT_ID }} + tenant-id: ${{ secrets.TENANT_ID }} + client-id: ${{ secrets.CLIENT_ID }} + client-secret: ${{ secrets.CLIENT_SECRET }} + + - name: Delete Submission 🗑️ + uses: jandedobbeleer/store-submission@submission-status + with: + command: delete diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 000000000000..78c8d3424bfe --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,12 @@ +name: Dependabot auto-merge +on: + pull_request: + types: [opened, reopened] + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + uses: jandedobbeleer/workflows/.github/workflows/dependabot.yml@main diff --git a/.github/workflows/discord.yml b/.github/workflows/discord.yml new file mode 100644 index 000000000000..3ce70bafd0c3 --- /dev/null +++ b/.github/workflows/discord.yml @@ -0,0 +1,10 @@ +name: Discord +on: + release: + types: [published] + +jobs: + notify: + uses: jandedobbeleer/workflows/.github/workflows/discord.yml@main + secrets: + webhook: ${{ secrets.CHANGELOG_WEBHOOK }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index af286b34e62a..77dcc1a200a1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,39 +1,76 @@ -name: Build and Deploy +name: Azure Static Web Apps CI/CD + on: push: branches: - main paths: - - 'docs/**' - - 'themes/**' + - "website/**" + - "themes/**" + workflow_dispatch: + +permissions: + id-token: write + contents: read + jobs: - build-and-deploy: + build_and_deploy: runs-on: ubuntu-latest + name: Build and Deploy steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 with: + submodules: true persist-credentials: false - - name: Install Go - uses: actions/setup-go@v2 + - name: Install Go 🗳 + uses: ./.github/workflows/composite/bootstrap-go + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e with: - go-version: 1.16 - - name: Build oh-my-posh 🎨 + node-version: 20.9.0 + # Create Kind cluster to have a Kubernetes context for cloud-native-azure theme + # Images are defined on every Kind release + # See https://github.com/kubernetes-sigs/kind/releases + - name: Create k8s v1.23 Kind Cluster + uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc + with: + node_image: kindest/node:v1.23.4@sha256:0e34f0d0fd448aa2f2819cfd74e99fe5793a6e4938b328f657c8e3f81ee0dfb9 + cluster_name: posh + - name: Create Kubernetes namespace + run: | + kubectl create ns demo + - name: Set default Kubernetes namespace + run: | + kubectl config set-context posh --namespace demo + - uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + - name: Build oh-my-posh 🔧 run: | cd src go build -o ./bin/oh-my-posh cd .. - - name: Install and Build 🔧 + - name: Render themes 🎨 run: | export PATH="$PWD/src/bin:$PATH" - cd docs + cd website npm install npm run themes - npm run build - - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@3.6.2 + cd .. + - name: Copy schema for MCP validator 📋 + run: | + mkdir -p website/api/data + cp themes/schema.json website/api/data/schema.json + echo "✅ Copied schema.json to website/api/data/" + - name: Build Docs And Deploy 🚀 + id: builddeploy + uses: Azure/static-web-apps-deploy@1a947af9992250f3bc2e68ad0754c0b0c11566c9 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: gh-pages # The branch the action should deploy to. - FOLDER: docs/build # The folder the action should deploy. - CLEAN: true # Automatically remove deleted files from the deploy branch + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ASHY_MEADOW_063E9BA03 }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for GitHub integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "/website" # App source code path + api_location: "/website/api" # Api source code path - optional + output_location: "build" # Built app content directory - optional diff --git a/.github/workflows/edit_rights.yml b/.github/workflows/edit_rights.yml new file mode 100644 index 000000000000..48854ac6a5f7 --- /dev/null +++ b/.github/workflows/edit_rights.yml @@ -0,0 +1,20 @@ +name: Notify When Maintainers Cannot Edit + +# **What it does**: Notifies the author of a PR when their PR does not allow maintainers to edit it. +# **Why we have it**: To prevent having to do this manually. +# **Who does it impact**: Open-source. + +on: + pull_request_target: + types: + - opened + - edited + +permissions: + pull-requests: write + +jobs: + notify-when-maintainers-cannot-edit: + uses: jandedobbeleer/workflows/.github/workflows/edit_rights.yml@main + secrets: + token: ${{ secrets.GH_PAT }} diff --git a/.github/workflows/gomod.yml b/.github/workflows/gomod.yml index e02980f771ed..150d085bd484 100644 --- a/.github/workflows/gomod.yml +++ b/.github/workflows/gomod.yml @@ -2,6 +2,10 @@ name: Go Mod on: [pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: go-mod: runs-on: ubuntu-latest @@ -9,16 +13,14 @@ jobs: run: working-directory: ${{ github.workspace }}/src steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: 1.16 - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - name: Install Go 🗳 + uses: ./.github/workflows/composite/bootstrap-go - name: Check for unused dependencies run: | go mod tidy - if [ "$(git status | grep -c "nothing to commit, working tree clean")" == "1" ]; then + if [ "$(git status | grep -c "nothing to commit, working tree clean")" -eq 1 ]; then echo "Nothing to tidy" exit 0 fi diff --git a/.github/workflows/homebrew.yml b/.github/workflows/homebrew.yml new file mode 100644 index 000000000000..058e693a007a --- /dev/null +++ b/.github/workflows/homebrew.yml @@ -0,0 +1,21 @@ +name: Homebrew +on: + release: + types: [published] + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Notify Homebrew Repo 🙋🏾‍♀️ + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 + with: + github-token: ${{ secrets.GH_PAT }} + script: | + await github.request('POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches', { + owner: 'jandedobbeleer', + repo: 'homebrew-oh-my-posh', + workflow_id: 'release.yml', + ref: 'main', + inputs: {"version": process.env.GITHUB_REF.replace('refs/tags/v', '')} + }) diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 000000000000..74e11e91be86 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,29 @@ +name: 'Lock Threads' + +on: + schedule: + - cron: '0 0 * * 1' + +permissions: + issues: write + +concurrency: + group: lock + +jobs: + action: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 + with: + issue-inactive-days: '90' + issue-comment: > + This issue has been automatically locked since there + has not been any recent activity (i.e. last half year) after it was closed. + It helps our maintainers focus on the active issues. + + If you have found a problem that seems similar, please open a + [discussion](https://github.com/JanDeDobbeleer/oh-my-posh/discussions/new?category=troubleshoot) + first, complete the body with all the details necessary to reproduce, + and mention this issue as reference. + process-only: 'issues' diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml index d18554801e40..c868c62dd763 100644 --- a/.github/workflows/markdown.yml +++ b/.github/workflows/markdown.yml @@ -2,14 +2,18 @@ name: Markdownlint on: [pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 - name: Lint files - uses: articulate/actions-markdownlint@v1 + uses: DavidAnson/markdownlint-cli2-action@ded1f9488f68a970bc66ea5619e13e9b52e601cd with: - files: . - config: .markdownlint.yaml + config: .markdownlint-cli2.yaml + globs: '**/*.md' diff --git a/.github/workflows/merge_contributions_pr.yml b/.github/workflows/merge_contributions_pr.yml new file mode 100644 index 000000000000..d5d14215845b --- /dev/null +++ b/.github/workflows/merge_contributions_pr.yml @@ -0,0 +1,32 @@ +name: Merge contributions PR +on: + pull_request_target: + types: + - opened + - reopened + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Checkout code 👋 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - name: Check and merge ⛙ + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 + with: + github-token: ${{ secrets.GH_PAT }} + script: | + const { repo: { owner, repo } } = context; + const pr = context.payload.pull_request; + + if (pr.user.id !== 46447321) { + console.log('Not an all-contributors pull request'); + return; + } + + console.log(`Merging pull request: ${pr.html_url}`); + await github.rest.pulls.merge({ + owner, repo, + pull_number: pr.number, + merge_method: "rebase", + }); diff --git a/.github/workflows/microsoft_store.yml b/.github/workflows/microsoft_store.yml new file mode 100644 index 000000000000..f9469b76aa83 --- /dev/null +++ b/.github/workflows/microsoft_store.yml @@ -0,0 +1,47 @@ +name: Windows Store +on: + release: + types: [published] + +jobs: + microsoft_store: + name: Publish To Windows Store + runs-on: ubuntu-latest + steps: + - name: Configure Store Credentials 🔑 + uses: jandedobbeleer/store-submission@submission-status + with: + command: configure + type: win32 + seller-id: ${{ secrets.SELLER_ID }} + product-id: ${{ secrets.PRODUCT_ID }} + tenant-id: ${{ secrets.TENANT_ID }} + client-id: ${{ secrets.CLIENT_ID }} + client-secret: ${{ secrets.CLIENT_SECRET }} + only-on-ready: true + - name: Update draft submission + uses: jandedobbeleer/store-submission@submission-status + with: + command: update + product-update: '{ + "packages":[ + { + "packageUrl":"https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/${{ github.event.release.tag_name }}/install-x64.msi", + "languages":["en"], + "architectures":["X64"], + "installerParameters":"/quiet INSTALLER=ws", + "isSilentInstall":false + }, + { + "packageUrl":"https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/${{ github.event.release.tag_name }}/install-arm64.msi", + "languages":["en"], + "architectures":["Arm64"], + "installerParameters":"/quiet INSTALLER=ws", + "isSilentInstall":false + } + ] + }' + - name: Publish Submission + uses: jandedobbeleer/store-submission@submission-status + with: + command: publish diff --git a/.github/workflows/publish-mcp.yml b/.github/workflows/publish-mcp.yml new file mode 100644 index 000000000000..6429be0e8b38 --- /dev/null +++ b/.github/workflows/publish-mcp.yml @@ -0,0 +1,65 @@ +name: Publish to MCP Registry + +on: + push: + branches: + - main + paths: + - 'website/api/mcp/**' + workflow_dispatch: + +jobs: + publish: + name: Publish MCP Server + runs-on: ubuntu-latest + permissions: + id-token: write # Required for OIDC authentication + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + with: + fetch-depth: 0 # Fetch all tags + + - name: Extract version from latest git tag + id: version + run: | + # Get the latest git tag (without 'v' prefix) + VERSION=$(git describe --tags --abbrev=0 | sed 's/^v//') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Using version from git tag: $VERSION" + + - name: Update server.json version + run: | + cd website/api/mcp + jq --arg v "${{ steps.version.outputs.version }}" '.version = $v' server.json > tmp.json && mv tmp.json server.json + echo "Updated server.json version to ${{ steps.version.outputs.version }}" + cat server.json + + - name: Validate server.json + run: | + cd website/api + npm ci + cd mcp + node validate-server.js + + - name: Install MCP Publisher + run: | + curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar -xzf - + chmod +x mcp-publisher + ./mcp-publisher --version + + - name: Login to MCP Registry + env: + MCP_REGISTRY_PEM: ${{ secrets.MCP_REGISTRY_PEM }} + run: | + echo "$MCP_REGISTRY_PEM" > key.pem + PRIVATE_KEY=$(openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n') + ./mcp-publisher login dns --domain ohmyposh.dev --private-key "$PRIVATE_KEY" + rm -f key.pem + + - name: Publish to MCP Registry + run: | + cd website/api/mcp + ../../../mcp-publisher publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1820c9760ae2..39a61ec652ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,286 +5,175 @@ on: branches: - main paths: - - 'src/**' - - 'packages/**' - - 'themes/**' + - "src/**" + - "packages/**" + - ".github/workflows/**" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} jobs: - release: + changelog: runs-on: ubuntu-latest outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} version: ${{ steps.changelog.outputs.version }} + body: ${{ steps.changelog.outputs.clean_changelog }} + tag: ${{ steps.changelog.outputs.tag }} skipped: ${{ steps.changelog.outputs.skipped }} steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Create changelog + - name: Checkout code 👋 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - name: Create changelog ✍️ id: changelog - uses: TriPSs/conventional-changelog-action@v3 + uses: TriPSs/conventional-changelog-action@952b14bbc4be87e8458a6ac5926fc655608b1b19 with: github-token: ${{ secrets.github_token }} skip-version-file: "true" output-file: "false" skip-commit: "true" skip-on-empty: "true" - - name: Create Github Release - id: create_release - uses: actions/create-release@v1 - if: ${{ steps.changelog.outputs.skipped == 'false' }} - env: - GITHUB_TOKEN: ${{ secrets.github_token }} - with: - tag_name: ${{ steps.changelog.outputs.tag }} - release_name: ${{ steps.changelog.outputs.tag }} - body: ${{ steps.changelog.outputs.clean_changelog }} + skip-tag: "true" + artifacts: - needs: release - if: ${{ needs.release.outputs.skipped == 'false' }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - goarch: [amd64] - include: - - os: ubuntu-latest - goarch: arm - - os: windows-latest - goarch: 386 - runs-on: ${{ matrix.os }} - defaults: - run: - working-directory: ${{ github.workspace }}/src - outputs: - hash_linux: ${{ steps.hash.outputs.hash_ubuntu-latest }} - hash_macos: ${{ steps.hash.outputs.hash_macos-latest }} - hash_windows: ${{ steps.hash.outputs.hash_windows-latest }} - steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: 1.16 - - name: Checkout code - uses: actions/checkout@v2 - - name: Asset name - id: artifact - run: | - if ($IsLinux) { - $artifact = "posh-linux-${{ matrix.goarch }}" - Write-Output "::set-output name=name::$($artifact)" - return - } - if ($IsMacOS) { - $artifact = "posh-darwin-${{ matrix.goarch }}" - Write-Output "::set-output name=name::$($artifact)" - return - } - if ($IsWindows) { - $artifact = "posh-windows-${{ matrix.goarch }}.exe" - Write-Output "::set-output name=name::$($artifact)" - return - } - shell: pwsh - - name: Build - id: build - run: go build -o ${{ steps.artifact.outputs.name }} -ldflags="-X 'main.Version=${{ needs.release.outputs.version }}'" - env: - GOARCH: ${{ matrix.goarch }} - - name: Hash - id: hash - run: | - $fileHash = Get-FileHash ${{ steps.artifact.outputs.name }} -Algorithm SHA256 - $fileHash.Hash | Out-File -Encoding 'UTF8' ${{ steps.artifact.outputs.name }}.sha256 - Write-Output "::set-output name=hash_${{ matrix.os }}::$($fileHash.Hash)" - shell: pwsh - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.upload_url }} - asset_path: src/${{ steps.artifact.outputs.name }} - asset_name: ${{ steps.artifact.outputs.name }} - asset_content_type: application/octet-stream - - name: Upload Hash Asset - id: upload-hash-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.upload_url }} - asset_path: src/${{ steps.artifact.outputs.name }}.sha256 - asset_name: ${{ steps.artifact.outputs.name }}.sha256 - asset_content_type: text/plain - themes: - needs: release - if: ${{ needs.release.outputs.skipped == 'false' }} - runs-on: ubuntu-latest - outputs: - hash_themes: ${{ steps.hash.outputs.hash_themes }} + needs: changelog + if: ${{ needs.changelog.outputs.skipped == 'false' }} + runs-on: windows-latest defaults: run: shell: pwsh + working-directory: ${{ github.workspace }}/build steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Zip theme files - run: | - $compress = @{ - Path = "themes\*.json" - CompressionLevel = "Fastest" - DestinationPath = "themes.zip" - } - Compress-Archive @compress - - name: Hash - id: hash - run: | - $fileHash = Get-FileHash themes.zip -Algorithm SHA256 - $fileHash.Hash | Out-File -Encoding 'UTF8' themes.zip.sha256 - Write-Output "::set-output name=hash_themes::$($fileHash.Hash)" - - name: Upload Themes - id: upload-themes - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.upload_url }} - asset_path: themes.zip - asset_name: themes.zip - asset_content_type: application/octet-stream - - name: Upload Themes Hash - id: upload-hash-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.upload_url }} - asset_path: themes.zip.sha256 - asset_name: themes.zip.sha256 - asset_content_type: text/plain - homebrew: - needs: release - if: ${{ needs.release.outputs.skipped == 'false' }} - runs-on: ubuntu-latest - env: - GH_KEY: ${{ secrets.GH_PAT }} - steps: - - name: Push Version - run: | - curl -XPOST -u "jandedobbeleer:$GH_KEY" \ - -H "Accept: application/vnd.github.everest-preview+json" \ - -H "Content-Type: application/json" https://api.github.com/repos/jandedobbeleer/homebrew-oh-my-posh/actions/workflows/release.yml/dispatches \ - --data '{"ref": "main", "inputs": {"version": "${{ needs.release.outputs.version }}"} }' - powershell: - needs: [release, artifacts] - if: ${{ needs.release.outputs.skipped == 'false' }} - runs-on: ubuntu-latest + - name: Checkout code 👋 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - name: Install Go 🗳 + uses: ./.github/workflows/composite/bootstrap-go + - name: Pre Build 😸 + env: + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + run: | + ./pre.ps1 -Version ${{ needs.changelog.outputs.version }} -SDKVersion "10.0.26100.0" + - name: Run GoReleaser 🚀 + uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 + with: + distribution: goreleaser + version: v2.3.2 + args: release --clean --skip publish + workdir: src + env: + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + - name: Post Build 🤐 + run: | + ./post.ps1 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: build-artifacts + path: | + src/dist/posh-* + src/dist/themes.* + src/dist/checksums.* + msi: + needs: + - changelog + - artifacts + runs-on: windows-latest + strategy: + matrix: + arch: [x64, arm64] defaults: run: shell: pwsh - working-directory: ${{ github.workspace }}/packages/powershell/oh-my-posh - env: - PSGALLERY_KEY: ${{ secrets.PSGALLERY_KEY }} + working-directory: ${{ github.workspace }}/packages/msi steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Pack and push - run: ./deploy.ps1 -BinVersion ${{ needs.release.outputs.version }} -ModuleVersion ${{ needs.release.outputs.version }} -Repository PSGallery -RepositoryAPIKey $env:PSGALLERY_KEY - scoop: - needs: [release, artifacts] - if: ${{ needs.release.outputs.skipped == 'false' }} + - name: Checkout code 👋 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: build-artifacts + path: dist + - name: Install Wix Toolset 🛠 + run: dotnet tool install --global wix + - name: Build installer 📦 + id: build + env: + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + run: | + $version = '${{ needs.changelog.outputs.version }}'.TrimStart("v") + ./build.ps1 -Architecture ${{ matrix.arch }} -Version $version -Copy -Sign -SDKVersion "10.0.26100.0" + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: msi-artifact-${{ matrix.arch }} + path: | + packages/msi/out/install-${{ matrix.arch }}.msi + packages/msi/out/install-${{ matrix.arch }}.msix + release: runs-on: ubuntu-latest - defaults: - run: - shell: pwsh - working-directory: ${{ github.workspace }}/packages/scoop + needs: + - changelog + - artifacts + - msi steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Update Template - run: ./build.ps1 -Version ${{ needs.release.outputs.version }} - - name: Upload Scoop JSON - id: upload-scoop-json - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/packages/scoop/oh-my-posh.json - asset_name: oh-my-posh.json - asset_content_type: text/plain - - name: Upload Scoop Archive - id: upload-scoop-post-install - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/packages/scoop/posh-windows-wsl-amd64.7z - asset_name: posh-windows-wsl-amd64.7z - asset_content_type: application/octet-stream - - name: Upload Scoop Archive Hash - id: upload-scoop-post-install-hash - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/packages/scoop/posh-windows-wsl-amd64.7z.sha256 - asset_name: posh-windows-wsl-amd64.7z.sha256 - asset_content_type: text/plain - inno: - needs: [release, artifacts] - if: ${{ needs.release.outputs.skipped == 'false' }} - runs-on: windows-latest - outputs: - hash: ${{ steps.hash.outputs.hash_inno }} - defaults: - run: - shell: pwsh - working-directory: ${{ github.workspace }}/packages/inno - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Build installer - run: ./build.ps1 -Version ${{ needs.release.outputs.version }} - - name: Output Hash - id: hash - run: | - $hash = Get-Content -Path Output/install.exe.sha256 - Write-Output "::set-output name=hash_inno::$($hash)" - - name: Upload Inno Installer - id: upload-inno-installer - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/packages/inno/Output/install.exe - asset_name: install.exe - asset_content_type: text/plain - - name: Upload Inno Installer Hash - id: upload-inno-installer-hash - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/packages/inno/Output/install.exe.sha256 - asset_name: install.exe.sha256 - asset_content_type: text/plain + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + merge-multiple: true + - name: Upload version file + env: + AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.CDN_CONNECTIONSTRING }} + run: | + echo v${{ needs.changelog.outputs.version }} > version.txt + az storage blob upload-batch --destination releases/v${{ needs.changelog.outputs.version }} --source . + az storage blob upload-batch --destination releases/latest --overwrite true --source . + - name: Release 🎓 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda + with: + tag_name: ${{ needs.changelog.outputs.tag }} + body: ${{ needs.changelog.outputs.body }} + fail_on_unmatched_files: true + token: ${{ secrets.GH_PAT }} + files: | + * winget: - needs: [release, inno] - if: ${{ needs.release.outputs.skipped == 'false' }} runs-on: windows-latest - defaults: - run: - shell: pwsh - working-directory: ${{ github.workspace }}/packages/winget + needs: + - changelog + - release env: WINGETCREATE_TOKEN: ${{ secrets.WINGETCREATE_TOKEN }} steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Create manifest and submit PR - run: ./build.ps1 -Version ${{ needs.release.outputs.version }} -Hash ${{ needs.inno.outputs.hash }} -Token $env:WINGETCREATE_TOKEN + - name: Create manifest and submit PR 📦 + shell: pwsh + run: | + Write-Host "Preparing to submit to WinGet repository..." -ForegroundColor Green + + # Install the latest wingetcreate exe + # Need to do things this way, see https://github.com/PowerShell/PowerShell/issues/13138 + Write-Verbose "Importing Appx module using Windows PowerShell compatibility" + Import-Module Appx -UseWindowsPowerShell -ErrorAction Stop + + # Download and install Winget-Create msixbundle + $appxBundleFile = Join-Path -Path $env:TEMP -ChildPath "wingetcreate.msixbundle" + Write-Verbose "Downloading wingetcreate to: $appxBundleFile" + + Invoke-WebRequest -Uri "https://aka.ms/wingetcreate/latest/msixbundle" -OutFile $appxBundleFile -ErrorAction Stop + Add-AppxPackage -Path $appxBundleFile -ErrorAction Stop + + Write-Verbose "Successfully installed wingetcreate" + + # Submit the PR to WinGet repository + Write-Host "Submitting pull request to WinGet repository..." -ForegroundColor Green + + $version = "${{ needs.changelog.outputs.tag }}" + $version = $version.TrimStart('v') + + $urls = @( + "https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/v$version/install-x64.msi|x64", + "https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/v$version/install-x64.msix|x64", + "https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/v$version/install-arm64.msi|arm64", + "https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/v$version/install-arm64.msix|arm64" + ) + + wingetcreate update JanDeDobbeleer.OhMyPosh --version $version --token $env:WINGETCREATE_TOKEN --submit --urls $urls diff --git a/.github/workflows/vale.yml b/.github/workflows/vale.yml new file mode 100644 index 000000000000..a7dbe0645fd8 --- /dev/null +++ b/.github/workflows/vale.yml @@ -0,0 +1,22 @@ +name: Vale + +on: [pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + vale: + name: runner / vale + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4 + - name: Install Vale + run: | + curl -sfL https://github.com/errata-ai/vale/releases/download/v3.13.1/vale_3.13.1_Linux_64-bit.tar.gz | tar -xz + sudo mv vale /usr/local/bin/vale + - name: Sync Vale packages + run: vale sync + - name: Lint + run: vale AGENTS.md .github/copilot-instructions.md .github/skills diff --git a/.gitignore b/.gitignore index c8842c10708b..3f821b48e35d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,23 @@ +# APM +apm_modules/ +.github/skills/* +.github/instructions/* +!.github/skills/segment-create/ +!.github/skills/segment-docs/ +.github/instructions/* +.agents/* + +# Others + +.specs/ +.fleet/ +src/test/umbraco/obj/ +src/keys +*.prof +*.wixpdb +packages/msi/Microsoft.Trusted.Signing.Client +.claude +.styles # Created by https://www.toptal.com/developers/gitignore/api/node,go,visualstudiocode # Edit at https://www.toptal.com/developers/gitignore?templates=node,go,visualstudiocode @@ -9,8 +29,9 @@ *.dll *.so *.dylib -# Initialization scripts generated by https://github.com/kevinburke/go-bindata -init.go + +# Windows asset files +/src/rsrc_windows_*.syso # Test binary, built with `go test -c` *.test @@ -158,3 +179,91 @@ Output/ # images *.png + +# go releaser +/src/dist + + +# Created by https://www.toptal.com/developers/gitignore/api/windows,linux,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,macos + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos + +# Keys + +cosign.key + +*.omp.json.bak + +__debug_bin +src/src diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml new file mode 100644 index 000000000000..ed9fc5f7020e --- /dev/null +++ b/.markdownlint-cli2.yaml @@ -0,0 +1,12 @@ +config: + MD013: + line_length: 120 + code_blocks: false + MD024: false +fix: true +gitignore: true +ignores: + - node_modules/ + - .github/agents/segment-docs.md + - .github/agents/architecture.md + - .github/PULL_REQUEST_TEMPLATE.md diff --git a/.markdownlint.yaml b/.markdownlint.yaml deleted file mode 100644 index 77f924f2896f..000000000000 --- a/.markdownlint.yaml +++ /dev/null @@ -1,5 +0,0 @@ -MD024: false -MD014: false -MD038: false -line-length: - line_length: 120 diff --git a/.markdownlintignore b/.markdownlintignore deleted file mode 100644 index c2658d7d1b31..000000000000 --- a/.markdownlintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000000..b777f7488f1f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,12 @@ +{ + "trailingComma": "none", + "overrides": [ + { + "files": ["*.jsonc", "*.json"], + "options": { + "parser": "json", + "trailingComma": "none" + } + } + ] +} diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 000000000000..e6607fbce6a4 --- /dev/null +++ b/.vale.ini @@ -0,0 +1,17 @@ +StylesPath = .styles + +MinAlertLevel = suggestion + +Packages = https://github.com/tbhb/vale-ai-tells/releases/download/v1.4.0/ai-tells.zip, https://github.com/HeyItsGilbert/vale-agentic/releases/download/v2.0.0/agentic.zip + +[*.{md}] +# ^ This section applies to only Markdown files. +# +# You can change (or add) file extensions here +# to apply these settings to other file types. +# +# For example, to apply these settings to both +# Markdown and reStructuredText: +# +# [*.{md,rst}] +BasedOnStyles = ai-tells, agentic diff --git a/.versionrc.json b/.versionrc.json index 5948bbe3ce63..2acfe69ffcac 100644 --- a/.versionrc.json +++ b/.versionrc.json @@ -14,7 +14,7 @@ }, { "type": "revert", - "section": "Refactor" + "section": "Reverts" }, { "type": "theme", diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 67fa4e3467eb..2ee83a066e91 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,13 +1,17 @@ { "recommendations": [ - "golang.go", - "hbenl.vscode-test-explorer", - "ethan-reesor.vscode-go-test-adapter", - "github.vscode-pull-request-github", - "esbenp.prettier-vscode", + "bmalehorn.vscode-fish", "davidanson.vscode-markdownlint", - "yzhang.markdown-all-in-one", - "bungcip.better-toml", - "redhat.vscode-yaml" + "elves.elvish", + "esbenp.prettier-vscode", + "github.vscode-pull-request-github", + "golang.go", + "jnoortheen.xonsh", + "ms-azuretools.vscode-azurefunctions", + "ms-vscode.powershell", + "redhat.vscode-yaml", + "sumneko.lua", + "tamasfe.even-better-toml", + "yzhang.markdown-all-in-one" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index cff40dd61010..810bf4eb4975 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,20 +2,43 @@ "version": "0.2.0", "configurations": [ { - "name": "Launch Package", + "name": "Primary", "type": "go", "request": "launch", "mode": "debug", "program": "${workspaceRoot}/src", - "args": ["--config=${workspaceRoot}/themes/jandedobbeleer.omp.json", "--shell=pwsh"] + "args": [ + "print", + "primary", + "--shell=pwsh", + "--terminal-width=200" + ] + }, + { + "name": "Tooltip", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "print", + "tooltip", + "--command=git", + "--shell=pwsh" + ] }, { - "name": "Launch Tooltip", + "name": "Transient", "type": "go", "request": "launch", "mode": "debug", "program": "${workspaceRoot}/src", - "args": ["--config=/Users/jan/.jandedobbeleer.omp.json", "--tooltip=git"] + "args": [ + "print", + "transient", + "--shell=pwsh", + "--status=1" + ] }, { "name": "Launch tests", @@ -23,52 +46,191 @@ "request": "launch", "mode": "test", "program": "${workspaceRoot}/src", - "args": ["--test.v"] + "args": [ + "--test.v" + ] }, { - "name": "Print debug", + "name": "Debug", "type": "go", "request": "launch", "mode": "debug", "program": "${workspaceRoot}/src", "args": [ - "--debug", - "--config=${workspaceRoot}/themes/jandedobbeleer.omp.json" + "debug" ] }, { - "name": "Print init pwsh", + "name": "Init", "type": "go", "request": "launch", "mode": "debug", "program": "${workspaceRoot}/src", "args": [ - "--print-init", - "--shell=pwsh", - "--config=${workspaceRoot}/themes/jandedobbeleer.omp.json" + "init", + "cmd", + "--print" + ] + }, + { + "name": "Export Config", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "config", + "export" + ] + }, + { + "name": "Export Image", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "config", + "export", + "image" + ] + }, + { + "name": "Migrate config", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "config", + "migrate" + ] + }, + { + "name": "Migrate glyphs", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "config", + "migrate", + "glyphs" + ] + }, + { + "name": "Get value", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "get", + "accent" + ] + }, + { + "name": "Toggle segment", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "toggle", + "git" + ] + }, + { + "name": "Notice", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "notice" + ] + }, + { + "name": "Upgrade", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "upgrade" + ] + }, + { + "name": "Font install", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "font", + "install", + "AnonymousPro" + ] + }, + { + "name": "Auth YTMDA", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "auth", + "ytmda" ] }, { - "name": "Export PNG", + "name": "DSC schema", "type": "go", "request": "launch", "mode": "debug", "program": "${workspaceRoot}/src", "args": [ - "--export-png", - "--shell=shell", - "--rprompt-offset=40", - "--cursor-padding=15", - "--config=${workspaceRoot}/themes/jandedobbeleer.omp.json" + "font", + "dsc", + "schema" ] }, { "type": "node", "request": "launch", "name": "Theme export", - "cwd": "${workspaceFolder}/docs", - "program": "${workspaceRoot}/docs/export_themes.js", + "cwd": "${workspaceFolder}/website", + "program": "${workspaceRoot}/website/export_themes.mjs", "console": "integratedTerminal" + }, + { + "type": "node", + "request": "launch", + "name": "Bluesky", + "cwd": "${workspaceFolder}/scripts/bluesky", + "program": "${workspaceRoot}/scripts/bluesky/main.cjs", + "console": "integratedTerminal", + "envFile": "${workspaceFolder}/scripts/bluesky/.env" + }, + { + "name": "Docs API", + "type": "node", + "request": "attach", + "port": 9229, + "preLaunchTask": "func: host start", + "cwd": "${workspaceFolder}/website", + "envFile": "${workspaceFolder}/website/.env" + }, + { + "name": "Cache clear", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}/src", + "args": [ + "cache", + "clear" + ] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 73e152604bdf..d7aa8528fdf5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,33 @@ { - "go.lintTool": "golangci-lint" + "go.lintTool": "golangci-lint", + "go.useLanguageServer": true, + "go.testOnSave": true, + "[go]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + }, + "go.formatTool": "gofmt", + "go.formatFlags": [ + "-s" + ], + "azureFunctions.deploySubpath": "docs/api", + "azureFunctions.postDeployTask": "npm install (functions)", + "azureFunctions.projectLanguage": "JavaScript", + "azureFunctions.projectRuntime": "~4", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.projectSubpath": "docs/api", + "azureFunctions.preDeployTask": "npm prune (functions)", + "[markdown]": { + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.codeActionsOnSave": { + "source.fixAll.markdownlint": "explicit" + } + }, + "files.encoding": "utf8", + "[powershell]": { + "files.encoding": "utf8" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000000..95c1f534b919 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,94 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "cwd": "${workspaceRoot}", + "echoCommand": true, + "type": "shell", + "tasks": [ + { + "type": "shell", + "command": "go", + "label": "build oh-my-posh", + "detail": "Build oh-my-posh in the /src folder locally", + "options": { + "cwd": "${workspaceRoot}/src" + }, + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$go", + "args": [ + "build", + "-v" + ] + }, + { + "type": "shell", + "command": "go", + "label": "devcontainer: rebuild oh-my-posh", + "detail": "Build oh-my-posh for all shells when inside the devcontainer", + "options": { + "cwd": "${workspaceRoot}/src", + "shell": { + "executable": "bash", + "args": [ + "-c" + ] + }, + "statusbar": { + "hide": false, + "color": "#22C1D6", + "label": "$(beaker) devcontainer: rebuild oh-my-posh", + "tooltip": "Compiles *oh-my-posh* from this repo while **overwriting** your preinstalled stable release." + } + }, + "group": "build", + "problemMatcher": "$go", + "args": [ + "build", + "-v", + "-o", + "/home/vscode/bin/oh-my-posh", + "-ldflags", + "\"-s -w -X 'github.com/jandedobbeleer/oh-my-posh/src/build.Version=development-$(git --no-pager log -1 --pretty=%h-%s)' -extldflags '-static'\"" + ] + }, + { + "type": "npm", + "script": "start", + "path": "website/", + "problemMatcher": [], + "label": "website: start", + "detail": "cross-env NODE_ENV=development docusaurus start" + }, + { + "type": "func", + "command": "host start", + "problemMatcher": "$func-node-watch", + "isBackground": true, + "dependsOn": "npm install (functions)", + "options": { + "cwd": "${workspaceFolder}/website/api" + } + }, + { + "type": "shell", + "label": "npm install (functions)", + "command": "npm install", + "options": { + "cwd": "${workspaceFolder}/website/api" + } + }, + { + "type": "shell", + "label": "npm prune (functions)", + "command": "npm prune --production", + "problemMatcher": [], + "options": { + "cwd": "${workspaceFolder}/website/api" + } + } + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..40e00464237c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,60 @@ +# GitHub Copilot Instructions + +For general coding guidelines, commit conventions, and agent workflows, see [AGENTS.md](../AGENTS.md). + +## Tech Stack + +| Layer | Technology | +|---|---| +| Core engine | Go (module root: `src/`) | +| Documentation site | Docusaurus (MDX) — `website/` | +| Themes | JSON — `themes/` | +| Config format | TOML / JSON / YAML | +| Package/installer scripts | `packages/` | +| Build scripts | `build/` | + +## Repository Layout + +``` +src/ # Go source — engine, runtime, segments, cache, color + segments/ # One Go file + one _test.go per segment + engine/ # Core rendering engine + runtime/ # OS/shell abstraction layer +themes/ # Bundled JSON theme files +website/ # Docusaurus docs site (MDX pages, sidebar config, JSON schema) +packages/ # Installer/package manifests +build/ # CI build helpers +``` + +## Segment Development + +When adding a new segment, four artifacts are required — use the `segment-create` skill to scaffold all of them automatically: + +1. `src/segments/.go` — segment implementation +2. `src/segments/_test.go` — unit tests +3. `website/docs/segments/.mdx` — user-facing docs +4. Update `website/sidebars.js` and `website/static/schema.json` + +See the `segment-docs` skill for the canonical mapping between Go source constructs and MDX documentation fields (template properties, type representations, option tables). + +## Go Conventions + +- Follow the `golang` skill for project-specific Go standards. +- Each segment implements the `Segment` interface; use `env` (the `Environment` abstraction) for all OS/shell calls — never call OS APIs directly. +- Test with `go test ./...` from `src/`. +- Lint with `golangci-lint run` from `src/`. + +## Documentation (website/) + +- Follow the `markdown` skill for `.md`/`.mdx` formatting rules. +- Segment doc pages live in `website/docs/segments/` and use MDX frontmatter with `title`, `sidebar_label`, and `id`. +- Run `npm run start` inside `website/` for a local dev server. +- Run `npm run build` inside `website/` to verify the site builds before opening a docs PR. + +## PowerShell + +PowerShell helper scripts live in `packages/` and `build/`. Follow the `powershell` skill for cmdlet conventions. + +## Themes + +Themes are plain JSON files in `themes/`. New themes must validate against `website/static/schema.json`. Do not introduce breaking schema changes without updating the schema file. \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..eb2305540a01 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,130 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement by reaching out +via [email](mailto:abuse@ohmyposh.dev). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available [in the documentation][version-2]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder][moz-div]. + +For answers to common questions about this code of conduct, see the [FAQ][faq]. +Translations are available [in the documentation][translations]. + +[homepage]: https://www.contributor-covenant.org +[moz-div]: https://github.com/mozilla/diversity +[version-2]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 910f5df6ac60..a3c409af032d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,143 +2,119 @@ Note we have a code of conduct, please follow it in all your interactions with the project. +> [!NOTE] +> Theme additions are no longer accepted due to the ever growing set. +> We do however accept showcasing your custom theme in the [themes discussion section here][themes-discussion] +> or the [themes channel on Discord][discord-link]. + Ensure you've read through the [documentation][docs] so you understand the core concepts of the project. If you're looking to get familiar with go, following the getting started [guide][guide] can be a good starting point. -## Pull Request Process - -1. Ensure any dependencies or build artifacts are removed/ignored before creating a commit. -2. Commits follow the [conventional commits][cc] guidelines. -3. Update the documentation with details of changes to the functionality, this includes new segments - or core functionality. -4. Pull Requests are merged once all checks pass and a project maintainer has approved it. - -## Code of Conduct +## Setting Up Agents and Skills -### Our Pledge +This project uses [APM (Agent Package Manager)][apm] to manage shared AI agent skills. +Project-specific skills live in `.github/skills/`, while shared skills are declared +in `apm.yml` and installed via APM. -We as maintainers and contributors pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. +### Install APM -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. +```bash +curl -sSL https://raw.githubusercontent.com/microsoft/apm/main/install.sh | sh +``` -### Our Standards +Alternatively, install via Homebrew or pip: -Examples of behavior that contributes to a positive environment for the -project include: +```bash +brew install microsoft/apm/apm +# or +pip install apm-cli +``` -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - project +### Install Skills -Examples of unacceptable behavior include: +After cloning the repository, run: -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +```bash +apm install +``` -### Enforcement Responsibilities +This pulls in the shared skills from [JanDeDobbeleer/agentic][agentic] (conventional commits, +Go, Markdown, and PowerShell conventions). The project-specific skills (segment-create and +segment-docs) are already included in the repository. -Project maintainers are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Project maintainers have the right and responsibility to remove, edit, or reject -comments, commits, code, documentation edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -### Scope +## Pull Request Process -This Code of Conduct applies within all project spaces, and also applies when -an individual is officially representing the project in public spaces. -Examples of representing the project include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. +1. Ensure any dependencies or build artifacts are removed/ignored before creating a commit. +2. Commits follow the [conventional commits][cc] guidelines. +(You can [look up the supported *types*][cc-types] along with an explanation [in the documentation][cc-types]) +3. Update the documentation with details of changes to the functionality, this includes new segments + or core functionality. +4. Pull Requests are merged once all checks pass and a project maintainer has approved it. -### Enforcement +## Codespaces / Devcontainer Development Environment -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the project maintainers responsible for enforcement via -[email][conduct]. -All complaints will be reviewed and investigated promptly and fairly. +Arguably the easiest way to contribute anything is to use our prepared development environment. -All project maintainers are obligated to respect the privacy and security of the -reporter of any incident. +We have a `.devcontainer/devcontainer.json` file, meaning we are compatible with: -### Enforcement Guidelines +- [![Open in GitHub Codespaces][codespaces-badge]][codespaces-link], or +- the [Visual Studio Code Remote - Containers][devcontainer-ext] extension. -Project maintainers will follow these Project Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: +This Linux environment includes all shells supported by oh-my-posh, including Bash, ZSH, +Fish and PowerShell, the latter of which is the default. -#### 1. Correction +### Configuring Devcontainer's Timezone & Theme -**Project Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the project. +1. Open the [`.devcontainer/devcontainer.json`][devcontainer] file and in the "*build*" section modify: -**Consequence**: A private, written warning from project maintainers, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. + - `TZ`: with [your own timezone][timezones] -#### 2. Warning +2. Summon the Command Panel (Ctrl+Shift+P) and select `Codespaces: Rebuild Container` + to rebuild your devcontainer. (This should take just a few seconds.) -**Project Impact**: A violation through a single incident or series -of actions. +### Recompiling oh-my-posh -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in project spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. +The devcontainer definition preinstalls the latest stable oh-my-posh release at build time. -#### 3. Temporary Ban +To overwrite the installation's version inside the running devcontainer, you may use the +VSCode *task* `devcontainer: build omp` to rebuild your oh-my-posh with that of +your running repository's state. (You might see a button for this in your statusbar.) -**Project Impact**: A serious violation of project standards, including -sustained inappropriate behavior. +If the compile succeeds, `oh-my-posh --version` should reply: +`development` -**Consequence**: A temporary ban from any sort of interaction or public -communication with the project for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. +Should you somehow mess up your devcontainer's OMP install catastrophically, remember that +if you do `Codespaces: Rebuild Container` again, you'll be back to the latest stable release. -#### 4. Permanent Ban +## Local development -**Project Impact**: Demonstrating a pattern of violation of project -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. +Make sure your local go version matches with the pinned version in [go.mod]. You can build +oh-my-posh by navigating the to the `/src` folder and executing the following command. -**Consequence**: A permanent ban from any sort of public interaction within -the project. +```bash +go build -v -o /path/to/oh-my-posh(.exe) +``` -### Attribution +### Running tests -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available [here][coc]. +To execute the tests, run the following command from the `/src` folder. -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). +```bash +go test "./..." +``` -[docs]: https://ohmyposh.dev/docs -[guide]: https://ohmyposh.dev/docs/contributing_started -[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary -[homepage]: https://www.contributor-covenant.org -[conduct]: mailto:conduct@ohmyposh.dev -[coc]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[themes-discussion]: [https://github.com/JanDeDobbeleer/oh-my-posh/discussions/categories/themes] +[discord-link]: [https://discord.com/channels/1023597603331526656/1055533233309233252] +[docs]: +[guide]: +[cc]: +[cc-types]: +[codespaces-badge]: +[codespaces-link]: +[devcontainer-ext]: +[timezones]: +[devcontainer]: .devcontainer/devcontainer.json +[go.mod]: src/go.mod +[apm]: https://github.com/microsoft/apm +[agentic]: https://github.com/JanDeDobbeleer/agentic diff --git a/COPYING b/COPYING index f288702d2fa1..0052a411de0b 100644 --- a/COPYING +++ b/COPYING @@ -1,674 +1,13 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +Copyright 2022 Jan De Dobbeleer - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - Preamble +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 180465ab969d..203e95f19679 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,92 @@ -# A prompt theme engine for any shell + +

+ Oh My Posh logo – Prompt theme engine for any shell +

+ -![Release Status][release-status] -[![Go Report Card][report-card]][report-card-link] +![MIT license badge](https://img.shields.io/github/license/JanDeDobbeleer/oh-my-posh.svg) -[![Release][release-badge]][release] -[![Documentation][docs-badge]][docs] +![Build Status badge](https://img.shields.io/github/actions/workflow/status/jandedobbeleer/oh-my-posh/release.yml?branch=main) + +[![Release version number badge][release-badge]][release] + +[![Documentation link badge ohmyposh.dev][docs-badge]][docs] + +![Number of GitHub Downloads badge](https://img.shields.io/github/downloads/jandedobbeleer/oh-my-posh/total?color=pink&label=GitHub%20Downloads) + +This repo was made with love using GitKraken. + +[![GitKraken shield][kraken]][kraken-ref] + + +## Sponsors + +[![Documentation link badge ohmyposh.dev][merge-conflict-logo]][merge-conflict] -[![PS Gallery][psgallery-badge]][powershell-gallery] -![GitHub Downloads][gh-downloads] +[Want to become a sponsor?][sponsor-link] -What started as the offspring of [oh-my-posh2][oh-my-posh2] for PowerShell resulted in a cross platform, -highly customizable and extensible prompt theme engine. After 4 years of working on oh-my-posh, -a modern and more efficient tool was needed to suit my personal needs. +## Join the community -## ❤ Support ❤ +![Mastodon badge](https://img.shields.io/mastodon/follow/110275292073181892?domain=https%3A%2F%2Fhachyderm.io&label=Mastodon&style=social) -[![Patreon][patreon-badge]][patreon] -[![Liberapay][liberapay-badge]][liberapay] -[![Ko-Fi][kofi-badge]][kofi] +![Discord badge](https://img.shields.io/discord/1023597603331526656) + +What started as the offspring of [oh-my-posh2](https://github.com/JanDeDobbeleer/oh-my-posh2) for PowerShell +resulted in a cross platform, highly customizable and extensible prompt theme engine. After 4 years of working +on oh-my-posh, a modern and more efficient tool was needed to suit my personal needs. + +## :heart: Support :heart: + +[![Swag][swag-badge]][swag] - Show your love with a t-shirt! + +[![GitHub][github-badge]][github-sponsors] - One time support, or a recurring donation? + +[![Ko-Fi][kofi-badge]][kofi] - No coffee, no code. ## Features -* Shell independent -* Git status indications -* Failed command indication -* Admin indication -* Current session indications -* Language info -* Shell info -* Configurable +* Shell and platform agnostic +* Easily configurable +* The __most__ configurable prompt utility +* Fast +* Secondary prompt +* Right prompt +* Transient prompt ## Documentation [![Documentation][docs-badge]][docs] -## Roadmap +## Reviews -* [x] CI -* [x] Github Releases -* [x] Create documentation for manual installation -* [x] Create documentation on the different segments -* Create easy installation packages - * [x] Powershell - * [x] Brew - * [x] Scoop - * [ ] Winget -* [x] Swap V2 with V3 +* [Repo review](https://repo-reviews.github.io//reviews/2023-06-21_TameWizard_JanDeDobbeleer_oh-my-posh) by [TameWizard](https://github.com/TameWizard) ## Thanks -* [Chris Benti][chrisbenti-psconfig] for providing the first influence to start oh-my-posh -* [Keith Dahlby][keithdahlby-poshgit] for creating posh-git and making life more enjoyable -* [Robby Russel][oh-my-zsh] for creating oh-my-zsh, without him this would probably not be here -* [Janne Mareike Koschinski][justjanne] for providing information on how to get certain information -using Go (and the amazing [README][powerline-go]) -* [Starship][starship] for creating an amazing way to initialize the prompt - -[release-status]: https://img.shields.io/github/workflow/status/jandedobbeleer/oh-my-posh/Release?label=Build -[psgallery-badge]: https://img.shields.io/powershellgallery/dt/oh-my-posh?color=pink&label=PowerShell%20Downloads -[powershell-gallery]: https://www.powershellgallery.com/packages/oh-my-posh/ -[gh-downloads]: https://img.shields.io/github/downloads/jandedobbeleer/oh-my-posh/total?color=pink&label=GitHub%20Downloads -[report-card]: https://goreportcard.com/badge/github.com/jandedobbeleer/oh-my-posh -[report-card-link]: https://goreportcard.com/report/github.com/jandedobbeleer/oh-my-posh -[oh-my-posh2]: https://github.com/JanDeDobbeleer/oh-my-posh2 -[patreon-badge]: https://img.shields.io/badge/Support-Become%20a%20Patreon!-red.svg -[patreon]: https://www.patreon.com/jandedobbeleer -[liberapay-badge]: https://img.shields.io/badge/Liberapay-Donate-%23f6c915.svg -[liberapay]: https://liberapay.com/jandedobbeleer +* [Chris Benti](https://github.com/chrisbenti/PS-Config) providing the first influence to start oh-my-posh +* [Keith Dahlby](https://github.com/dahlbyk/posh-git) for creating posh-git and making life more enjoyable +* [Robby Russell](https://github.com/ohmyzsh/ohmyzsh) for creating oh-my-zsh, without him this would probably not be here +* [Janne Mareike Koschinski](https://github.com/justjanne) for providing information on how to get certain information +using Go (and the amazing [README](https://github.com/justjanne/powerline-go)) +* [Starship](https://github.com/starship/starship/blob/master/src/init/mod.rs) for doing great things + +[kraken]: https://img.shields.io/badge/GitKraken-Legendary%20Git%20Tools-teal?style=plastic&logo=gitkraken +[kraken-ref]: https://www.gitkraken.com/invite/nQmDPR9D +[swag-badge]: https://img.shields.io/badge/Swag-Get%20some!-blue +[swag]: https://swag.ohmyposh.dev +[github-badge]: https://img.shields.io/badge/-Sponsor-fafbfc?logo=GitHub%20Sponsors +[github-sponsors]: https://github.com/sponsors/JanDeDobbeleer [kofi-badge]: https://img.shields.io/badge/Ko--fi-Buy%20me%20a%20coffee!-%2346b798.svg [kofi]: https://ko-fi.com/jandedobbeleer [docs-badge]: https://img.shields.io/badge/Docs-ohmyposh.dev-blue -[docs]: https://ohmyposh.dev/docs +[docs]: https://ohmyposh.dev [release-badge]: https://img.shields.io/github/v/release/jandedobbeleer/oh-my-posh?label=Release [release]: https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest -[chrisbenti-psconfig]: https://github.com/chrisbenti/PS-Config -[keithdahlby-poshgit]: https://github.com/dahlbyk/posh-git -[oh-my-zsh]: https://github.com/robbyrussell/oh-my-zsh -[justjanne]: https://github.com/justjanne -[powerline-go]: https://github.com/justjanne/powerline-go -[starship]: https://github.com/starship/starship/blob/master/src/init/mod.rs + +[merge-conflict]: https://www.mergeconflict.fm/ +[merge-conflict-logo]: https://media24.fireside.fm/file/fireside-images-2024/podcasts/images/0/02d84890-e58d-43eb-ab4c-26bcc8524289/cover_small.jpg?v=1 +[sponsor-link]: https://buy.polar.sh/polar_cl_qnmZxboq1IDUJo03mk2Jue6ktqZrCXElnzH2s2xbV2R diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000000..f2830c4c6c75 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +Only the latest [release][releases] is supported. + +## Reporting a Vulnerability + +Vulnerabilities can be sent in via [email][email] to avoid publishing in the open. +Oh My Posh does not have a bounty program, neither do we respond to bug bounties. + +For valid security concerns, you can expect a response within 48 hours, +and credit is given once an acceptable fix is found and published. + +[releases]: https://github.com/JanDeDobbeleer/oh-my-posh/releases +[email]: mailto:security@ohmyposh.dev diff --git a/apm.lock.yaml b/apm.lock.yaml new file mode 100644 index 000000000000..bf878e4e0a2d --- /dev/null +++ b/apm.lock.yaml @@ -0,0 +1,67 @@ +lockfile_version: '1' +generated_at: '2026-05-07T10:54:22.196155+00:00' +apm_version: 0.12.3 +dependencies: +- repo_url: JanDeDobbeleer/agentic + host: github.com + resolved_commit: b4e8062eaeef93582bf4cbb86ad4816f37630bb2 + virtual_path: skills/conventional-commit + is_virtual: true + package_type: claude_skill + deployed_files: + - .agents/skills/conventional-commit + content_hash: sha256:d842107e497b6fd51f24e2bfe74dbaf3f9e0e358bf5f95b9d79abbec5785e7af +- repo_url: JanDeDobbeleer/agentic + host: github.com + resolved_commit: b4e8062eaeef93582bf4cbb86ad4816f37630bb2 + virtual_path: skills/golang + is_virtual: true + package_type: claude_skill + deployed_files: + - .agents/skills/golang + content_hash: sha256:b0783cb13028c1b486aaffe7bce1b49294e92b22e1f8ce58895e61c639865f90 +- repo_url: JanDeDobbeleer/agentic + host: github.com + resolved_commit: b4e8062eaeef93582bf4cbb86ad4816f37630bb2 + virtual_path: skills/markdown + is_virtual: true + package_type: claude_skill + deployed_files: + - .agents/skills/markdown + content_hash: sha256:fa25f4f36faa33fa51ec6e9a3b5bf7cde618d8d7dfb572dd8f55733dc1813265 +- repo_url: JanDeDobbeleer/agentic + host: github.com + resolved_commit: b4e8062eaeef93582bf4cbb86ad4816f37630bb2 + virtual_path: skills/powershell + is_virtual: true + package_type: claude_skill + deployed_files: + - .agents/skills/powershell + content_hash: sha256:bc844acc1cf9ccc5d2d10e1c889b8b5b69b92f743e3bc11e0bc9a386770fc864 +- repo_url: JanDeDobbeleer/agentic + host: github.com + resolved_commit: b4e8062eaeef93582bf4cbb86ad4816f37630bb2 + virtual_path: skills/vale-user-facing-text + is_virtual: true + package_type: claude_skill + deployed_files: + - .agents/skills/vale-user-facing-text + content_hash: sha256:7676bf19dc93149f469655ffb0b166db7c953fe0483b8a96e763ce024b1c8a59 +- repo_url: ast-grep/agent-skill + host: github.com + resolved_commit: 577f4d4507678f2c8cee150fae25e6ce309f70b1 + virtual_path: ast-grep/skills/ast-grep + is_virtual: true + package_type: claude_skill + deployed_files: + - .agents/skills/ast-grep + content_hash: sha256:e812630c21ec2215bd0abfe33835ae382059112e990411b588b84bb24c4f0a4f +- repo_url: github/awesome-copilot + host: github.com + resolved_commit: bb91d3606099fc047798a8b8c28d60989a531a01 + virtual_path: skills/gh-cli + is_virtual: true + package_type: claude_skill + deployed_files: + - .agents/skills/gh-cli + content_hash: sha256:cbe644b0c6760ae2a1eaafa39133920d889331327065d92a131675a665273e56 diff --git a/apm.yml b/apm.yml new file mode 100644 index 000000000000..d2babb049c87 --- /dev/null +++ b/apm.yml @@ -0,0 +1,16 @@ +name: oh-my-posh +version: 1.0.0 +description: A prompt theme engine for any shell. + +dependencies: + apm: + - JanDeDobbeleer/agentic/skills/conventional-commit + - JanDeDobbeleer/agentic/instructions/golang.instructions.md + - JanDeDobbeleer/agentic/instructions/markdown.instructions.md + - JanDeDobbeleer/agentic/instructions/powershell.instructions.md + - JanDeDobbeleer/agentic/skills/golang + - JanDeDobbeleer/agentic/skills/markdown + - JanDeDobbeleer/agentic/skills/powershell + - JanDeDobbeleer/agentic/skills/vale-user-facing-text + - ast-grep/agent-skill/ast-grep/skills/ast-grep + - github/awesome-copilot/skills/gh-cli diff --git a/build/post.ps1 b/build/post.ps1 new file mode 100644 index 000000000000..226ee7cbd808 --- /dev/null +++ b/build/post.ps1 @@ -0,0 +1,16 @@ +# Description: Post build script to compress the themes and generate SHA256 hashes for all files in the dist folder + +# Compress all themes +$compress = @{ + Path = "../themes/*.omp.*" + CompressionLevel = "Fastest" + DestinationPath = "../src/dist/themes.zip" +} +Compress-Archive @compress + +# Generate SHA256 hashes for all files in the dist folder +Get-ChildItem ./dist -Exclude *.yaml, *.sig | Get-Unique | +Foreach-Object { + $zipHash = Get-FileHash $_.FullName -Algorithm SHA256 + $zipHash.Hash | Out-File -Encoding 'UTF8' "../src/dist/$($_.Name).sha256" +} diff --git a/build/pre.ps1 b/build/pre.ps1 new file mode 100644 index 000000000000..3942c8aa7cee --- /dev/null +++ b/build/pre.ps1 @@ -0,0 +1,30 @@ +Param +( + [string] + $Version, + [parameter(Mandatory = $false)] + [string] + $SDKVersion = "10.0.26100.0" +) + +git config --global user.name "GitHub Actions" +git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" +git tag $Version --force + +$PSDefaultParameterValues['Out-File:Encoding'] = 'UTF8' + +$shaSigningKeyLocation = Join-Path -Path $env:RUNNER_TEMP -ChildPath sha_signing_key.pem +$env:SIGNING_KEY > $shaSigningKeyLocation +Write-Output "SHA_SIGNING_KEY_LOCATION=$shaSigningKeyLocation" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + +# install code signing dlib +nuget.exe install Microsoft.Trusted.Signing.Client -Version 1.0.92 -ExcludeVersion -OutputDirectory $env:RUNNER_TEMP +Write-Output "SIGNTOOLDLIB=$env:RUNNER_TEMP/Microsoft.Trusted.Signing.Client/bin/x64/Azure.CodeSigning.Dlib.dll" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + +# requires Windows Dev Kit 10.0.26100.0 +$signtool = "C:/Program Files (x86)/Windows Kits/10/bin/$SDKVersion/x64/signtool.exe" +Write-Output "SIGNTOOL=$signtool" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + +# openssl +$openssl = 'C:/Program Files/Git/usr/bin/openssl.exe' +Write-Output "OPENSSL=$openssl" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append diff --git a/docs/docs/beta.mdx b/docs/docs/beta.mdx deleted file mode 100644 index 2d2eb1bd282f..000000000000 --- a/docs/docs/beta.mdx +++ /dev/null @@ -1,89 +0,0 @@ ---- -id: beta -title: Beta Features -sidebar_label: 🍼 Beta Features ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -## Tooltips - -Tooltips are segments that are rendered as a right aligned prompt while you're typing certain keywords. -They behave similar to the other segments when it comes to how and when they are shown so you can tweak -them to act and look like you want. The key difference is that they can be invoked using `tips` which are the -commands you are typing. Due to the possibility of the use of an alias, you can define for which keyword -the segment should be rendered. - -Due to limitations (or not having found a way just yet) this feature only work for `zsh` and `powershell` at -the time of writing. - -### Configuration - -You need to extend or create a custom theme with your tooltips. For example: - -```json -{ - "blocks": [ - ... - ], - "tooltips": [ - { - "type": "git", - "tips": ["git", "g"], - "style": "diamond", - "foreground": "#193549", - "background": "#fffb38", - "leading_diamond": "", - "trailing_diamond": "", - "properties": { - "display_status": true, - "display_upstream_icon": true, - "status_colors_enabled": true, - "local_changes_color": "#ff9248", - "ahead_and_behind_color": "#f26d50", - "behind_color": "#f17c37", - "ahead_color": "#89d1dc" - } - } - ] -} -``` - -This configuration will render a right aligned git segment when you type `git` or `g` followed by a space. Keep in mind that -this is a blocking call, meaning that if the segment renders slow, you can't type until it's visible. Optimizations in this space -are being explored. - -### Enable the feature - - - - -Import/invoke Oh My Posh in your `$PROFILE` and add the following setting: - -```pwsh -$Global:PoshSettings.EnableToolTips = $true -``` - -Restart your shell or reload your `$PROFILE` using `. $PROFILE` for the changes to take effect. - - - - -Invoke Oh My Posh in `.zshrc` and add the following setting: - -```bash -enable_poshtooltips -``` - -Restart your shell or reload `.zshrc` using `source ~/.zshrc` for the changes to take effect. - - - diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md deleted file mode 100644 index 43b809c01e7c..000000000000 --- a/docs/docs/configuration.md +++ /dev/null @@ -1,481 +0,0 @@ ---- -id: configure -title: Configuration -sidebar_label: ⚙️ Configuration ---- - -Oh My Posh renders your prompt based on the definition of _blocks_ (like Lego) which contain one or more _segments_. -A really simple configuration could look like this. - -```json -{ - "final_space": true, - "blocks": [ - { - "type": "prompt", - "alignment": "left", - "segments": [ - { - "type": "path", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#61AFEF", - "properties": { - "prefix": " \uE5FF ", - "style": "folder" - } - } - ] - } - ] -} -``` - -With this configuration, a single powerline segment is rendered that shows the name of the folder you're currently in. -To set this configuration in combination with a Oh My Posh [executable][releases], use the `--config` flag to -set a path to a json file containing the above code. The `--shell universal` flag is used to print the prompt without -escape characters to see the prompt as it would be shown inside a prompt function for your shell. - -:::info -The command below will not persist the configuration for your shell but print the prompt in your terminal. -If you want to use your own configuration permanently, adjust the prompt configuration to use your custom -theme. -::: - -```bash -oh-my-posh --config sample.json --shell universal -``` - -If all goes according to plan, you should see the prompt being printed out on the line below. In case you see a lot of -boxes with question marks, set up your terminal to use a supported font before continuing. - -## General Settings - -- final_space: `boolean` - when true adds a space at the end of the prompt -- osc99: `boolean` - when true adds support for OSC9;9; (notify terminal of current working directory) -- console_title: `boolean` - when true sets the current location as the console title -- console_title_style: `string` - the title to set in the console - defaults to `folder` -- console_title_template: `string` - the template to use when `"console_title_style" = "template"` -- terminal_background: `string` [color][colors] - terminal background color, set to your terminal's background color when -you notice black elements in Windows Terminal or the Visual Studio Code integrated terminal - -> "I Like The Way You Speak Words" - Gary Goodspeed - -### Console Title Style - -- `folder`: show the current folder name -- `path`: show the current path -- `template`: show a custom template - -### Console Title Template - -You can create a more custom console title with the use of `"console_title_style" = "template"`. -When this is set, a `console_title_template` is also expected, otherwise the title will remain empty. -Under the hood this uses go's [text/template][go-text-template] feature extended with [sprig][sprig] and -offers a few standard properties to work with. - -- `.Root`: `boolean` - is the current user root/admin or not -- `.Path`: `string` - the current working directory -- `.Folder`: `string` - the current working folder -- `.Shell`: `string` - the current shell name -- `.User`: `string` - the current user name -- `.Host`: `string` - the host name -- `.Env.VarName`: `string` - Any environment variable where `VarName` is the environment variable name - -A `boolean` can be used for conditional display purposes, a `string` can be displayed. - -The following examples illustrate possible contents for `console_title_template`, provided -the current working directory is `/usr/home/omp` and the shell is `zsh`. - -```json -{ - "console_title_template": "{{.Folder}}{{if .Root}} :: root{{end}} :: {{.Shell}}", - // outputs: - // when root == false: omp :: zsh - // when root == true: omp :: root :: zsh - "console_title_template": "{{.Folder}}", // outputs: omp - "console_title_template": "{{.Shell}} in {{.Path}}", // outputs: zsh in /usr/home/omp - "console_title_template": "{{.User}}@{{.Host}} {{.Shell}} in {{.Path}}", // outputs: MyUser@MyMachine zsh in /usr/home/omp - "console_title_template": "{{.Env.USERDOMAIN}} {{.Shell}} in {{.Path}}", // outputs: MyCompany zsh in /usr/home/omp -} -``` - -## Block - -Let's take a closer look at what defines a block. - -- type: `prompt` | `rprompt` -- newline: `boolean` -- alignment: `left` | `right` -- vertical_offset: `int` -- horizontal_offset: `int` -- segments: `array` of one or more `segments` - -### Type - -Tells the engine what to do with the block. There are three options: - -- `prompt` renders one or more segments -- `rprompt` renders one or more segments aligned to the right of the cursor. Only one `rprompt` block is permitted. -Supported on [ZSH][rprompt], Bash and Powershell. - -### Newline - -Start the block on a new line. Defaults to `false`. - -### Alignment - -Tell the engine if the block should be left or right aligned. - -### Vertical offset - -Move the block up or down x lines. For example `vertical_offset: 1` moves the prompt down one line, `vertical_offset: -1` -moves it up one line. - -### Horizontal offset - -Moves the segment to the left or the right to have it exactly where you want it to be. Works like `vertical_offset` -but on a horizontal level where a negative number moves the block left and a positive number right. - -### Segments - -Array of one or more segments. - -## Segment - -A segments is a part of the prompt with a certain context. There are different types available out of the box, if you're -looking for what's included, feel free to skip this part and browse through the [segments][segments]. Keep reading to -understand how to configure a segment. - -- type: `string` any of the included [segments][segments] -- style: `powerline` | `plain` | `diamond` -- powerline_symbol: `string` -- invert_powerline: `boolean` -- leading_diamond: `string` -- trailing_diamond: `string` -- foreground: `string` [color][colors] -- foreground_templates: `array` of `string` values -- background: `string` [color][colors] -- background_templates: `array` of `string` values -- properties: `array` of `Property`: `string` - -### Type - -Takes the `string` value referencing which segment logic it needs to run (see [segments][segments] for possible values). - -### Style - -Oh Hi! You made it to a really interesting part, great! Style defines how a prompt is rendered. Looking at most prompt -themes out there, we identified 3 types. All of these require a different configuration and depending on the look -you want to achieve you might need to understand/use them all. - -#### Powerline - -What started it all for us. Makes use of a single symbol (`powerline_symbol`) to separate the segments. It takes the -background color of the previous segment (or transparent if none) and the foreground of the current one (or transparent -if we're at the last segment). Expects segments to have a colored background, else there little use for this one. - -#### Plain - -Simple. Colored text on a transparent background. Make sure to set `foreground` for maximum enjoyment. - -#### Diamond - -While Powerline works great with as single symbol, sometimes you want a segment to have a different start and end symbol. -Just like a diamond: `< my segment text >`. The difference between this and plain is that the diamond symbols take the -segment background as their foreground color. - -### Powerline symbol - -Text character to use when `"style": "powerline"`. - -### Invert Powerline - -If `true` this swaps the foreground and background colors. Can be useful when the character you want does not exist -in the perfectly mirrored variant for example. - -### Leading diamond - -Text character to use at the start of the segment. Will take the background color of the segment as -its foreground color. - -### Trailing diamond - -Text character to use at the end of the segment. Will take the background color of the segment as its foreground color. - -### Foreground - -[Color][colors] to use as the segment text foreground color. Also supports transparency using the `transparent` keyword. - -### Foreground Templates - -Array if string templates to define the foreground color for the given Segment based on the Segment's Template Properties. -Under the hood this uses go's [text/template][go-text-template] feature extended with [sprig][sprig] and -offers a few standard properties to work with. For supported Segments, look for the **Template Properties** section in -the documentation. - -The following sample is based on the [AWS Segment][aws]. - -```json -{ - "type": "aws", - "style": "powerline", - "powerline_symbol": "", - "foreground": "#ffffff", - "background": "#111111", - "foreground_templates": [ - "{{if contains \"default\" .Profile}}#FFA400{{end}}", - "{{if contains \"jan\" .Profile}}#f1184c{{end}}" - ], - "properties": { - "prefix": " \uE7AD " - } -} -``` - -The logic is as follows, when `background_templates` contains an array, we will check every template line until there's -one that returns a non-empty string. So, when the contents of `.Profile` contain the word `default`, the first template -returns `#FFA400` and that's the color that will be used. If it contains `jan`, it returns `#f1184c`. When none of the -templates return a value, the foreground value `#ffffff` is used. - -### Background - -[Color][colors] to use as the segment text background color. Also supports transparency using the `transparent` keyword. - -### Background Templates - -Same as [Foreground Templates][fg-templ] but for the background color. - -### Properties - -An array of **Properties** with a value. This is used inside of the segment logic to tweak what the output of the segment -will be. Segments have the ability to define their own Properties, but there are some general ones being used by the -engine which allow you to customize the output even more. - -#### General purpose properties - -You can use these on any segment, the engine is responsible for adding them correctly. - -- prefix: `string` -- postfix: `string` -- include_folders: `[]string` -- exclude_folders: `[]string` - -##### Prefix - -The string content will be put in front of the segment's output text. Useful for symbols, text or other customizations. - -##### Postfix - -The string content will be put after the segment's output text. Useful for symbols, text or other customizations. - -##### Include / Exclude Folders - -Sometimes you might want to have a segment only rendered in certain folders. If `include_folders` is specified, -the segment will only be rendered when in one of those locations. If `exclude_folders` is specified, the segment -will not be rendered when in one of the excluded locations. - -```json -"include_folders": [ - "/Users/posh/Projects" -] -``` - -```json -"exclude_folders": [ - "/Users/posh/Projects" -] -``` - -You can also specify a [regular expression][regex] to create folder wildcards. -In the sample below, any folders inside the `/Users/posh/Projects` path will be matched. - -```json -"include_folders": [ - "/Users/posh/Projects.*" -] -``` - -You can also combine these properties: - -```json -"include_folders": [ - "/Users/posh/Projects.*" -], -"exclude_folders": [ - "/Users/posh/Projects/secret-project.*" -] -``` - -Note for Windows users: Windows directory separators should be specified as 4 backslashes. - -```json -"include_folders": [ - "C:\\\\Projects.*" -], -"exclude_folders": [ - "C:\\\\Projects\\\\secret-project.*" -] -``` - -#### Colors - -You have the ability to override the foreground and/or background color for text in any property that accepts it. -The syntax is custom but should be rather straighforward: -`<#ffffff,#000000>this is white with black background <#FF479C>but this is pink`. Anything between the color start -`<#FF479C>` and end `` will be colored accordingly. - -For example, if you want `prefix` to print a colored bracket which isn't the same as the segment's `foreground`, you can -do so like this: - -```json -"prefix": "<#CB4B16>┏[", -``` - -If you also wanted to change the background color in the previous command, you would do so like this: - -```json -"prefix": "<#CB4B16,#FFFFFF>┏[", -``` - -To change *only* the background color, just omit the first color from the above string: - -```json -"prefix": "<,#FFFFFF>┏[", -``` - -Oh My Posh mainly supports three different color types being - -- Typical [hex colors][hexcolors] (for example `#CB4B16`). -- The `transparent` keyword which can be used to create either a transparent foreground override - or transparent background color using the segment's foreground property. -- 16 [ANSI color names][ansicolors]. - - These include 8 basic ANSI colors and `default`: - - `black` `red` `green` `yellow` `blue` `magenta` `cyan` `white` `default` - - as well as 8 extended ANSI colors: - - `darkGray` `lightRed` `lightGreen` `lightYellow` `lightBlue` `lightMagenta` `lightCyan` `lightWhite` - -### Text decorations - -You can make use of the following syntax to decorate text: - -- `bold`: renders `bold` as bold text -- `underline`: renders `underline` as underlined text -- `italic`: renders `italic` as italic text -- `strikethrough`: renders `strikethrough` as strikethrough text - -This can be used in templates and icons/text inside your config. - -### Hyperlinks - -The engine has the ability to render hyperlinks. Your terminal has to support it and the option -has to be enabled at the segment level. Hyperlink generation is disabled by default. - -#### Supported segments - -- [Path][path-segment] - -#### Supported terminals - -- [Terminal list][terminal-list-hyperlinks] - -## Full Sample - -```json -{ - "final_space": true, - "blocks": [ - { - "type": "prompt", - "alignment": "right", - "vertical_offset": -1, - "segments": [ - { - "type": "time", - "style": "plain", - "foreground": "#007ACC", - "properties": { - "time_format": "15:04:05" - } - } - ] - }, - { - "type": "prompt", - "alignment": "left", - "newline": true, - "segments": [ - { - "type": "session", - "style": "diamond", - "foreground": "#ffffff", - "background": "#ffb300", - "leading_diamond": "\uE0B6", - "trailing_diamond": "\uE0B0", - "properties": { - "postfix": " " - } - }, - { - "type": "path", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#61AFEF", - "properties": { - "prefix": " \uE5FF ", - "style": "folder", - "exclude_folders": [ - "/super/secret/project" - ], - "enable_hyperlink": false - } - }, - { - "type": "git", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#193549", - "background": "#ffeb3b" - }, - { - "type": "exit", - "style": "diamond", - "foreground": "#ffffff", - "background": "#00897b", - "leading_diamond": "", - "trailing_diamond": "\uE0B4", - "properties": { - "display_exit_code": false, - "always_enabled": true, - "error_color": "#e91e63", - "color_background": true, - "prefix": "<#193549>\uE0B0 \uE23A" - } - } - ] - } - ] -} -``` - -[releases]: https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest -[nf]: https://www.nerdfonts.com/ -[segments]: /docs/battery -[colors]: #colors -[hexcolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/ -[ansicolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/ -[fg]: /docs/configure#foreground -[regex]: https://www.regular-expressions.info/tutorial.html -[rprompt]: https://scriptingosx.com/2019/07/moving-to-zsh-06-customizing-the-zsh-prompt/ -[path-segment]: /docs/path -[terminal-list-hyperlinks]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda -[go-text-template]: https://golang.org/pkg/text/template/ -[sprig]: https://masterminds.github.io/sprig/ -[aws]: /docs/aws -[fg-templ]: /docs/configure#foreground-templates diff --git a/docs/docs/contributing-segment.md b/docs/docs/contributing-segment.md deleted file mode 100644 index fda816aba8dc..000000000000 --- a/docs/docs/contributing-segment.md +++ /dev/null @@ -1,163 +0,0 @@ ---- -id: contributing_segment -title: Add Segment -sidebar_label: Add Segment ---- - -## Create the logic - -Add a new file following this convention: `new_segment.go`. -Ensure `new` is a single verb indicating the context the segment renders. - -You can use the following template as a guide. - -```go -package main - -type new struct { - props *properties - env environmentInfo -} - -const ( - //NewProp switches something - NewProp Property = "newprop" -) - -func (n *new) enabled() bool { - true -} - -func (n *new) string() string { - newText := n.props.getString(NewProp, "\uEFF1") - return newText -} - -func (n *new) init(props *properties, env environmentInfo) { - n.props = props - n.env = env -} -``` - -When it comes to properties, make sure to use the UTF32 representation (e.g. "\uEFF1") rather than the icon itself. -This will facilitate the review process as not all environments display the icons based on the font being used. -You can find these values and query for icons easily at [Nerd Fonts][nf-icons]. - -For each segment, there's a single test file ensuring the functionality going forward. The convention -is `new_segment_test.go`, have a look at existing segment tests for inspiration. - -## Create a name for your Segment - -[`segment.go`][segment-go] contains the list of available `SegmentType`'s, which gives them a name we can map from the -`.json` [themes][themes]. - -Add your segment. - -```go -//New is brand new -New SegmentType = "new" -``` - -## Add the SegmentType mapping - -Map your `SegmentType` to your Segment in the `mapSegmentWithWriter` function. - -```go -New: &new{}, -``` - -## Test your functionality - -Even with unit tests, it's a good idea to build and validate the changes: - -```shell -go build -o $GOPATH/bin/oh-my-posh -``` - -## Add the documentation - -Create a new `markdown` file underneath the [`docs/docs`][docs] folder called `segment-new.md`. -Use the following template as a guide. - -````markdown ---- -id: new -title: New -sidebar_label: New ---- - -## What - -Display something new. - -## Sample Configuration - -```json -{ - "type": "new", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#193549", - "background": "#ffeb3b", - "properties": { - "newprop": "\uEFF1" - } -} -``` - -## Properties - -- newprop: `string` - the new text to show - defaults to `\uEFF1` -```` - -## Map the new documentation in the sidebar - -Open [`sidebars.js`][sidebars] and add your document id (`new`) to the items of the Segments category. - -## Add the JSON schema - -Edit the `themes/schema.json` file to add your segment. - -At `$.definitions.segment.properties.type.enum`, add your `SegmentType` to the array: - -```json -new, -``` - -At `$.definitions.segment.allOf`, add your segment details: - -```json -{ - "if": { - "properties": { - "type": { "const": "new" } - } - }, - "then": { - "title": "Display something new", - "description": "https://ohmyposh.dev/docs/new", - "properties": { - "properties": { - "properties": { - "nwprop": { - "type": "string", - "title": "New Prop", - "description": "the new text to show", - "default": "\uEFF1" - } - } - } - } - } -} -``` - -## Create a pull request - -And be patient, I'm going as fast as I can 🏎 - -[segment-go]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/segment.go -[themes]: https://github.com/JanDeDobbeleer/oh-my-posh/tree/main/themes -[docs]: https://github.com/JanDeDobbeleer/oh-my-posh/tree/main/docs/docs -[sidebars]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/docs/sidebars.js -[nf-icons]: https://www.nerdfonts.com/cheat-sheet diff --git a/docs/docs/contributing-started.mdx b/docs/docs/contributing-started.mdx deleted file mode 100644 index 95fdf19286aa..000000000000 --- a/docs/docs/contributing-started.mdx +++ /dev/null @@ -1,108 +0,0 @@ ---- -id: contributing_started -title: Get Started -sidebar_label: Get Started ---- - -## Install dependencies - -### go - -The codebase is in [go][go], meaning we need a working go setup before we can do anything else. -Have a look at the [go guide][go-started] to get up and running with go in no time! - -:::info -Oh My Posh needs at least go 1.16. -::: - -### golangci-lint - -To make sure we keep on writing quality code, [golang-ci lint][golang-ci-lint] is used to validate the changes. -Have a look at the [local installation guide][golang-ci-lint-local] to make sure you can validate this yourself as well. - -## Get the source code - -The source is hosted on [Github][omp]. When you want to contribute, create a [fork][gh-fork] so you can make changes in -your repository and create pull request in the official Oh My Posh repository. - -Clone your fork of Oh My Posh locally, replace `` with your Github username. - -```bash -git clone git@github.com:/oh-my-posh.git -``` - -## Running tests - -The go source code can be found in the `src/` directory, make sure to change to that one before continuing. - -### Unit tests - -```bash -go test -v -``` - -### golangci-lint - -```bash -golangci-lint run -``` - -## Building the app - -The easiest way to validate your changes is to write tests. Unfortunately, as it's a visual tool, you'll want to validate -the changes by running the prompt in your shell as well. You can make use of go's `bin` folder which is usually added to -your path to add your own Oh My Posh binary to and immediately see the changes appear in your shell. - -```bash -go build -o $GOPATH/bin/oh-my-posh -``` - -## Get an editor - -A default config(.vscode folder) for [Visual Studio Code](https://code.visualstudio.com) is available in the repo: - -- golangci-lint is configured as the default linter. -- Recommended extensions available for a smooth bootstrap. - - ![recommended extensions](/img/recommended_extensions.png "Recommended extensions" ) -- Default run and debug configurations available. - -Once the extensions are installed: - -- Debug can be started by hitting F5. -- All tests can be run using the Test explorer. - -### Extra tips - -#### Configure Delve in VS Code - -[Delve](https://github.com/go-delve/delve) config is restrictive by default(string limit especially). You can expand some limits in VS Code(`settings.json` or directly in `launch.json`): -``` -"go.delveConfig": { - - "dlvLoadConfig": { - "followPointers": true, - "maxVariableRecurse": 3, - "maxStringLen": 400, - "maxArrayValues": 400, - "maxStructFields": -1 - }, - "apiVersion": 2, - "showGlobalVariables": false -} -``` - -## Up Next - -With everything set up, you're ready to start making changes and create your first [PR][gh-pr]! - -[go]: https://golang.org -[go-started]: https://golang.org/doc/install -[golang-ci-lint]: https://golangci-lint.run -[golang-ci-lint-local]: https://golangci-lint.run/usage/install/#local-installation -[go-bindata]: https://github.com/kevinburke/go-bindata/ -[go-global]: https://github.com/golang/go/issues/40276 -[pr-go-mod]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/.github/workflows/gomod.yml -[gh-pr]: https://github.com/JanDeDobbeleer/oh-my-posh/pulls -[omp]: https://github.com/JanDeDobbeleer/oh-my-posh -[gh-fork]: https://guides.github.com/activities/forking/ diff --git a/docs/docs/faq.mdx b/docs/docs/faq.mdx deleted file mode 100644 index a87a6612f12f..000000000000 --- a/docs/docs/faq.mdx +++ /dev/null @@ -1,109 +0,0 @@ ---- -id: faq -title: FAQ -sidebar_label: 🤨 FAQ ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -Before validating anything, make sure you're on the [latest version][latest] of Oh My Posh and your terminal and shell are up-to-date. - -### The prompt is slow (delay in showing the prompt between commands) - - - - -You can use the `Write-PoshDebug` function to see where Oh my posh spends its time. -In case there's no clear culprit (timings indicate everything's OK), chances are some modules are the culprit. -We bootstrap afew PowerShell modules to maximise compatibility, but sometimes these can introduce unwanted side-effects. - -The modules we support are: - -- posh-git -- Az.Accounts - -Especially the latter can slow things down so the first thing to do is disable that one: - -```powershell -$env:AZ_ENABLED=$false -``` - -Once added, reload your profile for the changes to take effect. - -```powershell -. $PROFILE -``` - - - - -You can use the Oh My Posh's built-in `--debug` flag to identify slow segments. - -```bash -oh-my-posh --config ~/.mytheme.omp.json --debug --shell uni -``` - -Whenever there's a segment that spikes, see if there might be updates to the underlying functionality (usally shell commands). - - - - -If nothing seems to resolve the issue, feel free to [create an issue][new-issue]. - -### Windows Terminal: Unexpected space between segments/text - -Windows Terminal has some issues with [rendering certain glyphs][wt-glyph]. These issues are on [their backlog][wt-glyphs]. -A temporary workaround is to use an invisible character at the end (`\u2800`). - -```json -{ - "type": "executiontime", - /* other attributes here */ - "properties": { - "always_enabled": true, - "prefix": "\ufa1e", - "postfix": "\u2800" // invisible spacing character - } -} -``` - -### There are rectangles instead of icons in my prompt - -The font you're using doesn't have the needed standard extended glyph set like [Nerd Font][nf] does. -Windows Terminal ships with Cascadia Code by default which has a powerline patched variant called Cascadia Code PL, -but also that one misses certain interesting icons. You can fall back to any theme with the `.minimal` indication, -or make use of a Nerd Font. Have a look at the [font][font] senction for more context in case you're using all the right conditions. - -### Jetbrains terminals: Icons do not rendering - -They need to work on their terminal, somehow it only supports UTF-8 and not UTF-16. -[An issue][jb-icons] is available for follow-up here. - -### The term 'Set-Theme' is not recognized as the name of a cmdlet, function, script file, or operable program. - -You need to migrate V2 to V3 using the following [guide][upgrading]. The quick fix is to replace `Set-Theme` with `Set-PoshPrompt`, -but it's advised to read the guide. - -### Strange colouring after exiting VIM or when using the PowerShell progress bootstrap - -This bug is caused by Windows Terminal and/or VIM. There are two issues for this, one at [Windows Terminal][wt-vim] and -one at [VIM][vim-wt]. - -[new-issue]: https://github.com/JanDeDobbeleer/oh-my-posh/issues/new -[latest]: https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest -[wt-glyph]: https://github.com/microsoft/terminal/issues/3546 -[wt-glyphs]: https://github.com/microsoft/terminal/issues?q=is%3Aissue+is%3Aopen+unicode+width -[nf]: https://www.nerdfonts.com/ -[font]: fonts.md -[jb-icons]: https://youtrack.jetbrains.com/issue/IDEA-248010 -[upgrading]: upgrading.md -[wt-vim]: https://github.com/microsoft/terminal/issues/3794 -[vim-wt]: https://github.com/vim/vim/issues/5092 diff --git a/docs/docs/fonts.md b/docs/docs/fonts.md deleted file mode 100644 index cba280389ce9..000000000000 --- a/docs/docs/fonts.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: fonts -title: Fonts -sidebar_label: 🆎 Fonts ---- - -### Nerd Fonts - -Oh My Posh was designed to use [Nerd Fonts][nerdfonts]. Nerd Fonts are popular fonts that are patched to include icons. -We recommend [Meslo LGM NF][meslo], but any Nerd Font should be compatible with the standard [themes][themes]. - -To see the icons displayed in Oh My Posh, **install** a [Nerd Font][nerdfonts], and **configure** your terminal to use it. - -#### Windows - -Download your chosen Nerd Font, and install the font system-wide. See this [thread][font-thread] for more context. - -#### Windows Terminal - -Once you have installed a Nerd Font, you will need to configure the Windows Terminal to use it. This can be easily done -by modifying the Windows Terminal settings (default shortcut: `CTRL + ,`). In your `settings.json` file, add the -`fontFace` attribute under the `defaults` attribute in `profiles`: - -```json -{ - "profiles": - { - "defaults": - { - "fontFace": "MesloLGM NF" - } - } -} -``` - -### Other Fonts - -If you are not interested in using a Nerd Font, you will want to use a theme which doesn't include any Nerd Font icons. -The `minimal` themes do not make use of Nerd Font icons. - -[Creating your own theme][configuration] is always an option too 😊 - -[nerdfonts]: https://www.nerdfonts.com/ -[meslo]: https://github.com/ryanoasis/nerd-fonts/releases/download/v2.1.0/Meslo.zip -[themes]: https://github.com/JanDeDobbeleer/oh-my-posh/tree/main/themes -[font-thread]: https://github.com/JanDeDobbeleer/oh-my-posh/issues/145#issuecomment-730162622 -[configuration]: /docs/configure diff --git a/docs/docs/install-customize-cmd.mdx b/docs/docs/install-customize-cmd.mdx deleted file mode 100644 index e904517f1a7d..000000000000 --- a/docs/docs/install-customize-cmd.mdx +++ /dev/null @@ -1,84 +0,0 @@ -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - - - - -```powershell -Export-PoshTheme -FilePath ~/.mytheme.omp.json -Format json -``` - -Once you're done editing, adjust your `$PROFILE` to use your newly created theme. - -```powershell -oh-my-posh --init --shell pwsh --config ~/.mytheme.omp.json | Invoke-Expression -``` - - - - -```bash -export_poshconfig "~/.mytheme.omp.json" json -``` - -Once you're done editing, adjust `~/.zshrc` to use your newly created theme. - -```bash -eval "$(oh-my-posh --init --shell zsh --config ~/.mytheme.omp.json)" -``` - -When adjusted, reload your profile for the changes to take effect. - -```bash -. ~/.zshrc -``` - - - - -```bash -export_poshconfig "~/.mytheme.omp.json" json -``` - -Once you're done editing, adjust `~/.bashrc` to use your newly created theme. - -```bash -eval "$(oh-my-posh --init --shell bash --config ~/.mytheme.omp.json)" -``` - -When adjusted, reload your profile for the changes to take effect. - -```bash -. ~/.bashrc -``` - - - - -```bash -export_poshconfig "~/.mytheme.omp.json" json -``` - -Once you're done editing, adjust `config.fish` to use your newly created theme. - -```bash -oh-my-posh --init --shell fish --config ~/.mytheme.omp.json | source -``` - -Once adjusted, reload your config for the changes to take effect. - -```bash -. ~/.config/fish/config.fish -``` - - - diff --git a/docs/docs/install-customize.md b/docs/docs/install-customize.md deleted file mode 100644 index 409252634bf0..000000000000 --- a/docs/docs/install-customize.md +++ /dev/null @@ -1,21 +0,0 @@ - -At this point you're good to go. The `jandedobbeleer.omp.json` theme displays most common use-cases -in your prompt so 9/10 you'll be more than happy with it. However, if you want to explore additional -functionality, going through the additional steps below will help you get started. - -#### Change the theme - -We downloaded all the themes and set `jandedobbeleer.omp.json` as the one to use. -However, there are [a lot more][themes] to be discovered and maybe there are some you like better. - -#### Override the theme settings - -Maybe there's a theme you like, but you don't fancy the colors. Or, maybe there's a segment you -want to tweak/add, or replace some of the icons with a different one. Whatever the case, read through all -available options first, by starting with the [configuration guide][configuration]. - -You can output the current theme to the format you like (`json`, `yaml` or `toml`) which can be used to tweak -and store as your custom theme. - -[themes]: themes.md -[configuration]: configuration.md diff --git a/docs/docs/install-linux.mdx b/docs/docs/install-linux.mdx deleted file mode 100644 index e2a86c412f8f..000000000000 --- a/docs/docs/install-linux.mdx +++ /dev/null @@ -1,61 +0,0 @@ ---- -id: linux -title: Linux -sidebar_label: 🐧 Linux ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import Shells from "./install-shells.mdx"; -import Customize from "./install-customize.md"; -import CustomizeCmd from "./install-customize-cmd.mdx"; - -### Setup your terminal - -Oh My Posh uses ANSI color codes under the hood, these should work in every terminal, -but you may have to set the environment variable `$TERM` to `xterm-256color` for it to work. - -### Installation - -```bash -sudo wget https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/posh-linux-amd64 -O /usr/local/bin/oh-my-posh -sudo chmod +x /usr/local/bin/oh-my-posh -``` - -#### Download the themes - -```bash -mkdir ~/.poshthemes -wget https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/themes.zip -O ~/.poshthemes/themes.zip -unzip ~/.poshthemes/themes.zip -d ~/.poshthemes -chmod u+rw ~/.poshthemes/*.json -rm ~/.poshthemes/themes.zip -``` - -#### Preview the themes - -```bash -for file in ~/.poshthemes/*.omp.json; do echo "$file\n"; oh-my-posh --config $file --shell universal; echo "\n"; done; -``` - -### Replace your existing prompt - -The guides below assume you copied the theme called `jandedobbeleer.omp.json` to your user's `$HOME` folder. -When you've downloaded the themes, you can find this one at `~/.poshthemes/jandedobbeleer.omp.json`. - - - -### Customize - - - - - -🎉🎉🎉 - -[scoop]: https://scoop.sh/ -[wt]: https://github.com/microsoft/terminal -[iterm2]: https://www.iterm2.com/ -[powershell]: https://www.powershellgallery.com/packages/oh-my-posh -[brew]: https://brew.sh -[configuration]: /docs/configure diff --git a/docs/docs/install-macos.mdx b/docs/docs/install-macos.mdx deleted file mode 100644 index 94062e8dcf0a..000000000000 --- a/docs/docs/install-macos.mdx +++ /dev/null @@ -1,67 +0,0 @@ ---- -id: macos -title: macOS -sidebar_label: 🍏 macOS ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import Shells from "./install-shells.mdx"; -import Customize from "./install-customize.md"; -import CustomizeCmd from "./install-customize-cmd.mdx"; - -### Setup your terminal - -As the standard terminal has issues displaying the ANSI characters correctly, we advise using -[iTerm2][iterm2] or any other modern day MacOS terminal that supports ANSI characters. - -### Installation - -A [Homebrew][brew] formula is available for easy installation. - -```bash -brew tap jandedobbeleer/oh-my-posh -brew install oh-my-posh -``` - -This installs two things: - -- `oh-my-posh` - Executable, added to `/usr/local/bin` -- `themes` - The latest Oh My Posh themes - -If you want to use a standard theme, you can find them in `$(brew --prefix oh-my-posh)/themes`, referencing them as such -will always keep them compatible with the binary when updating Oh My Posh. - -#### Preview the themes - -```bash -for file in $(brew --prefix oh-my-posh)/themes/*.omp.json; do echo "$file\n"; oh-my-posh --config $file --shell universal; echo "\n"; done; -``` - -#### Update - -```bash -brew upgrade oh-my-posh -``` - -### Replace your existing prompt - -The guides below assume you copied the theme called `jandedobbeleer.omp.json` to your user's `$HOME` folder. -When using brew, you can find this one at `$(brew --prefix oh-my-posh)/themes/jandedobbeleer.omp.json`. - - - -### Customize - - - - - -🎉🎉🎉 - -[scoop]: https://scoop.sh/ -[wt]: https://github.com/microsoft/terminal -[iterm2]: https://www.iterm2.com/ -[powershell]: https://www.powershellgallery.com/packages/oh-my-posh -[brew]: https://brew.sh -[configuration]: /docs/configure diff --git a/docs/docs/install-pwsh.mdx b/docs/docs/install-pwsh.mdx deleted file mode 100644 index 9906d99502b7..000000000000 --- a/docs/docs/install-pwsh.mdx +++ /dev/null @@ -1,86 +0,0 @@ ---- -id: pwsh -title: PowerShell -sidebar_label: 🐚 PowerShell ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import Customize from "./install-customize.md"; - -:::info -This guide is for usage inside PowerShell only. If you plan to use Oh My Posh inside other shells as well, -have a look at the platform specific install instructions. -::: - -## Installation - -```powershell -Install-Module oh-my-posh -Scope CurrentUser -``` - -:::caution -If you wish to install for **all users in a linux** environment, you will need to install from an **elevated PowerShell session**. -You will also need to import the module one time from the elevated session in order to set the executable permissions correctly. - -```powershell -sudo pwsh -Install-Module oh-my-posh -Scope AllUsers -Import-Module oh-my-posh -``` -::: - -## List all themes - -To display every available theme in the current directory, use the following -cmdlet. - -```powershell -Get-PoshThemes -``` - -The module installs all themes in the module folder. To find the actual files, you can use the following command: - -```powershell -Get-PoshThemes -list -``` - -## Replace your existing prompt - -Edit `$PROFILE` in your preferred PowerShell version and add the following line. Autocompletion is available so it will loop -through all available themes. - -```powershell -Set-PoshPrompt -Theme jandedobbeleer -``` - -Once added, reload your profile for the changes to take effect. - -```powershell -. $PROFILE -``` - - - -```powershell -Export-PoshTheme -FilePath ~/.mytheme.omp.json -Format json -``` - -Once you're done editing, adjust your `$PROFILE` to use your newly created theme. - -```powershell -Set-PoshPrompt -Theme ~/.mytheme.omp.json -``` - -## Update - -```powershell -Update-Module oh-my-posh -``` - -🎉🎉🎉 - -[scoop]: https://scoop.sh/ -[wt]: https://github.com/microsoft/terminal -[powershell]: https://www.powershellgallery.com/packages/oh-my-posh -[configuration]: /docs/configure diff --git a/docs/docs/install-shells.mdx b/docs/docs/install-shells.mdx deleted file mode 100644 index 8db5aed680b5..000000000000 --- a/docs/docs/install-shells.mdx +++ /dev/null @@ -1,113 +0,0 @@ -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -:::info -If you have no idea which shell you're currently using, Oh My Posh has a utility switch that can you tell you (not relevant -for the Powershell module). -::: - -```bash -oh-my-posh --print-shell -``` - - - - -Edit `$PROFILE` in your preferred PowerShell version and add the following line. - -```powershell -oh-my-posh --init --shell pwsh --config ~/jandedobbeleer.omp.json | Invoke-Expression -``` - -Once added, reload your profile for the changes to take effect. - -```powershell -. $PROFILE -``` - - - - -Add the following to `~/.zshrc`: - -```bash -eval "$(oh-my-posh --init --shell zsh --config ~/jandedobbeleer.omp.json)" -``` - -Once added, reload your profile for the changes to take effect. - -```bash -source ~/.zshrc -``` - - - - -Add the following to `~/.bashrc` (or `~/.profile` on MacOS): - -```bash -eval "$(oh-my-posh --init --shell bash --config ~/jandedobbeleer.omp.json)" -``` - -Once added, reload your profile for the changes to take effect. - -```bash -. ~/.bashrc -``` - -Or, when using `~/.profile`. - -```bash -. ~/.profile -``` - - - - -:::caution -It's advised to be on the latest version of fish. Versions below 3.1.2 have issues displaying the prompt. -::: - -Initialize Oh My Posh in `~/.config/fish/config.fish`: - -```bash -oh-my-posh --init --shell fish --config ~/jandedobbeleer.omp.json | source -``` - -Once added, reload your config for the changes to take effect. - -```bash -. ~/.config/fish/config.fish -``` - - - - -Set the prompt and restart nu shell: - -### Nu < 0.32.0 - -```bash -config set prompt "= `{{$(oh-my-posh --config ~/jandedobbeleer.omp.json | str collect)}}`" -``` - -### Nu >= 0.32.0 - -```bash -config set prompt "(oh-my-posh --config ~/jandedobbeleer.omp.json | str collect)" -``` - -Restart nu shell for the changes to take effect. - - - diff --git a/docs/docs/install-windows.mdx b/docs/docs/install-windows.mdx deleted file mode 100644 index b373d08d5a1c..000000000000 --- a/docs/docs/install-windows.mdx +++ /dev/null @@ -1,194 +0,0 @@ ---- -id: windows -title: Windows -sidebar_label: ⊞ Windows ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import Shells from "./install-shells.mdx"; -import Customize from "./install-customize.md"; -import CustomizeCmd from "./install-customize-cmd.mdx"; - -### Setup your terminal - -While Oh My Posh works on the standard terminal, we advise using the [Windows Terminal][wt]. - -### Installation - - - - -```powershell -winget install JanDeDobbeleer.OhMyPosh -``` - -This installs a couple of things: - -- `oh-my-posh.exe` - Windows executable, added to your `$PATH` -- `oh-my-posh-wsl` - Linux executable, added to your `$PATH` for use in the WSL -- `themes` - The latest Oh My Posh themes - -If you want to use a standard theme, you can find them in `~\AppData\Local\Programs\oh-my-posh\themes\`, -referencing them as such -will always keep them compatible with the binary when updating Oh My Posh. - - - - -```powershell -scoop install https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/oh-my-posh.json -``` - -This installs a couple of things: - -- `oh-my-posh.exe` - Windows executable, added to your `$PATH` -- `oh-my-posh-wsl` - Linux executable, added to your `$PATH` for use in the WSL -- `themes` - The latest Oh My Posh themes - -If you want to use a standard theme, you can find them in `~\scoop\apps\oh-my-posh\current\themes\`, -referencing them as such -will always keep them compatible with the binary when updating Oh My Posh. - - - - -For the `$PATH` to reload, a reboot is advised. - -### Usage - -#### Preview the themes - - - - -```powershell -Get-ChildItem -Path "~\AppData\Local\Programs\oh-my-posh\themes\*" -Include '*.omp.json' | Sort-Object Name | ForEach-Object -Process { - $esc = [char]27 - Write-Host "" - Write-Host "$esc[1m$($_.BaseName)$esc[0m" - Write-Host "" - oh-my-posh --config $($_.FullName) --pwd $PWD - Write-Host "" -} -``` - - - - -```powershell -Get-ChildItem -Path "$(scoop prefix oh-my-posh)\themes\*" -Include '*.omp.json' | Sort-Object Name | ForEach-Object -Process { - $esc = [char]27 - Write-Host "" - Write-Host "$esc[1m$($_.BaseName)$esc[0m" - Write-Host "" - oh-my-posh --config $($_.FullName) --pwd $PWD - Write-Host "" -} -``` - - - - -#### Update - - - - -```powershell -winget upgrade JanDeDobbeleer.OhMyPosh -``` - - - - -```powershell -scoop update oh-my-posh -``` - - - - -### Replace your existing prompt - - - - -You can find the themes scoop installs inside the `~\AppData\Local\Programs\oh-my-posh\themes\` folder. -To use `jandedobbeleer.omp.json` for example, you can refer to it using `~\AppData\Local\Programs\oh-my-posh\themes\jandedobbeleer.omp.json"` -when setting the prompt using the `--config` flag. - - - - -You can find the themes scoop installs inside the `"$(scoop prefix oh-my-posh)\themes\"` folder. -To use `jandedobbeleer.omp.json` for example, you can refer to it using `"$(scoop prefix oh-my-posh)\themes\jandedobbeleer.omp.json"` -when setting the prompt using the `--config` flag. - - - - -The guides below assume you copied the theme called `jandedobbeleer.omp.json` to your user's `$HOME` folder. -Based on the installation method used, you can find this theme at the following location: - - - - -`~\AppData\Local\Programs\oh-my-posh\themes\jandedobbeleer.omp.json` - - - - -`"$(scoop prefix oh-my-posh)\themes\jandedobbeleer.omp.json"` - - - - - - -### Customize - - - - - -🎉🎉🎉 - -[scoop]: https://scoop.sh/ -[wt]: https://github.com/microsoft/terminal -[powershell]: https://www.powershellgallery.com/packages/oh-my-posh diff --git a/docs/docs/segment-aws.md b/docs/docs/segment-aws.md deleted file mode 100644 index b6d71d2994c3..000000000000 --- a/docs/docs/segment-aws.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: aws -title: AWS Context -sidebar_label: AWS ---- - -## What - -Display the currently active AWS profile and region. - -## Sample Configuration - -```json -{ - "type": "aws", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#FFA400", - "properties": { - "prefix": " \uE7AD ", - "template": "{{.Profile}}{{if .Region}}@{{.Region}}{{end}}" - } -} -``` - -## Properties - -- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the -properties below. Defaults to `{{.Context}}{{if .Namespace}} :: {{.Namespace}}{{end}}` -- display_default: `boolean` - display the segment or not when the user profile matches `default` - defaults -to `true` - -## Template Properties - -- `.Profile`: `string` - the currently active profile -- `.Region`: `string` - the currently active region - -[go-text-template]: https://golang.org/pkg/text/template/ -[sprig]: https://masterminds.github.io/sprig/ diff --git a/docs/docs/segment-az.mdx b/docs/docs/segment-az.mdx deleted file mode 100644 index 58cbd7d1e9c9..000000000000 --- a/docs/docs/segment-az.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -id: az -title: Azure Subscription -sidebar_label: Azure ---- - -## What - -Display the currently active Azure subscription information. - -:::info -PowerShell offers support for the `Az.Accounts` module, but it is disabled by default. -To enable this, set `$env:AZ_ENABLED = $true` in your `$PROFILE`. -::: - -## Sample Configuration - -```json -{ - "type": "az", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#000000", - "background": "#9ec3f0", - "properties": { - "display_id": true, - "display_name": true, - "info_separator": " @ ", - "prefix": " \uFD03 " - } -} -``` - -## Properties - -- display_account: `boolean` - display the subscription account name or not - defaults to `false` -- display_name: `boolean` - display the subscription name or not - defaults to `true` -- display_id: `boolean` - display the subscription ID or not - defaults to `false` -- info_separator: `string` - text/icon to put in between the values - defaults to ` | ` diff --git a/docs/docs/segment-azfunc.md b/docs/docs/segment-azfunc.md deleted file mode 100644 index 573381741793..000000000000 --- a/docs/docs/segment-azfunc.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -id: azfunc -title: Azure functions -sidebar_label: Azure functions ---- - -## What - -Display the currently active Azure functions CLI version. - -## Sample Configuration - -```json -{ - "type": "azfunc", - "style": "powerline", - "powerline_symbol": "", - "foreground": "#ffffff", - "background": "#FEAC19", - "properties": { - "prefix": " \uf0e7 ", - "display_version": true, - "display_mode": "files" - } -} -``` - -## Properties - -- display_version: `boolean` - display the Azure functions CLI version - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: the segment is always displayed - - `files`: the segment is only displayed when a `host.json` or `local.settings.json` files is present (default) diff --git a/docs/docs/segment-battery.md b/docs/docs/segment-battery.md deleted file mode 100644 index 4e3ef089d12a..000000000000 --- a/docs/docs/segment-battery.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -id: battery -title: Battery -sidebar_label: Battery ---- - -## What - -Battery displays the remaining power percentage for your battery. - -## Sample Configuration - -```json -{ - "type": "battery", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#193549", - "background": "#ffeb3b", - "properties": { - "battery_icon": "", - "discharging_icon": "\uE231 ", - "charging_icon": "\uE234 ", - "charged_icon": "\uE22F ", - "color_background": true, - "charged_color": "#4caf50", - "charging_color": "#40c4ff", - "discharging_color": "#ff5722", - "postfix": "\uF295 ", - "display_charging": true - } -} -``` - -## Properties - -- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the -properties below. Defaults to `{{.Icon}}{{ if not .Error }}{{.Percentage}}{{ end }}{{.Error}}` -- display_error: `boolean` - show the error context when failing to retrieve the battery information - defaults to `false` -- charging_icon: `string` - icon to display on the left when charging - defaults to empty -- discharging_icon: `string` - icon to display on the left when discharging - defaults to empty -- charged_icon: `string` - icon to display on the left when fully charged - defaults to empty -- color_background: `boolean` - color the background or foreground for properties below - defaults to `false` -- charged_color: `string` [color][colors] - color to use when fully charged - defaults to segment color -- charging_color: `string` [color][colors] - color to use when charging - defaults to segment color -- discharging_color: `string` [color][colors] - color to use when discharging - defaults to segment color -- display_charging: `bool` - displays the battery status while charging (Charging or Full) - -## Template Properties - -- `.Battery`: `struct` - the [battery][battery] object, you can use any property it has e.g. `.Battery.State` -- `.Percentage`: `float64` - the current battery percentage -- `.Error`: `string` - the error in case fetching the battery information failed -- `.Icon`: `string` - the icon based on the battery state - -[colors]: /docs/configure#colors -[battery]: https://github.com/distatus/battery/blob/master/battery.go#L78 -[go-text-template]: https://golang.org/pkg/text/template/ -[sprig]: https://masterminds.github.io/sprig/ diff --git a/docs/docs/segment-command.md b/docs/docs/segment-command.md deleted file mode 100644 index 739f61fbf26d..000000000000 --- a/docs/docs/segment-command.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -id: command -title: Command -sidebar_label: Command ---- - -## What - -:::info Powershell -While powerful, it tends to take a lot of time executing the command on **Powershell**. -Even with `–noprofile` it's noticeably slower compared to `sh`. It's advised to look at using -[environment variables][env] when using Powershell. -::: - -Command allows you run an arbitrary shell command. Be aware it spawn a new process to fetch the result, meaning -it will not be able to fetch session based context (look at abusing [environment variables][env] for that). -When the command errors or returns an empty string, this segment isn't rendered. - -You have the ability to use `||` or `&&` to stitch commands together and achieve complex results. When using `||` -the first command that returns a string will be used (or none when they all fail to produce output that's not an -error). The `&&` functionality will join the output of the commands when successful. - -## Sample Configuration - -```json -{ - "type": "prompt", - "alignment": "right", - "segments": [ - { - "type": "command", - "style": "plain", - "foreground": "#ffffff", - "properties": { - "shell": "bash", - "command": "git log --pretty=format:%cr -1 || date +%H:%m:%S" - } - } - ] -} -``` - -## Properties - -- shell: `string` - the shell in which to run the command in. Uses `shell -c command` under the hood. -- command: `string` - the command(s) to run - -[env]: /docs/environment diff --git a/docs/docs/segment-crystal.md b/docs/docs/segment-crystal.md deleted file mode 100644 index b2797afaee12..000000000000 --- a/docs/docs/segment-crystal.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -id: crystal -title: Crystal -sidebar_label: Crystal ---- - -## What - -Display the currently active crystal version. - -## Sample Configuration - -```json -{ - "type": "crystal", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#4063D8", - "properties": { - "prefix": " \uE370 " - } -} -``` - -## Properties - -- display_version: `boolean` - display the julia version - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: the segment is always displayed - - `files`: the segment is only displayed when `*.cr` or `shard.yml` files are present (default) diff --git a/docs/docs/segment-dart.md b/docs/docs/segment-dart.md deleted file mode 100644 index 963b696e58e2..000000000000 --- a/docs/docs/segment-dart.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -id: dart -title: Dart -sidebar_label: Dart ---- - -## What - -Display the currently active dart version. - -## Sample Configuration - -```json -{ - "type": "dart", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#06A4CE", - "properties": { - "prefix": " \uE798 " - } -} -``` - -## Properties - -- display_version: `boolean` - display the julia version - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: the segment is always displayed - - `files`: the segment is only displayed when `*.dart`, `pubspec.yaml`, `pubspec.yml`, `pubspec.lock` files or the `.dart_tool` -folder are present (default) diff --git a/docs/docs/segment-dotnet.md b/docs/docs/segment-dotnet.md deleted file mode 100644 index 0293edc4dff4..000000000000 --- a/docs/docs/segment-dotnet.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -id: dotnet -title: Dotnet -sidebar_label: Dotnet ---- - -## What - -Display the currently active .NET SDK version. - -## Sample Configuration - -```json -{ - "type": "dotnet", - "style": "powerline", - "powerline_symbol": "", - "foreground": "#000000", - "background": "#00ffff", - "properties": { - "prefix": " \uE77F " - } -} -``` - -## Properties - -- display_version: `boolean` - display the active version or not; useful if all you need is an icon indicating `dotnet` - is present - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: the segment is always displayed - - `files`: the segment is only displayed when `*.cs`, `*.vb`, `*.sln`, `*.csproj`, or `*.vbproj` files are present (default) -- unsupported_version_icon: `string` - text/icon that is displayed when the active .NET SDK version (e.g., one specified - by `global.json`) is not installed/supported - defaults to `\uf071` (X in a rectangle box) diff --git a/docs/docs/segment-environment.md b/docs/docs/segment-environment.md deleted file mode 100644 index 8ddf5c8b778d..000000000000 --- a/docs/docs/segment-environment.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -id: environment -title: Environment Variable -sidebar_label: Environment Variable ---- - -## What - -Show the content of an environment variable. -Can be used to visualize a local settings/context unavailable to Go my Posh otherwise. - -For example, in PowerShell, adding the below configuration to a block and extending the prompt -function to set an environment variable before the prompt, you can work a bit of magic. - -```powershell -[ScriptBlock]$Prompt = { - $realLASTEXITCODE = $global:LASTEXITCODE - $env:POSH = "hello from Powershell" - & "C:\tools\oh-my-posh.exe" -config "~/downloadedtheme.json" -error $realLASTEXITCODE -pwd $PWD - $global:LASTEXITCODE = $realLASTEXITCODE - Remove-Variable realLASTEXITCODE -Confirm:$false -} -``` - -If you're using the PowerShell module, you can override a function to achieve the same effect. -make sure to do this after importing `go-my-posh` and you're good to go. - -```powershell -function Set-EnvVar { - $env:POSH=$(Get-Date) -} -New-Alias -Name 'Set-PoshContext' -Value 'Set-EnvVar' -Scope Global -Force -``` - -The segment will show when the value of the environment variable isn't empty. - -## Sample Configuration - -```json -{ - "type": "envvar", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#0077c2", - "properties": { - "var_name": "POSH" - } -} -``` - -- var_name: `string` - the name of the environment variable diff --git a/docs/docs/segment-executiontime.md b/docs/docs/segment-executiontime.md deleted file mode 100644 index baec7059a0e0..000000000000 --- a/docs/docs/segment-executiontime.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -id: executiontime -title: Execution Time -sidebar_label: Execution Time ---- - -## What - -Displays the execution time of the previously executed command. - -To use this, use the PowerShell module, or confirm that you are passing an `execution-time` argument containing the -elapsed milliseconds to the oh-my-posh executable. -The installation guide shows how to include this argument for PowerShell and Zsh. - -## Sample Configuration - -```json -{ - "type": "executiontime", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#8800dd", - "properties": { - "threshold": 500, - "style": "austin", - "prefix": " <#fefefe>\ufbab " - } -} -``` - -## Properties - -- always_enabled: `boolean` - always show the duration - defaults to `false` -- threshold: `number` - minimum duration (milliseconds) required to enable this segment - defaults to `500` -- style: `enum` - one of the available format options - defaults to `austin` - -## Style - -Style specifies the format in which the time will be displayed. The table below shows some example times in each option. - -| format | 0.001s | 2.1s | 3m2.1s | 4h3m2.1s | -| --------- | -------------- | ------------ | ------------- | ---------------- | -| austin | `1ms` | `2.1s` | `3m 2.1s` | `4h 3m 2.1s` | -| roundrock | `1ms` | `2s 100ms` | `3m 2s 100ms` | `4h 3m 2s 100ms` | -| dallas | `0.001` | `2.1` | `3:2.1` | `4:3:2.1` | -| galveston | `00:00:00` | `00:00:02` | `00:03:02` | `04:03:02` | -| houston | `00:00:00.001` | `00:00:02.1` | `00:03:02.1` | `04:03:02.1` | -| amarillo | `0.001s` | `2.1s` | `182.1s` | `14,582.1s` | -| round | `1ms` | `2s` | `3m 2s` | `4h 3m` | diff --git a/docs/docs/segment-exit.md b/docs/docs/segment-exit.md deleted file mode 100644 index f82b2099db79..000000000000 --- a/docs/docs/segment-exit.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -id: exit -title: Exit code -sidebar_label: Exit code ---- - -## What - -Displays the last exit code or that the last command failed based on the configuration. - -## Sample Configuration - -```json -{ - "type": "exit", - "style": "diamond", - "foreground": "#ffffff", - "background": "#00897b", - "leading_diamond": "", - "trailing_diamond": "\uE0B4", - "properties": { - "display_exit_code": false, - "always_enabled": true, - "error_color": "#e91e63", - "color_background": true, - "prefix": "<#193549>\uE0B0 \uE23A" - } -} -``` - -## Properties - -- display_exit_code: `boolean` - show or hide the exit code - defaults to `true` -- always_enabled: `boolean` - always show the status - defaults to `false` -- color_background: `boolean` - color the background or foreground when an error occurs - defaults to `false` -- error_color: `string` [color][colors] - color to use when an error occurred -- always_numeric: `boolean` - always display exit code as a number - defaults to `false` -- success_icon: `string` - displays when there's no error and `"always_enabled": true` - defaults to `""` -- error_icon: `string` - displays when there's an error - defaults to `""` - -[colors]: /docs/configure#colors diff --git a/docs/docs/segment-git.mdx b/docs/docs/segment-git.mdx deleted file mode 100644 index bb79ba509e67..000000000000 --- a/docs/docs/segment-git.mdx +++ /dev/null @@ -1,103 +0,0 @@ ---- -id: git -title: Git -sidebar_label: Git ---- - -## What - -Display git information when in a git repository. Also works for subfolders. For maximum compatibility, -make sure your `git` executable is up-to-date (when branch or status information is incorrect for example). - -Local changes can also be displayed which uses the following syntax for both the working and staging area: - -- `+` added -- `~` modified -- `-` deleted -- `?` untracked - -:::info -PowerShell offers support for the `posh-git` module for autocompletion, but it is disabled by default. -To enable this, set `$env:POSH_GIT_ENABLED = $true` in your `$PROFILE`. -::: - -:::warning -Starting from version 3.152.0, `display_status` is disabled by default. -It improves performance but reduces the quantity of information. Don't forget to enable it in your theme if needed. -An alternative is to use the [Posh-Git segment][poshgit] -::: - -## Sample Configuration - -```json -{ - "type": "git", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#193549", - "background": "#ffeb3b", - "properties": { - "display_status": true, - "display_stash_count": true, - "display_upstream_icon": true - } -} -``` - -## Properties - -### Standard - -- branch_icon: `string` - the icon to use in front of the git branch name - defaults to `\uE0A0 ` -- display_branch_status: `boolean` - display the branch status or not - defaults to `true` -- branch_identical_icon: `string` - the icon to display when remote and local are identical - defaults to `\u2261` -- branch_ahead_icon: `string` - the icon to display when the local branch is ahead of its remote - defaults to `\u2191` -- branch_behind_icon: `string` - the icon to display when the local branch is behind its remote - defaults to `\u2193` -- branch_gone_icon: `string` - the icon to display when there's no remote branch - defaults to `\u2262` -- branch_max_length: `int` - the max length for the displayed branch name where `0` implies full length - defaults to `0` - -### Status - -- display_status: `boolean` - display the local changes or not - defaults to `false` -- display_status_detail: `boolean` - display the local changes in detail or not - defaults to `true` -- display_stash_count: `boolean` show stash count or not - defaults to `false` -- status_separator_icon: `string` icon/text to display between staging and working area changes - defaults to ` |` -- local_working_icon: `string` - the icon to display in front of the working area changes - defaults to `\uF044` -- local_staged_icon: `string` - the icon to display in front of the staged area changes - defaults to `\uF046` -- stash_count_icon: `string` icon/text to display before the stash context - defaults to `\uF692` - -### HEAD context - -- commit_icon: `string` - icon/text to display before the commit context (detached HEAD) - defaults to `\uF417` -- tag_icon: `string` - icon/text to display before the tag context - defaults to `\uF412` -- rebase_icon: `string` - icon/text to display before the context when in a rebase - defaults to `\uE728 ` -- cherry_pick_icon: `string` - icon/text to display before the context when doing a cherry-pick - defaults to `\uE29B ` -- merge_icon: `string` icon/text to display before the merge context - defaults to `\uE727 ` -- no_commits_icon: `string` icon/text to display when there are no commits in the repo - defaults to `\uF594 ` - -### Upstream context - -- display_upstream_icon: `boolean` - display upstream icon or not - defaults to `false` -- github_icon: `string` - icon/text to display when the upstream is Github - defaults to `\uF408 ` -- gitlab_icon: `string` - icon/text to display when the upstream is Gitlab - defaults to `\uF296 ` -- bitbucket_icon: `string` - icon/text to display when the upstream is Bitbucket - defaults to `\uF171 ` -- azure_devops_icon: `string` - icon/text to display when the upstream is Azure DevOps - defaults to `\uFD03 ` -- git_icon: `string` - icon/text to display when the upstream is not known/mapped - defaults to `\uE5FB ` - -### Colors - -- working_color: `string` [color][colors] - foreground color for the working area status - defaults to segment foreground -- staging_color: `string` [color][colors] - foreground color for the staging area status - defaults to segment foreground -- status_colors_enabled: `boolean` - color the segment based on the repository status - defaults to `false` -- color_background: `boolean` - color background or foreground - defaults to `true` -- local_changes_color: `string` [color][colors] - segment color when there are local changes - defaults to segment -foreground/background (see `color_background`) -- ahead_and_behind_color: `string` [color][colors] - segment color when the branch is ahead and behind - -defaults to segment foreground/background (see `color_background`) -- behind_color: `string` [color][colors] - segment color when the branch is behind - defaults to segment -foreground/background (see `color_background`) -- ahead_color: `string` [color][colors] - segment color when the branch is ahead - defaults to segment -foreground/background (see `color_background`) - -[colors]: /docs/configure#colors -[poshgit]: /docs/poshgit diff --git a/docs/docs/segment-golang.md b/docs/docs/segment-golang.md deleted file mode 100644 index b934c81faf25..000000000000 --- a/docs/docs/segment-golang.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -id: golang -title: Golang -sidebar_label: Golang ---- - -## What - -Display the currently active golang version. - -## Sample Configuration - -```json -{ - "type": "go", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#7FD5EA", - "properties": { - "prefix": " \uFCD1 " - } -} -``` - -## Properties - -- display_version: `boolean` - display the golang version - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: the segment is always displayed - - `files`: the segment is only displayed when `*.go` or `go.mod` files are present (default) diff --git a/docs/docs/segment-java.md b/docs/docs/segment-java.md deleted file mode 100644 index 10870634b8fb..000000000000 --- a/docs/docs/segment-java.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -id: java -title: Java -sidebar_label: Java ---- - -## What - -Display the currently active java version. - -## Sample Configuration - -```json -{ - "type": "java", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#4063D8", - "properties": { - "prefix": " \uE738 " - } -} -``` - -## Properties - -- display_version: `boolean` - display the java version - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the java command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: the segment is always displayed - - `files`: the segment is only displayed when one of the following files is present: - - `pom.xml` - - `build.gradle.kts` - - `build.sbt` - - `.java-version` - - `.deps.edn` - - `project.clj` - - `build.boot` - - `*.java` - - `*.class` - - `*.gradle` - - `*.jar` - - `*.clj` - - `*.cljc` diff --git a/docs/docs/segment-julia.md b/docs/docs/segment-julia.md deleted file mode 100644 index 52c219ea0be8..000000000000 --- a/docs/docs/segment-julia.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -id: julia -title: Julia -sidebar_label: Julia ---- - -## What - -Display the currently active julia version. - -## Sample Configuration - -```json -{ - "type": "julia", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#4063D8", - "properties": { - "prefix": " \uE624 " - } -} -``` - -## Properties - -- display_version: `boolean` - display the julia version - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: the segment is always displayed - - `files`: the segment is only displayed when `*.jl` files are present (default) diff --git a/docs/docs/segment-kubectl.md b/docs/docs/segment-kubectl.md deleted file mode 100644 index e722db8dbc4e..000000000000 --- a/docs/docs/segment-kubectl.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -id: kubectl -title: Kubectl Context -sidebar_label: Kubectl ---- - -## What - -Display the currently active Kubernetes context name and namespace name. - -## Sample Configuration - -```json -{ - "type": "kubectl", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#000000", - "background": "#ebcc34", - "properties": { - "prefix": " \uFD31 ", - "template": "{{.Context}}{{if .Namespace}} :: {{.Namespace}}{{end}}" - } -} -``` - -## Properties - -- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the -properties below. Defaults to `{{.Context}}{{if .Namespace}} :: {{.Namespace}}{{end}}` -- display_error: `boolean` - show the error context when failing to retrieve the kubectl information - defaults to `false` - -## Template Properties - -- `.Context`: `string` - the current kubectl context -- `.Namespace`: `string` - the current kubectl namespace - -## Tips - -It is common for the Kubernetes "default" namespace to be used when no namespace is provided. If you want your prompt to - render an empty current namespace using the word "default", you can use something like this for the template: - -`{{.Context}} :: {{if .Namespace}}{{.Namespace}}{{else}}default{{end}}` - -[go-text-template]: https://golang.org/pkg/text/template/ -[sprig]: https://masterminds.github.io/sprig/ diff --git a/docs/docs/segment-nbgv.mdx b/docs/docs/segment-nbgv.mdx deleted file mode 100644 index 475bb40d8f98..000000000000 --- a/docs/docs/segment-nbgv.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: nbgv -title: Nerdbank.GitVersioning -sidebar_label: Nbgv ---- - -## What - -Display the [Nerdbank.GitVersioning][nbgv] version. - -:::warning -The Nerdbank.GitVersioning CLI can be a bit slow causing the prompt to feel slow. -::: - -## Sample Configuration - -```json -{ - "type": "nbgv", - "style": "powerline", - "powerline_symbol": "", - "foreground": "#ffffff", - "background": "#3a579a", - "properties": { - "prefix": " \uF1D2 " - } -} -``` - -## Properties - -- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the -properties below. Defaults to `{{ .Version }}` - -## Template Properties - -- `.Version`: `string` - the current version -- `.AssemblyVersion`: `string` - the current assembly version -- `.AssemblyInformationalVersion`: `string` - the current assembly informational version -- `.NuGetPackageVersion`: `string` - the current nuget package version -- `.ChocolateyPackageVersion`: `string` - the current chocolatey package version -- `.NpmPackageVersion`: `string` - the current npm package version -- `.SimpleVersion`: `string` - the current simple version - -[nbgv]: https://github.com/dotnet/Nerdbank.GitVersioning -[go-text-template]: https://golang.org/pkg/text/template/ -[sprig]: https://masterminds.github.io/sprig/ diff --git a/docs/docs/segment-node.md b/docs/docs/segment-node.md deleted file mode 100644 index a94511219622..000000000000 --- a/docs/docs/segment-node.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -id: node -title: Node -sidebar_label: Node ---- - -## What - -Display the currently active node version. - -## Sample Configuration - -```json -{ - "type": "node", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#6CA35E", - "properties": { - "prefix": " \uE718 " - } -} -``` - -## Properties - -- display_version: `boolean` - display the node version - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: The segment is always displayed - - `files`: The segment is only displayed when `*.js`, `*.ts`, or `package.json` files are present (default) -- enable_version_mismatch: `boolean` - color the segment when the version in `.nvmrc` doesn't match the -returned node version -- color_background: `boolean` - color the background or foreground for `version_mismatch_color` - defaults to `false` -- version_mismatch_color: `string` [color][colors] - the color to use for `enable_version_mismatch` - defaults to -segment's background or foreground color -- display_package_manager: `boolean` - show whether the current project uses Yarn or NPM - defaults to `false` -- yarn_icon: `string` - the icon/text to display when using Yarn - defaults to ` \uF61A` -- npm_icon: `string` - the icon/text to display when using NPM - defaults to ` \uE71E` diff --git a/docs/docs/segment-os.md b/docs/docs/segment-os.md deleted file mode 100644 index 489b6171e204..000000000000 --- a/docs/docs/segment-os.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -id: os -title: os -sidebar_label: OS ---- - -## What - -Display OS specific info. Defaults to Icon. - -## Sample Configuration - -```json -{ - "type": "os", - "style": "plain", - "foreground": "#26C6DA", - "background": "#546E7A", - "properties": { - "postfix": " \uE0B1", - "macos": "mac" - } -} -``` - -## Properties - -- macos: `string` - the string to use for macOS - defaults to macOS icon - defaults to `\uF179` -- linux: `string` - the icon to use for Linux - defaults to Linux icon - defaults to `\uF17C` -- windows: `string` - the icon to use for Windows - defaults to Windows icon - defaults to `\uE62A` -- wsl: `string` - the string/icon to use for WSL - defaults to `WSL` -- wsl_separator: `string` - the string to use for separating WSL from Linux - defaults to ` - ` -- display_distro_name: `boolean` - display the distro name or icon (for WSL and Linux) - defaults to `false` -- alpine: `string` - the icon to use for Alpine - defaults to Alpine icon - defaults to `\uF300` -- aosc: `string` - the icon to use for Aosc - defaults to Aosc icon - defaults to `\uF301` -- arch: `string` - the icon to use for Arch - defaults to Arch icon - defaults to `\uF303` -- centos: `string` - the icon to use for Centos - defaults to Centos icon - defaults to `\uF304` -- coreos: `string` - the icon to use for Coreos - defaults to Coreos icon - defaults to `\uF305` -- debian: `string` - the icon to use for Debian - defaults to Debian icon - defaults to `\uF306` -- devuan: `string` - the icon to use for Devuan - defaults to Devuan icon - defaults to `\uF307` -- raspbian: `string` - the icon to use for Raspbian - defaults to Raspbian icon - defaults to `\uF315` -- elementary: `string` - the icon to use for Elementary - defaults to Elementary icon - defaults to `\uF309` -- fedora: `string` - the icon to use for Fedora - defaults to Fedora icon - defaults to `\uF30a` -- gentoo: `string` - the icon to use for Gentoo - defaults to Gentoo icon - defaults to `\uF30d` -- mageia: `string` - the icon to use for Mageia - defaults to Mageia icon - defaults to `\uF310` -- manjaro: `string` - the icon to use for Manjaro - defaults to Manjaro icon - defaults to `\uF312` -- mint: `string` - the icon to use for Mint - defaults to Mint icon - defaults to `\uF30e` -- nixos: `string` - the icon to use for Nixos - defaults to Nixos icon - defaults to `\uF313` -- opensuse: `string` - the icon to use for Opensuse - defaults to Opensuse icon - defaults to `\uF314` -- sabayon: `string` - the icon to use for Sabayon - defaults to Sabayon icon - defaults to `\uF317` -- slackware: `string` - the icon to use for Slackware - defaults to Slackware icon - defaults to `\uF319` -- ubuntu: `string` - the icon to use for Ubuntu - defaults to Ubuntu icon - defaults to `\uF31b` - -## Template Properties - -- `.OS`: `string` - the OS platform diff --git a/docs/docs/segment-path.md b/docs/docs/segment-path.md deleted file mode 100644 index 749b8c4c9f05..000000000000 --- a/docs/docs/segment-path.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -id: path -title: Path -sidebar_label: Path ---- - -## What - -Display the current path. - -## Sample Configuration - -```json -{ - "type": "path", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#61AFEF", - "properties": { - "style": "folder", - "mapped_locations": { - "C:\\temp": "\ue799" - } - } -} -``` - -## Properties - -- folder_separator_icon: `string` - the symbol to use as a separator between folders - defaults to platform path separator -- home_icon: `string` - the icon to display when at `$HOME` - defaults to `~` -- folder_icon: `string` - the icon to use as a folder indication - defaults to `..` -- windows_registry_icon: `string` - the icon to display when in the Windows registry - defaults to `\uE0B1` -- style: `enum` - how to display the current path - -- enable_hyperlink: `boolean` - displays an hyperlink for the path - defaults to `false` -- mixed_threshold: `number` - the maximum length of a path segment that will be displayed when using `Mixed` - - defaults to `4` -- stack_count_enabled: `boolean` - displays the stack count when using pushd/popd - defaults to `false` - -## Mapped Locations - -Allows you to override a location with an icon. It validates if the current path **starts with** the value and replaces -it with the icon if there's a match. To avoid issues with nested overrides, Oh my posh will sort the list of mapped -locations before doing a replacement. - -- mapped_locations_enabled: `boolean` - replace known locations in the path with the replacements before applying the -style. defaults to `true` -- mapped_locations: `map[string]string` - custom glyph/text for specific paths (only when `mapped_locations_enabled` -is set to `true`) - -For example, to swap out `C:\Users\Leet\GitHub` with a GitHub icon, you can do the following: - -```json -"mapped_locations": { - "C:\\Users\\Leet\\GitHub": "\uF09B" -} -``` - -## Style - -Style sets the way the path is displayed. Based on previous experience and popular themes, there are 5 flavors. - -- agnoster -- agnoster_full -- agnoster_short -- full -- folder -- mixed - -### Agnoster - -Renders each folder as the `folder_icon` separated by the `folder_separator_icon`. -Only the current folder name is displayed at the end, `$HOME` is replaced by the `home_icon` if you're -inside the `$HOME` location or one of its children. - -### Agnoster Full - -Renders each folder name separated by the `folder_separator_icon`. - -### Agnoster Short - -When more than 1 level deep, it renders one `folder_icon` followed by the name of the current folder separated by the `folder_separator_icon`. - -### Full - -Display `$PWD` as a string. - -### Folder - -Display the name of the current folder. - -### Mixed - -Works like `Agnoster Full`, but for any middle folder short enough it will display its name instead. The maximum length -for the folders to display is governed by the `mixed_threshold` property. diff --git a/docs/docs/segment-posh-git.mdx b/docs/docs/segment-posh-git.mdx deleted file mode 100644 index a7f581b5e5e3..000000000000 --- a/docs/docs/segment-posh-git.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -id: poshgit -title: Posh-Git -sidebar_label: Git (posh-git) ---- - -## What - -Display the [posh-git][posh-git] prompt. - -:::info -This segment only works within Powershell and requires the posh-git module to be installed and imported. -To enable the `posh-git` module, set `$env:POSH_GIT_ENABLED = $true` in your `$PROFILE`. -::: - -## Sample Configuration - -```json -{ - "type": "poshgit", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#0077c2" -} -``` - -[posh-git]: https://github.com/dahlbyk/posh-git diff --git a/docs/docs/segment-python.md b/docs/docs/segment-python.md deleted file mode 100644 index 7768a7f708ce..000000000000 --- a/docs/docs/segment-python.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: python -title: Python -sidebar_label: Python ---- - -## What - -Display the currently active python version and virtualenv. -Supports conda, virtualenv and pyenv. - -## Sample Configuration - -```json -{ - "type": "python", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#100e23", - "background": "#906cff", - "properties": { - "prefix": " \uE235 " - } -} -``` - -## Properties - -- display_virtual_env: `boolean` - show the name of the virtualenv or not - defaults to `true` -- display_default: `boolean` - show the name of the virtualenv when it's default (`system`, `base`) -or not - defaults to `true` -- display_version: `boolean` - display the python version - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: the segment is always displayed - - `files`: the segment is only displayed when `*.py`, `*.ipynb`, `pyproject.toml`, `venv.bak`, `venv`, or `.venv` - files are present (default) - - `environment`: the segment is only displayed when a virtual env is present - - `context`: the segment is only displayed when either `environment` or `files` is active diff --git a/docs/docs/segment-ruby.md b/docs/docs/segment-ruby.md deleted file mode 100644 index 39ab86f33d26..000000000000 --- a/docs/docs/segment-ruby.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -id: ruby -title: Ruby -sidebar_label: Ruby ---- - -## What - -Display the currently active ruby version. - -## Sample Configuration - -```json -{ - "type": "ruby", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#4063D8", - "properties": { - "prefix": " \uE791 " - } -} -``` - -## Properties - -- display_version: `boolean` - display the ruby version - defaults to `true` -- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` -- missing_command_text: `string` - text to display when the command is missing - defaults to empty -- display_mode: `string` - determines when the segment is displayed - - `always`: the segment is always displayed - - `files`: the segment is only displayed when `*.rb`, `Gemfile` or `Rakefile` files are present (default) diff --git a/docs/docs/segment-session.md b/docs/docs/segment-session.md deleted file mode 100644 index 83991d8da589..000000000000 --- a/docs/docs/segment-session.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -id: session -title: Session -sidebar_label: Session ---- - -## What - -Show the current user and host name. - -## Sample Configuration - -```json -{ - "type": "session", - "style": "diamond", - "foreground": "#ffffff", - "background": "#c386f1", - "leading_diamond": "\uE0B6", - "trailing_diamond": "\uE0B0" -} -``` - -## Properties - -- user_info_separator: `string` - text/icon to put in between the user and host name - defaults to `@` -- ssh_icon: `string` - text/icon to display first when in an active SSH session - defaults -to `\uF817 ` -- user_color: `string` [color][colors] - override the foreground color of the user name -- host_color: `string` [color][colors] - override the foreground color of the host name -- display_user: `boolean` - display the user name or not - defaults to `true` -- display_host: `boolean` - display the host name or not - defaults to `true` -- default_user_name: `string` - name of the default user - defaults to empty -- display_default: `boolean` - display the segment or not when the user matches `default_user_name` - defaults -to `true` -- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the -properties below. Only used when a value is set, making the above properties obsolete. - -## Template Properties - -- `.UserName`: `string` - the current user's name -- `.DefaultUserName`: - the default user name (set with the `POSH_SESSION_DEFAULT_USER` env var or `default_user_name` property) -- `.ComputerName`: `string` - the current computer's name -- `.SSHSession`: `boolean` - active SSH session or not -- `.Root`: `boolean` - are you a root/admin user or not - -## Environmnent Variables - -- `POSH_SESSION_DEFAULT_USER` - used to override the hardcoded `default_user_name` property - -[colors]: /docs/configure#colors diff --git a/docs/docs/segment-shell.md b/docs/docs/segment-shell.md deleted file mode 100644 index 2ac87fd412c0..000000000000 --- a/docs/docs/segment-shell.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -id: shell -title: Shell -sidebar_label: Shell ---- - -## What - -Show the current shell name (zsh, powershell, bash, ...). - -## Sample Configuration - -```json -{ - "type": "shell", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#0077c2", - "properties": { - "prefix": " \uFCB5 " - } -} -``` diff --git a/docs/docs/segment-spotify.md b/docs/docs/segment-spotify.md deleted file mode 100644 index 7fbceddf4fbb..000000000000 --- a/docs/docs/segment-spotify.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: spotify -title: Spotify -sidebar_label: Spotify ---- - -## What - -Show the currently playing song in the Spotify MacOS/Windows client. -On Windows, only the playing state is supported (no information when paused/stopped). -On macOS, all states are supported (playing/paused/stopped). -**Be aware this can make the prompt a tad bit slower as it needs to get a response from the Spotify player.** - -## Sample Configuration - -```json -{ - "type": "spotify", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#1BD760", - "properties": { - "prefix": "\uF9C6 ", - "playing_icon": "\uE602 ", - "paused_icon": "\uF8E3 ", - "stopped_icon": "\uF04D ", - "track_separator" : " - " - } -} -``` - -## Properties - -- playing_icon: `string` - text/icon to show when playing - defaults to `\uE602 ` -- paused_icon: `string` - text/icon to show when paused - defaults to `\uF8E3 ` -- stopped_icon: `string` - text/icon to show when stopped - defaults to `\uF04D ` -- track_separator: `string` - text/icon to put between the artist and song name - defaults to ` - ` diff --git a/docs/docs/segment-terraform.md b/docs/docs/segment-terraform.md deleted file mode 100644 index b30c67bae4c8..000000000000 --- a/docs/docs/segment-terraform.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: terraform -title: Terraform Context -sidebar_label: Terraform ---- - -## What - -Display the currently active Terraform Workspace name. - -:::info -This requires a terraform binary in your PATH and will only show in directories that contain a `.terraform` subdirectory -::: - -## Sample Configuration - -```json -{ - "type": "terraform", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#000000", - "background": "#ebcc34" -} -``` diff --git a/docs/docs/segment-text.md b/docs/docs/segment-text.md deleted file mode 100644 index ed411bccc494..000000000000 --- a/docs/docs/segment-text.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -id: text -title: Text -sidebar_label: Text ---- - -## What - -Display text. - -## Sample Configuration - -```json -{ - "type": "text", - "style": "plain", - "foreground": "#E06C75", - "properties": { - "prefix": "", - "text": " \u276F" - } -} -``` - -## Properties - -- text: `string` - text/icon to display. Accepts [coloring foreground][coloring] just like `prefix` and `postfix`. - -[coloring]: /docs/configure#colors diff --git a/docs/docs/segment-time.md b/docs/docs/segment-time.md deleted file mode 100644 index 82517f4a11b5..000000000000 --- a/docs/docs/segment-time.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -id: time -title: Time -sidebar_label: Time ---- - -## What - -Show the current timestamp. - -## Sample Configuration - -```json -{ - "type": "time", - "style": "plain", - "foreground": "#007ACC", - "properties": { - "time_format": "15:04:05" - } -} -``` - -## Properties - -- time_format: `string` - format to use, follows the [golang standard][format] - defaults to `15:04:05` - -[format]: https://yourbasic.org/golang/format-parse-string-time-date-example/ - -- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the - properties below. Only used when a value is set, making the above properties obsolete. - - example: `{{ now | date \"January 02, 2006 15:04:05 PM\" | lower }}` - -## Template Properties - -- `.CurrentDate`: `time` - The time to display(testing purpose) - -### Standard time and date formats - -- January 2, 2006 **Date** -- 01/02/06 -- Jan-02-06 -- 15:04:05 **Time** -- 3:04:05 PM -- Jan _2 15:04:05 **Timestamp** -- Jan _2 15:04:05.000000 **with microseconds** -- 2006-01-02T15:04:05-0700 **ISO 8601 (RFC 3339)** -- 2006-01-02 -- 15:04:05 -- 02 Jan 06 15:04 MST **RFC 822** -- 02 Jan 06 15:04 -0700 **with numeric zone** -- Mon, 02 Jan 2006 15:04:05 MST 27e95cb -- Mon, 02 Jan 2006 15:04:05 -0700 **with numeric zone** - -#### The following predefined date and timestamp format constants are also available - -- ANSIC = "Mon Jan _2 15:04:05 2006" -- UnixDate = "Mon Jan _2 15:04:05 MST 2006" -- RubyDate = "Mon Jan 02 15:04:05 -0700 2006" -- RFC822 = "02 Jan 06 15:04 MST" -- RFC822Z = "02 Jan 06 15:04 -0700" -- RFC850 = "Monday, 02-Jan-06 15:04:05 MST" -- RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" -- RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" -- RFC3339 = "2006-01-02T15:04:05Z07:00" -- RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" -- Kitchen = "3:04PM" -// Useful time stamps. -- Stamp = "Jan _2 15:04:05" -- StampMilli = "Jan _2 15:04:05.000" -- StampMicro = "Jan _2 15:04:05.000000" -- StampNano = "Jan _2 15:04:05.000000000" diff --git a/docs/docs/segment-ytm.md b/docs/docs/segment-ytm.md deleted file mode 100644 index a79e41afee49..000000000000 --- a/docs/docs/segment-ytm.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -id: ytm -title: YouTube Music -sidebar_label: YouTube Music ---- - -## What - -Shows the currently playing song in the [YouTube Music Desktop App](https://github.com/ytmdesktop/ytmdesktop). - -**NOTE**: You **must** enable Remote Control in YTMDA for this segment to work: `Settings > Integrations > Remote Control` - -It is fine if `Protect remote control with password` is automatically enabled. This segment does not require the -Remote Control password. - -## Sample Configuration - -```json -{ - "type": "ytm", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#FF0000", - "properties": { - "prefix": "\uF16A ", - "playing_icon": "\uE602 ", - "paused_icon": "\uF8E3 ", - "stopped_icon": "\uF04D ", - "track_separator" : " - " - } -} -``` - -## Properties - -- playing_icon: `string` - text/icon to show when playing - defaults to `\uE602 ` -- paused_icon: `string` - text/icon to show when paused - defaults to `\uF8E3 ` -- stopped_icon: `string` - text/icon to show when paused - defaults to `\uF04D ` -- track_separator: `string` - text/icon to put between the artist and song name - defaults to ` - ` -- api_url: `string` - the YTMDA Remote Control API URL- defaults to `http://127.0.0.1:9863` diff --git a/docs/docs/share-theme.mdx b/docs/docs/share-theme.mdx deleted file mode 100644 index 389ac3302d2d..000000000000 --- a/docs/docs/share-theme.mdx +++ /dev/null @@ -1,54 +0,0 @@ ---- -id: share -title: Share theme -sidebar_label: 📸 Share theme ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -You can export your prompt to an image which you can share online. You have the ability to align it correctly and add your name for credits too. - -:::warning -Some glyphs aren't rendered correctly, that's not you but the limitations of the renderer. Depending on your config, you might have to tweak -the output a little bit. -::: - - - - -You can make use of the `Export-PoshImage` function to export your current theme configuration. - -```powershell -Export-PoshImage -CursorPadding 50 -``` - -There are a couple of parameters you can use to tweak the image rendering: - -- `CursorPadding`: spaces to add after the cursor indication (`_`) -- `RPromptOffset`: spaces to add **before** a block that's right aligned -- `Author`: the name of the creator, added after `https://ohmyposh.dev` - - - - -The oh-my-posh executable has the `--export-png` switch to export your current theme configuration. - -```powershell -oh-my-posh --config ~/.mytheme.omp.json --export-png --cursor-padding 50 -``` - -There are a couple of additional switches you can use to tweak the image rendering: - -- `--cursor-padding`: spaces to add after the cursor indication (`_`) -- `--rprompt-offset`: spaces to add **before** a block that's right aligned -- `--author`: the name of the creator, added after `https://ohmyposh.dev` - - - diff --git a/docs/docs/upgrading.md b/docs/docs/upgrading.md deleted file mode 100644 index 5b90c45a5e5b..000000000000 --- a/docs/docs/upgrading.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -id: upgrading -title: Upgrading -sidebar_label: 🤘 Upgrading from V2 ---- - -Just like V2, V3 is available in the [PowerShell gallery][psgallery]. Due to its nature, it's only -available as a prerelease while we work out the kinks and get it on par with V2. - -## V2's problem statement - -V2 has Powershell module files as [themes][themesv2]. That way of working was inspired from [oh-my-zsh][omz] and other -prompt rendering tools, but that approach has a few important downsides. - -- hard to extend/adjust when you're not proficient -- the need to expose a lot of functions/settings to allow ease of personalization -- limited to Powershell - -## Enter V3 - -This bring us to the first change, to allow a cross platform experience, [Oh My Posh V3][v3] is written entirely in [Go][golang]. -That way, cross platform binaries can be shipped which render the same prompt using the same config anywhere. - -The configuration is changed from `$ThemeSettings` towards a `.json` file that only contains the configuration for the -blocks and segments you want to render. See [concept][introduction] for more context on that part. - -Let's have a look at the V2 commands and how to move towards V3. - -## Import - -Stays the same! Alright. All you need to do is update oh-my-posh. - -```powershell -Update-Module -Name oh-my-posh -Scope CurrentUser -``` - -## Configuration - -Here we have a few options. If you're using an out-of-the box theme, you can simply change the current command to the -new one, provided your V2 theme has already been added to [V3][themesv3]. - -### I use an out-of-the-box theme - -Change the current prompt setting function to the new one. - -```powershell -# Set-Theme Agnoster -Set-PoshPrompt -Theme agnoster -``` - -### I use a custom theme/settings - -The first thing to do is to look for the theme you based your theme on. -If you don't remember which one, preview them all and take the one closest to yours. - -```powershell -Get-PoshThemes -``` - -If you see one you like, set it, then export its config so you can customize/extend the blocks and segments. - -```powershell -Set-PoshPrompt -Theme jandedobbeleer -Export-PoshTheme -FilePath ~/.oh-my-posh.omp.json -``` - -Adjust the config (`~/.oh-my-posh.omp.json`) to your liking by going through the [configuration][configuration] guide. -Set your custom theme and enjoy. - -```powershell -Set-PoshPrompt -Theme ~/.oh-my-posh.omp.json -``` - -### I have no idea just yet - -Great! There's an option for that too. You can easily list all available themes and pick the one you like best. - -```powershell -Get-PoshThemes -``` - -Choose and set the one you like. - -```powershell -Set-PoshPrompt -Theme jandedobbeleer -``` - -## All set, now what - -You can either tweak the theme to your liking, add segments or [submit an issue][issues] for new functionality. -Do not hesitate to [ask for assistance][issues] when you notice an issue or unexpected behavior. - -[psgallery]: https://www.powershellgallery.com/packages/oh-my-posh -[themesv2]: https://github.com/JanDeDobbeleer/oh-my-posh/tree/master/Themes -[omz]: https://github.com/ohmyzsh/ohmyzsh -[golang]: https://golang.org/ -[introduction]: /docs/#concept -[v3]: https://github.com/JanDeDobbeleer/oh-my-posh/ -[themesv3]: https://github.com/JanDeDobbeleer/oh-my-posh/tree/main/themes -[configuration]: /docs/configure -[issues]: https://github.com/JanDeDobbeleer/oh-my-posh/issues/new diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js deleted file mode 100644 index e6af3b66ddaf..000000000000 --- a/docs/docusaurus.config.js +++ /dev/null @@ -1,110 +0,0 @@ -module.exports = { - title: "Oh My Posh", - tagline: "A prompt theme engine for any shell.", - url: "https://ohmyposh.dev", - baseUrl: "/", - favicon: "img/favicon.ico", - organizationName: "jandedobbeleer", - projectName: "oh-my-posh", - onBrokenLinks: "ignore", - themeConfig: { - sidebarCollapsible: false, - prism: { - theme: require("prism-react-renderer/themes/duotoneLight"), - darkTheme: require("prism-react-renderer/themes/oceanicNext"), - }, - navbar: { - title: "Oh My Posh", - logo: { - alt: "Oh My Posh Logo", - src: "img/logo.svg", - }, - items: [ - { - to: "docs/", - activeBasePath: "docs", - label: "Docs", - position: "left", - }, - { - href: "https://github.com/sponsors/JanDeDobbeleer", - label: "Sponsor", - position: "left", - }, - { - href: "https://www.gitkraken.com/invite/nQmDPR9D", - label: "GitKraken", - position: "left", - }, - { - href: "https://github.com/jandedobbeleer/oh-my-posh", - label: "GitHub", - position: "right", - }, - ], - }, - footer: { - style: "dark", - links: [ - { - title: "How to", - items: [ - { - label: "Getting started", - to: "docs/", - }, - { - label: "Contributing", - to: "docs/contributing_started", - }, - ], - }, - { - title: "Social", - items: [ - { - label: "GitHub", - href: "https://github.com/jandedobbeleer/oh-my-posh", - }, - { - label: "Twitter", - href: "https://twitter.com/jandedobbeleer", - }, - ], - }, - { - title: "Links", - items: [ - { - label: "Sponsor", - href: "https://github.com/sponsors/JanDeDobbeleer", - }, - { - label: "GitKraken", - href: "https://www.gitkraken.com/invite/nQmDPR9D", - }, - { - label: "Docusaurus", - href: "https://github.com/facebook/docusaurus", - }, - ], - }, - ], - copyright: `Copyright © ${new Date().getFullYear()} Jan De Dobbeleer.`, - }, - }, - presets: [ - [ - "@docusaurus/preset-classic", - { - docs: { - sidebarPath: require.resolve("./sidebars.js"), - editUrl: "https://github.com/jandedobbeleer/oh-my-posh/edit/main/docs/", - }, - theme: { - customCss: require.resolve("./src/css/custom.css"), - }, - }, - ], - ], -}; diff --git a/docs/export_themes.js b/docs/export_themes.js deleted file mode 100644 index b79056920b59..000000000000 --- a/docs/export_themes.js +++ /dev/null @@ -1,104 +0,0 @@ -//jshint esversion:8 -//jshint node:true -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const exec = util.promisify(require('child_process').exec); - -const themesConfigDir = "./../themes"; -const themesStaticDir = "./static/img/themes"; - -function newThemeConfig(rpromptOffset = 40, cursorPadding = 30, author = "") { - var config = { - rpromptOffset: rpromptOffset, - cursorPadding: cursorPadding, - author: author - }; - return config; -} - -let themeConfigOverrrides = new Map(); -themeConfigOverrrides.set('agnoster.omp.json', newThemeConfig(40, 40)); -themeConfigOverrrides.set('agnosterplus.omp.json', newThemeConfig(80)); -themeConfigOverrrides.set('avit.omp.json', newThemeConfig(40, 80)); -themeConfigOverrrides.set('blueish.omp.json', newThemeConfig(40, 100)); -themeConfigOverrrides.set('cert.omp.json', newThemeConfig(40, 50)); -themeConfigOverrrides.set('cinnamon.omp.json', newThemeConfig(40, 80)); -themeConfigOverrrides.set('darkblood.omp.json', newThemeConfig(40, 40)); -themeConfigOverrrides.set('honukai.omp.json', newThemeConfig(20)); -themeConfigOverrrides.set('hotstick.minimal.omp.json', newThemeConfig(40, 10)); -themeConfigOverrrides.set('huvix.omp.json', newThemeConfig(40, 70)); -themeConfigOverrrides.set('jandedobbeleer.omp.json', newThemeConfig(40, 15)); -themeConfigOverrrides.set('lambda.omp.json', newThemeConfig(40, 40)); -themeConfigOverrrides.set('marcduiker.omp.json', newThemeConfig(0, 40)); -themeConfigOverrrides.set('material.omp.json', newThemeConfig(40, 40)); -themeConfigOverrrides.set('microverse-power.omp.json', newThemeConfig(40, 100)); -themeConfigOverrrides.set('negligible.omp.json', newThemeConfig(10)); -themeConfigOverrrides.set('paradox.omp.json', newThemeConfig(40, 100)); -themeConfigOverrrides.set('powerlevel10k_classic.omp.json', newThemeConfig(10)); -themeConfigOverrrides.set('powerlevel10k_lean.omp.json', newThemeConfig(80)); -themeConfigOverrrides.set('powerline.omp.json', newThemeConfig(40, 40)); -themeConfigOverrrides.set('pure.omp.json', newThemeConfig(40, 80)); -themeConfigOverrrides.set('remk.omp.json', newThemeConfig(40, 40)); -themeConfigOverrrides.set('robbyrussel.omp.json', newThemeConfig(40, 40)); -themeConfigOverrrides.set('slim.omp.json', newThemeConfig(10, 80)); -themeConfigOverrrides.set('slimfat.omp.json', newThemeConfig(10, 93)); -themeConfigOverrrides.set('space.omp.json', newThemeConfig(40, 40)); -themeConfigOverrrides.set('spaceship.omp.json', newThemeConfig(40, 40)); -themeConfigOverrrides.set('star.omp.json', newThemeConfig(40, 70)); -themeConfigOverrrides.set('stelbent.minimal.omp.json', newThemeConfig(70)); -themeConfigOverrrides.set('tonybaloney.omp.json', newThemeConfig(0,40)); -themeConfigOverrrides.set('ys.omp.json', newThemeConfig(40, 100)); -themeConfigOverrrides.set('zash.omp.json', newThemeConfig(40, 40)); - -(async () => { - const themes = await fs.promises.readdir(themesConfigDir); - let links = new Array(); - - for (const theme of themes) { - if (!theme.endsWith('.omp.json')) { - continue; - } - const configPath = path.join(themesConfigDir, theme); - - let config = newThemeConfig(); - if (themeConfigOverrrides.has(theme)) { - config = themeConfigOverrrides.get(theme); - } - - let poshCommand = `oh-my-posh --config=${configPath} --shell shell --export-png`; - poshCommand += ` --rprompt-offset=${config.rpromptOffset}`; - poshCommand += ` --cursor-padding=${config.cursorPadding}`; - if (config.author !== '') { - poshCommand += ` --author=${config.author}`; - } - - const { _, stderr } = await exec(poshCommand); - - if (stderr !== '') { - console.error(`Unable to create image for ${theme}, please try manually`); - continue; - } - - const image = theme.replace('.omp.json', '.png'); - const toPath = path.join(themesStaticDir, image); - - await fs.promises.rename(image, toPath); - - const themeName = theme.replace('.omp.json', ''); - - const themeData = ` -### [${themeName}] - -[![${themeName}](/img/themes/${themeName}.png)][${themeName}] -`; - - await fs.promises.appendFile('./docs/themes.md', themeData); - - links.push(`[${themeName}]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/${theme} '${themeName}'\n`); - } - - for (const link of links) { - await fs.promises.appendFile('./docs/themes.md', link); - } -})(); diff --git a/docs/package-lock.json b/docs/package-lock.json deleted file mode 100644 index f036be2ae325..000000000000 --- a/docs/package-lock.json +++ /dev/null @@ -1,24067 +0,0 @@ -{ - "name": "website", - "version": "0.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "website", - "version": "0.0.0", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/preset-classic": "2.0.0-beta.0", - "classnames": "^2.3.1", - "react": "^17.0.2", - "react-dom": "^17.0.2" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.0.0-alpha.44", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.0.0-alpha.44.tgz", - "integrity": "sha512-2iMXthldMIDXtlbg9omRKLgg1bLo2ZzINAEqwhNjUeyj1ceEyL1ck6FY0VnJpf2LsjmNthHCz2BuFk+nYUeDNA==", - "dependencies": { - "@algolia/autocomplete-shared": "1.0.0-alpha.44" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.0.0-alpha.44", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.0.0-alpha.44.tgz", - "integrity": "sha512-DCHwo5ovzg9k2ejUolGNTLFnIA7GpsrkbNJTy1sFbMnYfBmeK8egZPZnEl7lBTr27OaZu7IkWpTepLVSztZyng==", - "dependencies": { - "@algolia/autocomplete-shared": "1.0.0-alpha.44" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.0.0-alpha.44", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.0.0-alpha.44.tgz", - "integrity": "sha512-2oQZPERYV+yNx/yoVWYjZZdOqsitJ5dfxXJjL18yczOXH6ujnsq+DTczSrX+RjzjQdVeJ1UAG053EJQF/FOiMg==" - }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.9.1.tgz", - "integrity": "sha512-bAUU9vKCy45uTTlzJw0LYu1IjoZsmzL6lgjaVFaW1crhX/4P+JD5ReQv3n/wpiXSFaHq1WEO3WyH2g3ymzeipQ==", - "dependencies": { - "@algolia/cache-common": "4.9.1" - } - }, - "node_modules/@algolia/cache-common": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.9.1.tgz", - "integrity": "sha512-tcvw4mOfFy44V4ZxDEy9wNGr6vFROZKRpXKTEBgdw/WBn6mX51H1ar4RWtceDEcDU4H5fIv5tsY3ip2hU+fTPg==" - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.9.1.tgz", - "integrity": "sha512-IEJrHonvdymW2CnRfJtsTVWyfAH05xPEFkGXGCw00+6JNCj8Dln3TeaRLiaaY1srlyGedkemekQm1/Xb46CGOQ==", - "dependencies": { - "@algolia/cache-common": "4.9.1" - } - }, - "node_modules/@algolia/client-account": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.9.1.tgz", - "integrity": "sha512-Shpjeuwb7i2LR5QuWREb6UbEQLGB+Pl/J5+wPgILJDP/uWp7jpl0ase9mYNQGKj7TjztpSpQCPZ3dSHPnzZPfw==", - "dependencies": { - "@algolia/client-common": "4.9.1", - "@algolia/client-search": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.9.1.tgz", - "integrity": "sha512-/g6OkOSIA+A0t/tjvbL6iG/zV4El4LPFgv/tcAYHTH27BmlNtnEXw+iFpGjeUlQoPily9WVB3QNLMJkaNwL3HA==", - "dependencies": { - "@algolia/client-common": "4.9.1", - "@algolia/client-search": "4.9.1", - "@algolia/requester-common": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "node_modules/@algolia/client-common": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.9.1.tgz", - "integrity": "sha512-UziRTZ8km3qwoVPIyEre8TV6V+MX7UtbfVqPmSafZ0xu41UUZ+sL56YoKjOXkbKuybeIC9prXMGy/ID5bXkTqg==", - "dependencies": { - "@algolia/requester-common": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "node_modules/@algolia/client-recommendation": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-recommendation/-/client-recommendation-4.9.1.tgz", - "integrity": "sha512-Drtvvm1PNIOpYf4HFlkPFstFQ3IsN+TRmxur2F7y6Faplb5ybISa8ithu1tmlTdyTf3A78hQUQjgJet6qD2XZw==", - "dependencies": { - "@algolia/client-common": "4.9.1", - "@algolia/requester-common": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "node_modules/@algolia/client-search": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.9.1.tgz", - "integrity": "sha512-r9Cw2r8kJr45iYncFDht6EshARghU265wuY8Q8oHrpFHjAziEYdsUOdNmQKbsSH5J3gLjDPx1EI5DzVd6ivn3w==", - "dependencies": { - "@algolia/client-common": "4.9.1", - "@algolia/requester-common": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "node_modules/@algolia/logger-common": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.9.1.tgz", - "integrity": "sha512-9mPrbFlFyPT7or/7PXTiJjyOewWB9QRkZKVXkt5zHAUiUzGxmmdpJIGpPv3YQnDur8lXrXaRI0MHXUuIDMY1ng==" - }, - "node_modules/@algolia/logger-console": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.9.1.tgz", - "integrity": "sha512-74VUwjtFjFpjZpi3QoHIPv0kcr3vWUSHX/Vs8PJW3lPsD4CgyhFenQbG9v+ZnyH0JrJwiYTtzfmrVh7IMWZGrQ==", - "dependencies": { - "@algolia/logger-common": "4.9.1" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.9.1.tgz", - "integrity": "sha512-zc46tk5o0ikOAz3uYiRAMxC2iVKAMFKT7nNZnLB5IzT0uqAh7pz/+D/UvIxP4bKmsllpBSnPcpfQF+OI4Ag/BA==", - "dependencies": { - "@algolia/requester-common": "4.9.1" - } - }, - "node_modules/@algolia/requester-common": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.9.1.tgz", - "integrity": "sha512-9hPgXnlCSbqJqF69M5x5WN3h51Dc+mk/iWNeJSVxExHGvCDfBBZd0v6S15i8q2a9cD1I2RnhMpbnX5BmGtabVA==" - }, - "node_modules/@algolia/requester-node-http": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.9.1.tgz", - "integrity": "sha512-vYNVbSCuyrCSCjHBQJk+tLZtWCjvvDf5tSbRJjyJYMqpnXuIuP7gZm24iHil4NPYBhbBj5NU2ZDAhc/gTn75Ag==", - "dependencies": { - "@algolia/requester-common": "4.9.1" - } - }, - "node_modules/@algolia/transporter": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.9.1.tgz", - "integrity": "sha512-AbjFfGzX+cAuj7Qyc536OxIQzjFOA5FU2ANGStx8LBH+AKXScwfkx67C05riuaRR5adSCLMSEbVvUscH0nF+6A==", - "dependencies": { - "@algolia/cache-common": "4.9.1", - "@algolia/logger-common": "4.9.1", - "@algolia/requester-common": "4.9.1" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==" - }, - "node_modules/@babel/core": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.2.tgz", - "integrity": "sha512-OgC1mON+l4U4B4wiohJlQNUU3H73mpTyYY3j/c8U9dr9UagGGSm+WFpzjy/YLdoyjiG++c1kIDgxCo/mLwQJeQ==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.2", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.2.tgz", - "integrity": "sha512-OnADYbKrffDVai5qcpkMxQ7caomHOoEwjkouqnN2QhydAjowFAZcsdecFIRUBdb+ZcruwYE4ythYmF1UBZU5xQ==", - "dependencies": { - "@babel/types": "^7.14.2", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", - "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", - "dependencies": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", - "semver": "^6.3.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.2.tgz", - "integrity": "sha512-6YctwVsmlkchxfGUogvVrrhzyD3grFJyluj5JgDlQrwfMLJSt5tdAzFZfPf4H2Xoi5YLcQ6BxfJlaOBHuctyIw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz", - "integrity": "sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "regexpu-core": "^4.7.1" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", - "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz", - "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==", - "dependencies": { - "@babel/types": "^7.13.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz", - "integrity": "sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==", - "dependencies": { - "@babel/traverse": "^7.13.15", - "@babel/types": "^7.13.16" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", - "dependencies": { - "@babel/types": "^7.13.12" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", - "dependencies": { - "@babel/types": "^7.13.12" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", - "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", - "dependencies": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==" - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz", - "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-wrap-function": "^7.13.0", - "@babel/types": "^7.13.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", - "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", - "dependencies": { - "@babel/types": "^7.13.12" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", - "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", - "dependencies": { - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==" - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==" - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz", - "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==", - "dependencies": { - "@babel/helper-function-name": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", - "dependencies": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.2.tgz", - "integrity": "sha512-IoVDIHpsgE/fu7eXBeRWt8zLbDrSvD7H1gpomOkPpBoEN8KCruCqSDdqo8dddwQQrui30KSvQBaMUOJiuFu6QQ==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz", - "integrity": "sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.13.12" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.2.tgz", - "integrity": "sha512-b1AM4F6fwck4N8ItZ/AtC4FP/cqZqmKRQ4FaTDutwSYyjuhtvsGEMLK4N/ztV/ImP40BjIDyMgBQAeAMsQYVFQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-remap-async-to-generator": "^7.13.0", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz", - "integrity": "sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.13.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.13.11.tgz", - "integrity": "sha512-fJTdFI4bfnMjvxJyNuaf8i9mVcZ0UhetaGEUHaHV9KEnibLugJkZAtXikR8KcYj+NYmI4DZMS8yQAyg+hvfSqg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-class-static-block": "^7.12.13" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.2.tgz", - "integrity": "sha512-oxVQZIWFh91vuNEMKltqNsKLFWkOIyJc95k2Gv9lWVyDfPUQGSSlbDEgWuJUU1afGE9WwlzpucMZ3yDRHIItkA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.2.tgz", - "integrity": "sha512-sRxW3z3Zp3pFfLAgVEvzTFutTXax837oOatUIvSG9o5gRj9mKwm3br1Se5f4QalTQs9x4AzlA/HrCWbQIHASUQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.2.tgz", - "integrity": "sha512-w2DtsfXBBJddJacXMBhElGEYqCZQqN99Se1qeYn8DVLB33owlrlLftIbMzn5nz1OITfDVknXF433tBrLEAOEjA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.2.tgz", - "integrity": "sha512-1JAZtUrqYyGsS7IDmFeaem+/LJqujfLZ2weLR9ugB0ufUPjzf8cguyVT1g5im7f7RXxuLq1xUxEzvm68uYRtGg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.2.tgz", - "integrity": "sha512-ebR0zU9OvI2N4qiAC38KIAK75KItpIPTpAtd2r4OZmMFeKbKJpUFLYP2EuDut82+BmYi8sz42B+TfTptJ9iG5Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.2.tgz", - "integrity": "sha512-DcTQY9syxu9BpU3Uo94fjCB3LN9/hgPS8oUL7KrSW3bA2ePrKZZPJcc5y0hoJAM9dft3pGfErtEUvxXQcfLxUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.2.tgz", - "integrity": "sha512-hBIQFxwZi8GIp934+nj5uV31mqclC1aYDhctDu5khTi9PCCUOczyy0b34W0oE9U/eJXiqQaKyVsmjeagOaSlbw==", - "dependencies": { - "@babel/compat-data": "^7.14.0", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.14.2" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.2.tgz", - "integrity": "sha512-XtkJsmJtBaUbOxZsNk0Fvrv8eiqgneug0A6aqLFZ4TSkar2L5dSXWcnUKHgmjJt49pyB/6ZHvkr3dPgl9MOWRQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.2.tgz", - "integrity": "sha512-qQByMRPwMZJainfig10BoaDldx/+VDtNcrA7qdNaEOAj6VXud+gfrkA8j4CRAU5HjnWREXqIpSpH30qZX1xivA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", - "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.0.tgz", - "integrity": "sha512-59ANdmEwwRUkLjB7CRtwJxxwtjESw+X2IePItA+RGQh+oy5RmpCh/EvVVvh5XQc3yxsm5gtv0+i9oBZhaDNVTg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-create-class-features-plugin": "^7.14.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-private-property-in-object": "^7.14.0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", - "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.12.13.tgz", - "integrity": "sha512-ZmKQ0ZXR0nYpHZIIuj9zE7oIqCx2hw9TKi+lIo73NNrMPAZGHfS92/VRV0ZmPj6H2ffBgyFHXvJ5NYsNeEaP2A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz", - "integrity": "sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.0.tgz", - "integrity": "sha512-bda3xF8wGl5/5btF794utNOL0Jw+9jE5C1sLZcoK7c4uonE/y3iQiyG+KbkF3WBV/paX58VCpjhxLPkdj5Fe4w==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz", - "integrity": "sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", - "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz", - "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==", - "dependencies": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-remap-async-to-generator": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", - "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.2.tgz", - "integrity": "sha512-neZZcP19NugZZqNwMTH+KoBjx5WyvESPSIOQb4JHpfd+zPfqcH65RMu5xJju5+6q/Y2VzYrleQTr+b6METyyxg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.2.tgz", - "integrity": "sha512-7oafAVcucHquA/VZCsXv/gmuiHeYd64UJyyTYU+MPfNu0KeNlxw06IeENBO8bJjXVbolu+j1MM5aKQtH1OMCNg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "globals": "^11.1.0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz", - "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz", - "integrity": "sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", - "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", - "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", - "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz", - "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", - "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", - "dependencies": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", - "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", - "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.2.tgz", - "integrity": "sha512-hPC6XBswt8P3G2D1tSV2HzdKvkqOpmbyoy+g73JG0qlF/qx2y3KaMmXb1fLrpmWGLZYA0ojCvaHdzFWjlmV+Pw==", - "dependencies": { - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helper-plugin-utils": "^7.13.0", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "node_modules/@babel/plugin-transform-modules-amd/node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.0.tgz", - "integrity": "sha512-EX4QePlsTaRZQmw9BsoPeyh5OCtRGIhwfLquhxGp5e32w+dyL8htOcDwamlitmNFK6xBZYlygjdye9dbd9rUlQ==", - "dependencies": { - "@babel/helper-module-transforms": "^7.14.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-simple-access": "^7.13.12", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs/node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz", - "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==", - "dependencies": { - "@babel/helper-hoist-variables": "^7.13.0", - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-identifier": "^7.12.11", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs/node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.0.tgz", - "integrity": "sha512-nPZdnWtXXeY7I87UZr9VlsWme3Y0cfFFE41Wbxz4bbaexAjNMInXPFUpRRUJ8NoMm0Cw+zxbqjdPmLhcjfazMw==", - "dependencies": { - "@babel/helper-module-transforms": "^7.14.0", - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", - "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", - "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", - "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.2.tgz", - "integrity": "sha512-NxoVmA3APNCC1JdMXkdYXuQS+EMdqy0vIwyDHeKHiJKRxmp1qGSdb0JLEIoPRhkx6H/8Qi3RJ3uqOCYw8giy9A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", - "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.13.13.tgz", - "integrity": "sha512-SNJU53VM/SjQL0bZhyU+f4kJQz7bQQajnrZRSaU21hruG/NWY41AEM9AWXeXX90pYr/C2yAmTgI6yW3LlLrAUQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.14.2.tgz", - "integrity": "sha512-zCubvP+jjahpnFJvPaHPiGVfuVUjXHhFvJKQdNnsmSsiU9kR/rCZ41jHc++tERD2zV+p7Hr6is+t5b6iWTCqSw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.13.12.tgz", - "integrity": "sha512-jcEI2UqIcpCqB5U5DRxIl0tQEProI2gcu+g8VTIqxLO5Iidojb4d77q+fwGseCvd8af/lJ9masp4QWzBXFE2xA==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/types": "^7.13.12" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.17.tgz", - "integrity": "sha512-BPjYV86SVuOaudFhsJR1zjgxxOhJDt6JHNoD48DxWEIxUCAMjV1ys6DYw4SDYZh0b1QsS2vfIA9t/ZsQGsDOUQ==", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.12.17" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", - "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", - "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", - "dependencies": { - "regenerator-transform": "^0.14.2" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", - "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.14.2.tgz", - "integrity": "sha512-LyA2AiPkaYzI7G5e2YI4NCasTfFe7mZvlupNprDOB7CdNUHb2DQC4uV6oeZ0396gOcicUzUCh0MShL6wiUgk+Q==", - "dependencies": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-plugin-utils": "^7.13.0", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", - "semver": "^6.3.0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", - "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", - "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", - "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz", - "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", - "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz", - "integrity": "sha512-elQEwluzaU8R8dbVuW2Q2Y8Nznf7hnjM7+DSCd14Lo5fF63C9qNLbwZYbmZrtV9/ySpSUpkRpQXvJb6xyu4hCQ==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-typescript": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", - "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", - "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.2.tgz", - "integrity": "sha512-7dD7lVT8GMrE73v4lvDEb85cgcQhdES91BSD7jS/xjC6QY8PnRhux35ac+GCpbiRhp8crexBvZZqnaL6VrY8TQ==", - "dependencies": { - "@babel/compat-data": "^7.14.0", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", - "@babel/plugin-proposal-async-generator-functions": "^7.14.2", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-class-static-block": "^7.13.11", - "@babel/plugin-proposal-dynamic-import": "^7.14.2", - "@babel/plugin-proposal-export-namespace-from": "^7.14.2", - "@babel/plugin-proposal-json-strings": "^7.14.2", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.2", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.2", - "@babel/plugin-proposal-numeric-separator": "^7.14.2", - "@babel/plugin-proposal-object-rest-spread": "^7.14.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.14.2", - "@babel/plugin-proposal-optional-chaining": "^7.14.2", - "@babel/plugin-proposal-private-methods": "^7.13.0", - "@babel/plugin-proposal-private-property-in-object": "^7.14.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.12.13", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.0", - "@babel/plugin-syntax-top-level-await": "^7.12.13", - "@babel/plugin-transform-arrow-functions": "^7.13.0", - "@babel/plugin-transform-async-to-generator": "^7.13.0", - "@babel/plugin-transform-block-scoped-functions": "^7.12.13", - "@babel/plugin-transform-block-scoping": "^7.14.2", - "@babel/plugin-transform-classes": "^7.14.2", - "@babel/plugin-transform-computed-properties": "^7.13.0", - "@babel/plugin-transform-destructuring": "^7.13.17", - "@babel/plugin-transform-dotall-regex": "^7.12.13", - "@babel/plugin-transform-duplicate-keys": "^7.12.13", - "@babel/plugin-transform-exponentiation-operator": "^7.12.13", - "@babel/plugin-transform-for-of": "^7.13.0", - "@babel/plugin-transform-function-name": "^7.12.13", - "@babel/plugin-transform-literals": "^7.12.13", - "@babel/plugin-transform-member-expression-literals": "^7.12.13", - "@babel/plugin-transform-modules-amd": "^7.14.2", - "@babel/plugin-transform-modules-commonjs": "^7.14.0", - "@babel/plugin-transform-modules-systemjs": "^7.13.8", - "@babel/plugin-transform-modules-umd": "^7.14.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", - "@babel/plugin-transform-new-target": "^7.12.13", - "@babel/plugin-transform-object-super": "^7.12.13", - "@babel/plugin-transform-parameters": "^7.14.2", - "@babel/plugin-transform-property-literals": "^7.12.13", - "@babel/plugin-transform-regenerator": "^7.13.15", - "@babel/plugin-transform-reserved-words": "^7.12.13", - "@babel/plugin-transform-shorthand-properties": "^7.12.13", - "@babel/plugin-transform-spread": "^7.13.0", - "@babel/plugin-transform-sticky-regex": "^7.12.13", - "@babel/plugin-transform-template-literals": "^7.13.0", - "@babel/plugin-transform-typeof-symbol": "^7.12.13", - "@babel/plugin-transform-unicode-escapes": "^7.12.13", - "@babel/plugin-transform-unicode-regex": "^7.12.13", - "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.14.2", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", - "core-js-compat": "^3.9.0", - "semver": "^6.3.0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.13.13.tgz", - "integrity": "sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-transform-react-display-name": "^7.12.13", - "@babel/plugin-transform-react-jsx": "^7.13.12", - "@babel/plugin-transform-react-jsx-development": "^7.12.17", - "@babel/plugin-transform-react-pure-annotations": "^7.12.1" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.13.0.tgz", - "integrity": "sha512-LXJwxrHy0N3f6gIJlYbLta1D9BDtHpQeqwzM0LIfjDlr6UE/D5Mc7W4iDiQzaE+ks0sTjT26ArcHWnJVt0QiHw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-transform-typescript": "^7.13.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz", - "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.0.tgz", - "integrity": "sha512-0R0HTZWHLk6G8jIk0FtoX+AatCtKnswS98VhXwGImFc759PJRp4Tru0PQYZofyijTFUr+gT8Mu7sgXVJLQ0ceg==", - "dependencies": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "node_modules/@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.0", - "to-fast-properties": "^2.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.0.0-alpha.36", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.0.0-alpha.36.tgz", - "integrity": "sha512-zSN2SXuZPDqQaSFzYa1kOwToukqzhLHG7c66iO+/PlmWb6/RZ5cjTkG6VCJynlohRWea7AqZKWS/ptm8kM2Dmg==" - }, - "node_modules/@docsearch/react": { - "version": "3.0.0-alpha.36", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.0.0-alpha.36.tgz", - "integrity": "sha512-synYZDHalvMzesFiy7kK+uoz4oTdWSTbe2cU+iiUjwFMyQ+WWjWwGVnvcvk+cjj9pRCVaZo5y5WpqNXq1j8k9Q==", - "dependencies": { - "@algolia/autocomplete-core": "1.0.0-alpha.44", - "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.44", - "@docsearch/css": "3.0.0-alpha.36", - "algoliasearch": "^4.0.0" - } - }, - "node_modules/@docusaurus/core": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.0-beta.0.tgz", - "integrity": "sha512-xWwpuEwFRKJmZvNGOpr/dyRDnx/psckLPsozQTg2hu3u81Wqu9gigWgYK/C2fPlEjxMcVw0/2WH+zwpbyWmF2Q==", - "dependencies": { - "@babel/core": "^7.12.16", - "@babel/generator": "^7.12.15", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.12.15", - "@babel/preset-env": "^7.12.16", - "@babel/preset-react": "^7.12.13", - "@babel/preset-typescript": "^7.12.16", - "@babel/runtime": "^7.12.5", - "@babel/runtime-corejs3": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@docusaurus/cssnano-preset": "2.0.0-beta.0", - "@docusaurus/react-loadable": "5.5.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "@endiliey/static-site-generator-webpack-plugin": "^4.0.0", - "@svgr/webpack": "^5.5.0", - "autoprefixer": "^10.2.5", - "babel-loader": "^8.2.2", - "babel-plugin-dynamic-import-node": "2.3.0", - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "chokidar": "^3.5.1", - "clean-css": "^5.1.1", - "commander": "^5.1.0", - "copy-webpack-plugin": "^8.1.0", - "core-js": "^3.9.1", - "css-loader": "^5.1.1", - "css-minimizer-webpack-plugin": "^2.0.0", - "cssnano": "^5.0.1", - "del": "^6.0.0", - "detect-port": "^1.3.0", - "eta": "^1.12.1", - "express": "^4.17.1", - "file-loader": "^6.2.0", - "fs-extra": "^9.1.0", - "github-slugger": "^1.3.0", - "globby": "^11.0.2", - "html-minifier-terser": "^5.1.1", - "html-tags": "^3.1.0", - "html-webpack-plugin": "^5.2.0", - "import-fresh": "^3.3.0", - "is-root": "^2.1.0", - "leven": "^3.1.0", - "lodash": "^4.17.20", - "mini-css-extract-plugin": "^1.4.0", - "module-alias": "^2.2.2", - "nprogress": "^0.2.0", - "postcss": "^8.2.10", - "postcss-loader": "^5.2.0", - "prompts": "^2.4.0", - "react-dev-utils": "^11.0.1", - "react-error-overlay": "^6.0.9", - "react-helmet": "^6.1.0", - "react-loadable": "^5.5.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.2.0", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.2.0", - "resolve-pathname": "^3.0.0", - "rtl-detect": "^1.0.2", - "semver": "^7.3.4", - "serve-handler": "^6.1.3", - "shelljs": "^0.8.4", - "std-env": "^2.2.1", - "strip-ansi": "^6.0.0", - "terser-webpack-plugin": "^5.1.1", - "tslib": "^2.1.0", - "update-notifier": "^5.1.0", - "url-loader": "^4.1.1", - "wait-on": "^5.2.1", - "webpack": "^5.28.0", - "webpack-bundle-analyzer": "^4.4.0", - "webpack-dev-server": "^3.11.2", - "webpack-merge": "^5.7.3", - "webpackbar": "^5.0.0-3" - }, - "bin": { - "docusaurus": "bin/docusaurus.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.0.tgz", - "integrity": "sha512-gqQHeQCDHZDd5NaiKZwDiyg75sBCqDyAsvmFukkDAty8xE7u9IhzbOQKvCAtwseuvzu2BNN41gnJ8bz7vZzQiw==", - "dependencies": { - "cssnano-preset-advanced": "^5.0.0", - "postcss": "^8.2.10", - "postcss-sort-media-queries": "^3.8.9" - } - }, - "node_modules/@docusaurus/mdx-loader": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.0.tgz", - "integrity": "sha512-oQLS2ZeUnqw79CV37glglZpaYgFfA5Az5lT83m5tJfMUZjoK4ehG1XWBeUzWy8QQNI452yAID8jz8jihEQeCcw==", - "dependencies": { - "@babel/parser": "^7.12.16", - "@babel/traverse": "^7.12.13", - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@mdx-js/mdx": "^1.6.21", - "@mdx-js/react": "^1.6.21", - "escape-html": "^1.0.3", - "file-loader": "^6.2.0", - "fs-extra": "^9.1.0", - "github-slugger": "^1.3.0", - "gray-matter": "^4.0.2", - "mdast-util-to-string": "^2.0.0", - "remark-emoji": "^2.1.0", - "stringify-object": "^3.3.0", - "unist-util-visit": "^2.0.2", - "url-loader": "^4.1.1", - "webpack": "^5.28.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/plugin-content-blog": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.0.tgz", - "integrity": "sha512-lz63i5k/23RJ3Rk/2fIsYAoD8Wua3b5b0AbH2JoOhQu1iAIQiV8m91Z3XALBSzA3nBtAOIweNI7yzWL+JFSTvw==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/mdx-loader": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "chalk": "^4.1.0", - "feed": "^4.2.2", - "fs-extra": "^9.1.0", - "globby": "^11.0.2", - "loader-utils": "^2.0.0", - "lodash": "^4.17.20", - "reading-time": "^1.3.0", - "remark-admonitions": "^1.2.1", - "tslib": "^2.1.0", - "webpack": "^5.28.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/plugin-content-docs": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.0.tgz", - "integrity": "sha512-WdDQUh2rRCbfJswVc0vY9EaAspxgziqpVEZja8+BmQR/TZh7HuLplT6GJbiFbE4RvwM3+PwG/jHMPglYDK60kw==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/mdx-loader": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "chalk": "^4.1.0", - "combine-promises": "^1.1.0", - "execa": "^5.0.0", - "fs-extra": "^9.1.0", - "globby": "^11.0.2", - "import-fresh": "^3.2.2", - "js-yaml": "^4.0.0", - "loader-utils": "^1.2.3", - "lodash": "^4.17.20", - "remark-admonitions": "^1.2.1", - "shelljs": "^0.8.4", - "tslib": "^2.1.0", - "utility-types": "^3.10.0", - "webpack": "^5.28.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@docusaurus/plugin-content-pages": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.0.tgz", - "integrity": "sha512-mk5LVVSvn+HJPKBaAs/Pceq/hTGxF2LVBvJEquuQz0NMAW3QdBWaYRRpOrL9CO8v+ygn5RuLslXsyZBsDNuhww==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/mdx-loader": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "globby": "^11.0.2", - "lodash": "^4.17.20", - "minimatch": "^3.0.4", - "remark-admonitions": "^1.2.1", - "slash": "^3.0.0", - "tslib": "^2.1.0", - "webpack": "^5.28.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/plugin-debug": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.0.tgz", - "integrity": "sha512-m75sZdF8Yccxfih3qfdQg9DucMTrYBnmeTA8GNmdVaK701Ip8t50d1pDJchtu0FSEh6vzVB9C6D2YD5YgVFp8A==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "react-json-view": "^1.21.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/plugin-google-analytics": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.0.tgz", - "integrity": "sha512-7lHrg1L+adc8VbiaLexa15i4fdq4MRPUTLMxRPAWz+QskhisW89Ryi2/gDmfMNqLblX84Qg2RASa+2gqO4wepw==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/plugin-google-gtag": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.0.tgz", - "integrity": "sha512-V7zaYbhAMv0jexm5H/5sAnoM1GHibcn9QQk5UWC++x1kE0KRuLDZHV+9OyvW5wr0wWFajod/b88SpUpSMF5u+g==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/plugin-sitemap": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.0.tgz", - "integrity": "sha512-dvmk8Sr+6pBkiKDb7Rjdp0GeFDWPUlayoJWK3fN3g0Fno6uxFfYhNZyXJ+ObyCA7HoW5rzeBMiO+uAja19JXTg==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "fs-extra": "^9.1.0", - "sitemap": "^6.3.6", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/preset-classic": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.0.tgz", - "integrity": "sha512-cFpR0UaAeUt5qVx1bpidhlar6tiRNITIQlxP4bOVsjbxVTZhZ/cNuIz7C+2zFPCuKIflGXdTIQOrucPmd7z51Q==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/plugin-content-blog": "2.0.0-beta.0", - "@docusaurus/plugin-content-docs": "2.0.0-beta.0", - "@docusaurus/plugin-content-pages": "2.0.0-beta.0", - "@docusaurus/plugin-debug": "2.0.0-beta.0", - "@docusaurus/plugin-google-analytics": "2.0.0-beta.0", - "@docusaurus/plugin-google-gtag": "2.0.0-beta.0", - "@docusaurus/plugin-sitemap": "2.0.0-beta.0", - "@docusaurus/theme-classic": "2.0.0-beta.0", - "@docusaurus/theme-search-algolia": "2.0.0-beta.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/react-loadable": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.0.tgz", - "integrity": "sha512-Ld/kwUE6yATIOTLq3JCsWiTa/drisajwKqBQ2Rw6IcT+sFsKfYek8F2jSH8f68AT73xX97UehduZeCSlnuCBIg==", - "dependencies": { - "prop-types": "^15.6.2" - } - }, - "node_modules/@docusaurus/theme-classic": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.0.tgz", - "integrity": "sha512-cBNtwAyg3be7Gk41FazMtgyibAcfuYaGHhGHIDRsXfc/qp3RhbiGiei7tyh200QT0NgKZxiVQy/r4d0mtjC++Q==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/plugin-content-blog": "2.0.0-beta.0", - "@docusaurus/plugin-content-docs": "2.0.0-beta.0", - "@docusaurus/plugin-content-pages": "2.0.0-beta.0", - "@docusaurus/theme-common": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "@mdx-js/mdx": "^1.6.21", - "@mdx-js/react": "^1.6.21", - "chalk": "^4.1.0", - "clsx": "^1.1.1", - "copy-text-to-clipboard": "^3.0.0", - "fs-extra": "^9.1.0", - "globby": "^11.0.2", - "infima": "0.2.0-alpha.23", - "lodash": "^4.17.20", - "parse-numeric-range": "^1.2.0", - "postcss": "^8.2.10", - "prism-react-renderer": "^1.1.1", - "prismjs": "^1.23.0", - "prop-types": "^15.7.2", - "react-router-dom": "^5.2.0", - "rtlcss": "^3.1.2" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/theme-common": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.0.0-beta.0.tgz", - "integrity": "sha512-2rcVmQpvbdAgnzTWuM7Bfpu+2TQm928bhlvxn226jQy7IYz8ySRlIode63HhCtpx03hpdMCkrK6HxhfEcvHjQg==", - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/plugin-content-blog": "2.0.0-beta.0", - "@docusaurus/plugin-content-docs": "2.0.0-beta.0", - "@docusaurus/plugin-content-pages": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/theme-search-algolia": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.0.tgz", - "integrity": "sha512-/GhgAm4yuwqTXWTsWnqpFYxpjTv+t45Wk8q/LmTVINa+A7b6jkMkch2lygagIt69/ufDm2Uw6eYhgrmF4DJqfQ==", - "dependencies": { - "@docsearch/react": "^3.0.0-alpha.33", - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/theme-common": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "algoliasearch": "^4.8.4", - "algoliasearch-helper": "^3.3.4", - "clsx": "^1.1.1", - "eta": "^1.12.1", - "lodash": "^4.17.20" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/types": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.0.0-beta.0.tgz", - "integrity": "sha512-z9PI+GbtYwqTXnkX4/a/A6psDX2p8N2uWlN2f4ifrm8WY4WhR9yiTOh0uo0pIqqaUQQvkEq3o5hOXuXLECEs+w==", - "dependencies": { - "commander": "^5.1.0", - "joi": "^17.4.0", - "querystring": "0.2.0", - "webpack": "^5.28.0", - "webpack-merge": "^5.7.3" - } - }, - "node_modules/@docusaurus/utils": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.0-beta.0.tgz", - "integrity": "sha512-bvrT1EQu0maavr0Hb/lke9jmpzgVL/9tn5VQtbyahf472eJFY0bQDExllDrHK+l784SUvucqX0iaQeg0q6ySUw==", - "dependencies": { - "@docusaurus/types": "2.0.0-beta.0", - "@types/github-slugger": "^1.3.0", - "chalk": "^4.1.0", - "escape-string-regexp": "^4.0.0", - "fs-extra": "^9.1.0", - "gray-matter": "^4.0.2", - "lodash": "^4.17.20", - "resolve-pathname": "^3.0.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/utils-validation": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.0.tgz", - "integrity": "sha512-ELl/FVJ6xBz35TisZ1NmJhjbiVXDeU++K531PEFPCPmwnQPh7S6hZXdPnR71/Kc3BmuN9X2ZkwGOqNKVfys2Bg==", - "dependencies": { - "@docusaurus/utils": "2.0.0-beta.0", - "chalk": "^4.1.0", - "joi": "^17.4.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@docusaurus/utils/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@endiliey/static-site-generator-webpack-plugin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@endiliey/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.0.tgz", - "integrity": "sha512-3MBqYCs30qk1OBRC697NqhGouYbs71D1B8hrk/AFJC6GwF2QaJOQZtA1JYAaGSe650sZ8r5ppRTtCRXepDWlng==", - "dependencies": { - "bluebird": "^3.7.1", - "cheerio": "^0.22.0", - "eval": "^0.1.4", - "url": "^0.11.0", - "webpack-sources": "^1.4.3" - } - }, - "node_modules/@endiliey/static-site-generator-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@endiliey/static-site-generator-webpack-plugin/node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", - "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==" - }, - "node_modules/@hapi/topo": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", - "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@mdx-js/mdx": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz", - "integrity": "sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==", - "dependencies": { - "@babel/core": "7.12.9", - "@babel/plugin-syntax-jsx": "7.12.1", - "@babel/plugin-syntax-object-rest-spread": "7.8.3", - "@mdx-js/util": "1.6.22", - "babel-plugin-apply-mdx-type-prop": "1.6.22", - "babel-plugin-extract-import-names": "1.6.22", - "camelcase-css": "2.0.1", - "detab": "2.0.4", - "hast-util-raw": "6.0.1", - "lodash.uniq": "4.5.0", - "mdast-util-to-hast": "10.0.1", - "remark-footnotes": "2.0.0", - "remark-mdx": "1.6.22", - "remark-parse": "8.0.3", - "remark-squeeze-paragraphs": "4.0.0", - "style-to-object": "0.3.0", - "unified": "9.2.0", - "unist-builder": "2.0.3", - "unist-util-visit": "2.0.3" - } - }, - "node_modules/@mdx-js/mdx/node_modules/@babel/core": { - "version": "7.12.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", - "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.5", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.7", - "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.9", - "@babel/types": "^7.12.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@mdx-js/mdx/node_modules/@babel/plugin-syntax-jsx": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", - "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@mdx-js/mdx/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@mdx-js/react": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz", - "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==" - }, - "node_modules/@mdx-js/util": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.22.tgz", - "integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", - "dependencies": { - "@nodelib/fs.stat": "2.0.4", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.4", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.12", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.12.tgz", - "integrity": "sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ==" - }, - "node_modules/@sideway/address": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.2.tgz", - "integrity": "sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", - "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", - "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.5.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", - "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", - "dependencies": { - "@svgr/plugin-jsx": "^5.5.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", - "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", - "dependencies": { - "@babel/types": "^7.12.6" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", - "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", - "dependencies": { - "@babel/core": "^7.12.3", - "@svgr/babel-preset": "^5.5.0", - "@svgr/hast-util-to-babel-ast": "^5.5.0", - "svg-parser": "^2.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/plugin-svgo": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", - "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", - "dependencies": { - "cosmiconfig": "^7.0.0", - "deepmerge": "^4.2.2", - "svgo": "^1.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@svgr/webpack": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", - "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/plugin-transform-react-constant-elements": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.5", - "@svgr/core": "^5.5.0", - "@svgr/plugin-jsx": "^5.5.0", - "@svgr/plugin-svgo": "^5.5.0", - "loader-utils": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@trysound/sax": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.1.1.tgz", - "integrity": "sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@types/eslint": { - "version": "7.2.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.10.tgz", - "integrity": "sha512-kUEPnMKrqbtpCq/KTaGFFKAcz6Ethm2EjCoKIDaCmfRBWLbFuTcOJfTlorwbnboXBzahqWLgUp1BQeKHiJzPUQ==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", - "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.47", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.47.tgz", - "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==" - }, - "node_modules/@types/github-slugger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", - "integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==" - }, - "node_modules/@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/hast": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz", - "integrity": "sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==" - }, - "node_modules/@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" - }, - "node_modules/@types/mdast": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", - "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==" - }, - "node_modules/@types/node": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.3.tgz", - "integrity": "sha512-/WbxFeBU+0F79z9RdEOXH4CsDga+ibi5M8uEYr91u3CkT/pdWcV8MCook+4wDPnZBexRdwWS+PiVZ2xJviAzcQ==" - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "node_modules/@types/parse5": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", - "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" - }, - "node_modules/@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" - }, - "node_modules/@types/sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-dqYdvN7Sbw8QT/0Ci5rhjE4/iCMJEM0Y9rHpCu+gGXD9Lwbz28t6HI2yegsB6BoV1sShRMU6lAmAcgRjmFy7LA==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/unist": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", - "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", - "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz", - "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz", - "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz", - "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz", - "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz", - "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz", - "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==", - "dependencies": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz", - "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz", - "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz", - "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz", - "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==", - "dependencies": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/helper-wasm-section": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-opt": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "@webassemblyjs/wast-printer": "1.11.0" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz", - "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==", - "dependencies": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz", - "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==", - "dependencies": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz", - "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==", - "dependencies": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz", - "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==", - "dependencies": { - "@webassemblyjs/ast": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.2.4.tgz", - "integrity": "sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.0.tgz", - "integrity": "sha512-mjmzmv12YIG/G8JQdQuz2MUDShEJ6teYpT5bmWA4q7iwoGen8xtt3twF3OvzIUl+Q06aWIjvnwQUKvQ6TtMRjg==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", - "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, - "node_modules/algoliasearch": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.9.1.tgz", - "integrity": "sha512-EeJUYXzBEhZSsL6tXc3hseLBCtlNLa1MZ4mlMK6EeX38yRjY5vgnFcNNml6uUhlOjvheKxgkKRpPWkxgL8Cqkg==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.9.1", - "@algolia/cache-common": "4.9.1", - "@algolia/cache-in-memory": "4.9.1", - "@algolia/client-account": "4.9.1", - "@algolia/client-analytics": "4.9.1", - "@algolia/client-common": "4.9.1", - "@algolia/client-recommendation": "4.9.1", - "@algolia/client-search": "4.9.1", - "@algolia/logger-common": "4.9.1", - "@algolia/logger-console": "4.9.1", - "@algolia/requester-browser-xhr": "4.9.1", - "@algolia/requester-common": "4.9.1", - "@algolia/requester-node-http": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "node_modules/algoliasearch-helper": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.4.4.tgz", - "integrity": "sha512-OjyVLjykaYKCMxxRMZNiwLp8CS310E0qAeIY2NaublcmLAh8/SL19+zYHp7XCLtMem2ZXwl3ywMiA32O9jszuw==", - "dependencies": { - "events": "^1.1.1" - } - }, - "node_modules/algoliasearch-helper/node_modules/events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" - }, - "node_modules/ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dependencies": { - "string-width": "^3.0.0" - } - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "engines": { - "node": ">=10" - } - }, - "node_modules/ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.0.tgz", - "integrity": "sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/autoprefixer": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.5.tgz", - "integrity": "sha512-7H4AJZXvSsn62SqZyJCP+1AWwOuoYpUfK6ot9vm0e87XD6mT8lDywc9D9OTJPMULyGcvmIxzTAMeG2Cc+YX+fA==", - "dependencies": { - "browserslist": "^4.16.3", - "caniuse-lite": "^1.0.30001196", - "colorette": "^1.2.2", - "fraction.js": "^4.0.13", - "normalize-range": "^0.1.2", - "postcss-value-parser": "^4.1.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dependencies": { - "follow-redirects": "^1.10.0" - } - }, - "node_modules/babel-loader": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", - "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - } - }, - "node_modules/babel-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/babel-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/babel-loader/node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - } - }, - "node_modules/babel-plugin-apply-mdx-type-prop": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz", - "integrity": "sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==", - "dependencies": { - "@babel/helper-plugin-utils": "7.10.4", - "@mdx-js/util": "1.6.22" - } - }, - "node_modules/babel-plugin-apply-mdx-type-prop/node_modules/@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-extract-import-names": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz", - "integrity": "sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ==", - "dependencies": { - "@babel/helper-plugin-utils": "7.10.4" - } - }, - "node_modules/babel-plugin-extract-import-names/node_modules/@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", - "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", - "dependencies": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.0", - "semver": "^6.1.1" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", - "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.2.0", - "core-js-compat": "^3.9.1" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", - "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.2.0" - } - }, - "node_modules/bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base16": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dependencies": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "node_modules/bonjour/node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "node_modules/boxen": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.0.1.tgz", - "integrity": "sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA==", - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.0", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "dependencies": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "node_modules/buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" - }, - "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001228", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", - "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==" - }, - "node_modules/ccount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", - "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==" - }, - "node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" - }, - "node_modules/character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" - }, - "node_modules/cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", - "dependencies": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cheerio/node_modules/dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dependencies": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "node_modules/cheerio/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, - "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.1.1.tgz", - "integrity": "sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ==" - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "node_modules/clean-css": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.1.2.tgz", - "integrity": "sha512-QcaGg9OuMo+0Ds933yLOY+gHPWbxhxqF0HDexmToPf8pczvmvZGYzd+QqWp9/mkucAOKViI+dSFOqoZIvXbeBw==", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/clipboard": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", - "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", - "optional": true, - "dependencies": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dependencies": { - "mimic-response": "^1.0.0" - } - }, - "node_modules/clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dependencies": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/coa/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/collapse-white-space": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", - "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==" - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-convert/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/colord": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.0.1.tgz", - "integrity": "sha512-vm5YpaWamD0Ov6TSG0GGmUIwstrWcfKQV/h2CmbR7PbNu41+qdB5PW9lpzhjedrpm08uuYvcXi0Oel1RLZIJuA==" - }, - "node_modules/colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" - }, - "node_modules/combine-promises": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.1.0.tgz", - "integrity": "sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/comma-separated-tokens": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", - "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" - }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/convert-source-map/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-text-to-clipboard": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz", - "integrity": "sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==", - "engines": { - "node": ">=12" - } - }, - "node_modules/copy-webpack-plugin": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-8.1.1.tgz", - "integrity": "sha512-rYM2uzRxrLRpcyPqGceRBDpxxUV8vcDqIKxAUKfcnFpcrPxT5+XvhTxv7XLjo5AvEJFPdAE3zCogG2JVahqgSQ==", - "dependencies": { - "fast-glob": "^3.2.5", - "glob-parent": "^5.1.1", - "globby": "^11.0.3", - "normalize-path": "^3.0.0", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/core-js": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.12.1.tgz", - "integrity": "sha512-Ne9DKPHTObRuB09Dru5AjwKjY4cJHVGu+y5f7coGn1E9Grkc3p2iBwE9AI/nJzsE29mQF7oq+mhYYRqOMFN1Bw==" - }, - "node_modules/core-js-compat": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.12.1.tgz", - "integrity": "sha512-i6h5qODpw6EsHAoIdQhKoZdWn+dGBF3dSS8m5tif36RlWvW3A6+yu2S16QHUo3CrkzrnEskMAt9f8FxmY9fhWQ==", - "dependencies": { - "browserslist": "^4.16.6", - "semver": "7.0.0" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/core-js-pure": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.12.1.tgz", - "integrity": "sha512-1cch+qads4JnDSWsvc7d6nzlKAippwjUlf6vykkTLW53VSV+NkE6muGBToAjEA8pG90cSfcud3JgVmW2ds5TaQ==" - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "node_modules/cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cross-fetch": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", - "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", - "dependencies": { - "node-fetch": "2.6.1" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/css-color-names": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", - "integrity": "sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==", - "engines": { - "node": "*" - } - }, - "node_modules/css-declaration-sorter": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.0.3.tgz", - "integrity": "sha512-52P95mvW1SMzuRZegvpluT6yEv0FqQusydKQPZsNN5Q7hh8EwQvN8E2nwuJ16BBvNN6LcoIZXu/Bk58DAhrrxw==", - "dependencies": { - "timsort": "^0.3.0" - }, - "engines": { - "node": ">= 10" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-loader": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.4.tgz", - "integrity": "sha512-OFYGyINCKkdQsTrSYxzGSFnGS4gNjcXkKkQgWxK138jgnPt+lepxdjSZNc8sHAl5vP3DhsJUxufWIjOwI8PMMw==", - "dependencies": { - "camelcase": "^6.2.0", - "icss-utils": "^5.1.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.10", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^3.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-2.0.0.tgz", - "integrity": "sha512-cG/uc94727tx5pBNtb1Sd7gvUPzwmcQi1lkpfqTpdkuNq75hJCw7bIVsCNijLm4dhDcr1atvuysl2rZqOG8Txw==", - "dependencies": { - "cssnano": "^5.0.0", - "jest-worker": "^26.3.0", - "p-limit": "^3.0.2", - "postcss": "^8.2.9", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dependencies": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "node_modules/css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "engines": { - "node": "*" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.0.5.tgz", - "integrity": "sha512-L2VtPXnq6rmcMC9vkBOP131sZu3ccRQI27ejKZdmQiPDpUlFkUbpXHgKN+cibeO1U4PItxVZp1zTIn5dHsXoyg==", - "dependencies": { - "cosmiconfig": "^7.0.0", - "cssnano-preset-default": "^5.1.2", - "is-resolvable": "^1.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-preset-advanced": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.1.2.tgz", - "integrity": "sha512-Joym8pdrIKqzASYvyTwJ9FpkmEcrYToWKWMGVFSggindrEDOpe+FgNpWhWcv6Z7GDZ4kCC3p7PE/oPSGTc8/kw==", - "dependencies": { - "autoprefixer": "^10.2.0", - "cssnano-preset-default": "^5.1.2", - "postcss-discard-unused": "^5.0.1", - "postcss-merge-idents": "^5.0.1", - "postcss-reduce-idents": "^5.0.1", - "postcss-zindex": "^5.0.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-preset-default": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.1.2.tgz", - "integrity": "sha512-spilp8LRw0sacuxiN9A/dyyPr6G/WISKMBKcBD4NMoPV0ENx4DeuWvIIrSx9PII2nJIDCO3kywkqTPreECBVOg==", - "dependencies": { - "css-declaration-sorter": "^6.0.3", - "cssnano-utils": "^2.0.1", - "postcss-calc": "^8.0.0", - "postcss-colormin": "^5.2.0", - "postcss-convert-values": "^5.0.1", - "postcss-discard-comments": "^5.0.1", - "postcss-discard-duplicates": "^5.0.1", - "postcss-discard-empty": "^5.0.1", - "postcss-discard-overridden": "^5.0.1", - "postcss-merge-longhand": "^5.0.2", - "postcss-merge-rules": "^5.0.2", - "postcss-minify-font-values": "^5.0.1", - "postcss-minify-gradients": "^5.0.1", - "postcss-minify-params": "^5.0.1", - "postcss-minify-selectors": "^5.1.0", - "postcss-normalize-charset": "^5.0.1", - "postcss-normalize-display-values": "^5.0.1", - "postcss-normalize-positions": "^5.0.1", - "postcss-normalize-repeat-style": "^5.0.1", - "postcss-normalize-string": "^5.0.1", - "postcss-normalize-timing-functions": "^5.0.1", - "postcss-normalize-unicode": "^5.0.1", - "postcss-normalize-url": "^5.0.1", - "postcss-normalize-whitespace": "^5.0.1", - "postcss-ordered-values": "^5.0.1", - "postcss-reduce-initial": "^5.0.1", - "postcss-reduce-transforms": "^5.0.1", - "postcss-svgo": "^5.0.2", - "postcss-unique-selectors": "^5.0.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz", - "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dependencies": { - "css-tree": "^1.1.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "dependencies": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", - "optional": true - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "node_modules/detab": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.4.tgz", - "integrity": "sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==", - "dependencies": { - "repeat-string": "^1.5.4" - } - }, - "node_modules/detect-node": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.5.tgz", - "integrity": "sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==" - }, - "node_modules/detect-port": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.3.0.tgz", - "integrity": "sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==", - "dependencies": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "bin": { - "detect": "bin/detect-port", - "detect-port": "bin/detect-port" - }, - "engines": { - "node": ">= 4.2.1" - } - }, - "node_modules/detect-port/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/detect-port/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" - }, - "node_modules/dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "dependencies": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dependencies": { - "buffer-indexof": "^1.0.0" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/dom-serializer/node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "node_modules/domhandler": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", - "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domhandler/node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/electron-to-chromium": { - "version": "1.3.727", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz", - "integrity": "sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg==" - }, - "node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/emoticon": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-3.2.0.tgz", - "integrity": "sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", - "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-ex/node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "node_modules/es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz", - "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==" - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eta": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/eta/-/eta-1.12.1.tgz", - "integrity": "sha512-H8npoci2J/7XiPnVcCVulBSPsTNGvGaINyMjQDU8AFqp9LGsEYS88g2CiU+d01Sg44WtX7o4nb8wUJ9vnI+tiA==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eval": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.6.tgz", - "integrity": "sha512-o0XUw+5OGkXw4pJZzQoXUk+H87DHuC+7ZE//oSrRGtatTmr12oTnLfg6QOq9DyTt0c/p4TwzgmkKrBzWTSizyQ==", - "dependencies": { - "require-like": ">= 0.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", - "dependencies": { - "original": "^1.0.0" - }, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", - "dependencies": { - "punycode": "^1.3.2" - } - }, - "node_modules/fast-url-parser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "node_modules/fastq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", - "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fbemitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", - "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", - "dependencies": { - "fbjs": "^3.0.0" - } - }, - "node_modules/fbjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.0.tgz", - "integrity": "sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg==", - "dependencies": { - "cross-fetch": "^3.0.4", - "fbjs-css-vars": "^1.0.0", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - } - }, - "node_modules/fbjs-css-vars": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", - "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" - }, - "node_modules/feed": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", - "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", - "dependencies": { - "xml-js": "^1.6.11" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, - "node_modules/filesize": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", - "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flux": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.1.tgz", - "integrity": "sha512-emk4RCvJ8RzNP2lNpphKnG7r18q8elDYNAPx7xn+bDeOIo9FFfxEfIQ2y6YbQNmnsGD3nH1noxtLE64Puz1bRQ==", - "dependencies": { - "fbemitter": "^3.0.0", - "fbjs": "^3.0.0" - } - }, - "node_modules/follow-redirects": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", - "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", - "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", - "dependencies": { - "@babel/code-frame": "^7.5.5", - "chalk": "^2.4.1", - "micromatch": "^3.1.10", - "minimatch": "^3.0.4", - "semver": "^5.6.0", - "tapable": "^1.0.0", - "worker-rpc": "^0.1.0" - }, - "engines": { - "node": ">=6.11.5", - "yarn": ">=1.0.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.0.tgz", - "integrity": "sha512-o9lSKpK0TDqDwTL24Hxqi6I99s942l6TYkfl6WvGWgLOIFz/YonSGKfiSeMadoiNvTfqnfOa9mjb5SGVbBK9/w==", - "engines": { - "node": "*" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" - }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/github-slugger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz", - "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==", - "dependencies": { - "emoji-regex": ">=6.0.0 <=6.1.1" - } - }, - "node_modules/github-slugger/node_modules/emoji-regex": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", - "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=" - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", - "optional": true, - "dependencies": { - "delegate": "^3.1.2" - } - }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dependencies": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/hast-to-hyperscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", - "integrity": "sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==", - "dependencies": { - "@types/unist": "^2.0.3", - "comma-separated-tokens": "^1.0.0", - "property-information": "^5.3.0", - "space-separated-tokens": "^1.0.0", - "style-to-object": "^0.3.0", - "unist-util-is": "^4.0.0", - "web-namespaces": "^1.0.0" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz", - "integrity": "sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA==", - "dependencies": { - "@types/parse5": "^5.0.0", - "hastscript": "^6.0.0", - "property-information": "^5.0.0", - "vfile": "^4.0.0", - "vfile-location": "^3.2.0", - "web-namespaces": "^1.0.0" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", - "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==" - }, - "node_modules/hast-util-raw": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.1.tgz", - "integrity": "sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==", - "dependencies": { - "@types/hast": "^2.0.0", - "hast-util-from-parse5": "^6.0.0", - "hast-util-to-parse5": "^6.0.0", - "html-void-elements": "^1.0.0", - "parse5": "^6.0.0", - "unist-util-position": "^3.0.0", - "vfile": "^4.0.0", - "web-namespaces": "^1.0.0", - "xtend": "^4.0.0", - "zwitch": "^1.0.0" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz", - "integrity": "sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==", - "dependencies": { - "hast-to-hyperscript": "^9.0.0", - "property-information": "^5.0.0", - "web-namespaces": "^1.0.0", - "xtend": "^4.0.0", - "zwitch": "^1.0.0" - } - }, - "node_modules/hastscript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", - "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", - "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" - }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/hpack.js/node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" - }, - "node_modules/hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" - }, - "node_modules/html-entities": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", - "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==" - }, - "node_modules/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", - "dependencies": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", - "he": "^1.2.0", - "param-case": "^3.0.3", - "relateurl": "^0.2.7", - "terser": "^4.6.3" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/html-minifier-terser/node_modules/clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/html-minifier-terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/html-minifier-terser/node_modules/terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/html-void-elements": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", - "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==" - }, - "node_modules/html-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-rZsVvPXUYFyME0cuGkyOHfx9hmkFa4pWfxY/mdY38PsBEaVNsRoA+Id+8z6DBDgyv3zaw6XQszdF8HLwfQvcdQ==", - "dependencies": { - "@types/html-minifier-terser": "^5.0.0", - "html-minifier-terser": "^5.0.1", - "lodash": "^4.17.20", - "pretty-error": "^2.1.1", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dependencies": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "node_modules/htmlparser2/node_modules/domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dependencies": { - "domelementtype": "1" - } - }, - "node_modules/htmlparser2/node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" - }, - "node_modules/http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dependencies": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "engines": { - "node": "^10 || ^12 || >= 14" - } - }, - "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz", - "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==" - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "dependencies": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/infima": { - "version": "0.2.0-alpha.23", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.23.tgz", - "integrity": "sha512-V0RTjB1otjpH3E2asbydx3gz7ovdSJsuV7r9JTdBggqRilnelTJUcXxLawBQQKsjQi5qPcRTjxnlaV8xyyKhhw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/inline-style-parser": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", - "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" - }, - "node_modules/internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dependencies": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "engines": { - "node": ">=4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "node_modules/is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dependencies": { - "call-bind": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-ci/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "node_modules/is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "dependencies": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "node_modules/is-color-stop/node_modules/css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "engines": { - "node": "*" - } - }, - "node_modules/is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" - }, - "node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "engines": { - "node": ">=4" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" - }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dependencies": { - "is-path-inside": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-in-cwd/node_modules/is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dependencies": { - "path-is-inside": "^1.0.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "dependencies": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, - "node_modules/is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "node_modules/is-whitespace-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", - "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==" - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-word-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", - "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==" - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/joi": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", - "integrity": "sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==", - "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.0", - "@sideway/formula": "^3.0.0", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==" - }, - "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "engines": { - "node": ">=6" - } - }, - "node_modules/klona": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", - "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "node_modules/loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" - }, - "node_modules/lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" - }, - "node_modules/lodash.curry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "node_modules/lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, - "node_modules/lodash.flow": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", - "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" - }, - "node_modules/lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" - }, - "node_modules/lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "node_modules/lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" - }, - "node_modules/lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" - }, - "node_modules/lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" - }, - "node_modules/lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" - }, - "node_modules/lodash.toarray": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "node_modules/loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/markdown-escapes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", - "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" - }, - "node_modules/mdast-squeeze-paragraphs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", - "integrity": "sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==", - "dependencies": { - "unist-util-remove": "^2.0.0" - } - }, - "node_modules/mdast-util-definitions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", - "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", - "dependencies": { - "unist-util-visit": "^2.0.0" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz", - "integrity": "sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "mdast-util-definitions": "^4.0.0", - "mdurl": "^1.0.0", - "unist-builder": "^2.0.0", - "unist-util-generated": "^1.0.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^2.0.0" - } - }, - "node_modules/mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==" - }, - "node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/memory-fs/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/memory-fs/node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/memory-fs/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/memory-fs/node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/microevent.ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", - "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==" - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dependencies": { - "mime-db": "1.47.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.0.tgz", - "integrity": "sha512-nPFKI7NSy6uONUo9yn2hIfb9vyYvkFu95qki0e21DQ9uaqNKDP15DGpK0KnV6wDroWxPHtExrdEwx/yDQ8nVRw==", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0", - "webpack-sources": "^1.1.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/module-alias": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", - "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dependencies": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" - }, - "node_modules/nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", - "optional": true - }, - "node_modules/nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-emoji": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", - "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", - "dependencies": { - "lodash.toarray": "^4.4.0" - } - }, - "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/node-releases": { - "version": "1.1.72", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", - "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "engines": { - "node": ">=4" - } - }, - "node_modules/nprogress": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E=" - }, - "node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dependencies": { - "boolbase": "~1.0.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==" - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", - "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/opn/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dependencies": { - "url-parse": "^1.4.3" - } - }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "dependencies": { - "retry": "^0.12.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/parse-numeric-range": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.2.0.tgz", - "integrity": "sha512-1q2tXpAOplPxcl8vrIGPWz1dJxxfmdRkCFcpxxMBerDnGuuHalOWF/xj9L8Nn5XoTUoB/6F0CeQBp2fMgkOYFg==" - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", - "engines": { - "node": ">=8.6" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "8.2.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz", - "integrity": "sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==", - "dependencies": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map": "^0.6.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-calc": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.0.0.tgz", - "integrity": "sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==", - "dependencies": { - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-colormin": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.2.0.tgz", - "integrity": "sha512-+HC6GfWU3upe5/mqmxuqYZ9B2Wl4lcoUUNkoaX59nEWV4EtADCMiBqui111Bu8R8IvaZTmqmxrqOAqjbHIwXPw==", - "dependencies": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.0.1", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-convert-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.0.1.tgz", - "integrity": "sha512-C3zR1Do2BkKkCgC0g3sF8TS0koF2G+mN8xxayZx3f10cIRmTaAnpgpRQZjNekTZxM2ciSPoh2IWJm0VZx8NoQg==", - "dependencies": { - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-comments": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", - "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz", - "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-empty": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz", - "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz", - "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-unused": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-5.0.1.tgz", - "integrity": "sha512-tD6xR/xyZTwfhKYRw0ylfCY8wbfhrjpKAMnDKRTLMy2fNW5hl0hoV6ap5vo2JdCkuHkP3CHw72beO4Y8pzFdww==", - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-loader": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-5.2.0.tgz", - "integrity": "sha512-uSuCkENFeUaOYsKrXm0eNNgVIxc71z8RcckLMbVw473rGojFnrUeqEz6zBgXsH2q1EIzXnO/4pEz9RhALjlITA==", - "dependencies": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.4", - "semver": "^7.3.4" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/postcss-merge-idents": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-5.0.1.tgz", - "integrity": "sha512-xu8ueVU0RszbI2gKkxR6mluupsOSSLvt8q4gA2fcKFkA+x6SlH3cb4cFHpDvcRCNFbUmCR/VUub+Y6zPOjPx+Q==", - "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.0.2.tgz", - "integrity": "sha512-BMlg9AXSI5G9TBT0Lo/H3PfUy63P84rVz3BjCFE9e9Y9RXQZD3+h3YO1kgTNsNJy7bBc1YQp8DmSnwLIW5VPcw==", - "dependencies": { - "css-color-names": "^1.0.1", - "postcss-value-parser": "^4.1.0", - "stylehacks": "^5.0.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-merge-rules": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.0.2.tgz", - "integrity": "sha512-5K+Md7S3GwBewfB4rjDeol6V/RZ8S+v4B66Zk2gChRqLTCC8yjnHQ601omj9TKftS19OPGqZ/XzoqpzNQQLwbg==", - "dependencies": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^2.0.1", - "postcss-selector-parser": "^6.0.5", - "vendors": "^1.0.3" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz", - "integrity": "sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA==", - "dependencies": { - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.0.1.tgz", - "integrity": "sha512-odOwBFAIn2wIv+XYRpoN2hUV3pPQlgbJ10XeXPq8UY2N+9ZG42xu45lTn/g9zZ+d70NKSQD6EOi6UiCMu3FN7g==", - "dependencies": { - "cssnano-utils": "^2.0.1", - "is-color-stop": "^1.1.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-params": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.0.1.tgz", - "integrity": "sha512-4RUC4k2A/Q9mGco1Z8ODc7h+A0z7L7X2ypO1B6V8057eVK6mZ6xwz6QN64nHuHLbqbclkX1wyzRnIrdZehTEHw==", - "dependencies": { - "alphanum-sort": "^1.0.2", - "browserslist": "^4.16.0", - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0", - "uniqs": "^2.0.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz", - "integrity": "sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og==", - "dependencies": { - "alphanum-sort": "^1.0.2", - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "engines": { - "node": "^10 || ^12 || >= 14" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz", - "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz", - "integrity": "sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ==", - "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz", - "integrity": "sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg==", - "dependencies": { - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz", - "integrity": "sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w==", - "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-string": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz", - "integrity": "sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA==", - "dependencies": { - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz", - "integrity": "sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q==", - "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz", - "integrity": "sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA==", - "dependencies": { - "browserslist": "^4.16.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-url": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.0.1.tgz", - "integrity": "sha512-hkbG0j58Z1M830/CJ73VsP7gvlG1yF+4y7Fd1w4tD2c7CaA2Psll+pQ6eQhth9y9EaqZSLzamff/D0MZBMbYSg==", - "dependencies": { - "is-absolute-url": "^3.0.3", - "normalize-url": "^4.5.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz", - "integrity": "sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA==", - "dependencies": { - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-ordered-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.0.1.tgz", - "integrity": "sha512-6mkCF5BQ25HvEcDfrMHCLLFHlraBSlOXFnQMHYhSpDO/5jSR1k8LdEXOkv+7+uzW6o6tBYea1Km0wQSRkPJkwA==", - "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-reduce-idents": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-5.0.1.tgz", - "integrity": "sha512-6Rw8iIVFbqtaZExgWK1rpVgP7DPFRPh0DDFZxJ/ADNqPiH10sPCoq5tgo6kLiTyfh9sxjKYjXdc8udLEcPOezg==", - "dependencies": { - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz", - "integrity": "sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw==", - "dependencies": { - "browserslist": "^4.16.0", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz", - "integrity": "sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA==", - "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-sort-media-queries": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-3.9.10.tgz", - "integrity": "sha512-pyCWbMrpQq4WjcYFrcVAvxS/+iHnXK5pxa1SAm1s9U4HZjGYU4gkCHwbHbzJ2ZFiiRYpRNRp85QuFvg6ZyKHxw==", - "dependencies": { - "sort-css-media-queries": "1.5.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/postcss-svgo": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.0.2.tgz", - "integrity": "sha512-YzQuFLZu3U3aheizD+B1joQ94vzPfE6BNUcSYuceNxlVnKKsOtdo6hL9/zyC168Q8EwfLSgaDSalsUGa9f2C0A==", - "dependencies": { - "postcss-value-parser": "^4.1.0", - "svgo": "^2.3.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/postcss-svgo/node_modules/css-select": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz", - "integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^4.0.0", - "domhandler": "^4.0.0", - "domutils": "^2.4.3", - "nth-check": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/postcss-svgo/node_modules/css-what": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz", - "integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/postcss-svgo/node_modules/dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/postcss-svgo/node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/postcss-svgo/node_modules/domutils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", - "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/postcss-svgo/node_modules/nth-check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", - "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/postcss-svgo/node_modules/svgo": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.3.0.tgz", - "integrity": "sha512-fz4IKjNO6HDPgIQxu4IxwtubtbSfGEAJUq/IXyTPIkGhWck/faiiwfkvsB8LnBkKLvSoyNNIY6d13lZprJMc9Q==", - "dependencies": { - "@trysound/sax": "0.1.1", - "chalk": "^4.1.0", - "commander": "^7.1.0", - "css-select": "^3.1.2", - "css-tree": "^1.1.2", - "csso": "^4.2.0", - "stable": "^0.1.8" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.0.1.tgz", - "integrity": "sha512-gwi1NhHV4FMmPn+qwBNuot1sG1t2OmacLQ/AX29lzyggnjd+MnVD5uqQmpXO3J17KGL2WAxQruj1qTd3H0gG/w==", - "dependencies": { - "alphanum-sort": "^1.0.2", - "postcss-selector-parser": "^6.0.5", - "uniqs": "^2.0.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "node_modules/postcss-zindex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-5.0.1.tgz", - "integrity": "sha512-nwgtJJys+XmmSGoYCcgkf/VczP8Mp/0OfSv3v0+fw0uABY4yxw+eFs0Xp9nAZHIKnS5j+e9ywQ+RD+ONyvl5pA==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "engines": { - "node": ">=4" - } - }, - "node_modules/pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^2.0.4" - } - }, - "node_modules/pretty-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", - "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/prism-react-renderer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.2.0.tgz", - "integrity": "sha512-GHqzxLYImx1iKN1jJURcuRoA/0ygCcNhfGw1IT8nPIMzarmKQ3Nc+JcG0gi8JXQzuh0C5ShE4npMIoqNin40hg==" - }, - "node_modules/prismjs": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz", - "integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==", - "optionalDependencies": { - "clipboard": "^2.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dependencies": { - "asap": "~2.0.3" - } - }, - "node_modules/prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "node_modules/property-information": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", - "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", - "dependencies": { - "xtend": "^4.0.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pure-color": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", - "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-base16-styling": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", - "integrity": "sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=", - "dependencies": { - "base16": "^1.0.0", - "lodash.curry": "^4.0.1", - "lodash.flow": "^3.3.0", - "pure-color": "^1.2.0" - } - }, - "node_modules/react-dev-utils": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", - "integrity": "sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A==", - "dependencies": { - "@babel/code-frame": "7.10.4", - "address": "1.1.2", - "browserslist": "4.14.2", - "chalk": "2.4.2", - "cross-spawn": "7.0.3", - "detect-port-alt": "1.1.6", - "escape-string-regexp": "2.0.0", - "filesize": "6.1.0", - "find-up": "4.1.0", - "fork-ts-checker-webpack-plugin": "4.1.6", - "global-modules": "2.0.0", - "globby": "11.0.1", - "gzip-size": "5.1.1", - "immer": "8.0.1", - "is-root": "2.1.0", - "loader-utils": "2.0.0", - "open": "^7.0.2", - "pkg-up": "3.1.0", - "prompts": "2.4.0", - "react-error-overlay": "^6.0.9", - "recursive-readdir": "2.2.2", - "shell-quote": "1.7.2", - "strip-ansi": "6.0.0", - "text-table": "0.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/react-dev-utils/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/react-dev-utils/node_modules/browserslist": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", - "integrity": "sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==", - "dependencies": { - "caniuse-lite": "^1.0.30001125", - "electron-to-chromium": "^1.3.564", - "escalade": "^3.0.2", - "node-releases": "^1.1.61" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/react-dev-utils/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/react-dev-utils/node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/react-dev-utils/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/react-dev-utils/node_modules/detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "dependencies": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "bin": { - "detect": "bin/detect-port", - "detect-port": "bin/detect-port" - }, - "engines": { - "node": ">= 4.2.1" - } - }, - "node_modules/react-dev-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/react-dev-utils/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/react-dev-utils/node_modules/prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - } - }, - "node_modules/react-error-overlay": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", - "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" - }, - "node_modules/react-fast-compare": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" - }, - "node_modules/react-helmet": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", - "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", - "dependencies": { - "object-assign": "^4.1.1", - "prop-types": "^15.7.2", - "react-fast-compare": "^3.1.1", - "react-side-effect": "^2.1.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-json-view": { - "version": "1.21.3", - "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz", - "integrity": "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==", - "dependencies": { - "flux": "^4.0.1", - "react-base16-styling": "^0.6.0", - "react-lifecycles-compat": "^3.0.4", - "react-textarea-autosize": "^8.3.2" - } - }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, - "node_modules/react-loadable": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz", - "integrity": "sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg==", - "dependencies": { - "prop-types": "^15.5.0" - } - }, - "node_modules/react-loadable-ssr-addon-v5-slorber": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", - "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", - "dependencies": { - "@babel/runtime": "^7.10.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, - "node_modules/react-router-config": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", - "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", - "dependencies": { - "@babel/runtime": "^7.1.2" - } - }, - "node_modules/react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, - "node_modules/react-router/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/react-router/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/react-side-effect": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.1.tgz", - "integrity": "sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==" - }, - "node_modules/react-textarea-autosize": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.2.tgz", - "integrity": "sha512-JrMWVgQSaExQByP3ggI1eA8zF4mF0+ddVuX7acUeK2V7bmrpjVOY72vmLz2IXFJSAXoY3D80nEzrn0GWajWK3Q==", - "dependencies": { - "@babel/runtime": "^7.10.2", - "use-composed-ref": "^1.0.0", - "use-latest": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/reading-time": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.3.0.tgz", - "integrity": "sha512-RJ8J5O6UvrclfZpcPSPuKusrdRfoY7uXXoYOOdeswZNtSkQaewT3919yz6RyloDBR+iwcUyz5zGOUjhgvfuv3g==" - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", - "dependencies": { - "minimatch": "3.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "node_modules/regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "dependencies": { - "regenerate": "^1.4.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, - "node_modules/regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", - "dependencies": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" - }, - "node_modules/regjsparser": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", - "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/rehype-parse": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.2.tgz", - "integrity": "sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug==", - "dependencies": { - "hast-util-from-parse5": "^5.0.0", - "parse5": "^5.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/rehype-parse/node_modules/hast-util-from-parse5": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", - "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", - "dependencies": { - "ccount": "^1.0.3", - "hastscript": "^5.0.0", - "property-information": "^5.0.0", - "web-namespaces": "^1.1.2", - "xtend": "^4.0.1" - } - }, - "node_modules/rehype-parse/node_modules/hastscript": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", - "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", - "dependencies": { - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0" - } - }, - "node_modules/rehype-parse/node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remark-admonitions": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/remark-admonitions/-/remark-admonitions-1.2.1.tgz", - "integrity": "sha512-Ji6p68VDvD+H1oS95Fdx9Ar5WA2wcDA4kwrrhVU7fGctC6+d3uiMICu7w7/2Xld+lnU7/gi+432+rRbup5S8ow==", - "dependencies": { - "rehype-parse": "^6.0.2", - "unified": "^8.4.2", - "unist-util-visit": "^2.0.1" - } - }, - "node_modules/remark-admonitions/node_modules/unified": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-8.4.2.tgz", - "integrity": "sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==", - "dependencies": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - } - }, - "node_modules/remark-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-2.2.0.tgz", - "integrity": "sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w==", - "dependencies": { - "emoticon": "^3.2.0", - "node-emoji": "^1.10.0", - "unist-util-visit": "^2.0.3" - } - }, - "node_modules/remark-footnotes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-2.0.0.tgz", - "integrity": "sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==" - }, - "node_modules/remark-mdx": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.22.tgz", - "integrity": "sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==", - "dependencies": { - "@babel/core": "7.12.9", - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-proposal-object-rest-spread": "7.12.1", - "@babel/plugin-syntax-jsx": "7.12.1", - "@mdx-js/util": "1.6.22", - "is-alphabetical": "1.0.4", - "remark-parse": "8.0.3", - "unified": "9.2.0" - } - }, - "node_modules/remark-mdx/node_modules/@babel/core": { - "version": "7.12.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", - "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.5", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.7", - "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.9", - "@babel/types": "^7.12.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/remark-mdx/node_modules/@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" - }, - "node_modules/remark-mdx/node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", - "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.12.1" - } - }, - "node_modules/remark-mdx/node_modules/@babel/plugin-syntax-jsx": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", - "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/remark-mdx/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/remark-parse": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", - "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", - "dependencies": { - "ccount": "^1.0.0", - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^2.0.0", - "vfile-location": "^3.0.0", - "xtend": "^4.0.1" - } - }, - "node_modules/remark-squeeze-paragraphs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz", - "integrity": "sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==", - "dependencies": { - "mdast-squeeze-paragraphs": "^4.0.0" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "node_modules/renderkid": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.6.tgz", - "integrity": "sha512-GIis2GBr/ho0pFNf57D4XM4+PgnQuTii0WCPjEZmZfKivzUfGuRdjN2aQYtYMiNggHmNyBve+thFnNR1iBRcKg==", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.0" - } - }, - "node_modules/renderkid/node_modules/css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/renderkid/node_modules/css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/renderkid/node_modules/dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/renderkid/node_modules/domutils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", - "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/renderkid/node_modules/nth-check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", - "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", - "engines": { - "node": "*" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "node_modules/resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" - }, - "node_modules/rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rtl-detect": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.0.3.tgz", - "integrity": "sha512-2sMcZO60tL9YDEFe24gqddg3hJ+xSmJFN8IExcQUxeHxQzydQrN6GHPL+yAWgzItXSI7es53hcZC9pJneuZDKA==" - }, - "node_modules/rtlcss": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-3.1.2.tgz", - "integrity": "sha512-b04YSX37siupPOWUEguEBReWX2w4QT89C0PI9g2JzZycbq7zrgPmTr1DA1pizSWpKRFdCjjnrx/SSvU4fOHmGg==", - "dependencies": { - "chalk": "^4.1.0", - "find-up": "^5.0.0", - "mkdirp": "^1.0.4", - "postcss": "^8.2.4", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "rtlcss": "bin/rtlcss.js" - } - }, - "node_modules/rtlcss/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/rtlcss/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/rtlcss/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/rtlcss/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/rtlcss/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dependencies": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", - "optional": true - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" - }, - "node_modules/selfsigned": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", - "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", - "dependencies": { - "node-forge": "^0.10.0" - } - }, - "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-handler": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", - "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", - "dependencies": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", - "mime-types": "2.1.18", - "minimatch": "3.0.4", - "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", - "range-parser": "1.2.0" - } - }, - "node_modules/serve-handler/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-handler/node_modules/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" - }, - "node_modules/serve-handler/node_modules/range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" - }, - "node_modules/shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" - }, - "node_modules/sirv": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.11.tgz", - "integrity": "sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg==", - "dependencies": { - "@polka/url": "^1.0.0-next.9", - "mime": "^2.3.1", - "totalist": "^1.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sirv/node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, - "node_modules/sitemap": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-6.4.0.tgz", - "integrity": "sha512-DoPKNc2/apQZTUnfiOONWctwq7s6dZVspxAZe2VPMNtoqNq7HgXRvlRnbIpKjf+8+piQdWncwcy+YhhTGY5USQ==", - "dependencies": { - "@types/node": "^14.14.28", - "@types/sax": "^1.2.1", - "arg": "^5.0.0", - "sax": "^1.2.4" - }, - "bin": { - "sitemap": "dist/cli.js" - }, - "engines": { - "node": ">=10.3.0", - "npm": ">=5.6.0" - } - }, - "node_modules/sitemap/node_modules/@types/node": { - "version": "14.14.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.45.tgz", - "integrity": "sha512-DssMqTV9UnnoxDWu959sDLZzfvqCF0qDNRjaWeYSui9xkFe61kKo4l1TWNTQONpuXEm+gLMRvdlzvNHBamzmEw==" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sockjs-client": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz", - "integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==", - "dependencies": { - "debug": "^3.2.6", - "eventsource": "^1.0.7", - "faye-websocket": "^0.11.3", - "inherits": "^2.0.4", - "json3": "^3.3.3", - "url-parse": "^1.5.1" - } - }, - "node_modules/sockjs-client/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/sort-css-media-queries": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-1.5.4.tgz", - "integrity": "sha512-YP5W/h4Sid/YP7Lp87ejJ5jP13/Mtqt2vx33XyhO+IAugKlufRPbOrPlIiEUuxmpNBSBd3EeeQpFhdu3RfI2Ag==", - "engines": { - "node": ">= 6.3.0" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" - }, - "node_modules/space-separated-tokens": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", - "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "node_modules/state-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", - "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==" - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/std-env": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.3.0.tgz", - "integrity": "sha512-4qT5B45+Kjef2Z6pE0BkskzsH0GO7GrND0wGlTM1ioUe3v0dGYx9ZJH0Aro/YyA8fqQ5EyIKDRjZojJYMFTflw==", - "dependencies": { - "ci-info": "^3.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/stringify-object/node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/style-to-object": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", - "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", - "dependencies": { - "inline-style-parser": "0.1.1" - } - }, - "node_modules/stylehacks": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz", - "integrity": "sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA==", - "dependencies": { - "browserslist": "^4.16.0", - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" - }, - "node_modules/svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "dependencies": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/svgo/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/svgo/node_modules/css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "node_modules/svgo/node_modules/css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dependencies": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/svgo/node_modules/css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/svgo/node_modules/mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "node_modules/svgo/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz", - "integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==", - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.2.tgz", - "integrity": "sha512-6QhDaAiVHIQr5Ab3XUWZyDmrIPCHMiqJVljMF91YKyqwKkL5QHnYMkrMBy96v9Z7ev1hGhSEw1HQZc2p/s5Z8Q==", - "dependencies": { - "jest-worker": "^26.6.2", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.7.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" - }, - "node_modules/timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" - }, - "node_modules/tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", - "optional": true - }, - "node_modules/tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/to-regex/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/trim": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" - }, - "node_modules/trim-trailing-lines": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", - "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==" - }, - "node_modules/trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==" - }, - "node_modules/ts-essentials": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz", - "integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==" - }, - "node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.28", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", - "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", - "engines": { - "node": "*" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "node_modules/unherit": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", - "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", - "dependencies": { - "inherits": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unified": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", - "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", - "dependencies": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - } - }, - "node_modules/unified/node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/unist-builder": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", - "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==" - }, - "node_modules/unist-util-generated": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", - "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==" - }, - "node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==" - }, - "node_modules/unist-util-position": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", - "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==" - }, - "node_modules/unist-util-remove": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.1.0.tgz", - "integrity": "sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==", - "dependencies": { - "unist-util-is": "^4.0.0" - } - }, - "node_modules/unist-util-remove-position": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", - "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", - "dependencies": { - "unist-util-visit": "^2.0.0" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", - "dependencies": { - "@types/unist": "^2.0.2" - } - }, - "node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", - "dependencies": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.27", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/use-composed-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.1.0.tgz", - "integrity": "sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg==", - "dependencies": { - "ts-essentials": "^2.0.3" - } - }, - "node_modules/use-isomorphic-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz", - "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==" - }, - "node_modules/use-latest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz", - "integrity": "sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==", - "dependencies": { - "use-isomorphic-layout-effect": "^1.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" - }, - "node_modules/utility-types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", - "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/vfile": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", - "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", - "dependencies": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - } - }, - "node_modules/vfile-location": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.2.0.tgz", - "integrity": "sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==" - }, - "node_modules/vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - } - }, - "node_modules/vfile/node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/wait-on": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz", - "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==", - "dependencies": { - "axios": "^0.21.1", - "joi": "^17.3.0", - "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^6.6.3" - }, - "bin": { - "wait-on": "bin/wait-on" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/watchpack": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", - "integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/web-namespaces": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz", - "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==" - }, - "node_modules/webpack": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.37.0.tgz", - "integrity": "sha512-yvdhgcI6QkQkDe1hINBAJ1UNevqNGTVaCkD2SSJcB8rcrNNl922RI8i2DXUAuNfANoxwsiXXEA4ZPZI9q2oGLA==", - "dependencies": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.47", - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/wasm-edit": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "acorn": "^8.2.1", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.0", - "es-module-lexer": "^0.4.0", - "eslint-scope": "^5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.1", - "watchpack": "^2.0.0", - "webpack-sources": "^2.1.1" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz", - "integrity": "sha512-j5m7WgytCkiVBoOGavzNokBOqxe6Mma13X1asfVYtKWM3wxBiRRu1u1iG0Iol5+qp9WgyhkMmBAcvjEfJ2bdDw==", - "dependencies": { - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", - "commander": "^6.2.0", - "gzip-size": "^6.0.0", - "lodash": "^4.17.20", - "opener": "^1.5.2", - "sirv": "^1.0.7", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", - "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", - "dependencies": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack-dev-server": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz", - "integrity": "sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==", - "dependencies": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.8", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "sockjs-client": "^1.5.0", - "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 6.11.5" - } - }, - "node_modules/webpack-dev-server/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/webpack-dev-server/node_modules/del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "dependencies": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/webpack-dev-server/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/globby/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/webpack-dev-server/node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/webpack-dev-server/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/webpack-dev-server/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/webpack-dev-server/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/webpack-dev-server/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/webpack-dev-server/node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/webpack-dev-server/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dependencies": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-merge": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", - "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz", - "integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==", - "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-sources/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpackbar": { - "version": "5.0.0-3", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.0-3.tgz", - "integrity": "sha512-viW6KCYjMb0NPoDrw2jAmLXU2dEOhRrtku28KmOfeE1vxbfwCYuTbTaMhnkrCZLFAFyY9Q49Z/jzYO80Dw5b8g==", - "dependencies": { - "ansi-escapes": "^4.3.1", - "chalk": "^4.1.0", - "consola": "^2.15.0", - "figures": "^3.2.0", - "pretty-time": "^1.1.0", - "std-env": "^2.2.1", - "text-table": "^0.2.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" - }, - "node_modules/worker-rpc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", - "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", - "dependencies": { - "microevent.ts": "~0.1.1" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "dependencies": { - "sax": "^1.2.4" - }, - "bin": { - "xml-js": "bin/cli.js" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/yargs-parser/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - } - }, - "node_modules/zwitch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", - "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==" - } - }, - "dependencies": { - "@algolia/autocomplete-core": { - "version": "1.0.0-alpha.44", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.0.0-alpha.44.tgz", - "integrity": "sha512-2iMXthldMIDXtlbg9omRKLgg1bLo2ZzINAEqwhNjUeyj1ceEyL1ck6FY0VnJpf2LsjmNthHCz2BuFk+nYUeDNA==", - "requires": { - "@algolia/autocomplete-shared": "1.0.0-alpha.44" - } - }, - "@algolia/autocomplete-preset-algolia": { - "version": "1.0.0-alpha.44", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.0.0-alpha.44.tgz", - "integrity": "sha512-DCHwo5ovzg9k2ejUolGNTLFnIA7GpsrkbNJTy1sFbMnYfBmeK8egZPZnEl7lBTr27OaZu7IkWpTepLVSztZyng==", - "requires": { - "@algolia/autocomplete-shared": "1.0.0-alpha.44" - } - }, - "@algolia/autocomplete-shared": { - "version": "1.0.0-alpha.44", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.0.0-alpha.44.tgz", - "integrity": "sha512-2oQZPERYV+yNx/yoVWYjZZdOqsitJ5dfxXJjL18yczOXH6ujnsq+DTczSrX+RjzjQdVeJ1UAG053EJQF/FOiMg==" - }, - "@algolia/cache-browser-local-storage": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.9.1.tgz", - "integrity": "sha512-bAUU9vKCy45uTTlzJw0LYu1IjoZsmzL6lgjaVFaW1crhX/4P+JD5ReQv3n/wpiXSFaHq1WEO3WyH2g3ymzeipQ==", - "requires": { - "@algolia/cache-common": "4.9.1" - } - }, - "@algolia/cache-common": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.9.1.tgz", - "integrity": "sha512-tcvw4mOfFy44V4ZxDEy9wNGr6vFROZKRpXKTEBgdw/WBn6mX51H1ar4RWtceDEcDU4H5fIv5tsY3ip2hU+fTPg==" - }, - "@algolia/cache-in-memory": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.9.1.tgz", - "integrity": "sha512-IEJrHonvdymW2CnRfJtsTVWyfAH05xPEFkGXGCw00+6JNCj8Dln3TeaRLiaaY1srlyGedkemekQm1/Xb46CGOQ==", - "requires": { - "@algolia/cache-common": "4.9.1" - } - }, - "@algolia/client-account": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.9.1.tgz", - "integrity": "sha512-Shpjeuwb7i2LR5QuWREb6UbEQLGB+Pl/J5+wPgILJDP/uWp7jpl0ase9mYNQGKj7TjztpSpQCPZ3dSHPnzZPfw==", - "requires": { - "@algolia/client-common": "4.9.1", - "@algolia/client-search": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "@algolia/client-analytics": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.9.1.tgz", - "integrity": "sha512-/g6OkOSIA+A0t/tjvbL6iG/zV4El4LPFgv/tcAYHTH27BmlNtnEXw+iFpGjeUlQoPily9WVB3QNLMJkaNwL3HA==", - "requires": { - "@algolia/client-common": "4.9.1", - "@algolia/client-search": "4.9.1", - "@algolia/requester-common": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "@algolia/client-common": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.9.1.tgz", - "integrity": "sha512-UziRTZ8km3qwoVPIyEre8TV6V+MX7UtbfVqPmSafZ0xu41UUZ+sL56YoKjOXkbKuybeIC9prXMGy/ID5bXkTqg==", - "requires": { - "@algolia/requester-common": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "@algolia/client-recommendation": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-recommendation/-/client-recommendation-4.9.1.tgz", - "integrity": "sha512-Drtvvm1PNIOpYf4HFlkPFstFQ3IsN+TRmxur2F7y6Faplb5ybISa8ithu1tmlTdyTf3A78hQUQjgJet6qD2XZw==", - "requires": { - "@algolia/client-common": "4.9.1", - "@algolia/requester-common": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "@algolia/client-search": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.9.1.tgz", - "integrity": "sha512-r9Cw2r8kJr45iYncFDht6EshARghU265wuY8Q8oHrpFHjAziEYdsUOdNmQKbsSH5J3gLjDPx1EI5DzVd6ivn3w==", - "requires": { - "@algolia/client-common": "4.9.1", - "@algolia/requester-common": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "@algolia/logger-common": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.9.1.tgz", - "integrity": "sha512-9mPrbFlFyPT7or/7PXTiJjyOewWB9QRkZKVXkt5zHAUiUzGxmmdpJIGpPv3YQnDur8lXrXaRI0MHXUuIDMY1ng==" - }, - "@algolia/logger-console": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.9.1.tgz", - "integrity": "sha512-74VUwjtFjFpjZpi3QoHIPv0kcr3vWUSHX/Vs8PJW3lPsD4CgyhFenQbG9v+ZnyH0JrJwiYTtzfmrVh7IMWZGrQ==", - "requires": { - "@algolia/logger-common": "4.9.1" - } - }, - "@algolia/requester-browser-xhr": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.9.1.tgz", - "integrity": "sha512-zc46tk5o0ikOAz3uYiRAMxC2iVKAMFKT7nNZnLB5IzT0uqAh7pz/+D/UvIxP4bKmsllpBSnPcpfQF+OI4Ag/BA==", - "requires": { - "@algolia/requester-common": "4.9.1" - } - }, - "@algolia/requester-common": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.9.1.tgz", - "integrity": "sha512-9hPgXnlCSbqJqF69M5x5WN3h51Dc+mk/iWNeJSVxExHGvCDfBBZd0v6S15i8q2a9cD1I2RnhMpbnX5BmGtabVA==" - }, - "@algolia/requester-node-http": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.9.1.tgz", - "integrity": "sha512-vYNVbSCuyrCSCjHBQJk+tLZtWCjvvDf5tSbRJjyJYMqpnXuIuP7gZm24iHil4NPYBhbBj5NU2ZDAhc/gTn75Ag==", - "requires": { - "@algolia/requester-common": "4.9.1" - } - }, - "@algolia/transporter": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.9.1.tgz", - "integrity": "sha512-AbjFfGzX+cAuj7Qyc536OxIQzjFOA5FU2ANGStx8LBH+AKXScwfkx67C05riuaRR5adSCLMSEbVvUscH0nF+6A==", - "requires": { - "@algolia/cache-common": "4.9.1", - "@algolia/logger-common": "4.9.1", - "@algolia/requester-common": "4.9.1" - } - }, - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==" - }, - "@babel/core": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.2.tgz", - "integrity": "sha512-OgC1mON+l4U4B4wiohJlQNUU3H73mpTyYY3j/c8U9dr9UagGGSm+WFpzjy/YLdoyjiG++c1kIDgxCo/mLwQJeQ==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.2", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "@babel/generator": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.2.tgz", - "integrity": "sha512-OnADYbKrffDVai5qcpkMxQ7caomHOoEwjkouqnN2QhydAjowFAZcsdecFIRUBdb+ZcruwYE4ythYmF1UBZU5xQ==", - "requires": { - "@babel/types": "^7.14.2", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", - "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", - "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.2.tgz", - "integrity": "sha512-6YctwVsmlkchxfGUogvVrrhzyD3grFJyluj5JgDlQrwfMLJSt5tdAzFZfPf4H2Xoi5YLcQ6BxfJlaOBHuctyIw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz", - "integrity": "sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "regexpu-core": "^4.7.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", - "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", - "requires": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz", - "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==", - "requires": { - "@babel/types": "^7.13.0" - } - }, - "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz", - "integrity": "sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==", - "requires": { - "@babel/traverse": "^7.13.15", - "@babel/types": "^7.13.16" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-module-transforms": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", - "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", - "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==" - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz", - "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-wrap-function": "^7.13.0", - "@babel/types": "^7.13.0" - } - }, - "@babel/helper-replace-supers": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", - "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", - "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", - "requires": { - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==" - }, - "@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==" - }, - "@babel/helper-wrap-function": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz", - "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==", - "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" - } - }, - "@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", - "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" - } - }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@babel/parser": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.2.tgz", - "integrity": "sha512-IoVDIHpsgE/fu7eXBeRWt8zLbDrSvD7H1gpomOkPpBoEN8KCruCqSDdqo8dddwQQrui30KSvQBaMUOJiuFu6QQ==" - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz", - "integrity": "sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.13.12" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.2.tgz", - "integrity": "sha512-b1AM4F6fwck4N8ItZ/AtC4FP/cqZqmKRQ4FaTDutwSYyjuhtvsGEMLK4N/ztV/ImP40BjIDyMgBQAeAMsQYVFQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-remap-async-to-generator": "^7.13.0", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz", - "integrity": "sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.13.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.13.11.tgz", - "integrity": "sha512-fJTdFI4bfnMjvxJyNuaf8i9mVcZ0UhetaGEUHaHV9KEnibLugJkZAtXikR8KcYj+NYmI4DZMS8yQAyg+hvfSqg==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-class-static-block": "^7.12.13" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.2.tgz", - "integrity": "sha512-oxVQZIWFh91vuNEMKltqNsKLFWkOIyJc95k2Gv9lWVyDfPUQGSSlbDEgWuJUU1afGE9WwlzpucMZ3yDRHIItkA==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.2.tgz", - "integrity": "sha512-sRxW3z3Zp3pFfLAgVEvzTFutTXax837oOatUIvSG9o5gRj9mKwm3br1Se5f4QalTQs9x4AzlA/HrCWbQIHASUQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.2.tgz", - "integrity": "sha512-w2DtsfXBBJddJacXMBhElGEYqCZQqN99Se1qeYn8DVLB33owlrlLftIbMzn5nz1OITfDVknXF433tBrLEAOEjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.2.tgz", - "integrity": "sha512-1JAZtUrqYyGsS7IDmFeaem+/LJqujfLZ2weLR9ugB0ufUPjzf8cguyVT1g5im7f7RXxuLq1xUxEzvm68uYRtGg==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.2.tgz", - "integrity": "sha512-ebR0zU9OvI2N4qiAC38KIAK75KItpIPTpAtd2r4OZmMFeKbKJpUFLYP2EuDut82+BmYi8sz42B+TfTptJ9iG5Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.2.tgz", - "integrity": "sha512-DcTQY9syxu9BpU3Uo94fjCB3LN9/hgPS8oUL7KrSW3bA2ePrKZZPJcc5y0hoJAM9dft3pGfErtEUvxXQcfLxUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.2.tgz", - "integrity": "sha512-hBIQFxwZi8GIp934+nj5uV31mqclC1aYDhctDu5khTi9PCCUOczyy0b34W0oE9U/eJXiqQaKyVsmjeagOaSlbw==", - "requires": { - "@babel/compat-data": "^7.14.0", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.14.2" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.2.tgz", - "integrity": "sha512-XtkJsmJtBaUbOxZsNk0Fvrv8eiqgneug0A6aqLFZ4TSkar2L5dSXWcnUKHgmjJt49pyB/6ZHvkr3dPgl9MOWRQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.2.tgz", - "integrity": "sha512-qQByMRPwMZJainfig10BoaDldx/+VDtNcrA7qdNaEOAj6VXud+gfrkA8j4CRAU5HjnWREXqIpSpH30qZX1xivA==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", - "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.0.tgz", - "integrity": "sha512-59ANdmEwwRUkLjB7CRtwJxxwtjESw+X2IePItA+RGQh+oy5RmpCh/EvVVvh5XQc3yxsm5gtv0+i9oBZhaDNVTg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-create-class-features-plugin": "^7.14.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-private-property-in-object": "^7.14.0" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", - "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.12.13.tgz", - "integrity": "sha512-ZmKQ0ZXR0nYpHZIIuj9zE7oIqCx2hw9TKi+lIo73NNrMPAZGHfS92/VRV0ZmPj6H2ffBgyFHXvJ5NYsNeEaP2A==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz", - "integrity": "sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.0.tgz", - "integrity": "sha512-bda3xF8wGl5/5btF794utNOL0Jw+9jE5C1sLZcoK7c4uonE/y3iQiyG+KbkF3WBV/paX58VCpjhxLPkdj5Fe4w==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz", - "integrity": "sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", - "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz", - "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==", - "requires": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-remap-async-to-generator": "^7.13.0" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", - "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.2.tgz", - "integrity": "sha512-neZZcP19NugZZqNwMTH+KoBjx5WyvESPSIOQb4JHpfd+zPfqcH65RMu5xJju5+6q/Y2VzYrleQTr+b6METyyxg==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.2.tgz", - "integrity": "sha512-7oafAVcucHquA/VZCsXv/gmuiHeYd64UJyyTYU+MPfNu0KeNlxw06IeENBO8bJjXVbolu+j1MM5aKQtH1OMCNg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz", - "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz", - "integrity": "sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", - "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", - "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", - "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz", - "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", - "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", - "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", - "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", - "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.2.tgz", - "integrity": "sha512-hPC6XBswt8P3G2D1tSV2HzdKvkqOpmbyoy+g73JG0qlF/qx2y3KaMmXb1fLrpmWGLZYA0ojCvaHdzFWjlmV+Pw==", - "requires": { - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helper-plugin-utils": "^7.13.0", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "dependencies": { - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "requires": { - "object.assign": "^4.1.0" - } - } - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.0.tgz", - "integrity": "sha512-EX4QePlsTaRZQmw9BsoPeyh5OCtRGIhwfLquhxGp5e32w+dyL8htOcDwamlitmNFK6xBZYlygjdye9dbd9rUlQ==", - "requires": { - "@babel/helper-module-transforms": "^7.14.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-simple-access": "^7.13.12", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "dependencies": { - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "requires": { - "object.assign": "^4.1.0" - } - } - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz", - "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==", - "requires": { - "@babel/helper-hoist-variables": "^7.13.0", - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-identifier": "^7.12.11", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "dependencies": { - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "requires": { - "object.assign": "^4.1.0" - } - } - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.0.tgz", - "integrity": "sha512-nPZdnWtXXeY7I87UZr9VlsWme3Y0cfFFE41Wbxz4bbaexAjNMInXPFUpRRUJ8NoMm0Cw+zxbqjdPmLhcjfazMw==", - "requires": { - "@babel/helper-module-transforms": "^7.14.0", - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", - "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", - "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", - "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.2.tgz", - "integrity": "sha512-NxoVmA3APNCC1JdMXkdYXuQS+EMdqy0vIwyDHeKHiJKRxmp1qGSdb0JLEIoPRhkx6H/8Qi3RJ3uqOCYw8giy9A==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", - "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-react-constant-elements": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.13.13.tgz", - "integrity": "sha512-SNJU53VM/SjQL0bZhyU+f4kJQz7bQQajnrZRSaU21hruG/NWY41AEM9AWXeXX90pYr/C2yAmTgI6yW3LlLrAUQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.14.2.tgz", - "integrity": "sha512-zCubvP+jjahpnFJvPaHPiGVfuVUjXHhFvJKQdNnsmSsiU9kR/rCZ41jHc++tERD2zV+p7Hr6is+t5b6iWTCqSw==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.13.12.tgz", - "integrity": "sha512-jcEI2UqIcpCqB5U5DRxIl0tQEProI2gcu+g8VTIqxLO5Iidojb4d77q+fwGseCvd8af/lJ9masp4QWzBXFE2xA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/types": "^7.13.12" - } - }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.17.tgz", - "integrity": "sha512-BPjYV86SVuOaudFhsJR1zjgxxOhJDt6JHNoD48DxWEIxUCAMjV1ys6DYw4SDYZh0b1QsS2vfIA9t/ZsQGsDOUQ==", - "requires": { - "@babel/plugin-transform-react-jsx": "^7.12.17" - } - }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", - "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", - "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", - "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.14.2.tgz", - "integrity": "sha512-LyA2AiPkaYzI7G5e2YI4NCasTfFe7mZvlupNprDOB7CdNUHb2DQC4uV6oeZ0396gOcicUzUCh0MShL6wiUgk+Q==", - "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-plugin-utils": "^7.13.0", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", - "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", - "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", - "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz", - "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", - "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz", - "integrity": "sha512-elQEwluzaU8R8dbVuW2Q2Y8Nznf7hnjM7+DSCd14Lo5fF63C9qNLbwZYbmZrtV9/ySpSUpkRpQXvJb6xyu4hCQ==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-typescript": "^7.12.13" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", - "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", - "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/preset-env": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.2.tgz", - "integrity": "sha512-7dD7lVT8GMrE73v4lvDEb85cgcQhdES91BSD7jS/xjC6QY8PnRhux35ac+GCpbiRhp8crexBvZZqnaL6VrY8TQ==", - "requires": { - "@babel/compat-data": "^7.14.0", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", - "@babel/plugin-proposal-async-generator-functions": "^7.14.2", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-class-static-block": "^7.13.11", - "@babel/plugin-proposal-dynamic-import": "^7.14.2", - "@babel/plugin-proposal-export-namespace-from": "^7.14.2", - "@babel/plugin-proposal-json-strings": "^7.14.2", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.2", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.2", - "@babel/plugin-proposal-numeric-separator": "^7.14.2", - "@babel/plugin-proposal-object-rest-spread": "^7.14.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.14.2", - "@babel/plugin-proposal-optional-chaining": "^7.14.2", - "@babel/plugin-proposal-private-methods": "^7.13.0", - "@babel/plugin-proposal-private-property-in-object": "^7.14.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.12.13", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.0", - "@babel/plugin-syntax-top-level-await": "^7.12.13", - "@babel/plugin-transform-arrow-functions": "^7.13.0", - "@babel/plugin-transform-async-to-generator": "^7.13.0", - "@babel/plugin-transform-block-scoped-functions": "^7.12.13", - "@babel/plugin-transform-block-scoping": "^7.14.2", - "@babel/plugin-transform-classes": "^7.14.2", - "@babel/plugin-transform-computed-properties": "^7.13.0", - "@babel/plugin-transform-destructuring": "^7.13.17", - "@babel/plugin-transform-dotall-regex": "^7.12.13", - "@babel/plugin-transform-duplicate-keys": "^7.12.13", - "@babel/plugin-transform-exponentiation-operator": "^7.12.13", - "@babel/plugin-transform-for-of": "^7.13.0", - "@babel/plugin-transform-function-name": "^7.12.13", - "@babel/plugin-transform-literals": "^7.12.13", - "@babel/plugin-transform-member-expression-literals": "^7.12.13", - "@babel/plugin-transform-modules-amd": "^7.14.2", - "@babel/plugin-transform-modules-commonjs": "^7.14.0", - "@babel/plugin-transform-modules-systemjs": "^7.13.8", - "@babel/plugin-transform-modules-umd": "^7.14.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", - "@babel/plugin-transform-new-target": "^7.12.13", - "@babel/plugin-transform-object-super": "^7.12.13", - "@babel/plugin-transform-parameters": "^7.14.2", - "@babel/plugin-transform-property-literals": "^7.12.13", - "@babel/plugin-transform-regenerator": "^7.13.15", - "@babel/plugin-transform-reserved-words": "^7.12.13", - "@babel/plugin-transform-shorthand-properties": "^7.12.13", - "@babel/plugin-transform-spread": "^7.13.0", - "@babel/plugin-transform-sticky-regex": "^7.12.13", - "@babel/plugin-transform-template-literals": "^7.13.0", - "@babel/plugin-transform-typeof-symbol": "^7.12.13", - "@babel/plugin-transform-unicode-escapes": "^7.12.13", - "@babel/plugin-transform-unicode-regex": "^7.12.13", - "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.14.2", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", - "core-js-compat": "^3.9.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-react": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.13.13.tgz", - "integrity": "sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-transform-react-display-name": "^7.12.13", - "@babel/plugin-transform-react-jsx": "^7.13.12", - "@babel/plugin-transform-react-jsx-development": "^7.12.17", - "@babel/plugin-transform-react-pure-annotations": "^7.12.1" - } - }, - "@babel/preset-typescript": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.13.0.tgz", - "integrity": "sha512-LXJwxrHy0N3f6gIJlYbLta1D9BDtHpQeqwzM0LIfjDlr6UE/D5Mc7W4iDiQzaE+ks0sTjT26ArcHWnJVt0QiHw==", - "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-transform-typescript": "^7.13.0" - } - }, - "@babel/runtime": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz", - "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/runtime-corejs3": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.0.tgz", - "integrity": "sha512-0R0HTZWHLk6G8jIk0FtoX+AatCtKnswS98VhXwGImFc759PJRp4Tru0PQYZofyijTFUr+gT8Mu7sgXVJLQ0ceg==", - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "to-fast-properties": "^2.0.0" - } - }, - "@docsearch/css": { - "version": "3.0.0-alpha.36", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.0.0-alpha.36.tgz", - "integrity": "sha512-zSN2SXuZPDqQaSFzYa1kOwToukqzhLHG7c66iO+/PlmWb6/RZ5cjTkG6VCJynlohRWea7AqZKWS/ptm8kM2Dmg==" - }, - "@docsearch/react": { - "version": "3.0.0-alpha.36", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.0.0-alpha.36.tgz", - "integrity": "sha512-synYZDHalvMzesFiy7kK+uoz4oTdWSTbe2cU+iiUjwFMyQ+WWjWwGVnvcvk+cjj9pRCVaZo5y5WpqNXq1j8k9Q==", - "requires": { - "@algolia/autocomplete-core": "1.0.0-alpha.44", - "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.44", - "@docsearch/css": "3.0.0-alpha.36", - "algoliasearch": "^4.0.0" - } - }, - "@docusaurus/core": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.0-beta.0.tgz", - "integrity": "sha512-xWwpuEwFRKJmZvNGOpr/dyRDnx/psckLPsozQTg2hu3u81Wqu9gigWgYK/C2fPlEjxMcVw0/2WH+zwpbyWmF2Q==", - "requires": { - "@babel/core": "^7.12.16", - "@babel/generator": "^7.12.15", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.12.15", - "@babel/preset-env": "^7.12.16", - "@babel/preset-react": "^7.12.13", - "@babel/preset-typescript": "^7.12.16", - "@babel/runtime": "^7.12.5", - "@babel/runtime-corejs3": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@docusaurus/cssnano-preset": "2.0.0-beta.0", - "@docusaurus/react-loadable": "5.5.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "@endiliey/static-site-generator-webpack-plugin": "^4.0.0", - "@svgr/webpack": "^5.5.0", - "autoprefixer": "^10.2.5", - "babel-loader": "^8.2.2", - "babel-plugin-dynamic-import-node": "2.3.0", - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "chokidar": "^3.5.1", - "clean-css": "^5.1.1", - "commander": "^5.1.0", - "copy-webpack-plugin": "^8.1.0", - "core-js": "^3.9.1", - "css-loader": "^5.1.1", - "css-minimizer-webpack-plugin": "^2.0.0", - "cssnano": "^5.0.1", - "del": "^6.0.0", - "detect-port": "^1.3.0", - "eta": "^1.12.1", - "express": "^4.17.1", - "file-loader": "^6.2.0", - "fs-extra": "^9.1.0", - "github-slugger": "^1.3.0", - "globby": "^11.0.2", - "html-minifier-terser": "^5.1.1", - "html-tags": "^3.1.0", - "html-webpack-plugin": "^5.2.0", - "import-fresh": "^3.3.0", - "is-root": "^2.1.0", - "leven": "^3.1.0", - "lodash": "^4.17.20", - "mini-css-extract-plugin": "^1.4.0", - "module-alias": "^2.2.2", - "nprogress": "^0.2.0", - "postcss": "^8.2.10", - "postcss-loader": "^5.2.0", - "prompts": "^2.4.0", - "react-dev-utils": "^11.0.1", - "react-error-overlay": "^6.0.9", - "react-helmet": "^6.1.0", - "react-loadable": "^5.5.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.2.0", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.2.0", - "resolve-pathname": "^3.0.0", - "rtl-detect": "^1.0.2", - "semver": "^7.3.4", - "serve-handler": "^6.1.3", - "shelljs": "^0.8.4", - "std-env": "^2.2.1", - "strip-ansi": "^6.0.0", - "terser-webpack-plugin": "^5.1.1", - "tslib": "^2.1.0", - "update-notifier": "^5.1.0", - "url-loader": "^4.1.1", - "wait-on": "^5.2.1", - "webpack": "^5.28.0", - "webpack-bundle-analyzer": "^4.4.0", - "webpack-dev-server": "^3.11.2", - "webpack-merge": "^5.7.3", - "webpackbar": "^5.0.0-3" - } - }, - "@docusaurus/cssnano-preset": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.0.tgz", - "integrity": "sha512-gqQHeQCDHZDd5NaiKZwDiyg75sBCqDyAsvmFukkDAty8xE7u9IhzbOQKvCAtwseuvzu2BNN41gnJ8bz7vZzQiw==", - "requires": { - "cssnano-preset-advanced": "^5.0.0", - "postcss": "^8.2.10", - "postcss-sort-media-queries": "^3.8.9" - } - }, - "@docusaurus/mdx-loader": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.0.tgz", - "integrity": "sha512-oQLS2ZeUnqw79CV37glglZpaYgFfA5Az5lT83m5tJfMUZjoK4ehG1XWBeUzWy8QQNI452yAID8jz8jihEQeCcw==", - "requires": { - "@babel/parser": "^7.12.16", - "@babel/traverse": "^7.12.13", - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@mdx-js/mdx": "^1.6.21", - "@mdx-js/react": "^1.6.21", - "escape-html": "^1.0.3", - "file-loader": "^6.2.0", - "fs-extra": "^9.1.0", - "github-slugger": "^1.3.0", - "gray-matter": "^4.0.2", - "mdast-util-to-string": "^2.0.0", - "remark-emoji": "^2.1.0", - "stringify-object": "^3.3.0", - "unist-util-visit": "^2.0.2", - "url-loader": "^4.1.1", - "webpack": "^5.28.0" - } - }, - "@docusaurus/plugin-content-blog": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.0.tgz", - "integrity": "sha512-lz63i5k/23RJ3Rk/2fIsYAoD8Wua3b5b0AbH2JoOhQu1iAIQiV8m91Z3XALBSzA3nBtAOIweNI7yzWL+JFSTvw==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/mdx-loader": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "chalk": "^4.1.0", - "feed": "^4.2.2", - "fs-extra": "^9.1.0", - "globby": "^11.0.2", - "loader-utils": "^2.0.0", - "lodash": "^4.17.20", - "reading-time": "^1.3.0", - "remark-admonitions": "^1.2.1", - "tslib": "^2.1.0", - "webpack": "^5.28.0" - } - }, - "@docusaurus/plugin-content-docs": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.0.tgz", - "integrity": "sha512-WdDQUh2rRCbfJswVc0vY9EaAspxgziqpVEZja8+BmQR/TZh7HuLplT6GJbiFbE4RvwM3+PwG/jHMPglYDK60kw==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/mdx-loader": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "chalk": "^4.1.0", - "combine-promises": "^1.1.0", - "execa": "^5.0.0", - "fs-extra": "^9.1.0", - "globby": "^11.0.2", - "import-fresh": "^3.2.2", - "js-yaml": "^4.0.0", - "loader-utils": "^1.2.3", - "lodash": "^4.17.20", - "remark-admonitions": "^1.2.1", - "shelljs": "^0.8.4", - "tslib": "^2.1.0", - "utility-types": "^3.10.0", - "webpack": "^5.28.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - } - } - }, - "@docusaurus/plugin-content-pages": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.0.tgz", - "integrity": "sha512-mk5LVVSvn+HJPKBaAs/Pceq/hTGxF2LVBvJEquuQz0NMAW3QdBWaYRRpOrL9CO8v+ygn5RuLslXsyZBsDNuhww==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/mdx-loader": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "globby": "^11.0.2", - "lodash": "^4.17.20", - "minimatch": "^3.0.4", - "remark-admonitions": "^1.2.1", - "slash": "^3.0.0", - "tslib": "^2.1.0", - "webpack": "^5.28.0" - } - }, - "@docusaurus/plugin-debug": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.0.tgz", - "integrity": "sha512-m75sZdF8Yccxfih3qfdQg9DucMTrYBnmeTA8GNmdVaK701Ip8t50d1pDJchtu0FSEh6vzVB9C6D2YD5YgVFp8A==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "react-json-view": "^1.21.1", - "tslib": "^2.1.0" - } - }, - "@docusaurus/plugin-google-analytics": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.0.tgz", - "integrity": "sha512-7lHrg1L+adc8VbiaLexa15i4fdq4MRPUTLMxRPAWz+QskhisW89Ryi2/gDmfMNqLblX84Qg2RASa+2gqO4wepw==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0" - } - }, - "@docusaurus/plugin-google-gtag": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.0.tgz", - "integrity": "sha512-V7zaYbhAMv0jexm5H/5sAnoM1GHibcn9QQk5UWC++x1kE0KRuLDZHV+9OyvW5wr0wWFajod/b88SpUpSMF5u+g==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0" - } - }, - "@docusaurus/plugin-sitemap": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.0.tgz", - "integrity": "sha512-dvmk8Sr+6pBkiKDb7Rjdp0GeFDWPUlayoJWK3fN3g0Fno6uxFfYhNZyXJ+ObyCA7HoW5rzeBMiO+uAja19JXTg==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "fs-extra": "^9.1.0", - "sitemap": "^6.3.6", - "tslib": "^2.1.0" - } - }, - "@docusaurus/preset-classic": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.0.tgz", - "integrity": "sha512-cFpR0UaAeUt5qVx1bpidhlar6tiRNITIQlxP4bOVsjbxVTZhZ/cNuIz7C+2zFPCuKIflGXdTIQOrucPmd7z51Q==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/plugin-content-blog": "2.0.0-beta.0", - "@docusaurus/plugin-content-docs": "2.0.0-beta.0", - "@docusaurus/plugin-content-pages": "2.0.0-beta.0", - "@docusaurus/plugin-debug": "2.0.0-beta.0", - "@docusaurus/plugin-google-analytics": "2.0.0-beta.0", - "@docusaurus/plugin-google-gtag": "2.0.0-beta.0", - "@docusaurus/plugin-sitemap": "2.0.0-beta.0", - "@docusaurus/theme-classic": "2.0.0-beta.0", - "@docusaurus/theme-search-algolia": "2.0.0-beta.0" - } - }, - "@docusaurus/react-loadable": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.0.tgz", - "integrity": "sha512-Ld/kwUE6yATIOTLq3JCsWiTa/drisajwKqBQ2Rw6IcT+sFsKfYek8F2jSH8f68AT73xX97UehduZeCSlnuCBIg==", - "requires": { - "prop-types": "^15.6.2" - } - }, - "@docusaurus/theme-classic": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.0.tgz", - "integrity": "sha512-cBNtwAyg3be7Gk41FazMtgyibAcfuYaGHhGHIDRsXfc/qp3RhbiGiei7tyh200QT0NgKZxiVQy/r4d0mtjC++Q==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/plugin-content-blog": "2.0.0-beta.0", - "@docusaurus/plugin-content-docs": "2.0.0-beta.0", - "@docusaurus/plugin-content-pages": "2.0.0-beta.0", - "@docusaurus/theme-common": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "@mdx-js/mdx": "^1.6.21", - "@mdx-js/react": "^1.6.21", - "chalk": "^4.1.0", - "clsx": "^1.1.1", - "copy-text-to-clipboard": "^3.0.0", - "fs-extra": "^9.1.0", - "globby": "^11.0.2", - "infima": "0.2.0-alpha.23", - "lodash": "^4.17.20", - "parse-numeric-range": "^1.2.0", - "postcss": "^8.2.10", - "prism-react-renderer": "^1.1.1", - "prismjs": "^1.23.0", - "prop-types": "^15.7.2", - "react-router-dom": "^5.2.0", - "rtlcss": "^3.1.2" - } - }, - "@docusaurus/theme-common": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.0.0-beta.0.tgz", - "integrity": "sha512-2rcVmQpvbdAgnzTWuM7Bfpu+2TQm928bhlvxn226jQy7IYz8ySRlIode63HhCtpx03hpdMCkrK6HxhfEcvHjQg==", - "requires": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/plugin-content-blog": "2.0.0-beta.0", - "@docusaurus/plugin-content-docs": "2.0.0-beta.0", - "@docusaurus/plugin-content-pages": "2.0.0-beta.0", - "@docusaurus/types": "2.0.0-beta.0", - "tslib": "^2.1.0" - } - }, - "@docusaurus/theme-search-algolia": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.0.tgz", - "integrity": "sha512-/GhgAm4yuwqTXWTsWnqpFYxpjTv+t45Wk8q/LmTVINa+A7b6jkMkch2lygagIt69/ufDm2Uw6eYhgrmF4DJqfQ==", - "requires": { - "@docsearch/react": "^3.0.0-alpha.33", - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/theme-common": "2.0.0-beta.0", - "@docusaurus/utils": "2.0.0-beta.0", - "@docusaurus/utils-validation": "2.0.0-beta.0", - "algoliasearch": "^4.8.4", - "algoliasearch-helper": "^3.3.4", - "clsx": "^1.1.1", - "eta": "^1.12.1", - "lodash": "^4.17.20" - } - }, - "@docusaurus/types": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.0.0-beta.0.tgz", - "integrity": "sha512-z9PI+GbtYwqTXnkX4/a/A6psDX2p8N2uWlN2f4ifrm8WY4WhR9yiTOh0uo0pIqqaUQQvkEq3o5hOXuXLECEs+w==", - "requires": { - "commander": "^5.1.0", - "joi": "^17.4.0", - "querystring": "0.2.0", - "webpack": "^5.28.0", - "webpack-merge": "^5.7.3" - } - }, - "@docusaurus/utils": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.0-beta.0.tgz", - "integrity": "sha512-bvrT1EQu0maavr0Hb/lke9jmpzgVL/9tn5VQtbyahf472eJFY0bQDExllDrHK+l784SUvucqX0iaQeg0q6ySUw==", - "requires": { - "@docusaurus/types": "2.0.0-beta.0", - "@types/github-slugger": "^1.3.0", - "chalk": "^4.1.0", - "escape-string-regexp": "^4.0.0", - "fs-extra": "^9.1.0", - "gray-matter": "^4.0.2", - "lodash": "^4.17.20", - "resolve-pathname": "^3.0.0", - "tslib": "^2.1.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - } - } - }, - "@docusaurus/utils-validation": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.0.tgz", - "integrity": "sha512-ELl/FVJ6xBz35TisZ1NmJhjbiVXDeU++K531PEFPCPmwnQPh7S6hZXdPnR71/Kc3BmuN9X2ZkwGOqNKVfys2Bg==", - "requires": { - "@docusaurus/utils": "2.0.0-beta.0", - "chalk": "^4.1.0", - "joi": "^17.4.0", - "tslib": "^2.1.0" - } - }, - "@endiliey/static-site-generator-webpack-plugin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@endiliey/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.0.tgz", - "integrity": "sha512-3MBqYCs30qk1OBRC697NqhGouYbs71D1B8hrk/AFJC6GwF2QaJOQZtA1JYAaGSe650sZ8r5ppRTtCRXepDWlng==", - "requires": { - "bluebird": "^3.7.1", - "cheerio": "^0.22.0", - "eval": "^0.1.4", - "url": "^0.11.0", - "webpack-sources": "^1.4.3" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - } - } - }, - "@hapi/hoek": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", - "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==" - }, - "@hapi/topo": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", - "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@mdx-js/mdx": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz", - "integrity": "sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==", - "requires": { - "@babel/core": "7.12.9", - "@babel/plugin-syntax-jsx": "7.12.1", - "@babel/plugin-syntax-object-rest-spread": "7.8.3", - "@mdx-js/util": "1.6.22", - "babel-plugin-apply-mdx-type-prop": "1.6.22", - "babel-plugin-extract-import-names": "1.6.22", - "camelcase-css": "2.0.1", - "detab": "2.0.4", - "hast-util-raw": "6.0.1", - "lodash.uniq": "4.5.0", - "mdast-util-to-hast": "10.0.1", - "remark-footnotes": "2.0.0", - "remark-mdx": "1.6.22", - "remark-parse": "8.0.3", - "remark-squeeze-paragraphs": "4.0.0", - "style-to-object": "0.3.0", - "unified": "9.2.0", - "unist-builder": "2.0.3", - "unist-util-visit": "2.0.3" - }, - "dependencies": { - "@babel/core": { - "version": "7.12.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", - "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.5", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.7", - "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.9", - "@babel/types": "^7.12.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", - "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "@mdx-js/react": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz", - "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==" - }, - "@mdx-js/util": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.22.tgz", - "integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==" - }, - "@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", - "requires": { - "@nodelib/fs.stat": "2.0.4", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" - }, - "@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", - "requires": { - "@nodelib/fs.scandir": "2.1.4", - "fastq": "^1.6.0" - } - }, - "@polka/url": { - "version": "1.0.0-next.12", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.12.tgz", - "integrity": "sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ==" - }, - "@sideway/address": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.2.tgz", - "integrity": "sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" - }, - "@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==" - }, - "@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==" - }, - "@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==" - }, - "@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==" - }, - "@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==" - }, - "@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==" - }, - "@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==" - }, - "@svgr/babel-plugin-transform-svg-component": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", - "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==" - }, - "@svgr/babel-preset": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", - "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", - "requires": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.5.0" - } - }, - "@svgr/core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", - "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", - "requires": { - "@svgr/plugin-jsx": "^5.5.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.0" - } - }, - "@svgr/hast-util-to-babel-ast": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", - "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", - "requires": { - "@babel/types": "^7.12.6" - } - }, - "@svgr/plugin-jsx": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", - "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", - "requires": { - "@babel/core": "^7.12.3", - "@svgr/babel-preset": "^5.5.0", - "@svgr/hast-util-to-babel-ast": "^5.5.0", - "svg-parser": "^2.0.2" - } - }, - "@svgr/plugin-svgo": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", - "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", - "requires": { - "cosmiconfig": "^7.0.0", - "deepmerge": "^4.2.2", - "svgo": "^1.2.2" - } - }, - "@svgr/webpack": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", - "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", - "requires": { - "@babel/core": "^7.12.3", - "@babel/plugin-transform-react-constant-elements": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.5", - "@svgr/core": "^5.5.0", - "@svgr/plugin-jsx": "^5.5.0", - "@svgr/plugin-svgo": "^5.5.0", - "loader-utils": "^2.0.0" - } - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "@trysound/sax": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.1.1.tgz", - "integrity": "sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow==" - }, - "@types/eslint": { - "version": "7.2.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.10.tgz", - "integrity": "sha512-kUEPnMKrqbtpCq/KTaGFFKAcz6Ethm2EjCoKIDaCmfRBWLbFuTcOJfTlorwbnboXBzahqWLgUp1BQeKHiJzPUQ==", - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", - "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.47", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.47.tgz", - "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==" - }, - "@types/github-slugger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", - "integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==" - }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/hast": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz", - "integrity": "sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q==", - "requires": { - "@types/unist": "*" - } - }, - "@types/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==" - }, - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" - }, - "@types/mdast": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", - "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", - "requires": { - "@types/unist": "*" - } - }, - "@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==" - }, - "@types/node": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.3.tgz", - "integrity": "sha512-/WbxFeBU+0F79z9RdEOXH4CsDga+ibi5M8uEYr91u3CkT/pdWcV8MCook+4wDPnZBexRdwWS+PiVZ2xJviAzcQ==" - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "@types/parse5": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", - "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" - }, - "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" - }, - "@types/sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-dqYdvN7Sbw8QT/0Ci5rhjE4/iCMJEM0Y9rHpCu+gGXD9Lwbz28t6HI2yegsB6BoV1sShRMU6lAmAcgRjmFy7LA==", - "requires": { - "@types/node": "*" - } - }, - "@types/unist": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", - "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" - }, - "@webassemblyjs/ast": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", - "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==", - "requires": { - "@webassemblyjs/helper-numbers": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz", - "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==" - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz", - "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==" - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz", - "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==" - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz", - "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==", - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz", - "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==" - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz", - "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==", - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz", - "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==", - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz", - "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==", - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz", - "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==" - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz", - "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==", - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/helper-wasm-section": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-opt": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "@webassemblyjs/wast-printer": "1.11.0" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz", - "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==", - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz", - "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==", - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-buffer": "1.11.0", - "@webassemblyjs/wasm-gen": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz", - "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==", - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/helper-api-error": "1.11.0", - "@webassemblyjs/helper-wasm-bytecode": "1.11.0", - "@webassemblyjs/ieee754": "1.11.0", - "@webassemblyjs/leb128": "1.11.0", - "@webassemblyjs/utf8": "1.11.0" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz", - "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==", - "requires": { - "@webassemblyjs/ast": "1.11.0", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.2.4.tgz", - "integrity": "sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==" - }, - "acorn-walk": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.0.tgz", - "integrity": "sha512-mjmzmv12YIG/G8JQdQuz2MUDShEJ6teYpT5bmWA4q7iwoGen8xtt3twF3OvzIUl+Q06aWIjvnwQUKvQ6TtMRjg==" - }, - "address": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", - "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==" - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, - "algoliasearch": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.9.1.tgz", - "integrity": "sha512-EeJUYXzBEhZSsL6tXc3hseLBCtlNLa1MZ4mlMK6EeX38yRjY5vgnFcNNml6uUhlOjvheKxgkKRpPWkxgL8Cqkg==", - "requires": { - "@algolia/cache-browser-local-storage": "4.9.1", - "@algolia/cache-common": "4.9.1", - "@algolia/cache-in-memory": "4.9.1", - "@algolia/client-account": "4.9.1", - "@algolia/client-analytics": "4.9.1", - "@algolia/client-common": "4.9.1", - "@algolia/client-recommendation": "4.9.1", - "@algolia/client-search": "4.9.1", - "@algolia/logger-common": "4.9.1", - "@algolia/logger-console": "4.9.1", - "@algolia/requester-browser-xhr": "4.9.1", - "@algolia/requester-common": "4.9.1", - "@algolia/requester-node-http": "4.9.1", - "@algolia/transporter": "4.9.1" - } - }, - "algoliasearch-helper": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.4.4.tgz", - "integrity": "sha512-OjyVLjykaYKCMxxRMZNiwLp8CS310E0qAeIY2NaublcmLAh8/SL19+zYHp7XCLtMem2ZXwl3ywMiA32O9jszuw==", - "requires": { - "events": "^1.1.1" - }, - "dependencies": { - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" - } - } - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" - }, - "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "requires": { - "string-width": "^3.0.0" - }, - "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" - } - } - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.0.tgz", - "integrity": "sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" - }, - "autoprefixer": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.5.tgz", - "integrity": "sha512-7H4AJZXvSsn62SqZyJCP+1AWwOuoYpUfK6ot9vm0e87XD6mT8lDywc9D9OTJPMULyGcvmIxzTAMeG2Cc+YX+fA==", - "requires": { - "browserslist": "^4.16.3", - "caniuse-lite": "^1.0.30001196", - "colorette": "^1.2.2", - "fraction.js": "^4.0.13", - "normalize-range": "^0.1.2", - "postcss-value-parser": "^4.1.0" - } - }, - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "babel-loader": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", - "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", - "requires": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "babel-plugin-apply-mdx-type-prop": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz", - "integrity": "sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==", - "requires": { - "@babel/helper-plugin-utils": "7.10.4", - "@mdx-js/util": "1.6.22" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" - } - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-extract-import-names": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz", - "integrity": "sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ==", - "requires": { - "@babel/helper-plugin-utils": "7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" - } - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", - "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", - "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.0", - "semver": "^6.1.1" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", - "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.0", - "core-js-compat": "^3.9.1" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", - "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.0" - } - }, - "bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "base16": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - }, - "dependencies": { - "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - } - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "boxen": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.0.1.tgz", - "integrity": "sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA==", - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.0", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "requires": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" - }, - "camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001228", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", - "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==" - }, - "ccount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", - "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==" - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" - }, - "character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" - }, - "character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" - }, - "cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - }, - "dependencies": { - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - } - } - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" - }, - "ci-info": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.1.1.tgz", - "integrity": "sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ==" - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "clean-css": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.1.2.tgz", - "integrity": "sha512-QcaGg9OuMo+0Ds933yLOY+gHPWbxhxqF0HDexmToPf8pczvmvZGYzd+QqWp9/mkucAOKViI+dSFOqoZIvXbeBw==", - "requires": { - "source-map": "~0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" - }, - "clipboard": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", - "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", - "optional": true, - "requires": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - } - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "collapse-white-space": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", - "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - }, - "dependencies": { - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - } - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "colord": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.0.1.tgz", - "integrity": "sha512-vm5YpaWamD0Ov6TSG0GGmUIwstrWcfKQV/h2CmbR7PbNu41+qdB5PW9lpzhjedrpm08uuYvcXi0Oel1RLZIJuA==" - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" - }, - "combine-promises": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.1.0.tgz", - "integrity": "sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==" - }, - "comma-separated-tokens": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", - "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" - }, - "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, - "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" - }, - "consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" - }, - "copy-text-to-clipboard": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz", - "integrity": "sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==" - }, - "copy-webpack-plugin": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-8.1.1.tgz", - "integrity": "sha512-rYM2uzRxrLRpcyPqGceRBDpxxUV8vcDqIKxAUKfcnFpcrPxT5+XvhTxv7XLjo5AvEJFPdAE3zCogG2JVahqgSQ==", - "requires": { - "fast-glob": "^3.2.5", - "glob-parent": "^5.1.1", - "globby": "^11.0.3", - "normalize-path": "^3.0.0", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1" - } - }, - "core-js": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.12.1.tgz", - "integrity": "sha512-Ne9DKPHTObRuB09Dru5AjwKjY4cJHVGu+y5f7coGn1E9Grkc3p2iBwE9AI/nJzsE29mQF7oq+mhYYRqOMFN1Bw==" - }, - "core-js-compat": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.12.1.tgz", - "integrity": "sha512-i6h5qODpw6EsHAoIdQhKoZdWn+dGBF3dSS8m5tif36RlWvW3A6+yu2S16QHUo3CrkzrnEskMAt9f8FxmY9fhWQ==", - "requires": { - "browserslist": "^4.16.6", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } - }, - "core-js-pure": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.12.1.tgz", - "integrity": "sha512-1cch+qads4JnDSWsvc7d6nzlKAippwjUlf6vykkTLW53VSV+NkE6muGBToAjEA8pG90cSfcud3JgVmW2ds5TaQ==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "cross-fetch": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", - "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", - "requires": { - "node-fetch": "2.6.1" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" - }, - "css-color-names": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", - "integrity": "sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==" - }, - "css-declaration-sorter": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.0.3.tgz", - "integrity": "sha512-52P95mvW1SMzuRZegvpluT6yEv0FqQusydKQPZsNN5Q7hh8EwQvN8E2nwuJ16BBvNN6LcoIZXu/Bk58DAhrrxw==", - "requires": { - "timsort": "^0.3.0" - } - }, - "css-loader": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.4.tgz", - "integrity": "sha512-OFYGyINCKkdQsTrSYxzGSFnGS4gNjcXkKkQgWxK138jgnPt+lepxdjSZNc8sHAl5vP3DhsJUxufWIjOwI8PMMw==", - "requires": { - "camelcase": "^6.2.0", - "icss-utils": "^5.1.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.10", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^3.0.0", - "semver": "^7.3.5" - } - }, - "css-minimizer-webpack-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-2.0.0.tgz", - "integrity": "sha512-cG/uc94727tx5pBNtb1Sd7gvUPzwmcQi1lkpfqTpdkuNq75hJCw7bIVsCNijLm4dhDcr1atvuysl2rZqOG8Txw==", - "requires": { - "cssnano": "^5.0.0", - "jest-worker": "^26.3.0", - "p-limit": "^3.0.2", - "postcss": "^8.2.9", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" - }, - "cssnano": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.0.5.tgz", - "integrity": "sha512-L2VtPXnq6rmcMC9vkBOP131sZu3ccRQI27ejKZdmQiPDpUlFkUbpXHgKN+cibeO1U4PItxVZp1zTIn5dHsXoyg==", - "requires": { - "cosmiconfig": "^7.0.0", - "cssnano-preset-default": "^5.1.2", - "is-resolvable": "^1.1.0" - } - }, - "cssnano-preset-advanced": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.1.2.tgz", - "integrity": "sha512-Joym8pdrIKqzASYvyTwJ9FpkmEcrYToWKWMGVFSggindrEDOpe+FgNpWhWcv6Z7GDZ4kCC3p7PE/oPSGTc8/kw==", - "requires": { - "autoprefixer": "^10.2.0", - "cssnano-preset-default": "^5.1.2", - "postcss-discard-unused": "^5.0.1", - "postcss-merge-idents": "^5.0.1", - "postcss-reduce-idents": "^5.0.1", - "postcss-zindex": "^5.0.1" - } - }, - "cssnano-preset-default": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.1.2.tgz", - "integrity": "sha512-spilp8LRw0sacuxiN9A/dyyPr6G/WISKMBKcBD4NMoPV0ENx4DeuWvIIrSx9PII2nJIDCO3kywkqTPreECBVOg==", - "requires": { - "css-declaration-sorter": "^6.0.3", - "cssnano-utils": "^2.0.1", - "postcss-calc": "^8.0.0", - "postcss-colormin": "^5.2.0", - "postcss-convert-values": "^5.0.1", - "postcss-discard-comments": "^5.0.1", - "postcss-discard-duplicates": "^5.0.1", - "postcss-discard-empty": "^5.0.1", - "postcss-discard-overridden": "^5.0.1", - "postcss-merge-longhand": "^5.0.2", - "postcss-merge-rules": "^5.0.2", - "postcss-minify-font-values": "^5.0.1", - "postcss-minify-gradients": "^5.0.1", - "postcss-minify-params": "^5.0.1", - "postcss-minify-selectors": "^5.1.0", - "postcss-normalize-charset": "^5.0.1", - "postcss-normalize-display-values": "^5.0.1", - "postcss-normalize-positions": "^5.0.1", - "postcss-normalize-repeat-style": "^5.0.1", - "postcss-normalize-string": "^5.0.1", - "postcss-normalize-timing-functions": "^5.0.1", - "postcss-normalize-unicode": "^5.0.1", - "postcss-normalize-url": "^5.0.1", - "postcss-normalize-whitespace": "^5.0.1", - "postcss-ordered-values": "^5.0.1", - "postcss-reduce-initial": "^5.0.1", - "postcss-reduce-transforms": "^5.0.1", - "postcss-svgo": "^5.0.2", - "postcss-unique-selectors": "^5.0.1" - } - }, - "cssnano-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz", - "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==", - "requires": {} - }, - "csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "requires": { - "css-tree": "^1.1.2" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" - }, - "default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "requires": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - } - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - } - }, - "delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", - "optional": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "detab": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.4.tgz", - "integrity": "sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==", - "requires": { - "repeat-string": "^1.5.4" - } - }, - "detect-node": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.5.tgz", - "integrity": "sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==" - }, - "detect-port": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.3.0.tgz", - "integrity": "sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==", - "requires": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "requires": { - "path-type": "^4.0.0" - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" - }, - "dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "requires": { - "buffer-indexof": "^1.0.0" - } - }, - "dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "requires": { - "utila": "~0.4" - } - }, - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" - } - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domhandler": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", - "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", - "requires": { - "domelementtype": "^2.2.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" - } - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "electron-to-chromium": { - "version": "1.3.727", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz", - "integrity": "sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg==" - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "emoticon": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-3.2.0.tgz", - "integrity": "sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "enhanced-resolve": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", - "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" - }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "requires": { - "prr": "~1.0.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - } - } - }, - "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" - } - }, - "es-module-lexer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz", - "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==" - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "eta": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/eta/-/eta-1.12.1.tgz", - "integrity": "sha512-H8npoci2J/7XiPnVcCVulBSPsTNGvGaINyMjQDU8AFqp9LGsEYS88g2CiU+d01Sg44WtX7o4nb8wUJ9vnI+tiA==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "eval": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.6.tgz", - "integrity": "sha512-o0XUw+5OGkXw4pJZzQoXUk+H87DHuC+7ZE//oSrRGtatTmr12oTnLfg6QOq9DyTt0c/p4TwzgmkKrBzWTSizyQ==", - "requires": { - "require-like": ">= 0.1.1" - } - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", - "requires": { - "original": "^1.0.0" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", - "requires": { - "punycode": "^1.3.2" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } - } - }, - "fastq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", - "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", - "requires": { - "reusify": "^1.0.4" - } - }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "fbemitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", - "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", - "requires": { - "fbjs": "^3.0.0" - } - }, - "fbjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.0.tgz", - "integrity": "sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg==", - "requires": { - "cross-fetch": "^3.0.4", - "fbjs-css-vars": "^1.0.0", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - } - }, - "fbjs-css-vars": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", - "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" - }, - "feed": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", - "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", - "requires": { - "xml-js": "^1.6.11" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, - "filesize": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", - "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==" - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flux": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.1.tgz", - "integrity": "sha512-emk4RCvJ8RzNP2lNpphKnG7r18q8elDYNAPx7xn+bDeOIo9FFfxEfIQ2y6YbQNmnsGD3nH1noxtLE64Puz1bRQ==", - "requires": { - "fbemitter": "^3.0.0", - "fbjs": "^3.0.0" - } - }, - "follow-redirects": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", - "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "fork-ts-checker-webpack-plugin": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", - "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", - "requires": { - "@babel/code-frame": "^7.5.5", - "chalk": "^2.4.1", - "micromatch": "^3.1.10", - "minimatch": "^3.0.4", - "semver": "^5.6.0", - "tapable": "^1.0.0", - "worker-rpc": "^0.1.0" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fraction.js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.0.tgz", - "integrity": "sha512-o9lSKpK0TDqDwTL24Hxqi6I99s942l6TYkfl6WvGWgLOIFz/YonSGKfiSeMadoiNvTfqnfOa9mjb5SGVbBK9/w==" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" - }, - "github-slugger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz", - "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==", - "requires": { - "emoji-regex": ">=6.0.0 <=6.1.1" - }, - "dependencies": { - "emoji-regex": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", - "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=" - } - } - }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "requires": { - "ini": "2.0.0" - }, - "dependencies": { - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" - } - } - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", - "optional": true, - "requires": { - "delegate": "^3.1.2" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" - }, - "gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "requires": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - } - }, - "gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "requires": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - } - }, - "handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" - }, - "hast-to-hyperscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", - "integrity": "sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==", - "requires": { - "@types/unist": "^2.0.3", - "comma-separated-tokens": "^1.0.0", - "property-information": "^5.3.0", - "space-separated-tokens": "^1.0.0", - "style-to-object": "^0.3.0", - "unist-util-is": "^4.0.0", - "web-namespaces": "^1.0.0" - } - }, - "hast-util-from-parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz", - "integrity": "sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA==", - "requires": { - "@types/parse5": "^5.0.0", - "hastscript": "^6.0.0", - "property-information": "^5.0.0", - "vfile": "^4.0.0", - "vfile-location": "^3.2.0", - "web-namespaces": "^1.0.0" - } - }, - "hast-util-parse-selector": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", - "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==" - }, - "hast-util-raw": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.1.tgz", - "integrity": "sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==", - "requires": { - "@types/hast": "^2.0.0", - "hast-util-from-parse5": "^6.0.0", - "hast-util-to-parse5": "^6.0.0", - "html-void-elements": "^1.0.0", - "parse5": "^6.0.0", - "unist-util-position": "^3.0.0", - "vfile": "^4.0.0", - "web-namespaces": "^1.0.0", - "xtend": "^4.0.0", - "zwitch": "^1.0.0" - } - }, - "hast-util-to-parse5": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz", - "integrity": "sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==", - "requires": { - "hast-to-hyperscript": "^9.0.0", - "property-information": "^5.0.0", - "web-namespaces": "^1.0.0", - "xtend": "^4.0.0", - "zwitch": "^1.0.0" - } - }, - "hastscript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", - "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", - "requires": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - }, - "hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" - }, - "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - } - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - } - } - }, - "hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" - }, - "hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" - }, - "html-entities": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", - "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==" - }, - "html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", - "requires": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", - "he": "^1.2.0", - "param-case": "^3.0.3", - "relateurl": "^0.2.7", - "terser": "^4.6.3" - }, - "dependencies": { - "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", - "requires": { - "source-map": "~0.6.0" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - } - } - } - } - }, - "html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" - }, - "html-void-elements": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", - "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==" - }, - "html-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-rZsVvPXUYFyME0cuGkyOHfx9hmkFa4pWfxY/mdY38PsBEaVNsRoA+Id+8z6DBDgyv3zaw6XQszdF8HLwfQvcdQ==", - "requires": { - "@types/html-minifier-terser": "^5.0.0", - "html-minifier-terser": "^5.0.1", - "lodash": "^4.17.20", - "pretty-error": "^2.1.1", - "tapable": "^2.0.0" - } - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - } - } - }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==" - }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" - }, - "immer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz", - "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==" - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" - }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "requires": { - "find-up": "^3.0.0" - } - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" - }, - "infima": { - "version": "0.2.0-alpha.23", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.23.tgz", - "integrity": "sha512-V0RTjB1otjpH3E2asbydx3gz7ovdSJsuV7r9JTdBggqRilnelTJUcXxLawBQQKsjQi5qPcRTjxnlaV8xyyKhhw==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "inline-style-parser": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", - "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" - }, - "internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "requires": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" - }, - "is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "requires": { - "ci-info": "^2.0.0" - }, - "dependencies": { - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - } - } - }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - }, - "dependencies": { - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" - } - } - }, - "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==" - }, - "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" - }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" - }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==" - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==" - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" - }, - "is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "requires": { - "is-path-inside": "^2.1.0" - }, - "dependencies": { - "is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "requires": { - "path-is-inside": "^1.0.2" - } - } - } - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - } - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, - "is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==" - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-whitespace-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", - "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==" - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "is-word-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", - "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==" - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "requires": { - "is-docker": "^2.0.0" - } - }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "joi": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", - "integrity": "sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==", - "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.0", - "@sideway/formula": "^3.0.0", - "@sideway/pinpoint": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==" - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "requires": { - "json-buffer": "3.0.0" - } - }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==" - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" - }, - "klona": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", - "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==" - }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "requires": { - "package-json": "^6.3.0" - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" - }, - "lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" - }, - "lodash.curry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, - "lodash.flow": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", - "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" - }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" - }, - "lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" - }, - "lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" - }, - "lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" - }, - "lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" - }, - "lodash.toarray": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "requires": { - "tslib": "^2.0.3" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "requires": { - "object-visit": "^1.0.0" - } - }, - "markdown-escapes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", - "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" - }, - "mdast-squeeze-paragraphs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", - "integrity": "sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==", - "requires": { - "unist-util-remove": "^2.0.0" - } - }, - "mdast-util-definitions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", - "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", - "requires": { - "unist-util-visit": "^2.0.0" - } - }, - "mdast-util-to-hast": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz", - "integrity": "sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==", - "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "mdast-util-definitions": "^4.0.0", - "mdurl": "^1.0.0", - "unist-builder": "^2.0.0", - "unist-util-generated": "^1.0.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^2.0.0" - } - }, - "mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==" - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - } - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "microevent.ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", - "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==" - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" - }, - "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "requires": { - "mime-db": "1.47.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, - "mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "requires": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - } - }, - "mini-css-extract-plugin": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.0.tgz", - "integrity": "sha512-nPFKI7NSy6uONUo9yn2hIfb9vyYvkFu95qki0e21DQ9uaqNKDP15DGpK0KnV6wDroWxPHtExrdEwx/yDQ8nVRw==", - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0", - "webpack-sources": "^1.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - } - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "module-alias": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", - "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" - }, - "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", - "optional": true - }, - "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==" - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node-emoji": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", - "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", - "requires": { - "lodash.toarray": "^4.4.0" - } - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" - }, - "node-releases": { - "version": "1.1.72", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", - "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - }, - "dependencies": { - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - } - } - }, - "nprogress": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E=" - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==" - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", - "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" - } - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "requires": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - } - }, - "opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" - }, - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "requires": { - "is-wsl": "^1.1.0" - }, - "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" - } - } - }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "requires": { - "url-parse": "^1.4.3" - } - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "requires": { - "retry": "^0.12.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "requires": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse-numeric-range": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.2.0.tgz", - "integrity": "sha512-1q2tXpAOplPxcl8vrIGPWz1dJxxfmdRkCFcpxxMBerDnGuuHalOWF/xj9L8Nn5XoTUoB/6F0CeQBp2fMgkOYFg==" - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "picomatch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==" - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "requires": { - "find-up": "^4.0.0" - } - }, - "pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - } - } - }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" - }, - "postcss": { - "version": "8.2.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz", - "integrity": "sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==", - "requires": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-calc": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.0.0.tgz", - "integrity": "sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==", - "requires": { - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - } - }, - "postcss-colormin": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.2.0.tgz", - "integrity": "sha512-+HC6GfWU3upe5/mqmxuqYZ9B2Wl4lcoUUNkoaX59nEWV4EtADCMiBqui111Bu8R8IvaZTmqmxrqOAqjbHIwXPw==", - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.0.1", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-convert-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.0.1.tgz", - "integrity": "sha512-C3zR1Do2BkKkCgC0g3sF8TS0koF2G+mN8xxayZx3f10cIRmTaAnpgpRQZjNekTZxM2ciSPoh2IWJm0VZx8NoQg==", - "requires": { - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-discard-comments": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", - "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", - "requires": {} - }, - "postcss-discard-duplicates": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz", - "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==", - "requires": {} - }, - "postcss-discard-empty": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz", - "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==", - "requires": {} - }, - "postcss-discard-overridden": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz", - "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==", - "requires": {} - }, - "postcss-discard-unused": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-5.0.1.tgz", - "integrity": "sha512-tD6xR/xyZTwfhKYRw0ylfCY8wbfhrjpKAMnDKRTLMy2fNW5hl0hoV6ap5vo2JdCkuHkP3CHw72beO4Y8pzFdww==", - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-loader": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-5.2.0.tgz", - "integrity": "sha512-uSuCkENFeUaOYsKrXm0eNNgVIxc71z8RcckLMbVw473rGojFnrUeqEz6zBgXsH2q1EIzXnO/4pEz9RhALjlITA==", - "requires": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.4", - "semver": "^7.3.4" - } - }, - "postcss-merge-idents": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-5.0.1.tgz", - "integrity": "sha512-xu8ueVU0RszbI2gKkxR6mluupsOSSLvt8q4gA2fcKFkA+x6SlH3cb4cFHpDvcRCNFbUmCR/VUub+Y6zPOjPx+Q==", - "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-merge-longhand": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.0.2.tgz", - "integrity": "sha512-BMlg9AXSI5G9TBT0Lo/H3PfUy63P84rVz3BjCFE9e9Y9RXQZD3+h3YO1kgTNsNJy7bBc1YQp8DmSnwLIW5VPcw==", - "requires": { - "css-color-names": "^1.0.1", - "postcss-value-parser": "^4.1.0", - "stylehacks": "^5.0.1" - } - }, - "postcss-merge-rules": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.0.2.tgz", - "integrity": "sha512-5K+Md7S3GwBewfB4rjDeol6V/RZ8S+v4B66Zk2gChRqLTCC8yjnHQ601omj9TKftS19OPGqZ/XzoqpzNQQLwbg==", - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^2.0.1", - "postcss-selector-parser": "^6.0.5", - "vendors": "^1.0.3" - } - }, - "postcss-minify-font-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz", - "integrity": "sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA==", - "requires": { - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-minify-gradients": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.0.1.tgz", - "integrity": "sha512-odOwBFAIn2wIv+XYRpoN2hUV3pPQlgbJ10XeXPq8UY2N+9ZG42xu45lTn/g9zZ+d70NKSQD6EOi6UiCMu3FN7g==", - "requires": { - "cssnano-utils": "^2.0.1", - "is-color-stop": "^1.1.0", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-minify-params": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.0.1.tgz", - "integrity": "sha512-4RUC4k2A/Q9mGco1Z8ODc7h+A0z7L7X2ypO1B6V8057eVK6mZ6xwz6QN64nHuHLbqbclkX1wyzRnIrdZehTEHw==", - "requires": { - "alphanum-sort": "^1.0.2", - "browserslist": "^4.16.0", - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0", - "uniqs": "^2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz", - "integrity": "sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og==", - "requires": { - "alphanum-sort": "^1.0.2", - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" - }, - "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "requires": { - "icss-utils": "^5.0.0" - } - }, - "postcss-normalize-charset": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz", - "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==", - "requires": {} - }, - "postcss-normalize-display-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz", - "integrity": "sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ==", - "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-normalize-positions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz", - "integrity": "sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg==", - "requires": { - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz", - "integrity": "sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w==", - "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-normalize-string": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz", - "integrity": "sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA==", - "requires": { - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz", - "integrity": "sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q==", - "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-normalize-unicode": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz", - "integrity": "sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA==", - "requires": { - "browserslist": "^4.16.0", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-normalize-url": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.0.1.tgz", - "integrity": "sha512-hkbG0j58Z1M830/CJ73VsP7gvlG1yF+4y7Fd1w4tD2c7CaA2Psll+pQ6eQhth9y9EaqZSLzamff/D0MZBMbYSg==", - "requires": { - "is-absolute-url": "^3.0.3", - "normalize-url": "^4.5.0", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-normalize-whitespace": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz", - "integrity": "sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA==", - "requires": { - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-ordered-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.0.1.tgz", - "integrity": "sha512-6mkCF5BQ25HvEcDfrMHCLLFHlraBSlOXFnQMHYhSpDO/5jSR1k8LdEXOkv+7+uzW6o6tBYea1Km0wQSRkPJkwA==", - "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-reduce-idents": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-5.0.1.tgz", - "integrity": "sha512-6Rw8iIVFbqtaZExgWK1rpVgP7DPFRPh0DDFZxJ/ADNqPiH10sPCoq5tgo6kLiTyfh9sxjKYjXdc8udLEcPOezg==", - "requires": { - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-reduce-initial": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz", - "integrity": "sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw==", - "requires": { - "browserslist": "^4.16.0", - "caniuse-api": "^3.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz", - "integrity": "sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA==", - "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-sort-media-queries": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-3.9.10.tgz", - "integrity": "sha512-pyCWbMrpQq4WjcYFrcVAvxS/+iHnXK5pxa1SAm1s9U4HZjGYU4gkCHwbHbzJ2ZFiiRYpRNRp85QuFvg6ZyKHxw==", - "requires": { - "sort-css-media-queries": "1.5.4" - } - }, - "postcss-svgo": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.0.2.tgz", - "integrity": "sha512-YzQuFLZu3U3aheizD+B1joQ94vzPfE6BNUcSYuceNxlVnKKsOtdo6hL9/zyC168Q8EwfLSgaDSalsUGa9f2C0A==", - "requires": { - "postcss-value-parser": "^4.1.0", - "svgo": "^2.3.0" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" - }, - "css-select": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz", - "integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^4.0.0", - "domhandler": "^4.0.0", - "domutils": "^2.4.3", - "nth-check": "^2.0.0" - } - }, - "css-what": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz", - "integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==" - }, - "dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" - }, - "domutils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", - "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "nth-check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", - "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", - "requires": { - "boolbase": "^1.0.0" - } - }, - "svgo": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.3.0.tgz", - "integrity": "sha512-fz4IKjNO6HDPgIQxu4IxwtubtbSfGEAJUq/IXyTPIkGhWck/faiiwfkvsB8LnBkKLvSoyNNIY6d13lZprJMc9Q==", - "requires": { - "@trysound/sax": "0.1.1", - "chalk": "^4.1.0", - "commander": "^7.1.0", - "css-select": "^3.1.2", - "css-tree": "^1.1.2", - "csso": "^4.2.0", - "stable": "^0.1.8" - } - } - } - }, - "postcss-unique-selectors": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.0.1.tgz", - "integrity": "sha512-gwi1NhHV4FMmPn+qwBNuot1sG1t2OmacLQ/AX29lzyggnjd+MnVD5uqQmpXO3J17KGL2WAxQruj1qTd3H0gG/w==", - "requires": { - "alphanum-sort": "^1.0.2", - "postcss-selector-parser": "^6.0.5", - "uniqs": "^2.0.0" - } - }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "postcss-zindex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-5.0.1.tgz", - "integrity": "sha512-nwgtJJys+XmmSGoYCcgkf/VczP8Mp/0OfSv3v0+fw0uABY4yxw+eFs0Xp9nAZHIKnS5j+e9ywQ+RD+ONyvl5pA==", - "requires": {} - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - }, - "pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", - "requires": { - "lodash": "^4.17.20", - "renderkid": "^2.0.4" - } - }, - "pretty-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", - "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==" - }, - "prism-react-renderer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.2.0.tgz", - "integrity": "sha512-GHqzxLYImx1iKN1jJURcuRoA/0ygCcNhfGw1IT8nPIMzarmKQ3Nc+JcG0gi8JXQzuh0C5ShE4npMIoqNin40hg==" - }, - "prismjs": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz", - "integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==", - "requires": { - "clipboard": "^2.0.0" - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "~2.0.3" - } - }, - "prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "property-information": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", - "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", - "requires": { - "xtend": "^4.0.0" - } - }, - "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "requires": { - "escape-goat": "^2.0.0" - } - }, - "pure-color": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", - "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "react-base16-styling": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", - "integrity": "sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=", - "requires": { - "base16": "^1.0.0", - "lodash.curry": "^4.0.1", - "lodash.flow": "^3.3.0", - "pure-color": "^1.2.0" - } - }, - "react-dev-utils": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", - "integrity": "sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A==", - "requires": { - "@babel/code-frame": "7.10.4", - "address": "1.1.2", - "browserslist": "4.14.2", - "chalk": "2.4.2", - "cross-spawn": "7.0.3", - "detect-port-alt": "1.1.6", - "escape-string-regexp": "2.0.0", - "filesize": "6.1.0", - "find-up": "4.1.0", - "fork-ts-checker-webpack-plugin": "4.1.6", - "global-modules": "2.0.0", - "globby": "11.0.1", - "gzip-size": "5.1.1", - "immer": "8.0.1", - "is-root": "2.1.0", - "loader-utils": "2.0.0", - "open": "^7.0.2", - "pkg-up": "3.1.0", - "prompts": "2.4.0", - "react-error-overlay": "^6.0.9", - "recursive-readdir": "2.2.2", - "shell-quote": "1.7.2", - "strip-ansi": "6.0.0", - "text-table": "0.2.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "browserslist": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", - "integrity": "sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==", - "requires": { - "caniuse-lite": "^1.0.30001125", - "electron-to-chromium": "^1.3.564", - "escalade": "^3.0.2", - "node-releases": "^1.1.61" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "requires": { - "address": "^1.0.1", - "debug": "^2.6.0" - } - }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - }, - "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - } - } - }, - "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - } - }, - "react-error-overlay": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", - "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" - }, - "react-fast-compare": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" - }, - "react-helmet": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", - "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", - "requires": { - "object-assign": "^4.1.1", - "prop-types": "^15.7.2", - "react-fast-compare": "^3.1.1", - "react-side-effect": "^2.1.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "react-json-view": { - "version": "1.21.3", - "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz", - "integrity": "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==", - "requires": { - "flux": "^4.0.1", - "react-base16-styling": "^0.6.0", - "react-lifecycles-compat": "^3.0.4", - "react-textarea-autosize": "^8.3.2" - } - }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, - "react-loadable": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz", - "integrity": "sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg==", - "requires": { - "prop-types": "^15.5.0" - } - }, - "react-loadable-ssr-addon-v5-slorber": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", - "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", - "requires": { - "@babel/runtime": "^7.10.3" - } - }, - "react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "requires": { - "isarray": "0.0.1" - } - } - } - }, - "react-router-config": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", - "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", - "requires": { - "@babel/runtime": "^7.1.2" - } - }, - "react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, - "react-side-effect": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.1.tgz", - "integrity": "sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==" - }, - "react-textarea-autosize": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.2.tgz", - "integrity": "sha512-JrMWVgQSaExQByP3ggI1eA8zF4mF0+ddVuX7acUeK2V7bmrpjVOY72vmLz2IXFJSAXoY3D80nEzrn0GWajWK3Q==", - "requires": { - "@babel/runtime": "^7.10.2", - "use-composed-ref": "^1.0.0", - "use-latest": "^1.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "requires": { - "picomatch": "^2.2.1" - } - }, - "reading-time": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.3.0.tgz", - "integrity": "sha512-RJ8J5O6UvrclfZpcPSPuKusrdRfoY7uXXoYOOdeswZNtSkQaewT3919yz6RyloDBR+iwcUyz5zGOUjhgvfuv3g==" - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "requires": { - "resolve": "^1.1.6" - } - }, - "recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", - "requires": { - "minimatch": "3.0.4" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "requires": { - "regenerate": "^1.4.0" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, - "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "requires": { - "rc": "^1.2.8" - } - }, - "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" - }, - "regjsparser": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", - "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" - } - } - }, - "rehype-parse": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.2.tgz", - "integrity": "sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug==", - "requires": { - "hast-util-from-parse5": "^5.0.0", - "parse5": "^5.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "hast-util-from-parse5": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", - "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", - "requires": { - "ccount": "^1.0.3", - "hastscript": "^5.0.0", - "property-information": "^5.0.0", - "web-namespaces": "^1.1.2", - "xtend": "^4.0.1" - } - }, - "hastscript": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", - "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", - "requires": { - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0" - } - }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - } - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" - }, - "remark-admonitions": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/remark-admonitions/-/remark-admonitions-1.2.1.tgz", - "integrity": "sha512-Ji6p68VDvD+H1oS95Fdx9Ar5WA2wcDA4kwrrhVU7fGctC6+d3uiMICu7w7/2Xld+lnU7/gi+432+rRbup5S8ow==", - "requires": { - "rehype-parse": "^6.0.2", - "unified": "^8.4.2", - "unist-util-visit": "^2.0.1" - }, - "dependencies": { - "unified": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-8.4.2.tgz", - "integrity": "sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==", - "requires": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - } - } - } - }, - "remark-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-2.2.0.tgz", - "integrity": "sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w==", - "requires": { - "emoticon": "^3.2.0", - "node-emoji": "^1.10.0", - "unist-util-visit": "^2.0.3" - } - }, - "remark-footnotes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-2.0.0.tgz", - "integrity": "sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==" - }, - "remark-mdx": { - "version": "1.6.22", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.22.tgz", - "integrity": "sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==", - "requires": { - "@babel/core": "7.12.9", - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-proposal-object-rest-spread": "7.12.1", - "@babel/plugin-syntax-jsx": "7.12.1", - "@mdx-js/util": "1.6.22", - "is-alphabetical": "1.0.4", - "remark-parse": "8.0.3", - "unified": "9.2.0" - }, - "dependencies": { - "@babel/core": { - "version": "7.12.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", - "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.5", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.7", - "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.9", - "@babel/types": "^7.12.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", - "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.12.1" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", - "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "remark-parse": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", - "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", - "requires": { - "ccount": "^1.0.0", - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^2.0.0", - "vfile-location": "^3.0.0", - "xtend": "^4.0.1" - } - }, - "remark-squeeze-paragraphs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz", - "integrity": "sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==", - "requires": { - "mdast-squeeze-paragraphs": "^4.0.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "renderkid": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.6.tgz", - "integrity": "sha512-GIis2GBr/ho0pFNf57D4XM4+PgnQuTii0WCPjEZmZfKivzUfGuRdjN2aQYtYMiNggHmNyBve+thFnNR1iBRcKg==", - "requires": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" - } - }, - "css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==" - }, - "dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" - }, - "domutils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", - "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "nth-check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", - "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", - "requires": { - "boolbase": "^1.0.0" - } - } - } - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=" - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "requires": { - "resolve-from": "^3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" - }, - "rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" - }, - "rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, - "rtl-detect": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.0.3.tgz", - "integrity": "sha512-2sMcZO60tL9YDEFe24gqddg3hJ+xSmJFN8IExcQUxeHxQzydQrN6GHPL+yAWgzItXSI7es53hcZC9pJneuZDKA==" - }, - "rtlcss": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-3.1.2.tgz", - "integrity": "sha512-b04YSX37siupPOWUEguEBReWX2w4QT89C0PI9g2JzZycbq7zrgPmTr1DA1pizSWpKRFdCjjnrx/SSvU4fOHmGg==", - "requires": { - "chalk": "^4.1.0", - "find-up": "^5.0.0", - "mkdirp": "^1.0.4", - "postcss": "^8.2.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "requires": { - "p-locate": "^5.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "requires": { - "p-limit": "^3.0.2" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - } - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "requires": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - } - }, - "select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", - "optional": true - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" - }, - "selfsigned": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", - "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", - "requires": { - "node-forge": "^0.10.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-handler": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", - "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", - "requires": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", - "mime-types": "2.1.18", - "minimatch": "3.0.4", - "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", - "range-parser": "1.2.0" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "~1.33.0" - } - }, - "path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - } - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - } - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" - }, - "shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" - }, - "sirv": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.11.tgz", - "integrity": "sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg==", - "requires": { - "@polka/url": "^1.0.0-next.9", - "mime": "^2.3.1", - "totalist": "^1.0.0" - }, - "dependencies": { - "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==" - } - } - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, - "sitemap": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-6.4.0.tgz", - "integrity": "sha512-DoPKNc2/apQZTUnfiOONWctwq7s6dZVspxAZe2VPMNtoqNq7HgXRvlRnbIpKjf+8+piQdWncwcy+YhhTGY5USQ==", - "requires": { - "@types/node": "^14.14.28", - "@types/sax": "^1.2.1", - "arg": "^5.0.0", - "sax": "^1.2.4" - }, - "dependencies": { - "@types/node": { - "version": "14.14.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.45.tgz", - "integrity": "sha512-DssMqTV9UnnoxDWu959sDLZzfvqCF0qDNRjaWeYSui9xkFe61kKo4l1TWNTQONpuXEm+gLMRvdlzvNHBamzmEw==" - } - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", - "requires": { - "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", - "websocket-driver": "^0.7.4" - } - }, - "sockjs-client": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz", - "integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==", - "requires": { - "debug": "^3.2.6", - "eventsource": "^1.0.7", - "faye-websocket": "^0.11.3", - "inherits": "^2.0.4", - "json3": "^3.3.3", - "url-parse": "^1.5.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "sort-css-media-queries": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-1.5.4.tgz", - "integrity": "sha512-YP5W/h4Sid/YP7Lp87ejJ5jP13/Mtqt2vx33XyhO+IAugKlufRPbOrPlIiEUuxmpNBSBd3EeeQpFhdu3RfI2Ag==" - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" - }, - "space-separated-tokens": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", - "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" - }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - } - }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "requires": { - "extend-shallow": "^3.0.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "state-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", - "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "std-env": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.3.0.tgz", - "integrity": "sha512-4qT5B45+Kjef2Z6pE0BkskzsH0GO7GrND0wGlTM1ioUe3v0dGYx9ZJH0Aro/YyA8fqQ5EyIKDRjZojJYMFTflw==", - "requires": { - "ci-info": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - } - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "dependencies": { - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" - } - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - } - } - }, - "strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=" - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "style-to-object": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", - "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", - "requires": { - "inline-style-parser": "0.1.1" - } - }, - "stylehacks": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz", - "integrity": "sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA==", - "requires": { - "browserslist": "^4.16.0", - "postcss-selector-parser": "^6.0.4" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - } - }, - "css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==" - }, - "terser": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz", - "integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==", - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - } - } - }, - "terser-webpack-plugin": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.2.tgz", - "integrity": "sha512-6QhDaAiVHIQr5Ab3XUWZyDmrIPCHMiqJVljMF91YKyqwKkL5QHnYMkrMBy96v9Z7ev1hGhSEw1HQZc2p/s5Z8Q==", - "requires": { - "jest-worker": "^26.6.2", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.7.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" - }, - "tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", - "optional": true - }, - "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==" - }, - "trim": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" - }, - "trim-trailing-lines": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", - "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==" - }, - "trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==" - }, - "ts-essentials": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz", - "integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==" - }, - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "ua-parser-js": { - "version": "0.7.28", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", - "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==" - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "unherit": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", - "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", - "requires": { - "inherits": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" - }, - "unified": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", - "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", - "requires": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" - } - } - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "unist-builder": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", - "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==" - }, - "unist-util-generated": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", - "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==" - }, - "unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==" - }, - "unist-util-position": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", - "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==" - }, - "unist-util-remove": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.1.0.tgz", - "integrity": "sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==", - "requires": { - "unist-util-is": "^4.0.0" - } - }, - "unist-util-remove-position": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", - "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", - "requires": { - "unist-util-visit": "^2.0.0" - } - }, - "unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", - "requires": { - "@types/unist": "^2.0.2" - } - }, - "unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - } - }, - "unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" - }, - "update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "requires": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - } - } - }, - "url-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", - "requires": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.27", - "schema-utils": "^3.0.0" - } - }, - "url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "requires": { - "prepend-http": "^2.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" - }, - "use-composed-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.1.0.tgz", - "integrity": "sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg==", - "requires": { - "ts-essentials": "^2.0.3" - } - }, - "use-isomorphic-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz", - "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==" - }, - "use-latest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz", - "integrity": "sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==", - "requires": { - "use-isomorphic-layout-effect": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" - }, - "utility-types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", - "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" - }, - "vfile": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", - "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", - "requires": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" - } - } - }, - "vfile-location": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.2.0.tgz", - "integrity": "sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==" - }, - "vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", - "requires": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - } - }, - "wait-on": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz", - "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==", - "requires": { - "axios": "^0.21.1", - "joi": "^17.3.0", - "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^6.6.3" - } - }, - "watchpack": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", - "integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==", - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "web-namespaces": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz", - "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==" - }, - "webpack": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.37.0.tgz", - "integrity": "sha512-yvdhgcI6QkQkDe1hINBAJ1UNevqNGTVaCkD2SSJcB8rcrNNl922RI8i2DXUAuNfANoxwsiXXEA4ZPZI9q2oGLA==", - "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.47", - "@webassemblyjs/ast": "1.11.0", - "@webassemblyjs/wasm-edit": "1.11.0", - "@webassemblyjs/wasm-parser": "1.11.0", - "acorn": "^8.2.1", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.0", - "es-module-lexer": "^0.4.0", - "eslint-scope": "^5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.1", - "watchpack": "^2.0.0", - "webpack-sources": "^2.1.1" - } - }, - "webpack-bundle-analyzer": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz", - "integrity": "sha512-j5m7WgytCkiVBoOGavzNokBOqxe6Mma13X1asfVYtKWM3wxBiRRu1u1iG0Iol5+qp9WgyhkMmBAcvjEfJ2bdDw==", - "requires": { - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", - "commander": "^6.2.0", - "gzip-size": "^6.0.0", - "lodash": "^4.17.20", - "opener": "^1.5.2", - "sirv": "^1.0.7", - "ws": "^7.3.1" - }, - "dependencies": { - "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" - }, - "gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "requires": { - "duplexer": "^0.1.2" - } - } - } - }, - "webpack-dev-middleware": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", - "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", - "requires": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "dependencies": { - "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==" - } - } - }, - "webpack-dev-server": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz", - "integrity": "sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==", - "requires": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.8", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "sockjs-client": "^1.5.0", - "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "requires": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "requires": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - } - }, - "webpack-merge": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", - "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "webpack-sources": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz", - "integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==", - "requires": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "webpackbar": { - "version": "5.0.0-3", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.0-3.tgz", - "integrity": "sha512-viW6KCYjMb0NPoDrw2jAmLXU2dEOhRrtku28KmOfeE1vxbfwCYuTbTaMhnkrCZLFAFyY9Q49Z/jzYO80Dw5b8g==", - "requires": { - "ansi-escapes": "^4.3.1", - "chalk": "^4.1.0", - "consola": "^2.15.0", - "figures": "^3.2.0", - "pretty-time": "^1.1.0", - "std-env": "^2.2.1", - "text-table": "^0.2.0", - "wrap-ansi": "^7.0.0" - } - }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "requires": { - "string-width": "^4.0.0" - } - }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" - }, - "worker-rpc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", - "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", - "requires": { - "microevent.ts": "~0.1.1" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" - }, - "xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "requires": { - "sax": "^1.2.4" - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - } - } - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" - }, - "zwitch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", - "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==" - } - } -} diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index 9553a203d1eb..000000000000 --- a/docs/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "website", - "version": "0.0.0", - "private": true, - "scripts": { - "start": "docusaurus start", - "build": "docusaurus build", - "themes": "node export_themes.js" - }, - "dependencies": { - "@docusaurus/core": "2.0.0-beta.0", - "@docusaurus/preset-classic": "2.0.0-beta.0", - "classnames": "^2.3.1", - "react": "^17.0.2", - "react-dom": "^17.0.2" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/docs/sidebars.js b/docs/sidebars.js deleted file mode 100644 index 3f9d6acb3201..000000000000 --- a/docs/sidebars.js +++ /dev/null @@ -1,70 +0,0 @@ -module.exports = { - docs: [ - { - type: "category", - label: "Getting Started", - items: [ - "introduction", - "upgrading", - { - type: "category", - label: "🚀 Installation", - items: [ - "pwsh", - "windows", - "macos", - "linux", - ], - }, - "configure", - "beta", - "themes", - "share", - "fonts", - "faq" - ], - }, - { - type: "category", - label: "Segments", - items: [ - "aws", - "az", - "azfunc", - "battery", - "command", - "crystal", - "dart", - "dotnet", - "environment", - "executiontime", - "exit", - "git", - "poshgit", - "golang", - "java", - "julia", - "kubectl", - "nbgv", - "node", - "os", - "path", - "python", - "root", - "ruby", - "session", - "shell", - "spotify", - "terraform", - "text", - "time", - "ytm", - ] - }, - { - type: "category", - label: "Contributing", - items: ["contributing_started", "contributing_segment", "contributing_git"], - }, - ], -}; diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css deleted file mode 100644 index 0f64d763195e..000000000000 --- a/docs/src/css/custom.css +++ /dev/null @@ -1,37 +0,0 @@ -/* stylelint-disable docusaurus/copyright-header */ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ - -/* You can override the default Infima variables here. */ -:root { - --ifm-color-primary: #2c7ae0; - --ifm-color-primary-dark: rgb(38, 103, 189); - --ifm-color-primary-darker: rgb(28, 75, 138); - --ifm-color-primary-darkest: rgb(19, 51, 94); - --ifm-color-primary-light: rgb(74, 143, 232); - --ifm-color-primary-lighter: rgb(95, 157, 237); - --ifm-color-primary-lightest: rgb(130, 185, 255); - --ifm-code-font-size: 95%; -} - -.docusaurus-highlight-code-line { - background-color: rgb(72, 77, 91); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); -} - -.badge { - display: none !important; -} - -.hero { - background-color: #173448; -} - -.hero--image { - margin-top: 10px; -} diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico deleted file mode 100644 index 68bca5250908..000000000000 Binary files a/docs/static/img/favicon.ico and /dev/null differ diff --git a/docs/static/img/hero.png b/docs/static/img/hero.png deleted file mode 100644 index ee345ebc9e62..000000000000 Binary files a/docs/static/img/hero.png and /dev/null differ diff --git a/docs/static/img/recommended_extensions.png b/docs/static/img/recommended_extensions.png deleted file mode 100644 index c4b8d74925dc..000000000000 Binary files a/docs/static/img/recommended_extensions.png and /dev/null differ diff --git a/packages/inno/build.ps1 b/packages/inno/build.ps1 deleted file mode 100644 index 53eee78e7b17..000000000000 --- a/packages/inno/build.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -Param -( - [parameter(Mandatory = $true)] - [string] - $Version -) - -New-Item -Path "." -Name "bin" -ItemType Directory -Copy-Item -Path "../../themes" -Destination "./bin" -Recurse - -# download the files and pack them -@{name = 'posh-windows-amd64.exe' }, @{name = 'posh-linux-amd64' }, @{name = 'posh-windows-386.exe' } | ForEach-Object -Process { - $download = "https://github.com/jandedobbeleer/oh-my-posh/releases/download/v$Version/$($_.name)" - Invoke-WebRequest $download -Out "./bin/$($_.name)" -} -# license -Invoke-WebRequest "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/v$Version/COPYING" -Out "./bin/COPYING.txt" -$content = Get-Content '.\oh-my-posh.iss' -Raw -$content = $content.Replace('', $Version) -$content | Out-File -Encoding 'UTF8' ".oh-my-posh-$Version.iss" -# package content -ISCC.exe ".oh-my-posh-$Version.iss" -# get hash -$zipHash = Get-FileHash 'Output/install.exe' -Algorithm SHA256 -$zipHash.Hash | Out-File -Encoding 'UTF8' 'Output/install.exe.sha256' diff --git a/packages/inno/oh-my-posh.iss b/packages/inno/oh-my-posh.iss deleted file mode 100644 index d0e76b0a13d4..000000000000 --- a/packages/inno/oh-my-posh.iss +++ /dev/null @@ -1,38 +0,0 @@ -[Setup] -AppName=Oh My Posh -AppVersion= -DefaultDirName={autopf}\oh-my-posh -DefaultGroupName=Oh My Posh -PrivilegesRequired=lowest -AppPublisher=Jan De Dobbeleer -AppPublisherURL=https://ohmyposh.dev -AppSupportURL=https://github.com/JanDeDobbeleer/oh-my-posh/issues -LicenseFile="bin\COPYING.txt" -OutputBaseFilename=install - -[Files] -Source: "bin\posh-windows-amd64.exe"; DestDir: "{app}\bin"; DestName: "oh-my-posh.exe"; Flags: 64bit -Source: "bin\posh-windows-386.exe"; DestDir: "{app}\bin"; DestName: "oh-my-posh.exe"; Flags: 32bit -Source: "bin\posh-linux-amd64"; DestDir: "{app}\bin"; DestName: "oh-my-posh-wsl"; Flags: 64bit -Source: "bin\themes\*"; DestDir: "{app}\themes" - -[Registry] -Root: "HKCU"; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Check: NeedsAddPathHKCU(ExpandConstant('{app}\bin')) -Root: "HKCU"; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\themes"; Check: NeedsAddPathHKCU(ExpandConstant('{app}\themes')) - -[Code] -function NeedsAddPathHKCU(Param: string): boolean; -var -OrigPath: string; -begin -if not RegQueryStringValue(HKEY_CURRENT_USER, -'Environment', -'Path', OrigPath) -then begin -Result := True; -exit; -end; -// look for the path with leading and trailing semicolon -// Pos() returns 0 if not found -Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0; -end; diff --git a/packages/msi/README.md b/packages/msi/README.md new file mode 100644 index 000000000000..4c4459156397 --- /dev/null +++ b/packages/msi/README.md @@ -0,0 +1,39 @@ +# MSI Package + +## Prerequisites + +- [dotnet] +- [wix]: `dotnet tool install --global wix` + +## Build the package + +This guide assumes and advices the use of PowerShell as your shell environment for this purpose. + +### Set the environment variables + +```powershell +$env:VERSION = "1.3.37" +``` + +### Build the installer + +```powershell +wix build -arch arm64 -out install-arm64.msi +``` + +## Install the package + +### For the current user + +```powershell +install-arm64.msi +``` + +### For all users + +```powershell +install-arm64.msi ALLUSERS=1 +``` + +[dotnet]: https://dotnet.microsoft.com/en-us/download/dotnet?cid=getdotnetcorecli +[wix]: https://wixtoolset.org/docs/intro/ diff --git a/packages/msi/appxmanifest.xml b/packages/msi/appxmanifest.xml new file mode 100644 index 000000000000..3a3172e30497 --- /dev/null +++ b/packages/msi/appxmanifest.xml @@ -0,0 +1,34 @@ + + + + + Oh My Posh + Jan Joris De Dobbeleer + A prompt theme engine for any shell. + icons\icon.png + disabled + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/msi/build.ps1 b/packages/msi/build.ps1 new file mode 100644 index 000000000000..5f522af8c132 --- /dev/null +++ b/packages/msi/build.ps1 @@ -0,0 +1,268 @@ +<# +.SYNOPSIS + Builds MSI and MSIX packages for Oh My Posh. + +.DESCRIPTION + This script creates MSI and MSIX installer packages for Oh My Posh with the specified architecture and version. + It can optionally copy the executable, sign the packages, and generate hash files for verification. + +.PARAMETER Architecture + The target architecture for the package. Must be either 'x64' or 'arm64'. + +.PARAMETER Version + The version number to assign to the package (e.g., "1.2.3"). + +.PARAMETER SDKVersion + The Windows SDK version to use for signing and packaging tools. Defaults to "10.0.26100.0". + +.PARAMETER Sign + When specified, signs the MSI and MSIX packages using Azure Code Signing. + +.PARAMETER Copy + When specified, copies the appropriate executable from the dist folder before packaging. + +.EXAMPLE + .\build.ps1 -Architecture x64 -Version "1.2.3" -Copy + + Creates MSI and MSIX packages for x64 architecture with version 1.2.3, copying the executable first. + +.EXAMPLE + .\build.ps1 -Architecture arm64 -Version "1.2.3" -Sign -Copy + + Creates and signs MSI and MSIX packages for arm64 architecture with version 1.2.3. + +.OUTPUTS + Creates the following files in the 'out' directory: + - install-{Architecture}.msi + - install-{Architecture}.msix + - Hash files (.sha256) for verification + +.NOTES + Requires WiX toolset for MSI creation and Windows SDK for MSIX packaging and signing. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [ValidateSet('x64', 'arm64')] + [string]$Architecture, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$Version, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$SDKVersion = "10.0.26100.0", + + [Parameter()] + [switch]$Sign, + + [Parameter()] + [switch]$Copy +) + +# Set error handling preferences +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $true +$PSDefaultParameterValues['Out-File:Encoding'] = 'UTF8' + +#region Helper Functions + +function Initialize-SigningEnvironment { + <# + .SYNOPSIS + Sets up the signing environment and returns signing tool paths. + + .PARAMETER SDKVersion + The Windows SDK version to use. + + .OUTPUTS + Hashtable containing signtool and signtoolDlib paths. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$SDKVersion + ) + + try { + Write-Verbose "Setting up signing environment" -Verbose + + # Install Microsoft.Trusted.Signing.Client + nuget.exe install Microsoft.Trusted.Signing.Client -Version 1.0.92 -x | Out-Null + + $signtoolDlib = "$PWD/Microsoft.Trusted.Signing.Client/bin/x64/Azure.CodeSigning.Dlib.dll" -replace '\\', '/' + $signtool = "C:/Program Files (x86)/Windows Kits/10/bin/$SDKVersion/x64/signtool.exe" -replace '\\', '/' + + # Validate tools exist + if (-not (Test-Path $signtool)) { + throw "signtool.exe not found at: $signtool" + } + if (-not (Test-Path $signtoolDlib)) { + throw "Azure.CodeSigning.Dlib.dll not found at: $signtoolDlib" + } + + # Explicitly create and return a hashtable + [hashtable]$result = @{ + SignTool = $signtool + SignToolDlib = $signtoolDlib + } + + return $result + } + catch { + Write-Error "Failed to initialize signing environment: $_" + throw + } +} + +function Invoke-PackageSigning { + <# + .SYNOPSIS + Signs a package using Azure Code Signing. + + .PARAMETER PackagePath + The path to the package to sign. + + .PARAMETER SigningTools + Hashtable containing signing tool paths from Initialize-SigningEnvironment. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateScript({Test-Path $_})] + [string]$PackagePath, + + [Parameter(Mandatory = $true)] + [hashtable]$SigningTools + ) + + try { + $packageName = Split-Path $PackagePath -Leaf + Write-Verbose "Signing package: $packageName" -Verbose + + & $SigningTools.SignTool sign /v /debug /d "Oh My Posh" /fd SHA256 /tr 'http://timestamp.acs.microsoft.com' /td SHA256 /dlib $SigningTools.SignToolDlib /dmdf ../../src/metadata.json $PackagePath + + Write-Verbose "Successfully signed: $packageName" -Verbose + } + catch { + Write-Error "Failed to sign package ${PackagePath}: ${_}" + throw + } +} + +#endregion + +#region Main Script + +Write-Verbose "Building MSI for $Architecture with version $Version" -Verbose +Write-Verbose "Setting up output directories" -Verbose + +try { + New-Item -Path "." -Name "dist" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null + New-Item -Path "." -Name "out" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null +} +catch { + Write-Error "Failed to create output directories: ${_}" + throw +} + +if ($Copy) { + $sourceFile = switch ($Architecture) { + 'x64' { "posh-windows-amd64.exe" } + Default { "posh-windows-$Architecture.exe" } + } + + Write-Verbose "Copying $sourceFile to ./dist/oh-my-posh.exe" -Verbose + + try { + $sourcePath = "../../dist/$sourceFile" + if (-not (Test-Path $sourcePath)) { + throw "Source file not found: $sourcePath" + } + Copy-Item -Path $sourcePath -Destination "./dist/oh-my-posh.exe" -Force + } + catch { + Write-Error "Failed to copy executable: $_" + throw + } +} + +# Set version environment variable for WiX +$env:VERSION = $Version + +Write-Verbose "Creating MSI package" -Verbose + +try { + # Define MSI package paths + $msiFileName = "install-$Architecture.msi" + $msiPackagePath = "$PWD/out/$msiFileName" -replace '\\', '/' + + Write-Verbose "Building MSI: $msiPackagePath" -Verbose + wix build -acceptEula wix7 -arch $Architecture -out $msiPackagePath .\oh-my-posh.wxs + + if (-not (Test-Path $msiPackagePath)) { + throw "MSI package was not created successfully" + } +} +catch { + Write-Error "Failed to create MSI package: ${_}" + throw +} + +if ($Sign) { + $signingTools = Initialize-SigningEnvironment -SDKVersion $SDKVersion + Invoke-PackageSigning -PackagePath $msiPackagePath -SigningTools $signingTools +} + +Write-Verbose "Creating MSIX package" -Verbose + +try { + # Define MSIX package paths and files + $currentPath = $PWD -replace '\\', '/' + $manifestPath = "$currentPath/appxmanifest.xml" + $mappingFilePath = "$currentPath/mapping.txt" + $msixPackagePath = "$currentPath/out/$($msiFileName)x" + $makeappxPath = "C:/Program Files (x86)/Windows Kits/10/bin/$SDKVersion/x64/makeappx.exe" + + # Validate required files exist + if (-not (Test-Path $manifestPath)) { + throw "Manifest file not found: $manifestPath" + } + if (-not (Test-Path $mappingFilePath)) { + throw "Mapping file not found: $mappingFilePath" + } + if (-not (Test-Path $makeappxPath)) { + throw "makeappx.exe not found at: $makeappxPath" + } + + # Update manifest with version and architecture + [xml]$manifestDocument = Get-Content $manifestPath + $manifestDocument.Package.Identity.Version = "$Version.0" + $manifestDocument.Package.Identity.ProcessorArchitecture = $Architecture + $manifestDocument.Save($manifestPath) + + # Build MSIX package + Write-Verbose "Building MSIX: $msixPackagePath" -Verbose + & "$makeappxPath" pack /p $msixPackagePath /v /o /m $manifestPath /f $mappingFilePath + + if (-not (Test-Path $msixPackagePath)) { + throw "MSIX package was not created successfully" + } +} +catch { + Write-Error "Failed to create MSIX package: ${_}" + throw +} + +if ($Sign) { + if ($null -eq $signingTools) { + $signingTools = Initialize-SigningEnvironment -SDKVersion $SDKVersion + } + Invoke-PackageSigning -PackagePath $msixPackagePath -SigningTools $signingTools +} + +Write-Verbose "Successfully completed building MSI and MSIX packages" -Verbose + +#endregion diff --git a/packages/msi/dsc/oh-my-posh.config.dsc.resource.json b/packages/msi/dsc/oh-my-posh.config.dsc.resource.json new file mode 100644 index 000000000000..b7fb90677145 --- /dev/null +++ b/packages/msi/dsc/oh-my-posh.config.dsc.resource.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.vscode.json", + "description": "Allows configuring the Oh My Posh config files.", + "export": { + "executable": "oh-my-posh", + "input": "stdin", + "args": [ + "config", + "dsc", + "export" + ] + }, + "get": { + "executable": "oh-my-posh", + "input": "stdin", + "args": [ + "config", + "dsc", + "get" + ] + }, + "schema": { + "command": { + "executable": "oh-my-posh", + "args": [ + "config", + "dsc", + "schema" + ] + } + }, + "set": { + "executable": "oh-my-posh", + "implementsPretest": true, + "args": [ + "config", + "dsc", + "set", + { + "jsonInputArg": "--state", + "mandatory": true + } + ] + }, + "tags": [ + "OhMyPosh", + "linux", + "macos", + "windows", + "shell", + "powershell", + "terminal", + "theming", + "configuration" + ], + "type": "OhMyPosh/Config", + "version": "0.1.0" +} diff --git a/packages/msi/dsc/oh-my-posh.font.dsc.resource.json b/packages/msi/dsc/oh-my-posh.font.dsc.resource.json new file mode 100644 index 000000000000..b2efd7a49f59 --- /dev/null +++ b/packages/msi/dsc/oh-my-posh.font.dsc.resource.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.vscode.json", + "description": "Allows configuring the Oh My Posh font installs.", + "export": { + "executable": "oh-my-posh", + "input": "stdin", + "args": [ + "font", + "dsc", + "export" + ] + }, + "get": { + "executable": "oh-my-posh", + "input": "stdin", + "args": [ + "font", + "dsc", + "get" + ] + }, + "schema": { + "command": { + "executable": "oh-my-posh", + "args": [ + "font", + "dsc", + "schema" + ] + } + }, + "set": { + "executable": "oh-my-posh", + "implementsPretest": true, + "args": [ + "font", + "dsc", + "set", + { + "jsonInputArg": "--state", + "mandatory": true + } + ] + }, + "tags": [ + "OhMyPosh", + "linux", + "macos", + "windows", + "powershell", + "terminal", + "theming", + "fonts" + ], + "type": "OhMyPosh/Font", + "version": "0.1.0" +} diff --git a/packages/msi/dsc/oh-my-posh.shell.dsc.resource.json b/packages/msi/dsc/oh-my-posh.shell.dsc.resource.json new file mode 100644 index 000000000000..eef66dfd27fb --- /dev/null +++ b/packages/msi/dsc/oh-my-posh.shell.dsc.resource.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.vscode.json", + "description": "Allows configuring the Oh My Posh shell integration.", + "export": { + "executable": "oh-my-posh", + "input": "stdin", + "args": [ + "shell", + "dsc", + "export" + ] + }, + "get": { + "executable": "oh-my-posh", + "input": "stdin", + "args": [ + "shell", + "dsc", + "get" + ] + }, + "schema": { + "command": { + "executable": "oh-my-posh", + "args": [ + "shell", + "dsc", + "schema" + ] + } + }, + "set": { + "executable": "oh-my-posh", + "implementsPretest": true, + "args": [ + "shell", + "dsc", + "set", + { + "jsonInputArg": "--state", + "mandatory": true + } + ] + }, + "tags": [ + "OhMyPosh", + "linux", + "macos", + "windows", + "shell", + "powershell", + "terminal", + "theming" + ], + "type": "OhMyPosh/Shell", + "version": "0.1.0" +} diff --git a/packages/msi/icon.ico b/packages/msi/icon.ico new file mode 100644 index 000000000000..9bbf13c61d39 Binary files /dev/null and b/packages/msi/icon.ico differ diff --git a/packages/msi/icons/44.png b/packages/msi/icons/44.png new file mode 100644 index 000000000000..4d653ec50674 Binary files /dev/null and b/packages/msi/icons/44.png differ diff --git a/packages/msi/icons/icon.png b/packages/msi/icons/icon.png new file mode 100644 index 000000000000..e60c91afc254 Binary files /dev/null and b/packages/msi/icons/icon.png differ diff --git a/packages/msi/mapping.txt b/packages/msi/mapping.txt new file mode 100644 index 000000000000..5b143043c2e2 --- /dev/null +++ b/packages/msi/mapping.txt @@ -0,0 +1,135 @@ +[ResourceMetadata] +"ResourceDimensions" "language-en-us" +"ResourceId" "English" + +[Files] +"./dist/oh-my-posh.exe" "oh-my-posh.exe" +"./icons/icon.png" "/icons/icon.png" +"./icons/44.png" "/icons/44.png" +"./dsc/oh-my-posh.config.dsc.resource.json" "oh-my-posh.config.dsc.resource.json" +"./dsc/oh-my-posh.shell.dsc.resource.json" "oh-my-posh.shell.dsc.resource.json" +"./dsc/oh-my-posh.font.dsc.resource.json" "oh-my-posh.font.dsc.resource.json" +"../../themes/1_shell.omp.json" "/themes/1_shell.omp.json" +"../../themes/agnoster.minimal.omp.json" "/themes/agnoster.minimal.omp.json" +"../../themes/agnoster.omp.json" "/themes/agnoster.omp.json" +"../../themes/agnosterplus.omp.json" "/themes/agnosterplus.omp.json" +"../../themes/aliens.omp.json" "/themes/aliens.omp.json" +"../../themes/amro.omp.json" "/themes/amro.omp.json" +"../../themes/atomic.omp.json" "/themes/atomic.omp.json" +"../../themes/atomicBit.omp.json" "/themes/atomicBit.omp.json" +"../../themes/avit.omp.json" "/themes/avit.omp.json" +"../../themes/blue-owl.omp.json" "/themes/blue-owl.omp.json" +"../../themes/blueish.omp.json" "/themes/blueish.omp.json" +"../../themes/bubbles.omp.json" "/themes/bubbles.omp.json" +"../../themes/bubblesextra.omp.json" "/themes/bubblesextra.omp.json" +"../../themes/bubblesline.omp.json" "/themes/bubblesline.omp.json" +"../../themes/capr4n.omp.json" "/themes/capr4n.omp.json" +"../../themes/catppuccin.omp.json" "/themes/catppuccin.omp.json" +"../../themes/catppuccin_frappe.omp.json" "/themes/catppuccin_frappe.omp.json" +"../../themes/catppuccin_latte.omp.json" "/themes/catppuccin_latte.omp.json" +"../../themes/catppuccin_macchiato.omp.json" "/themes/catppuccin_macchiato.omp.json" +"../../themes/catppuccin_mocha.omp.json" "/themes/catppuccin_mocha.omp.json" +"../../themes/cert.omp.json" "/themes/cert.omp.json" +"../../themes/chips.omp.json" "/themes/chips.omp.json" +"../../themes/cinnamon.omp.json" "/themes/cinnamon.omp.json" +"../../themes/clean-detailed.omp.json" "/themes/clean-detailed.omp.json" +"../../themes/cloud-context.omp.json" "/themes/cloud-context.omp.json" +"../../themes/cloud-native-azure.omp.json" "/themes/cloud-native-azure.omp.json" +"../../themes/cobalt2.omp.json" "/themes/cobalt2.omp.json" +"../../themes/craver.omp.json" "/themes/craver.omp.json" +"../../themes/darkblood.omp.json" "/themes/darkblood.omp.json" +"../../themes/devious-diamonds.omp.yaml" "/themes/devious-diamonds.omp.yaml" +"../../themes/di4am0nd.omp.json" "/themes/di4am0nd.omp.json" +"../../themes/dracula.omp.json" "/themes/dracula.omp.json" +"../../themes/easy-term.omp.json" "/themes/easy-term.omp.json" +"../../themes/emodipt-extend.omp.json" "/themes/emodipt-extend.omp.json" +"../../themes/emodipt.omp.json" "/themes/emodipt.omp.json" +"../../themes/fish.omp.json" "/themes/fish.omp.json" +"../../themes/free-ukraine.omp.json" "/themes/free-ukraine.omp.json" +"../../themes/froczh.omp.json" "/themes/froczh.omp.json" +"../../themes/glowsticks.omp.yaml" "/themes/glowsticks.omp.yaml" +"../../themes/gmay.omp.json" "/themes/gmay.omp.json" +"../../themes/grandpa-style.omp.json" "/themes/grandpa-style.omp.json" +"../../themes/gruvbox.omp.json" "/themes/gruvbox.omp.json" +"../../themes/half-life.omp.json" "/themes/half-life.omp.json" +"../../themes/honukai.omp.json" "/themes/honukai.omp.json" +"../../themes/hotstick.minimal.omp.json" "/themes/hotstick.minimal.omp.json" +"../../themes/hul10.omp.json" "/themes/hul10.omp.json" +"../../themes/hunk.omp.json" "/themes/hunk.omp.json" +"../../themes/huvix.omp.json" "/themes/huvix.omp.json" +"../../themes/if_tea.omp.json" "/themes/if_tea.omp.json" +"../../themes/illusi0n.omp.json" "/themes/illusi0n.omp.json" +"../../themes/iterm2.omp.json" "/themes/iterm2.omp.json" +"../../themes/jandedobbeleer.omp.json" "/themes/jandedobbeleer.omp.json" +"../../themes/jblab_2021.omp.json" "/themes/jblab_2021.omp.json" +"../../themes/jonnychipz.omp.json" "/themes/jonnychipz.omp.json" +"../../themes/json.omp.json" "/themes/json.omp.json" +"../../themes/jtracey93.omp.json" "/themes/jtracey93.omp.json" +"../../themes/jv_sitecorian.omp.json" "/themes/jv_sitecorian.omp.json" +"../../themes/kali.omp.json" "/themes/kali.omp.json" +"../../themes/kushal.omp.json" "/themes/kushal.omp.json" +"../../themes/lambda.omp.json" "/themes/lambda.omp.json" +"../../themes/lambdageneration.omp.json" "/themes/lambdageneration.omp.json" +"../../themes/larserikfinholt.omp.json" "/themes/larserikfinholt.omp.json" +"../../themes/lightgreen.omp.json" "/themes/lightgreen.omp.json" +"../../themes/M365Princess.omp.json" "/themes/M365Princess.omp.json" +"../../themes/marcduiker.omp.json" "/themes/marcduiker.omp.json" +"../../themes/markbull.omp.json" "/themes/markbull.omp.json" +"../../themes/material.omp.json" "/themes/material.omp.json" +"../../themes/microverse-power.omp.json" "/themes/microverse-power.omp.json" +"../../themes/mojada.omp.json" "/themes/mojada.omp.json" +"../../themes/montys.omp.json" "/themes/montys.omp.json" +"../../themes/mt.omp.json" "/themes/mt.omp.json" +"../../themes/multiverse-neon.omp.json" "/themes/multiverse-neon.omp.json" +"../../themes/negligible.omp.json" "/themes/negligible.omp.json" +"../../themes/neko.omp.json" "/themes/neko.omp.json" +"../../themes/night-owl.omp.json" "/themes/night-owl.omp.json" +"../../themes/nordtron.omp.json" "/themes/nordtron.omp.json" +"../../themes/nu4a.omp.json" "/themes/nu4a.omp.json" +"../../themes/onehalf.minimal.omp.json" "/themes/onehalf.minimal.omp.json" +"../../themes/paradox.omp.json" "/themes/paradox.omp.json" +"../../themes/pararussel.omp.json" "/themes/pararussel.omp.json" +"../../themes/patriksvensson.omp.json" "/themes/patriksvensson.omp.json" +"../../themes/peru.omp.json" "/themes/peru.omp.json" +"../../themes/pixelrobots.omp.json" "/themes/pixelrobots.omp.json" +"../../themes/plague.omp.json" "/themes/plague.omp.json" +"../../themes/poshmon.omp.json" "/themes/poshmon.omp.json" +"../../themes/powerlevel10k_classic.omp.json" "/themes/powerlevel10k_classic.omp.json" +"../../themes/powerlevel10k_lean.omp.json" "/themes/powerlevel10k_lean.omp.json" +"../../themes/powerlevel10k_modern.omp.json" "/themes/powerlevel10k_modern.omp.json" +"../../themes/powerlevel10k_rainbow.omp.json" "/themes/powerlevel10k_rainbow.omp.json" +"../../themes/powerline.omp.json" "/themes/powerline.omp.json" +"../../themes/probua.minimal.omp.json" "/themes/probua.minimal.omp.json" +"../../themes/pure.omp.json" "/themes/pure.omp.json" +"../../themes/quick-term.omp.json" "/themes/quick-term.omp.json" +"../../themes/remk.omp.json" "/themes/remk.omp.json" +"../../themes/robbyrussell.omp.json" "/themes/robbyrussell.omp.json" +"../../themes/rudolfs-dark.omp.json" "/themes/rudolfs-dark.omp.json" +"../../themes/rudolfs-light.omp.json" "/themes/rudolfs-light.omp.json" +"../../themes/sim-web.omp.json" "/themes/sim-web.omp.json" +"../../themes/slim.omp.json" "/themes/slim.omp.json" +"../../themes/slimfat.omp.json" "/themes/slimfat.omp.json" +"../../themes/smoothie.omp.json" "/themes/smoothie.omp.json" +"../../themes/sonicboom_dark.omp.json" "/themes/sonicboom_dark.omp.json" +"../../themes/sonicboom_light.omp.json" "/themes/sonicboom_light.omp.json" +"../../themes/sorin.omp.json" "/themes/sorin.omp.json" +"../../themes/space.omp.json" "/themes/space.omp.json" +"../../themes/spaceship.omp.json" "/themes/spaceship.omp.json" +"../../themes/star.omp.json" "/themes/star.omp.json" +"../../themes/stelbent-compact.minimal.omp.json" "/themes/stelbent-compact.minimal.omp.json" +"../../themes/stelbent.minimal.omp.json" "/themes/stelbent.minimal.omp.json" +"../../themes/takuya.omp.json" "/themes/takuya.omp.json" +"../../themes/the-unnamed.omp.json" "/themes/the-unnamed.omp.json" +"../../themes/thecyberden.omp.json" "/themes/thecyberden.omp.json" +"../../themes/tiwahu.omp.json" "/themes/tiwahu.omp.json" +"../../themes/tokyo.omp.json" "/themes/tokyo.omp.json" +"../../themes/tokyonight_storm.omp.json" "/themes/tokyonight_storm.omp.json" +"../../themes/tonybaloney.omp.json" "/themes/tonybaloney.omp.json" +"../../themes/uew.omp.json" "/themes/uew.omp.json" +"../../themes/unicorn.omp.json" "/themes/unicorn.omp.json" +"../../themes/velvet.omp.json" "/themes/velvet.omp.json" +"../../themes/wholespace.omp.json" "/themes/wholespace.omp.json" +"../../themes/wopian.omp.json" "/themes/wopian.omp.json" +"../../themes/xtoys.omp.json" "/themes/xtoys.omp.json" +"../../themes/ys.omp.json" "/themes/ys.omp.json" +"../../themes/zash.omp.json" "/themes/zash.omp.json" diff --git a/packages/msi/oh-my-posh.wxs b/packages/msi/oh-my-posh.wxs new file mode 100644 index 000000000000..e96840539994 --- /dev/null +++ b/packages/msi/oh-my-posh.wxs @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/powershell/oh-my-posh/README.md b/packages/powershell/oh-my-posh/README.md deleted file mode 100644 index 6ced9256b55a..000000000000 --- a/packages/powershell/oh-my-posh/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# PowerShell package - -The goal of this module is to wrap the `oh-my-posh` binaries into a PowerShell module and allow easy installation -and ease of use when setting the prompt. - -## Testing - -## Create local package repository to validate changes locally - -### Create the repository - -```powershell -Register-PSRepository -Name 'LocalRepo' -SourceLocation 'C:\Repo' -PublishLocation 'C:\Repo' -InstallationPolicy Trusted -``` - -## Make changes and publish to your local repository - -For ease testing, up the version number when using the build script, that way you are always able to update the module. - -```powershell -deploy.ps1 -BinVersion 0.1.0 -ModuleVersion 0.0.2 -Repository LocalRepo -``` - -## Validate changes - -Install/Update the module from your local repository and validate the changes. - -```powershell -Install-Module oh-my-posh -Repository LocalRepo -Force -``` diff --git a/packages/powershell/oh-my-posh/deploy.ps1 b/packages/powershell/oh-my-posh/deploy.ps1 deleted file mode 100644 index cf1754bf4594..000000000000 --- a/packages/powershell/oh-my-posh/deploy.ps1 +++ /dev/null @@ -1,37 +0,0 @@ -Param -( - [parameter(Mandatory=$true)] - [string] - $BinVersion, - [parameter(Mandatory=$true)] - [string] - $ModuleVersion, - [parameter(Mandatory=$true)] - [string] - $Repository, - [parameter(Mandatory=$false)] - [string] - $RepositoryAPIKey -) - -# set the actual version number -(Get-Content '.\oh-my-posh.psd1' -Raw).Replace('0.0.0.1', $ModuleVersion) | Out-File -Encoding 'UTF8' '.\oh-my-posh.psd1' -# copy all themes into the module folder -Copy-Item -Path "../../../themes" -Destination "./themes" -Recurse -# fetch all the binaries from the version's GitHub release -New-Item -Path "./" -Name "bin" -ItemType "directory" -"posh-windows-amd64.exe", "posh-windows-386.exe", "posh-darwin-amd64", "posh-linux-amd64", "posh-linux-arm" | ForEach-Object -Process { - $download = "https://github.com/jandedobbeleer/oh-my-posh/releases/download/v$BinVersion/$_" - Invoke-WebRequest $download -Out "./bin/$_" -} -# publish the module -if ($RepositoryAPIKey) { - Publish-Module -Path . -Repository $Repository -NuGetApiKey $RepositoryAPIKey -Verbose -} else { - Publish-Module -Path . -Repository $Repository -Verbose -} -# reset module version (for local testing only as we don't want PR's with changed version numbers all the time) -(Get-Content '.\oh-my-posh.psd1' -Raw).Replace($ModuleVersion, '0.0.0.1') | Out-File -Encoding 'UTF8' '.\oh-my-posh.psd1' -Remove-Item "./bin" -Recurse -Force -Remove-Item "./themes" -Recurse -Force - diff --git a/packages/powershell/oh-my-posh/oh-my-posh.psd1 b/packages/powershell/oh-my-posh/oh-my-posh.psd1 deleted file mode 100644 index e0463e1c6331..000000000000 --- a/packages/powershell/oh-my-posh/oh-my-posh.psd1 +++ /dev/null @@ -1,109 +0,0 @@ -# -# Module manifest for module 'oh-my-posh' -# -# Generated by: Jan De Dobbeleer -# -# Generated on: 11-Sep-20 -# -@{ - # Version number of this module. - ModuleVersion = '0.0.0.1' - # Script module or binary module file associated with this manifest. - RootModule = 'oh-my-posh.psm1' - # ID used to uniquely identify this module - GUID = '7d7c4a78-e2fe-4e5f-9510-34ac893e4562' - # Company or vendor of this module - CompanyName = 'Unknown' - # Author of this module - Author = 'Jan De Dobbeleer' - # Copyright statement for this module - Copyright = '(c) 2020 Jan De Dobbeleer. All rights reserved.' - # Description of the functionality provided by this module - Description = 'A prompt theme engine for any shell' - # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.0' - # List of all files packaged with this module - FileList = @() - # Cmdlets to export from this module - CmdletsToExport = @() - # Variables to export from this module - VariablesToExport = @() - # Aliases to export from this module - AliasesToExport = '*' - # Functions to export from this module - FunctionsToExport = @('Get-PoshThemes', 'Set-PoshPrompt', 'Get-PoshInfoForV2Users') - # Private data to pass to the module specified in RootModule. This may also contain a PSData hashtable with additional module metadata used by PowerShell. - PrivateData = @{ - PSData = @{ - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('git', 'agnoster', 'theme', 'zsh', 'posh-git', 'prompt', 'paradox', 'robbyrussel', 'oh-my-posh') - # A URL to the license for this module. - LicenseUri = 'https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/COPYING' - # A URL to the main website for this project. - ProjectUri = 'https://github.com/JanDeDobbeleer/oh-my-posh' - } # End of PSData hashtable - } # End of PrivateData hashtable -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/powershell/oh-my-posh/oh-my-posh.psm1 b/packages/powershell/oh-my-posh/oh-my-posh.psm1 deleted file mode 100644 index 3ec16868cf09..000000000000 --- a/packages/powershell/oh-my-posh/oh-my-posh.psm1 +++ /dev/null @@ -1,185 +0,0 @@ -<# - .SYNOPSIS - Generates the prompt before each line in the console -#> - -# Powershell doesn't default to UTF8 just yet, so we're forcing it as there are too many problems -# that pop up when we don't -[console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding - -function Get-PoshCommand { - if ($IsMacOS) { - return "$PSScriptRoot/bin/posh-darwin-amd64" - } - if ($IsLinux) { - # this is rather hacky but there's no other way for the time being - $arch = uname -m - if (($arch -eq 'aarch64') -or ($arch -eq 'armv7l')) { - return "$PSScriptRoot/bin/posh-linux-arm" - } - return "$PSScriptRoot/bin/posh-linux-amd64" - } - if ([Environment]::Is64BitOperatingSystem) { - return "$PSScriptRoot/bin/posh-windows-amd64.exe" - } - return "$PSScriptRoot/bin/posh-windows-386.exe" -} - -function Set-ExecutablePermissions { - # Set the right binary to executable before doing anything else - # Permissions don't need to be set on Windows - if ($PSVersionTable.PSEdition -ne "Core" -or $IsWindows) { - return - } - - $executable = Get-PoshCommand - if (-Not (Test-Path $executable)) { - # This should only happen with a corrupt installation - Write-Warning "Executable at $executable was not found" - return - } - - chmod a+x $executable 2>&1 -} - -function Set-PoshPrompt { - param( - [Parameter(Mandatory = $false)] - [string] - $Theme - ) - - $config = "" - if (Test-Path "$PSScriptRoot/themes/$Theme.omp.json") { - $path = "$PSScriptRoot/themes/$Theme.omp.json" - $config = (Resolve-Path -Path $path).ProviderPath - } - elseif (Test-Path $Theme) { - $config = (Resolve-Path -Path $Theme).ProviderPath - } - else { - $config = "$PSScriptRoot/themes/jandedobbeleer.omp.json" - } - - # Workaround for get-location/push-location/pop-location from within a module - # https://github.com/PowerShell/PowerShell/issues/12868 - # https://github.com/JanDeDobbeleer/oh-my-posh2/issues/113 - $global:omp_global_sessionstate = $PSCmdlet.SessionState - - $poshCommand = Get-PoshCommand - (& $poshCommand --init --shell=pwsh --config="$config") | Invoke-Expression -} - -<# -.SYNOPSIS - Display a preview or a list of installed themes. -.EXAMPLE - Get-PoshThemes -.Example - Gest-PoshThemes -list -#> -function Get-PoshThemes() { - param( - [switch] - [Parameter(Mandatory = $false, HelpMessage = "List themes path")] - $list - ) - $esc = [char]27 - $consoleWidth = $Host.UI.RawUI.WindowSize.Width - $logo = @' - __ _____ _ ___ ___ ______ _ __ - / / | _ | | | \/ | | ___ \ | | \ \ - / / | | | | |__ | . . |_ _ | |_/ /__ ___| |__ \ \ -< < | | | | '_ \ | |\/| | | | | | __/ _ \/ __| '_ \ > > - \ \ \ \_/ / | | | | | | | |_| | | | | (_) \__ \ | | | / / - \_\ \___/|_| |_| \_| |_/\__, | \_| \___/|___/_| |_| /_/ - __/ | - |___/ -'@ - Write-Host $logo - $themes = Get-ChildItem -Path "$PSScriptRoot\themes\*" -Include '*.omp.json' | Sort-Object Name - Write-Host ("-" * $consoleWidth) - if ($list -eq $true) { - $themes | Select-Object fullname | Format-Table -HideTableHeaders - } - else { - $poshCommand = Get-PoshCommand - $themes | ForEach-Object -Process { - Write-Host "Theme: $esc[1m$($_.BaseName.Replace('.omp', ''))$esc[0m" - Write-Host "" - & $poshCommand -config $($_.FullName) -pwd $PWD - Write-Host "" - } - } - Write-Host ("-" * $consoleWidth) - Write-Host "" - Write-Host "Themes location: $PSScriptRoot\themes" - Write-Host "" - Write-Host "To change your theme, use the Set-PoshPrompt command. Example:" - Write-Host " Set-PoshPrompt -Theme jandedobbeleer" - Write-Host "" -} - -# Helper function to create argument completion results -function New-CompletionResult { - param( - [Parameter(Mandatory)] - [string]$CompletionText, - [string]$ListItemText = $CompletionText, - [System.Management.Automation.CompletionResultType]$CompletionResultType = [System.Management.Automation.CompletionResultType]::ParameterValue, - [string]$ToolTip = $CompletionText - ) - - New-Object System.Management.Automation.CompletionResult $CompletionText, $ListItemText, $CompletionResultType, $ToolTip -} - -function ThemeCompletion { - param( - $commandName, - $parameterName, - $wordToComplete, - $commandAst, - $fakeBoundParameter - ) - $themes = Get-ChildItem -Path "$PSScriptRoot\themes\*" -Include '*.omp.json' | Sort-Object Name | Select-Object -Property @{ - label = 'BaseName' - expression = { $_.BaseName.Replace('.omp', '') } - } - $themes | - Where-Object { $_.BaseName.ToLower().StartsWith($wordToComplete.ToLower()); } | - Select-Object -Unique -ExpandProperty BaseName | - ForEach-Object { New-CompletionResult -CompletionText $_ } -} - -Set-ExecutablePermissions - -Register-ArgumentCompleter ` - -CommandName Set-PoshPrompt ` - -ParameterName Theme ` - -ScriptBlock $function:ThemeCompletion - - -# V2 compatibility functions -# These should be removed at a certain point in time -# but to facilitate ease of transition they are kept -# as long as issues/feature requests keep popping up - -function Get-PoshInfoForV2Users { - Write-Host @' - -Hi there! - -It seems you're using an oh-my-posh V2 cmdlet while running V3. -To migrate your current setup to V3, have a look the documentation. - -https://ohmyposh.dev/docs/upgrading - -'@ -} - -Set-Alias -Name Set-Prompt -Value Get-PoshInfoForV2Users -Force -Set-Alias -Name Get-ThemesLocation -Value Get-PoshInfoForV2Users -Force -Set-Alias -Name Show-ThemeSymbols -Value Get-PoshInfoForV2Users -Force -Set-Alias -Name Show-ThemeColors -Value Get-PoshInfoForV2Users -Force -Set-Alias -Name Show-Colors -Value Get-PoshInfoForV2Users -Force -Set-Alias -Name Write-ColorPreview -Value Get-PoshInfoForV2Users -Force diff --git a/packages/scoop/build.ps1 b/packages/scoop/build.ps1 deleted file mode 100644 index 5044c419a726..000000000000 --- a/packages/scoop/build.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -Param -( - [parameter(Mandatory = $true)] - [string] - $Version -) - -New-Item -Path "." -Name "package/bin" -ItemType Directory -Copy-Item -Path "../../themes" -Destination "./package" -Recurse - -# Download the files and pack them -@{name = 'posh-windows-amd64.exe'; outName = 'oh-my-posh.exe' }, @{name = 'posh-linux-amd64'; outName = 'oh-my-posh-wsl' } | ForEach-Object -Process { - $download = "https://github.com/jandedobbeleer/oh-my-posh/releases/download/v$Version/$($_.name)" - Invoke-WebRequest $download -Out "./package/bin/$($_.outName)" -} -$compress = @{ - Path = "./package/*" - CompressionLevel = "Fastest" - DestinationPath = "./posh-windows-wsl-amd64.7z" -} -Compress-Archive @compress -$zipHash = Get-FileHash ./posh-windows-wsl-amd64.7z -Algorithm SHA256 -$content = Get-Content '.\oh-my-posh.json' -Raw -$content = $content.Replace('', $Version) -$content = $content.Replace('', $zipHash.Hash) -$content | Out-File -Encoding 'UTF8' './oh-my-posh.json' -$zipHash.Hash | Out-File -Encoding 'UTF8' 'posh-windows-wsl-amd64.7z.sha256' - -Remove-Item ./package/ -Recurse diff --git a/packages/scoop/oh-my-posh.json b/packages/scoop/oh-my-posh.json deleted file mode 100644 index 31e30670ced3..000000000000 --- a/packages/scoop/oh-my-posh.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": "", - "description": "A prompt theme engine for any shell", - "homepage": "https://ohmyposh.dev", - "license": { - "identifier": "GPL-3.0-only", - "url": "https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/COPYING" - }, - "architecture": { - "64bit": { - "url": "https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/v/posh-windows-wsl-amd64.7z", - "hash": "" - } - }, - "env_add_path": "bin", - "checkver": { - "github": "https://github.com/JanDeDobbeleer/oh-my-posh" - }, - "post_install": [ - "Write-Host 'Thanks for installing Oh My Posh.'", - "Write-Host 'Have a look at https://ohmyposh.dev/docs/installation for detailed instructions for your shell.'" - ], - "autoupdate": { - "architecture": { - "64bit": { - "url": "https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/v$version/posh-windows-wsl-amd64.7z", - "hash": { - "url": "$url.sha256" - } - } - } - } -} diff --git a/packages/winget/JanDeDobbeleer.OhMyPosh.installer.yaml b/packages/winget/JanDeDobbeleer.OhMyPosh.installer.yaml deleted file mode 100644 index 9e4ed20671f7..000000000000 --- a/packages/winget/JanDeDobbeleer.OhMyPosh.installer.yaml +++ /dev/null @@ -1,11 +0,0 @@ -PackageIdentifier: JanDeDobbeleer.OhMyPosh -PackageVersion: -InstallModes: - - "silent" -Installers: -- Architecture: x86 - InstallerType: inno - InstallerUrl: https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/v/install.exe - InstallerSha256: -ManifestType: "installer" -ManifestVersion: 1.0.0 diff --git a/packages/winget/JanDeDobbeleer.OhMyPosh.locale.en-US.yaml b/packages/winget/JanDeDobbeleer.OhMyPosh.locale.en-US.yaml deleted file mode 100644 index 2f8fa5ecc555..000000000000 --- a/packages/winget/JanDeDobbeleer.OhMyPosh.locale.en-US.yaml +++ /dev/null @@ -1,22 +0,0 @@ -PackageIdentifier: JanDeDobbeleer.OhMyPosh -PackageVersion: -PackageLocale: en-US -Publisher: Jan De Dobbeleer -PackageName: Oh My Posh -License: GPL -ShortDescription: Prompt theme engine for any shell -Tags: -- "Console" -- "Command-Line" -- "Shell" -- "Command-Prompt" -- "PowerShell" -- "WSL" -- "Developer-Tools" -- "Utilities" -- "cli" -- "cmd" -- "ps" -- "terminal" -ManifestType: defaultLocale -ManifestVersion: 1.0.0 diff --git a/packages/winget/JanDeDobbeleer.OhMyPosh.yaml b/packages/winget/JanDeDobbeleer.OhMyPosh.yaml deleted file mode 100644 index 38d58943abf0..000000000000 --- a/packages/winget/JanDeDobbeleer.OhMyPosh.yaml +++ /dev/null @@ -1,5 +0,0 @@ -PackageIdentifier: JanDeDobbeleer.OhMyPosh -PackageVersion: -DefaultLocale: en-US -ManifestType: version -ManifestVersion: 1.0.0 diff --git a/packages/winget/build.ps1 b/packages/winget/build.ps1 deleted file mode 100644 index 25c3ccccb410..000000000000 --- a/packages/winget/build.ps1 +++ /dev/null @@ -1,49 +0,0 @@ - -Param -( - [parameter(Mandatory = $true)] - [string] - $Version, - [parameter(Mandatory = $true)] - [string] - $Hash, - [parameter(Mandatory = $false)] - [string] - $Token -) - -function Set-Version { - param ( - [parameter(Mandatory = $true)] - [string] - $FileName, - [parameter(Mandatory = $true)] - [string] - $Version, - [parameter(Mandatory = $true)] - [string] - $Hash - ) - $content = Get-Content $FileName -Raw - $content = $content.Replace('', $Version) - $content = $content.Replace('', $Hash) - $content | Out-File -Encoding 'UTF8' "./$Version/$FileName" -} - -New-Item -Path $PWD -Name $Version -ItemType "directory" -# Get all files inside the folder and adjust the version/hash -Get-ChildItem '*.yaml' | ForEach-Object -Process { - Set-Version -FileName $_.Name -Version $Version -Hash $hash -} -if (-not $Token) { - return -} -# Get the latest wingetcreate exe -# Replace with the following once https://github.com/microsoft/winget-create/issues/38 is resolved: -# Invoke-WebRequest https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe -Invoke-WebRequest 'https://github.com/JanDeDobbeleer/winget-create/releases/latest/download/wingetcreate.zip' -OutFile wingetcreate.zip -Expand-Archive -LiteralPath wingetcreate.zip -DestinationPath wingetcreate -$wingetcreate = Resolve-Path -Path wingetcreate -$env:Path += ";$($wingetcreate.Path)" -# Create the PR -WingetCreateCLI.exe submit --token $Token $Version diff --git a/src/.golangci.yml b/src/.golangci.yml index 91ad3f0ff03c..95dae73d9dde 100644 --- a/src/.golangci.yml +++ b/src/.golangci.yml @@ -1,52 +1,67 @@ +version: "2" run: - timeout: 5m allow-parallel-runners: true linters: - disable-all: true + default: none enable: - bodyclose - - deadcode - - depguard + - copyloopvar - dupl - errcheck - exhaustive - - gochecknoinits - goconst - gocritic - gocyclo - - gofmt - - goimports - - revive - goprintffuncname - - gosimple - govet - ineffassign + - lll - misspell - nakedret - noctx - nolintlint + - revive - rowserrcheck - - exportloopref - staticcheck - - structcheck - - typecheck - unconvert - unparam - unused - - varcheck - whitespace - - lll -linters-settings: - gocritic: - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style - disabled-tags: - - experimental - disabled-checks: - - ifElseChain - lll: - line-length: 180 + settings: + goconst: + ignore-tests: true + gocritic: + enabled-tags: + - diagnostic + - opinionated + - performance + - style + disabled-tags: + - experimental + lll: + line-length: 180 + revive: + rules: + - name: var-naming + disabled: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/src/.goreleaser.yml b/src/.goreleaser.yml new file mode 100644 index 000000000000..85492693bdb5 --- /dev/null +++ b/src/.goreleaser.yml @@ -0,0 +1,58 @@ +# Make sure to check the documentation at https://goreleaser.com +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +version: 2 +before: + hooks: + - go mod tidy + - go install github.com/tc-hib/go-winres@latest + - go-winres make --product-version=git-tag --file-version=git-tag --arch="amd64,arm64" +builds: + - + binary: "posh-{{ .Os }}-{{ .Arch }}" + no_unique_dist_dir: true + flags: + - -a + ldflags: + - -s -w + - -X github.com/jandedobbeleer/oh-my-posh/src/build.Version={{ .Version }} + - -X github.com/jandedobbeleer/oh-my-posh/src/build.Date={{ .Date }} + - -extldflags "-static" + tags: + - netgo + - osusergo + - static_build + - timetzdata + env: + - CGO_ENABLED=0 + - GOEXPERIMENT=greenteagc,jsonv2 + goos: + - linux + - windows + - darwin + - freebsd + goarch: + - amd64 + - arm64 + - arm + ignore: + - goos: darwin + goarch: arm + - goos: windows + goarch: arm + hooks: + post: + - pwsh -c "if ('{{ .Path }}'.EndsWith('.exe')) { & '{{ .Env.SIGNTOOL }}' sign /v /debug /fd SHA256 /tr 'http://timestamp.acs.microsoft.com' /td SHA256 /dlib '{{ .Env.SIGNTOOLDLIB }}' /dmdf './metadata.json' '{{ .Path }}' }" +archives: + - id: oh-my-posh + format: binary + name_template: "posh-{{ .Os }}-{{ .Arch }}" +checksum: + name_template: 'checksums.txt' +signs: + - cmd: pwsh + args: + - "-c" + - "& '{{ .Env.OPENSSL }}' pkeyutl -sign -inkey '{{ .Env.SHA_SIGNING_KEY_LOCATION }}' -out '${artifact}.sig' -rawin -in '${artifact}'" + artifacts: checksum +changelog: + disable: true diff --git a/src/ansi.go b/src/ansi.go deleted file mode 100644 index 05263cc1ff08..000000000000 --- a/src/ansi.go +++ /dev/null @@ -1,196 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -const ( - ansiRegex = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" -) - -type ansiUtils struct { - shell string - linechange string - left string - right string - creset string - clearEOL string - saveCursorPosition string - restoreCursorPosition string - title string - colorSingle string - colorFull string - colorTransparent string - escapeLeft string - escapeRight string - hyperlink string - osc99 string - bold string - italic string - underline string - strikethrough string - bashFormat string -} - -func (a *ansiUtils) init(shell string) { - a.shell = shell - a.bashFormat = "\\[%s\\]" - switch shell { - case zsh: - a.linechange = "%%{\x1b[%d%s%%}" - a.left = "%%{\x1b[%dC%%}" - a.right = "%%{\x1b[%dD%%}" - a.creset = "%{\x1b[0m%}" - a.clearEOL = "%{\x1b[K%}" - a.saveCursorPosition = "%{\x1b7%}" - a.restoreCursorPosition = "%{\x1b8%}" - a.title = "%%{\x1b]0;%s\007%%}" - a.colorSingle = "%%{\x1b[%sm%%}%s%%{\x1b[0m%%}" - a.colorFull = "%%{\x1b[%sm\x1b[%sm%%}%s%%{\x1b[0m%%}" - a.colorTransparent = "%%{\x1b[%s;49m\x1b[7m%%}%s%%{\x1b[0m%%}" - a.escapeLeft = "%{" - a.escapeRight = "%}" - a.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}" - a.osc99 = "%%{\x1b]9;9;\"%s\"\x1b\\%%}" - a.bold = "%%{\x1b[1m%%}%s%%{\x1b[22m%%}" - a.italic = "%%{\x1b[3m%%}%s%%{\x1b[23m%%}" - a.underline = "%%{\x1b[4m%%}%s%%{\x1b[24m%%}" - a.strikethrough = "%%{\x1b[9m%%}%s%%{\x1b[29m%%}" - case bash: - a.linechange = "\\[\x1b[%d%s\\]" - a.left = "\\[\x1b[%dC\\]" - a.right = "\\[\x1b[%dD\\]" - a.creset = "\\[\x1b[0m\\]" - a.clearEOL = "\\[\x1b[K\\]" - a.saveCursorPosition = "\\[\x1b7\\]" - a.restoreCursorPosition = "\\[\x1b8\\]" - a.title = "\\[\x1b]0;%s\007\\]" - a.colorSingle = "\\[\x1b[%sm\\]%s\\[\x1b[0m\\]" - a.colorFull = "\\[\x1b[%sm\x1b[%sm\\]%s\\[\x1b[0m\\]" - a.colorTransparent = "\\[\x1b[%s;49m\x1b[7m\\]%s\\[\x1b[0m\\]" - a.escapeLeft = "\\[" - a.escapeRight = "\\]" - a.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]" - a.osc99 = "\\[\x1b]9;9;\"%s\"\x1b\\\\\\]" - a.bold = "\\[\x1b[1m\\]%s\\[\x1b[22m\\]" - a.italic = "\\[\x1b[3m\\]%s\\[\x1b[23m\\]" - a.underline = "\\[\x1b[4m\\]%s\\[\x1b[24m\\]" - a.strikethrough = "\\[\x1b[9m\\]%s\\[\x1b[29m\\]" - default: - a.linechange = "\x1b[%d%s" - a.left = "\x1b[%dC" - a.right = "\x1b[%dD" - a.creset = "\x1b[0m" - a.clearEOL = "\x1b[K" - a.saveCursorPosition = "\x1b7" - a.restoreCursorPosition = "\x1b8" - a.title = "\x1b]0;%s\007" - a.colorSingle = "\x1b[%sm%s\x1b[0m" - a.colorFull = "\x1b[%sm\x1b[%sm%s\x1b[0m" - a.colorTransparent = "\x1b[%s;49m\x1b[7m%s\x1b[0m" - a.escapeLeft = "" - a.escapeRight = "" - a.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\" - a.osc99 = "\x1b]9;9;\"%s\"\x1b\\" - a.bold = "\x1b[1m%s\x1b[22m" - a.italic = "\x1b[3m%s\x1b[23m" - a.underline = "\x1b[4m%s\x1b[24m" - a.strikethrough = "\x1b[9m%s\x1b[29m" - } -} - -func (a *ansiUtils) lenWithoutANSI(text string) int { - if len(text) == 0 { - return 0 - } - // replace hyperlinks - matches := findAllNamedRegexMatch(`(?P\x1b]8;;file:\/\/(.+)\x1b\\(?P.+)\x1b]8;;\x1b\\)`, text) - for _, match := range matches { - text = strings.ReplaceAll(text, match[str], match[url]) - } - // replace console title - matches = findAllNamedRegexMatch(`(?P\x1b\]0;(.+)\007)`, text) - for _, match := range matches { - text = strings.ReplaceAll(text, match[str], "") - } - stripped := replaceAllString(ansiRegex, text, "") - stripped = strings.ReplaceAll(stripped, a.escapeLeft, "") - stripped = strings.ReplaceAll(stripped, a.escapeRight, "") - runeText := []rune(stripped) - return len(runeText) -} - -func (a *ansiUtils) generateHyperlink(text string) string { - // hyperlink matching - results := findNamedRegexMatch("(?P(?:\\[(?P.+)\\])(?:\\((?P.*)\\)))", text) - if len(results) != 3 { - return text - } - // build hyperlink ansi - hyperlink := fmt.Sprintf(a.hyperlink, results["url"], results["name"]) - // replace original text by the new one - return strings.Replace(text, results["all"], hyperlink, 1) -} - -func (a *ansiUtils) formatText(text string) string { - results := findAllNamedRegexMatch("(?P<(?P[buis])>(?P[^<]+))", text) - for _, result := range results { - var formatted string - switch result["format"] { - case "b": - formatted = fmt.Sprintf(a.bold, result["text"]) - case "u": - formatted = fmt.Sprintf(a.underline, result["text"]) - case "i": - formatted = fmt.Sprintf(a.italic, result["text"]) - case "s": - formatted = fmt.Sprintf(a.strikethrough, result["text"]) - } - text = strings.Replace(text, result["context"], formatted, 1) - } - return text -} - -func (a *ansiUtils) carriageForward() string { - return fmt.Sprintf(a.left, 1000) -} - -func (a *ansiUtils) getCursorForRightWrite(text string, offset int) string { - strippedLen := a.lenWithoutANSI(text) + -offset - return fmt.Sprintf(a.right, strippedLen) -} - -func (a *ansiUtils) changeLine(numberOfLines int) string { - position := "B" - if numberOfLines < 0 { - position = "F" - numberOfLines = -numberOfLines - } - return fmt.Sprintf(a.linechange, numberOfLines, position) -} - -func (a *ansiUtils) consolePwd(pwd string) string { - if strings.HasSuffix(pwd, ":") { - pwd += "\\" - } - return fmt.Sprintf(a.osc99, pwd) -} - -func (a *ansiUtils) escapeText(text string) string { - // what to escape/replace is different per shell - // maybe we should refactor and maintain a list of characters to escap/replace - // like we do in ansi.go for ansi codes - switch a.shell { - case zsh: - // escape double quotes - text = strings.ReplaceAll(text, "\"", "\"\"") - case bash: - // escape backslashes to avoid replacements - // https://tldp.org/HOWTO/Bash-Prompt-HOWTO/bash-prompt-escape-sequences.html - text = strings.ReplaceAll(text, "\\", "\\\\") - } - // escape backtick - text = strings.ReplaceAll(text, "`", "'") - return text -} diff --git a/src/ansi_color.go b/src/ansi_color.go deleted file mode 100644 index 47070ddbf2e7..000000000000 --- a/src/ansi_color.go +++ /dev/null @@ -1,155 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "strings" - - "github.com/gookit/color" -) - -var ( - // Map for color names and their respective foreground [0] or background [1] color codes - colorMap = map[string][2]string{ - "black": {"30", "40"}, - "red": {"31", "41"}, - "green": {"32", "42"}, - "yellow": {"33", "43"}, - "blue": {"34", "44"}, - "magenta": {"35", "45"}, - "cyan": {"36", "46"}, - "white": {"37", "47"}, - "default": {"39", "49"}, - "darkGray": {"90", "100"}, - "lightRed": {"91", "101"}, - "lightGreen": {"92", "102"}, - "lightYellow": {"93", "103"}, - "lightBlue": {"94", "104"}, - "lightMagenta": {"95", "105"}, - "lightCyan": {"96", "106"}, - "lightWhite": {"97", "107"}, - } -) - -// Returns the color code for a given color name -func getColorFromName(colorName string, isBackground bool) (string, error) { - colorMapOffset := 0 - if isBackground { - colorMapOffset = 1 - } - if colorCodes, found := colorMap[colorName]; found { - return colorCodes[colorMapOffset], nil - } - return "", errors.New("color name does not exist") -} - -type colorWriter interface { - write(background, foreground, text string) - string() string - reset() -} - -// AnsiColor writes colorized strings -type AnsiColor struct { - builder strings.Builder - ansi *ansiUtils - terminalBackground string -} - -const ( - // Transparent implies a transparent color - Transparent = "transparent" -) - -// Gets the ANSI color code for a given color string. -// This can include a valid hex color in the format `#FFFFFF`, -// but also a name of one of the first 16 ANSI colors like `lightBlue`. -func (a *AnsiColor) getAnsiFromColorString(colorString string, isBackground bool) string { - colorFromName, err := getColorFromName(colorString, isBackground) - if err == nil { - return colorFromName - } - style := color.HEX(colorString, isBackground) - if style.IsEmpty() { - return "" - } - return style.String() -} - -func (a *AnsiColor) writeColoredText(background, foreground, text string) { - // Avoid emitting empty strings with color codes - if text == "" { - return - } - if foreground == Transparent && background != "" && a.terminalBackground != "" { - bgAnsiColor := a.getAnsiFromColorString(background, true) - fgAnsiColor := a.getAnsiFromColorString(a.terminalBackground, false) - coloredText := fmt.Sprintf(a.ansi.colorFull, bgAnsiColor, fgAnsiColor, text) - a.builder.WriteString(coloredText) - return - } - if foreground == Transparent && background != "" { - ansiColor := a.getAnsiFromColorString(background, false) - coloredText := fmt.Sprintf(a.ansi.colorTransparent, ansiColor, text) - a.builder.WriteString(coloredText) - return - } else if background == "" || background == Transparent { - ansiColor := a.getAnsiFromColorString(foreground, false) - coloredText := fmt.Sprintf(a.ansi.colorSingle, ansiColor, text) - a.builder.WriteString(coloredText) - return - } - bgAnsiColor := a.getAnsiFromColorString(background, true) - fgAnsiColor := a.getAnsiFromColorString(foreground, false) - coloredText := fmt.Sprintf(a.ansi.colorFull, bgAnsiColor, fgAnsiColor, text) - a.builder.WriteString(coloredText) -} - -func (a *AnsiColor) writeAndRemoveText(background, foreground, text, textToRemove, parentText string) string { - a.writeColoredText(background, foreground, text) - return strings.Replace(parentText, textToRemove, "", 1) -} - -func (a *AnsiColor) write(background, foreground, text string) { - if len(text) == 0 { - return - } - text = a.ansi.escapeText(text) - text = a.ansi.formatText(text) - text = a.ansi.generateHyperlink(text) - - // first we match for any potentially valid colors enclosed in <> - match := findAllNamedRegexMatch(`<(?P[^,>]+)?,?(?P[^>]+)?>(?P[^<]*)<\/>`, text) - for i := range match { - extractedForegroundColor := match[i]["foreground"] - extractedBackgroundColor := match[i]["background"] - if col := a.getAnsiFromColorString(extractedForegroundColor, false); col == "" && extractedForegroundColor != Transparent && len(extractedBackgroundColor) == 0 { - continue // we skip invalid colors - } - if col := a.getAnsiFromColorString(extractedBackgroundColor, false); col == "" && extractedBackgroundColor != Transparent && len(extractedForegroundColor) == 0 { - continue // we skip invalid colors - } - // reuse function colors if only one was specified - if len(extractedBackgroundColor) == 0 { - extractedBackgroundColor = background - } - if len(extractedForegroundColor) == 0 { - extractedForegroundColor = foreground - } - escapedTextSegment := match[i]["text"] - innerText := match[i]["content"] - textBeforeColorOverride := strings.Split(text, escapedTextSegment)[0] - text = a.writeAndRemoveText(background, foreground, textBeforeColorOverride, textBeforeColorOverride, text) - text = a.writeAndRemoveText(extractedBackgroundColor, extractedForegroundColor, innerText, escapedTextSegment, text) - } - // color the remaining part of text with background and foreground - a.writeColoredText(background, foreground, text) -} - -func (a *AnsiColor) string() string { - return a.builder.String() -} - -func (a *AnsiColor) reset() { - a.builder.Reset() -} diff --git a/src/ansi_color_test.go b/src/ansi_color_test.go deleted file mode 100644 index bd128319de6e..000000000000 --- a/src/ansi_color_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package main - -import ( - "testing" - - "github.com/gookit/color" - "github.com/stretchr/testify/assert" -) - -const ( - inputText = "This is white, <#ff5733>this is orange, white again" -) - -func TestWriteAndRemoveText(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := renderer.writeAndRemoveText("#193549", "#fff", "This is white, ", "This is white, ", inputText) - assert.Equal(t, "<#ff5733>this is orange, white again", text) - assert.NotContains(t, renderer.string(), "<#ff5733>") -} - -func TestWriteAndRemoveTextColored(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := renderer.writeAndRemoveText("#193549", "#ff5733", "this is orange", "<#ff5733>this is orange", inputText) - assert.Equal(t, "This is white, , white again", text) - assert.NotContains(t, renderer.string(), "<#ff5733>") -} - -func TestWriteColorOverride(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - renderer.write("#193549", "#ff5733", inputText) - assert.NotContains(t, renderer.string(), "<#ff5733>") -} - -func TestWriteColorOverrideBackground(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := "This is white, <,#000000>this is black, white again" - renderer.write("#193549", "#ff5733", text) - assert.NotContains(t, renderer.string(), "000000") -} - -func TestWriteColorOverrideBackground16(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := "This is default <,white> this background is changed default again" - renderer.write("#193549", "#ff5733", text) - assert.NotContains(t, renderer.string(), "white") - assert.NotContains(t, renderer.string(), "") - assert.NotContains(t, renderer.string(), "<,") -} - -func TestWriteColorOverrideBoth(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := "This is white, <#000000,#ffffff>this is black, white again" - renderer.write("#193549", "#ff5733", text) - assert.NotContains(t, renderer.string(), "ffffff") - assert.NotContains(t, renderer.string(), "000000") -} - -func TestWriteColorOverrideBoth16(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := "This is white, this is black, white again" - renderer.write("#193549", "#ff5733", text) - assert.NotContains(t, renderer.string(), "") - assert.NotContains(t, renderer.string(), "") -} - -func TestWriteColorOverrideDouble(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := "<#ffffff>jan@<#ffffff>Jans-MBP" - renderer.write("#193549", "#ff5733", text) - assert.NotContains(t, renderer.string(), "<#ffffff>") - assert.NotContains(t, renderer.string(), "") -} - -func TestWriteColorTransparent(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := "This is white" - renderer.writeColoredText("#193549", Transparent, text) - t.Log(renderer.string()) -} - -func TestWriteColorName(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := "This is white, this is red, white again" - renderer.write("#193549", "red", text) - assert.NotContains(t, renderer.string(), "") -} - -func TestWriteColorInvalid(t *testing.T) { - ansi := &ansiUtils{} - ansi.init("pwsh") - renderer := &AnsiColor{ - ansi: ansi, - } - text := "This is white, this is orange, white again" - renderer.write("#193549", "invalid", text) - assert.Contains(t, renderer.string(), "") -} - -func TestGetAnsiFromColorStringBg(t *testing.T) { - renderer := &AnsiColor{} - colorCode := renderer.getAnsiFromColorString("blue", true) - assert.Equal(t, color.BgBlue.String(), colorCode) -} - -func TestGetAnsiFromColorStringFg(t *testing.T) { - renderer := &AnsiColor{} - colorCode := renderer.getAnsiFromColorString("red", false) - assert.Equal(t, color.FgRed.String(), colorCode) -} - -func TestGetAnsiFromColorStringHex(t *testing.T) { - renderer := &AnsiColor{} - colorCode := renderer.getAnsiFromColorString("#AABBCC", false) - assert.Equal(t, color.HEX("#AABBCC").String(), colorCode) -} - -func TestGetAnsiFromColorStringInvalidFg(t *testing.T) { - renderer := &AnsiColor{} - colorCode := renderer.getAnsiFromColorString("invalid", false) - assert.Equal(t, "", colorCode) -} - -func TestGetAnsiFromColorStringInvalidBg(t *testing.T) { - renderer := &AnsiColor{} - colorCode := renderer.getAnsiFromColorString("invalid", true) - assert.Equal(t, "", colorCode) -} diff --git a/src/ansi_test.go b/src/ansi_test.go deleted file mode 100644 index 7655e9460f3f..000000000000 --- a/src/ansi_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestLenWithoutAnsi(t *testing.T) { - cases := []struct { - Text string - ShellName string - Expected int - }{ - {Text: "%{\x1b[44m%}hello%{\x1b[0m%}", ShellName: zsh, Expected: 5}, - {Text: "\x1b[44mhello\x1b[0m", ShellName: pwsh, Expected: 5}, - {Text: "\\[\x1b[44m\\]hello\\[\x1b[0m\\]", ShellName: bash, Expected: 5}, - } - for _, tc := range cases { - a := ansiUtils{} - a.init(tc.ShellName) - strippedLength := a.lenWithoutANSI(tc.Text) - assert.Equal(t, 5, strippedLength) - } -} - -func TestGenerateHyperlinkNoUrl(t *testing.T) { - cases := []struct { - Text string - ShellName string - Expected string - }{ - {Text: "sample text with no url", ShellName: zsh, Expected: "sample text with no url"}, - {Text: "sample text with no url", ShellName: pwsh, Expected: "sample text with no url"}, - {Text: "sample text with no url", ShellName: bash, Expected: "sample text with no url"}, - } - for _, tc := range cases { - a := ansiUtils{} - a.init(tc.ShellName) - hyperlinkText := a.generateHyperlink(tc.Text) - assert.Equal(t, tc.Expected, hyperlinkText) - } -} - -func TestGenerateHyperlinkWithUrl(t *testing.T) { - cases := []struct { - Text string - ShellName string - Expected string - }{ - {Text: "[google](http://www.google.be)", ShellName: zsh, Expected: "%{\x1b]8;;http://www.google.be\x1b\\%}google%{\x1b]8;;\x1b\\%}"}, - {Text: "[google](http://www.google.be)", ShellName: pwsh, Expected: "\x1b]8;;http://www.google.be\x1b\\google\x1b]8;;\x1b\\"}, - {Text: "[google](http://www.google.be)", ShellName: bash, Expected: "\\[\x1b]8;;http://www.google.be\x1b\\\\\\]google\\[\x1b]8;;\x1b\\\\\\]"}, - } - for _, tc := range cases { - a := ansiUtils{} - a.init(tc.ShellName) - hyperlinkText := a.generateHyperlink(tc.Text) - assert.Equal(t, tc.Expected, hyperlinkText) - } -} - -func TestGenerateHyperlinkWithUrlNoName(t *testing.T) { - cases := []struct { - Text string - ShellName string - Expected string - }{ - {Text: "[](http://www.google.be)", ShellName: zsh, Expected: "[](http://www.google.be)"}, - {Text: "[](http://www.google.be)", ShellName: pwsh, Expected: "[](http://www.google.be)"}, - {Text: "[](http://www.google.be)", ShellName: bash, Expected: "[](http://www.google.be)"}, - } - for _, tc := range cases { - a := ansiUtils{} - a.init(tc.ShellName) - hyperlinkText := a.generateHyperlink(tc.Text) - assert.Equal(t, tc.Expected, hyperlinkText) - } -} - -func TestFormatText(t *testing.T) { - cases := []struct { - Case string - Text string - Expected string - }{ - {Case: "single format", Text: "This is white", Expected: "This \x1b[1mis\x1b[22m white"}, - {Case: "double format", Text: "This is white, this is orange", Expected: "This \x1b[1mis\x1b[22m white, this \x1b[1mis\x1b[22m orange"}, - {Case: "underline", Text: "This is white", Expected: "This \x1b[4mis\x1b[24m white"}, - {Case: "italic", Text: "This is white", Expected: "This \x1b[3mis\x1b[23m white"}, - {Case: "strikethrough", Text: "This is white", Expected: "This \x1b[9mis\x1b[29m white"}, - } - for _, tc := range cases { - a := ansiUtils{} - a.init("") - formattedText := a.formatText(tc.Text) - assert.Equal(t, tc.Expected, formattedText, tc.Case) - } -} diff --git a/src/block.go b/src/block.go deleted file mode 100644 index cb6fcb398b5f..000000000000 --- a/src/block.go +++ /dev/null @@ -1,211 +0,0 @@ -package main - -import ( - "fmt" - "sync" - "time" -) - -// BlockType type of block -type BlockType string - -// BlockAlignment aligment of a Block -type BlockAlignment string - -const ( - // Prompt writes one or more Segments - Prompt BlockType = "prompt" - // LineBreak creates a line break in the prompt - LineBreak BlockType = "newline" - // RPrompt a right aligned prompt in ZSH and Powershell - RPrompt BlockType = "rprompt" - // Left aligns left - Left BlockAlignment = "left" - // Right aligns right - Right BlockAlignment = "right" -) - -// Block defines a part of the prompt with optional segments -type Block struct { - Type BlockType `config:"type"` - Alignment BlockAlignment `config:"alignment"` - HorizontalOffset int `config:"horizontal_offset"` - VerticalOffset int `config:"vertical_offset"` - Segments []*Segment `config:"segments"` - Newline bool `config:"newline"` - - env environmentInfo - writer colorWriter - ansi *ansiUtils - activeSegment *Segment - previousActiveSegment *Segment -} - -func (b *Block) init(env environmentInfo, writer colorWriter, ansi *ansiUtils) { - b.env = env - b.writer = writer - b.ansi = ansi -} - -func (b *Block) initPlain(env environmentInfo, config *Config) { - b.ansi = &ansiUtils{} - b.ansi.init(plain) - b.writer = &AnsiColor{ - ansi: b.ansi, - terminalBackground: getConsoleBackgroundColor(env, config.TerminalBackground), - } - b.env = env -} - -func (b *Block) enabled() bool { - if b.Type == LineBreak { - return true - } - for _, segment := range b.Segments { - if segment.active { - return true - } - } - return false -} - -func (b *Block) setStringValues() { - wg := sync.WaitGroup{} - wg.Add(len(b.Segments)) - defer wg.Wait() - cwd := b.env.getcwd() - for _, segment := range b.Segments { - go func(s *Segment) { - defer wg.Done() - s.setStringValue(b.env, cwd) - }(segment) - } -} - -func (b *Block) renderSegments() string { - defer b.writer.reset() - for _, segment := range b.Segments { - if !segment.active { - continue - } - b.activeSegment = segment - b.endPowerline() - b.renderSegmentText(segment.stringValue) - } - if b.previousActiveSegment != nil && b.previousActiveSegment.Style == Powerline { - b.writePowerLineSeparator(Transparent, b.previousActiveSegment.background(), true) - } - return b.writer.string() -} - -func (b *Block) endPowerline() { - if b.previousActiveSegment == nil || b.activeSegment == nil { - return - } - if b.activeSegment.Style != Powerline && - b.previousActiveSegment.Style == Powerline { - b.writePowerLineSeparator(b.getPowerlineColor(false), b.previousActiveSegment.background(), true) - } -} - -func (b *Block) writePowerLineSeparator(background, foreground string, end bool) { - symbol := b.activeSegment.PowerlineSymbol - if end { - symbol = b.previousActiveSegment.PowerlineSymbol - } - if b.activeSegment.InvertPowerline { - b.writer.write(foreground, background, symbol) - return - } - b.writer.write(background, foreground, symbol) -} - -func (b *Block) getPowerlineColor(foreground bool) string { - if b.previousActiveSegment == nil { - return Transparent - } - if b.previousActiveSegment.Style == Diamond && len(b.previousActiveSegment.TrailingDiamond) == 0 { - return b.previousActiveSegment.background() - } - if b.activeSegment.Style == Diamond && len(b.activeSegment.LeadingDiamond) == 0 { - return b.activeSegment.background() - } - if !foreground && b.activeSegment.Style != Powerline { - return Transparent - } - if foreground && b.previousActiveSegment.Style != Powerline { - return Transparent - } - return b.previousActiveSegment.background() -} - -func (b *Block) renderSegmentText(text string) { - switch b.activeSegment.Style { - case Plain: - b.renderPlainSegment(text) - case Diamond: - b.renderDiamondSegment(text) - case Powerline: - b.renderPowerLineSegment(text) - } - b.previousActiveSegment = b.activeSegment -} - -func (b *Block) renderPowerLineSegment(text string) { - b.writePowerLineSeparator(b.activeSegment.background(), b.getPowerlineColor(true), false) - b.renderText(text) -} - -func (b *Block) renderPlainSegment(text string) { - b.renderText(text) -} - -func (b *Block) renderDiamondSegment(text string) { - b.writer.write(Transparent, b.activeSegment.background(), b.activeSegment.LeadingDiamond) - b.renderText(text) - b.writer.write(Transparent, b.activeSegment.background(), b.activeSegment.TrailingDiamond) -} - -func (b *Block) renderText(text string) { - defaultValue := " " - prefix := b.activeSegment.getValue(Prefix, defaultValue) - postfix := b.activeSegment.getValue(Postfix, defaultValue) - b.writer.write(b.activeSegment.background(), b.activeSegment.foreground(), fmt.Sprintf("%s%s%s", prefix, text, postfix)) -} - -func (b *Block) debug() (int, []*SegmentTiming) { - var segmentTimings []*SegmentTiming - largestSegmentNameLength := 0 - for _, segment := range b.Segments { - err := segment.mapSegmentWithWriter(b.env) - if err != nil || !segment.shouldIncludeFolder(b.env.getcwd()) { - continue - } - var segmentTiming SegmentTiming - segmentTiming.name = string(segment.Type) - segmentTiming.nameLength = len(segmentTiming.name) - if segmentTiming.nameLength > largestSegmentNameLength { - largestSegmentNameLength = segmentTiming.nameLength - } - // enabled() timing - start := time.Now() - segmentTiming.enabled = segment.enabled() - segmentTiming.enabledDuration = time.Since(start) - // string() timing - if segmentTiming.enabled { - start = time.Now() - segmentTiming.stringValue = segment.string() - segmentTiming.stringDuration = time.Since(start) - b.previousActiveSegment = nil - b.activeSegment = segment - b.renderSegmentText(segmentTiming.stringValue) - if b.activeSegment.Style == Powerline { - b.writePowerLineSeparator(Transparent, b.activeSegment.background(), true) - } - segmentTiming.stringValue = b.writer.string() - b.writer.reset() - } - segmentTimings = append(segmentTimings, &segmentTiming) - } - return largestSegmentNameLength, segmentTimings -} diff --git a/src/block_test.go b/src/block_test.go deleted file mode 100644 index 863d8515a70b..000000000000 --- a/src/block_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestBlockEnabled(t *testing.T) { - cases := []struct { - Case string - Expected bool - Segments []*Segment - Type BlockType - }{ - {Case: "line break block", Expected: true, Type: LineBreak}, - {Case: "prompt enabled", Expected: true, Type: Prompt, Segments: []*Segment{{active: true}}}, - {Case: "prompt disabled", Expected: false, Type: Prompt, Segments: []*Segment{{active: false}}}, - {Case: "prompt enabled multiple", Expected: true, Type: Prompt, Segments: []*Segment{{active: false}, {active: true}}}, - {Case: "rprompt enabled multiple", Expected: true, Type: RPrompt, Segments: []*Segment{{active: false}, {active: true}}}, - } - for _, tc := range cases { - block := &Block{ - Type: tc.Type, - Segments: tc.Segments, - } - assert.Equal(t, tc.Expected, block.enabled(), tc.Case) - } -} diff --git a/src/build/version.go b/src/build/version.go new file mode 100644 index 000000000000..228a14bb3aeb --- /dev/null +++ b/src/build/version.go @@ -0,0 +1,6 @@ +package build + +var ( + Date string + Version = "0.0.0-dev" +) diff --git a/src/cache/cache.go b/src/cache/cache.go new file mode 100644 index 000000000000..c34e6f6eb912 --- /dev/null +++ b/src/cache/cache.go @@ -0,0 +1,41 @@ +package cache + +import ( + "encoding/gob" + "time" +) + +func init() { + gob.Register(&Entry[any]{}) + gob.Register(Template{}) + gob.Register(SimpleTemplate{}) + gob.Register((*Duration)(nil)) + gob.Register(map[string]bool{}) +} + +const ( + DeviceStore = "omp.cache" +) + +const ( + TEMPLATECACHE = "template_cache" + TOGGLECACHE = "toggle_cache" + PROMPTCOUNTCACHE = "prompt_count_cache" + ENGINECACHE = "engine_cache" + FONTLISTCACHE = "font_list_cache" + CLAUDECACHE = "claude_cache" +) + +type Entry[T any] struct { + Value T + Timestamp int64 + TTL int +} + +func (c *Entry[T]) Expired() bool { + if c.TTL < 0 { + return false + } + + return time.Now().Unix() >= (c.Timestamp + int64(c.TTL)) +} diff --git a/src/cache/clear.go b/src/cache/clear.go new file mode 100644 index 000000000000..443f69d93a33 --- /dev/null +++ b/src/cache/clear.go @@ -0,0 +1,98 @@ +package cache + +import ( + "os" + "path/filepath" + "slices" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +// Clear removes cache files from the cache directory. +// +// If force is true, the entire cache directory is removed. +// If force is false, only cache files older than 7 days that match certain patterns are deleted. +// The excludedFiles parameter allows you to specify file names that should not be deleted, +// even if they would otherwise be eligible for removal. +func Clear(force bool, excludedFiles ...string) error { + defer log.Trace(time.Now()) + + if force { + return os.RemoveAll(Path()) + } + + // get all files in the cache directory that start with omp.cache and delete them + files, err := os.ReadDir(Path()) + if err != nil { + return err + } + + // get all log files as well + if logFiles, err := os.ReadDir(filepath.Join(Path(), "logs")); err == nil { + files = append(files, logFiles...) + } + + shouldSkip := func(fileName string) bool { + if slices.Contains(excludedFiles, fileName) { + return true + } + + return strings.EqualFold(fileName, DeviceStore) || strings.HasPrefix(fileName, "init.") + } + + if len(excludedFiles) > 0 { + log.Debug("excluding files from deletion:", strings.Join(excludedFiles, ", ")) + } + + deleteFile := func(file string) { + path := filepath.Join(Path(), file) + err := os.Remove(path) + if err != nil { + log.Error(err) + return + } + + log.Debugf("removed cache file: %s", path) + } + + cacheTTL := GetTTL() + + log.Debugf("removing cache files older than %d days", cacheTTL) + + for _, file := range files { + if file.IsDir() { + continue + } + + if shouldSkip(file.Name()) { + log.Debug("skipping excluded file:", file.Name()) + continue + } + + cacheFileInfo, err := file.Info() + if err != nil { + log.Debug("skipping file, cannot get info:", file.Name()) + continue + } + + if cacheFileInfo.ModTime().After(time.Now().AddDate(0, 0, -cacheTTL)) { + log.Debug("skipping recently used file:", file.Name()) + continue + } + + deleteFile(file.Name()) + } + + return nil +} + +func GetTTL() int { + cacheTTL, OK := Get[int](Device, TTL) + if !OK || cacheTTL <= 0 { + cacheTTL = 7 + } + + return cacheTTL +} diff --git a/src/cache/command.go b/src/cache/command.go new file mode 100644 index 000000000000..4ba0be523946 --- /dev/null +++ b/src/cache/command.go @@ -0,0 +1,22 @@ +package cache + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/maps" +) + +type Command struct { + Commands *maps.Concurrent[string] +} + +func (c *Command) Set(command, path string) { + c.Commands.Set(command, path) +} + +func (c *Command) Get(command string) (string, bool) { + cacheCommand, found := c.Commands.Get(command) + if !found { + return "", false + } + + return cacheCommand, true +} diff --git a/src/cache/duration.go b/src/cache/duration.go new file mode 100644 index 000000000000..2be229395169 --- /dev/null +++ b/src/cache/duration.go @@ -0,0 +1,49 @@ +package cache + +import ( + "time" +) + +type Duration string + +const ( + INFINITE = Duration("infinite") + NONE = Duration("none") + ONEWEEK = Duration("168h") + ONEDAY = Duration("24h") + TWOYEARS = Duration("17520h") +) + +func (d Duration) Seconds() int { + if d == NONE { + return 0 + } + + if d == INFINITE { + return -1 + } + + duration, err := time.ParseDuration(string(d)) + if err != nil { + return 0 + } + + return int(duration.Seconds()) +} + +func (d Duration) IsEmpty() bool { + return d == "" +} + +func ToDuration(seconds int) Duration { + if seconds == 0 { + return "" + } + + if seconds == -1 { + return INFINITE + } + + duration := time.Duration(seconds) * time.Second + return Duration(duration.String()) +} diff --git a/src/cache/duration_test.go b/src/cache/duration_test.go new file mode 100644 index 000000000000..115a0664d61a --- /dev/null +++ b/src/cache/duration_test.go @@ -0,0 +1,55 @@ +package cache + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSeconds(t *testing.T) { + cases := []struct { + Case string + Duration Duration + Expected int + }{ + { + Case: "2 seconds", + Duration: "2s", + Expected: 2, + }, + { + Case: "1 minute", + Duration: "1m", + Expected: 60, + }, + { + Case: "2 hours", + Duration: "2h", + Expected: 7200, + }, + { + Case: "2 days", + Duration: "48h", + Expected: 172800, + }, + { + Case: "invalid", + Duration: "foo", + Expected: 0, + }, + { + Case: "1 fortnight", + Duration: "1fortnight", + Expected: 0, + }, + { + Case: "infinite", + Duration: "infinite", + Expected: -1, + }, + } + for _, tc := range cases { + got := tc.Duration.Seconds() + assert.Equal(t, tc.Expected, got, tc.Case) + } +} diff --git a/src/cache/file_map_windows.go b/src/cache/file_map_windows.go new file mode 100644 index 000000000000..ef056fd4272b --- /dev/null +++ b/src/cache/file_map_windows.go @@ -0,0 +1,282 @@ +package cache + +import ( + "fmt" + "syscall" + "unsafe" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +// Configuration constants +const ( + minStringSize = 50 * 1024 // 50KB minimum string size + maxStringSize = 10 * 1024 * 1024 // 10MB maximum string size +) + +// Windows API constants +const ( + fileMapAllAccess = 0x001f001f + pageReadwrite = 0x04 + genericRead = 0x80000000 + genericWrite = 0x40000000 + createAlways = 2 + openExisting = 3 + fileAttributeNormal = 0x80 +) + +// Windows API functions +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + createFileW = kernel32.NewProc("CreateFileW") + createFileMappingW = kernel32.NewProc("CreateFileMappingW") + mapViewOfFile = kernel32.NewProc("MapViewOfFile") + unmapViewOfFile = kernel32.NewProc("UnmapViewOfFile") + closeHandle = kernel32.NewProc("CloseHandle") + setFilePointer = kernel32.NewProc("SetFilePointer") + setEndOfFile = kernel32.NewProc("SetEndOfFile") + getFileSizeEx = kernel32.NewProc("GetFileSizeEx") +) + +// PersistentSharedString represents a memory-mapped file for storing a single string +type PersistentSharedString struct { + filePath string + fileHandle uintptr + mapHandle uintptr + data uintptr + size int // Current allocated size +} + +func createOrOpenPersistentString(filePath string) (*PersistentSharedString, error) { + return createOrOpenPersistentStringWithSize(filePath, minStringSize) +} + +func createOrOpenPersistentStringWithSize(filePath string, requiredSize int) (*PersistentSharedString, error) { + // Ensure size is within bounds + if requiredSize < minStringSize { + requiredSize = minStringSize + } + if requiredSize > maxStringSize { + return nil, fmt.Errorf("required size %d exceeds maximum %d", requiredSize, maxStringSize) + } + + // First, try to open existing file + pss, err := openExistingFileWithSize(filePath, requiredSize) + if err == nil { + return pss, nil + } + + // File doesn't exist or too small, create new one with required size + return createNewFileWithSize(filePath, requiredSize) +} + +// openExistingFileWithSize attempts to open an existing memory-mapped file +// openExistingFileWithSize attempts to open an existing memory-mapped file +func openExistingFileWithSize(filePath string, requiredSize int) (*PersistentSharedString, error) { + filePathPtr, err := syscall.UTF16PtrFromString(filePath) + if err != nil { + return nil, fmt.Errorf("failed to convert file path to UTF16: %v", err) + } + + // Try to open existing file + fileHandle, _, _ := createFileW.Call( + uintptr(unsafe.Pointer(filePathPtr)), // lpFileName + genericRead|genericWrite, // dwDesiredAccess + 0, // dwShareMode + 0, // lpSecurityAttributes + openExisting, // dwCreationDisposition + fileAttributeNormal, // dwFlagsAndAttributes + 0, // hTemplateFile + ) + + if fileHandle == uintptr(0xFFFFFFFFFFFFFFFF) { // INVALID_HANDLE_VALUE + return nil, fmt.Errorf("file does not exist") + } + + // Get file size to check if it's large enough + var fileSize int64 + ret, _, _ := getFileSizeEx.Call(fileHandle, uintptr(unsafe.Pointer(&fileSize))) + if ret == 0 { + _, _, _ = closeHandle.Call(fileHandle) + return nil, fmt.Errorf("failed to get file size") + } + + actualSize := int(fileSize) - 5 // Subtract header (4 bytes length + 1 null terminator) + if actualSize < requiredSize { + // Existing file is too small, close and recreate + _, _, _ = closeHandle.Call(fileHandle) + return nil, fmt.Errorf("existing file is too small (%d < %d)", actualSize, requiredSize) + } + + return createMappingFromFileWithSize(filePath, fileHandle, actualSize) +} + +// createNewFileWithSize creates a new memory-mapped file with the specified size +func createNewFileWithSize(filePath string, size int) (*PersistentSharedString, error) { + filePathPtr, err := syscall.UTF16PtrFromString(filePath) + if err != nil { + return nil, fmt.Errorf("failed to convert file path to UTF16: %v", err) + } + + // Create new file + fileHandle, _, err := createFileW.Call( + uintptr(unsafe.Pointer(filePathPtr)), // lpFileName + genericRead|genericWrite, // dwDesiredAccess + 0, // dwShareMode + 0, // lpSecurityAttributes + createAlways, // dwCreationDisposition (overwrites if exists) + fileAttributeNormal, // dwFlagsAndAttributes + 0, // hTemplateFile + ) + + if fileHandle == uintptr(0xFFFFFFFFFFFFFFFF) { // INVALID_HANDLE_VALUE + return nil, fmt.Errorf("CreateFileW failed: %v", err) + } + + // Set file size (4 bytes for length + size for string + 1 for null terminator) + totalSize := size + 5 + _, _, _ = setFilePointer.Call(fileHandle, uintptr(totalSize), 0, 0) // FILE_BEGIN = 0 + _, _, _ = setEndOfFile.Call(fileHandle) + + pss, mapErr := createMappingFromFileWithSize(filePath, fileHandle, size) + if mapErr != nil { + _, _, _ = closeHandle.Call(fileHandle) + return nil, mapErr + } + + // Initialize new file with empty string + basePtr := unsafe.Pointer(pss.data) + lengthPtr := (*uint32)(basePtr) + *lengthPtr = 0 + + return pss, nil +} + +// createMappingFromFileWithSize creates a memory mapping from an open file handle with specified size +func createMappingFromFileWithSize(filePath string, fileHandle uintptr, size int) (*PersistentSharedString, error) { + totalSize := size + 5 // 4 bytes length + size + 1 null terminator + + // Create file mapping + mapHandle, _, err := createFileMappingW.Call( + fileHandle, // hFile + 0, // lpAttributes (NULL) + pageReadwrite, // flProtect + 0, // dwMaximumSizeHigh + uintptr(totalSize), // dwMaximumSizeLow + 0, // lpName (NULL for unnamed mapping) + ) + + if mapHandle == 0 { + return nil, fmt.Errorf("CreateFileMappingW failed: %v", err) + } + + // Map view of file + data, _, err := mapViewOfFile.Call( + mapHandle, // hFileMappingObject + fileMapAllAccess, // dwDesiredAccess + 0, // dwFileOffsetHigh + 0, // dwFileOffsetLow + uintptr(totalSize), // dwNumberOfBytesToMap + ) + + if data == 0 { + _, _, _ = closeHandle.Call(mapHandle) + return nil, fmt.Errorf("MapViewOfFile failed: %v", err) + } + + return &PersistentSharedString{ + filePath: filePath, + fileHandle: fileHandle, + mapHandle: mapHandle, + data: data, + size: size, + }, nil +} + +// SetString stores a string in the memory-mapped file (automatically persisted) +func (pss *PersistentSharedString) SetString(value string) error { + strBytes := []byte(value) + + if len(strBytes) > pss.size { + return fmt.Errorf("string too large for allocated space (%d > %d)", len(strBytes), pss.size) + } + + basePtr := unsafe.Pointer(pss.data) + + // Write length as first 4 bytes (little-endian) + lengthPtr := (*uint32)(basePtr) + *lengthPtr = uint32(len(strBytes)) + + // Write string data starting at offset 4 + if len(strBytes) > 0 { + stringPtr := unsafe.Add(basePtr, 4) + stringSlice := unsafe.Slice((*byte)(stringPtr), len(strBytes)) + copy(stringSlice, strBytes) + } + + // Write null terminator + nullPtr := (*byte)(unsafe.Add(basePtr, 4+len(strBytes))) + *nullPtr = 0 + + // No need to explicitly flush - Windows handles this automatically + return nil +} + +func (pss *PersistentSharedString) bytes() []byte { + basePtr := unsafe.Pointer(pss.data) + + // Read length from first 4 bytes + lengthPtr := (*uint32)(basePtr) + length := *lengthPtr + + if length == 0 { + log.Debug("empty string") + return []byte{0} + } + + if length > uint32(pss.size) { + log.Error(fmt.Errorf("corrupted data: length %d exceeds allocated size %d", length, pss.size)) + return []byte{0} + } + + // Read string data starting at offset 4 + stringPtr := unsafe.Add(basePtr, 4) + stringSlice := unsafe.Slice((*byte)(stringPtr), length) + + // Convert to string + result := make([]byte, length) + copy(result, stringSlice) + return result +} + +// Close closes the memory-mapped file and handles +func (pss *PersistentSharedString) close() error { + var err error + + if pss.data != 0 { + if ret, _, e := unmapViewOfFile.Call(pss.data); ret == 0 { + err = fmt.Errorf("UnmapViewOfFile failed: %v", e) + } + pss.data = 0 + } + + if pss.mapHandle != 0 { + if ret, _, e := closeHandle.Call(pss.mapHandle); ret == 0 { + if err == nil { + err = fmt.Errorf("CloseHandle (mapping) failed: %v", e) + } + } + pss.mapHandle = 0 + } + + if pss.fileHandle != 0 { + if ret, _, e := closeHandle.Call(pss.fileHandle); ret == 0 { + if err == nil { + err = fmt.Errorf("CloseHandle (file) failed: %v", e) + } + } + pss.fileHandle = 0 + } + + return err +} diff --git a/src/cache/file_unix.go b/src/cache/file_unix.go new file mode 100644 index 000000000000..8514356bdf5b --- /dev/null +++ b/src/cache/file_unix.go @@ -0,0 +1,12 @@ +//go:build !windows + +package cache + +import ( + "io" + "os" +) + +func openFile(filePath string) (io.ReadWriteCloser, error) { + return os.OpenFile(filePath, os.O_CREATE|os.O_RDWR, 0o644) +} diff --git a/src/cache/file_windows.go b/src/cache/file_windows.go new file mode 100644 index 000000000000..014bab5deca6 --- /dev/null +++ b/src/cache/file_windows.go @@ -0,0 +1,87 @@ +package cache + +import ( + "bytes" + "fmt" + "io" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +// persistentStringRWCloser implements io.ReadWriteCloser for PersistentSharedString +type persistentStringRWCloser struct { + pss *PersistentSharedString + buf *bytes.Buffer + filePath string + dirty bool +} + +func NewPersistentStringRWCloser(pss *PersistentSharedString) io.ReadWriteCloser { + return &persistentStringRWCloser{ + pss: pss, + buf: bytes.NewBuffer(pss.bytes()), + filePath: pss.filePath, + } +} + +func (rw *persistentStringRWCloser) Read(p []byte) (int, error) { + return rw.buf.Read(p) +} + +func (rw *persistentStringRWCloser) Write(p []byte) (int, error) { + if !rw.dirty { + rw.buf.Reset() + rw.dirty = true + } + + return rw.buf.Write(p) +} + +func (rw *persistentStringRWCloser) Close() error { + defer rw.pss.close() + + if !rw.dirty { + return nil + } + + data := rw.buf.String() + dataSize := len(data) + + // Check if the data fits in the current allocation + if dataSize <= rw.pss.size { + return rw.pss.SetString(data) + } + + // Data is too large, need to recreate with larger size + log.Debugf("cache data size (%d) exceeds current allocation (%d), recreating file", dataSize, rw.pss.size) + + // Calculate new size with some growth factor (1.5x) to reduce future reallocations + newSize := max(dataSize+(dataSize/2), minStringSize) + if newSize > maxStringSize { + return fmt.Errorf("required cache size %d exceeds maximum %d", dataSize, maxStringSize) + } + + // Close current mapping before recreating + if err := rw.pss.close(); err != nil { + log.Error(err) + } + + // Create new file with larger size + newPss, err := createOrOpenPersistentStringWithSize(rw.filePath, newSize) + if err != nil { + return fmt.Errorf("failed to recreate cache file with size %d: %v", newSize, err) + } + + // Write the data to the new file + return newPss.SetString(data) +} + +func openFile(filePath string) (io.ReadWriteCloser, error) { + pss, err := createOrOpenPersistentString(filePath) + if err != nil { + log.Error(err) + return nil, err + } + + return NewPersistentStringRWCloser(pss), nil +} diff --git a/src/cache/init.go b/src/cache/init.go new file mode 100644 index 000000000000..17c399556c5b --- /dev/null +++ b/src/cache/init.go @@ -0,0 +1,74 @@ +package cache + +import ( + "fmt" + "os" + "sync" + "time" + + "github.com/google/uuid" + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +type Option func() + +var ( + sessionID string + newSession bool + persist bool + noSession bool + once sync.Once +) + +var NewSession Option = func() { + log.Debug("starting a new session") + newSession = true +} + +var Persist Option = func() { + log.Debug("enable persistent cache") + persist = true +} + +var NoSession Option = func() { + log.Debug("disable session cache") + noSession = true +} + +func Init(shell string, options ...Option) { + for _, opt := range options { + opt() + } + + Device.init(DeviceStore, persist) + + if noSession { + return + } + + sessionFileName := fmt.Sprintf("%s.%s.%s", shell, SessionID(), DeviceStore) + Session.init(sessionFileName, persist) +} + +func SessionID() string { + defer log.Trace(time.Now()) + + once.Do(func() { + if newSession { + sessionID = uuid.NewString() + return + } + + sessionID = os.Getenv("POSH_SESSION_ID") + if sessionID == "" { + sessionID = uuid.NewString() + } + }) + + return sessionID +} + +func Close() { + Session.close() + Device.close() +} diff --git a/src/cache/path.go b/src/cache/path.go new file mode 100644 index 000000000000..6e405d9e9a5d --- /dev/null +++ b/src/cache/path.go @@ -0,0 +1,63 @@ +package cache + +import ( + "os" + "path/filepath" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" +) + +var cachePath string + +func Path() string { + defer log.Trace(time.Now()) + + if cachePath != "" { + return cachePath + } + + var OK bool + + // allow the user to set the cache path using OMP_CACHE_DIR + if cachePath, OK = returnOrBuildCachePath(os.Getenv("OMP_CACHE_DIR")); OK { + return cachePath + } + + if cachePath, OK = platformCachePath(); OK { + return cachePath + } + + // try to create the cache folder in the user's home directory if non-existent + dotCache := filepath.Join(path.Home(), ".cache") + if _, err := os.Stat(dotCache); err != nil { + _ = os.Mkdir(dotCache, 0o755) + } + + // HOME cache folder + if cachePath, OK = returnOrBuildCachePath(dotCache); OK { + return cachePath + } + + return cachePath +} + +func returnOrBuildCachePath(input string) (string, bool) { + // validate root path + if _, err := os.Stat(input); err != nil { + return "", false + } + + // validate oh-my-posh folder, if non existent, create it + cachePath := filepath.Join(input, "oh-my-posh") + if _, err := os.Stat(cachePath); err == nil { + return cachePath, true + } + + if err := os.Mkdir(cachePath, 0o755); err != nil { + return "", false + } + + return cachePath, true +} diff --git a/src/cache/path_unix.go b/src/cache/path_unix.go new file mode 100644 index 000000000000..244cccc21216 --- /dev/null +++ b/src/cache/path_unix.go @@ -0,0 +1,17 @@ +//go:build !windows + +package cache + +import "os" + +func platformCachePath() (string, bool) { + if cachePath, OK := returnOrBuildCachePath(os.Getenv("XDG_CACHE_HOME")); OK { + return cachePath, true + } + + return "", false +} + +func PackageFamilyName() (string, bool) { + return "", false +} diff --git a/src/cache/path_windows.go b/src/cache/path_windows.go new file mode 100644 index 000000000000..a919520cfcd5 --- /dev/null +++ b/src/cache/path_windows.go @@ -0,0 +1,52 @@ +package cache + +import ( + "os" + "path/filepath" + "syscall" + "time" + "unsafe" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +func platformCachePath() (string, bool) { + if pfn, OK := PackageFamilyName(); OK { + // WINDOWS MSIX cache folder, will only be present when oh-my-posh is installed via MSIX + msixLocalAppData := filepath.Join(os.Getenv("LOCALAPPDATA"), "Packages", pfn, "LocalCache", "Local") + if cachePath, OK := returnOrBuildCachePath(msixLocalAppData); OK { + return cachePath, true + } + } + + // WINDOWS cache folder, should not exist elsewhere + if cachePath, OK := returnOrBuildCachePath(os.Getenv("LOCALAPPDATA")); OK { + return cachePath, true + } + + return "", false +} + +func PackageFamilyName() (string, bool) { + defer log.Trace(time.Now()) + + kernel32 := syscall.NewLazyDLL("kernel32.dll") + procGetCurrentPackageFamilyName := kernel32.NewProc("GetCurrentPackageFamilyName") + + var length uint32 = 256 + buf := make([]uint16, length) + ret, _, _ := procGetCurrentPackageFamilyName.Call( + uintptr(unsafe.Pointer(&length)), + uintptr(unsafe.Pointer(&buf[0])), + ) + + if ret != 0 { + log.Debug("failed to get PackageFamilyName") + return "", false + } + + pfn := syscall.UTF16ToString(buf) + log.Debug("PackageFamilyName:", pfn) + + return pfn, true +} diff --git a/src/cache/store.go b/src/cache/store.go new file mode 100644 index 000000000000..1f4319b02c8d --- /dev/null +++ b/src/cache/store.go @@ -0,0 +1,279 @@ +package cache + +import ( + "encoding/gob" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/maps" +) + +type store struct { + cache *maps.Concurrent[*Entry[any]] + filePath string + dirty bool + persist bool +} + +var ( + session *store + device *store +) + +type Store string + +const ( + Session Store = "session" + Device Store = "device" + TTL string = "ttl" +) + +func (s Store) new() *store { + return &store{ + cache: maps.NewConcurrent[*Entry[any]](), + } +} + +// getStore returns the appropriate store based on the Store identifier +func (s Store) get() *store { + switch s { //nolint:exhaustive + case Device: + if device == nil { + device = s.new() + } + + return device + default: + if session == nil { + session = s.new() + } + + return session + } +} + +// Init initializes a store with the given file path +func (s Store) init(filePath string, persist bool) { + defer log.Trace(time.Now(), string(s), filePath) + + store := s.get() + store.cache = maps.NewConcurrent[*Entry[any]]() + store.filePath = filepath.Join(Path(), filePath) + store.persist = persist + + reader, err := openFile(store.filePath) + if err != nil { + // set to dirty so we create it on close + log.Error(err) + store.dirty = true + return + } + + defer reader.Close() + + var list maps.Simple[*Entry[any]] + + dec := gob.NewDecoder(reader) + if err := dec.Decode(&list); err != nil { + log.Error(err) + // If gob decoding fails, the cache file might be from the old format + // Set dirty to true so we recreate it in gob format + store.dirty = true + return + } + + for key, entry := range list { + if entry.Expired() { + log.Debugf("(%s) skipping expired key: %s", string(s), key) + continue + } + + log.Debugf("(%s) loading %s", string(s), key) + store.cache.Set(key, entry) + } +} + +// touchSessionFile updates the session file's modification time if it's older than 1 hour. +// This prevents stale session cache files from being cleaned up while reducing steady-state overhead. +func touchSessionFile(filePath string) { + info, err := os.Stat(filePath) + if err != nil { + return + } + + if time.Since(info.ModTime()) <= time.Hour { + return + } + + if err := os.Chtimes(filePath, time.Now(), time.Now()); err != nil { + log.Error(err) + } +} + +func (s Store) close() { + defer log.Trace(time.Now(), string(s)) + + store := s.get() + if store == nil || !store.persist || !store.dirty { + if s == Session && store != nil && store.filePath != "" { + touchSessionFile(store.filePath) + } + + log.Debugf("(%s) not persisting", string(s)) + return + } + + cache := store.cache.ToSimple() + + file, err := openFile(store.filePath) + if err != nil { + log.Error(err) + return + } + + defer func() { + if err := file.Close(); err != nil { + log.Error(err) + } + }() + + enc := gob.NewEncoder(file) + if err := enc.Encode(cache); err != nil { + log.Error(err) + } +} + +// Get retrieves a typed value from the specified store +func Get[T any](s Store, key string) (T, bool) { + var zero T + defer log.Trace(time.Now(), string(s), key) + + store := s.get() + if store == nil { + log.Debugf("(%s) store is nil", string(s)) + return zero, false + } + + entry, found := store.cache.Get(key) + if !found { + log.Debugf("(%s) key not found: %s", string(s), key) + return zero, false + } + + if entry.Expired() { + log.Debugf("(%s) key expired: %s", string(s), key) + store.cache.Delete(key) + store.dirty = true + return zero, false + } + + // Type assertion to get the typed value + if typed, ok := entry.Value.(T); ok { + log.Debugf("(%s) found entry: %s - %v", string(s), key, typed) + return typed, true + } + + log.Error(fmt.Errorf("(%s) type mismatch for key: %s. Got %T, expected %T", string(s), key, entry.Value, zero)) + return zero, false +} + +// Set stores a typed value in the specified store +func Set[T any](s Store, key string, value T, duration Duration) { + defer log.Trace(time.Now(), string(s), key) + + store := s.get() + if store == nil { + log.Debugf("(%s) store is nil", string(s)) + return + } + + seconds := duration.Seconds() + if seconds == 0 { + return + } + + log.Debugf("(%s) setting entry: %s - %v with duration: %s", string(s), key, value, string(duration)) + + store.cache.Set(key, &Entry[any]{ + Value: value, + Timestamp: time.Now().Unix(), + TTL: seconds, + }) + + store.dirty = true +} + +// Delete removes a key from the specified store +func Delete(s Store, key string) { + defer log.Trace(time.Now(), string(s), key) + + store := s.get() + if store == nil { + log.Debugf("(%s) store is nil", string(s)) + return + } + + log.Debugf("(%s) deleting key: %s", string(s), key) + store.cache.Delete(key) + store.dirty = true +} + +func DeleteAll(s Store) { + defer log.Trace(time.Now(), string(s)) + + store := s.get() + if store == nil { + log.Debugf("(%s) store is nil", string(s)) + return + } + + store.cache = maps.NewConcurrent[*Entry[any]]() + store.dirty = true +} + +func Print(s Store) string { + defer log.Trace(time.Now(), string(s)) + + store := s.get() + if store == nil { + return fmt.Sprintf("Store %s is nil", string(s)) + } + + cache := store.cache.ToSimple() + if len(cache) == 0 { + return fmt.Sprintf("Store %s is empty", string(s)) + } + + var builder strings.Builder + + for key, entry := range cache { + builder.WriteString("\n") + + if entry.Expired() { + fmt.Fprintf(&builder, "Key: %s [EXPIRED]\n", key) + builder.WriteString("\n") + continue + } + + var ttlInfo string + if entry.TTL < 0 { + ttlInfo = "never expires" + } + if entry.TTL >= 0 { + expiresAt := time.Unix(entry.Timestamp+int64(entry.TTL), 0) + ttlInfo = fmt.Sprintf("expires at %s", expiresAt.Format("2006-01-02 15:04:05")) + } + + fmt.Fprintf(&builder, "Key: %s\n", key) + fmt.Fprintf(&builder, " Value: %s\n", fmt.Sprintf("%#v", entry.Value)) + fmt.Fprintf(&builder, " Type: %T\n", entry.Value) + fmt.Fprintf(&builder, " Created: %s\n", time.Unix(entry.Timestamp, 0).Format("2006-01-02 15:04:05")) + fmt.Fprintf(&builder, " TTL: %s\n", ttlInfo) + } + + return builder.String() +} diff --git a/src/cache/store_test.go b/src/cache/store_test.go new file mode 100644 index 000000000000..4dc03b42e360 --- /dev/null +++ b/src/cache/store_test.go @@ -0,0 +1,89 @@ +package cache + +import ( + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestStore(t *testing.T) { + cases := []struct { + setupFunc func() *store + testFunc func(t *testing.T) + name string + }{ + { + name: "Print store with data", + setupFunc: func() *store { + testStore := Session.new() + testStore.cache.Set("test_key1", &Entry[any]{ + Value: "test_value1", + Timestamp: time.Now().Unix(), + TTL: 3600, // 1 hour + }) + testStore.cache.Set("test_key2", &Entry[any]{ + Value: 42, + Timestamp: time.Now().Unix(), + TTL: -1, // never expires + }) + testStore.cache.Set("expired_key", &Entry[any]{ + Value: "expired_value", + Timestamp: time.Now().Unix() - 7200, // 2 hours ago + TTL: 3600, // 1 hour (should be expired) + }) + session = testStore + return testStore + }, + testFunc: func(t *testing.T) { + result := Print(Session) + assert.Contains(t, result, "Key: test_key1") + assert.Contains(t, result, `Value: "test_value1"`) // Note: quotes are included in output + assert.Contains(t, result, "Type: string") + assert.Contains(t, result, "Key: test_key2") + assert.Contains(t, result, "Value: 42") + assert.Contains(t, result, "Type: int") + assert.Contains(t, result, "Key: expired_key [EXPIRED]") + assert.Contains(t, result, "never expires") + assert.Contains(t, result, "expires at") + + // Verify structure + lines := strings.Split(result, "\n") + assert.True(t, len(lines) > 10, "Output should have multiple lines") + }, + }, + { + name: "Print empty store", + setupFunc: func() *store { + testStore := Session.new() + session = testStore + return testStore + }, + testFunc: func(t *testing.T) { + result := Print(Session) + assert.Contains(t, result, "Store session is empty") + }, + }, + { + name: "Print nil store check", + setupFunc: func() *store { + testStore := Session.new() + session = testStore + return testStore + }, + testFunc: func(t *testing.T) { + // Since get() always creates a store, we test empty store behavior + result := Print(Session) + assert.Contains(t, result, "Store session is empty") + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + tc.setupFunc() + tc.testFunc(t) + }) + } +} diff --git a/src/cache/template.go b/src/cache/template.go new file mode 100644 index 000000000000..223cb50a8b93 --- /dev/null +++ b/src/cache/template.go @@ -0,0 +1,39 @@ +package cache + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/maps" +) + +type Template struct { + Segments *maps.Concurrent[any] + SimpleTemplate +} + +type SimpleTemplate struct { + SegmentsCache maps.Simple[any] + Var maps.Simple[any] + PWD string + Folder string + PSWD string + UserName string + HostName string + ShellVersion string + Shell string + AbsolutePWD string + OS string + Version string + PromptCount int + SHLVL int + Jobs int + Code int + WSL bool + Root bool +} + +func (t *Template) AddSegmentData(key string, value any) { + t.Segments.Set(key, value) +} + +func (t *Template) RemoveSegmentData(key string) { + t.Segments.Delete(key) +} diff --git a/src/cli/args.go b/src/cli/args.go new file mode 100644 index 000000000000..6fb9aec7705d --- /dev/null +++ b/src/cli/args.go @@ -0,0 +1,17 @@ +package cli + +import ( + "github.com/spf13/cobra" +) + +func NoArgsOrOneValidArg(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return nil + } + + if err := cobra.ExactArgs(1)(cmd, args); err != nil { + return err + } + + return cobra.OnlyValidArgs(cmd, args) +} diff --git a/src/cli/auth.go b/src/cli/auth.go new file mode 100644 index 000000000000..5863e05e2d2a --- /dev/null +++ b/src/cli/auth.go @@ -0,0 +1,68 @@ +package cli + +import ( + "os" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/auth" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + + "github.com/spf13/cobra" +) + +var authCmd = &cobra.Command{ + Use: "auth [service]", + Short: "Authenticate against a service", + Long: `Authenticate against a service. + +Available services: + +- copilot: GitHub Copilot API +- ytmda: YouTube Music Desktop App (YTMDA) API`, + ValidArgs: []string{ + "copilot", + "ytmda", + }, + Args: NoArgsOrOneValidArg, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + flags := &runtime.Flags{ + Shell: os.Getenv("POSH_SHELL"), + } + + env := &runtime.Terminal{} + env.Init(flags) + + cache.Init(env.Shell(), cache.Persist) + + defer func() { + cache.Close() + }() + + switch args[0] { + case "copilot": + authenticator := auth.NewCopilot(env) + if err := auth.Run(authenticator); err != nil { + log.Error(err) + exitcode = 70 + } + case "ytmda": + authenticator := auth.NewYtmda(env) + if err := auth.Run(authenticator); err != nil { + log.Error(err) + exitcode = 70 + } + default: + _ = cmd.Help() + } + }, +} + +func init() { + RootCmd.AddCommand(authCmd) +} diff --git a/src/cli/auth/cli.go b/src/cli/auth/cli.go new file mode 100644 index 000000000000..0a0826256d8a --- /dev/null +++ b/src/cli/auth/cli.go @@ -0,0 +1,105 @@ +package auth + +import ( + "fmt" + + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" +) + +var ( + program *tea.Program + textStyle = lipgloss.NewStyle().Margin(1, 0, 2, 2) +) + +type stateMsg state + +type state int + +const ( + code state = iota + token + done +) + +// ErrorGetter is implemented by auth models to get the error. +type ErrorGetter interface { + GetError() error +} + +func setState(message state) { + if program == nil { + return + } + + program.Send(stateMsg(message)) +} + +type model struct { + env runtime.Environment + err error + spinner *spinner.Model + status func(error) string + code string + state state +} + +func (m *model) Init() tea.Cmd { + s := spinner.New() + s.Spinner = spinner.Globe + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) + m.spinner = &s + + return m.spinner.Tick +} + +func (m *model) GetError() error { + return m.err +} + +func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case stateMsg: + m.state = state(msg) + if m.state == done { + return m, tea.Quit + } + + return m, nil + + default: + s, cmd := m.spinner.Update(msg) + m.spinner = &s + return m, cmd + } +} + +func (m *model) View() string { + var message string + + switch m.state { + case code: + message = fmt.Sprintf("%s Fetching code for authentication", m.spinner.View()) + case token: + message = fmt.Sprintf("%s Fetching token with code: %s", m.spinner.View(), m.code) + case done: + message = m.status(m.err) + } + + return textStyle.Render(message) +} + +func Run(m tea.Model) error { + program = tea.NewProgram(m) + resultModel, _ := program.Run() + + if eg, ok := resultModel.(ErrorGetter); ok { + return eg.GetError() + } + + log.Debug("model does not implement ErrorGetter") + return nil +} diff --git a/src/cli/auth/copilot.go b/src/cli/auth/copilot.go new file mode 100644 index 000000000000..20d560cedd12 --- /dev/null +++ b/src/cli/auth/copilot.go @@ -0,0 +1,219 @@ +package auth + +import ( + "encoding/json" + "fmt" + httplib "net/http" + "strings" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" +) + +const ( + // GitHub Copilot's OAuth client ID - This is a public client ID used for device code flow + CopilotClientID = "Iv1.b507a08c87ecfe98" + CopilotScope = "read:email" + + CopilotDeviceCodeURL = "https://github.com/login/device/code" + CopilotAccessTokenURL = "https://github.com/login/oauth/access_token" + + CopilotTokenKey = "copilot_token" +) + +// DeviceCodeResponse represents the response from GitHub's device code endpoint. +type DeviceCodeResponse struct { + DeviceCode string `json:"device_code"` + UserCode string `json:"user_code"` + VerificationURI string `json:"verification_uri"` + ExpiresIn int `json:"expires_in"` + Interval int `json:"interval"` +} + +// AccessTokenResponse represents the response from GitHub's access token endpoint. +type AccessTokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + Scope string `json:"scope"` + Error string `json:"error"` + ErrorDescription string `json:"error_description"` +} + +func NewCopilot(env runtime.Environment) *CopilotAuth { + return &CopilotAuth{ + model: model{ + env: env, + }, + } +} + +type CopilotAuth struct { + deviceCodeExpiry time.Time + verificationURI string + model + lastState state +} + +func (c *CopilotAuth) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case stateMsg: + c.state = state(msg) + if c.state == done { + return c, tea.Quit + } + + return c, nil + + default: + s, cmd := c.spinner.Update(msg) + c.spinner = &s + return c, cmd + } +} + +func (c *CopilotAuth) Init() tea.Cmd { + c.model.status = c.status + cmd := c.model.Init() + go c.Authenticate() + return cmd +} + +func (c *CopilotAuth) Authenticate() { + setState(code) + c.lastState = code + + deviceCode, err := c.requestDeviceCode() + if err != nil { + c.err = err + setState(done) + return + } + + c.code = deviceCode.UserCode + c.verificationURI = deviceCode.VerificationURI + c.deviceCodeExpiry = time.Now().Add(time.Duration(deviceCode.ExpiresIn) * time.Second) + + setState(token) + c.lastState = token + + interval := max(deviceCode.Interval, 5) + + token, err := c.pollForToken(deviceCode.DeviceCode, interval) + if err != nil { + c.err = err + setState(done) + return + } + + if token == "" { + c.err = fmt.Errorf("received empty token") + setState(done) + return + } + + cache.Set(cache.Device, CopilotTokenKey, token, cache.TWOYEARS) + + setState(done) +} + +func (c *CopilotAuth) requestDeviceCode() (*DeviceCodeResponse, error) { + body := fmt.Sprintf("client_id=%s&scope=%s", CopilotClientID, CopilotScope) + + modifyRequest := func(request *httplib.Request) { + request.Method = httplib.MethodPost + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + request.Header.Set("Accept", "application/json") + } + + response, err := c.env.HTTPRequest(CopilotDeviceCodeURL, strings.NewReader(body), 30000, modifyRequest) + if err != nil { + return nil, fmt.Errorf("failed to request device code: %w", err) + } + + var result DeviceCodeResponse + if err := json.Unmarshal(response, &result); err != nil { + return nil, fmt.Errorf("failed to parse device code response: %w", err) + } + + return &result, nil +} + +func (c *CopilotAuth) pollForToken(deviceCode string, interval int) (string, error) { + modifyRequest := func(request *httplib.Request) { + request.Method = httplib.MethodPost + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + request.Header.Set("Accept", "application/json") + } + + for { + if time.Now().After(c.deviceCodeExpiry) { + return "", fmt.Errorf("device code expired, please try again") + } + + time.Sleep(time.Duration(interval) * time.Second) + + body := fmt.Sprintf("client_id=%s&device_code=%s&grant_type=urn:ietf:params:oauth:grant-type:device_code", CopilotClientID, deviceCode) + response, err := c.env.HTTPRequest(CopilotAccessTokenURL, strings.NewReader(body), 30000, modifyRequest) + if err != nil { + // Log error but continue polling + continue + } + + var result AccessTokenResponse + if err := json.Unmarshal(response, &result); err != nil { + // Log error but continue polling + continue + } + + if result.AccessToken != "" { + return result.AccessToken, nil + } + + switch result.Error { + case "authorization_pending": + continue + case "slow_down": + interval += 5 + continue + case "expired_token": + return "", fmt.Errorf("device code expired, please try again") + case "access_denied": + return "", fmt.Errorf("access was denied by the user") + default: + if result.Error != "" { + return "", fmt.Errorf("authentication error: %s - %s", result.Error, result.ErrorDescription) + } + } + } +} + +func (c *CopilotAuth) status(err error) string { + if err == nil { + return "Successfully authenticated with GitHub Copilot" + } + + httpErr, ok := err.(*http.Error) + if !ok { + return err.Error() + } + + return fmt.Sprintf("HTTP error %d: %s", httpErr.StatusCode, httpErr.Error()) +} + +func (c *CopilotAuth) View() string { + var message string + + switch c.state { + case code: + message = fmt.Sprintf("%s Requesting device code from GitHub", c.spinner.View()) + case token: + message = fmt.Sprintf("%s Please visit %s and enter code: %s", c.spinner.View(), c.verificationURI, c.code) + case done: + message = c.status(c.err) + } + + return textStyle.Render(message) +} diff --git a/src/cli/auth/ytmda.go b/src/cli/auth/ytmda.go new file mode 100644 index 000000000000..f2dc3735dcfd --- /dev/null +++ b/src/cli/auth/ytmda.go @@ -0,0 +1,152 @@ +package auth + +import ( + "encoding/json" + "errors" + "fmt" + "net" + httplib "net/http" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" +) + +const ( + YTMDABASEURL = "http://localhost:9863/api/v1" + YTMDATOKEN = "ytmda_token" + + tokenURL = YTMDABASEURL + "/auth/request" + codeURL = YTMDABASEURL + "/auth/requestcode" +) + +func NewYtmda(env runtime.Environment) *Ytmda { + return &Ytmda{ + model: model{ + env: env, + }, + } +} + +type Ytmda struct { + model + lastState state +} + +func (y *Ytmda) Init() tea.Cmd { + y.model.status = y.status + cmd := y.model.Init() + go y.Authenticate() + return cmd +} + +func (y *Ytmda) Authenticate() { + setState(code) + y.lastState = code + + code, err := y.requestCode() + if err != nil { + y.err = err + setState(done) + return + } + + y.code = code + setState(token) + y.lastState = token + + token, err := y.requestToken(code) + if err != nil { + y.err = err + setState(done) + return + } + + if token == "" { + y.err = fmt.Errorf("received empty token") + setState(done) + return + } + + cache.Set(cache.Device, YTMDATOKEN, token, cache.INFINITE) + + setState(done) +} + +func (y *Ytmda) requestCode() (string, error) { + body := fmt.Sprintf(`{"appId": "ohmyposh", "appName": "oh-my-posh", "appVersion": "%s"}`, strings.TrimPrefix(build.Version, "v")) + + type codeResponse struct { + Code string `json:"code"` + } + + result, err := ytmdaRequest[codeResponse](httplib.MethodPost, codeURL, body, y.env) + + return result.Code, err +} + +func (y *Ytmda) requestToken(code string) (string, error) { + body := fmt.Sprintf(`{"appId": "ohmyposh", "code": "%s"}`, code) + + type tokenResponse struct { + Token string `json:"token"` + } + + result, err := ytmdaRequest[tokenResponse](httplib.MethodPost, tokenURL, body, y.env) + + return result.Token, err +} + +func ytmdaRequest[a any](method, url, body string, env runtime.Environment, requestModifiers ...http.RequestModifier) (a, error) { + if requestModifiers == nil { + requestModifiers = []http.RequestModifier{} + } + + modifyRequest := func(request *httplib.Request) { + request.Method = method + request.Header.Set("Content-Type", "application/json") + } + + requestModifiers = append(requestModifiers, modifyRequest) + + var result a + + response, err := env.HTTPRequest(url, strings.NewReader(body), 50000, requestModifiers...) + if err != nil { + return result, err + } + + err = json.Unmarshal(response, &result) + return result, err +} + +func (y *Ytmda) status(err error) string { + // get the status code from the error if available + if err == nil { + return "Successfully authenticated with YouTube Music Desktop App" + } + + var netErr net.Error + if errors.As(err, &netErr) && netErr.Timeout() { + return "There was a timeout while trying to connect to the YouTube Music Desktop App Companion API. Please try again" + } + + httpErr, ok := err.(*http.Error) + if !ok { + // if the error is not an http.Error, the service isn't running + return "YouTube Music Desktop App is not running, please start the Companion API" + } + + if httpErr.StatusCode != httplib.StatusForbidden { + return err.Error() + } + + if y.lastState == token { + return "Failed to request token with code. Please press Allow in the pop-up window" + } + + return "Please enable companion authorization in the YouTube Music Desktop App settings" +} diff --git a/src/cli/auth/ytmda_test.go b/src/cli/auth/ytmda_test.go new file mode 100644 index 000000000000..20752e19db55 --- /dev/null +++ b/src/cli/auth/ytmda_test.go @@ -0,0 +1,107 @@ +package auth + +import ( + "errors" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + runtime_ "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestYtdma_Authenticate(t *testing.T) { + testCases := []struct { + name string + requestCodeResponse string + requestCodeError error + requestTokenResponse string + requestTokenError error + expectedError error + expectedToken string + shouldSetToken bool + }{ + { + name: "successful authentication", + requestCodeResponse: `{"code":"test-code-123"}`, + requestCodeError: nil, + requestTokenResponse: `{"token":"test-token-456"}`, + requestTokenError: nil, + expectedError: nil, + expectedToken: "test-token-456", + shouldSetToken: true, + }, + { + name: "request code fails", + requestCodeResponse: "", + requestCodeError: errors.New("failed to request code"), + requestTokenResponse: "", + requestTokenError: nil, + expectedError: errors.New("failed to request code"), + expectedToken: "", + shouldSetToken: false, + }, + { + name: "request token fails", + requestCodeResponse: `{"code":"test-code-123"}`, + requestCodeError: nil, + requestTokenResponse: "", + requestTokenError: errors.New("failed to request token"), + expectedError: errors.New("failed to request token"), + expectedToken: "", + shouldSetToken: false, + }, + { + name: "invalid code response JSON", + requestCodeResponse: `{"invalid":"json"}`, + requestCodeError: nil, + requestTokenResponse: "", + requestTokenError: nil, + expectedError: errors.New("unexpected end of JSON input"), + expectedToken: "", + shouldSetToken: false, + }, + { + name: "invalid token response JSON", + requestCodeResponse: `{"code":"test-code-123"}`, + requestCodeError: nil, + requestTokenResponse: `{"invalid":"json"}`, + requestTokenError: nil, + expectedError: errors.New("received empty token"), + expectedToken: "", + shouldSetToken: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + env := &runtime_.Environment{} + + env.On("HTTPRequest", codeURL).Return([]byte(tc.requestCodeResponse), tc.requestCodeError) + env.On("HTTPRequest", tokenURL).Return([]byte(tc.requestTokenResponse), tc.requestTokenError) + + ytmda := &Ytmda{ + model: model{ + env: env, + }, + } + + ytmda.Authenticate() + + if tc.expectedError != nil { + require.NotNil(t, ytmda.err) + assert.Equal(t, tc.expectedError.Error(), ytmda.err.Error()) + } else { + assert.Nil(t, ytmda.err) + } + + if tc.shouldSetToken { + token, ok := cache.Get[string](cache.Device, YTMDATOKEN) + require.True(t, ok) + assert.Equal(t, tc.expectedToken, token) + } + + cache.DeleteAll(cache.Device) + }) + } +} diff --git a/src/cli/cache.go b/src/cli/cache.go new file mode 100644 index 000000000000..03676299373d --- /dev/null +++ b/src/cli/cache.go @@ -0,0 +1,86 @@ +package cli + +import ( + "fmt" + "os" + "strconv" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + + "github.com/spf13/cobra" +) + +var ( + session bool +) + +// cacheCmd represents the cache command +var cacheCmd = &cobra.Command{ + Use: "cache [path|clear|ttl|show]", + Short: "Interact with the oh-my-posh cache", + Long: `Interact with the oh-my-posh cache. + +You can do the following: + +- path: list cache path +- clear: remove all cache values +- ttl: get cache TTL in days +- show: print a detailed list of all cached values`, + ValidArgs: []string{ + "path", + "clear", + cache.TTL, + "show", + }, + Args: cobra.RangeArgs(1, 2), + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + switch args[0] { + case "path": + fmt.Println(cache.Path()) + case "clear": + err := cache.Clear(true) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("cache cleared") + case cache.TTL: + // get the second argument as int + if len(args) < 2 { + fmt.Println("please provide a TTL value in days") + exitcode = 2 + return + } + + ttl, err := strconv.Atoi(args[1]) + if err != nil { + fmt.Println("error parsing TTL:", err.Error()) + exitcode = 2 + return + } + + cache.Init(os.Getenv("POSH_SHELL"), cache.Persist) + cache.Set(cache.Device, cache.TTL, ttl, cache.INFINITE) + cache.Close() + case "show": + cache.Init(os.Getenv("POSH_SHELL")) + store := cache.Device + if session { + store = cache.Session + } + + fmt.Println(cache.Print(store)) + } + }, +} + +func init() { + cacheCmd.Flags().BoolVarP(&session, "session", "s", false, "show the session cache") + RootCmd.AddCommand(cacheCmd) +} diff --git a/src/cli/claude.go b/src/cli/claude.go new file mode 100644 index 000000000000..aa80b3d9d608 --- /dev/null +++ b/src/cli/claude.go @@ -0,0 +1,119 @@ +package cli + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/prompt" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/segments" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + + "github.com/spf13/cobra" +) + +// claudeCmd represents the claude command +var claudeCmd = &cobra.Command{ + Use: "claude", + Short: "Render a prompt for Claude Code statusline", + Long: `Render a prompt for Claude Code statusline integration. + +This command reads Claude Code's contextual JSON data from stdin and renders +a prompt that can include a Claude segment with session information like +model name, costs, tokens, and more. + +Example usage in Claude Code settings: + "statusLine": { + "command": "oh-my-posh claude --config ~/.config/ohmyposh/claude.toml" + }`, + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { + log.Debug("claude command started") + + // Read JSON from stdin + stdinData, err := io.ReadAll(os.Stdin) + if err != nil { + log.Error(err) + return + } + + log.Debugf("received data from stdin: %s", string(stdinData)) + + // Process Claude data and initialize cache + processClaudeData(stdinData) + + flags := &runtime.Flags{ + ConfigPath: configFlag, + Shell: shell.CLAUDE, + } + + env := &runtime.Terminal{} + env.Init(flags) + + var cfg *config.Config + + cfg, err = config.Parse(configFlag) + if err != nil { + cfg = config.Claude() + } + + template.Init(env, cfg.Var, cfg.Maps) + terminal.Init(shell.CLAUDE) + terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() + terminal.Colors = cfg.MakeColors(env) + + eng := &prompt.Engine{ + Config: cfg, + Env: env, + } + + defer func() { + template.SaveCache() + cache.Close() + }() + + result := eng.Status() + fmt.Print(result) + }, +} + +// processClaudeData handles parsing and caching of Claude JSON data +func processClaudeData(stdinData []byte) { + if len(stdinData) == 0 { + cache.Init(shell.CLAUDE, cache.Persist, cache.NoSession) + return + } + + var claudeData segments.ClaudeData + if err := json.Unmarshal(stdinData, &claudeData); err != nil { + log.Error(err) + cache.Init(shell.CLAUDE, cache.Persist, cache.NoSession) + return + } + + log.Debugf("parsed Claude data: session_id=%s, model=%s", claudeData.SessionID, claudeData.Model.DisplayName) + + // Set the session ID from Claude data if available + if claudeData.SessionID != "" { + os.Setenv("POSH_SESSION_ID", claudeData.SessionID) + log.Debugf("set POSH_SESSION_ID to: %s", claudeData.SessionID) + } + + // Initialize cache first so we can store the data + cache.Init(shell.CLAUDE, cache.Persist) + + // Store the parsed data in session cache + cache.Set(cache.Session, cache.CLAUDECACHE, claudeData, cache.INFINITE) + log.Debug("stored Claude data in session cache") +} + +func init() { + RootCmd.AddCommand(claudeCmd) +} diff --git a/src/cli/config.go b/src/cli/config.go new file mode 100644 index 000000000000..e5f01f6cab20 --- /dev/null +++ b/src/cli/config.go @@ -0,0 +1,49 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/dsc" + "github.com/spf13/cobra" +) + +// configCmd represents the config command +var configCmd = &cobra.Command{ + Use: "config edit", + Short: "Interact with the config", + Long: `Interact with the config. + +You can export, migrate or edit the config (via the editor specified in the environment variable "EDITOR").`, + ValidArgs: []string{ + "edit", + }, + Args: NoArgsOrOneValidArg, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + switch args[0] { + case "edit": + cache.Init(os.Getenv("POSH_SHELL")) + if configPath, OK := cache.Get[string](cache.Session, config.SourceKey); OK { + exitcode = editFileWithEditor(configPath) + return + } + + fmt.Println("no config found in session cache") + exitcode = 666 + default: + _ = cmd.Help() + } + }, +} + +func init() { + configCmd.AddCommand(dsc.Command(config.DSC())) + RootCmd.AddCommand(configCmd) +} diff --git a/src/cli/config_export.go b/src/cli/config_export.go new file mode 100644 index 000000000000..180a737a17d1 --- /dev/null +++ b/src/cli/config_export.go @@ -0,0 +1,119 @@ +package cli + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" + + "github.com/spf13/cobra" +) + +var ( + format string + output string +) + +// exportCmd represents the export command +var exportCmd = &cobra.Command{ + Use: "export", + Short: "Export your config", + Long: `Export your config. + +You can choose to print the output to stdout, or export your config in the format of your choice. + +Example usage: + +> oh-my-posh config export --config ~/myconfig.omp.json --format toml + +Exports the config file "~/myconfig.omp.json" in TOML format and prints the result to stdout. + +> oh-my-posh config export --output ~/new_config.omp.json + +Exports the current config to "~/new_config.omp.json" (in JSON format).`, + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { + if output == "" && format == "" { + // usage error + fmt.Println("neither output path nor export format is specified") + exitcode = 2 + return + } + + cache.Init(os.Getenv("POSH_SHELL")) + + err := setConfigFlag() + if err != nil { + exitcode = 666 + fmt.Println(err.Error()) + return + } + + cfg := config.Load(configFlag) + + validateExportFormat := func() error { + format = strings.ToLower(format) + switch format { + case config.JSON, config.JSONC: + format = config.JSON + case config.TOML, config.TML: + format = config.TOML + case config.YAML, config.YML: + format = config.YAML + default: + formats := []string{config.JSON, config.JSONC, config.TOML, config.TML, config.YAML, config.YML} + // usage error + fmt.Printf("export format must be one of these: %s\n", strings.Join(formats, ", ")) + exitcode = 2 + return errors.New("invalid export format") + } + + return nil + } + + if len(format) != 0 { + if err := validateExportFormat(); err != nil { + return + } + } + + if output == "" { + fmt.Print(cfg.Export(format)) + return + } + + cfg.Source = cleanOutputPath(output) + + if format == "" { + format = strings.TrimPrefix(filepath.Ext(output), ".") + if err := validateExportFormat(); err != nil { + return + } + } + + cfg.Write(format) + }, +} + +func cleanOutputPath(output string) string { + output = path.ReplaceTildePrefixWithHomeDir(output) + + if !filepath.IsAbs(output) { + if absPath, err := filepath.Abs(output); err == nil { + output = absPath + } + } + + return filepath.Clean(output) +} + +func init() { + exportCmd.Flags().StringVarP(&format, "format", "f", "json", "config format to migrate to") + exportCmd.Flags().StringVarP(&output, "output", "o", "", "config file to export to") + configCmd.AddCommand(exportCmd) +} diff --git a/src/cli/config_export_image.go b/src/cli/config_export_image.go new file mode 100644 index 000000000000..65c79eb5f71b --- /dev/null +++ b/src/cli/config_export_image.go @@ -0,0 +1,160 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/image" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/prompt" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + + "github.com/spf13/cobra" +) + +var ( + author string + colorSettingsFile string + bgColor string + outputImage string +) + +// imageCmd represents the image command +var imageCmd = &cobra.Command{ + Use: "image", + Short: "Export your config to an image", + Long: `Export your config to an image. + +You can tweak the output by using additional flags: + +- cursor-padding: the padding of the prompt cursor +- rprompt-offset: the offset of the right prompt +- settings: JSON file with overrides + +Example usage: + +> oh-my-posh config export image --config ~/myconfig.omp.json + +Exports the config to an image file called myconfig.png in the current working directory. + +> oh-my-posh config export image --config ~/myconfig.omp.json --output ~/mytheme.png + +Exports the config to an image file ~/mytheme.png. + +> oh-my-posh config export image --config ~/myconfig.omp.json --settings ~/.image.settings.json + +Exports the config to an image file using customized output settings.`, + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { + cache.Init(os.Getenv("POSH_SHELL")) + + err := setConfigFlag() + if err != nil { + exitcode = 666 + fmt.Println(err.Error()) + return + } + + cfg := config.Load(configFlag) + + flags := &runtime.Flags{ + ConfigPath: cfg.Source, + Shell: shell.GENERIC, + TerminalWidth: 120, + } + + env := &runtime.Terminal{} + env.Init(flags) + + template.Init(env, cfg.Var, cfg.Maps) + + defer func() { + template.SaveCache() + cache.Close() + }() + + // set sane defaults for things we don't print + cfg.ConsoleTitleTemplate = "" + cfg.PWD = "" + cfg.ShellIntegration = false + + terminal.Init(shell.GENERIC) + terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() + terminal.Colors = cfg.MakeColors(env) + + eng := &prompt.Engine{ + Config: cfg, + Env: env, + } + + settings, err := image.LoadSettings(colorSettingsFile) + if err != nil { + settings = &image.Settings{ + Colors: image.NewColors(), + Author: author, + BackgroundColor: bgColor, + } + } + + if settings.Colors == nil { + settings.Colors = image.NewColors() + } + + if settings.Cursor == "" { + settings.Cursor = "_" + } + + primaryPrompt := eng.Primary() + + imageCreator := &image.Renderer{ + AnsiString: primaryPrompt, + Settings: *settings, + } + + if outputImage != "" { + imageCreator.Path = cleanOutputPath(outputImage) + } + + err = imageCreator.Init(env) + if err != nil { + fmt.Print(err.Error()) + return + } + + err = imageCreator.SavePNG() + if err != nil { + fmt.Print(err.Error()) + } + }, +} + +func init() { + imageCmd.Flags().StringVar(&author, "author", "", "config author") + imageCmd.Flags().StringVar(&bgColor, "background-color", "", "image background color") + imageCmd.Flags().StringVarP(&outputImage, "output", "o", "", "image file (.png) to export to") + imageCmd.Flags().StringVar(&colorSettingsFile, "settings", "", "color settings file to override ANSI color codes and metadata") + + // deprecated flags + _ = imageCmd.Flags().MarkHidden("author") + _ = imageCmd.Flags().MarkHidden("background-color") + + exportCmd.AddCommand(imageCmd) +} + +func setConfigFlag() error { + if configFlag != "" { + return nil + } + + configPath, OK := cache.Get[string](cache.Session, config.SourceKey) + if !OK { + return fmt.Errorf("no config found in session cache, please provide a config using the --config flag") + } + + configFlag = configPath + return nil +} diff --git a/src/cli/debug.go b/src/cli/debug.go new file mode 100644 index 000000000000..6063756a1b47 --- /dev/null +++ b/src/cli/debug.go @@ -0,0 +1,95 @@ +package cli + +import ( + "fmt" + "os" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/prompt" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + + "github.com/spf13/cobra" +) + +// debugCmd represents the debug command +var ( + debugCmd = createDebugCmd() + startTime = time.Now() +) + +func init() { + RootCmd.AddCommand(debugCmd) +} + +func createDebugCmd() *cobra.Command { + debugCmd := &cobra.Command{ + Use: "debug", + Short: "Print the prompt in debug mode", + Long: "Print the prompt in debug mode.", + Run: func(_ *cobra.Command, _ []string) { + startTime := time.Now() + + log.Enable(plain) + + flags := &runtime.Flags{ + Debug: true, + PWD: pwd, + Shell: shell.GENERIC, + Plain: plain, + } + + env := &runtime.Terminal{} + env.Init(flags) + + cache.Init(os.Getenv("POSH_SHELL")) + + cfg := getDebugConfig(configFlag) + + template.Init(env, cfg.Var, cfg.Maps) + + defer func() { + template.SaveCache() + cache.Close() + }() + + terminal.Init(shell.GENERIC) + terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() + terminal.Colors = cfg.MakeColors(env) + terminal.Plain = plain + + eng := &prompt.Engine{ + Config: cfg, + Env: env, + Plain: plain, + } + + fmt.Print(eng.PrintDebug(startTime, build.Version)) + }, + } + + debugCmd.Flags().StringVar(&pwd, "pwd", "", "current working directory") + + // Deprecated flags, should be kept to avoid breaking CLI integration. + debugCmd.Flags().StringVar(&shellName, "shell", "", "the shell to print for") + + // Hide flags that are deprecated or for internal use only. + _ = debugCmd.Flags().MarkHidden("shell") + + return debugCmd +} + +func getDebugConfig(configpath string) *config.Config { + if len(configpath) != 0 { + return config.Load(configpath) + } + + reload, _ := cache.Get[bool](cache.Device, config.RELOAD) + return config.Get(configpath, reload) +} diff --git a/src/cli/disable.go b/src/cli/disable.go new file mode 100644 index 000000000000..747959a046a2 --- /dev/null +++ b/src/cli/disable.go @@ -0,0 +1,27 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// disableCmd represents the disable command +var disableCmd = &cobra.Command{ + Use: fmt.Sprintf(toggleUse, "disable"), + Short: "Disable a feature", + Long: fmt.Sprintf(toggleLong, "Disable"), + ValidArgs: toggleArgs, + Args: NoArgsOrOneValidArg, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + toggleFeature(cmd, args[0], false) + }, +} + +func init() { + RootCmd.AddCommand(disableCmd) +} diff --git a/src/cli/edit.go b/src/cli/edit.go new file mode 100644 index 000000000000..ad2be0d30972 --- /dev/null +++ b/src/cli/edit.go @@ -0,0 +1,37 @@ +package cli + +import ( + "context" + "fmt" + "os" + "os/exec" + "strings" +) + +func editFileWithEditor(file string) int { + editor := strings.TrimSpace(os.Getenv("EDITOR")) + if editor == "" { + fmt.Println(`no editor specified in the environment variable "EDITOR"`) + return 1 + } + + editor = strings.TrimSpace(editor) + args := strings.Split(editor, " ") + editor = args[0] + args = append(args[1:], file) + + ctx := context.Background() + cmd := exec.CommandContext(ctx, editor, args...) + + cmd.Stdin = os.Stdin + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + fmt.Println(err.Error()) + return 1 + } + + return 0 +} diff --git a/src/cli/enable.go b/src/cli/enable.go new file mode 100644 index 000000000000..ac13d5e9611b --- /dev/null +++ b/src/cli/enable.go @@ -0,0 +1,55 @@ +package cli + +import ( + "fmt" + "os" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + + "github.com/spf13/cobra" +) + +var ( + toggleHelpText = `%s one of the following features: +` + toggleArgs = []string{ + config.UPGRADENOTICE, + config.AUTOUPGRADE, + config.RELOAD, + } + toggleUse = fmt.Sprintf("%%s [%s]", strings.Join(toggleArgs, "|")) + toggleLong = strings.Join(append([]string{toggleHelpText}, toggleArgs...), "\n- ") +) + +// enableCmd represents the enable command +var enableCmd = &cobra.Command{ + Use: fmt.Sprintf(toggleUse, "enable"), + Short: "Enable a feature", + Long: fmt.Sprintf(toggleLong, "Enable"), + ValidArgs: toggleArgs, + Args: NoArgsOrOneValidArg, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + toggleFeature(cmd, args[0], true) + }, +} + +func init() { + RootCmd.AddCommand(enableCmd) +} + +func toggleFeature(cmd *cobra.Command, feature string, enable bool) { + if feature == "" { + _ = cmd.Help() + return + } + + cache.Init(os.Getenv("POSH_SHELL"), cache.Persist) + cache.Set(cache.Device, feature, enable, cache.INFINITE) + cache.Close() +} diff --git a/src/cli/font.go b/src/cli/font.go new file mode 100644 index 000000000000..982cef9e327b --- /dev/null +++ b/src/cli/font.go @@ -0,0 +1,94 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/font" + "github.com/jandedobbeleer/oh-my-posh/src/dsc" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + + "github.com/spf13/cobra" +) + +var ( + zipFolder string + headless bool + + fontCmd = &cobra.Command{ + Use: "font [install|configure]", + Short: "Manage fonts", + Long: `Manage fonts. + +This command is used to install fonts and configure the font in your terminal. + + - install: oh-my-posh font install 3270`, + ValidArgs: []string{ + "install", + "configure", + }, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + switch args[0] { + case "install": + var fontName string + if len(args) > 1 { + fontName = args[1] + } + + env := &runtime.Terminal{} + env.Init(&runtime.Flags{}) + + sh := env.Shell() + + cache.Init(sh, cache.Persist) + + defer func() { + cache.Close() + }() + + terminal.Init(sh) + + if !strings.HasPrefix(zipFolder, "/") { + zipFolder += "/" + } + + fontName, err := font.Run(fontName, zipFolder, headless) + if err != nil { + log.Error(err) + exitcode = 70 + return + } + + if env.Root() { + // do not update the DSC cache if we are running as root + return + } + + fontDSC := font.DSC() + fontDSC.Load() + fontDSC.Add(fontName) + fontDSC.Save() + + return + case "configure": + fmt.Println("not implemented") + default: + _ = cmd.Help() + } + }, + } +) + +func init() { + fontCmd.Flags().StringVar(&zipFolder, "zip-folder", "", "the folder inside the zip file to install fonts from") + fontCmd.Flags().BoolVar(&headless, "headless", false, "install font without TUI") + fontCmd.AddCommand(dsc.Command(font.DSC())) + RootCmd.AddCommand(fontCmd) +} diff --git a/src/cli/font/download.go b/src/cli/font/download.go new file mode 100644 index 000000000000..80465d9cf937 --- /dev/null +++ b/src/cli/font/download.go @@ -0,0 +1,93 @@ +package font + +import ( + "context" + "errors" + "fmt" + "io" + httplib "net/http" + "net/url" + "os" + "path" + "path/filepath" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/progress" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" +) + +func Download(fontURL string) ([]byte, error) { + if zipPath, OK := cache.Get[string](cache.Device, fontURL); OK { + if b, err := os.ReadFile(zipPath); err == nil { + return b, nil + } + } + + // validate if we have a local file + u, err := url.Parse(fontURL) + if err != nil || u.Scheme != "https" { + return nil, errors.New("font path must be a valid URL") + } + + var b []byte + if b, err = getRemoteFile(fontURL); err != nil { + return nil, err + } + + if !isZipFile(b) { + return nil, fmt.Errorf("%s is not a valid zip file", fontURL) + } + + fileName := path.Base(fontURL) + + zipPath := filepath.Join(os.TempDir(), fileName) + tempFile, err := os.Create(zipPath) + defer func() { + _ = tempFile.Close() + }() + + if err != nil { + return b, nil + } + + _, err = tempFile.Write(b) + if err != nil { + return b, nil + } + + cache.Set(cache.Device, fontURL, zipPath, cache.ONEDAY) + + return b, nil +} + +func isZipFile(data []byte) bool { + contentType := httplib.DetectContentType(data) + return contentType == "application/zip" +} + +func getRemoteFile(location string) (data []byte, err error) { + req, err := httplib.NewRequestWithContext(context.Background(), "GET", location, nil) + if err != nil { + return nil, err + } + + resp, err := http.HTTPClient.Do(req) + if err != nil { + return + } + + defer resp.Body.Close() + + if resp.StatusCode != httplib.StatusOK { + return data, fmt.Errorf("failed to download zip file: %s\n→ %s", resp.Status, location) + } + + reader := progress.NewReader(resp.Body, resp.ContentLength, program) + + data, err = io.ReadAll(reader) + if err != nil { + return + } + + return +} diff --git a/src/cli/font/dsc.go b/src/cli/font/dsc.go new file mode 100644 index 000000000000..2e25b48cf903 --- /dev/null +++ b/src/cli/font/dsc.go @@ -0,0 +1,31 @@ +package font + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/dsc" + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +type Resource struct { + dsc.Resource[*Font] +} + +func DSC() *Resource { + return &Resource{ + Resource: dsc.Resource[*Font]{}, + } +} + +func (s *Resource) Apply(schema string) error { + return s.Resource.Apply(schema) +} + +func (s *Resource) Add(name string) { + if IsLocalZipFile(name) { + log.Debug("Skipping local zip file font:", name) + return + } + + s.Resource.Add(&Font{ + Name: name, + }) +} diff --git a/src/cli/font/font.go b/src/cli/font/font.go new file mode 100644 index 000000000000..cff4831c4daa --- /dev/null +++ b/src/cli/font/font.go @@ -0,0 +1,120 @@ +// Derived from https://github.com/Crosse/font-install +// Copyright 2020 Seth Wright +package font + +import ( + "bytes" + "encoding/gob" + "fmt" + "path" + "strings" + + "github.com/ConradIrwin/font/sfnt" +) + +func init() { + gob.Register([]*Font{}) + gob.Register([]*Asset{}) +} + +// Font describes a font file and the various metadata associated with it. +type Font struct { + Name string `json:"name,omitempty" jsonschema:"title=Font name,description=The name of the font"` + Family string `json:"-"` + FileName string `json:"-"` + Metadata map[sfnt.NameID]string `json:"-"` + Data []byte `json:"-"` +} + +func (f *Font) Apply() error { + _, err := downloadAndInstall(f.Name, "") + return err +} + +// downloadAndInstall resolves a font by name or URL, downloads it, and installs it. +// It returns the resolved font name and any error encountered. +func downloadAndInstall(font, zipFolder string) (string, error) { + asset, err := ResolveFontAsset(font) + if err != nil { + return "", err + } + + if asset.Folder != "" && zipFolder == "" { + zipFolder = asset.Folder + } + + zipFile, err := Download(asset.URL) + if err != nil { + return "", err + } + + _, err = InstallZIP(zipFile, zipFolder) + return asset.Name, err +} + +func (f *Font) Equal(font *Font) bool { + if font == nil { + return false + } + + return f.Name == font.Name +} + +func (f *Font) Resolve() (*Font, bool) { + return nil, false +} + +// fontExtensions is a list of file extensions that denote fonts. +// Only files ending with these extensions will be installed. +var fontExtensions = map[string]bool{ + ".otf": true, + ".ttf": true, +} + +// newFont creates a newFont Font struct. +// fileName is the font's file name, and data is a byte slice containing the font file data. +// It returns a FontData struct describing the font, or an error. +func newFont(fileName string, data []byte) (*Font, error) { + if _, ok := fontExtensions[strings.ToLower(path.Ext(fileName))]; !ok { + return nil, fmt.Errorf("not a font: %v", fileName) + } + + font := &Font{ + FileName: fileName, + Metadata: make(map[sfnt.NameID]string), + Data: data, + } + + fontData, err := sfnt.Parse(bytes.NewReader(font.Data)) + if err != nil { + return nil, err + } + + if !fontData.HasTable(sfnt.TagName) { + return nil, fmt.Errorf("font %v has no name table", fileName) + } + + nameTable, err := fontData.NameTable() + if err != nil { + return nil, err + } + + for _, nameEntry := range nameTable.List() { + font.Metadata[nameEntry.NameID] = nameEntry.String() + } + + font.Name = font.Metadata[sfnt.NameFull] + font.Family = font.Metadata[sfnt.NamePreferredFamily] + + if font.Family == "" { + if v, ok := font.Metadata[sfnt.NameFontFamily]; ok { + font.Family = v + } + } + + if font.Name == "" { + font.Name = fileName + } + + return font, nil +} diff --git a/src/cli/font/fonts.go b/src/cli/font/fonts.go new file mode 100644 index 000000000000..6cc0fa7d21d5 --- /dev/null +++ b/src/cli/font/fonts.go @@ -0,0 +1,142 @@ +package font + +import ( + "context" + "encoding/json" + "errors" + "fmt" + httplib "net/http" + "sort" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" +) + +const ( + CascadiaCodeMS = "CascadiaCode (MS)" +) + +type release struct { + Assets []*Asset `json:"assets"` +} + +type Asset struct { + Name string `json:"name"` + URL string `json:"browser_download_url"` + State string `json:"state"` + Folder string `json:"folder"` +} + +func (a Asset) FilterValue() string { return a.Name } + +func IsLocalZipFile(name string) bool { + return !strings.HasPrefix(name, "https") && strings.HasSuffix(name, ".zip") +} + +func ResolveFontAsset(font string) (*Asset, error) { + if strings.HasPrefix(font, "https") { + return &Asset{URL: font}, nil + } + + fonts, err := fonts() + if err != nil { + return nil, err + } + + var asset *Asset + for _, f := range fonts { + if !strings.EqualFold(font, f.Name) { + continue + } + + asset = f + break + } + + if asset == nil { + return nil, fmt.Errorf("no matching font found") + } + + return asset, nil +} + +func fonts() ([]*Asset, error) { + if assets, err := getCachedFontData(); err == nil { + return assets, nil + } + + assets, err := fetchFontAssets("ryanoasis/nerd-fonts") + if err != nil { + return nil, err + } + + cascadiaCode, err := CascadiaCode() + if err == nil { + assets = append(assets, cascadiaCode) + } + + sort.Slice(assets, func(i, j int) bool { return assets[i].Name < assets[j].Name }) + + cache.Set(cache.Device, cache.FONTLISTCACHE, assets, cache.ONEDAY) + + return assets, nil +} + +func getCachedFontData() ([]*Asset, error) { + list, OK := cache.Get[[]*Asset](cache.Device, cache.FONTLISTCACHE) + if !OK { + return nil, errors.New("cache not found") + } + + return list, nil +} + +func CascadiaCode() (*Asset, error) { + assets, err := fetchFontAssets("microsoft/cascadia-code") + if err != nil || len(assets) != 1 { + return nil, errors.New("no assets found") + } + + return &Asset{ + Name: CascadiaCodeMS, + URL: assets[0].URL, + Folder: "ttf/", + }, nil +} + +func fetchFontAssets(repo string) ([]*Asset, error) { + ctx, cancelF := context.WithTimeout(context.Background(), time.Second*time.Duration(20)) + defer cancelF() + + repoURL := "https://api.github.com/repos/" + repo + "/releases/latest" + req, err := httplib.NewRequestWithContext(ctx, "GET", repoURL, nil) + if err != nil { + return nil, err + } + + req.Header.Add("Accept", "application/vnd.github.v3+json") + response, err := http.HTTPClient.Do(req) + if err != nil || response.StatusCode != httplib.StatusOK { + return nil, fmt.Errorf("failed to get %s release", repo) + } + + defer response.Body.Close() + + var release release + err = json.NewDecoder(response.Body).Decode(&release) + if err != nil { + return nil, errors.New("failed to parse nerd fonts release") + } + + var fonts []*Asset + for _, asset := range release.Assets { + if asset.State == "uploaded" && strings.HasSuffix(asset.Name, ".zip") { + asset.Name = strings.TrimSuffix(asset.Name, ".zip") + fonts = append(fonts, asset) + } + } + + return fonts, nil +} diff --git a/src/cli/font/install.go b/src/cli/font/install.go new file mode 100644 index 000000000000..f0a5b572622d --- /dev/null +++ b/src/cli/font/install.go @@ -0,0 +1,99 @@ +// Derived from https://github.com/Crosse/font-install +// Copyright 2020 Seth Wright +package font + +import ( + "archive/zip" + "bytes" + "io" + "path" + stdruntime "runtime" + "slices" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/cmd" +) + +func contains[S ~[]E, E comparable](s S, e E) bool { + return slices.Contains(s, e) +} + +func InstallZIP(data []byte, folder string) ([]string, error) { + var families []string + bytesReader := bytes.NewReader(data) + + zipReader, err := zip.NewReader(bytesReader, int64(bytesReader.Len())) + if err != nil { + return families, err + } + + fonts := make(map[string]*Font) + + for _, file := range zipReader.File { + // prevent zipslip attacks + // https://security.snyk.io/research/zip-slip-vulnerability + // skip folders + if strings.Contains(file.Name, "..") || strings.HasSuffix(file.Name, "/") { + continue + } + + fontFileName := path.Base(file.Name) + fontRelativeFileName := strings.TrimPrefix(file.Name, folder) + + // do not install fonts that are not in the specified installation folder + if fontFileName != fontRelativeFileName { + continue + } + + fontReader, err := file.Open() + if err != nil { + continue + } + + defer fontReader.Close() + + fontBytes, err := io.ReadAll(fontReader) + if err != nil { + continue + } + + font, err := newFont(fontFileName, fontBytes) + if err != nil { + continue + } + + if _, found := fonts[font.Name]; !found { + fonts[font.Name] = font + continue + } + + // prefer .ttf files over other file types when we have a duplicate + first := strings.ToLower(path.Ext(fonts[font.Name].FileName)) + second := strings.ToLower(path.Ext(font.FileName)) + if first != second && second == ".ttf" { + fonts[font.Name] = font + } + } + + for _, font := range fonts { + if err = install(font); err != nil { + log.Error(err) + continue + } + + if found := contains(families, font.Family); !found { + families = append(families, font.Family) + } + } + + // Update the font cache when installing fonts on Linux + if stdruntime.GOOS == runtime.LINUX || stdruntime.GOOS == runtime.DARWIN { + _, _ = cmd.Run("fc-cache", "-f") + } + + slices.Sort(families) + + return families, nil +} diff --git a/src/cli/font/install_darwin.go b/src/cli/font/install_darwin.go new file mode 100644 index 000000000000..97d2454fd265 --- /dev/null +++ b/src/cli/font/install_darwin.go @@ -0,0 +1,23 @@ +// Derived from https://github.com/Crosse/font-install +// Copyright 2020 Seth Wright +package font + +import ( + "os" + "path" +) + +var FontsDir = path.Join(os.Getenv("HOME"), "Library", "Fonts") + +func install(font *Font) error { + // On darwin/OSX, the user's fonts directory is ~/Library/Fonts, + // and fonts should be installed directly into that path; + // i.e., not in subfolders. + fullPath := path.Join(FontsDir, path.Base(font.FileName)) + + if err := os.MkdirAll(path.Dir(fullPath), 0700); err != nil { + return err + } + + return os.WriteFile(fullPath, font.Data, 0644) +} diff --git a/src/cli/font/install_unix.go b/src/cli/font/install_unix.go new file mode 100644 index 000000000000..f34a525ba33a --- /dev/null +++ b/src/cli/font/install_unix.go @@ -0,0 +1,38 @@ +//go:build !windows && !darwin + +// Derived from https://github.com/Crosse/font-install +// Copyright 2020 Seth Wright +package font + +import ( + "os" + "path" + "strings" +) + +var ( + fontsDir = path.Join(os.Getenv("HOME"), "/.local/share/fonts") + systemFontsDir = "/usr/share/fonts" +) + +func install(font *Font) error { + // If we're running as root, install the font system-wide. + targetDir := fontsDir + if os.Geteuid() == 0 { + targetDir = systemFontsDir + } + + // On Linux, fontconfig can understand subdirectories. So, to keep the + // font directory clean, install all font files for a particular font + // family into a subdirectory named after the family (with hyphens instead + // of spaces). + fullPath := path.Join(targetDir, + strings.ToLower(strings.ReplaceAll(font.Family, " ", "-")), + path.Base(font.FileName)) + + if err := os.MkdirAll(path.Dir(fullPath), 0700); err != nil { + return err + } + + return os.WriteFile(fullPath, font.Data, 0644) +} diff --git a/src/cli/font/install_windows.go b/src/cli/font/install_windows.go new file mode 100644 index 000000000000..244f74a3ccb2 --- /dev/null +++ b/src/cli/font/install_windows.go @@ -0,0 +1,147 @@ +package font + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "syscall" + "unsafe" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "golang.org/x/sys/windows/registry" +) + +// https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-addfontresourcea + +const ( + WM_FONTCHANGE = 0x001D + HWND_BROADCAST = 0xFFFF +) + +func install(font *Font) error { + // To install a font on Windows: + // - Copy the file to the fonts directory + // - Add registry entry + // - Call AddFontResourceW to set the font + fontsDir := filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local", "Microsoft", "Windows", "Fonts") + + log.Debugf("installing font %s to %s", font.FileName, fontsDir) + + // check if the Fonts folder exists, if not, create it + if _, err := os.Stat(fontsDir); os.IsNotExist(err) { + if err = os.MkdirAll(fontsDir, 0755); err != nil { + return fmt.Errorf("unable to create fonts directory: %s", err.Error()) + } + } + + log.Debug("fonts directory exists, proceeding with installation") + + fullPath := filepath.Join(fontsDir, font.FileName) + // validate if the font is already installed, remove it in case it is + if _, err := os.Stat(fullPath); err == nil { + log.Debugf("font %s already exists, removing it", fullPath) + if err = os.Remove(fullPath); err != nil { + return fmt.Errorf("unable to remove existing font file: %s", err.Error()) + } + } + + log.Debugf("writing font file to %s", fullPath) + + err := os.WriteFile(fullPath, font.Data, 0644) + if err != nil { + return fmt.Errorf("unable to write font file: %s", err.Error()) + } + + log.Debug("font file written successfully, proceeding with registry entry") + + // Add registry entry + reg := registry.CURRENT_USER + regValue := fullPath + + log.Debug("opening HKEY_CURRENT_USER for writing (SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts)") + + k, _, err := registry.CreateKey(reg, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`, registry.WRITE) + if err != nil { + log.Error(err) + // If this fails, remove the font file as well. + if nexterr := os.Remove(fullPath); nexterr != nil { + log.Error(nexterr) + return errors.New("unable to delete font file after registry key open error") + } + + return errors.New("unable to open HKEY_CURRENT_USER") + } + + defer func() { + err := k.Close() + if err != nil { + log.Error(err) + } + }() + + fontName := fmt.Sprintf("%v (TrueType)", font.Name) + var alreadyInstalled, newFontType bool + + log.Debugf("validating if font %s is already installed", fontName) + + // check if we already had this key set + oldFullPath, _, err := k.GetStringValue(fontName) + if err == nil { + log.Debugf("font %s is already installed with path %s", fontName, oldFullPath) + alreadyInstalled = true + newFontType = oldFullPath != fullPath + } + + if !alreadyInstalled { + log.Debug("font is not registered, adding to registry") + if err := k.SetStringValue(fontName, fullPath); err != nil { + return err + } + + log.Debug("font registry entry added successfully") + } + + // do not call AddFontResourceW if the font was already installed + if alreadyInstalled && !newFontType { + log.Debugf("font %s is already installed, skipping AddFontResourceW", fontName) + return nil + } + + gdi32 := syscall.NewLazyDLL("gdi32.dll") + addFontResourceW := gdi32.NewProc("AddFontResourceW") + + // remove the old font resource in case we have a new font type with the same name + if newFontType { + log.Debug("removing old font resource before adding new one") + fontPtr, err := syscall.UTF16PtrFromString(oldFullPath) + if err == nil { + removeFontResourceW := gdi32.NewProc("RemoveFontResourceW") + _, _, _ = removeFontResourceW.Call(uintptr(unsafe.Pointer(fontPtr))) + } + } + + if err = k.SetStringValue(fontName, regValue); err != nil { + log.Error(err) + // If this fails, remove the font file as well. + if nexterr := os.Remove(fullPath); nexterr != nil { + return errors.New("unable to delete font file after registry key set error") + } + + return fmt.Errorf("unable to set registry value: %s", err.Error()) + } + + fontPtr, err := syscall.UTF16PtrFromString(fullPath) + if err != nil { + return err + } + + ret, _, _ := addFontResourceW.Call(uintptr(unsafe.Pointer(fontPtr))) + if ret == 0 { + return errors.New("unable to add font resource using AddFontResourceW") + } + + log.Debug("font resource added successfully") + + return nil +} diff --git a/src/cli/font/tui.go b/src/cli/font/tui.go new file mode 100644 index 000000000000..9f75310e549e --- /dev/null +++ b/src/cli/font/tui.go @@ -0,0 +1,366 @@ +package font + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/charmbracelet/bubbles/list" + progress_ "github.com/charmbracelet/bubbles/progress" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/jandedobbeleer/oh-my-posh/src/cli/progress" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + "github.com/jandedobbeleer/oh-my-posh/src/text" +) + +var ( + program *tea.Program +) + +const listHeight = 14 + +var ( + itemStyle = lipgloss.NewStyle().PaddingLeft(3) + selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) + paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(3) + helpStyle = lipgloss.NewStyle().PaddingLeft(3).PaddingBottom(1) + textStyle = lipgloss.NewStyle().Margin(1, 0, 2, 2) +) + +type loadMsg []*Asset + +type zipMsg []byte + +type successMsg []string + +type errMsg error + +type state int + +type itemDelegate struct{} + +func (d itemDelegate) Height() int { return 1 } +func (d itemDelegate) Spacing() int { return 0 } +func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } +func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { //nolint: gocritic + i, ok := listItem.(*Asset) + if !ok { + return + } + + fn := itemStyle.Render + if index == m.Index() { + fn = func(s ...string) string { + return selectedItemStyle.Render("•" + strings.Join(s, " ")) + } + } + + fmt.Fprint(w, fn(i.Name)) +} + +const ( + getFonts state = iota + selectFont + downloadFont + unzipFont + installFont + quit + done +) + +type main struct { + err error + list *list.Model + spinner *spinner.Model + progress *progress.Model + Asset + families []string + state state +} + +func (m *main) buildFontList(nerdFonts []*Asset) { + var items []list.Item + for _, font := range nerdFonts { + items = append(items, font) + } + + const defaultWidth = 20 + + l := list.New(items, itemDelegate{}, defaultWidth, listHeight) + l.Title = "Select font" + l.SetShowStatusBar(false) + l.SetFilteringEnabled(false) + l.Styles.PaginationStyle = paginationStyle + l.Styles.HelpStyle = helpStyle + + m.list = &l +} + +func getFontsList() { + fonts, err := fonts() + if err != nil { + program.Send(errMsg(err)) + return + } + + program.Send(loadMsg(fonts)) +} + +func downloadFontZip(location string) { + zipFile, err := Download(location) + if err != nil { + program.Send(errMsg(err)) + return + } + + program.Send(zipMsg(zipFile)) +} + +func installLocalFontZIP(m *main) { + data, err := os.ReadFile(m.URL) + if err != nil { + program.Send(errMsg(err)) + return + } + + installFontZIP(data, m) +} + +func installFontZIP(zipFile []byte, m *main) { + families, err := InstallZIP(zipFile, m.Folder) + if err != nil { + program.Send(errMsg(err)) + return + } + + program.Send(successMsg(families)) +} + +func (m *main) Init() tea.Cmd { + m.progress = progress.NewModel() + + s := spinner.New() + m.spinner = &s + + if len(m.URL) != 0 && !IsLocalZipFile(m.URL) { + m.state = downloadFont + + asset, err := ResolveFontAsset(m.URL) + if err != nil { + m.err = err + return tea.Quit + } + + m.Asset = *asset + + defer func() { + go downloadFontZip(asset.URL) + }() + + m.spinner.Spinner = spinner.Globe + return m.spinner.Tick + } + + defer func() { + if IsLocalZipFile(m.URL) { + go installLocalFontZIP(m) + return + } + + go getFontsList() + }() + + m.spinner.Spinner = spinner.Dot + m.spinner.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) + m.state = getFonts + + if IsLocalZipFile(m.URL) { + m.state = unzipFont + } + + return m.spinner.Tick +} + +func (m *main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case loadMsg: + m.state = selectFont + m.buildFontList(msg) + return m, nil + + case tea.WindowSizeMsg: + if m.list == nil { + return m, nil + } + m.list.SetWidth(msg.Width) + return m, nil + + case tea.KeyMsg: + switch keypress := msg.String(); keypress { + case "ctrl+c", "q", "esc": + m.state = quit + return m, tea.Quit + + case "enter": + if len(m.URL) != 0 || m.list == nil || m.list.SelectedItem() == nil { + return m, nil + } + + var font *Asset + var ok bool + + if font, ok = m.list.SelectedItem().(*Asset); !ok { + m.err = fmt.Errorf("no font selected") + return m, tea.Quit + } + + m.state = downloadFont + m.Asset = *font + + defer func() { + go downloadFontZip(font.URL) + }() + + m.spinner.Spinner = spinner.Globe + return m, m.spinner.Tick + + case "up", "k": + if m.list != nil { + if m.list.Index() == 0 { + m.list.Select(len(m.list.Items()) - 1) + } else { + m.list.Select(m.list.Index() - 1) + } + } + return m, nil + + case "down", "j": + if m.list != nil { + if m.list.Index() == len(m.list.Items())-1 { + m.list.Select(0) + } else { + m.list.Select(m.list.Index() + 1) + } + } + return m, nil + } + + case progress.Message: + return m, m.progress.SetPercent(float64(msg)) + + case progress_.FrameMsg: + return m, m.progress.Update(msg) + + case zipMsg: + m.state = installFont + defer func() { + go installFontZIP(msg, m) + }() + m.spinner.Spinner = spinner.Dot + return m, m.spinner.Tick + + case successMsg: + m.state = done + m.families = msg + return m, tea.Quit + + case errMsg: + m.err = msg + return m, tea.Quit + + default: + s, cmd := m.spinner.Update(msg) + m.spinner = &s + return m, cmd + } + + if m.list == nil { + return m, nil + } + + lst, cmd := m.list.Update(msg) + m.list = &lst + return m, cmd +} + +func (m *main) View() string { + if m.err != nil { + return textStyle.Render(m.err.Error()) + } + + switch m.state { + case getFonts: + return textStyle.Render(fmt.Sprintf("%s Downloading font list%s", m.spinner.View(), terminal.StartProgress())) + case selectFont: + return fmt.Sprintf("\n%s%s", m.list.View(), terminal.StopProgress()) + case downloadFont: + return textStyle.Render(fmt.Sprintf("Downloading %s...\n%s", m.Name, m.progress.View())) + case unzipFont: + return textStyle.Render(fmt.Sprintf("%s Extracting %s", m.spinner.View(), m.Name)) + case installFont: + return textStyle.Render(fmt.Sprintf("%s Installing %s", m.spinner.View(), m.Name)) + case quit: + return textStyle.Render(fmt.Sprintf("No need to install a new font? That's cool.%s", terminal.StopProgress())) + case done: + if len(m.families) == 0 { + return textStyle.Render(fmt.Sprintf("No matching font families were installed. Try setting --zip-folder to the correct folder when using CascadiaCode (MS) or a custom font zip file. %s", terminal.StopProgress())) //nolint: lll + } + + sb := text.NewBuilder() + + sb.WriteString(fmt.Sprintf("Successfully installed %s 🚀\n\n%s", m.Name, terminal.StopProgress())) + sb.WriteString("The following font families are now available for configuration:\n\n") + + for i, family := range m.families { + sb.WriteString(fmt.Sprintf(" • %s", family)) + + if i < len(m.families)-1 { + sb.WriteString("\n") + } + } + + return textStyle.Render(sb.String()) + } + + return "" +} + +func Run(font, zipFolder string, headless bool) (string, error) { + if headless { + return installHeadless(font, zipFolder) + } + + return tui(font, zipFolder) +} + +func tui(font, zipFolder string) (string, error) { + main := &main{ + Asset: Asset{ + Name: font, + URL: font, + Folder: zipFolder, + }, + } + + program = tea.NewProgram(main) + _, err := program.Run() + return main.Name, err +} + +func installHeadless(font, zipFolder string) (string, error) { + // Handle local zip file + if IsLocalZipFile(font) { + data, err := os.ReadFile(font) + if err != nil { + return "", err + } + + _, err = InstallZIP(data, zipFolder) + return font, err + } + + return downloadAndInstall(font, zipFolder) +} diff --git a/src/cli/get.go b/src/cli/get.go new file mode 100644 index 000000000000..ed6e0ab44df5 --- /dev/null +++ b/src/cli/get.go @@ -0,0 +1,108 @@ +package cli + +import ( + "fmt" + "os" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + + color2 "github.com/gookit/color" + "github.com/spf13/cobra" +) + +// getCmd represents the get command +var getCmd = &cobra.Command{ + Use: "get [shell|millis|accent|toggles|width]", + Short: "Get a value from oh-my-posh", + Long: `Get a value from oh-my-posh. + +This command is used to get the value of the following variables: + +- shell +- millis +- accent +- toggles +- width`, + ValidArgs: []string{ + "millis", + "shell", + "accent", + "toggles", + "width", + cache.TTL, + }, + Args: NoArgsOrOneValidArg, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + if args[0] == "millis" { + fmt.Print(time.Now().UnixNano() / 1000000) + return + } + + flags := &runtime.Flags{ + Shell: os.Getenv("POSH_SHELL"), + } + + env := &runtime.Terminal{} + env.Init(flags) + + switch args[0] { + case "shell": + fmt.Print(env.Shell()) + return + case "accent": + rgb, err := color.GetAccentColor(env) + if err != nil { + fmt.Println("error getting accent color:", err.Error()) + return + } + accent := color2.RGB(rgb.R, rgb.G, rgb.B) + fmt.Print("#" + accent.Hex()) + return + case "width": + width, err := env.TerminalWidth() + if err != nil { + fmt.Println("error getting terminal width:", err.Error()) + return + } + + fmt.Print(width) + return + } + + cache.Init(env.Shell(), cache.Persist) + + defer func() { + cache.Close() + }() + + switch args[0] { + case "toggles": + togglesMap, _ := cache.Get[map[string]bool](cache.Session, cache.TOGGLECACHE) + if len(togglesMap) == 0 { + fmt.Println("No segments are toggled off") + return + } + + fmt.Println("Toggled off segments:") + for toggle := range togglesMap { + fmt.Println("- " + toggle) + } + case cache.TTL: + fmt.Print(cache.GetTTL()) + default: + _ = cmd.Help() + } + }, +} + +func init() { + RootCmd.AddCommand(getCmd) +} diff --git a/src/cli/image/config.go b/src/cli/image/config.go new file mode 100644 index 000000000000..a81a96c77870 --- /dev/null +++ b/src/cli/image/config.go @@ -0,0 +1,91 @@ +package image + +import ( + "encoding/json" + "fmt" + "os" + "strconv" + "strings" +) + +// Settings represents the structure for base 16 color overrides and other image settings. +// Expected JSON format: +// +// { +// "colors": { +// "red": "#FF0000", +// "blue": "#0000FF", +// "green": "#00FF00" +// }, +// "author": "Your Name", +// "background_color": "#FFFFFF" +// } +type Settings struct { + Colors Colors `json:"colors"` + Author string `json:"author"` + BackgroundColor string `json:"background_color"` + Fonts *Fonts `json:"fonts"` + Cursor string `json:"cursor,omitempty"` +} + +type Colors map[string]HexColor + +func NewColors() Colors { + return map[string]HexColor{} +} + +func LoadSettings(filePath string) (*Settings, error) { + if filePath == "" { + return nil, fmt.Errorf("color settings file path is empty") + } + + data, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("failed to read color settings file: %w", err) + } + + var settings Settings + if err := json.Unmarshal(data, &settings); err != nil { + return nil, fmt.Errorf("failed to parse color settings: %w", err) + } + + return &settings, nil +} + +type HexColor string + +func (color HexColor) RGB() (*RGB, error) { + hex := string(color) + hex = strings.TrimPrefix(hex, "#") + + if len(hex) != 6 { + return nil, fmt.Errorf("invalid hex color format: %s", hex) + } + + var r, g, b int64 + var err error + + if r, err = strconv.ParseInt(hex[0:2], 16, 64); err != nil { + return nil, err + } + if g, err = strconv.ParseInt(hex[2:4], 16, 64); err != nil { + return nil, err + } + if b, err = strconv.ParseInt(hex[4:6], 16, 64); err != nil { + return nil, err + } + + return &RGB{int(r), int(g), int(b)}, nil +} + +func (colors Colors) RGBFromColorName(colorName string) (*RGB, error) { + if colors == nil || colorName == "" { + return nil, fmt.Errorf("colors map or colorName is empty") + } + + if hexColor, exists := colors[colorName]; exists { + return hexColor.RGB() + } + + return nil, fmt.Errorf("color name '%s' not found in colors map", colorName) +} diff --git a/src/cli/image/config_test.go b/src/cli/image/config_test.go new file mode 100644 index 000000000000..f7a2d0be9f88 --- /dev/null +++ b/src/cli/image/config_test.go @@ -0,0 +1,166 @@ +package image + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadSettings(t *testing.T) { + cases := []struct { + expectedResult *Settings + name string + jsonContent string + expectError bool + }{ + { + name: "Valid settings with all fields", + jsonContent: `{ + "colors": { + "red": "#FF0000", + "blue": "#0000FF", + "green": "#00FF00" + }, + "author": "John Doe", + "background_color": "#FFFFFF" + }`, + expectedResult: &Settings{ + Colors: map[string]HexColor{ + "red": "#FF0000", + "blue": "#0000FF", + "green": "#00FF00", + }, + Author: "John Doe", + BackgroundColor: "#FFFFFF", + }, + expectError: false, + }, + { + name: "Valid settings with only colors", + jsonContent: `{ + "colors": { + "red": "#FF6B6B", + "yellow": "#FFA07A" + } + }`, + expectedResult: &Settings{ + Colors: map[string]HexColor{ + "red": "#FF6B6B", + "yellow": "#FFA07A", + }, + Author: "", + BackgroundColor: "", + }, + expectError: false, + }, + { + name: "Valid settings with only author", + jsonContent: `{ + "author": "Jane Smith" + }`, + expectedResult: &Settings{ + Colors: nil, + Author: "Jane Smith", + BackgroundColor: "", + }, + expectError: false, + }, + { + name: "Empty JSON object", + jsonContent: `{}`, + expectedResult: &Settings{ + Colors: nil, + Author: "", + BackgroundColor: "", + }, + expectError: false, + }, + { + name: "Invalid JSON", + jsonContent: `{ + "colors": { + "red": "#FF0000" + "author": "John Doe" + }`, + expectedResult: nil, + expectError: true, + }, + { + name: "JSON with invalid color format", + jsonContent: `{ + "colors": { + "red": "not-a-color" + } + }`, + expectedResult: &Settings{ + Colors: map[string]HexColor{ + "red": "not-a-color", + }, + Author: "", + BackgroundColor: "", + }, + expectError: false, + }, + { + name: "JSON with extended color names", + jsonContent: `{ + "colors": { + "lightRed": "#FF9999", + "darkGray": "#333333", + "lightBlue": "#87CEEB" + } + }`, + expectedResult: &Settings{ + Colors: map[string]HexColor{ + "lightRed": "#FF9999", + "darkGray": "#333333", + "lightBlue": "#87CEEB", + }, + Author: "", + BackgroundColor: "", + }, + expectError: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Create a temporary file + tempFile := createTempFile(t, tc.jsonContent) + defer os.Remove(tempFile) + + // Test LoadSettings + result, err := LoadSettings(tempFile) + + if tc.expectError { + assert.Error(t, err) + assert.Nil(t, result) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedResult, result) + } + }) + } +} + +// Helper interface for testing types that have TempDir method +type testingInterface interface { + TempDir() string + Helper() +} + +// Helper function to create a temporary file with given content +func createTempFile(t testingInterface, content string) string { + t.Helper() + tempDir := t.TempDir() + tempFile := filepath.Join(tempDir, "test-settings.json") + + err := os.WriteFile(tempFile, []byte(content), 0644) + if err != nil { + panic(err) // Use panic since we can't return error from generic interface + } + + return tempFile +} diff --git a/src/cli/image/fonts.go b/src/cli/image/fonts.go new file mode 100644 index 000000000000..76e418e860a6 --- /dev/null +++ b/src/cli/image/fonts.go @@ -0,0 +1,95 @@ +package image + +import ( + "fmt" + stdOS "os" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" +) + +const ( + regular = "regular" +) + +type Fonts struct { + Regular string `json:"regular"` + Bold string `json:"bold"` + Italic string `json:"italic"` +} + +func (f *Fonts) IsValid() bool { + if f == nil { + return false + } + + // Check that all required font paths are non-empty + return f.Regular != "" && f.Bold != "" && f.Italic != "" +} + +func (f *Fonts) Load() (map[string]font.Face, error) { + defer log.Trace(time.Now()) + + result := make(map[string]font.Face) + + fonts := map[string]Font{ + regular: Font(f.Regular), + bold: Font(f.Bold), + italic: Font(f.Italic), + } + + for name, fontPath := range fonts { + fontFace, err := fontPath.Load() + if err != nil { + return nil, fmt.Errorf("failed to load font %s: %w", fontPath, err) + } + + result[name] = fontFace + } + + return result, nil +} + +type Font string + +func (f Font) Load() (font.Face, error) { + defer log.Trace(time.Now(), string(f)) + + data, err := stdOS.ReadFile(string(f)) + if err != nil { + return nil, fmt.Errorf("failed to read font file %s: %w", f, err) + } + + fontObject, err := opentype.Parse(data) + + // handle collections + if err != nil { + collection, err := opentype.ParseCollection(data) + if err != nil { + return nil, fmt.Errorf("failed to parse font %s as single font or collection: %w", f, err) + } + + if collection.NumFonts() == 0 { + return nil, fmt.Errorf("font collection %s is empty", f) + } + + fontObject, err = collection.Font(0) + if err != nil { + return nil, fmt.Errorf("failed to get first font from collection %s: %w", f, err) + } + } + + face, err := opentype.NewFace(fontObject, &opentype.FaceOptions{Size: 2.0 * 12, DPI: 144}) + if err != nil { + return nil, fmt.Errorf("failed to create font face for %s: %w", f, err) + } + + if face == nil { + return nil, fmt.Errorf("failed to create font face for %s: face is nil", f) + } + + return face, nil +} diff --git a/src/cli/image/image.go b/src/cli/image/image.go new file mode 100644 index 000000000000..b509c6595206 --- /dev/null +++ b/src/cli/image/image.go @@ -0,0 +1,777 @@ +// Copyright © 2020 The Homeport Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// https://github.com/homeport/termshot + +package image + +import ( + "archive/zip" + "bytes" + "fmt" + "image" + "io" + "math" + stdOS "os" + "path/filepath" + "slices" + "strconv" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + font_ "github.com/jandedobbeleer/oh-my-posh/src/cli/font" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + + "github.com/esimov/stackblur-go" + "github.com/fogleman/gg" + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" +) + +type ConnectionError struct { + reason string +} + +func (f *ConnectionError) Error() string { + return f.reason +} + +const ( + red = "#ED655A" + yellow = "#E1C04C" + green = "#71BD47" + + // known ansi sequences + + fg = "FG" + bg = "BG" + bc = "BC" // for base 16 colors + str = "STR" + text = "TEXT" + invertedColor = "inverted" + invertedColorSingle = "invertedsingle" + fullColor = "full" + foreground = "foreground" + background = "background" + reset = "reset" + bold = "bold" + boldReset = "boldr" + italic = "italic" + italicReset = "italicr" + underline = "underline" + underlineReset = "underliner" + overline = "overline" + overlineReset = "overliner" + strikethrough = "strikethrough" + strikethroughReset = "strikethroughr" + backgroundReset = "backgroundr" + color16 = "color16" + left = "left" + lineChange = "linechange" + consoleTitle = "title" + link = "link" +) + +type RGB struct { + r int + g int + b int +} + +func NewRGBColor(ansiColor string) *RGB { + colors := strings.Split(ansiColor, ";") + b, _ := strconv.Atoi(colors[2]) + g, _ := strconv.Atoi(colors[1]) + r, _ := strconv.Atoi(colors[0]) + return &RGB{ + r: r, + g: g, + b: b, + } +} + +type Renderer struct { + italic font.Face + bold font.Face + regular font.Face + backgroundColor *RGB + ansiSequenceRegexMap map[string]string + foregroundColor *RGB + defaultBackgroundColor *RGB + defaultForegroundColor *RGB + Settings + Path string + AnsiString string + shadowBaseColor string + style string + shadowOffsetX float64 + margin float64 + factor float64 + shadowOffsetY float64 + rows int + lineSpacing float64 + columns int + padding float64 + shadowRadius uint8 +} + +func (ir *Renderer) Init(env runtime.Environment) error { + ir.setOutputPath(env.Flags().ConfigPath) + + ir.cleanContent() + + if err := ir.loadFonts(); err != nil { + return err + } + + ir.initDefaults() + + return nil +} + +func (ir *Renderer) loadFonts() error { + if !ir.Fonts.IsValid() { + return ir.loadDefaultFonts() + } + + fonts, err := ir.Fonts.Load() + if err != nil { + return err + } + + ir.regular = fonts[regular] + ir.bold = fonts[bold] + ir.italic = fonts[italic] + + return nil +} + +func (ir *Renderer) initDefaults() { + ir.defaultForegroundColor = &RGB{255, 255, 255} + ir.defaultBackgroundColor = &RGB{21, 21, 21} + + ir.factor = 2.0 + ir.columns = 80 + ir.rows = 25 + + ir.margin = ir.factor * 48 + ir.padding = ir.factor * 24 + + ir.shadowBaseColor = "#10101066" + ir.shadowRadius = uint8(math.Min(ir.factor*16, 255)) + ir.shadowOffsetX = ir.factor * 16 + ir.shadowOffsetY = ir.factor * 16 + + ir.lineSpacing = 1.2 + + // Set background color from settings if provided, otherwise use default + if ir.BackgroundColor == "" { + ir.BackgroundColor = "#151515" // Default dark background + } + + ir.ansiSequenceRegexMap = map[string]string{ + invertedColor: `^(?P(\x1b\[38;2;(?P(\d+;?){3});49m){1}(\x1b\[7m))`, + invertedColorSingle: `^(?P\x1b\[(?P\d{2,3});49m\x1b\[7m)`, + fullColor: `^(?P(\x1b\[48;2;(?P(\d+;?){3})m)(\x1b\[38;2;(?P(\d+;?){3})m))`, + foreground: `^(?P(\x1b\[38;2;(?P(\d+;?){3})m))`, + background: `^(?P(\x1b\[48;2;(?P(\d+;?){3})m))`, + reset: `^(?P\x1b\[0m)`, + bold: `^(?P\x1b\[1m)`, + boldReset: `^(?P\x1b\[22m)`, + italic: `^(?P\x1b\[3m)`, + italicReset: `^(?P\x1b\[23m)`, + underline: `^(?P\x1b\[4m)`, + underlineReset: `^(?P\x1b\[24m)`, + overline: `^(?P\x1b\[53m)`, + overlineReset: `^(?P\x1b\[55m)`, + strikethrough: `^(?P\x1b\[9m)`, + strikethroughReset: `^(?P\x1b\[29m)`, + backgroundReset: `^(?P\x1b\[49m)`, + color16: `^(?P\x1b\[(?P[349][0-7]|10[0-7]|39)m)`, + left: `^(?P\x1b\[(\d{1,3})D)`, + lineChange: `^(?P\x1b\[(\d)[FB])`, + consoleTitle: `^(?P\x1b\]0;(.+)\007)`, + link: fmt.Sprintf(`^%s`, regex.LINK), + } +} + +func (ir *Renderer) setOutputPath(config string) { + if len(ir.Path) != 0 { + return + } + + if config == "" { + ir.Path = "prompt.png" + return + } + + config = filepath.Base(config) + + match := regex.FindNamedRegexMatch(`(\.?)(?P.*)\.(json|yaml|yml|toml|jsonc)`, config) + path := strings.TrimRight(match[str], ".omp") + + if path == "" { + path = "prompt" + } + + ir.Path = fmt.Sprintf("%s.png", path) +} + +func (ir *Renderer) loadDefaultFonts() error { + var data []byte + + fontCachePath := filepath.Join(cache.Path(), "Hack.zip") + if _, err := stdOS.Stat(fontCachePath); err == nil { + data, _ = stdOS.ReadFile(fontCachePath) + } + + // Download font if not cached + if data == nil { + url := "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/Hack.zip" + var err error + + data, err = font_.Download(url) + if err != nil { + return &ConnectionError{reason: err.Error()} + } + + err = stdOS.WriteFile(fontCachePath, data, 0644) + if err != nil { + return err + } + } + + bytesReader := bytes.NewReader(data) + zipReader, err := zip.NewReader(bytesReader, int64(bytesReader.Len())) + if err != nil { + return err + } + + fontFaceOptions := &opentype.FaceOptions{Size: 2.0 * 12, DPI: 144} + + parseFont := func(file *zip.File) (font.Face, error) { + rc, err := file.Open() + if err != nil { + return nil, err + } + + defer rc.Close() + + data, err := io.ReadAll(rc) + if err != nil { + return nil, err + } + + font, err := opentype.Parse(data) + if err != nil { + return nil, err + } + + fontFace, err := opentype.NewFace(font, fontFaceOptions) + if err != nil { + return nil, err + } + return fontFace, nil + } + + for _, file := range zipReader.File { + switch file.Name { + case "HackNerdFont-Regular.ttf": + if regular, err := parseFont(file); err == nil { + ir.regular = regular + } + case "HackNerdFont-Bold.ttf": + if bold, err := parseFont(file); err == nil { + ir.bold = bold + } + case "HackNerdFont-Italic.ttf": + if italic, err := parseFont(file); err == nil { + ir.italic = italic + } + } + } + + return nil +} + +func (ir *Renderer) fontHeight() float64 { + return float64(ir.regular.Metrics().Height >> 6) +} + +type RuneRange struct { + Start rune + End rune +} + +// If we're a Nerd Font code point, treat as double width +var doubleWidthRunes = []RuneRange{ + // Seti-UI + Custom range + {Start: '\ue5fa', End: '\ue6b1'}, + // Devicons + {Start: '\ue700', End: '\ue7c5'}, + // Font Awesome + {Start: '\uf000', End: '\uf2e0'}, + // Font Awesome Extension + {Start: '\ue200', End: '\ue2a9'}, + // Material Design Icons + {Start: '\U000f0001', End: '\U000f1af0'}, + // Weather + {Start: '\ue300', End: '\ue3e3'}, + // Octicons + {Start: '\uf400', End: '\uf532'}, + {Start: '\u2665', End: '\u2665'}, + {Start: '\u26A1', End: '\u26A1'}, + // Powerline Extra Symbols (intentionally excluding single width bubbles (e0b4-e0b7) and pixelated (e0c4-e0c7)) + {Start: '\ue0a3', End: '\ue0a3'}, + {Start: '\ue0b4', End: '\ue0c8'}, + {Start: '\ue0ca', End: '\ue0ca'}, + {Start: '\ue0cc', End: '\ue0d4'}, + // IEC Power Symbols + {Start: '\u23fb', End: '\u23fe'}, + {Start: '\u2b58', End: '\u2b58'}, + // Font Logos + {Start: '\uf300', End: '\uf372'}, + // Pomicons + {Start: '\ue000', End: '\ue00a'}, + // Codicons + {Start: '\uea60', End: '\uebeb'}, +} + +// This is getting how many additional characters of width to allocate when drawing +// e.g. for characters that are 2 or more wide. A standard character will return 0 +// Nerd Font glyphs will return 1, since most are double width +func (ir *Renderer) runeAdditionalWidth(r rune) int { + // exclude the round leading diamond + singles := []rune{'\ue0b6', '\ue0ba', '\ue0bc'} + if slices.Contains(singles, r) { + return 0 + } + + for _, runeRange := range doubleWidthRunes { + if runeRange.Start <= r && r <= runeRange.End { + return 1 + } + } + return 0 +} + +func (ir *Renderer) cleanContent() { + // clean abundance of empty lines + ir.AnsiString = strings.Trim(ir.AnsiString, "\n") + ir.AnsiString = "\n" + ir.AnsiString + + // clean string before render + ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b[m", "\x1b[0m") + ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b[K", "") + ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b[0J", "") + ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b[27m", "") + ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b8", "") + ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\u2800", " ") + + // cursor indication + saveCursorAnsi := "\x1b7" + if !strings.Contains(ir.AnsiString, saveCursorAnsi) { + ir.AnsiString += ir.Cursor + } + + ir.AnsiString = strings.ReplaceAll(ir.AnsiString, saveCursorAnsi, ir.Cursor) + + // add watermarks + ir.AnsiString += "\n\n\x1b[1mohmyposh.dev\x1b[22m" + if len(ir.Author) > 0 { + createdBy := fmt.Sprintf(" by \x1b[1m%s\x1b[22m", ir.Author) + ir.AnsiString += createdBy + } +} + +func (ir *Renderer) measureContent() (width, height float64) { + // Use actual rendering logic for accurate width measurement + // This simulates the exact same process as the actual drawing to ensure + // the canvas width perfectly matches the rendered content width + var maxX float64 + var x float64 + + // Save original ansi string and style state + originalAnsi := ir.AnsiString + originalStyle := ir.style + ir.style = "" + + tmpDrawer := &font.Drawer{Face: ir.regular} + + for ir.AnsiString != "" { + if !ir.processAnsiSequence() { + continue + } + + runes := []rune(ir.AnsiString) + if len(runes) == 0 { + continue + } + + str := string(runes[0:1]) + ir.AnsiString = string(runes[1:]) + + // Use appropriate font face for measurement + var face font.Face + switch ir.style { + case bold: + face = ir.bold + case italic: + face = ir.italic + default: + face = ir.regular + } + + tmpDrawer.Face = face + advance := tmpDrawer.MeasureString(str) + w := float64(advance >> 6) + + // Add additional width for Nerd Font glyphs + w += (w * float64(ir.runeAdditionalWidth(runes[0]))) + + if str == "\n" { + x = 0 + continue + } + + x += w + if x > maxX { + maxX = x + } + } + + // Restore original state + ir.AnsiString = originalAnsi + ir.style = originalStyle + + // Ensure we have a minimum width for very short content + minWidth := tmpDrawer.MeasureString(strings.Repeat(" ", 80)) + width = math.Max(maxX, float64(minWidth>>6)) + + // height, lines times font height and line spacing + lines := strings.Split(originalAnsi, "\n") + height = float64(len(lines)) * ir.fontHeight() * ir.lineSpacing + return width, height +} + +func (ir *Renderer) SavePNG() error { + var scale = func(value float64) float64 { return ir.factor * value } + + var ( + corner = scale(6) + radius = scale(9) + distance = scale(25) + ) + + contentWidth, contentHeight := ir.measureContent() + + // Make sure the output window is big enough in case no content or very few + // content will be rendered. Also account for potential font variations. + minRequiredWidth := 3*distance + 3*radius + // Add extra buffer for wider fonts (20% more than minimum) + minRequiredWidth *= 1.2 + contentWidth = math.Max(contentWidth, minRequiredWidth) + + marginX, marginY := ir.margin, ir.margin + paddingX, paddingY := ir.padding, ir.padding + + xOffset := marginX + yOffset := marginY + titleOffset := scale(40) + + width := contentWidth + 2*marginX + 2*paddingX + height := contentHeight + 2*marginY + 2*paddingY + titleOffset + + dc := gg.NewContext(int(width), int(height)) + + xOffset -= ir.shadowOffsetX / 2 + yOffset -= ir.shadowOffsetY / 2 + + bc := gg.NewContext(int(width), int(height)) + bc.DrawRoundedRectangle(xOffset+ir.shadowOffsetX, yOffset+ir.shadowOffsetY, width-2*marginX, height-2*marginY, corner) + bc.SetHexColor(ir.shadowBaseColor) + bc.Fill() + + dst := image.NewNRGBA(bc.Image().Bounds()) + + // var done = make(chan struct{}, ir.shadowRadius) + err := stackblur.Process( + dst, + bc.Image(), + uint32(ir.shadowRadius), + ) + + if err != nil { + return err + } + + // <-done + dc.DrawImage(dst, 0, 0) + + // Draw rounded rectangle with outline and three button to produce the + // impression of a window with controls and a content area + dc.DrawRoundedRectangle(xOffset, yOffset, width-2*marginX, height-2*marginY, corner) + dc.SetHexColor(ir.BackgroundColor) + dc.Fill() + + dc.DrawRoundedRectangle(xOffset, yOffset, width-2*marginX, height-2*marginY, corner) + dc.SetHexColor("#404040") + dc.SetLineWidth(scale(1)) + dc.Stroke() + + for i, color := range []string{red, yellow, green} { + dc.DrawCircle(xOffset+paddingX+float64(i)*distance+scale(4), yOffset+paddingY+scale(4), radius) + dc.SetHexColor(color) + dc.Fill() + } + + // Apply the actual text into the prepared content area of the window + var x, y = xOffset + paddingX, yOffset + paddingY + titleOffset + ir.fontHeight() + + for ir.AnsiString != "" { + if !ir.processAnsiSequence() { + continue + } + + runes := []rune(ir.AnsiString) + if len(runes) == 0 { + continue + } + + str := string(runes[0:1]) + ir.AnsiString = string(runes[1:]) + switch ir.style { + case bold: + dc.SetFontFace(ir.bold) + case italic: + dc.SetFontFace(ir.italic) + default: + dc.SetFontFace(ir.regular) + } + + w, _ := dc.MeasureString(str) + // The gg library unfortunately returns a single character width for *all* glyphs in a font. + // So if we know the glyph to occupy n additional characters in width, allocate that area + // e.g. this will double the space for Nerd Fonts, but some could even be 3 or 4 wide + // If there's 0 additional characters of width (the common case), this won't add anything + w += (w * float64(ir.runeAdditionalWidth(runes[0]))) + + if ir.backgroundColor != nil { + dc.SetRGB255(ir.backgroundColor.r, ir.backgroundColor.g, ir.backgroundColor.b) + // Use consistent line height for all background rectangles + fontLineHeight := ir.fontHeight() * ir.lineSpacing + + // Center all characters (including powerline glyphs) within the line height + // Position background to align properly with text baseline and ensure consistent height + bgY := y - fontLineHeight*0.75 // Adjusted for better centering with text + bgHeight := fontLineHeight + + dc.DrawRectangle(x, bgY, w, bgHeight) + dc.Fill() + } + + if ir.foregroundColor != nil { + dc.SetRGB255(ir.foregroundColor.r, ir.foregroundColor.g, ir.foregroundColor.b) + } else { + dc.SetRGB255(ir.defaultForegroundColor.r, ir.defaultForegroundColor.g, ir.defaultForegroundColor.b) + } + + if str == "\n" { + x = xOffset + paddingX + y += ir.fontHeight() * ir.lineSpacing // Use consistent line height instead of character height + continue + } + + dc.DrawString(str, x, y) + + if ir.style == underline { + dc.DrawLine(x, y+scale(4), x+w, y+scale(4)) + dc.SetLineWidth(scale(1)) + dc.Stroke() + } + + if ir.style == overline { + dc.DrawLine(x, y-scale(22), x+w, y-scale(22)) + dc.SetLineWidth(scale(1)) + dc.Stroke() + } + + x += w + } + + return dc.SavePNG(ir.Path) +} + +func (ir *Renderer) processAnsiSequence() bool { + for sequence, re := range ir.ansiSequenceRegexMap { + match := regex.FindNamedRegexMatch(re, ir.AnsiString) + if len(match) == 0 { + continue + } + + ir.AnsiString = strings.TrimPrefix(ir.AnsiString, match[str]) + switch sequence { + case invertedColor: + ir.foregroundColor = ir.defaultBackgroundColor + ir.backgroundColor = NewRGBColor(match[bg]) + return false + case invertedColorSingle: + ir.foregroundColor = ir.defaultBackgroundColor + bgColor, _ := strconv.Atoi(match[bg]) + bgColor += 10 + ir.setBase16Color(fmt.Sprint(bgColor)) + return false + case fullColor: + ir.foregroundColor = NewRGBColor(match[fg]) + ir.backgroundColor = NewRGBColor(match[bg]) + return false + case foreground: + ir.foregroundColor = NewRGBColor(match[fg]) + return false + case background: + ir.backgroundColor = NewRGBColor(match[bg]) + return false + case reset: + ir.foregroundColor = ir.defaultForegroundColor + ir.backgroundColor = nil + return false + case backgroundReset: + ir.backgroundColor = nil + return false + case bold, italic, underline, overline: + ir.style = sequence + return false + case boldReset, italicReset, underlineReset, overlineReset: + ir.style = "" + return false + case strikethrough, strikethroughReset, left, lineChange, consoleTitle: + return false + case color16: + ir.setBase16Color(match[bc]) + return false + case link: + ir.AnsiString = match[text] + ir.AnsiString + } + } + + return true +} + +func (ir *Renderer) setBase16Color(colorStr string) { + tempColor := ir.defaultForegroundColor + + colorInt, err := strconv.Atoi(colorStr) + if err != nil { + ir.foregroundColor = tempColor + return + } + + // Check for color override first + colorName := colorNameFromCode(colorInt) + if rgb, err := ir.Colors.RGBFromColorName(colorName); err == nil { + tempColor = rgb + } + + // If no override found, use default colors + if tempColor == ir.defaultForegroundColor { + switch colorInt { + case 30, 40: // Black + tempColor = &RGB{1, 1, 1} + case 31, 41: // Red + tempColor = &RGB{222, 56, 43} + case 32, 42: // Green + tempColor = &RGB{57, 181, 74} + case 33, 43: // Yellow + tempColor = &RGB{255, 199, 6} + case 34, 44: // Blue + tempColor = &RGB{0, 111, 184} + case 35, 45: // Magenta + tempColor = &RGB{118, 38, 113} + case 36, 46: // Cyan + tempColor = &RGB{44, 181, 233} + case 37, 47: // White + tempColor = &RGB{204, 204, 204} + case 90, 100: // Bright Black (Gray) + tempColor = &RGB{128, 128, 128} + case 91, 101: // Bright Red + tempColor = &RGB{255, 0, 0} + case 92, 102: // Bright Green + tempColor = &RGB{0, 255, 0} + case 93, 103: // Bright Yellow + tempColor = &RGB{255, 255, 0} + case 94, 104: // Bright Blue + tempColor = &RGB{0, 0, 255} + case 95, 105: // Bright Magenta + tempColor = &RGB{255, 0, 255} + case 96, 106: // Bright Cyan + tempColor = &RGB{101, 194, 205} + case 97, 107: // Bright White + tempColor = &RGB{255, 255, 255} + } + } + + if colorInt < 40 || (colorInt >= 90 && colorInt < 100) { + ir.foregroundColor = tempColor + return + } + + ir.backgroundColor = tempColor +} + +// colorNameFromCode maps ANSI color codes to color names +func colorNameFromCode(colorInt int) string { + switch colorInt { + case 30, 40: + return "black" + case 31, 41: + return "red" + case 32, 42: + return "green" + case 33, 43: + return "yellow" + case 34, 44: + return "blue" + case 35, 45: + return "magenta" + case 36, 46: + return "cyan" + case 37, 47: + return "white" + case 90, 100: + return "darkGray" + case 91, 101: + return "lightRed" + case 92, 102: + return "lightGreen" + case 93, 103: + return "lightYellow" + case 94, 104: + return "lightBlue" + case 95, 105: + return "lightMagenta" + case 96, 106: + return "lightCyan" + case 97, 107: + return "lightWhite" + default: + return "" + } +} diff --git a/src/cli/image/image_test.go b/src/cli/image/image_test.go new file mode 100644 index 000000000000..5a92b1a3ed61 --- /dev/null +++ b/src/cli/image/image_test.go @@ -0,0 +1,402 @@ +package image + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSetOutputPath(t *testing.T) { + cases := []struct { + Case string + Config string + Path string + Expected string + }{ + {Case: "default config", Expected: "prompt.png"}, + {Case: "hidden file", Config: ".posh.omp.json", Expected: "posh.png"}, + {Case: "hidden file toml", Config: ".posh.omp.toml", Expected: "posh.png"}, + {Case: "hidden file yaml", Config: ".posh.omp.yaml", Expected: "posh.png"}, + {Case: "hidden file yml", Config: ".posh.omp.yml", Expected: "posh.png"}, + {Case: "path provided", Path: "mytheme.png", Expected: "mytheme.png"}, + {Case: "relative, no omp", Config: "~/jandedobbeleer.json", Expected: "jandedobbeleer.png"}, + {Case: "relative path", Config: "~/jandedobbeleer.omp.json", Expected: "jandedobbeleer.png"}, + {Case: "invalid config name", Config: "~/jandedobbeleer.omp.foo", Expected: "prompt.png"}, + } + + for _, tc := range cases { + image := &Renderer{ + Path: tc.Path, + } + + image.setOutputPath(tc.Config) + + assert.Equal(t, tc.Expected, image.Path, tc.Case) + } +} + +func TestHexToRGB(t *testing.T) { + cases := []struct { + expected *RGB + name string + hex HexColor + hasError bool + }{ + { + name: "Valid hex with hash", + hex: "#FF0000", + expected: &RGB{255, 0, 0}, + hasError: false, + }, + { + name: "Valid hex without hash", + hex: "00FF00", + expected: &RGB{0, 255, 0}, + hasError: false, + }, + { + name: "Valid hex blue", + hex: "#0000FF", + expected: &RGB{0, 0, 255}, + hasError: false, + }, + { + name: "Invalid hex too short", + hex: "#FFF", + expected: nil, + hasError: true, + }, + { + name: "Invalid hex too long", + hex: "#FFFFFFF", + expected: nil, + hasError: true, + }, + { + name: "Invalid hex characters", + hex: "#GGGGGG", + expected: nil, + hasError: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + result, err := tc.hex.RGB() + + if tc.hasError { + assert.Error(t, err) + assert.Nil(t, result) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expected, result) + } + }) + } +} + +func TestGetColorNameFromCode(t *testing.T) { + cases := []struct { + expected string + colorCode int + }{ + {"black", 30}, + {"red", 31}, + {"green", 32}, + {"yellow", 33}, + {"blue", 34}, + {"magenta", 35}, + {"cyan", 36}, + {"white", 37}, + {"black", 40}, // background + {"red", 41}, // background + {"darkGray", 90}, + {"lightRed", 91}, + {"lightGreen", 92}, + {"lightYellow", 93}, + {"lightBlue", 94}, + {"lightMagenta", 95}, + {"lightCyan", 96}, + {"lightWhite", 97}, + {"", 999}, // invalid code + } + + for _, tc := range cases { + t.Run(tc.expected, func(t *testing.T) { + result := colorNameFromCode(tc.colorCode) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestSetBase16Color(t *testing.T) { + cases := []struct { + colorOverrides map[string]HexColor + expectedForeground *RGB + expectedBackground *RGB + name string + colorCode string + }{ + { + name: "Red foreground with override", + colorCode: "31", + colorOverrides: map[string]HexColor{"red": "#FF6B6B", "blue": "#4ECDC4"}, + expectedForeground: &RGB{255, 107, 107}, + expectedBackground: nil, + }, + { + name: "Blue background with override", + colorCode: "44", + colorOverrides: map[string]HexColor{"red": "#FF6B6B", "blue": "#4ECDC4"}, + expectedForeground: nil, + expectedBackground: &RGB{78, 205, 196}, + }, + { + name: "Green foreground without override", + colorCode: "32", + colorOverrides: map[string]HexColor{"red": "#FF6B6B", "blue": "#4ECDC4"}, + expectedForeground: &RGB{57, 181, 74}, + expectedBackground: nil, + }, + { + name: "Red foreground without any overrides", + colorCode: "31", + colorOverrides: nil, + expectedForeground: &RGB{222, 56, 43}, + expectedBackground: nil, + }, + { + name: "Blue background without any overrides", + colorCode: "44", + colorOverrides: nil, + expectedForeground: nil, + expectedBackground: &RGB{0, 111, 184}, + }, + { + name: "Invalid color code", + colorCode: "invalid", + colorOverrides: nil, + expectedForeground: &RGB{255, 255, 255}, + expectedBackground: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + renderer := &Renderer{ + defaultForegroundColor: &RGB{255, 255, 255}, + Settings: Settings{ + Colors: tc.colorOverrides, + }, + } + + renderer.setBase16Color(tc.colorCode) + + if tc.expectedForeground != nil { + assert.Equal(t, tc.expectedForeground, renderer.foregroundColor) + } + + if tc.expectedBackground != nil { + assert.Equal(t, tc.expectedBackground, renderer.backgroundColor) + } + }) + } +} + +func TestProcessAnsiSequence(t *testing.T) { + cases := []struct { + expectedForegroundColor *RGB + expectedBackgroundColor *RGB + colorOverrides map[string]HexColor + name string + ansiString string + expectedAnsiString string + expectedStyle string + }{ + { + name: "Regular character", + ansiString: "hello", + expectedAnsiString: "hello", + }, + { + name: "Inverted color", + ansiString: "\x1b[38;2;255;0;0;49m\x1b[7mtest", + expectedAnsiString: "test", + expectedForegroundColor: &RGB{21, 21, 21}, // defaultBackgroundColor + expectedBackgroundColor: &RGB{255, 0, 0}, + }, + { + name: "Inverted color single", + ansiString: "\x1b[31;49m\x1b[7mtest", + expectedAnsiString: "test", + expectedForegroundColor: &RGB{21, 21, 21}, // defaultBackgroundColor + expectedBackgroundColor: &RGB{222, 56, 43}, // red background (31 + 10 = 41) + }, + { + name: "Full color", + ansiString: "\x1b[48;2;100;200;50m\x1b[38;2;255;0;0mtest", + expectedAnsiString: "test", + expectedBackgroundColor: &RGB{100, 200, 50}, + expectedForegroundColor: &RGB{255, 0, 0}, + }, + { + name: "Foreground color", + ansiString: "\x1b[38;2;255;128;0mtest", + expectedAnsiString: "test", + expectedForegroundColor: &RGB{255, 128, 0}, + }, + { + name: "Background color", + ansiString: "\x1b[48;2;0;255;128mtest", + expectedAnsiString: "test", + expectedBackgroundColor: &RGB{0, 255, 128}, + }, + { + name: "Reset sequence", + ansiString: "\x1b[0mtest", + expectedAnsiString: "test", + expectedForegroundColor: &RGB{255, 255, 255}, // defaultForegroundColor + expectedBackgroundColor: nil, + }, + { + name: "Background reset", + ansiString: "\x1b[49mtest", + expectedAnsiString: "test", + expectedBackgroundColor: nil, + }, + { + name: "Bold style", + ansiString: "\x1b[1mtest", + expectedAnsiString: "test", + expectedStyle: "bold", + }, + { + name: "Italic style", + ansiString: "\x1b[3mtest", + expectedAnsiString: "test", + expectedStyle: "italic", + }, + { + name: "Underline style", + ansiString: "\x1b[4mtest", + expectedAnsiString: "test", + expectedStyle: "underline", + }, + { + name: "Overline style", + ansiString: "\x1b[53mtest", + expectedAnsiString: "test", + expectedStyle: "overline", + }, + { + name: "Bold reset", + ansiString: "\x1b[22mtest", + expectedAnsiString: "test", + expectedStyle: "", + }, + { + name: "Italic reset", + ansiString: "\x1b[23mtest", + expectedAnsiString: "test", + expectedStyle: "", + }, + { + name: "Underline reset", + ansiString: "\x1b[24mtest", + expectedAnsiString: "test", + expectedStyle: "", + }, + { + name: "Overline reset", + ansiString: "\x1b[55mtest", + expectedAnsiString: "test", + expectedStyle: "", + }, + { + name: "Strikethrough", + ansiString: "\x1b[9mtest", + expectedAnsiString: "test", + }, + { + name: "Strikethrough reset", + ansiString: "\x1b[29mtest", + expectedAnsiString: "test", + }, + { + name: "Left cursor movement", + ansiString: "\x1b[5Dtest", + expectedAnsiString: "test", + }, + { + name: "Line change", + ansiString: "\x1b[2Ftest", + expectedAnsiString: "test", + }, + { + name: "Console title", + ansiString: "\x1b]0;My Title\007test", + expectedAnsiString: "test", + }, + { + name: "Base16 red color", + ansiString: "\x1b[31mtest", + expectedAnsiString: "test", + expectedForegroundColor: &RGB{222, 56, 43}, + }, + { + name: "Base16 blue background", + ansiString: "\x1b[44mtest", + expectedAnsiString: "test", + expectedBackgroundColor: &RGB{0, 111, 184}, + }, + { + name: "Base16 red with override", + ansiString: "\x1b[31mtest", + expectedAnsiString: "test", + expectedForegroundColor: &RGB{255, 107, 107}, + colorOverrides: map[string]HexColor{"red": "#FF6B6B"}, + }, + { + name: "Link sequence", + ansiString: "\x1b]8;;https://example.com\x1b\\Click here\x1b]8;;\x1b\\test", + expectedAnsiString: "Click heretest", + }, + { + name: "No matching sequence", + ansiString: "plain text", + expectedAnsiString: "plain text", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + renderer := &Renderer{ + AnsiString: tc.ansiString, + Settings: Settings{ + Colors: tc.colorOverrides, + }, + } + + renderer.initDefaults() + + var result bool + for !result { + result = renderer.processAnsiSequence() + } + + assert.Equal(t, tc.expectedAnsiString, renderer.AnsiString) + + if tc.expectedForegroundColor != nil { + assert.Equal(t, tc.expectedForegroundColor, renderer.foregroundColor) + } + + if tc.expectedBackgroundColor != nil { + assert.Equal(t, tc.expectedBackgroundColor, renderer.backgroundColor) + } + + if tc.expectedStyle != "" { + assert.Equal(t, tc.expectedStyle, renderer.style) + } + }) + } +} diff --git a/src/cli/init.go b/src/cli/init.go new file mode 100644 index 000000000000..d52d3ab8f595 --- /dev/null +++ b/src/cli/init.go @@ -0,0 +1,189 @@ +package cli + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + printOutput bool + strict bool + debug bool + + supportedShells = []string{ + "bash", + "zsh", + "fish", + "powershell", + "pwsh", + "cmd", + "nu", + "elvish", + "xonsh", + } + + initCmd = createInitCmd() +) + +func init() { + RootCmd.AddCommand(initCmd) +} + +func createInitCmd() *cobra.Command { + initCmd := &cobra.Command{ + Use: "init [bash|zsh|fish|powershell|pwsh|cmd|nu|elvish|xonsh]", + Short: "Initialize your shell and config", + Long: `Initialize your shell and config. + +See the documentation to initialize your shell: https://ohmyposh.dev/docs/installation/prompt.`, + ValidArgs: supportedShells, + Args: NoArgsOrOneValidArg, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + runInit(args[0], getFullCommand(cmd, args)) + }, + } + + initCmd.Flags().BoolVarP(&printOutput, "print", "p", false, "print the init script") + initCmd.Flags().BoolVarP(&strict, "strict", "s", false, "run in strict mode") + initCmd.Flags().BoolVar(&debug, "debug", false, "enable/disable debug mode") + initCmd.Flags().BoolVar(&eval, "eval", false, "output the full init script for eval") + + _ = initCmd.MarkPersistentFlagRequired("config") + + return initCmd +} + +func runInit(sh, command string) { + if os.Getenv("CURSOR_AGENT") == "1" { + log.Errorf("oh-my-posh init is disabled when running inside Cursor agent mode") + return + } + + if debug { + log.Enable(plain) + } + + if sh == "powershell" { + sh = shell.PWSH + } + + initCache(sh) + + cfg := config.Load(configFlag) + + flags := &runtime.Flags{ + Shell: sh, + ConfigPath: cfg.Source, + ConfigHash: cfg.Hash(), + Strict: strict, + Debug: debug, + Init: true, + Eval: eval, + Plain: plain, + } + + env := &runtime.Terminal{} + env.Init(flags) + + template.Init(env, cfg.Var, cfg.Maps) + + defer func() { + cfg.Store() + template.SaveCache() + if err := cache.Clear(false, shell.InitScriptName(env.Flags())); err != nil { + log.Error(err) + } + cache.Close() + }() + + feats := cfg.Features(env) + + var output string + + switch { + case debug: + output = shell.Debug(env, feats, &startTime) + case printOutput: + output = shell.Script(env, feats) + default: + output = shell.Init(env, feats) + } + + shellDSC := shell.DSC() + shellDSC.Load() + shellDSC.Add(&shell.Shell{ + Command: command, + Name: sh, + }) + shellDSC.Save() + + if silent { + return + } + + fmt.Print(output) +} + +func getFullCommand(cmd *cobra.Command, args []string) string { + // Start with the command path + cmdPath := cmd.CommandPath() + + // Add arguments + if len(args) > 0 { + cmdPath += " " + strings.Join(args, " ") + } + + // Add flags that were actually set + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + if !flag.Changed { + return + } + + if flag.Value.Type() == "bool" && flag.Value.String() == "true" { + cmdPath += fmt.Sprintf(" --%s", flag.Name) + return + } + + if flag.Name == "config" { + configPath := filepath.Clean(flag.Value.String()) + configPath = strings.ReplaceAll(configPath, path.Home(), "~") + cmdPath += fmt.Sprintf(" --%s=%s", flag.Name, configPath) + return + } + + cmdPath += fmt.Sprintf(" --%s=%s", flag.Name, flag.Value.String()) + }) + + return cmdPath +} + +func initCache(sh string) { + switch { + case !printOutput: + if (eval && sh == shell.PWSH) || sh == shell.ELVISH { + cache.Init(sh) + return + } + + fallthrough + default: + cache.Init(sh, cache.NewSession, cache.Persist) + } +} diff --git a/src/cli/notice.go b/src/cli/notice.go new file mode 100644 index 000000000000..605b561b4bb2 --- /dev/null +++ b/src/cli/notice.go @@ -0,0 +1,39 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/spf13/cobra" +) + +// noticeCmd represents the notice command +var noticeCmd = &cobra.Command{ + Use: "notice", + Short: "Print the upgrade notice when a new version is available.", + Long: "Print the upgrade notice when a new version is available.", + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { + env := &runtime.Terminal{} + env.Init(&runtime.Flags{}) + + cache.Init(os.Getenv("POSH_SHELL"), cache.Persist) + + defer func() { + cache.Close() + }() + + cfg := config.Get(configFlag, false) + + if notice, hasNotice := cfg.Upgrade.Notice(); hasNotice { + fmt.Println(notice) + } + }, +} + +func init() { + RootCmd.AddCommand(noticeCmd) +} diff --git a/src/cli/print.go b/src/cli/print.go new file mode 100644 index 000000000000..cbcc692ddac7 --- /dev/null +++ b/src/cli/print.go @@ -0,0 +1,155 @@ +package cli + +import ( + "fmt" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/prompt" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + + "github.com/spf13/cobra" +) + +var ( + pwd string + pswd string + status int + pipestatus string + timing float64 + stackCount int + terminalWidth int + eval bool + cleared bool + jobCount int + saveCache bool + + command string + shellVersion string + plain bool + noStatus bool + column int + escape bool +) + +// printCmd represents the print command +var printCmd = createPrintCmd() + +func init() { + RootCmd.AddCommand(printCmd) +} + +func createPrintCmd() *cobra.Command { + printCmd := &cobra.Command{ + Use: "print [debug|primary|secondary|transient|right|tooltip|valid|error|preview]", + Short: "Print the prompt/context", + Long: "Print one of the prompts based on the location/use-case.", + ValidArgs: []string{ + prompt.DEBUG, + prompt.PRIMARY, + prompt.SECONDARY, + prompt.TRANSIENT, + prompt.RIGHT, + prompt.TOOLTIP, + prompt.VALID, + prompt.ERROR, + prompt.PREVIEW, + }, + Args: NoArgsOrOneValidArg, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + if shellName == "" { + shellName = shell.GENERIC + } + + flags := &runtime.Flags{ + ConfigPath: configFlag, + PWD: pwd, + PSWD: pswd, + ErrorCode: status, + PipeStatus: pipestatus, + ExecutionTime: timing, + StackCount: stackCount, + TerminalWidth: terminalWidth, + Eval: eval, + Shell: shellName, + ShellVersion: shellVersion, + Plain: plain, + Type: args[0], + Cleared: cleared, + NoExitCode: noStatus, + Column: column, + JobCount: jobCount, + IsPrimary: args[0] == prompt.PRIMARY, + Escape: escape, + Force: force, + } + + options := []cache.Option{} + if saveCache { + options = append(options, cache.Persist) + } + + cache.Init(shellName, options...) + + eng := prompt.New(flags) + + defer func() { + template.SaveCache() + cache.Close() + }() + + switch args[0] { + case prompt.DEBUG: + fmt.Print(eng.ExtraPrompt(prompt.Debug)) + case prompt.PRIMARY: + fmt.Print(eng.Primary()) + case prompt.SECONDARY: + fmt.Print(eng.ExtraPrompt(prompt.Secondary)) + case prompt.TRANSIENT: + fmt.Print(eng.ExtraPrompt(prompt.Transient)) + case prompt.RIGHT: + fmt.Print(eng.RPrompt()) + case prompt.TOOLTIP: + fmt.Print(eng.Tooltip(command)) + case prompt.VALID: + fmt.Print(eng.ExtraPrompt(prompt.Valid)) + case prompt.ERROR: + fmt.Print(eng.ExtraPrompt(prompt.Error)) + case prompt.PREVIEW: + fmt.Print(eng.Preview()) + default: + _ = cmd.Help() + } + }, + } + + printCmd.Flags().StringVar(&pwd, "pwd", "", "current working directory") + printCmd.Flags().StringVar(&pswd, "pswd", "", "current working directory (according to pwsh)") + printCmd.Flags().StringVar(&shellName, "shell", "", "the shell to print for") + printCmd.Flags().StringVar(&shellVersion, "shell-version", "", "the shell version") + printCmd.Flags().IntVar(&status, "status", 0, "last known status code") + printCmd.Flags().BoolVar(&noStatus, "no-status", false, "no valid status code (cancelled or no command yet)") + printCmd.Flags().StringVar(&pipestatus, "pipestatus", "", "the PIPESTATUS array") + printCmd.Flags().Float64Var(&timing, "execution-time", 0, "timing of the last command") + printCmd.Flags().IntVarP(&stackCount, "stack-count", "s", 0, "number of locations on the stack") + printCmd.Flags().IntVarP(&terminalWidth, "terminal-width", "w", 0, "width of the terminal") + printCmd.Flags().StringVar(&command, "command", "", "tooltip command") + printCmd.Flags().BoolVar(&cleared, "cleared", false, "do we have a clear terminal or not") + printCmd.Flags().BoolVar(&eval, "eval", false, "output the prompt for eval") + printCmd.Flags().IntVar(&column, "column", 0, "the column position of the cursor") + printCmd.Flags().IntVar(&jobCount, "job-count", 0, "number of background jobs") + printCmd.Flags().BoolVar(&saveCache, "save-cache", false, "save updated cache to file") + printCmd.Flags().BoolVar(&escape, "escape", true, "escape the ANSI sequences for the shell") + printCmd.Flags().BoolVarP(&force, "force", "f", false, "force rendering the segments") + + // Hide flags that are for internal use only. + _ = printCmd.Flags().MarkHidden("save-cache") + + return printCmd +} diff --git a/src/cli/progress/model.go b/src/cli/progress/model.go new file mode 100644 index 000000000000..e3214ab655b5 --- /dev/null +++ b/src/cli/progress/model.go @@ -0,0 +1,29 @@ +package progress + +import ( + "github.com/charmbracelet/bubbles/progress" + tea "github.com/charmbracelet/bubbletea" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +type Message float64 + +func NewModel() *Model { + p := progress.New(progress.WithScaledGradient("#800080", "#ffc0cb")) + return &Model{Model: p} +} + +type Model struct { + progress.Model +} + +func (m *Model) Update(msg tea.Msg) tea.Cmd { + model, cmd := m.Model.Update(msg) + m.Model = model.(progress.Model) + + return cmd +} + +func (m *Model) View() string { + return m.Model.View() + terminal.SetProgress(int(m.Percent()*100)) +} diff --git a/src/cli/progress/reader.go b/src/cli/progress/reader.go new file mode 100644 index 000000000000..3d4bc5d23216 --- /dev/null +++ b/src/cli/progress/reader.go @@ -0,0 +1,35 @@ +package progress + +import ( + "io" + + tea "github.com/charmbracelet/bubbletea" +) + +func NewReader(reader io.Reader, total int64, program *tea.Program) *Reader { + return &Reader{ + Reader: reader, + program: program, + total: total, + } +} + +type Reader struct { + io.Reader + + program *tea.Program + total int64 + current int64 +} + +func (r *Reader) Read(p []byte) (int, error) { + n, err := r.Reader.Read(p) + r.current += int64(n) + percent := float64(r.current) / float64(r.total) + + if r.program != nil { + r.program.Send(Message(percent)) + } + + return n, err +} diff --git a/src/cli/root.go b/src/cli/root.go new file mode 100644 index 000000000000..97f20f7fe401 --- /dev/null +++ b/src/cli/root.go @@ -0,0 +1,124 @@ +package cli + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/spf13/cobra" +) + +var ( + configFlag string + shellName string + printVersion bool + trace bool + exitcode int + + // for internal use only + silent bool + + // deprecated + initialize bool +) + +var RootCmd = &cobra.Command{ + Use: "oh-my-posh", + Short: "oh-my-posh is a tool to render your prompt", + Long: `oh-my-posh is a cross platform tool to render your prompt. +It can use the same configuration everywhere to offer a consistent +experience, regardless of where you are. For a detailed guide +on getting started, have a look at the docs at https://ohmyposh.dev`, + Run: func(cmd *cobra.Command, args []string) { + if initialize { + runInit(strings.ToLower(shellName), getFullCommand(cmd, args)) + return + } + + if printVersion { + fmt.Println(build.Version) + return + } + + _ = cmd.Help() + }, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + configEnv := os.Getenv("POSH_CONFIG") + if configEnv != "" && configFlag == "" { + configFlag = configEnv + } + + traceEnv := os.Getenv("POSH_TRACE") + if traceEnv == "" && !trace { + return + } + + trace = true + + log.Enable(true) + + log.Debug("version:", build.Version) + log.Debug("command:", getFullCommand(cmd, args)) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + defer func() { + if exitcode != 0 { + os.Exit(exitcode) + } + }() + + if !trace { + return + } + + var prefix string + if shellName != "" { + prefix = fmt.Sprintf("%s-", shellName) + } + + cli := append([]string{cmd.Name()}, args...) + + filename := fmt.Sprintf("%s-%s%s.log", time.Now().Format("02012006T150405.000"), prefix, strings.Join(cli, "-")) + + logPath := filepath.Join(cache.Path(), "logs") + err := os.MkdirAll(logPath, 0755) + if err != nil { + return + } + + err = os.WriteFile(filepath.Join(logPath, filename), []byte(log.String()), 0644) + if err != nil { + return + } + }, +} + +func Execute() { + if err := RootCmd.Execute(); err != nil { + // software error + os.Exit(70) + } +} + +func init() { + RootCmd.PersistentFlags().StringVarP(&configFlag, "config", "c", "", "config file path") + RootCmd.PersistentFlags().BoolVar(&silent, "silent", false, "do not print anything") + RootCmd.PersistentFlags().BoolVar(&trace, "trace", false, "enable tracing") + RootCmd.PersistentFlags().BoolVar(&plain, "plain", false, "plain text output (no ANSI)") + RootCmd.Flags().BoolVar(&printVersion, "version", false, "print the version number and exit") + + // Deprecated flags, should be kept to avoid breaking CLI integration. + RootCmd.Flags().BoolVarP(&initialize, "init", "i", false, "init") + RootCmd.Flags().StringVarP(&shellName, "shell", "s", "", "shell") + + // Hide flags that are deprecated or for internal use only. + _ = RootCmd.PersistentFlags().MarkHidden("silent") + + // Disable completions + RootCmd.CompletionOptions.DisableDefaultCmd = true +} diff --git a/src/cli/shell.go b/src/cli/shell.go new file mode 100644 index 000000000000..4a5671fc6a3b --- /dev/null +++ b/src/cli/shell.go @@ -0,0 +1,50 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/jandedobbeleer/oh-my-posh/src/dsc" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/spf13/cobra" +) + +// shellCmd represents the shell command +var shellCmd = &cobra.Command{ + Use: "shell get", + Short: "Get the shell name", + Long: `Get the shell name. + +This command retrieves the name of the current shell being used.`, + Example: ` oh-my-posh shell get`, + ValidArgs: []string{ + "get", + }, + Args: NoArgsOrOneValidArg, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + flags := &runtime.Flags{ + Shell: os.Getenv("POSH_SHELL"), + } + + env := &runtime.Terminal{} + env.Init(flags) + + switch args[0] { + case "get": + fmt.Print(env.Shell()) + default: + _ = cmd.Help() + } + }, +} + +func init() { + shellCmd.AddCommand(dsc.Command(shell.DSC())) + RootCmd.AddCommand(shellCmd) +} diff --git a/src/cli/stream.go b/src/cli/stream.go new file mode 100644 index 000000000000..5d27adb32704 --- /dev/null +++ b/src/cli/stream.go @@ -0,0 +1,105 @@ +package cli + +import ( + "fmt" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/prompt" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + + "github.com/spf13/cobra" +) + +// streamCmd represents the stream command +var streamCmd = createStreamCmd() + +func init() { + RootCmd.AddCommand(streamCmd) +} + +func createStreamCmd() *cobra.Command { + streamCmd := &cobra.Command{ + Use: "stream", + Short: "Stream the prompt with incremental updates", + Long: `Stream the primary prompt with incremental updates as segments complete. +Output format: null-byte delimited prompt strings (each complete prompt separated by \0). +This allows multi-line prompts to be handled correctly. +The shell can read records incrementally and update the display. +Command exits when all segments are resolved.`, + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { + if shellName == "" { + shellName = shell.GENERIC + } + + flags := &runtime.Flags{ + ConfigPath: configFlag, + PWD: pwd, + PSWD: pswd, + ErrorCode: status, + PipeStatus: pipestatus, + ExecutionTime: timing, + StackCount: stackCount, + TerminalWidth: terminalWidth, + Eval: eval, + Shell: shellName, + ShellVersion: shellVersion, + Plain: plain, + Type: prompt.PRIMARY, + Cleared: cleared, + NoExitCode: noStatus, + Column: column, + JobCount: jobCount, + IsPrimary: true, + Escape: escape, + Force: force, + Streaming: true, + } + + options := []cache.Option{} + if saveCache { + options = append(options, cache.Persist) + } + + cache.Init(shellName, options...) + + eng := prompt.New(flags) + + defer func() { + template.SaveCache() + cache.Close() + }() + + // Stream prompt updates + for promptString := range eng.StreamPrimary() { + fmt.Print(promptString) + fmt.Print("\x00") // Null byte delimiter for multi-line prompts + } + }, + } + + streamCmd.Flags().StringVar(&pwd, "pwd", "", "current working directory") + streamCmd.Flags().StringVar(&pswd, "pswd", "", "current working directory (according to pwsh)") + streamCmd.Flags().StringVar(&shellName, "shell", "", "the shell to stream for") + streamCmd.Flags().StringVar(&shellVersion, "shell-version", "", "the shell version") + streamCmd.Flags().IntVar(&status, "status", 0, "last known status code") + streamCmd.Flags().BoolVar(&noStatus, "no-status", false, "no valid status code (cancelled or no command yet)") + streamCmd.Flags().StringVar(&pipestatus, "pipestatus", "", "the PIPESTATUS array") + streamCmd.Flags().Float64Var(&timing, "execution-time", 0, "timing of the last command") + streamCmd.Flags().IntVarP(&stackCount, "stack-count", "s", 0, "number of locations on the stack") + streamCmd.Flags().IntVarP(&terminalWidth, "terminal-width", "w", 0, "width of the terminal") + streamCmd.Flags().BoolVar(&cleared, "cleared", false, "do we have a clear terminal or not") + streamCmd.Flags().BoolVar(&eval, "eval", false, "output the prompt for eval") + streamCmd.Flags().IntVar(&column, "column", 0, "the column position of the cursor") + streamCmd.Flags().IntVar(&jobCount, "job-count", 0, "number of background jobs") + streamCmd.Flags().BoolVar(&saveCache, "save-cache", false, "save updated cache to file") + streamCmd.Flags().BoolVar(&escape, "escape", true, "escape the ANSI sequences for the shell") + streamCmd.Flags().BoolVarP(&force, "force", "f", false, "force rendering the segments") + + // Hide flags that are for internal use only. + _ = streamCmd.Flags().MarkHidden("save-cache") + + return streamCmd +} diff --git a/src/cli/stream_test.go b/src/cli/stream_test.go new file mode 100644 index 000000000000..ef934eda6034 --- /dev/null +++ b/src/cli/stream_test.go @@ -0,0 +1,222 @@ +package cli + +import ( + "bytes" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStreamCommand_Creation(t *testing.T) { + cmd := createStreamCmd() + + assert.NotNil(t, cmd) + assert.Equal(t, "stream", cmd.Use) + assert.Equal(t, "Stream the prompt with incremental updates", cmd.Short) +} + +func TestStreamCommand_Flags(t *testing.T) { + cmd := createStreamCmd() + + // Verify all expected flags exist + expectedFlags := []string{ + "pwd", + "pswd", + "shell", + "shell-version", + "status", + "no-status", + "pipestatus", + "execution-time", + "stack-count", + "terminal-width", + "cleared", + "eval", + "column", + "job-count", + "save-cache", + "escape", + "force", + } + + for _, flagName := range expectedFlags { + flag := cmd.Flags().Lookup(flagName) + assert.NotNil(t, flag, "Flag '%s' should exist", flagName) + } +} + +func TestStreamCommand_RequiredFlagsForStreaming(t *testing.T) { + // This test validates that the stream command sets the correct flags + // for streaming execution mode + + cmd := createStreamCmd() + + // Verify that running the command would set streaming=true + // We can't easily test the actual run without a full config, + // but we can verify the command is properly configured + assert.NotNil(t, cmd.Run) + assert.NotNil(t, cmd.Args) +} + +func TestStreamCommand_FlagInheritance(t *testing.T) { + // Verify that stream command uses the same flags as print command + // This ensures consistency between commands + + streamCmd := createStreamCmd() + printCmd := createPrintCmd() + + // Core flags that should exist in both + sharedFlags := []string{ + "pwd", + "shell", + "status", + "execution-time", + "terminal-width", + "eval", + "force", + } + + for _, flagName := range sharedFlags { + streamFlag := streamCmd.Flags().Lookup(flagName) + printFlag := printCmd.Flags().Lookup(flagName) + + assert.NotNil(t, streamFlag, "Stream command should have '%s' flag", flagName) + assert.NotNil(t, printFlag, "Print command should have '%s' flag", flagName) + + // Verify default values match + if streamFlag != nil && printFlag != nil { + assert.Equal(t, printFlag.DefValue, streamFlag.DefValue, + "Flag '%s' should have same default value in both commands", flagName) + } + } +} + +func TestStreamCommand_OutputDelimiter(t *testing.T) { + // Test that output uses null byte delimiter for multi-line prompts + tests := []struct { + name string + expected string + prompts []string + }{ + { + name: "Single line prompt", + prompts: []string{"prompt1"}, + expected: "prompt1\x00", + }, + { + name: "Multi-line prompt", + prompts: []string{"line1\nline2\nline3"}, + expected: "line1\nline2\nline3\x00", + }, + { + name: "Multiple prompts", + prompts: []string{"prompt1", "prompt2", "prompt3"}, + expected: "prompt1\x00prompt2\x00prompt3\x00", + }, + { + name: "Multiple multi-line prompts", + prompts: []string{"line1\nline2", "line3\nline4"}, + expected: "line1\nline2\x00line3\nline4\x00", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Simulate output with null byte delimiter + var buf bytes.Buffer + for _, prompt := range tt.prompts { + buf.WriteString(prompt) + buf.WriteString("\x00") + } + + assert.Equal(t, tt.expected, buf.String()) + }) + } +} + +func TestStreamCommand_Integration_MockOutput(t *testing.T) { + // This test validates the output structure without requiring a full engine + // It simulates what the stream command would output with null byte delimiter + + tests := []struct { + validateOutput func(t *testing.T, output string) + name string + promptCount int + }{ + { + name: "Single prompt with null byte", + promptCount: 1, + validateOutput: func(t *testing.T, output string) { + assert.True(t, strings.HasSuffix(output, "\x00")) + }, + }, + { + name: "Multiple prompts with null bytes", + promptCount: 3, + validateOutput: func(t *testing.T, output string) { + parts := strings.Split(output, "\x00") + // 3 prompts = 4 parts (including trailing empty string after last \x00) + assert.Len(t, parts, 4) + // Last part should be empty + assert.Equal(t, "", parts[3]) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Simulate stream output with null byte delimiter + var output bytes.Buffer + for i := 0; i < tt.promptCount; i++ { + output.WriteString("prompt") + output.WriteString("\x00") + } + + tt.validateOutput(t, output.String()) + }) + } +} + +func TestStreamCommand_HiddenFlags(t *testing.T) { + cmd := createStreamCmd() + + // Verify save-cache is hidden (internal use only) + saveCacheFlag := cmd.Flags().Lookup("save-cache") + require.NotNil(t, saveCacheFlag) + assert.True(t, saveCacheFlag.Hidden, "save-cache flag should be hidden") +} + +func TestStreamCommand_NoArgs(t *testing.T) { + cmd := createStreamCmd() + + // Stream command should not accept positional arguments + // (unlike print which accepts primary/secondary/etc.) + assert.NotNil(t, cmd.Args) + + // Test that NoArgs validator rejects arguments + err := cmd.Args(cmd, []string{"extra"}) + assert.Error(t, err, "Should reject arguments when NoArgs is used") + + // Test that NoArgs validator accepts no arguments + err = cmd.Args(cmd, []string{}) + assert.NoError(t, err, "Should accept no arguments") +} + +func TestStreamCommand_StreamingFlagEnabled(t *testing.T) { + // This validates that the stream command would create + // a Flags struct with Streaming=true + + // We can't easily test the full execution without mocking the entire engine, + // but we can verify the command structure is correct + + cmd := createStreamCmd() + assert.NotNil(t, cmd.Run) + + // The Run function should: + // 1. Create Flags with Streaming=true + // 2. Set Type=prompt.PRIMARY + // 3. Set IsPrimary=true + // These are validated by code inspection in the createStreamCmd implementation +} diff --git a/src/cli/toggle.go b/src/cli/toggle.go new file mode 100644 index 000000000000..937edf6f3c2d --- /dev/null +++ b/src/cli/toggle.go @@ -0,0 +1,69 @@ +package cli + +import ( + "os" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/spf13/cobra" +) + +// toggleCmd represents the toggle command +var toggleCmd = &cobra.Command{ + Use: "toggle segment1 segment2 ...", + Short: "Toggle one or more segments on/off", + Long: "Toggle one or more segments on/off on the fly. Multiple segments can be specified separated by spaces.", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + env := &runtime.Terminal{} + env.Init(&runtime.Flags{}) + + cache.Init(os.Getenv("POSH_SHELL"), cache.Persist) + + defer func() { + cache.Close() + }() + + // Get current toggles from cache as a map + currentToggleSet, _ := cache.Get[map[string]bool](cache.Session, cache.TOGGLECACHE) + if currentToggleSet == nil { + currentToggleSet = make(map[string]bool) + } + + segmentsToToggle := parseSegments(args) + + // Toggle segments: remove if present, add if not present + for _, segment := range segmentsToToggle { + if currentToggleSet[segment] { + delete(currentToggleSet, segment) + continue + } + + currentToggleSet[segment] = true + } + + // Store the map directly in cache + cache.Set(cache.Session, cache.TOGGLECACHE, currentToggleSet, cache.INFINITE) + }, +} + +func parseSegments(args []string) []string { + var segments []string + for _, arg := range args { + if segment := strings.TrimSpace(arg); segment != "" { + segments = append(segments, segment) + } + } + + return segments +} + +func init() { + RootCmd.AddCommand(toggleCmd) +} diff --git a/src/cli/upgrade.go b/src/cli/upgrade.go new file mode 100644 index 000000000000..2d8f2f4cbc09 --- /dev/null +++ b/src/cli/upgrade.go @@ -0,0 +1,152 @@ +package cli + +import ( + "fmt" + "os" + stdruntime "runtime" + "slices" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/upgrade" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + "github.com/jandedobbeleer/oh-my-posh/src/text" + "github.com/spf13/cobra" +) + +var ( + force bool + auto bool +) + +// upgradeCmd represents the upgrade command +var upgradeCmd = &cobra.Command{ + Use: "upgrade", + Short: "Upgrade when a new version is available.", + Long: "Upgrade when a new version is available.", + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { + var startTime time.Time + + if debug { + startTime = time.Now() + log.Enable(plain) + } + + if upgrade.IsPackagedInstallation() { + msg := "upgrade is not supported when installed as a MSIX package" + log.Debug(msg) + fmt.Printf("\n ❌ %s\n\n", msg) + return + } + + supportedPlatforms := []string{ + runtime.WINDOWS, + runtime.DARWIN, + runtime.LINUX, + } + + if !slices.Contains(supportedPlatforms, stdruntime.GOOS) { + log.Debug("unsupported platform") + return + } + + sh := os.Getenv("POSH_SHELL") + + env := &runtime.Terminal{} + env.Init(&runtime.Flags{ + Debug: debug, + }) + + cache.Init(sh, cache.Persist) + + // Only respect the cache interval when using --auto flag + if _, OK := cache.Get[string](cache.Device, upgrade.CACHEKEY); OK && auto { + log.Debug("upgrade check already performed recently, skipping") + return + } + + terminal.Init(sh) + fmt.Print(terminal.StartProgress()) + + cfg := config.Get(configFlag, false) + + defer func() { + fmt.Print(terminal.StopProgress()) + + // Set the cache key after any upgrade check to prevent redundant checks + cache.Set(cache.Device, upgrade.CACHEKEY, "true", cfg.Upgrade.Interval) + + cache.Close() + + if !debug { + return + } + + sb := text.NewBuilder() + + sb.WriteString(fmt.Sprintf("%s %s\n", log.Text("Upgrade duration:").Green().Bold().Plain(), time.Since(startTime))) + + sb.WriteString(log.Text("\nLogs:\n\n").Green().Bold().Plain().String()) + sb.WriteString(env.Logs()) + + fmt.Println(sb.String()) + }() + + latest, err := cfg.Upgrade.FetchLatest() + if err != nil { + log.Debug("failed to get latest version") + log.Error(err) + fmt.Printf("\n ❌ %s\n\n", err) + + exitcode = 1 + return + } + + log.Debugf("current version: v%s, latest version: v%s", build.Version, latest) + + if force { + log.Debug("forced upgrade") + exitcode = executeUpgrade(cfg.Upgrade) + return + } + + if upgrade.IsMajorUpgrade(build.Version, latest) { + log.Debug("major upgrade available") + message := fmt.Sprintf("\n 🚨 major upgrade available: v%s -> v%s, use oh-my-posh upgrade --force to upgrade\n\n", build.Version, latest) + fmt.Print(message) + return + } + + if build.Version != latest { + log.Debug("upgrade available") + exitcode = executeUpgrade(cfg.Upgrade) + return + } + + log.Debug("already on the latest version") + }, +} + +func executeUpgrade(cfg *upgrade.Config) int { + err := upgrade.Run(cfg) + if err == nil { + return 0 + } + + log.Debug("failed to upgrade") + log.Error(err) + + return 1 +} + +func init() { + upgradeCmd.Flags().BoolVarP(&force, "force", "f", false, "force the upgrade even if the version is up to date") + upgradeCmd.Flags().BoolVar(&auto, "auto", false, "respect the cache interval for automatic upgrades") + upgradeCmd.Flags().BoolVar(&debug, "debug", false, "enable/disable debug mode") + RootCmd.AddCommand(upgradeCmd) +} diff --git a/src/cli/upgrade/config.go b/src/cli/upgrade/config.go new file mode 100644 index 000000000000..ecd8a55c4aea --- /dev/null +++ b/src/cli/upgrade/config.go @@ -0,0 +1,123 @@ +package upgrade + +import ( + "context" + "encoding/gob" + "fmt" + "io" + httplib "net/http" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/progress" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" +) + +func init() { + gob.Register(&Config{}) + gob.Register((*Source)(nil)) +} + +type Config struct { + Source Source `json:"source" toml:"source" yaml:"source"` + Interval cache.Duration `json:"interval" toml:"interval" yaml:"interval"` + Latest string `json:"-" toml:"-" yaml:"-"` + Auto bool `json:"auto" toml:"auto" yaml:"auto"` + DisplayNotice bool `json:"notice" toml:"notice" yaml:"notice"` + Force bool `json:"-" toml:"-" yaml:"-"` +} + +type Source string + +const ( + GitHub Source = "github" + CDN Source = "cdn" +) + +func (s Source) String() string { + switch s { + case GitHub: + return "github.com" + case CDN: + return "cdn.ohmyposh.dev" + default: + return "Unknown" + } +} + +func (cfg *Config) FetchLatest() (string, error) { + cfg.Latest = "latest" + v, err := cfg.DownloadAsset("version.txt") + if err != nil { + log.Debugf("failed to get latest version for source: %s", cfg.Source) + return "", err + } + + version := strings.TrimSpace(string(v)) + cfg.Latest = version + + version = strings.TrimPrefix(version, "v") + log.Debugf("latest version: %s", version) + + return version, err +} + +func (cfg *Config) DownloadAsset(asset string) ([]byte, error) { + if cfg.Source == "" { + log.Debug("no source specified, defaulting to github") + cfg.Source = GitHub + } + + switch cfg.Source { + case GitHub: + var url string + + switch cfg.Latest { + case "latest": + url = fmt.Sprintf("https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/%s", asset) + default: + url = fmt.Sprintf("https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/%s/%s", cfg.Latest, asset) + } + + return cfg.Download(url) + case CDN: + fallthrough + default: + url := fmt.Sprintf("https://cdn.ohmyposh.dev/releases/%s/%s", cfg.Latest, asset) + return cfg.Download(url) + } +} + +func (cfg *Config) Download(url string) ([]byte, error) { + req, err := httplib.NewRequestWithContext(context.Background(), "GET", url, nil) + if err != nil { + log.Debugf("failed to create request for url: %s", url) + return nil, err + } + + req.Header.Add("User-Agent", "oh-my-posh") + req.Header.Add("Cache-Control", "max-age=0") + + resp, err := http.HTTPClient.Do(req) + if err != nil { + log.Debugf("failed to execute HTTP request: %s", url) + return nil, err + } + + if resp.StatusCode != httplib.StatusOK { + return nil, fmt.Errorf("failed to download asset: %s", url) + } + + defer resp.Body.Close() + + reader := progress.NewReader(resp.Body, resp.ContentLength, program) + + data, err := io.ReadAll(reader) + if err != nil { + log.Debugf("failed to read response body: %s", url) + return nil, err + } + + return data, nil +} diff --git a/src/cli/upgrade/install.go b/src/cli/upgrade/install.go new file mode 100644 index 000000000000..5ac419eafb38 --- /dev/null +++ b/src/cli/upgrade/install.go @@ -0,0 +1,86 @@ +package upgrade + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +func install(cfg *Config) error { + setState(validating) + + executable, err := os.Executable() + if err != nil { + log.Debug("failed to get executable path") + return err + } + + targetDir := filepath.Dir(executable) + fileName := filepath.Base(executable) + + newPath := filepath.Join(targetDir, fmt.Sprintf(".%s.new", fileName)) + fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775) + if err != nil { + log.Error(err) + return errors.New("we do not have permissions to update") + } + + setState(downloading) + + data, err := downloadAndVerify(cfg) + if err != nil { + log.Debug("failed to download and verify") + return err + } + + setState(installing) + + _, err = io.Copy(fp, bytes.NewReader(data)) + // windows will have a lock when we do not close the file + fp.Close() + + if err != nil { + log.Debug("failed to copy data to new file") + return err + } + + oldPath := filepath.Join(targetDir, fmt.Sprintf(".%s.old", fileName)) + + _ = os.Remove(oldPath) + + err = os.Rename(executable, oldPath) + if err != nil { + log.Debug("failed to rename old file") + return err + } + + err = os.Rename(newPath, executable) + + if err != nil { + log.Debug("failed to rename new file, rolling back") + // rollback + rerr := os.Rename(oldPath, executable) + if rerr != nil { + log.Debug("failed to rollback old file") + return rerr + } + + return err + } + + removeErr := os.Remove(oldPath) + + // hide the old executable if we can't remove it + if removeErr != nil { + log.Error(removeErr) + // hide the old executable + _ = hideFile(oldPath) + } + + return nil +} diff --git a/src/cli/upgrade/install_noop.go b/src/cli/upgrade/install_noop.go new file mode 100644 index 000000000000..a06e6208225c --- /dev/null +++ b/src/cli/upgrade/install_noop.go @@ -0,0 +1,11 @@ +//go:build !windows + +package upgrade + +func hideFile(_ string) error { + return nil +} + +func IsPackagedInstallation() bool { + return false +} diff --git a/src/cli/upgrade/install_windows.go b/src/cli/upgrade/install_windows.go new file mode 100644 index 000000000000..e7e0804d164c --- /dev/null +++ b/src/cli/upgrade/install_windows.go @@ -0,0 +1,31 @@ +package upgrade + +import ( + "syscall" + "unsafe" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" +) + +func hideFile(path string) error { + kernel32 := syscall.NewLazyDLL("kernel32.dll") + setFileAttributes := kernel32.NewProc("SetFileAttributesW") + + ptr, err := syscall.UTF16PtrFromString(path) + if err != nil { + return err + } + + r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(ptr)), 2) + + if r1 == 0 { + return err + } + + return nil +} + +func IsPackagedInstallation() bool { + _, ok := cache.PackageFamilyName() + return ok +} diff --git a/src/cli/upgrade/notice.go b/src/cli/upgrade/notice.go new file mode 100644 index 000000000000..25aedba69a1d --- /dev/null +++ b/src/cli/upgrade/notice.go @@ -0,0 +1,53 @@ +package upgrade + +import ( + "fmt" + "os" + + "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" +) + +const ( + CACHEKEY = "upgrade_check" + + upgradeNotice = ` +A new release of Oh My Posh is available: v%s → v%s +To upgrade, run: 'oh-my-posh upgrade%s' + +To enable automated upgrades, run: 'oh-my-posh enable upgrade'. +` +) + +// Returns the upgrade notice if a new version is available +// that should be displayed to the user. +// +// The upgrade check is only performed every other week. +func (cfg *Config) Notice() (string, bool) { + // never validate when we install using the Windows Store + if os.Getenv("POSH_INSTALLER") == "ws" { + log.Debug("skipping upgrade check because we are using the Windows Store") + return "", false + } + + if !http.IsConnected() { + return "", false + } + + latest, err := cfg.FetchLatest() + if err != nil { + return "", false + } + + if latest == build.Version { + return "", false + } + + var forceUpdate string + if IsMajorUpgrade(build.Version, latest) { + forceUpdate = " --force" + } + + return fmt.Sprintf(upgradeNotice, build.Version, latest, forceUpdate), true +} diff --git a/src/cli/upgrade/notice_test.go b/src/cli/upgrade/notice_test.go new file mode 100644 index 000000000000..e550696be4a5 --- /dev/null +++ b/src/cli/upgrade/notice_test.go @@ -0,0 +1,82 @@ +package upgrade + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/build" + rhttp "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" + "github.com/stretchr/testify/assert" +) + +// testRoundTripper redirects all outbound requests to a local test server. +type testRoundTripper struct { + target *url.URL +} + +func (rt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + cloned := req.Clone(req.Context()) + cloned.URL = &url.URL{ + Scheme: rt.target.Scheme, + Host: rt.target.Host, + Path: req.URL.Path, + } + return http.DefaultTransport.RoundTrip(cloned) +} + +func TestCanUpgrade(t *testing.T) { + const fakeLatest = "99.99.99" + + // Serve a fake version file locally so tests never hit GitHub. + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + fmt.Fprintf(w, "v%s", fakeLatest) + })) + defer server.Close() + + targetURL, _ := url.Parse(server.URL) + + savedHTTPClient := rhttp.HTTPClient + rhttp.HTTPClient = &http.Client{Transport: &testRoundTripper{target: targetURL}} + defer func() { rhttp.HTTPClient = savedHTTPClient }() + + savedIsConnected := rhttp.IsConnected + rhttp.IsConnected = func() bool { return true } + defer func() { rhttp.IsConnected = savedIsConnected }() + + ugc := &Config{} + latest, err := ugc.FetchLatest() + if err != nil { + t.Fatalf("failed to fetch latest version: %v", err) + } + + cases := []struct { + Case string + CurrentVersion string + Installer string + Expected bool + Cache bool + }{ + {Case: "Up to date", CurrentVersion: latest}, + {Case: "Outdated Linux", Expected: true, CurrentVersion: "3.0.0"}, + {Case: "Outdated Darwin", Expected: true, CurrentVersion: "3.0.0"}, + {Case: "Cached", Cache: true, CurrentVersion: latest}, + {Case: "Windows Store", Installer: "ws"}, + } + + for _, tc := range cases { + build.Version = tc.CurrentVersion + + if len(tc.Installer) > 0 { + os.Setenv("POSH_INSTALLER", tc.Installer) + } + + _, canUpgrade := ugc.Notice() + assert.Equal(t, tc.Expected, canUpgrade, tc.Case) + + os.Setenv("POSH_INSTALLER", "") + } +} diff --git a/src/cli/upgrade/public_key.pem b/src/cli/upgrade/public_key.pem new file mode 100644 index 000000000000..932761019ac0 --- /dev/null +++ b/src/cli/upgrade/public_key.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA98lHhNau5x0JtjSuwiWLuC2yKO6NA6/0bH2gE8tAq4c= +-----END PUBLIC KEY----- diff --git a/src/cli/upgrade/tui.go b/src/cli/upgrade/tui.go new file mode 100644 index 000000000000..1ea73eef76b8 --- /dev/null +++ b/src/cli/upgrade/tui.go @@ -0,0 +1,166 @@ +package upgrade + +import ( + "fmt" + "strings" + + progress_ "github.com/charmbracelet/bubbles/progress" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/jandedobbeleer/oh-my-posh/src/cli/progress" + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +var ( + program *tea.Program + textStyle = lipgloss.NewStyle().Margin(1, 0, 2, 2) +) + +type resultMsg string + +type stateMsg state + +type state int + +const ( + validating state = iota + downloading + verifying + installing +) + +func setState(message state) { + if program == nil { + return + } + + program.Send(stateMsg(message)) +} + +type model struct { + error error + config *Config + spinner *spinner.Model + progress *progress.Model + message string + state state +} + +func initialModel(cfg *Config) *model { + s := spinner.New() + s.Spinner = spinner.Dot + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) + + p := progress.NewModel() + + return &model{spinner: &s, config: cfg, progress: p} +} + +func (m *model) Init() tea.Cmd { + go m.start() + + return m.spinner.Tick +} + +func (m *model) start() { + if err := install(m.config); err != nil { + m.error = err + log.Debug("failed to install") + program.Send(resultMsg(fmt.Sprintf(" ❌ upgrade failed: %v", err))) + return + } + + current := fmt.Sprintf("v%s", build.Version) + message := fmt.Sprintf("🚀 Upgraded from %s to %s", current, m.config.Latest) + + if current != m.config.Latest { + log.Debug("new version installed, user needs to restart shell") + message += ", restart your shell to take full advantage of the new functionality" + } + + program.Send(resultMsg(message)) +} + +func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "q", "esc", "ctrl+c": + return m, tea.Quit + default: + return m, nil + } + + case resultMsg: + m.message = string(msg) + return m, tea.Quit + + case stateMsg: + m.state = state(msg) + return m, nil + + case progress.Message: + return m, m.progress.SetPercent(float64(msg)) + + case progress_.FrameMsg: + return m, m.progress.Update(msg) + + default: + s, cmd := m.spinner.Update(msg) + m.spinner = &s + return m, cmd + } +} + +func (m *model) View() string { + if len(m.message) > 0 { + return textStyle.Render(m.message) + } + + var message string + m.spinner.Spinner = spinner.Dot + + switch m.state { + case validating: + message = "Validating current installation" + case downloading: + message = fmt.Sprintf("Downloading %s from %s...\n%s", m.config.Latest, m.config.Source.String(), m.progress.View()) + return textStyle.Render(message) + case verifying: + m.spinner.Spinner = spinner.Moon + message = "Verifying download" + case installing: + m.spinner.Spinner = spinner.Jump + message = "Installing" + } + + return textStyle.Render(fmt.Sprintf("%s %s", m.spinner.View(), message)) +} + +func Run(cfg *Config) error { + program = tea.NewProgram(initialModel(cfg)) + resultModel, _ := program.Run() + + programModel, OK := resultModel.(*model) + if !OK { + log.Debug("failed to cast model") + return nil + } + + return programModel.error +} + +func IsMajorUpgrade(current, latest string) bool { + if current == "" { + return false + } + + getMajorNumber := func(version string) string { + major, _, _ := strings.Cut(version, ".") + return major + } + + return getMajorNumber(current) != getMajorNumber(latest) +} diff --git a/src/cli/upgrade/tui_test.go b/src/cli/upgrade/tui_test.go new file mode 100644 index 000000000000..be538834bd70 --- /dev/null +++ b/src/cli/upgrade/tui_test.go @@ -0,0 +1,25 @@ +package upgrade + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsMajorUpgrade(t *testing.T) { + cases := []struct { + Case string + CurrentVersion string + LatestVersion string + Expected bool + }{ + {Case: "Same version", Expected: false, CurrentVersion: "v3.0.0", LatestVersion: "v3.0.0"}, + {Case: "Breaking change", Expected: true, CurrentVersion: "v3.0.0", LatestVersion: "v4.0.0"}, + {Case: "Empty version, mostly development build", Expected: false, LatestVersion: "v4.0.0"}, + } + + for _, tc := range cases { + canUpgrade := IsMajorUpgrade(tc.CurrentVersion, tc.LatestVersion) + assert.Equal(t, tc.Expected, canUpgrade, tc.Case) + } +} diff --git a/src/cli/upgrade/verify.go b/src/cli/upgrade/verify.go new file mode 100644 index 000000000000..18c1c12d1f6e --- /dev/null +++ b/src/cli/upgrade/verify.go @@ -0,0 +1,145 @@ +package upgrade + +import ( + "crypto/ed25519" + "crypto/sha256" + "crypto/x509" + _ "embed" + "encoding/pem" + "fmt" + stdruntime "runtime" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" +) + +// This is based on the following key generation and validation. +// Generate a private key: +// openssl genpkey -algorithm Ed25519 -out private_key.pem +// Extract the public key: +// openssl pkey -in private_key.pem -pubout -out public_key.pem +// Sign the checksums.txt file: +// openssl pkeyutl -sign -inkey private_key.pem -out checksums.txt.sig -rawin -in checksums.txt +// Verify the signature: +// openssl pkeyutl -verify -pubin -inkey public_key.pem -sigfile checksums.txt.sig -rawin -in checksums.txt +// The public key is embedded in the binary. +// The private key is used to sign the checksums.txt file. +// The signature is embedded in the release. +// The checksums.txt file contains the checksums of the release assets. +// All checks are done in memory. +// Only then the binary is written to disk. + +//go:embed public_key.pem +var publicKey []byte + +func downloadAndVerify(cfg *Config) ([]byte, error) { + extension := "" + if stdruntime.GOOS == runtime.WINDOWS { + extension = ".exe" + } + + asset := fmt.Sprintf("posh-%s-%s%s", stdruntime.GOOS, stdruntime.GOARCH, extension) + + log.Debug("downloading asset:", asset) + + data, err := cfg.DownloadAsset(asset) + if err != nil { + log.Debug("failed to download asset") + return nil, err + } + + setState(verifying) + + err = verify(cfg, asset, data) + if err != nil { + log.Debug("failed to verify asset") + return nil, err + } + + return data, nil +} + +func verify(cfg *Config, asset string, binary []byte) error { + checksums, err := cfg.DownloadAsset("checksums.txt") + if err != nil { + log.Debug("failed to download checksums") + return err + } + + signature, err := cfg.DownloadAsset("checksums.txt.sig") + if err != nil { + log.Debug("failed to download checksums signature") + return err + } + + OK := validateSignature(checksums, signature) + if !OK { + log.Debug("failed to verify checksums signature") + return fmt.Errorf("failed to verify checksums signature") + } + + return validateChecksum(asset, checksums, binary) +} + +func validateSignature(data, signature []byte) bool { + ed25519PublicKey, err := loadPublicKey() + if err != nil { + log.Debug("failed to load public key") + log.Error(err) + return false + } + + return ed25519.Verify(*ed25519PublicKey, data, signature) +} + +func loadPublicKey() (*ed25519.PublicKey, error) { + block, _ := pem.Decode(publicKey) + if block == nil { + log.Debug("failed to decode PEM block") + return nil, fmt.Errorf("error parsing PEM block: key not found") + } + + pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + log.Debug("failed to parse public key") + return nil, fmt.Errorf("error parsing public key: %v", err) + } + + ed25519PubKey, ok := pubKey.(ed25519.PublicKey) + if !ok { + log.Debug("failed to convert public key to ed25519") + return nil, fmt.Errorf("invalid public key format: %v", err) + } + + return &ed25519PubKey, nil +} + +func validateChecksum(asset string, sha256sums, binary []byte) error { + var assetChecksum string + checksums := strings.SplitSeq(string(sha256sums), "\n") + + for line := range checksums { + if !strings.HasSuffix(line, asset) { + continue + } + + assetChecksum = strings.Fields(line)[0] + break + } + + if assetChecksum == "" { + log.Debug("failed to find checksum for asset") + return fmt.Errorf("failed to find checksum for asset") + } + + // calculate the checksum of the binary + binaryChecksum := fmt.Sprintf("%x", sha256.Sum256(binary)) + + if assetChecksum != binaryChecksum { + log.Debugf("checksum mismatch, expected: %s, got: %s", assetChecksum, binaryChecksum) + return fmt.Errorf("checksum mismatch") + } + + return nil +} diff --git a/src/cli/upgrade/verify_test.go b/src/cli/upgrade/verify_test.go new file mode 100644 index 000000000000..866c66b73a21 --- /dev/null +++ b/src/cli/upgrade/verify_test.go @@ -0,0 +1,30 @@ +package upgrade + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVerify(t *testing.T) { + checksum, err := os.ReadFile("../../test/signing/checksums.txt") + assert.NoError(t, err) + + signature, err := os.ReadFile("../../test/signing/checksums.txt.sig") + assert.NoError(t, err) + + OK := validateSignature(checksum, signature) + assert.True(t, OK) +} + +func TestVerifyFail(t *testing.T) { + checksum, err := os.ReadFile("../../test/signing/checksums.txt") + assert.NoError(t, err) + + signature, err := os.ReadFile("../../test/signing/checksums.txt.invalid.sig") + assert.NoError(t, err) + + OK := validateSignature(checksum, signature) + assert.False(t, OK) +} diff --git a/src/cli/version.go b/src/cli/version.go new file mode 100644 index 000000000000..2b562df47c8f --- /dev/null +++ b/src/cli/version.go @@ -0,0 +1,33 @@ +package cli + +import ( + "fmt" + + "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/spf13/cobra" +) + +var ( + verbose bool +) + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version", + Long: "Print the version number of oh-my-posh.", + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { + if !verbose { + fmt.Println(build.Version) + return + } + fmt.Println("Version: ", build.Version) + fmt.Println("Date: ", build.Date) + }, +} + +func init() { + versionCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "write verbose output") + RootCmd.AddCommand(versionCmd) +} diff --git a/src/color/colors.go b/src/color/colors.go new file mode 100644 index 000000000000..15691bbeec9d --- /dev/null +++ b/src/color/colors.go @@ -0,0 +1,369 @@ +package color + +import ( + "encoding/gob" + "fmt" + "strconv" + "strings" + "time" + + "github.com/gookit/color" + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/generics" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/template" +) + +func init() { + gob.Register(&Set{}) + gob.Register((*Ansi)(nil)) + gob.Register(&Palette{}) + gob.Register(&Palettes{}) + gob.Register(&Cycle{}) +} + +const ( + accentColor = "accent_color" +) + +var TrueColor = true + +// String is the interface that wraps ToColor method. +// +// ToColor gets the ANSI color code for a given color string. +// This can include a valid hex color in the format `#FFFFFF`, +// but also a name of one of the first 16 ANSI colors like `lightBlue`. +type String interface { + ToAnsi(colorString Ansi, isBackground bool) Ansi + Resolve(colorString Ansi) (Ansi, error) +} + +type Set struct { + Background Ansi `json:"background" toml:"background" yaml:"background"` + Foreground Ansi `json:"foreground" toml:"foreground" yaml:"foreground"` +} + +func (c *Set) String() string { + return fmt.Sprintf("%s|%s", c.Foreground, c.Background) +} + +func (c *Set) ParseString(colors string) { + parts := strings.SplitN(colors, "|", 3) + if len(parts) != 2 { + return + } + + c.Foreground = Ansi(parts[0]) + c.Background = Ansi(parts[1]) +} + +type History []*Set + +func (c *History) Len() int { + return len(*c) +} + +func (c *History) Add(background, foreground Ansi) { + colors := &Set{ + Foreground: foreground, + Background: background, + } + + if c.Len() == 0 { + *c = append(*c, colors) + return + } + + last := (*c)[c.Len()-1] + // never add the same colors twice + if last.Foreground == colors.Foreground && last.Background == colors.Background { + return + } + + *c = append(*c, colors) +} + +func (c *History) Pop() { + if c.Len() == 0 { + return + } + + *c = (*c)[:c.Len()-1] +} + +func (c *History) Background() Ansi { + if c.Len() == 0 { + return emptyColor + } + + return (*c)[c.Len()-1].Background +} + +func (c *History) Foreground() Ansi { + if c.Len() == 0 { + return emptyColor + } + + return (*c)[c.Len()-1].Foreground +} + +// Ansi is an ANSI color code ready to be printed to the console. +// Example: "38;2;255;255;255", "48;2;255;255;255", "31", "95". +type Ansi string + +const ( + emptyColor = Ansi("") +) + +func (c Ansi) IsEmpty() bool { + return c == emptyColor +} + +func (c Ansi) IsTransparent() bool { + return c == Transparent +} + +func (c Ansi) IsClear() bool { + return c == Transparent || c == emptyColor +} + +func (c Ansi) ToForeground() Ansi { + colorString := c.String() + if strings.HasPrefix(colorString, "38;") { + return Ansi(strings.Replace(colorString, "38;", "48;", 1)) + } + return c +} + +func (c Ansi) ResolveTemplate() Ansi { + if c.IsEmpty() { + return c + } + + if c.IsTransparent() { + return emptyColor + } + + text, err := template.Render(string(c), nil) + if err != nil { + return Transparent + } + + return Ansi(text) +} + +func (c Ansi) String() string { + return string(c) +} + +func MakeColors(palette Palette, cacheEnabled bool, accentColor Ansi, env runtime.Environment) (colors String) { + defaultColors := &Defaults{} + defaultColors.SetAccentColor(env, accentColor) + colors = defaultColors + + if palette != nil { + colors = &PaletteColors{ansiColors: colors, palette: palette} + } + + if cacheEnabled { + colors = &Cached{ansiColors: colors} + } + + return +} + +func (d *Defaults) SetAccentColor(env runtime.Environment, defaultColor Ansi) { + defer log.Trace(time.Now()) + + // get accent color from session cache first + if accent, OK := cache.Get[*Set](cache.Device, accentColor); OK { + d.accent = accent + return + } + + rgb, err := GetAccentColor(env) + if err != nil { + d.accent = &Set{ + Foreground: d.ToAnsi(defaultColor, false), + Background: d.ToAnsi(defaultColor, true), + } + + return + } + + if defaultColor == "" { + return + } + + foreground := color.RGB(rgb.R, rgb.G, rgb.B, false) + background := color.RGB(rgb.R, rgb.G, rgb.B, true) + + d.accent = &Set{ + Foreground: Ansi(foreground.String()), + Background: Ansi(background.String()), + } + + cache.Set(cache.Device, accentColor, d.accent, cache.INFINITE) +} + +type RGB struct { + R, G, B uint8 +} + +// Defaults is the default AnsiColors implementation. +type Defaults struct { + accent *Set +} + +var ( + // Map for color names and their respective foreground [0] or background [1] color codes + ansiColorCodes = map[Ansi][2]Ansi{ + "black": {"30", "40"}, + "red": {"31", "41"}, + "green": {"32", "42"}, + "yellow": {"33", "43"}, + "blue": {"34", "44"}, + "magenta": {"35", "45"}, + "cyan": {"36", "46"}, + "white": {"37", "47"}, + "default": {"39", "49"}, + "darkGray": {"90", "100"}, + "lightRed": {"91", "101"}, + "lightGreen": {"92", "102"}, + "lightYellow": {"93", "103"}, + "lightBlue": {"94", "104"}, + "lightMagenta": {"95", "105"}, + "lightCyan": {"96", "106"}, + "lightWhite": {"97", "107"}, + } +) + +func (d *Defaults) ToAnsi(ansiColor Ansi, isBackground bool) Ansi { + if ansiColor == "" { + return emptyColor + } + + if ansiColor.IsTransparent() { + return ansiColor + } + + if ansiColor == Accent { + if d.accent == nil { + return emptyColor + } + + if isBackground { + return d.accent.Background + } + + return d.accent.Foreground + } + + colorFromName, err := getAnsiColorFromName(ansiColor, isBackground) + if err == nil { + return colorFromName + } + + colorString := ansiColor.String() + + if !strings.HasPrefix(colorString, "#") { + val, err := strconv.ParseUint(colorString, 10, 64) + if err != nil || val > 255 { + return emptyColor + } + + c256 := color.C256(uint8(val), isBackground) + return Ansi(c256.String()) + } + + style := color.HEX(colorString, isBackground) + if !style.IsEmpty() { + if TrueColor { + return Ansi(style.String()) + } + + return Ansi(style.C256().String()) + } + + if colorInt, err := strconv.ParseInt(colorString, 10, 8); err == nil { + c := color.C256(uint8(colorInt), isBackground) + + return Ansi(c.String()) + } + + return emptyColor +} + +func (d *Defaults) Resolve(colorString Ansi) (Ansi, error) { + return colorString, nil +} + +// getAnsiColorFromName returns the color code for a given color name if the name is +// known ANSI color name. +func getAnsiColorFromName(colorValue Ansi, isBackground bool) (Ansi, error) { + if colorCodes, found := ansiColorCodes[colorValue]; found { + return colorCodes[generics.ToInt[int](isBackground)], nil + } + + return "", fmt.Errorf("color name %s does not exist", colorValue) +} + +func IsAnsiColorName(colorValue Ansi) bool { + _, ok := ansiColorCodes[colorValue] + return ok +} + +// PaletteColors is the AnsiColors Decorator that uses the Palette to do named color +// lookups before ANSI color code generation. +type PaletteColors struct { + ansiColors String + palette Palette +} + +func (p *PaletteColors) ToAnsi(colorString Ansi, isBackground bool) Ansi { + paletteColor, err := p.palette.ResolveColor(colorString) + if err != nil { + return emptyColor + } + + ansiColor := p.ansiColors.ToAnsi(paletteColor, isBackground) + + return ansiColor +} + +func (p *PaletteColors) Resolve(colorString Ansi) (Ansi, error) { + return p.palette.ResolveColor(colorString) +} + +// Cached is the AnsiColors Decorator that does simple color lookup caching. +// ToColor calls are cheap, but not free, and having a simple cache in +// has measurable positive effect on performance. +type Cached struct { + ansiColors String + colorCache map[cachedColorKey]Ansi +} + +type cachedColorKey struct { + colorString Ansi + isBackground bool +} + +func (c *Cached) ToAnsi(colorString Ansi, isBackground bool) Ansi { + if c.colorCache == nil { + c.colorCache = make(map[cachedColorKey]Ansi) + } + + key := cachedColorKey{colorString, isBackground} + if ansiColor, hit := c.colorCache[key]; hit { + return ansiColor + } + + ansiColor := c.ansiColors.ToAnsi(colorString, isBackground) + c.colorCache[key] = ansiColor + return ansiColor +} + +func (c *Cached) Resolve(colorString Ansi) (Ansi, error) { + return c.ansiColors.Resolve(colorString) +} diff --git a/src/color/colors_darwin.go b/src/color/colors_darwin.go new file mode 100644 index 000000000000..76713e898b22 --- /dev/null +++ b/src/color/colors_darwin.go @@ -0,0 +1,41 @@ +package color + +import ( + "errors" + "strconv" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" +) + +func GetAccentColor(env runtime.Environment) (*RGB, error) { + output, err := env.RunCommand("defaults", "read", "-g", "AppleAccentColor") + if err != nil { + log.Error(err) + return nil, errors.New("unable to read accent color") + } + + index, err := strconv.Atoi(output) + if err != nil { + log.Error(err) + return nil, errors.New("unable to parse accent color index") + } + + var accentColors = map[int]RGB{ + -1: {152, 152, 152}, // Graphite + 0: {224, 55, 62}, // Red + 1: {247, 130, 25}, // Orange + 2: {255, 199, 38}, // Yellow + 3: {96, 186, 70}, // Green + 4: {0, 122, 255}, // Blue + 5: {149, 61, 150}, // Purple + 6: {247, 79, 159}, // Pink + } + + color, exists := accentColors[index] + if !exists { + color = accentColors[6] // Default to graphite (white) + } + + return &color, nil +} diff --git a/src/color/colors_test.go b/src/color/colors_test.go new file mode 100644 index 000000000000..529f500c25dc --- /dev/null +++ b/src/color/colors_test.go @@ -0,0 +1,89 @@ +package color + +import ( + "errors" + "testing" + + "github.com/alecthomas/assert" + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/template" +) + +func TestGetAnsiFromColorString(t *testing.T) { + cases := []struct { + Case string + Expected Ansi + Color Ansi + Background bool + Color256 bool + }{ + {Case: "256 color", Expected: Ansi("38;5;99"), Color: "99", Background: false}, + {Case: "256 color", Expected: Ansi("38;5;122"), Color: "122", Background: false}, + {Case: "Invalid background", Expected: emptyColor, Color: "invalid", Background: true}, + {Case: "Invalid background", Expected: emptyColor, Color: "invalid", Background: false}, + {Case: "Hex foreground", Expected: Ansi("38;2;170;187;204"), Color: "#AABBCC", Background: false}, + {Case: "Hex background", Expected: Ansi("48;2;170;187;204"), Color: "#AABBCC", Background: true}, + {Case: "Base 8 foreground", Expected: Ansi("31"), Color: "red", Background: false}, + {Case: "Base 8 background", Expected: Ansi("41"), Color: "red", Background: true}, + {Case: "Base 16 foreground", Expected: Ansi("91"), Color: "lightRed", Background: false}, + {Case: "Base 16 background", Expected: Ansi("101"), Color: "lightRed", Background: true}, + {Case: "Non true color TERM", Expected: Ansi("38;5;146"), Color: "#AABBCC", Color256: true}, + } + for _, tc := range cases { + ansiColors := &Defaults{} + TrueColor = !tc.Color256 + ansiColor := ansiColors.ToAnsi(tc.Color, tc.Background) + assert.Equal(t, tc.Expected, ansiColor, tc.Case) + } +} + +func TestMakeColors(t *testing.T) { + env := &mock.Environment{} + + cache.Set(cache.Device, accentColor, &Set{}, cache.INFINITE) + defer cache.DeleteAll(cache.Device) + + env.On("WindowsRegistryKeyValue", `HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM\ColorizationColor`).Return(&runtime.WindowsRegistryValue{}, errors.New("err")) + colors := MakeColors(nil, false, "", env) + assert.IsType(t, &Defaults{}, colors) + + colors = MakeColors(nil, true, "", env) + assert.IsType(t, &Cached{}, colors) + assert.IsType(t, &Defaults{}, colors.(*Cached).ansiColors) + + colors = MakeColors(testPalette, false, "", env) + assert.IsType(t, &PaletteColors{}, colors) + assert.IsType(t, &Defaults{}, colors.(*PaletteColors).ansiColors) + + colors = MakeColors(testPalette, true, "", env) + assert.IsType(t, &Cached{}, colors) + assert.IsType(t, &PaletteColors{}, colors.(*Cached).ansiColors) + assert.IsType(t, &Defaults{}, colors.(*Cached).ansiColors.(*PaletteColors).ansiColors) +} + +func TestAnsiRender(t *testing.T) { + cases := []struct { + Case string + Expected Ansi + Term string + }{ + {Case: "Inside vscode", Expected: "#123456", Term: "vscode"}, + {Case: "Outside vscode", Expected: "", Term: "windowsterminal"}, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Getenv", "TERM_PROGRAM").Return(tc.Term) + env.On("Shell").Return("foo") + + template.Cache = new(cache.Template) + template.Init(env, nil, nil) + + ansi := Ansi("{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}") + got := ansi.ResolveTemplate() + + assert.Equal(t, tc.Expected, got, tc.Case) + } +} diff --git a/src/color/colors_unix.go b/src/color/colors_unix.go new file mode 100644 index 000000000000..e5a147c3afb5 --- /dev/null +++ b/src/color/colors_unix.go @@ -0,0 +1,9 @@ +//go:build !windows && !darwin + +package color + +import "github.com/jandedobbeleer/oh-my-posh/src/runtime" + +func GetAccentColor(_ runtime.Environment) (*RGB, error) { + return nil, &runtime.NotImplemented{} +} diff --git a/src/color/colors_windows.go b/src/color/colors_windows.go new file mode 100644 index 000000000000..4c029fc7d5da --- /dev/null +++ b/src/color/colors_windows.go @@ -0,0 +1,29 @@ +package color + +import ( + "errors" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" +) + +func GetAccentColor(env runtime.Environment) (*RGB, error) { + defer log.Trace(time.Now()) + + if env == nil { + return nil, errors.New("unable to get color without environment") + } + + // see https://stackoverflow.com/questions/3560890/vista-7-how-to-get-glass-color + value, err := env.WindowsRegistryKeyValue(`HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM\ColorizationColor`) + if err != nil || value.ValueType != runtime.DWORD { + return nil, err + } + + return &RGB{ + R: byte(value.DWord >> 16), + G: byte(value.DWord >> 8), + B: byte(value.DWord), + }, nil +} diff --git a/src/color/cycle.go b/src/color/cycle.go new file mode 100644 index 000000000000..67310d69796b --- /dev/null +++ b/src/color/cycle.go @@ -0,0 +1,11 @@ +package color + +type Cycle []*Set + +func (c Cycle) Loop() (*Set, Cycle) { + if len(c) == 0 { + return nil, c + } + + return c[0], append(c[1:], c[0]) +} diff --git a/src/color/keywords.go b/src/color/keywords.go new file mode 100644 index 000000000000..b0f340ff8ed3 --- /dev/null +++ b/src/color/keywords.go @@ -0,0 +1,77 @@ +package color + +const ( + // Transparent implies a transparent color + Transparent Ansi = "transparent" + // Accent is the OS accent color + Accent Ansi = "accent" + // ParentBackground takes the previous segment's background color + ParentBackground Ansi = "parentBackground" + // ParentForeground takes the previous segment's color + ParentForeground Ansi = "parentForeground" + // Background takes the current segment's background color + Background Ansi = "background" + // Foreground takes the current segment's foreground color + Foreground Ansi = "foreground" +) + +func (color Ansi) isKeyword() bool { + switch color { //nolint: exhaustive + case Transparent, ParentBackground, ParentForeground, Background, Foreground: + return true + default: + return false + } +} + +func (color Ansi) Resolve(current *Set, parents []*Set) Ansi { + resolveParentColor := func(keyword Ansi) Ansi { + for _, parentColor := range parents { + if parentColor == nil { + return Transparent + } + + switch keyword { //nolint: exhaustive + case ParentBackground: + keyword = parentColor.Background + case ParentForeground: + keyword = parentColor.Foreground + default: + if keyword == "" { + return Transparent + } + return keyword + } + } + + if keyword == "" { + return Transparent + } + + return keyword + } + + resolveKeyword := func(keyword Ansi) Ansi { + switch { + case keyword == Background && current != nil: + return current.Background + case keyword == Foreground && current != nil: + return current.Foreground + case (keyword == ParentBackground || keyword == ParentForeground) && parents != nil: + return resolveParentColor(keyword) + default: + return Transparent + } + } + + for color.isKeyword() { + resolved := resolveKeyword(color) + if resolved == color { + break + } + + color = resolved + } + + return color +} diff --git a/src/color/palette.go b/src/color/palette.go new file mode 100644 index 000000000000..721fafe8c771 --- /dev/null +++ b/src/color/palette.go @@ -0,0 +1,102 @@ +package color + +import ( + "fmt" + "sort" + "strings" +) + +type Palette map[Ansi]Ansi + +const ( + paletteKeyPrefix = "p:" + paletteKeyError = "palette: requested color %s does not exist in palette of colors %s" + paletteMaxRecursionDepth = 3 // allows 3 or less recursive resolutions + paletteRecursiveKeyError = "palette: recursive resolution of color %s returned palette reference %s and reached recursion depth %d" +) + +// ResolveColor gets a color value from the palette using given colorName. +// If colorName is not a palette reference, it is returned as is. +func (p Palette) ResolveColor(colorName Ansi) (Ansi, error) { + return p.resolveColor(colorName, 1, &colorName) +} + +// originalColorName is a pointer to save allocations +func (p Palette) resolveColor(colorName Ansi, depth int, originalColorName *Ansi) (Ansi, error) { + key, ok := asPaletteKey(colorName) + // colorName is not a palette key, return it as is + if !ok { + return colorName, nil + } + + color, ok := p[key] + if !ok { + return "", &PaletteKeyError{Key: key, palette: p} + } + + if _, isKey := isPaletteKey(color); isKey { + if depth > paletteMaxRecursionDepth { + return "", &PaletteRecursiveKeyError{Key: *originalColorName, Value: color, depth: depth} + } + + return p.resolveColor(color, depth+1, originalColorName) + } + + return color, nil +} + +func asPaletteKey(colorName Ansi) (Ansi, bool) { + prefix, isKey := isPaletteKey(colorName) + if !isKey { + return "", false + } + + key := strings.TrimPrefix(colorName.String(), prefix.String()) + + return Ansi(key), true +} + +func isPaletteKey(colorName Ansi) (Ansi, bool) { + return paletteKeyPrefix, strings.HasPrefix(colorName.String(), paletteKeyPrefix) +} + +// PaletteKeyError records the missing Palette key. +type PaletteKeyError struct { + palette Palette + Key Ansi +} + +func (p *PaletteKeyError) Error() string { + keys := make([]string, 0, len(p.palette)) + for key := range p.palette { + keys = append(keys, key.String()) + } + sort.Strings(keys) + allColors := strings.Join(keys, ",") + errorStr := fmt.Sprintf(paletteKeyError, p.Key, allColors) + return errorStr +} + +// PaletteRecursiveKeyError records the Palette key and resolved color value (which +// is also a Palette key) +type PaletteRecursiveKeyError struct { + Key Ansi + Value Ansi + depth int +} + +func (p *PaletteRecursiveKeyError) Error() string { + errorStr := fmt.Sprintf(paletteRecursiveKeyError, p.Key, p.Value, p.depth) + return errorStr +} + +// MaybeResolveColor wraps resolveColor and silences possible errors, returning +// Transparent color by default, as a Block does not know how to handle color errors. +func (p Palette) MaybeResolveColor(colorName Ansi) Ansi { + color, err := p.ResolveColor(colorName) + if err != nil { + return "" + } + + return color +} diff --git a/src/color/palette_test.go b/src/color/palette_test.go new file mode 100644 index 000000000000..72415ba79680 --- /dev/null +++ b/src/color/palette_test.go @@ -0,0 +1,233 @@ +package color + +import ( + "testing" + + "github.com/alecthomas/assert" +) + +var ( + testPalette = Palette{ + "red": "#FF0000", + "green": "#00FF00", + "blue": "#0000FF", + "white": "#FFFFFF", + "black": "#000000", + } +) + +type TestPaletteRequest struct { + Case string + Request Ansi + Expected Ansi + ExpectedError bool +} + +func TestPaletteShouldResolveColorFromTestPalette(t *testing.T) { + cases := []TestPaletteRequest{ + {Case: "Palette red", Request: "p:red", Expected: "#FF0000"}, + {Case: "Palette green", Request: "p:green", Expected: "#00FF00"}, + {Case: "Palette blue", Request: "p:blue", Expected: "#0000FF"}, + {Case: "Palette white", Request: "p:white", Expected: "#FFFFFF"}, + {Case: "Palette black", Request: "p:black", Expected: "#000000"}, + } + + for _, tc := range cases { + testPaletteRequest(t, tc) + } +} + +func testPaletteRequest(t *testing.T, tc TestPaletteRequest) { + actual, err := testPalette.ResolveColor(tc.Request) + + if !tc.ExpectedError { + assert.Nil(t, err, tc.Case) + assert.Equal(t, tc.Expected, actual, "expected different color value") + } else { + assert.NotNil(t, err, tc.Case) + assert.Equal(t, string(tc.Expected), err.Error()) + } +} + +func TestPaletteShouldIgnoreNonPaletteColors(t *testing.T) { + cases := []TestPaletteRequest{ + {Case: "Deep puprple", Request: "#1F1137", Expected: "#1F1137"}, + {Case: "Light red", Request: "#D55252", Expected: "#D55252"}, + {Case: "ANSI black", Request: "black", Expected: "black"}, + {Case: "Foreground", Request: "foreground", Expected: "foreground"}, + } + + for _, tc := range cases { + testPaletteRequest(t, tc) + } +} + +func TestPaletteShouldReturnErrorOnMissingColor(t *testing.T) { + cases := []TestPaletteRequest{ + { + Case: "Palette deep purple", + Request: "p:deep-purple", + ExpectedError: true, + Expected: "palette: requested color deep-purple does not exist in palette of colors black,blue,green,red,white", + }, + { + Case: "Palette cyan", + Request: "p:cyan", + ExpectedError: true, + Expected: "palette: requested color cyan does not exist in palette of colors black,blue,green,red,white", + }, + { + Case: "Palette foreground", + Request: "p:foreground", + ExpectedError: true, + Expected: "palette: requested color foreground does not exist in palette of colors black,blue,green,red,white", + }, + } + + for _, tc := range cases { + testPaletteRequest(t, tc) + } +} + +func TestPaletteShouldHandleMixedCases(t *testing.T) { + cases := []TestPaletteRequest{ + {Case: "Palette red", Request: "p:red", Expected: "#FF0000"}, + {Case: "ANSI black", Request: "black", Expected: "black"}, + {Case: "Cyan", Request: "#05E6FA", Expected: "#05E6FA"}, + {Case: "Palette black", Request: "p:black", Expected: "#000000"}, + {Case: "Palette pink", Request: "p:pink", ExpectedError: true, Expected: "palette: requested color pink does not exist in palette of colors black,blue,green,red,white"}, + } + + for _, tc := range cases { + testPaletteRequest(t, tc) + } +} + +func TestPaletteShouldUseEmptyColorByDefault(t *testing.T) { + cases := []TestPaletteRequest{ + {Case: "Palette magenta", Request: "p:magenta", Expected: ""}, + {Case: "Palette gray", Request: "p:gray", Expected: ""}, + {Case: "Palette rose", Request: "p:rose", Expected: ""}, + } + + for _, tc := range cases { + actual := testPalette.MaybeResolveColor(tc.Request) + + assert.Equal(t, tc.Expected, actual, "expected different color value") + } +} + +func TestPaletteShouldResolveRecursiveReference(t *testing.T) { + tp := Palette{ + "light-blue": "#CAF0F8", + "dark-blue": "#023E8A", + "foreground": "p:light-blue", + "background": "p:dark-blue", + "text": "p:foreground", + "icon": "p:background", + "void": "p:void", // infinite recursion - error + "1": "white", + "2": "p:1", + "3": "p:2", + "4": "p:3", // 3 recursive lookups - allowed + "5": "p:4", // 4 recursive lookups - error + } + + cases := []TestPaletteRequest{ + { + Case: "Palette light-blue", + Request: "p:light-blue", + Expected: "#CAF0F8", + }, + { + Case: "Palette foreground", + Request: "p:foreground", + Expected: "#CAF0F8", + }, + { + Case: "Palette background", + Request: "p:background", + Expected: "#023E8A", + }, + { + Case: "Palette text (2 recursive lookups)", + Request: "p:text", + Expected: "#CAF0F8", + }, + { + Case: "Palette icon (2 recursive lookups)", + Request: "p:icon", + Expected: "#023E8A", + }, + { + Case: "Palette void (infinite recursion)", + Request: "p:void", + ExpectedError: true, + Expected: "palette: recursive resolution of color p:void returned palette reference p:void and reached recursion depth 4", + }, + { + Case: "Palette p:4 (3 recursive lookups)", + Request: "p:4", + Expected: "white", + }, + { + Case: "Palette p:5 (4 recursive lookups)", + Request: "p:5", + ExpectedError: true, + Expected: "palette: recursive resolution of color p:5 returned palette reference p:1 and reached recursion depth 4", + }, + } + + for _, tc := range cases { + actual, err := tp.ResolveColor(tc.Request) + + if !tc.ExpectedError { + assert.Nil(t, err, "expected no error") + assert.Equal(t, tc.Expected, actual, "expected different color value") + } else { + assert.NotNil(t, err, "expected error") + assert.Equal(t, string(tc.Expected), err.Error()) + } + } +} + +func TestPaletteShouldHandleEmptyKey(t *testing.T) { + tp := Palette{ + "": "#000000", + } + + actual, err := tp.ResolveColor("p:") + + assert.Nil(t, err, "expected no error") + assert.Equal(t, Ansi("#000000"), actual, "expected different color value") +} + +func BenchmarkPaletteMixedCaseResolution(b *testing.B) { + for b.Loop() { + benchmarkPaletteMixedCaseResolution() + } +} + +func benchmarkPaletteMixedCaseResolution() { + cases := []TestPaletteRequest{ + {Case: "Palette red", Request: "p:red", Expected: "#FF0000"}, + {Case: "ANSI black", Request: "black", Expected: "black"}, + {Case: "Cyan", Request: "#05E6FA", Expected: "#05E6FA"}, + {Case: "Palette black", Request: "p:black", Expected: "#000000"}, + {Case: "Palette pink", Request: "p:pink", ExpectedError: true, Expected: "palette: requested color pink does not exist in palette of colors black,blue,green,red,white"}, + {Case: "Palette blue", Request: "p:blue", Expected: "#0000FF"}, + // repeating the same set to have longer benchmarks + {Case: "Palette red", Request: "p:red", Expected: "#FF0000"}, + {Case: "ANSI black", Request: "black", Expected: "black"}, + {Case: "Cyan", Request: "#05E6FA", Expected: "#05E6FA"}, + {Case: "Palette black", Request: "p:black", Expected: "#000000"}, + {Case: "Palette pink", Request: "p:pink", ExpectedError: true, Expected: "palette: requested color pink does not exist in palette of colors black,blue,green,red,white"}, + {Case: "Palette blue", Request: "p:blue", Expected: "#0000FF"}, + } + + for _, tc := range cases { + // both value and error values are irrelevant, but such assignment calms down + // golangci-lint "return value of `testPalette.ResolveColor` is not checked" error + _, _ = testPalette.ResolveColor(tc.Request) + } +} diff --git a/src/color/palettes.go b/src/color/palettes.go new file mode 100644 index 000000000000..0ac012832d6a --- /dev/null +++ b/src/color/palettes.go @@ -0,0 +1,6 @@ +package color + +type Palettes struct { + List map[string]Palette `json:"list,omitempty" toml:"list,omitempty" yaml:"list,omitempty"` + Template string `json:"template,omitempty" toml:"template,omitempty" yaml:"template,omitempty"` +} diff --git a/src/config.go b/src/config.go deleted file mode 100644 index 212f9a3ce11b..000000000000 --- a/src/config.go +++ /dev/null @@ -1,255 +0,0 @@ -package main - -import ( - // "encoding/json" - - "bytes" - json2 "encoding/json" - "errors" - "fmt" - "os" - "strconv" - "strings" - - "github.com/gookit/config/v2" - "github.com/gookit/config/v2/json" - "github.com/gookit/config/v2/toml" - "github.com/gookit/config/v2/yaml" - "github.com/mitchellh/mapstructure" -) - -// Config holds all the theme for rendering the prompt -type Config struct { - FinalSpace bool `config:"final_space"` - OSC99 bool `config:"osc99"` - ConsoleTitle bool `config:"console_title"` - ConsoleTitleStyle ConsoleTitleStyle `config:"console_title_style"` - ConsoleTitleTemplate string `config:"console_title_template"` - TerminalBackground string `config:"terminal_background"` - Blocks []*Block `config:"blocks"` - Tooltips []*Segment `config:"tooltips"` -} - -const ( - // EnableHyperlink enable hyperlink - EnableHyperlink Property = "enable_hyperlink" -) - -func printConfigError(err error) { - fmt.Println("Oh My Posh Error:\n", err.Error()) -} - -// GetConfig returns the default configuration including possible user overrides -func GetConfig(env environmentInfo) *Config { - cfg, err := loadConfig(env) - if err != nil { - return getDefaultConfig(err.Error()) - } - return cfg -} - -func loadConfig(env environmentInfo) (*Config, error) { - var cfg Config - configFile := *env.getArgs().Config - if configFile == "" { - return nil, errors.New("NO CONFIG") - } - if _, err := os.Stat(configFile); os.IsNotExist(err) { - printConfigError(err) - return nil, errors.New("INVALID CONFIG PATH") - } - - config.AddDriver(yaml.Driver) - config.AddDriver(json.Driver) - config.AddDriver(toml.Driver) - config.WithOptions(func(opt *config.Options) { - opt.DecoderConfig = &mapstructure.DecoderConfig{ - TagName: "config", - } - }) - - err := config.LoadFiles(configFile) - if err != nil { - printConfigError(err) - return nil, errors.New("UNABLE TO OPEN CONFIG") - } - - err = config.BindStruct("", &cfg) - if err != nil { - printConfigError(err) - return nil, errors.New("INVALID CONFIG") - } - - return &cfg, nil -} - -func exportConfig(configFile, format string) string { - if len(format) == 0 { - format = config.JSON - } - - config.AddDriver(yaml.Driver) - config.AddDriver(json.Driver) - config.AddDriver(toml.Driver) - - err := config.LoadFiles(configFile) - if err != nil { - printConfigError(err) - return fmt.Sprintf("INVALID CONFIG:\n\n%s", err.Error()) - } - - schemaKey := "$schema" - if format == config.JSON && !config.Exists(schemaKey) { - data := config.Data() - data[schemaKey] = "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json" - config.SetData(data) - } - - buf := new(bytes.Buffer) - _, err = config.DumpTo(buf, format) - if err != nil { - printConfigError(err) - return "UNABLE TO DUMP CONFIG" - } - - switch format { - case config.JSON: - var prettyJSON bytes.Buffer - err := json2.Indent(&prettyJSON, buf.Bytes(), "", " ") - if err == nil { - unescapeUnicodeCharactersInJSON := func(rawJSON []byte) string { - str, err := strconv.Unquote(strings.ReplaceAll(strconv.Quote(string(rawJSON)), `\\u`, `\u`)) - if err != nil { - return err.Error() - } - return str - } - return unescapeUnicodeCharactersInJSON(prettyJSON.Bytes()) - } - case config.Yaml: - prefix := "# yaml-language-server: $schema=https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n" - content := buf.String() - return prefix + content - - case config.Toml: - prefix := "#:schema https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n" - content := buf.String() - return prefix + content - } - - return buf.String() -} - -func getDefaultConfig(info string) *Config { - cfg := &Config{ - FinalSpace: true, - ConsoleTitle: true, - ConsoleTitleStyle: FolderName, - Blocks: []*Block{ - { - Type: Prompt, - Alignment: Left, - Segments: []*Segment{ - { - Type: Session, - Style: Diamond, - Background: "#c386f1", - Foreground: "#ffffff", - LeadingDiamond: "\uE0B6", - TrailingDiamond: "\uE0B0", - }, - { - Type: Path, - Style: Powerline, - PowerlineSymbol: "\uE0B0", - Background: "#ff479c", - Foreground: "#ffffff", - Properties: map[Property]interface{}{ - Prefix: " \uE5FF ", - Style: "folder", - }, - }, - { - Type: Git, - Style: Powerline, - PowerlineSymbol: "\uE0B0", - Background: "#fffb38", - Foreground: "#193549", - Properties: map[Property]interface{}{ - DisplayStashCount: true, - DisplayUpstreamIcon: true, - }, - }, - { - Type: Battery, - Style: Powerline, - PowerlineSymbol: "\uE0B0", - Background: "#f36943", - Foreground: "#193549", - Properties: map[Property]interface{}{ - ColorBackground: true, - ChargedColor: "#4caf50", - ChargingColor: "#40c4ff", - DischargingColor: "#ff5722", - Postfix: "\uF295 ", - }, - }, - { - Type: Node, - Style: Powerline, - PowerlineSymbol: "\uE0B0", - Background: "#6CA35E", - Foreground: "#ffffff", - Properties: map[Property]interface{}{ - Prefix: " \uE718", - DisplayVersion: false, - }, - }, - { - Type: ShellInfo, - Style: Powerline, - PowerlineSymbol: "\uE0B0", - Background: "#0077c2", - Foreground: "#ffffff", - Properties: map[Property]interface{}{ - Prefix: " \uFCB5 ", - }, - }, - { - Type: Root, - Style: Powerline, - PowerlineSymbol: "\uE0B0", - Background: "#ffff66", - Foreground: "#ffffff", - }, - { - Type: Text, - Style: Powerline, - PowerlineSymbol: "\uE0B0", - Background: "#ffffff", - Foreground: "#111111", - Properties: map[Property]interface{}{ - TextProperty: info, - }, - }, - { - Type: Exit, - Style: Diamond, - Background: "#2e9599", - Foreground: "#ffffff", - LeadingDiamond: "\uE0B0", - TrailingDiamond: "\uE0B4", - Properties: map[Property]interface{}{ - DisplayExitCode: false, - AlwaysEnabled: true, - ErrorColor: "#f1184c", - ColorBackground: true, - Prefix: " \uE23A", - }, - }, - }, - }, - }, - } - return cfg -} diff --git a/src/config/backup.go b/src/config/backup.go new file mode 100644 index 000000000000..6862183d3a7c --- /dev/null +++ b/src/config/backup.go @@ -0,0 +1,94 @@ +package config + +import ( + "bytes" + "encoding/json" + "io" + "os" + "strings" + + toml "github.com/pelletier/go-toml/v2" + yaml "go.yaml.in/yaml/v3" +) + +func (cfg *Config) Backup() { + dst := cfg.Source + ".bak" + source, err := os.Open(cfg.Source) + if err != nil { + return + } + defer source.Close() + destination, err := os.Create(dst) + if err != nil { + return + } + defer destination.Close() + _, err = io.Copy(destination, source) + if err != nil { + return + } +} + +func (cfg *Config) Export(format string) string { + if len(format) != 0 { + cfg.Format = format + } + + var result bytes.Buffer + + switch cfg.Format { + case YAML: + prefix := "# yaml-language-server: $schema=https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n" + yamlEncoder := yaml.NewEncoder(&result) + + err := yamlEncoder.Encode(cfg) + if err != nil { + return "" + } + + return prefix + result.String() + case JSON: + jsonEncoder := json.NewEncoder(&result) + jsonEncoder.SetEscapeHTML(false) + jsonEncoder.SetIndent("", " ") + _ = jsonEncoder.Encode(cfg) + prefix := "{\n \"$schema\": \"https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\"," + data := strings.Replace(result.String(), "{", prefix, 1) + return EscapeGlyphs(data, cfg.MigrateGlyphs) + case TOML: + tomlEncoder := toml.NewEncoder(&result) + tomlEncoder.SetIndentTables(true) + + err := tomlEncoder.Encode(cfg) + if err != nil { + return "" + } + + return result.String() + } + + // unsupported format + return "" +} + +func (cfg *Config) Write(format string) { + content := cfg.Export(format) + if content == "" { + // we are unable to perform the export + return + } + + f, err := os.OpenFile(cfg.Source, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return + } + + defer func() { + _ = f.Close() + }() + + _, err = f.WriteString(content) + if err != nil { + return + } +} diff --git a/src/config/block.go b/src/config/block.go new file mode 100644 index 000000000000..6c8ccbdbab7c --- /dev/null +++ b/src/config/block.go @@ -0,0 +1,49 @@ +package config + +import "fmt" + +// BlockType type of block +type BlockType string + +// BlockAlignment alignment of a Block +type BlockAlignment string + +// Overflow defines how to handle a right block that overflows with the previous block +type Overflow string + +const ( + // Prompt writes one or more Segments + Prompt BlockType = "prompt" + // RPrompt is a right aligned prompt + RPrompt BlockType = "rprompt" + // Left aligns left + Left BlockAlignment = "left" + // Right aligns right + Right BlockAlignment = "right" + // Break adds a line break + Break Overflow = "break" + // Hide hides the block + Hide Overflow = "hide" +) + +// Block defines a part of the prompt with optional segments +type Block struct { + Type BlockType `json:"type,omitempty" toml:"type,omitempty" yaml:"type,omitempty"` + Alignment BlockAlignment `json:"alignment,omitempty" toml:"alignment,omitempty" yaml:"alignment,omitempty"` + Filler string `json:"filler,omitempty" toml:"filler,omitempty" yaml:"filler,omitempty"` + Overflow Overflow `json:"overflow,omitempty" toml:"overflow,omitempty" yaml:"overflow,omitempty"` + LeadingDiamond string `json:"leading_diamond,omitempty" toml:"leading_diamond,omitempty" yaml:"leading_diamond,omitempty"` + TrailingDiamond string `json:"trailing_diamond,omitempty" toml:"trailing_diamond,omitempty" yaml:"trailing_diamond,omitempty"` + Segments []*Segment `json:"segments,omitempty" toml:"segments,omitempty" yaml:"segments,omitempty"` + Newline bool `json:"newline,omitempty" toml:"newline,omitempty" yaml:"newline,omitempty"` + Force bool `json:"force,omitempty" toml:"force,omitempty" yaml:"force,omitempty"` + Index int `json:"index,omitempty" toml:"index,omitempty" yaml:"index,omitempty"` +} + +func (b *Block) key() any { + if b.Index > 0 { + return b.Index - 1 + } + + return fmt.Sprintf("%s-%s", b.Type, b.Alignment) +} diff --git a/src/config/cache.go b/src/config/cache.go new file mode 100644 index 000000000000..245b456f8ad1 --- /dev/null +++ b/src/config/cache.go @@ -0,0 +1,16 @@ +package config + +import "github.com/jandedobbeleer/oh-my-posh/src/cache" + +type Cache struct { + Duration cache.Duration `json:"duration,omitempty" toml:"duration,omitempty" yaml:"duration,omitempty"` + Strategy Strategy `json:"strategy,omitempty" toml:"strategy,omitempty" yaml:"strategy,omitempty"` +} + +type Strategy string + +const ( + Folder Strategy = "folder" + Session Strategy = "session" + Device Strategy = "device" +) diff --git a/src/config/config.go b/src/config/config.go new file mode 100644 index 000000000000..9cb4000a7190 --- /dev/null +++ b/src/config/config.go @@ -0,0 +1,277 @@ +package config + +import ( + "encoding/gob" + "slices" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/upgrade" + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/maps" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/segments" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +func init() { + gob.Register(&Config{}) +} + +const ( + JSON string = "json" + YAML string = "yaml" + TOML string = "toml" + + TML string = "tml" + YML string = "yml" + JSONC string = "jsonc" + + AUTOUPGRADE = "upgrade" + UPGRADENOTICE = "notice" + RELOAD = "reload" + + Version = 4 +) + +type Action string + +func (a Action) IsDefault() bool { + return a != Prepend && a != Extend +} + +const ( + Prepend Action = "prepend" + Extend Action = "extend" +) + +// Config holds all the theme for rendering the prompt +type Config struct { + Palette color.Palette `json:"palette,omitempty" toml:"palette,omitempty" yaml:"palette,omitempty"` + DebugPrompt *Segment `json:"debug_prompt,omitempty" toml:"debug_prompt,omitempty" yaml:"debug_prompt,omitempty"` + Var map[string]any `json:"var,omitempty" toml:"var,omitempty" yaml:"var,omitempty"` + Palettes *color.Palettes `json:"palettes,omitempty" toml:"palettes,omitempty" yaml:"palettes,omitempty"` + ValidLine *Segment `json:"valid_line,omitempty" toml:"valid_line,omitempty" yaml:"valid_line,omitempty"` + SecondaryPrompt *Segment `json:"secondary_prompt,omitempty" toml:"secondary_prompt,omitempty" yaml:"secondary_prompt,omitempty"` + TransientPrompt *Segment `json:"transient_prompt,omitempty" toml:"transient_prompt,omitempty" yaml:"transient_prompt,omitempty"` + ErrorLine *Segment `json:"error_line,omitempty" toml:"error_line,omitempty" yaml:"error_line,omitempty"` + Maps *maps.Config `json:"maps,omitempty" toml:"maps,omitempty" yaml:"maps,omitempty"` + Upgrade *upgrade.Config `json:"upgrade,omitempty" toml:"upgrade,omitempty" yaml:"upgrade,omitempty"` + Extends string `json:"extends,omitempty" toml:"extends,omitempty" yaml:"extends,omitempty"` + AccentColor color.Ansi `json:"accent_color,omitempty" toml:"accent_color,omitempty" yaml:"accent_color,omitempty"` + ConsoleTitleTemplate string `json:"console_title_template,omitempty" toml:"console_title_template,omitempty" yaml:"console_title_template,omitempty"` + PWD string `json:"pwd,omitempty" toml:"pwd,omitempty" yaml:"pwd,omitempty"` + Source string `json:"-" toml:"-" yaml:"-"` + Format string `json:"-" toml:"-" yaml:"-"` + TerminalBackground color.Ansi `json:"terminal_background,omitempty" toml:"terminal_background,omitempty" yaml:"terminal_background,omitempty"` + ToolTipsAction Action `json:"tooltips_action,omitempty" toml:"tooltips_action,omitempty" yaml:"tooltips_action,omitempty"` + Blocks []*Block `json:"blocks,omitempty" toml:"blocks,omitempty" yaml:"blocks,omitempty"` + Cycle color.Cycle `json:"cycle,omitempty" toml:"cycle,omitempty" yaml:"cycle,omitempty"` + ITermFeatures terminal.ITermFeatures `json:"iterm_features,omitempty" toml:"iterm_features,omitempty" yaml:"iterm_features,omitempty"` + Tooltips []*Segment `json:"tooltips,omitempty" toml:"tooltips,omitempty" yaml:"tooltips,omitempty"` + hash uint64 + Version int `json:"version" toml:"version" yaml:"version"` + MigrateGlyphs bool `json:"-" toml:"-" yaml:"-"` + Async bool `json:"async,omitempty" toml:"async,omitempty" yaml:"async,omitempty"` + ShellIntegration bool `json:"shell_integration,omitempty" toml:"shell_integration,omitempty" yaml:"shell_integration,omitempty"` + FinalSpace bool `json:"final_space,omitempty" toml:"final_space,omitempty" yaml:"final_space,omitempty"` + UpgradeNotice bool `json:"-" toml:"-" yaml:"-"` + extended bool + PatchPwshBleed bool `json:"patch_pwsh_bleed,omitempty" toml:"patch_pwsh_bleed,omitempty" yaml:"patch_pwsh_bleed,omitempty"` + AutoUpgrade bool `json:"-" toml:"-" yaml:"-"` + EnableCursorPositioning bool `json:"enable_cursor_positioning,omitempty" toml:"enable_cursor_positioning,omitempty" yaml:"enable_cursor_positioning,omitempty"` + Streaming int `json:"streaming,omitempty" toml:"streaming,omitempty" yaml:"streaming,omitempty"` +} + +func (cfg *Config) MakeColors(env runtime.Environment) color.String { + cacheDisabled := env.Getenv("OMP_CACHE_DISABLED") == "1" + return color.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, env) +} + +func (cfg *Config) getPalette() color.Palette { + if cfg.Palettes == nil { + return cfg.Palette + } + + key, err := template.Render(cfg.Palettes.Template, nil) + if err != nil { + return cfg.Palette + } + + palette, ok := cfg.Palettes.List[key] + if !ok { + return cfg.Palette + } + + for key, color := range cfg.Palette { + if _, ok := palette[key]; ok { + continue + } + + palette[key] = color + } + + return palette +} + +func (cfg *Config) Features(env runtime.Environment) shell.Features { + var feats shell.Features + + asyncShells := []string{shell.BASH, shell.ZSH, shell.FISH, shell.PWSH} + + if cfg.Async && slices.Contains(asyncShells, env.Shell()) { + log.Debug("async enabled") + feats |= shell.Async + } + + if cfg.TransientPrompt != nil { + log.Debug("transient prompt enabled") + feats |= shell.Transient + } + + if cfg.Streaming > 0 { + log.Debug("streaming enabled") + feats |= shell.Streaming + } + + if feats&(shell.Streaming|shell.Transient) != 0 { + feats |= shell.KeyHandlers + } + + unsupportedShells := []string{shell.ELVISH, shell.XONSH} + if slices.Contains(unsupportedShells, env.Shell()) { + cfg.ShellIntegration = false + } + + if cfg.ShellIntegration { + log.Debug("shell integration enabled") + feats |= shell.FTCSMarks + // PowerShell emits FTCS_COMMAND_EXECUTED (OSC 133;C) inside the Enter key handler, + // so KeyHandlers must be enabled whenever shell integration is active. + if env.Shell() == shell.PWSH { + feats |= shell.KeyHandlers + } + } + + // do not enable upgrade features when async is enabled + if feats&shell.Async == 0 { + feats |= cfg.upgradeFeatures() + } + + if cfg.ErrorLine != nil || cfg.ValidLine != nil { + log.Debug("error or valid line enabled") + feats |= shell.LineError + } + + if len(cfg.Tooltips) > 0 { + log.Debug("tooltips enabled") + feats |= shell.Tooltips + } + + if env.Shell() == shell.FISH && cfg.ITermFeatures != nil && cfg.ITermFeatures.Contains(terminal.PromptMark) { + log.Debug("prompt mark enabled") + feats |= shell.PromptMark + } + + for i, block := range cfg.Blocks { + if (i == 0 && block.Newline) && cfg.EnableCursorPositioning { + log.Debug("cursor positioning enabled") + feats |= shell.CursorPositioning + } + + if block.Type == RPrompt { + log.Debug("rprompt enabled") + feats |= shell.RPrompt + } + + for _, segment := range block.Segments { + if segment.Type == AZ { + source := segment.Options.String(segments.Source, segments.FirstMatch) + if strings.Contains(source, segments.Pwsh) { + log.Debug("azure enabled") + feats |= shell.Azure + } + } + + if segment.Type == GIT { + source := segment.Options.String(segments.Source, segments.Cli) + if source == segments.Pwsh { + log.Debug("posh-git enabled") + feats |= shell.PoshGit + } + } + } + } + + return feats +} + +func (cfg *Config) upgradeFeatures() shell.Features { + var feats shell.Features + + autoUpgrade := cfg.Upgrade.Auto + if val, OK := cache.Get[bool](cache.Device, AUTOUPGRADE); OK { + log.Debug("auto upgrade key found, overriding config") + autoUpgrade = val + } + + upgradeNotice := cfg.Upgrade.DisplayNotice + if val, OK := cache.Get[bool](cache.Device, UPGRADENOTICE); OK { + log.Debug("upgrade notice key found, overriding config") + upgradeNotice = val + } + + if upgradeNotice && !autoUpgrade { + log.Debug("notice enabled, no auto upgrade") + feats |= shell.Notice + } + + if autoUpgrade { + log.Debug("auto upgrade enabled") + feats |= shell.Upgrade + } + + return feats +} + +func (cfg *Config) Hash() uint64 { + return cfg.hash +} + +// migrateSegmentProperties migrates the deprecated Properties field to Options for all segments. +// This is needed for TOML configs since go-toml/v2 doesn't support custom unmarshalers. +func (cfg *Config) migrateSegmentProperties() { + for _, block := range cfg.Blocks { + for _, segment := range block.Segments { + segment.MigratePropertiesToOptions() + } + } +} + +// toggleSegments processes all segments in all blocks and adds segments +// with Toggled == true to the toggle cache, effectively toggling them off. +func (cfg *Config) toggleSegments() { + currentToggleSet, _ := cache.Get[map[string]bool](cache.Session, cache.TOGGLECACHE) + if currentToggleSet == nil { + currentToggleSet = make(map[string]bool) + } + + for _, block := range cfg.Blocks { + for _, segment := range block.Segments { + if segment.Toggled { + segmentName := segment.Alias + if segmentName == "" { + segmentName = string(segment.Type) + } + + currentToggleSet[segmentName] = true + } + } + } + + // Update cache with the map directly + cache.Set(cache.Session, cache.TOGGLECACHE, currentToggleSet, cache.INFINITE) +} diff --git a/src/config/config_test.go b/src/config/config_test.go new file mode 100644 index 000000000000..a24cfa9d38ef --- /dev/null +++ b/src/config/config_test.go @@ -0,0 +1,239 @@ +package config + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/upgrade" + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + + "github.com/stretchr/testify/assert" +) + +func TestGetPalette(t *testing.T) { + palette := color.Palette{ + "red": "#ff0000", + "blue": "#0000ff", + } + + cases := []struct { + Palettes *color.Palettes + Palette color.Palette + ExpectedPalette color.Palette + Case string + }{ + { + Case: "match", + Palettes: &color.Palettes{ + Template: "{{ .Shell }}", + List: map[string]color.Palette{ + "bash": palette, + "zsh": { + "red": "#ff0001", + "blue": "#0000fb", + }, + }, + }, + ExpectedPalette: palette, + }, + { + Case: "no match, no fallback", + Palettes: &color.Palettes{ + Template: "{{ .Shell }}", + List: map[string]color.Palette{ + "fish": palette, + "zsh": { + "red": "#ff0001", + "blue": "#0000fb", + }, + }, + }, + ExpectedPalette: nil, + }, + { + Case: "no match, default", + Palettes: &color.Palettes{ + Template: "{{ .Shell }}", + List: map[string]color.Palette{ + "zsh": { + "red": "#ff0001", + "blue": "#0000fb", + }, + }, + }, + Palette: palette, + ExpectedPalette: palette, + }, + { + Case: "no palettes", + ExpectedPalette: nil, + }, + { + Case: "match, with override", + Palettes: &color.Palettes{ + Template: "{{ .Shell }}", + List: map[string]color.Palette{ + "bash": { + "red": "#ff0001", + "yellow": "#ffff00", + }, + }, + }, + Palette: palette, + ExpectedPalette: color.Palette{ + "red": "#ff0001", + "blue": "#0000ff", + "yellow": "#ffff00", + }, + }, + } + + for _, tc := range cases { + env := &mock.Environment{} + env.On("Shell").Return("bash") + + template.Cache = &cache.Template{ + SimpleTemplate: cache.SimpleTemplate{ + Shell: "bash", + }, + } + template.Init(env, nil, nil) + + cfg := &Config{ + Palette: tc.Palette, + Palettes: tc.Palettes, + } + + got := cfg.getPalette() + assert.Equal(t, tc.ExpectedPalette, got, tc.Case) + } +} +func TestFeaturesShellIntegration(t *testing.T) { + cases := []struct { + Case string + Shell string + ShellIntegration bool + ExpectedFeats shell.Features + }{ + { + Case: "pwsh with shell integration enables FTCSMarks and KeyHandlers", + Shell: shell.PWSH, + ShellIntegration: true, + ExpectedFeats: shell.FTCSMarks | shell.KeyHandlers, + }, + { + Case: "bash with shell integration enables FTCSMarks only", + Shell: shell.BASH, + ShellIntegration: true, + ExpectedFeats: shell.FTCSMarks, + }, + { + Case: "zsh with shell integration enables FTCSMarks only", + Shell: shell.ZSH, + ShellIntegration: true, + ExpectedFeats: shell.FTCSMarks, + }, + { + Case: "pwsh without shell integration enables nothing", + Shell: shell.PWSH, + ShellIntegration: false, + ExpectedFeats: 0, + }, + } + + for _, tc := range cases { + env := &mock.Environment{} + env.On("Shell").Return(tc.Shell) + + template.Cache = &cache.Template{ + SimpleTemplate: cache.SimpleTemplate{ + Shell: tc.Shell, + }, + } + template.Init(env, nil, nil) + + cfg := &Config{ + ShellIntegration: tc.ShellIntegration, + Upgrade: &upgrade.Config{}, + } + + got := cfg.Features(env) + assert.Equal(t, tc.ExpectedFeats, got, tc.Case) + } +} + +func TestUpgradeFeatures(t *testing.T) { + cases := []struct { + Case string + ExpectedFeats shell.Features + UpgradeCacheKeyExists bool + AutoUpgrade bool + Force bool + DisplayNotice bool + AutoUpgradeKey bool + NoticeKey bool + }{ + { + Case: "cache exists, no force", + UpgradeCacheKeyExists: true, + ExpectedFeats: 0, + }, + { + Case: "auto upgrade enabled", + AutoUpgrade: true, + ExpectedFeats: shell.Upgrade, + }, + { + Case: "auto upgrade via cache", + AutoUpgradeKey: true, + ExpectedFeats: shell.Upgrade, + }, + { + Case: "notice enabled, no auto upgrade", + DisplayNotice: true, + ExpectedFeats: shell.Notice, + }, + { + Case: "notice via cache, no auto upgrade", + NoticeKey: true, + ExpectedFeats: shell.Notice, + }, + { + Case: "force upgrade ignores cache", + UpgradeCacheKeyExists: true, + Force: true, + AutoUpgrade: true, + ExpectedFeats: shell.Upgrade, + }, + } + + for _, tc := range cases { + if tc.UpgradeCacheKeyExists { + cache.Set(cache.Device, upgrade.CACHEKEY, "", cache.INFINITE) + } + + if tc.AutoUpgradeKey { + cache.Set(cache.Device, AUTOUPGRADE, true, cache.INFINITE) + } + + if tc.NoticeKey { + cache.Set(cache.Device, UPGRADENOTICE, true, cache.INFINITE) + } + + cfg := &Config{ + Upgrade: &upgrade.Config{ + Auto: tc.AutoUpgrade, + Force: tc.Force, + DisplayNotice: tc.DisplayNotice, + }, + } + + got := cfg.upgradeFeatures() + assert.Equal(t, tc.ExpectedFeats, got, tc.Case) + + cache.DeleteAll(cache.Device) + } +} diff --git a/src/config/default.go b/src/config/default.go new file mode 100644 index 000000000000..3dd774c230eb --- /dev/null +++ b/src/config/default.go @@ -0,0 +1,294 @@ +package config + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/upgrade" + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/segments" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +const ( + paletteBlack = "p:black" + paletteBlue = "p:blue" + paletteGreen = "p:green" + paletteOrange = "p:orange" + paletteWhite = "p:white" + paletteYellow = "p:yellow" + backgroundTransparent = "transparent" +) + +func Default(configError error) *Config { + exitBackgroundTemplate := "{{ if gt .Code 0 }}p:red{{ end }}" + exitTemplate := " {{ if gt .Code 0 }}\uf00d{{ else }}\uf00c{{ end }} " + + if configError != nil && configError != ErrNoConfig { + exitBackgroundTemplate = "p:red" + exitTemplate = configError.Error() + } + + cfg := &Config{ + hash: 1234567890, // placeholder hash value + Version: 4, + FinalSpace: true, + Blocks: []*Block{ + { + Type: Prompt, + Alignment: Left, + Segments: []*Segment{ + { + Type: SESSION, + Style: Diamond, + LeadingDiamond: "\ue0b6", + TrailingDiamond: "\ue0b0", + Foreground: paletteBlack, + Background: paletteYellow, + Template: " {{ if .SSHSession }}\ueba9 {{ end }}{{ .UserName }} ", + }, + { + Type: PATH, + Style: Powerline, + PowerlineSymbol: "\ue0b0", + Foreground: paletteWhite, + Background: paletteOrange, + Options: options.Map{ + options.Style: "folder", + }, + Template: " \uea83 {{ path .Path .Location }} ", + }, + { + Type: GIT, + Style: Powerline, + PowerlineSymbol: "\ue0b0", + Foreground: paletteBlack, + Background: paletteGreen, + BackgroundTemplates: []string{ + "{{ if or (.Working.Changed) (.Staging.Changed) }}p:yellow{{ end }}", + "{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:red{{ end }}", + "{{ if gt .Ahead 0 }}#49416D{{ end }}", + "{{ if gt .Behind 0 }}#7A306C{{ end }}", + }, + ForegroundTemplates: []string{ + "{{ if or (.Working.Changed) (.Staging.Changed) }}p:black{{ end }}", + "{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:white{{ end }}", + "{{ if gt .Ahead 0 }}p:white{{ end }}", + }, + Options: options.Map{ + segments.BranchTemplate: "{{ trunc 25 .Branch }}", + segments.FetchStatus: true, + segments.FetchUpstreamIcon: true, + }, + Template: " {{ if .UpstreamURL }}{{ url .UpstreamIcon .UpstreamURL }} {{ end }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }} ", //nolint:lll + }, + { + Type: ROOT, + Style: Powerline, + PowerlineSymbol: "\ue0b0", + Foreground: paletteWhite, + Background: paletteYellow, + Template: " \uf0e7 ", + }, + { + Type: STATUS, + Style: Diamond, + LeadingDiamond: "\ue0b0", + TrailingDiamond: "\ue0b4", + Foreground: paletteWhite, + Background: paletteBlue, + BackgroundTemplates: []string{ + exitBackgroundTemplate, + }, + Options: options.Map{ + options.AlwaysEnabled: true, + }, + Template: exitTemplate, + }, + }, + }, + { + Type: RPrompt, + Segments: []*Segment{ + { + Type: NODE, + Style: Plain, + Foreground: paletteGreen, + Background: backgroundTransparent, + Template: "\ue718 ", + Options: options.Map{ + segments.HomeEnabled: false, + segments.FetchPackageManager: false, + segments.DisplayMode: "files", + }, + }, + { + Type: GOLANG, + Style: Plain, + Foreground: paletteBlue, + Background: backgroundTransparent, + Template: "\ue626 ", + Options: options.Map{ + options.FetchVersion: false, + }, + }, + { + Type: PYTHON, + Style: Plain, + Foreground: paletteYellow, + Background: backgroundTransparent, + Template: "\ue235 ", + Options: options.Map{ + options.FetchVersion: false, + segments.DisplayMode: "files", + segments.FetchVirtualEnv: false, + }, + }, + { + Type: SHELL, + Style: Plain, + Foreground: paletteWhite, + Background: backgroundTransparent, + Template: "in {{ .Name }} ", + }, + { + Type: TIME, + Style: Plain, + Foreground: paletteWhite, + Background: backgroundTransparent, + Template: "at {{ .CurrentDate | date \"15:04:05\" }}", + }, + }, + }, + }, + ConsoleTitleTemplate: "{{ .Shell }} in {{ .Folder }}", + Palette: color.Palette{ + "black": "#262B44", + "blue": "#4B95E9", + "green": "#59C9A5", + "orange": "#F07623", + "red": "#D81E5B", + "white": "#E0DEF4", + "yellow": "#F3AE35", + }, + SecondaryPrompt: &Segment{ + Foreground: paletteBlack, + Background: backgroundTransparent, + Template: "\ue0b6<,p:yellow> > \ue0b0 ", + }, + TransientPrompt: &Segment{ + Foreground: paletteBlack, + Background: backgroundTransparent, + Template: "\ue0b6<,p:yellow> {{ .Folder }} \ue0b0 ", + }, + Tooltips: []*Segment{ + { + Type: AWS, + Style: Diamond, + LeadingDiamond: "\ue0b0", + TrailingDiamond: "\ue0b4", + Foreground: paletteWhite, + Background: paletteOrange, + Template: " \ue7ad {{ .Profile }}{{ if .Region }}@{{ .Region }}{{ end }} ", + Options: options.Map{ + options.DisplayDefault: true, + }, + Tips: []string{"aws"}, + }, + { + Type: AZ, + Style: Diamond, + LeadingDiamond: "\ue0b0", + TrailingDiamond: "\ue0b4", + Foreground: paletteWhite, + Background: paletteBlue, + Template: " \uebd8 {{ .Name }} ", + Options: options.Map{ + options.DisplayDefault: true, + }, + Tips: []string{"az"}, + }, + }, + Upgrade: &upgrade.Config{ + Source: upgrade.CDN, + Interval: cache.ONEWEEK, + }, + } + + return cfg +} + +func Claude() *Config { + cfg := &Config{ + hash: 1234567890, // placeholder hash value + Version: 4, + Blocks: []*Block{ + { + Type: Prompt, + Alignment: Left, + Segments: []*Segment{ + { + Type: PATH, + Style: Diamond, + LeadingDiamond: "\ue0b6", + Foreground: paletteWhite, + Background: paletteOrange, + Options: options.Map{ + segments.DirLength: 3, + segments.FolderSeparatorIcon: "\ue0bb", + options.Style: "fish", + }, + Template: "{{ if .Segments.Git.Dir }} \uf1d2 {{ .Segments.Git.RepoName }}{{ if .Segments.Git.IsWorkTree }} \ue21c{{ end }}{{ $rel := .Segments.Git.RelativeDir }}{{ if $rel }} \ueaf7 {{ .Format $rel }}{{ end }}{{ else }} \uea83 {{ path .Path .Location }}{{ end }} ", //nolint:lll + }, + { + Type: GIT, + Style: Diamond, + LeadingDiamond: "\ue0b0", + TrailingDiamond: "\ue0b4", + Foreground: paletteBlack, + Background: paletteGreen, + BackgroundTemplates: []string{ + "{{ if or (.Working.Changed) (.Staging.Changed) }}p:yellow{{ end }}", + "{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:red{{ end }}", + "{{ if gt .Ahead 0 }}#49416D{{ end }}", + "{{ if gt .Behind 0 }}#7A306C{{ end }}", + }, + ForegroundTemplates: []string{ + "{{ if or (.Working.Changed) (.Staging.Changed) }}p:black{{ end }}", + "{{ if or (gt .Ahead 0) (gt .Behind 0) }}p:white{{ end }}", + }, + Options: options.Map{ + segments.FetchStatus: true, + segments.FetchUpstreamIcon: false, + }, + Template: " {{ if .UpstreamURL }}{{ url .UpstreamIcon .UpstreamURL }} {{ end }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ nospace .Working.String }}{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }} ", //nolint:lll + }, + }, + }, + { + Type: Prompt, + Alignment: Right, + Segments: []*Segment{ + { + Type: CLAUDE, + Style: Diamond, + LeadingDiamond: "\ue0b6", + TrailingDiamond: "\ue0b4", + Foreground: paletteBlack, + Background: paletteBlue, + Template: " \U000f0bc9 {{ .Model.DisplayName }} \uf2d0 {{ .TokenUsagePercent.Gauge }} ", + }, + }, + }, + }, + Palette: color.Palette{ + "black": "#262B44", + "blue": "#4B95E9", + "green": "#59C9A5", + "orange": "#F07623", + "red": "#D81E5B", + "white": "#E0DEF4", + "yellow": "#F3AE35", + }, + } + + return cfg +} diff --git a/src/config/dsc.go b/src/config/dsc.go new file mode 100644 index 000000000000..1bda019e8b35 --- /dev/null +++ b/src/config/dsc.go @@ -0,0 +1,134 @@ +package config + +import ( + "encoding/gob" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/dsc" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" +) + +func init() { + gob.Register([]*Configuration{}) +} + +type Resource struct { + dsc.Resource[*Configuration] +} + +func DSC() *Resource { + return &Resource{ + Resource: dsc.Resource[*Configuration]{}, + } +} + +type Configuration struct { + Format string `json:"format,omitempty" jsonschema:"title=Format,description=The format of the configuration file,enum=json,enum=jsonc,enum=yaml,enum=yml,enum=toml,enum=tml"` + Source string `json:"source,omitempty" jsonschema:"title=Source,description=The source of the configuration file"` + Config + resolved bool `json:"-"` +} + +func (s *Resource) Add(configPath string) { + if configPath == "" || strings.HasPrefix(configPath, "http") { + log.Debug("local configuration not provided or remote configuration, skipping") + return + } + + // replace $HOME with tilde as we can't guarantee the home path + configPath = filepath.Clean(configPath) + configPath = strings.ReplaceAll(configPath, path.Home(), "~") + + s.Resource.Add(&Configuration{ + Source: configPath, + }) +} + +func (s *Resource) ToJSON() string { + output := s.Resource.ToJSON() + return EscapeGlyphs(output, false) +} + +func (c *Configuration) Apply() error { + if c == nil { + return nil + } + + formats := map[string][]string{ + JSON: {".json", ".jsonc"}, + YAML: {".yaml", ".yml"}, + TOML: {".toml", ".tml"}, + } + + if !slices.Contains(formats[c.Format], filepath.Ext(c.Source)) { + return fmt.Errorf("source file %s does not match format %s", c.Source, c.Format) + } + + log.Debug("Applying configuration %s", c.Source) + + // Expand tilde to home directory for file operations + filePath := strings.ReplaceAll(c.Source, "~", path.Home()) + + // Create directory if it doesn't exist + dir := filepath.Dir(filePath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + + data := c.Export(c.Format) + + // Write file + if err := os.WriteFile(filePath, []byte(data), 0644); err != nil { + return fmt.Errorf("failed to write configuration file %s: %w", filePath, err) + } + + log.Debug("Configuration written to %s", filePath) + return nil +} + +func (c *Configuration) Equal(config *Configuration) bool { + if config == nil { + return false + } + + return c.Source == config.Source +} + +func (c *Configuration) Resolve() (*Configuration, bool) { + log.Debug("Resolving configuration %s", c.Source) + + if c.resolved { + log.Debug("Configuration already resolved") + return c, true + } + + c.resolved = true + + // we use pwsh as that will never omit any feature + data := Load(c.Source) + if data == nil { + log.Debug("No configuration data found") + return nil, false + } + + c.Config = *data + c.Format = data.Format + + // Skip if no extends, http URL + if data.Extends == "" || strings.HasPrefix(data.Extends, "http") { + log.Debug("No extends found or remote configuration") + return c, false + } + + // Resolve the extends configuration + parent := &Configuration{ + Source: data.Extends, + } + + return parent, true +} diff --git a/src/config/gob.go b/src/config/gob.go new file mode 100644 index 000000000000..1d82e8b2a3f1 --- /dev/null +++ b/src/config/gob.go @@ -0,0 +1,85 @@ +package config + +import ( + "bytes" + "encoding/base64" + "encoding/gob" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +const ( + configKey = "CONFIG" + SourceKey = "CONFIG_SOURCE" +) + +func (cfg *Config) Store() { + defer log.Trace(time.Now()) + + cache.Set(cache.Session, SourceKey, cfg.Source, cache.INFINITE) + cache.Set(cache.Session, configKey, cfg.Base64(), cache.INFINITE) +} + +func Get(configFile string, reload bool) *Config { + defer log.Trace(time.Now()) + + if reload { + log.Debug("reload mode enabled") + if source, OK := cache.Get[string](cache.Session, SourceKey); OK { + cfg := Load(source) + cfg.Store() + return cfg + } + } + + base64String, found := cache.Get[string](cache.Session, configKey) + if !found { + log.Debug("no cached config found") + return Load(configFile) + } + + var cfg Config + if err := cfg.Restore(base64String); err != nil { + log.Debug("failed to restore config from cache") + return Load(configFile) + } + + return &cfg +} + +func (cfg *Config) Base64() string { + defer log.Trace(time.Now()) + + var buffer bytes.Buffer + encoder := gob.NewEncoder(&buffer) + err := encoder.Encode(cfg) + if err != nil { + log.Error(err) + return "" + } + + return base64.StdEncoding.EncodeToString(buffer.Bytes()) +} + +func (cfg *Config) Restore(base64String string) error { + defer log.Trace(time.Now()) + + data, err := base64.StdEncoding.DecodeString(base64String) + if err != nil { + log.Error(err) + return err + } + + var buffer bytes.Buffer + buffer.Write(data) + decoder := gob.NewDecoder(&buffer) + err = decoder.Decode(cfg) + if err != nil { + log.Error(err) + return err + } + + return nil +} diff --git a/src/config/load.go b/src/config/load.go new file mode 100644 index 000000000000..ea8717aa246b --- /dev/null +++ b/src/config/load.go @@ -0,0 +1,417 @@ +package config + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "hash/fnv" + "os" + "path/filepath" + runtimelib "runtime" + "strings" + "time" + + "github.com/gookit/goutil/jsonutil" + "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/upgrade" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" + + toml "github.com/pelletier/go-toml/v2" + yaml "go.yaml.in/yaml/v3" +) + +// Custom error types for config validation +type Error struct { + message string +} + +func (e Error) Error() string { + return fmt.Sprintf(" %s ", e.message) +} + +var ( + ErrFileNotFound = Error{"CONFIG NOT FOUND"} + ErrInvalidExtension = Error{"INVALID CONFIG EXTENSION"} + ErrInvalidTheme = Error{"INVALID CONFIG THEME"} + ErrURLFetch = Error{"CONFIG URL FETCH FAILED"} + ErrParse = Error{"CONFIG PARSE ERROR"} + ErrNoConfig = Error{"NO CONFIG"} +) + +func Load(configFile string) *Config { + defer log.Trace(time.Now()) + + cfg, err := Parse(configFile) + if err != nil { + cfg = Default(err) + } + + return cfg +} + +func resolveConfigLocation(config string) string { + defer log.Trace(time.Now()) + + if strings.HasPrefix(config, "https://") { + return config + } + + if url, OK := isTheme(config); OK { + log.Debug("theme detected, using theme file") + return url + } + + // Clean the config path so it works regardless of the OS + config = filepath.ToSlash(config) + + // Cygwin path always needs the full path as we're on Windows but not really. + // Doing filepath actions will convert it to a Windows path and break the init script. + if isCygwin() { + log.Debug("cygwin detected, using full path for config") + return config + } + + configFile := path.ReplaceTildePrefixWithHomeDir(config) + + abs, err := filepath.Abs(configFile) + if err != nil { + log.Error(err) + return filepath.Clean(configFile) + } + + return abs +} + +type hashWriter interface { + Write(p []byte) (n int, err error) +} + +func Parse(configFile string) (*Config, error) { + defer log.Trace(time.Now()) + + if configFile == "" { + log.Debug("no config file specified") + return nil, ErrNoConfig + } + + configFile = resolveConfigLocation(configFile) + + configDSC := DSC() + configDSC.Load() + configDSC.Add(configFile) + + defer configDSC.Save() + + h := fnv.New64a() + + cfg, err := read(configFile, h) + if err != nil { + log.Errorf("failed to read config: %s", configFile) + return nil, err + } + + parentFolder := filepath.Dir(configFile) + + for cfg.Extends != "" { + cfg.Extends = resolvePath(cfg.Extends, parentFolder) + base, err := read(cfg.Extends, h) + if err != nil { + log.Errorf("failed to read extended config: %s", cfg.Extends) + break + } + + configDSC.Add(cfg.Extends) + + err = base.merge(cfg) + if err != nil { + log.Error(err) + break + } + + cfg = base + } + + cfg.Source = configFile + cfg.hash = h.Sum64() + // Migrate segment properties to options for TOML configs + // (go-toml/v2 doesn't support custom unmarshalers) + cfg.migrateSegmentProperties() + + cfg.toggleSegments() + + if cfg.Upgrade == nil { + cfg.Upgrade = &upgrade.Config{ + Source: upgrade.CDN, + DisplayNotice: cfg.UpgradeNotice, + Auto: cfg.AutoUpgrade, + Interval: cache.ONEWEEK, + } + } + + if cfg.Upgrade.Interval.IsEmpty() { + cfg.Upgrade.Interval = cache.ONEWEEK + } + + return cfg, nil +} + +func resolvePath(configFile, parentFolder string) string { + if url, OK := isTheme(configFile); OK { + return url + } + + if strings.HasPrefix(configFile, "https://") { + return configFile + } + + configFile = path.ReplaceTildePrefixWithHomeDir(configFile) + + if filepath.IsAbs(configFile) { + return configFile + } + + return filepath.Join(parentFolder, configFile) +} + +func read(configFile string, h hashWriter) (*Config, error) { + defer log.Trace(time.Now()) + + if configFile == "" { + log.Debug("no config file specified, using default") + return Default(nil), nil + } + + var cfg Config + cfg.Source = configFile + cfg.Format = strings.TrimPrefix(filepath.Ext(configFile), ".") + + data, err := getData(configFile) + if err != nil { + // Determine the type of error + if strings.HasPrefix(configFile, "https://") { + log.Errorf("failed to fetch config from URL: %v", err) + return nil, ErrURLFetch + } + if errors.Is(err, os.ErrNotExist) { + log.Errorf("config file not found: %v", err) + return nil, ErrFileNotFound + } + log.Errorf("failed to read config: %v", err) + return nil, ErrFileNotFound + } + + var parseErr error + switch cfg.Format { + case YAML, YML: + cfg.Format = YAML + parseErr = yaml.Unmarshal(data, &cfg) + case JSONC, JSON: + cfg.Format = JSON + + str := jsonutil.StripComments(string(data)) + data = []byte(str) + + decoder := json.NewDecoder(bytes.NewReader(data)) + parseErr = decoder.Decode(&cfg) + case TOML, TML: + cfg.Format = TOML + parseErr = toml.Unmarshal(data, &cfg) + default: + log.Errorf("unsupported config file format: %s", cfg.Format) + return nil, ErrInvalidExtension + } + + if parseErr != nil { + log.Errorf("failed to parse config: %v", parseErr) + return nil, ErrParse + } + + _, err = h.Write(data) + if err != nil { + log.Error(err) + } + + return &cfg, nil +} + +func getData(configFile string) ([]byte, error) { + if !strings.HasPrefix(configFile, "https://") { + return os.ReadFile(configFile) + } + + return http.Download(configFile, true) +} + +// isCygwin checks if we're running in Cygwin environment +func isCygwin() bool { + return runtimelib.GOOS == "windows" && len(os.Getenv("OSTYPE")) > 0 +} + +func isTheme(config string) (string, bool) { + themes := map[string]string{ + "1_shell": "1_shell.omp.json", + "m365princess": "M365Princess.omp.json", + "agnoster": "agnoster.omp.json", + "agnoster.minimal": "agnoster.minimal.omp.json", + "agnosterplus": "agnosterplus.omp.json", + "aliens": "aliens.omp.json", + "amro": "amro.omp.json", + "atomic": "atomic.omp.json", + "atomicbit": "atomicBit.omp.json", + "avit": "avit.omp.json", + "blue-owl": "blue-owl.omp.json", + "blueish": "blueish.omp.json", + "bubbles": "bubbles.omp.json", + "bubblesextra": "bubblesextra.omp.json", + "bubblesline": "bubblesline.omp.json", + "capr4n": "capr4n.omp.json", + "catppuccin": "catppuccin.omp.json", + "catppuccin_frappe": "catppuccin_frappe.omp.json", + "catppuccin_latte": "catppuccin_latte.omp.json", + "catppuccin_macchiato": "catppuccin_macchiato.omp.json", + "catppuccin_mocha": "catppuccin_mocha.omp.json", + "cert": "cert.omp.json", + "chips": "chips.omp.json", + "cinnamon": "cinnamon.omp.json", + "clean-detailed": "clean-detailed.omp.json", + "cloud-context": "cloud-context.omp.json", + "cloud-native-azure": "cloud-native-azure.omp.json", + "cobalt2": "cobalt2.omp.json", + "craver": "craver.omp.json", + "darkblood": "darkblood.omp.json", + "devious-diamonds": "devious-diamonds.omp.yaml", + "di4am0nd": "di4am0nd.omp.json", + "dracula": "dracula.omp.json", + "easy-term": "easy-term.omp.json", + "emodipt": "emodipt.omp.json", + "emodipt-extend": "emodipt-extend.omp.json", + "fish": "fish.omp.json", + "free-ukraine": "free-ukraine.omp.json", + "froczh": "froczh.omp.json", + "glowsticks": "glowsticks.omp.yaml", + "gmay": "gmay.omp.json", + "grandpa-style": "grandpa-style.omp.json", + "gruvbox": "gruvbox.omp.json", + "half-life": "half-life.omp.json", + "honukai": "honukai.omp.json", + "hotstick.minimal": "hotstick.minimal.omp.json", + "hul10": "hul10.omp.json", + "hunk": "hunk.omp.json", + "huvix": "huvix.omp.json", + "if_tea": "if_tea.omp.json", + "illusi0n": "illusi0n.omp.json", + "iterm2": "iterm2.omp.json", + "jandedobbeleer": "jandedobbeleer.omp.json", + "jblab_2021": "jblab_2021.omp.json", + "jonnychipz": "jonnychipz.omp.json", + "json": "json.omp.json", + "jtracey93": "jtracey93.omp.json", + "jv_sitecorian": "jv_sitecorian.omp.json", + "kali": "kali.omp.json", + "kushal": "kushal.omp.json", + "lambda": "lambda.omp.json", + "lambdageneration": "lambdageneration.omp.json", + "larserikfinholt": "larserikfinholt.omp.json", + "lightgreen": "lightgreen.omp.json", + "marcduiker": "marcduiker.omp.json", + "markbull": "markbull.omp.json", + "material": "material.omp.json", + "microverse-power": "microverse-power.omp.json", + "mojada": "mojada.omp.json", + "montys": "montys.omp.json", + "mt": "mt.omp.json", + "multiverse-neon": "multiverse-neon.omp.json", + "negligible": "negligible.omp.json", + "neko": "neko.omp.json", + "night-owl": "night-owl.omp.json", + "nordtron": "nordtron.omp.json", + "nu4a": "nu4a.omp.json", + "onehalf.minimal": "onehalf.minimal.omp.json", + "paradox": "paradox.omp.json", + "pararussel": "pararussel.omp.json", + "patriksvensson": "patriksvensson.omp.json", + "peru": "peru.omp.json", + "pixelrobots": "pixelrobots.omp.json", + "plague": "plague.omp.json", + "poshmon": "poshmon.omp.json", + "powerlevel10k_classic": "powerlevel10k_classic.omp.json", + "powerlevel10k_lean": "powerlevel10k_lean.omp.json", + "powerlevel10k_modern": "powerlevel10k_modern.omp.json", + "powerlevel10k_rainbow": "powerlevel10k_rainbow.omp.json", + "powerline": "powerline.omp.json", + "probua.minimal": "probua.minimal.omp.json", + "pure": "pure.omp.json", + "quick-term": "quick-term.omp.json", + "remk": "remk.omp.json", + "robbyrussell": "robbyrussell.omp.json", + "rudolfs-dark": "rudolfs-dark.omp.json", + "rudolfs-light": "rudolfs-light.omp.json", + "sim-web": "sim-web.omp.json", + "slim": "slim.omp.json", + "slimfat": "slimfat.omp.json", + "smoothie": "smoothie.omp.json", + "sonicboom_dark": "sonicboom_dark.omp.json", + "sonicboom_light": "sonicboom_light.omp.json", + "sorin": "sorin.omp.json", + "space": "space.omp.json", + "spaceship": "spaceship.omp.json", + "star": "star.omp.json", + "stelbent-compact.minimal": "stelbent-compact.minimal.omp.json", + "stelbent.minimal": "stelbent.minimal.omp.json", + "takuya": "takuya.omp.json", + "the-unnamed": "the-unnamed.omp.json", + "thecyberden": "thecyberden.omp.json", + "tiwahu": "tiwahu.omp.json", + "tokyo": "tokyo.omp.json", + "tokyonight_storm": "tokyonight_storm.omp.json", + "tonybaloney": "tonybaloney.omp.json", + "uew": "uew.omp.json", + "unicorn": "unicorn.omp.json", + "velvet": "velvet.omp.json", + "wholespace": "wholespace.omp.json", + "wopian": "wopian.omp.json", + "xtoys": "xtoys.omp.json", + "ys": "ys.omp.json", + "zash": "zash.omp.json", + } + + themeFile, OK := themes[config] + if !OK { + log.Debug(config, "is not a theme") + return "", false + } + + log.Debug(config, "is a theme") + + if themeFilePath, err := getMSIXThemePath(themeFile); err == nil { + return themeFilePath, true + } + + log.Debug("building theme URL for:", themeFile) + url := fmt.Sprintf("https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/refs/tags/v%s/themes/%s", build.Version, themeFile) + return url, true +} + +func getMSIXThemePath(themeFile string) (string, error) { + log.Trace(time.Now(), themeFile) + + // For MSIX packages, the executable location is the package root + exePath, err := os.Executable() + if err != nil { + log.Error(err) + return "", err + } + + themeFilePath := filepath.Join(filepath.Dir(exePath), "themes", themeFile) + if _, err := os.Stat(themeFilePath); err != nil { + log.Error(err) + return "", err + } + + log.Debug("found theme in MSIX installation:", themeFilePath) + return themeFilePath, nil +} diff --git a/src/config/merge.go b/src/config/merge.go new file mode 100644 index 000000000000..b51add49537b --- /dev/null +++ b/src/config/merge.go @@ -0,0 +1,210 @@ +package config + +import ( + "errors" + "reflect" + "slices" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +type matcher interface { + key() any +} + +type matchMap[T matcher] map[any]T + +func (mm *matchMap[T]) hasMatch(index int, m T) (T, bool) { + for _, item := range *mm { + if item.key() == index { + return item, true + } + } + + match, OK := (*mm)[m.key()] + return match, OK +} + +func (mm *matchMap[T]) add(m T) { + if *mm == nil { + *mm = make(matchMap[T]) + } + + (*mm)[m.key()] = m +} + +func (mm *matchMap[T]) remove(m T) { + delete(*mm, m.key()) +} + +func createMatchMap[T matcher](items []T) matchMap[T] { + mm := make(matchMap[T]) + for _, item := range items { + if any(item) != nil { + mm.add(item) + } + } + return mm +} + +func (cfg *Config) merge(override *Config) error { + if cfg == nil || override == nil { + return errors.New("configs cannot be nil") + } + + nextExtends := cfg.Extends + + err := merge(override, cfg, "Blocks", "Source", "Format") + if err != nil { + return err + } + + overrideBlockMap := createMatchMap(override.Blocks) + + for i := range cfg.Blocks { + overrideBlock, exists := overrideBlockMap.hasMatch(i, cfg.Blocks[i]) + if !exists { + continue + } + + // remove the block from the override map so we don't match it again + overrideBlockMap.remove(overrideBlock) + + err = merge(overrideBlock, cfg.Blocks[i], "Segments") + if err != nil { + return err + } + + overrideSegmentMap := createMatchMap(overrideBlock.Segments) + + for k := range cfg.Blocks[i].Segments { + overrideSegment, exists := overrideSegmentMap.hasMatch(k, cfg.Blocks[i].Segments[k]) + if !exists { + log.Debugf("No matching segment found for %s in block %s", cfg.Blocks[i].Segments[k].Type, cfg.Blocks[i].Type) + continue + } + + // remove the block from the override map so we don't match it again + overrideSegmentMap.remove(overrideSegment) + + baseSegment := cfg.Blocks[i].Segments[k] + + if baseSegment.Type != overrideSegment.Type { + log.Debugf("Replacing segment %s with %s in block %s", baseSegment.Type, overrideSegment.Type, cfg.Blocks[i].Type) + cfg.Blocks[i].Segments[k] = overrideSegment + continue + } + + err = merge(overrideSegment, baseSegment) + if err != nil { + return err + } + } + + // add any remaining segments that were not matched + for _, segment := range overrideSegmentMap { + log.Debugf("Adding segment %s to block %s", segment.Type, cfg.Blocks[i].Type) + cfg.Blocks[i].Segments = append(cfg.Blocks[i].Segments, segment) + } + } + + cfg.Extends = nextExtends + cfg.extended = true + + return nil +} + +func merge(override, base any, skipFields ...string) error { + if base == nil || override == nil { + return errors.New("config to merge cannot be nil") + } + + overrideValue := reflect.ValueOf(override).Elem() + baseValue := reflect.ValueOf(base).Elem() + overrideType := overrideValue.Type() + + for i := 0; i < overrideValue.NumField(); i++ { + field := overrideType.Field(i) + + if !field.IsExported() { + continue + } + + overrideField := overrideValue.Field(i) + baseField := baseValue.FieldByName(field.Name) + + // Skip unexported fields or fields that can't be set + if isZeroValue(overrideField) || !baseField.CanSet() { + continue + } + + // Skip internal fields that shouldn't be merged + if slices.Contains(skipFields, field.Name) { + continue + } + + // Special handling for slices - merge instead of replace + if overrideField.Kind() == reflect.Slice { + mergeSlices(overrideField, baseField) + continue + } + + // Special handling for maps - merge instead of replace + if overrideField.Kind() == reflect.Map { + mergeMaps(overrideField, baseField) + continue + } + + if baseField.CanSet() { + baseField.Set(overrideField) + } + } + + return nil +} + +func isZeroValue(v reflect.Value) bool { + switch v.Kind() { //nolint: exhaustive + case reflect.Slice, reflect.Map: + return v.IsNil() || v.Len() == 0 + case reflect.Pointer: + return v.IsNil() + case reflect.String: + return v.String() == "" + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return false + default: + return v.IsZero() + } +} + +func mergeSlices(override, base reflect.Value) { + if base.IsNil() && !override.IsNil() { + base.Set(override) + return + } + + if !base.IsNil() && !override.IsNil() { + newSlice := reflect.AppendSlice(base, override) + base.Set(newSlice) + } +} + +func mergeMaps(override, base reflect.Value) { + if base.IsNil() && !override.IsNil() { + base.Set(override) + return + } + + if !base.IsNil() && !override.IsNil() { + // Merge maps - cfg values override base values + for _, key := range override.MapKeys() { + base.SetMapIndex(key, override.MapIndex(key)) + } + } + + if base.IsNil() { + // Initialize empty map if both are nil but base has the type + base.Set(reflect.MakeMap(base.Type())) + } +} diff --git a/src/config/merge_test.go b/src/config/merge_test.go new file mode 100644 index 000000000000..938ae7f9528b --- /dev/null +++ b/src/config/merge_test.go @@ -0,0 +1,402 @@ +package config + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConfigMerge(t *testing.T) { + testCases := []struct { + baseConfig *Config + overrideConfig *Config + expectedResult *Config + name string + expectError bool + }{ + { + name: "merge basic options", + baseConfig: &Config{ + Version: 3, + FinalSpace: true, + Async: false, + AccentColor: "red", + }, + overrideConfig: &Config{ + Version: 3, + FinalSpace: false, + Async: true, + }, + expectedResult: &Config{ + Version: 3, + FinalSpace: false, + Async: true, + AccentColor: "red", + extended: true, + }, + expectError: false, + }, + { + name: "merge with nil override", + baseConfig: &Config{ + Version: 3, + FinalSpace: true, + }, + overrideConfig: nil, + expectedResult: &Config{ + Version: 3, + FinalSpace: true, + }, + expectError: true, + }, + { + name: "merge console title template", + baseConfig: &Config{ + ConsoleTitleTemplate: "Base Title", + Version: 3, + }, + overrideConfig: &Config{ + ConsoleTitleTemplate: "Override Title", + Version: 3, + }, + expectedResult: &Config{ + ConsoleTitleTemplate: "Override Title", + Version: 3, + extended: true, + }, + expectError: false, + }, + { + name: "merge variables map", + baseConfig: &Config{ + Var: map[string]any{ + "base_var": "base_value", + "shared_var": "base_shared", + }, + Version: 3, + }, + overrideConfig: &Config{ + Var: map[string]any{ + "added_var": "added_value", + "shared_var": "override_shared", + }, + Version: 3, + }, + expectedResult: &Config{ + Var: map[string]any{ + "base_var": "base_value", + "added_var": "added_value", + "shared_var": "override_shared", + }, + Version: 3, + extended: true, + }, + expectError: false, + }, + { + name: "merge blocks with matching alignment", + baseConfig: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "path", Options: options.Map{"style": "full"}}, + }, + }, + }, + Version: 3, + }, + overrideConfig: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "path", Options: options.Map{"style": "short"}}, + }, + }, + }, + Version: 3, + }, + expectedResult: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "path", Options: options.Map{"style": "short"}}, + }, + }, + }, + Version: 3, + extended: true, + }, + expectError: false, + }, + { + name: "merge blocks with different segment types", + baseConfig: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "path", Alias: "override", Options: options.Map{"style": "full"}}, + }, + }, + }, + Version: 3, + }, + overrideConfig: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "git", Alias: "override", Options: options.Map{"branch_icon": "branch"}}, + }, + }, + }, + Version: 3, + }, + expectedResult: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "git", Alias: "override", Options: options.Map{"branch_icon": "branch"}}, + }, + }, + }, + Version: 3, + extended: true, + }, + expectError: false, + }, + { + name: "merge segments by index", + baseConfig: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "path", Options: options.Map{"style": "full"}}, + {Type: "git", Options: options.Map{"branch_icon": ""}}, + }, + }, + }, + Version: 3, + }, + overrideConfig: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "path", Index: 1, Options: options.Map{"style": "short"}}, + }, + }, + }, + Version: 3, + }, + expectedResult: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "path", Index: 1, Options: options.Map{"style": "short"}}, + {Type: "git", Options: options.Map{"branch_icon": ""}}, + }, + }, + }, + Version: 3, + extended: true, + }, + expectError: false, + }, + { + name: "merge block by index", + baseConfig: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Segments: []*Segment{ + {Type: "path", Options: options.Map{"style": "full"}}, + {Type: "git", Options: options.Map{"branch_icon": ""}}, + }, + }, + }, + Version: 3, + }, + overrideConfig: &Config{ + Blocks: []*Block{ + { + Index: 1, + Segments: []*Segment{ + {Type: "path", Index: 1, Options: options.Map{"style": "short"}}, + }, + }, + }, + Version: 3, + }, + expectedResult: &Config{ + Blocks: []*Block{ + { + Alignment: "left", + Type: "prompt", + Index: 1, + Segments: []*Segment{ + {Type: "path", Index: 1, Options: options.Map{"style": "short"}}, + {Type: "git", Options: options.Map{"branch_icon": ""}}, + }, + }, + }, + Version: 3, + extended: true, + }, + expectError: false, + }, + { + name: "merge palette colors", + baseConfig: &Config{ + Palette: color.Palette{ + "primary": "blue", + "secondary": "green", + }, + Version: 3, + }, + overrideConfig: &Config{ + Palette: color.Palette{ + "primary": "red", + "accent": "yellow", + }, + Version: 3, + }, + expectedResult: &Config{ + Palette: color.Palette{ + "primary": "red", + "secondary": "green", + "accent": "yellow", + }, + Version: 3, + extended: true, + }, + expectError: false, + }, + { + name: "preserve extends field", + baseConfig: &Config{ + Extends: "/path/to/base.json", + Version: 3, + }, + overrideConfig: &Config{ + Extends: "/path/to/override.json", + Version: 3, + }, + expectedResult: &Config{ + Extends: "/path/to/base.json", + Version: 3, + extended: true, + }, + expectError: false, + }, + { + name: "merge tooltips slice", + baseConfig: &Config{ + Tooltips: []*Segment{ + {Type: "git", Tips: []string{"git"}}, + }, + Version: 3, + }, + overrideConfig: &Config{ + Tooltips: []*Segment{ + {Type: "path", Tips: []string{"pwd"}}, + }, + Version: 3, + }, + expectedResult: &Config{ + Tooltips: []*Segment{ + {Type: "git", Tips: []string{"git"}}, + {Type: "path", Tips: []string{"pwd"}}, + }, + Version: 3, + extended: true, + }, + expectError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.baseConfig.merge(tc.overrideConfig) + + if tc.expectError { + require.Error(t, err, tc.name) + return + } + + require.NoError(t, err, tc.name) + assert.EqualExportedValues(t, tc.expectedResult, tc.baseConfig, tc.name) + }) + } +} + +func TestConfigMergeEdgeCases(t *testing.T) { + testCases := []struct { + baseConfig *Config + overrideConfig *Config + name string + expectError bool + }{ + { + name: "nil base config", + baseConfig: nil, + overrideConfig: &Config{Version: 3}, + expectError: true, + }, + { + name: "empty configs", + baseConfig: &Config{}, + overrideConfig: &Config{}, + expectError: false, + }, + { + name: "override with empty blocks", + baseConfig: &Config{ + Blocks: []*Block{ + {Alignment: "left", Type: "prompt"}, + }, + Version: 3, + }, + overrideConfig: &Config{ + Blocks: []*Block{}, + Version: 3, + }, + expectError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.baseConfig.merge(tc.overrideConfig) + + if tc.expectError { + require.Error(t, err, tc.name) + return + } + + require.NoError(t, err, tc.name) + if tc.baseConfig != nil { + assert.True(t, tc.baseConfig.extended, tc.name) + } + }) + } +} diff --git a/src/config/migrate_glyphs.go b/src/config/migrate_glyphs.go new file mode 100644 index 000000000000..8aa28c6e97ea --- /dev/null +++ b/src/config/migrate_glyphs.go @@ -0,0 +1,123 @@ +package config + +import ( + "fmt" + "strconv" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" + "github.com/jandedobbeleer/oh-my-posh/src/text" +) + +type ConnectionError struct { + reason string +} + +func (f *ConnectionError) Error() string { + return f.reason +} + +type codePoints map[uint64]uint64 + +func getGlyphCodePoints() (codePoints, error) { + var codePoints = make(codePoints) + + bytes, err := http.Download("https://ohmyposh.dev/codepoints.csv", false) + if err != nil { + return codePoints, &ConnectionError{reason: err.Error()} + } + + lines := strings.SplitSeq(string(bytes), "\n") + + for line := range lines { + fields := strings.Split(line, ",") + if len(fields) < 2 { + continue + } + + oldGlyph, err := strconv.ParseUint(fields[0], 16, 32) + if err != nil { + continue + } + + newGlyph, err := strconv.ParseUint(fields[1], 16, 32) + if err != nil { + continue + } + + codePoints[oldGlyph] = newGlyph + } + + return codePoints, nil +} + +func EscapeGlyphs(s string, migrate bool) string { + shouldExclude := func(r rune) bool { + if r < 0x1000 { // Basic Multilingual Plane + return true + } + if r > 0x1F600 && r < 0x1F64F { // Emoticons + return true + } + if r > 0x1F300 && r < 0x1F5FF { // Misc Symbols and Pictographs + return true + } + if r > 0x1F680 && r < 0x1F6FF { // Transport and Map + return true + } + if r > 0x2600 && r < 0x26FF { // Misc symbols + return true + } + if r > 0x2700 && r < 0x27BF { // Dingbats + return true + } + if r > 0xFE00 && r < 0xFE0F { // Variation Selectors + return true + } + if r > 0x1F900 && r < 0x1F9FF { // Supplemental Symbols and Pictographs + return true + } + if r > 0x1F1E6 && r < 0x1F1FF { // Flags + return true + } + return false + } + + var cp codePoints + var err error + if migrate { + cp, err = getGlyphCodePoints() + if err != nil { + migrate = false + } + } + + sb := text.NewBuilder() + for _, r := range s { + // exclude regular characters and emojis + if shouldExclude(r) { + sb.WriteRune(r) + continue + } + + if migrate { + if val, OK := cp[uint64(r)]; OK { + r = rune(val) + } + } + + if r > 0x10000 { + // calculate surrogate pairs + one := 0xd800 + (((r - 0x10000) >> 10) & 0x3ff) + two := 0xdc00 + ((r - 0x10000) & 0x3ff) + quoted := fmt.Sprintf("\\u%04x\\u%04x", one, two) + sb.WriteString(quoted) + continue + } + + quoted := fmt.Sprintf("\\u%04x", r) + sb.WriteString(quoted) + } + + return sb.String() +} diff --git a/src/config/migrate_glyphs_test.go b/src/config/migrate_glyphs_test.go new file mode 100644 index 000000000000..c2dccc2fe579 --- /dev/null +++ b/src/config/migrate_glyphs_test.go @@ -0,0 +1,35 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetCodePoints(t *testing.T) { + codepoints, err := getGlyphCodePoints() + if connectionError, ok := err.(*ConnectionError); ok { + t.Log(connectionError.Error()) + return + } + assert.Equal(t, 1939, len(codepoints)) +} + +func TestEscapeGlyphs(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "󰉋", Expected: "\\udb80\\ude4b"}, + {Input: "a", Expected: "a"}, + {Input: "\ue0b4", Expected: "\\ue0b4"}, + {Input: "\ufd03", Expected: "\\ufd03"}, + {Input: "}", Expected: "}"}, + {Input: "🏚", Expected: "🏚"}, + {Input: "\U000f0bc9", Expected: "\\udb82\\udfc9"}, + {Input: "󰯉", Expected: "\\udb82\\udfc9"}, + } + for _, tc := range cases { + assert.Equal(t, tc.Expected, EscapeGlyphs(tc.Input, false), tc.Input) + } +} diff --git a/src/config/responsive.go b/src/config/responsive.go new file mode 100644 index 000000000000..64c85734570a --- /dev/null +++ b/src/config/responsive.go @@ -0,0 +1,23 @@ +package config + +import "github.com/jandedobbeleer/oh-my-posh/src/runtime" + +func shouldHideForWidth(env runtime.Environment, minWidth, maxWidth int) bool { + if maxWidth == 0 && minWidth == 0 { + return false + } + width, err := env.TerminalWidth() + if err != nil { + return false + } + if minWidth > 0 && maxWidth > 0 { + return width < minWidth || width > maxWidth + } + if maxWidth > 0 && width > maxWidth { + return true + } + if minWidth > 0 && width < minWidth { + return true + } + return false +} diff --git a/src/config/responsive_test.go b/src/config/responsive_test.go new file mode 100644 index 000000000000..751f3ba7bf4d --- /dev/null +++ b/src/config/responsive_test.go @@ -0,0 +1,35 @@ +package config + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + + "github.com/stretchr/testify/assert" +) + +func TestShouldHideForWidth(t *testing.T) { + cases := []struct { + Error error + Case string + MinWidth int + MaxWidth int + Width int + Expected bool + }{ + {Case: "No settings"}, + {Case: "Min cols - hide", MinWidth: 10, Width: 9, Expected: true}, + {Case: "Min cols - show", MinWidth: 10, Width: 20, Expected: false}, + {Case: "Max cols - hide", MaxWidth: 10, Width: 11, Expected: true}, + {Case: "Max cols - show", MaxWidth: 10, Width: 8, Expected: false}, + {Case: "Min & Max cols - hide", MinWidth: 10, MaxWidth: 20, Width: 21, Expected: true}, + {Case: "Min & Max cols - hide 2", MinWidth: 10, MaxWidth: 20, Width: 8, Expected: true}, + {Case: "Min & Max cols - show", MinWidth: 10, MaxWidth: 20, Width: 11, Expected: false}, + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("TerminalWidth").Return(tc.Width, tc.Error) + got := shouldHideForWidth(env, tc.MinWidth, tc.MaxWidth) + assert.Equal(t, tc.Expected, got, tc.Case) + } +} diff --git a/src/config/segment.go b/src/config/segment.go new file mode 100644 index 000000000000..60cabdb0ccb1 --- /dev/null +++ b/src/config/segment.go @@ -0,0 +1,468 @@ +package config + +import ( + "encoding/json" + "fmt" + "slices" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + runjobs "github.com/jandedobbeleer/oh-my-posh/src/runtime/jobs" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/template" + + "go.yaml.in/yaml/v3" + c "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// SegmentStyle the style of segment, for more information, see the constants +type SegmentStyle string + +func (s *SegmentStyle) resolve(context any) SegmentStyle { + value, err := template.Render(string(*s), context) + + // default to Plain + if err != nil || value == "" { + return Plain + } + + return SegmentStyle(value) +} + +type Segment struct { + writer SegmentWriter + env runtime.Environment + Options options.Map `json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty"` + Properties options.Map `json:"-" toml:"properties,omitempty" yaml:"-"` + Cache *Cache `json:"cache,omitempty" toml:"cache,omitempty" yaml:"cache,omitempty"` + Alias string `json:"alias,omitempty" toml:"alias,omitempty" yaml:"alias,omitempty"` + styleCache SegmentStyle + name string + LeadingDiamond string `json:"leading_diamond,omitempty" toml:"leading_diamond,omitempty" yaml:"leading_diamond,omitempty"` + TrailingDiamond string `json:"trailing_diamond,omitempty" toml:"trailing_diamond,omitempty" yaml:"trailing_diamond,omitempty"` + Template string `json:"template,omitempty" toml:"template,omitempty" yaml:"template,omitempty"` + Foreground color.Ansi `json:"foreground,omitempty" toml:"foreground,omitempty" yaml:"foreground,omitempty"` + TemplatesLogic template.Logic `json:"templates_logic,omitempty" toml:"templates_logic,omitempty" yaml:"templates_logic,omitempty"` + PowerlineSymbol string `json:"powerline_symbol,omitempty" toml:"powerline_symbol,omitempty" yaml:"powerline_symbol,omitempty"` + Background color.Ansi `json:"background,omitempty" toml:"background,omitempty" yaml:"background,omitempty"` + Filler string `json:"filler,omitempty" toml:"filler,omitempty" yaml:"filler,omitempty"` + Type SegmentType `json:"type,omitempty" toml:"type,omitempty" yaml:"type,omitempty"` + Style SegmentStyle `json:"style,omitempty" toml:"style,omitempty" yaml:"style,omitempty"` + LeadingPowerlineSymbol string `json:"leading_powerline_symbol,omitempty" toml:"leading_powerline_symbol,omitempty" yaml:"leading_powerline_symbol,omitempty"` + Placeholder string `json:"placeholder,omitempty" toml:"placeholder,omitempty" yaml:"placeholder,omitempty"` + Tips []string `json:"tips,omitempty" toml:"tips,omitempty" yaml:"tips,omitempty"` + BackgroundTemplates template.List `json:"background_templates,omitempty" toml:"background_templates,omitempty" yaml:"background_templates,omitempty"` + Templates template.List `json:"templates,omitempty" toml:"templates,omitempty" yaml:"templates,omitempty"` + ExcludeFolders []string `json:"exclude_folders,omitempty" toml:"exclude_folders,omitempty" yaml:"exclude_folders,omitempty"` + IncludeFolders []string `json:"include_folders,omitempty" toml:"include_folders,omitempty" yaml:"include_folders,omitempty"` + Needs []string `json:"-" toml:"-" yaml:"-"` + ForegroundTemplates template.List `json:"foreground_templates,omitempty" toml:"foreground_templates,omitempty" yaml:"foreground_templates,omitempty"` + Index int `json:"index,omitempty" toml:"index,omitempty" yaml:"index,omitempty"` + MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty" yaml:"min_width,omitempty"` + Duration time.Duration `json:"-" toml:"-" yaml:"-"` + NameLength int `json:"-" toml:"-" yaml:"-"` + MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty" yaml:"max_width,omitempty"` + Timeout int `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty"` + Newline bool `json:"newline,omitempty" toml:"newline,omitempty" yaml:"newline,omitempty"` + Enabled bool `json:"-" toml:"-" yaml:"-"` + InvertPowerline bool `json:"invert_powerline,omitempty" toml:"invert_powerline,omitempty" yaml:"invert_powerline,omitempty"` + Force bool `json:"force,omitempty" toml:"force,omitempty" yaml:"force,omitempty"` + restored bool `json:"-" toml:"-" yaml:"-"` + Toggled bool `json:"toggled,omitempty" toml:"toggled,omitempty" yaml:"toggled,omitempty"` + Pending bool `json:"-" toml:"-" yaml:"-"` + Interactive bool `json:"interactive,omitempty" toml:"interactive,omitempty" yaml:"interactive,omitempty"` +} + +// segmentAlias is used to avoid recursion during unmarshaling +type segmentAlias Segment + +// segmentAux is a helper struct that captures the legacy 'properties' field +type segmentAux struct { + Properties options.Map `json:"properties,omitempty" yaml:"properties,omitempty" toml:"properties,omitempty"` + *segmentAlias +} + +func (segment *Segment) UnmarshalJSON(data []byte) error { + aux := &segmentAux{ + segmentAlias: (*segmentAlias)(segment), + } + + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + // Migrate 'properties' to 'options' if present + if len(aux.Properties) > 0 && len(segment.Options) == 0 { + segment.Options = aux.Properties + } + + return nil +} + +func (segment *Segment) UnmarshalYAML(node *yaml.Node) error { + // Decode into a map to handle field renaming + var raw map[string]any + if err := node.Decode(&raw); err != nil { + return err + } + + // If 'properties' exists and 'options' doesn't, rename it + if props, hasProps := raw["properties"]; hasProps { + if _, hasOptions := raw["options"]; !hasOptions { + raw["options"] = props + delete(raw, "properties") + } + } + + // Re-encode and decode into the struct + modifiedNode := &yaml.Node{} + if err := modifiedNode.Encode(raw); err != nil { + return err + } + + return modifiedNode.Decode((*segmentAlias)(segment)) +} + +// MigratePropertiesToOptions migrates the deprecated Properties field to Options. +// This is needed for TOML configs since go-toml/v2 doesn't support custom unmarshalers. +func (segment *Segment) MigratePropertiesToOptions() { + if len(segment.Properties) > 0 && len(segment.Options) == 0 { + segment.Options = segment.Properties + segment.Properties = nil + } +} + +func (segment *Segment) Name() string { + if len(segment.name) != 0 { + return segment.name + } + + name := segment.Alias + if name == "" { + name = c.Title(language.English).String(string(segment.Type)) + } + + segment.name = name + return name +} + +func (segment *Segment) Execute(env runtime.Environment) { + // segment timings for debug purposes + var start time.Time + if env.Flags().Debug { + start = time.Now() + segment.NameLength = len(segment.Name()) + defer func() { + segment.Duration = time.Since(start) + }() + } + + defer segment.evaluateNeeds() + + err := segment.MapSegmentWithWriter(env) + if err != nil || !segment.shouldIncludeFolder() { + return + } + + log.Debugf("segment: %s", segment.Name()) + + if segment.isToggled() { + return + } + + cacheRestored := segment.restoreCache() + if cacheRestored && !env.Flags().Streaming { + return + } + + if shouldHideForWidth(segment.env, segment.MinWidth, segment.MaxWidth) { + return + } + + defer func() { + if segment.Enabled { + template.Cache.AddSegmentData(segment.Name(), segment.writer) + } + }() + + // Create Job for this goroutine so child processes can be tracked and killed on timeout + if err := runjobs.CreateJobForGoroutine(segment.Name()); err != nil { + log.Errorf("failed to create job for goroutine (segment: %s): %v", segment.Name(), err) + } + + segment.Enabled = segment.writer.Enabled() +} + +func (segment *Segment) Render(index int, force bool) bool { + // Allow pending segments to render (they'll show "..." text) + if !segment.Pending && !segment.Enabled && !force { + return false + } + + if force { + segment.Force = true + } + + segment.writer.SetIndex(index) + + text := segment.string() + + // Only update Enabled if segment is NOT pending (avoid race with Execute goroutine) + if !segment.Pending { + segment.Enabled = segment.Force || len(strings.ReplaceAll(text, " ", "")) > 0 + + if !segment.Enabled { + template.Cache.RemoveSegmentData(segment.Name()) + return false + } + } + + segment.SetText(text) + segment.setCache() + + // We do this to make `.Text` available for a cross-segment reference in an extra prompt. + template.Cache.AddSegmentData(segment.Name(), segment.writer) + + return true +} + +func (segment *Segment) Text() string { + return segment.writer.Text() +} + +func (segment *Segment) SetText(text string) { + segment.writer.SetText(text) +} + +func (segment *Segment) ResolveForeground() color.Ansi { + if len(segment.ForegroundTemplates) != 0 { + match := segment.ForegroundTemplates.FirstMatch(segment.writer, segment.Foreground.String()) + segment.Foreground = color.Ansi(match) + } + + return segment.Foreground +} + +func (segment *Segment) ResolveBackground() color.Ansi { + if len(segment.BackgroundTemplates) != 0 { + match := segment.BackgroundTemplates.FirstMatch(segment.writer, segment.Background.String()) + segment.Background = color.Ansi(match) + } + + return segment.Background +} + +func (segment *Segment) ResolveStyle() SegmentStyle { + if len(segment.styleCache) != 0 { + return segment.styleCache + } + + segment.styleCache = segment.Style.resolve(segment.writer) + + return segment.styleCache +} + +func (segment *Segment) IsPowerline() bool { + style := segment.ResolveStyle() + return style == Powerline || style == Accordion +} + +func (segment *Segment) HasEmptyDiamondAtEnd() bool { + if segment.ResolveStyle() != Diamond { + return false + } + + return segment.TrailingDiamond == "" +} + +func (segment *Segment) hasCache() bool { + return segment.Cache != nil && !segment.Cache.Duration.IsEmpty() +} + +func (segment *Segment) isToggled() bool { + togglesMap, OK := cache.Get[map[string]bool](cache.Session, cache.TOGGLECACHE) + if !OK || len(togglesMap) == 0 { + log.Debug("no toggles found") + return false + } + + segmentName := segment.Alias + if segmentName == "" { + segmentName = string(segment.Type) + } + + if togglesMap[segmentName] { + log.Debugf("segment toggled off: %s", segment.Name()) + return true + } + + return false +} + +func (segment *Segment) restoreCache() bool { + if !segment.hasCache() { + return false + } + + key, store := segment.cacheKeyAndStore() + data, OK := cache.Get[string](store, key) + if !OK { + log.Debugf("no cache found for segment: %s, key: %s", segment.Name(), key) + return false + } + + err := json.Unmarshal([]byte(data), &segment.writer) + if err != nil { + log.Error(err) + } + + segment.Enabled = true + template.Cache.AddSegmentData(segment.Name(), segment.writer) + + log.Debug("restored segment from cache: ", segment.Name()) + + segment.restored = true + + return true +} + +func (segment *Segment) setCache() { + if segment.restored || !segment.hasCache() { + return + } + + // Never cache pending state to avoid polluting cache with incomplete data + if segment.Pending { + return + } + + data, err := json.Marshal(segment.writer) + if err != nil { + log.Error(err) + return + } + + // TODO: check if we can make segmentwriter a generic Type indicator + // that way we can actually get the value straight from cache.Get + // and marchalling is obsolete + key, store := segment.cacheKeyAndStore() + cache.Set(store, key, string(data), segment.Cache.Duration) +} + +func (segment *Segment) cacheKeyAndStore() (string, cache.Store) { + format := "segment_cache_%s" + switch segment.Cache.Strategy { + case Session: + return fmt.Sprintf(format, segment.Name()), cache.Session + case Device: + return fmt.Sprintf(format, segment.Name()), cache.Device + case Folder: + fallthrough + default: + return fmt.Sprintf(format, strings.Join([]string{segment.Name(), segment.folderKey()}, "_")), cache.Device + } +} + +func (segment *Segment) folderKey() string { + key, ok := segment.writer.CacheKey() + if !ok { + return segment.env.Pwd() + } + + return key +} + +func (segment *Segment) string() string { + // Use simple pending text if segment is still pending + if segment.Pending { + if segment.Placeholder != "" { + return segment.Placeholder + } + + return "..." + } + + result := segment.Templates.Resolve(segment.writer, "", segment.TemplatesLogic) + if len(result) != 0 { + return result + } + + if segment.Template == "" { + segment.Template = segment.writer.Template() + } + + text, err := template.Render(segment.Template, segment.writer) + if err != nil { + return err.Error() + } + + return text +} + +func (segment *Segment) shouldIncludeFolder() bool { + if segment.env == nil { + return true + } + + cwdIncluded := segment.cwdIncluded() + cwdExcluded := segment.cwdExcluded() + + return cwdIncluded && !cwdExcluded +} + +func (segment *Segment) cwdIncluded() bool { + if len(segment.IncludeFolders) == 0 { + return true + } + + return segment.env.DirMatchesOneOf(segment.env.Pwd(), segment.IncludeFolders) +} + +func (segment *Segment) cwdExcluded() bool { + return segment.env.DirMatchesOneOf(segment.env.Pwd(), segment.ExcludeFolders) +} + +func (segment *Segment) evaluateNeeds() { + value := segment.Template + + if len(segment.ForegroundTemplates) != 0 { + value += strings.Join(segment.ForegroundTemplates, "") + } + + if len(segment.BackgroundTemplates) != 0 { + value += strings.Join(segment.BackgroundTemplates, "") + } + + if len(segment.Templates) != 0 { + value += strings.Join(segment.Templates, "") + } + + if !strings.Contains(value, ".Segments.") { + return + } + + matches := regex.FindAllNamedRegexMatch(`\.Segments\.(?P[a-zA-Z0-9]+)`, value) + for _, name := range matches { + segmentName := name["NAME"] + + if len(name) == 0 || slices.Contains(segment.Needs, segmentName) { + continue + } + + segment.Needs = append(segment.Needs, segmentName) + } +} + +func (segment *Segment) key() any { + if segment.Index > 0 { + return segment.Index - 1 + } + + return segment.Name() +} diff --git a/src/config/segment_test.go b/src/config/segment_test.go new file mode 100644 index 000000000000..31a20bb6c381 --- /dev/null +++ b/src/config/segment_test.go @@ -0,0 +1,347 @@ +package config + +import ( + "encoding/json" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments" + + toml "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/assert" + "go.yaml.in/yaml/v3" +) + +const ( + cwd = "Projects/oh-my-posh" +) + +func TestMapSegmentWriterCanMap(t *testing.T) { + sc := &Segment{ + Type: SESSION, + } + env := new(mock.Environment) + err := sc.MapSegmentWithWriter(env) + assert.NoError(t, err) + assert.NotNil(t, sc.writer) +} + +func TestMapSegmentWriterCannotMap(t *testing.T) { + sc := &Segment{ + Type: "nilwriter", + } + env := new(mock.Environment) + err := sc.MapSegmentWithWriter(env) + assert.Error(t, err) +} + +func TestParseTestConfig(t *testing.T) { + segmentJSON := + ` + { + "type": "path", + "style": "powerline", + "powerline_symbol": "\uE0B0", + "foreground": "#ffffff", + "background": "#61AFEF", + "options": { + "style": "folder" + }, + "exclude_folders": [ + "/super/secret/project" + ] + } + ` + segment := &Segment{} + err := json.Unmarshal([]byte(segmentJSON), segment) + assert.NoError(t, err) + assert.NotNil(t, segment.Options) + assert.Equal(t, "folder", segment.Options.String("style", "")) +} + +func TestParseConfigWithOptions(t *testing.T) { + segmentJSON := + ` + { + "type": "path", + "style": "powerline", + "options": { + "style": "folder" + } + } + ` + segment := &Segment{} + err := json.Unmarshal([]byte(segmentJSON), segment) + assert.NoError(t, err) + assert.NotNil(t, segment.Options) + assert.Equal(t, "folder", segment.Options.String("style", "")) +} + +func TestParseYAMLConfigWithProperties(t *testing.T) { + segmentYAML := ` +type: path +style: powerline +properties: + style: folder +` + segment := &Segment{} + err := yaml.Unmarshal([]byte(segmentYAML), segment) + assert.NoError(t, err) + assert.NotNil(t, segment.Options) + assert.Equal(t, "folder", segment.Options.String("style", "")) +} + +func TestParseYAMLConfigWithOptions(t *testing.T) { + segmentYAML := ` +type: path +style: powerline +options: + style: folder +` + segment := &Segment{} + err := yaml.Unmarshal([]byte(segmentYAML), segment) + assert.NoError(t, err) + assert.NotNil(t, segment.Options) + assert.Equal(t, "folder", segment.Options.String("style", "")) +} + +func TestParseTOMLConfigWithProperties(t *testing.T) { + segmentTOML := ` +type = "path" +style = "powerline" +[properties] +style = "folder" +` + segment := &Segment{} + err := toml.Unmarshal([]byte(segmentTOML), segment) + assert.NoError(t, err) + + // Migrate properties to options (normally done by Config.migrateSegmentProperties) + segment.MigratePropertiesToOptions() + + assert.NotNil(t, segment.Options) + assert.Equal(t, "folder", segment.Options.String("style", "")) +} + +func TestParseTOMLConfigWithOptions(t *testing.T) { + segmentTOML := ` +type = "path" +style = "powerline" +[options] +style = "folder" +` + segment := &Segment{} + err := toml.Unmarshal([]byte(segmentTOML), segment) + assert.NoError(t, err) + + // Migrate properties to options (should be a no-op since options is set) + segment.MigratePropertiesToOptions() + + assert.NotNil(t, segment.Options) + assert.Equal(t, "folder", segment.Options.String("style", "")) +} + +func TestParseTOMLConfigWithBothOptionsAndProperties(t *testing.T) { + // If both are specified, options takes precedence + segmentTOML := ` +type = "path" +style = "powerline" +[options] +style = "folder" +[properties] +style = "letter" +` + segment := &Segment{} + err := toml.Unmarshal([]byte(segmentTOML), segment) + assert.NoError(t, err) + + // Migrate should not overwrite options + segment.MigratePropertiesToOptions() + + assert.NotNil(t, segment.Options) + assert.Equal(t, "folder", segment.Options.String("style", "")) +} + +func TestShouldIncludeFolder(t *testing.T) { + cases := []struct { + Case string + Included bool + Excluded bool + Expected bool + }{ + {Case: "Include", Included: true, Excluded: false, Expected: true}, + {Case: "Exclude", Included: false, Excluded: true, Expected: false}, + {Case: "Include & Exclude", Included: true, Excluded: true, Expected: false}, + {Case: "!Include & !Exclude", Included: false, Excluded: false, Expected: false}, + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("GOOS").Return(runtime.LINUX) + env.On("Home").Return("") + env.On("Pwd").Return(cwd) + env.On("DirMatchesOneOf", cwd, []string{"Projects/oh-my-posh"}).Return(tc.Included) + env.On("DirMatchesOneOf", cwd, []string{"Projects/nope"}).Return(tc.Excluded) + segment := &Segment{ + IncludeFolders: []string{"Projects/oh-my-posh"}, + ExcludeFolders: []string{"Projects/nope"}, + env: env, + } + got := segment.shouldIncludeFolder() + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestGetColors(t *testing.T) { + cases := []struct { + Case string + Expected color.Ansi + Default color.Ansi + Region string + Profile string + Templates []string + Background bool + }{ + {Case: "No template - foreground", Expected: "color", Background: false, Default: "color"}, + {Case: "No template - background", Expected: "color", Background: true, Default: "color"}, + {Case: "Nil template", Expected: "color", Default: "color", Templates: nil}, + { + Case: "Template - default", + Expected: "color", + Default: "color", + Templates: []string{ + "{{if contains \"john\" .Profile}}color2{{end}}", + }, + Profile: "doe", + }, + { + Case: "Template - override", + Expected: "color2", + Default: "color", + Templates: []string{ + "{{if contains \"john\" .Profile}}color2{{end}}", + }, + Profile: "john", + }, + { + Case: "Template - override multiple", + Expected: "color3", + Default: "color", + Templates: []string{ + "{{if contains \"doe\" .Profile}}color2{{end}}", + "{{if contains \"john\" .Profile}}color3{{end}}", + }, + Profile: "john", + }, + { + Case: "Template - override multiple no match", + Expected: "color", + Default: "color", + Templates: []string{ + "{{if contains \"doe\" .Profile}}color2{{end}}", + "{{if contains \"philip\" .Profile}}color3{{end}}", + }, + Profile: "john", + }, + } + for _, tc := range cases { + segment := &Segment{ + writer: &segments.Aws{ + Profile: tc.Profile, + Region: tc.Region, + }, + } + + if tc.Background { + segment.Background = tc.Default + segment.BackgroundTemplates = tc.Templates + bgColor := segment.ResolveBackground() + assert.Equal(t, tc.Expected, bgColor, tc.Case) + continue + } + + segment.Foreground = tc.Default + segment.ForegroundTemplates = tc.Templates + fgColor := segment.ResolveForeground() + assert.Equal(t, tc.Expected, fgColor, tc.Case) + } +} + +func TestEvaluateNeeds(t *testing.T) { + cases := []struct { + Segment *Segment + Case string + Needs []string + }{ + { + Case: "No needs", + Segment: &Segment{ + Template: "foo", + }, + }, + { + Case: "Template needs", + Segment: &Segment{ + Template: "{{ .Segments.Git.URL }}", + }, + Needs: []string{"Git"}, + }, + { + Case: "Template & Foreground needs", + Segment: &Segment{ + Template: "{{ .Segments.Git.URL }}", + ForegroundTemplates: []string{"foo", "{{ .Segments.Os.Icon }}"}, + }, + Needs: []string{"Git", "Os"}, + }, + { + Case: "Template & Foreground & Background needs", + Segment: &Segment{ + Template: "{{ .Segments.Git.URL }}", + ForegroundTemplates: []string{"foo", "{{ .Segments.Os.Icon }}"}, + BackgroundTemplates: []string{"bar", "{{ .Segments.Exit.Icon }}"}, + }, + Needs: []string{"Git", "Os", "Exit"}, + }, + } + for _, tc := range cases { + tc.Segment.evaluateNeeds() + assert.Equal(t, tc.Needs, tc.Segment.Needs, tc.Case) + } +} + +func TestSegment_NoCachingWhenPending(t *testing.T) { + env := new(mock.Environment) + env.On("Shell").Return("pwsh") + env.On("Flags").Return(&runtime.Flags{}) + env.On("Pwd").Return("/test") + env.On("Home").Return("/home") + + segment := &Segment{ + Type: SESSION, + Pending: true, + Template: "test", + } + + err := segment.MapSegmentWithWriter(env) + assert.NoError(t, err) + + // When Pending=true, setCache should return early without caching + // We can't easily mock cache.Set, but we can verify the method doesn't panic + // and that the behavior differs between Pending=true and Pending=false + + // With Pending=true, setCache returns early + segment.Cache = &Cache{Duration: "5h"} + segment.setCache() // Should return early, not attempt to cache + + // Verify this doesn't panic and segment still works + assert.True(t, segment.Pending, "Segment should still be pending") + + // Now with Pending=false, setCache will attempt to cache + segment.Pending = false + segment.restored = false + segment.setCache() // Should attempt to cache (may fail but shouldn't panic) + + assert.False(t, segment.Pending, "Segment should not be pending") +} diff --git a/src/config/segment_types.go b/src/config/segment_types.go new file mode 100644 index 000000000000..0ec281e2cd54 --- /dev/null +++ b/src/config/segment_types.go @@ -0,0 +1,515 @@ +package config + +import ( + "encoding/gob" + "errors" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/segments" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +// SegmentType the type of segment, for more information, see the constants +type SegmentType string + +// SegmentWriter is the interface used to define what and if to write to the prompt +type SegmentWriter interface { + Enabled() bool + Template() string + SetText(text string) + SetIndex(index int) + Text() string + Init(props options.Provider, env runtime.Environment) + CacheKey() (string, bool) +} + +func init() { + gob.Register(&segments.Angular{}) + gob.Register(&segments.Version{}) + gob.Register(&segments.Argocd{}) + gob.Register(&segments.Aurelia{}) + gob.Register(&segments.Aws{}) + gob.Register(&segments.Az{}) + gob.Register(&segments.Azd{}) + gob.Register(&segments.AzFunc{}) + gob.Register(&segments.Battery{}) + gob.Register(&segments.Bazel{}) + gob.Register(&segments.Brewfather{}) + gob.Register(&segments.Buf{}) + gob.Register(&segments.Bun{}) + gob.Register(&segments.CarbonIntensity{}) + gob.Register(&segments.Cds{}) + gob.Register(&segments.Copilot{}) + gob.Register(&segments.Cf{}) + gob.Register(&segments.CfTarget{}) + gob.Register(&segments.Claude{}) + gob.Register(&segments.ClaudeData{}) + gob.Register(&segments.Clojure{}) + gob.Register(&segments.Cmake{}) + gob.Register(&segments.Connection{}) + gob.Register(&segments.Crystal{}) + gob.Register(&segments.Dart{}) + gob.Register(&segments.Deno{}) + gob.Register(&segments.Docker{}) + gob.Register(&segments.Dotnet{}) + gob.Register(&segments.Elixir{}) + gob.Register(&segments.Executiontime{}) + gob.Register(&segments.Status{}) + gob.Register(&segments.Firebase{}) + gob.Register(&segments.Flutter{}) + gob.Register(&segments.Fortran{}) + gob.Register(&segments.Fossil{}) + gob.Register(&segments.FossilStatus{}) + gob.Register(&segments.Gcp{}) + gob.Register(&segments.Git{}) + gob.Register(&segments.GitStatus{}) + gob.Register(&segments.Rebase{}) + gob.Register(&segments.User{}) + gob.Register(&segments.Commit{}) + gob.Register(&segments.GitVersion{}) + gob.Register(&segments.Golang{}) + gob.Register(&segments.Haskell{}) + gob.Register(&segments.Helm{}) + gob.Register(&segments.IPify{}) + gob.Register(&segments.Java{}) + gob.Register(&segments.HTTP{}) + gob.Register(&segments.Jujutsu{}) + gob.Register(&segments.JujutsuStatus{}) + gob.Register(&segments.Julia{}) + gob.Register(&segments.Kotlin{}) + gob.Register(&segments.Kubectl{}) + gob.Register(&segments.LastFM{}) + gob.Register(&segments.Lua{}) + gob.Register(&segments.Mercurial{}) + gob.Register(&segments.MercurialStatus{}) + gob.Register(&segments.Mojo{}) + gob.Register(&segments.Mvn{}) + gob.Register(&segments.Nba{}) + gob.Register(&segments.Nbgv{}) + gob.Register(&segments.Nightscout{}) + gob.Register(&segments.NixShell{}) + gob.Register(&segments.Nim{}) + gob.Register(&segments.Node{}) + gob.Register(&segments.Npm{}) + gob.Register(&segments.Nx{}) + gob.Register(&segments.OCaml{}) + gob.Register(&segments.Os{}) + gob.Register(&segments.Owm{}) + gob.Register(&segments.Path{}) + gob.Register(&segments.Folders{}) + gob.Register(&segments.Perl{}) + gob.Register(&segments.Php{}) + gob.Register(&segments.Plastic{}) + gob.Register(&segments.PlasticStatus{}) + gob.Register(&segments.Pnpm{}) + gob.Register(&segments.Project{}) + gob.Register(&segments.Pulumi{}) + gob.Register(&segments.Python{}) + gob.Register(&segments.Quasar{}) + gob.Register(&segments.Package{}) + gob.Register(&segments.R{}) + gob.Register(&segments.Ramadan{}) + gob.Register(&segments.React{}) + gob.Register(&segments.Root{}) + gob.Register(&segments.Ruby{}) + gob.Register(&segments.Rust{}) + gob.Register(&segments.Sapling{}) + gob.Register(&segments.SaplingStatus{}) + gob.Register(&segments.Session{}) + gob.Register(&segments.Shell{}) + gob.Register(&segments.Sitecore{}) + gob.Register(&segments.Spotify{}) + gob.Register(&segments.Status{}) + gob.Register(&segments.Strava{}) + gob.Register(&segments.Svelte{}) + gob.Register(&segments.Svn{}) + gob.Register(&segments.SvnStatus{}) + gob.Register(&segments.Swift{}) + gob.Register(&segments.SystemInfo{}) + gob.Register(&segments.TalosCTL{}) + gob.Register(&segments.Taskwarrior{}) + gob.Register(&segments.Tauri{}) + gob.Register(&segments.Terraform{}) + gob.Register(&segments.Text{}) + gob.Register(&segments.Time{}) + gob.Register(&segments.Todoist{}) + gob.Register(&segments.UI5Tooling{}) + gob.Register(&segments.Umbraco{}) + gob.Register(&segments.Unity{}) + gob.Register(&segments.Upgrade{}) + gob.Register(&segments.UpgradeCache{}) + gob.Register(&segments.V{}) + gob.Register(&segments.Vala{}) + gob.Register(&segments.Wakatime{}) + gob.Register(&segments.WinGet{}) + gob.Register(&segments.WinGetPackage{}) + gob.Register(&segments.WindowsRegistry{}) + gob.Register(&segments.Withings{}) + gob.Register(&segments.XMake{}) + gob.Register(&segments.Yarn{}) + gob.Register(&segments.Ytm{}) + gob.Register(&segments.Zig{}) + gob.Register(&segments.Segment{}) +} + +const ( + // Plain writes it without ornaments + Plain SegmentStyle = "plain" + // Powerline writes it Powerline style + Powerline SegmentStyle = "powerline" + // Accordion writes it Powerline style but collapses the segment when disabled instead of hiding + Accordion SegmentStyle = "accordion" + // Diamond writes the prompt shaped with a leading and trailing symbol + Diamond SegmentStyle = "diamond" + // ANGULAR writes which angular cli version us currently active + ANGULAR SegmentType = "angular" + // ARGOCD writes the current argocd context + ARGOCD SegmentType = "argocd" + // AURELIA writes which aurelia version is currently referenced in package.json + AURELIA SegmentType = "aurelia" + // AWS writes the active aws context + AWS SegmentType = "aws" + // AZ writes the Azure subscription info we're currently in + AZ SegmentType = "az" + // AZD writes the Azure Developer CLI environment info we're current in + AZD SegmentType = "azd" + // AZFUNC writes current AZ func version + AZFUNC SegmentType = "azfunc" + // BATTERY writes the battery percentage + BATTERY SegmentType = "battery" + // BAZEL writes the bazel version + BAZEL SegmentType = "bazel" + // Brewfather segment + BREWFATHER SegmentType = "brewfather" + // Buf segment writes the active buf version + BUF SegmentType = "buf" + // BUN writes the active bun version + BUN SegmentType = "bun" + // CARBONINTENSITY writes the actual and forecast carbon intensity in gCO2/kWh + CARBONINTENSITY SegmentType = "carbonintensity" + // cds (SAP CAP) version + CDS SegmentType = "cds" + // Cloud Foundry segment + CF SegmentType = "cf" + // Cloud Foundry logged in target + CFTARGET SegmentType = "cftarget" + // CLAUDE writes Claude Code session information + CLAUDE SegmentType = "claude" + // CLOJURE writes the active clojure version + CLOJURE SegmentType = "clojure" + // CMAKE writes the active cmake version + CMAKE SegmentType = "cmake" + // CONNECTION writes a connection's information + CONNECTION SegmentType = "connection" + // COPILOT writes GitHub Copilot usage statistics + COPILOT SegmentType = "copilot" + // CRYSTAL writes the active crystal version + CRYSTAL SegmentType = "crystal" + // DART writes the active dart version + DART SegmentType = "dart" + // DENO writes the active deno version + DENO SegmentType = "deno" + // DOCKER writes the docker context + DOCKER SegmentType = "docker" + // DOTNET writes which dotnet version is currently active + DOTNET SegmentType = "dotnet" + // ELIXIR writes the elixir version + ELIXIR SegmentType = "elixir" + // EXECUTIONTIME writes the execution time of the last run command + EXECUTIONTIME SegmentType = "executiontime" + // EXIT writes the last exit code + EXIT SegmentType = "exit" + // FIREBASE writes the active firebase project + FIREBASE SegmentType = "firebase" + // FLUTTER writes the flutter version + FLUTTER SegmentType = "flutter" + // FORTRAN writes the gfortran version + FORTRAN SegmentType = "fortran" + // FOSSIL writes the fossil status + FOSSIL SegmentType = "fossil" + // GCP writes the active GCP context + GCP SegmentType = "gcp" + // GIT represents the git status and information + GIT SegmentType = "git" + // GITVERSION represents the gitversion information + GITVERSION SegmentType = "gitversion" + // GOLANG writes which go version is currently active + GOLANG SegmentType = "go" + // HASKELL segment + HASKELL SegmentType = "haskell" + // HELM segment + HELM SegmentType = "helm" + // IPIFY segment + IPIFY SegmentType = "ipify" + // JAVA writes the active java version + JAVA SegmentType = "java" + // API writes the output of a custom JSON API + HTTP SegmentType = "http" + // JUJUTSU writes Jujutsu source control information + JUJUTSU SegmentType = "jujutsu" + // JULIA writes which julia version is currently active + JULIA SegmentType = "julia" + // KOTLIN writes the active kotlin version + KOTLIN SegmentType = "kotlin" + // KUBECTL writes the Kubernetes context we're currently in + KUBECTL SegmentType = "kubectl" + // LASTFM writes the lastfm status + LASTFM SegmentType = "lastfm" + // LUA writes the active lua version + LUA SegmentType = "lua" + // MERCURIAL writes Mercurial source control information + MERCURIAL SegmentType = "mercurial" + // MOJO writes the active version of Mojo and the name of the Magic virtual env + MOJO SegmentType = "mojo" + // MVN writes the active maven version + MVN SegmentType = "mvn" + // NBA writes NBA game data + NBA SegmentType = "nba" + // NBGV writes the nbgv version information + NBGV SegmentType = "nbgv" + // NIGHTSCOUT is an open source diabetes system + NIGHTSCOUT SegmentType = "nightscout" + // NIM writes the active nim version + NIM SegmentType = "nim" + // NIXSHELL writes the active nix shell details + NIXSHELL SegmentType = "nix-shell" + // NODE writes which node version is currently active + NODE SegmentType = "node" + // npm version + NPM SegmentType = "npm" + // NX writes which Nx version us currently active + NX SegmentType = "nx" + // OCAML writes the active Ocaml version + OCAML SegmentType = "ocaml" + // OS write os specific icon + OS SegmentType = "os" + // OWM writes the weather coming from openweatherdata + OWM SegmentType = "owm" + // PATH represents the current path segment + PATH SegmentType = "path" + // PERL writes which perl version is currently active + PERL SegmentType = "perl" + // PHP writes which php version is currently active + PHP SegmentType = "php" + // PLASTIC represents the plastic scm status and information + PLASTIC SegmentType = "plastic" + // pnpm version + PNPM SegmentType = "pnpm" + // Project version + PROJECT SegmentType = "project" + // PULUMI writes the pulumi user, store and stack + PULUMI SegmentType = "pulumi" + // PYTHON writes the virtual env name + PYTHON SegmentType = "python" + // QUASAR writes the QUASAR version and context + QUASAR SegmentType = "quasar" + // R version + R SegmentType = "r" + // RAMADAN displays Sehar and Iftar prayer times during Ramadan + RAMADAN SegmentType = "ramadan" + // REACT writes the current react version + REACT SegmentType = "react" + // ROOT writes root symbol + ROOT SegmentType = "root" + // RUBY writes which ruby version is currently active + RUBY SegmentType = "ruby" + // RUST writes the cargo version information if cargo.toml is present + RUST SegmentType = "rust" + // SAPLING represents the sapling segment + SAPLING SegmentType = "sapling" + // SESSION represents the user info segment + SESSION SegmentType = "session" + // SHELL writes which shell we're currently in + SHELL SegmentType = "shell" + // SITECORE displays the current context for the Sitecore CLI + SITECORE SegmentType = "sitecore" + // SPOTIFY writes the SPOTIFY status for Mac + SPOTIFY SegmentType = "spotify" + // STATUS writes the last know command status + STATUS SegmentType = "status" + // STRAVA is a sports activity tracker + STRAVA SegmentType = "strava" + // Svelte segment + SVELTE SegmentType = "svelte" + // Subversion segment + SVN SegmentType = "svn" + // SWIFT writes the active swift version + SWIFT SegmentType = "swift" + // SYSTEMINFO writes system information (memory, cpu, load) + SYSTEMINFO SegmentType = "sysinfo" + // TALOSCTL writes the talosctl context + TALOSCTL SegmentType = "talosctl" + // TASKWARRIOR writes Taskwarrior task counts and context + TASKWARRIOR SegmentType = "taskwarrior" + // Tauri Segment + TAURI SegmentType = "tauri" + // TERRAFORM writes the terraform workspace we're currently in + TERRAFORM SegmentType = "terraform" + // TEXT writes a text + TEXT SegmentType = "text" + // TIME writes the current timestamp + TIME SegmentType = "time" + // TODOIST segment + TODOIST SegmentType = "todoist" + // UI5 Tooling segment + UI5TOOLING SegmentType = "ui5tooling" + // UMBRACO writes the Umbraco version if Umbraco is present + UMBRACO SegmentType = "umbraco" + // UNITY writes which Unity version is currently active + UNITY SegmentType = "unity" + // UPGRADE lets you know if you can upgrade Oh My Posh + UPGRADE SegmentType = "upgrade" + // V writes the active vlang version + V SegmentType = "v" + // VALA writes the active vala version + VALA SegmentType = "vala" + // WAKATIME writes tracked time spend in dev editors + WAKATIME SegmentType = "wakatime" + // WINGET writes the number of available WinGet package updates + WINGET SegmentType = "winget" + // WINREG queries the Windows registry. + WINREG SegmentType = "winreg" + // WITHINGS queries the Withings API. + WITHINGS SegmentType = "withings" + // XMAKE write the xmake version if xmake.lua is present + XMAKE SegmentType = "xmake" + // yarn version + YARN SegmentType = "yarn" + // YTM writes YouTube Music information and status + YTM SegmentType = "ytm" + // ZIG writes the active zig version + ZIG SegmentType = "zig" +) + +// Segments contains all available prompt segment writers. +// Consumers of the library can also add their own segment writer. +var Segments = map[SegmentType]func() SegmentWriter{ + ANGULAR: func() SegmentWriter { return &segments.Angular{} }, + ARGOCD: func() SegmentWriter { return &segments.Argocd{} }, + AURELIA: func() SegmentWriter { return &segments.Aurelia{} }, + AWS: func() SegmentWriter { return &segments.Aws{} }, + AZ: func() SegmentWriter { return &segments.Az{} }, + AZD: func() SegmentWriter { return &segments.Azd{} }, + AZFUNC: func() SegmentWriter { return &segments.AzFunc{} }, + BATTERY: func() SegmentWriter { return &segments.Battery{} }, + BAZEL: func() SegmentWriter { return &segments.Bazel{} }, + BREWFATHER: func() SegmentWriter { return &segments.Brewfather{} }, + BUF: func() SegmentWriter { return &segments.Buf{} }, + BUN: func() SegmentWriter { return &segments.Bun{} }, + CARBONINTENSITY: func() SegmentWriter { return &segments.CarbonIntensity{} }, + CDS: func() SegmentWriter { return &segments.Cds{} }, + CF: func() SegmentWriter { return &segments.Cf{} }, + CFTARGET: func() SegmentWriter { return &segments.CfTarget{} }, + CLAUDE: func() SegmentWriter { return &segments.Claude{} }, + CLOJURE: func() SegmentWriter { return &segments.Clojure{} }, + CMAKE: func() SegmentWriter { return &segments.Cmake{} }, + CONNECTION: func() SegmentWriter { return &segments.Connection{} }, + COPILOT: func() SegmentWriter { return &segments.Copilot{} }, + CRYSTAL: func() SegmentWriter { return &segments.Crystal{} }, + DART: func() SegmentWriter { return &segments.Dart{} }, + DENO: func() SegmentWriter { return &segments.Deno{} }, + DOCKER: func() SegmentWriter { return &segments.Docker{} }, + DOTNET: func() SegmentWriter { return &segments.Dotnet{} }, + ELIXIR: func() SegmentWriter { return &segments.Elixir{} }, + EXECUTIONTIME: func() SegmentWriter { return &segments.Executiontime{} }, + EXIT: func() SegmentWriter { return &segments.Status{} }, + FIREBASE: func() SegmentWriter { return &segments.Firebase{} }, + FLUTTER: func() SegmentWriter { return &segments.Flutter{} }, + FORTRAN: func() SegmentWriter { return &segments.Fortran{} }, + FOSSIL: func() SegmentWriter { return &segments.Fossil{} }, + GCP: func() SegmentWriter { return &segments.Gcp{} }, + GIT: func() SegmentWriter { return &segments.Git{} }, + GITVERSION: func() SegmentWriter { return &segments.GitVersion{} }, + GOLANG: func() SegmentWriter { return &segments.Golang{} }, + HASKELL: func() SegmentWriter { return &segments.Haskell{} }, + HELM: func() SegmentWriter { return &segments.Helm{} }, + IPIFY: func() SegmentWriter { return &segments.IPify{} }, + JAVA: func() SegmentWriter { return &segments.Java{} }, + HTTP: func() SegmentWriter { return &segments.HTTP{} }, + JUJUTSU: func() SegmentWriter { return &segments.Jujutsu{} }, + JULIA: func() SegmentWriter { return &segments.Julia{} }, + KOTLIN: func() SegmentWriter { return &segments.Kotlin{} }, + KUBECTL: func() SegmentWriter { return &segments.Kubectl{} }, + LASTFM: func() SegmentWriter { return &segments.LastFM{} }, + LUA: func() SegmentWriter { return &segments.Lua{} }, + MERCURIAL: func() SegmentWriter { return &segments.Mercurial{} }, + MOJO: func() SegmentWriter { return &segments.Mojo{} }, + MVN: func() SegmentWriter { return &segments.Mvn{} }, + NBA: func() SegmentWriter { return &segments.Nba{} }, + NBGV: func() SegmentWriter { return &segments.Nbgv{} }, + NIGHTSCOUT: func() SegmentWriter { return &segments.Nightscout{} }, + NIXSHELL: func() SegmentWriter { return &segments.NixShell{} }, + NIM: func() SegmentWriter { return &segments.Nim{} }, + NODE: func() SegmentWriter { return &segments.Node{} }, + NPM: func() SegmentWriter { return &segments.Npm{} }, + NX: func() SegmentWriter { return &segments.Nx{} }, + OCAML: func() SegmentWriter { return &segments.OCaml{} }, + OS: func() SegmentWriter { return &segments.Os{} }, + OWM: func() SegmentWriter { return &segments.Owm{} }, + PATH: func() SegmentWriter { return &segments.Path{} }, + PERL: func() SegmentWriter { return &segments.Perl{} }, + PHP: func() SegmentWriter { return &segments.Php{} }, + PLASTIC: func() SegmentWriter { return &segments.Plastic{} }, + PNPM: func() SegmentWriter { return &segments.Pnpm{} }, + PROJECT: func() SegmentWriter { return &segments.Project{} }, + PULUMI: func() SegmentWriter { return &segments.Pulumi{} }, + PYTHON: func() SegmentWriter { return &segments.Python{} }, + QUASAR: func() SegmentWriter { return &segments.Quasar{} }, + R: func() SegmentWriter { return &segments.R{} }, + RAMADAN: func() SegmentWriter { return &segments.Ramadan{} }, + REACT: func() SegmentWriter { return &segments.React{} }, + ROOT: func() SegmentWriter { return &segments.Root{} }, + RUBY: func() SegmentWriter { return &segments.Ruby{} }, + RUST: func() SegmentWriter { return &segments.Rust{} }, + SAPLING: func() SegmentWriter { return &segments.Sapling{} }, + SESSION: func() SegmentWriter { return &segments.Session{} }, + SHELL: func() SegmentWriter { return &segments.Shell{} }, + SITECORE: func() SegmentWriter { return &segments.Sitecore{} }, + SPOTIFY: func() SegmentWriter { return &segments.Spotify{} }, + STATUS: func() SegmentWriter { return &segments.Status{} }, + STRAVA: func() SegmentWriter { return &segments.Strava{} }, + SVELTE: func() SegmentWriter { return &segments.Svelte{} }, + SVN: func() SegmentWriter { return &segments.Svn{} }, + SWIFT: func() SegmentWriter { return &segments.Swift{} }, + SYSTEMINFO: func() SegmentWriter { return &segments.SystemInfo{} }, + TALOSCTL: func() SegmentWriter { return &segments.TalosCTL{} }, + TASKWARRIOR: func() SegmentWriter { return &segments.Taskwarrior{} }, + TAURI: func() SegmentWriter { return &segments.Tauri{} }, + TERRAFORM: func() SegmentWriter { return &segments.Terraform{} }, + TEXT: func() SegmentWriter { return &segments.Text{} }, + TIME: func() SegmentWriter { return &segments.Time{} }, + TODOIST: func() SegmentWriter { return &segments.Todoist{} }, + UI5TOOLING: func() SegmentWriter { return &segments.UI5Tooling{} }, + UMBRACO: func() SegmentWriter { return &segments.Umbraco{} }, + UNITY: func() SegmentWriter { return &segments.Unity{} }, + UPGRADE: func() SegmentWriter { return &segments.Upgrade{} }, + V: func() SegmentWriter { return &segments.V{} }, + VALA: func() SegmentWriter { return &segments.Vala{} }, + WAKATIME: func() SegmentWriter { return &segments.Wakatime{} }, + WINGET: func() SegmentWriter { return &segments.WinGet{} }, + WINREG: func() SegmentWriter { return &segments.WindowsRegistry{} }, + WITHINGS: func() SegmentWriter { return &segments.Withings{} }, + XMAKE: func() SegmentWriter { return &segments.XMake{} }, + YARN: func() SegmentWriter { return &segments.Yarn{} }, + YTM: func() SegmentWriter { return &segments.Ytm{} }, + ZIG: func() SegmentWriter { return &segments.Zig{} }, +} + +func (segment *Segment) MapSegmentWithWriter(env runtime.Environment) error { + segment.env = env + + if segment.Options == nil { + segment.Options = make(options.Map) + } + + f, ok := Segments[segment.Type] + if !ok { + return errors.New("unable to map writer") + } + + writer := f() + writer.Init(segment.Options, env) + segment.writer = writer + + return nil +} diff --git a/src/config_test.go b/src/config_test.go deleted file mode 100644 index 2ea3b81557e2..000000000000 --- a/src/config_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSettingsExportJSON(t *testing.T) { - content := exportConfig("../themes/jandedobbeleer.omp.json", "json") - assert.NotContains(t, content, "\\u003ctransparent\\u003e") - assert.Contains(t, content, "") -} diff --git a/src/console_title.go b/src/console_title.go deleted file mode 100644 index e3719ea5481f..000000000000 --- a/src/console_title.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -type consoleTitle struct { - env environmentInfo - config *Config - ansi *ansiUtils -} - -// ConsoleTitleStyle defines how to show the title in the console window -type ConsoleTitleStyle string - -const ( - // FolderName show the current folder name - FolderName ConsoleTitleStyle = "folder" - // FullPath show the current path - FullPath ConsoleTitleStyle = "path" - // Template allows a more powerful custom string - Template ConsoleTitleStyle = "template" -) - -func (t *consoleTitle) getConsoleTitle() string { - var title string - switch t.config.ConsoleTitleStyle { - case FullPath: - title = t.getPwd() - case Template: - title = t.getTemplateText() - case FolderName: - fallthrough - default: - title = base(t.getPwd(), t.env) - } - title = t.ansi.escapeText(title) - return fmt.Sprintf(t.ansi.title, title) -} - -func (t *consoleTitle) getTemplateText() string { - context := make(map[string]interface{}) - - context["Root"] = t.env.isRunningAsRoot() - context["Path"] = t.getPwd() - context["Folder"] = base(t.getPwd(), t.env) - context["Shell"] = t.env.getShellName() - context["User"] = t.env.getCurrentUser() - context["Host"] = "" - if host, err := t.env.getHostName(); err == nil { - context["Host"] = host - } - - template := &textTemplate{ - Template: t.config.ConsoleTitleTemplate, - Context: context, - Env: t.env, - } - text, err := template.render() - if err != nil { - return err.Error() - } - return text -} - -func (t *consoleTitle) getPwd() string { - pwd := t.env.getcwd() - pwd = strings.Replace(pwd, t.env.homeDir(), "~", 1) - return pwd -} diff --git a/src/console_title_test.go b/src/console_title_test.go deleted file mode 100644 index bbc42f42d328..000000000000 --- a/src/console_title_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetConsoleTitle(t *testing.T) { - cases := []struct { - Style ConsoleTitleStyle - Template string - Root bool - User string - Cwd string - PathSeperator string - ShellName string - Expected string - }{ - {Style: FolderName, Cwd: "/usr/home", PathSeperator: "/", ShellName: "default", Expected: "\x1b]0;~\a"}, - {Style: FullPath, Cwd: "/usr/home/jan", PathSeperator: "/", ShellName: "default", Expected: "\x1b]0;~/jan\a"}, - { - Style: Template, - Template: "{{.Env.USERDOMAIN}} :: {{.Path}}{{if .Root}} :: Admin{{end}} :: {{.Shell}}", - Cwd: "C:\\vagrant", - PathSeperator: "\\", - ShellName: "PowerShell", - Root: true, - Expected: "\x1b]0;MyCompany :: C:\\vagrant :: Admin :: PowerShell\a", - }, - { - Style: Template, - Template: "{{.Folder}}{{if .Root}} :: Admin{{end}} :: {{.Shell}}", - Cwd: "C:\\vagrant", - PathSeperator: "\\", - ShellName: "PowerShell", - Expected: "\x1b]0;vagrant :: PowerShell\a", - }, - { - Style: Template, - Template: "{{.User}}@{{.Host}}{{if .Root}} :: Admin{{end}} :: {{.Shell}}", - Root: true, - User: "MyUser", - PathSeperator: "\\", - ShellName: "PowerShell", - Expected: "\x1b]0;MyUser@MyHost :: Admin :: PowerShell\a", - }, - } - - for _, tc := range cases { - config := &Config{ - ConsoleTitleStyle: tc.Style, - ConsoleTitleTemplate: tc.Template, - } - env := new(MockedEnvironment) - env.On("getcwd", nil).Return(tc.Cwd) - env.On("homeDir", nil).Return("/usr/home") - env.On("getPathSeperator", nil).Return(tc.PathSeperator) - env.On("isRunningAsRoot", nil).Return(tc.Root) - env.On("getShellName", nil).Return(tc.ShellName) - env.On("getenv", "USERDOMAIN").Return("MyCompany") - env.On("getCurrentUser", nil).Return("MyUser") - env.On("getHostName", nil).Return("MyHost", nil) - ansi := &ansiUtils{} - ansi.init(tc.ShellName) - ct := &consoleTitle{ - env: env, - config: config, - ansi: ansi, - } - got := ct.getConsoleTitle() - assert.Equal(t, tc.Expected, got) - } -} - -func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) { - cases := []struct { - Style ConsoleTitleStyle - Template string - Root bool - User string - Cwd string - PathSeperator string - ShellName string - Expected string - }{ - { - Style: Template, - Template: "Not using Host only {{.User}} and {{.Shell}}", - User: "MyUser", - PathSeperator: "\\", - ShellName: "PowerShell", - Expected: "\x1b]0;Not using Host only MyUser and PowerShell\a", - }, - { - Style: Template, - Template: "{{.User}}@{{.Host}} :: {{.Shell}}", - User: "MyUser", - PathSeperator: "\\", - ShellName: "PowerShell", - Expected: "\x1b]0;MyUser@ :: PowerShell\a", - }, - } - - for _, tc := range cases { - config := &Config{ - ConsoleTitleStyle: tc.Style, - ConsoleTitleTemplate: tc.Template, - } - env := new(MockedEnvironment) - env.On("getcwd", nil).Return(tc.Cwd) - env.On("homeDir", nil).Return("/usr/home") - env.On("getPathSeperator", nil).Return(tc.PathSeperator) - env.On("isRunningAsRoot", nil).Return(tc.Root) - env.On("getShellName", nil).Return(tc.ShellName) - env.On("getenv", "USERDOMAIN").Return("MyCompany") - env.On("getCurrentUser", nil).Return("MyUser") - env.On("getHostName", nil).Return("", fmt.Errorf("I have a bad feeling about this")) - ansi := &ansiUtils{} - ansi.init(tc.ShellName) - ct := &consoleTitle{ - env: env, - config: config, - ansi: ansi, - } - got := ct.getConsoleTitle() - assert.Equal(t, tc.Expected, got) - } -} diff --git a/src/constants/constants_unix.go b/src/constants/constants_unix.go new file mode 100644 index 000000000000..62c4c00e039b --- /dev/null +++ b/src/constants/constants_unix.go @@ -0,0 +1,7 @@ +//go:build !windows + +package constants + +const ( + DotnetExitCode = 142 +) diff --git a/src/constants/constants_windows.go b/src/constants/constants_windows.go new file mode 100644 index 000000000000..9efc27598a7f --- /dev/null +++ b/src/constants/constants_windows.go @@ -0,0 +1,5 @@ +package constants + +const ( + DotnetExitCode = int(0x80008091) +) diff --git a/src/dsc/cli.go b/src/dsc/cli.go new file mode 100644 index 000000000000..52faaead8aae --- /dev/null +++ b/src/dsc/cli.go @@ -0,0 +1,86 @@ +package dsc + +import ( + "fmt" + "os" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/spf13/cobra" +) + +var ( + state string +) + +type resource interface { + Load() + Save() + Resolve() + ToJSON() string + Schema() string + Apply(schema string) error + Test(input string) error +} + +func Command(r resource) *cobra.Command { + cmd := &cobra.Command{ + Use: "dsc", + Short: "Manage Oh My Posh DSC (Desired State Configuration)", + Long: "Manage Oh My Posh DSC (Desired State Configuration).", + ValidArgs: []string{"get", "set", "test", "schema", "export"}, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + env := &runtime.Terminal{} + env.Init(&runtime.Flags{}) + + cache.Init(os.Getenv("POSH_SHELL"), cache.Persist) + + defer func() { + cache.Close() + }() + + var err error + + switch args[0] { + case "get", "export": + r.Load() + r.Resolve() + fmt.Print(r.ToJSON()) + case "set": + if state == "" { + err = newError("please provide a state configuration to set") + break + } + + r.Load() + err = r.Apply(state) + case "schema": + fmt.Print(r.Schema()) + case "test": + if state == "" { + err = newError("please provide a state configuration to test") + break + } + + r.Load() + err = r.Test(state) + default: + _ = cmd.Help() + return + } + + if err != nil { + fmt.Println(err.Error()) + return + } + }, + } + + cmd.Flags().StringVar(&state, "state", "", "State configuration to set") + return cmd +} diff --git a/src/dsc/error.go b/src/dsc/error.go new file mode 100644 index 000000000000..b1e29f1b811c --- /dev/null +++ b/src/dsc/error.go @@ -0,0 +1,17 @@ +package dsc + +type Error struct { + message string +} + +func (e *Error) Error() string { + return `{ + "error": "` + e.message + `" +}` +} + +func newError(message string) *Error { + return &Error{ + message: message, + } +} diff --git a/src/dsc/resource.go b/src/dsc/resource.go new file mode 100644 index 000000000000..a415079f3b3a --- /dev/null +++ b/src/dsc/resource.go @@ -0,0 +1,127 @@ +package dsc + +import ( + "bytes" + "encoding/json" + "errors" + "reflect" + "strings" + + "github.com/invopop/jsonschema" + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +type Resource[T State[T]] struct { + States []T `json:"states,omitempty" jsonschema:"title=states,description=The different states of the resource"` +} + +type State[T any] interface { + Equal(state T) bool + Apply() error + Resolve() (T, bool) +} + +func (resource *Resource[T]) Load() { + states, ok := cache.Get[[]T](cache.Device, resource.cacheKey()) + if !ok { + log.Debug("no states found in cache") + return + } + + resource.States = states +} + +func (resource *Resource[T]) Save() { + cache.Set(cache.Device, resource.cacheKey(), resource.States, cache.INFINITE) +} + +func (resource *Resource[T]) Add(item T) { + for _, existingItem := range resource.States { + if existingItem.Equal(item) { + log.Debug("item already exists") + return + } + } + + log.Debug("adding item") + + resource.States = append(resource.States, item) +} + +func (resource *Resource[T]) Resolve() { + for _, item := range resource.States { + if resolvedItem, ok := item.Resolve(); ok { + resource.States = append(resource.States, resolvedItem) + } + } +} + +func (resource *Resource[T]) Apply(schema string) error { + log.Debug("applying items") + + err := json.Unmarshal([]byte(schema), resource) + if err != nil { + return newError(err.Error()) + } + + // TODO: validate if we need to filter out States + // which are already available in the cache (and thus set) + + for _, item := range resource.States { + if applyErr := item.Apply(); applyErr != nil { + log.Error(applyErr) + err = errors.Join(err, applyErr) + } + } + + log.Debug("items applied") + + resource.Save() + + if err != nil { + return newError(err.Error()) + } + + return nil +} + +func (resource *Resource[T]) Test(_ string) error { + return newError("test functionality not implemented") +} + +func (resource *Resource[T]) Schema() string { + reflector := jsonschema.Reflector{ + ExpandedStruct: true, + DoNotReference: true, + } + + schema := reflector.Reflect(resource) + schema.ID = jsonschema.ID(resource.getItemTypeName()) + schema.Properties.Delete("$schema") + schemaJSON, _ := json.MarshalIndent(schema, "", " ") + + return string(schemaJSON) +} + +func (resource *Resource[T]) getItemTypeName() string { + var zero T + t := reflect.TypeOf(zero) + if t.Kind() == reflect.Pointer { + return strings.ToLower(t.Elem().Name()) + } + + return strings.ToLower(t.Name()) +} + +func (resource *Resource[T]) cacheKey() string { + return "DSC_" + strings.ToUpper(resource.getItemTypeName()) +} + +func (resource *Resource[T]) ToJSON() string { + var result bytes.Buffer + jsonEncoder := json.NewEncoder(&result) + jsonEncoder.SetEscapeHTML(false) + _ = jsonEncoder.Encode(resource) + return result.String() +} diff --git a/src/engine.go b/src/engine.go deleted file mode 100644 index dbff67aa63a5..000000000000 --- a/src/engine.go +++ /dev/null @@ -1,219 +0,0 @@ -package main - -import ( - "fmt" - "strings" - "time" -) - -type engine struct { - config *Config - env environmentInfo - colorWriter colorWriter - ansi *ansiUtils - consoleTitle *consoleTitle - - console strings.Builder - rprompt string -} - -func (e *engine) write(text string) { - e.console.WriteString(text) -} - -func (e *engine) string() string { - return e.console.String() -} - -func (e *engine) canWriteRPrompt() bool { - prompt := e.string() - consoleWidth, err := e.env.getTerminalWidth() - if err != nil || consoleWidth == 0 { - return true - } - promptWidth := e.ansi.lenWithoutANSI(prompt) - availableSpace := consoleWidth - promptWidth - if promptWidth > consoleWidth { - availableSpace = promptWidth - (promptWidth % consoleWidth) - } - promptBreathingRoom := 30 - return (availableSpace - e.ansi.lenWithoutANSI(e.rprompt)) >= promptBreathingRoom -} - -func (e *engine) render() string { - for _, block := range e.config.Blocks { - e.renderBlock(block) - } - if e.config.ConsoleTitle { - e.write(e.consoleTitle.getConsoleTitle()) - } - e.write(e.ansi.creset) - if e.config.FinalSpace { - e.write(" ") - } - - if !e.config.OSC99 { - return e.print() - } - cwd := e.env.getcwd() - if e.env.isWsl() { - cwd, _ = e.env.runCommand("wslpath", "-m", cwd) - } - e.write(e.ansi.consolePwd(cwd)) - return e.print() -} - -func (e *engine) renderBlock(block *Block) { - // when in bash, for rprompt blocks we need to write plain - // and wrap in escaped mode or the prompt will not render correctly - if block.Type == RPrompt && e.env.getShellName() == bash { - block.initPlain(e.env, e.config) - } else { - block.init(e.env, e.colorWriter, e.ansi) - } - block.setStringValues() - if !block.enabled() { - return - } - if block.Newline { - e.write("\n") - } - switch block.Type { - // This is deprecated but leave if to not break current configs - // It is encouraged to used "newline": true on block level - // rather than the standalone the linebreak block - case LineBreak: - e.write("\n") - case Prompt: - if block.VerticalOffset != 0 { - e.write(e.ansi.changeLine(block.VerticalOffset)) - } - switch block.Alignment { - case Right: - e.write(e.ansi.carriageForward()) - blockText := block.renderSegments() - e.write(e.ansi.getCursorForRightWrite(blockText, block.HorizontalOffset)) - e.write(blockText) - case Left: - e.write(block.renderSegments()) - } - case RPrompt: - blockText := block.renderSegments() - if e.env.getShellName() == bash { - blockText = fmt.Sprintf(e.ansi.bashFormat, blockText) - } - e.rprompt = blockText - } - // Due to a bug in Powershell, the end of the line needs to be cleared. - // If this doesn't happen, the portion after the prompt gets colored in the background - // color of the line above the new input line. Clearing the line fixes this, - // but can hopefully one day be removed when this is resolved natively. - if e.ansi.shell == pwsh || e.ansi.shell == powershell5 { - e.write(e.ansi.clearEOL) - } -} - -// debug will loop through your config file and output the timings for each segments -func (e *engine) debug() string { - var segmentTimings []*SegmentTiming - largestSegmentNameLength := 0 - e.write("\n\x1b[1mHere are the timings of segments in your prompt:\x1b[0m\n\n") - - // console title timing - start := time.Now() - consoleTitle := e.consoleTitle.getTemplateText() - duration := time.Since(start) - segmentTiming := &SegmentTiming{ - name: "ConsoleTitle", - nameLength: 12, - enabled: e.config.ConsoleTitle, - stringValue: consoleTitle, - enabledDuration: 0, - stringDuration: duration, - } - segmentTimings = append(segmentTimings, segmentTiming) - // loop each segments of each blocks - for _, block := range e.config.Blocks { - block.init(e.env, e.colorWriter, e.ansi) - longestSegmentName, timings := block.debug() - segmentTimings = append(segmentTimings, timings...) - if longestSegmentName > largestSegmentNameLength { - largestSegmentNameLength = longestSegmentName - } - } - - // pad the output so the tabs render correctly - largestSegmentNameLength += 7 - for _, segment := range segmentTimings { - duration := segment.enabledDuration.Milliseconds() - if segment.enabled { - duration += segment.stringDuration.Milliseconds() - } - segmentName := fmt.Sprintf("%s(%t)", segment.name, segment.enabled) - e.write(fmt.Sprintf("%-*s - %3d ms - %s\n", largestSegmentNameLength, segmentName, duration, segment.stringValue)) - } - return e.string() -} - -func (e *engine) print() string { - switch e.env.getShellName() { - case zsh: - if !*e.env.getArgs().Eval { - break - } - // escape double quotes contained in the prompt - prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.string(), "\"", "\"\"")) - prompt += fmt.Sprintf("\nRPROMPT=\"%s\"", e.rprompt) - return prompt - case pwsh, powershell5, bash, plain: - if e.rprompt == "" || !e.canWriteRPrompt() { - break - } - e.write(e.ansi.saveCursorPosition) - e.write(e.ansi.carriageForward()) - e.write(e.ansi.getCursorForRightWrite(e.rprompt, 0)) - e.write(e.rprompt) - e.write(e.ansi.restoreCursorPosition) - } - return e.string() -} - -func (e *engine) renderTooltip(tip string) string { - tip = strings.Trim(tip, " ") - var tooltip *Segment - for _, tp := range e.config.Tooltips { - if !tp.shouldInvokeWithTip(tip) { - continue - } - tooltip = tp - } - if tooltip == nil { - return "" - } - if err := tooltip.mapSegmentWithWriter(e.env); err != nil { - return "" - } - if !tooltip.enabled() { - return "" - } - tooltip.stringValue = tooltip.string() - // little hack to reuse the current logic - block := &Block{ - Alignment: Right, - Segments: []*Segment{tooltip}, - } - switch e.env.getShellName() { - case zsh: - block.init(e.env, e.colorWriter, e.ansi) - return block.renderSegments() - case pwsh, powershell5: - block.initPlain(e.env, e.config) - tooltipText := block.renderSegments() - e.write(e.ansi.clearEOL) - e.write(e.ansi.carriageForward()) - e.write(e.ansi.getCursorForRightWrite(tooltipText, 0)) - e.write(tooltipText) - return e.string() - } - return "" -} diff --git a/src/engine_test.go b/src/engine_test.go deleted file mode 100644 index 8b39d62c31fb..000000000000 --- a/src/engine_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "errors" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCanWriteRPrompt(t *testing.T) { - cases := []struct { - Case string - Expected bool - TerminalWidth int - TerminalWidthError error - PromptLength int - RPromptLength int - }{ - {Case: "Width Error", Expected: true, TerminalWidthError: errors.New("burp")}, - {Case: "Terminal > Prompt enabled", Expected: true, TerminalWidth: 200, PromptLength: 100, RPromptLength: 10}, - {Case: "Terminal > Prompt enabled edge", Expected: true, TerminalWidth: 200, PromptLength: 100, RPromptLength: 70}, - {Case: "Terminal > Prompt disabled no breathing", Expected: false, TerminalWidth: 200, PromptLength: 100, RPromptLength: 71}, - {Case: "Prompt > Terminal enabled", Expected: true, TerminalWidth: 200, PromptLength: 300, RPromptLength: 70}, - {Case: "Prompt > Terminal disabled no breathing", Expected: true, TerminalWidth: 200, PromptLength: 300, RPromptLength: 80}, - {Case: "Prompt > Terminal disabled no room", Expected: true, TerminalWidth: 200, PromptLength: 400, RPromptLength: 80}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getTerminalWidth", nil).Return(tc.TerminalWidth, tc.TerminalWidthError) - ansi := &ansiUtils{} - ansi.init(plain) - engine := &engine{ - env: env, - ansi: ansi, - } - engine.rprompt = strings.Repeat("x", tc.RPromptLength) - engine.console.WriteString(strings.Repeat("x", tc.PromptLength)) - got := engine.canWriteRPrompt() - assert.Equal(t, tc.Expected, got) - } -} diff --git a/src/environment.go b/src/environment.go deleted file mode 100644 index e7aa0c8d7f54..000000000000 --- a/src/environment.go +++ /dev/null @@ -1,339 +0,0 @@ -package main - -import ( - "context" - "errors" - "io/ioutil" - "net/http" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - "sync" - "time" - - "github.com/distatus/battery" - "github.com/shirou/gopsutil/host" - "github.com/shirou/gopsutil/process" -) - -const ( - unknown = "unknown" - windowsPlatform = "windows" - darwinPlatform = "darwin" - linuxPlatform = "linux" -) - -type commandError struct { - err string - exitCode int -} - -func (e *commandError) Error() string { - return e.err -} - -type noBatteryError struct{} - -func (m *noBatteryError) Error() string { - return "no battery" -} - -type fileInfo struct { - parentFolder string - path string - isDir bool -} - -type environmentInfo interface { - getenv(key string) string - getcwd() string - homeDir() string - hasFiles(pattern string) bool - hasFilesInDir(dir, pattern string) bool - hasFolder(folder string) bool - getFileContent(file string) string - getPathSeperator() string - getCurrentUser() string - isRunningAsRoot() bool - getHostName() (string, error) - getRuntimeGOOS() string - getPlatform() string - hasCommand(command string) bool - runCommand(command string, args ...string) (string, error) - runShellCommand(shell, command string) string - lastErrorCode() int - executionTime() float64 - getArgs() *args - getBatteryInfo() ([]*battery.Battery, error) - getShellName() string - getWindowTitle(imageName, windowTitleRegex string) (string, error) - doGet(url string) ([]byte, error) - hasParentFilePath(path string) (fileInfo *fileInfo, err error) - isWsl() bool - stackCount() int - getTerminalWidth() (int, error) -} - -type commandCache struct { - commands map[string]string - lock sync.RWMutex -} - -func (c *commandCache) set(command, path string) { - c.lock.Lock() - defer c.lock.Unlock() - c.commands[command] = path -} - -func (c *commandCache) get(command string) (string, bool) { - c.lock.RLock() - defer c.lock.RUnlock() - if cmd, ok := c.commands[command]; ok { - command = cmd - return command, true - } - return "", false -} - -type environment struct { - args *args - cwd string - cmdCache *commandCache -} - -func (env *environment) init(args *args) { - env.args = args - cmdCache := &commandCache{ - commands: make(map[string]string), - lock: sync.RWMutex{}, - } - env.cmdCache = cmdCache -} - -func (env *environment) getenv(key string) string { - return os.Getenv(key) -} - -func (env *environment) getcwd() string { - if env.cwd != "" { - return env.cwd - } - correctPath := func(pwd string) string { - // on Windows, and being case sensitive and not consistent and all, this gives silly issues - return strings.Replace(pwd, "c:", "C:", 1) - } - if env.args != nil && *env.args.PWD != "" { - env.cwd = correctPath(*env.args.PWD) - return env.cwd - } - dir, err := os.Getwd() - if err != nil { - return "" - } - env.cwd = correctPath(dir) - return env.cwd -} - -func (env *environment) hasFiles(pattern string) bool { - cwd := env.getcwd() - pattern = cwd + env.getPathSeperator() + pattern - matches, err := filepath.Glob(pattern) - if err != nil { - return false - } - return len(matches) > 0 -} - -func (env *environment) hasFilesInDir(dir, pattern string) bool { - pattern = dir + env.getPathSeperator() + pattern - matches, err := filepath.Glob(pattern) - if err != nil { - return false - } - return len(matches) > 0 -} - -func (env *environment) hasFolder(folder string) bool { - _, err := os.Stat(folder) - return !os.IsNotExist(err) -} - -func (env *environment) getFileContent(file string) string { - content, err := ioutil.ReadFile(file) - if err != nil { - return "" - } - return string(content) -} - -func (env *environment) getPathSeperator() string { - return string(os.PathSeparator) -} - -func (env *environment) getCurrentUser() string { - user := os.Getenv("USER") - if user == "" { - user = os.Getenv("USERNAME") - } - return user -} - -func (env *environment) getHostName() (string, error) { - hostName, err := os.Hostname() - if err != nil { - return "", err - } - return cleanHostName(hostName), nil -} - -func (env *environment) getRuntimeGOOS() string { - return runtime.GOOS -} - -func (env *environment) getPlatform() string { - if runtime.GOOS == windowsPlatform { - return windowsPlatform - } - p, _, _, _ := host.PlatformInformation() - - return p -} - -func (env *environment) runCommand(command string, args ...string) (string, error) { - if cmd, ok := env.cmdCache.get(command); ok { - command = cmd - } - out, err := exec.Command(command, args...).CombinedOutput() - if err != nil { - if exitErr, ok := err.(*exec.ExitError); ok { - return "", &commandError{ - err: exitErr.Error(), - exitCode: exitErr.ExitCode(), - } - } - } - return strings.TrimSpace(string(out)), nil -} - -func (env *environment) runShellCommand(shell, command string) string { - out, _ := env.runCommand(shell, "-c", command) - return out -} - -func (env *environment) hasCommand(command string) bool { - if _, ok := env.cmdCache.get(command); ok { - return true - } - path, err := exec.LookPath(command) - if err == nil { - env.cmdCache.set(command, path) - return true - } - return false -} - -func (env *environment) lastErrorCode() int { - return *env.args.ErrorCode -} - -func (env *environment) executionTime() float64 { - if *env.args.ExecutionTime < 0 { - return 0 - } - return *env.args.ExecutionTime -} - -func (env *environment) getArgs() *args { - return env.args -} - -func (env *environment) getBatteryInfo() ([]*battery.Battery, error) { - return battery.GetAll() -} - -func (env *environment) getShellName() string { - if *env.args.Shell != "" { - return *env.args.Shell - } - pid := os.Getppid() - p, _ := process.NewProcess(int32(pid)) - name, err := p.Name() - if err != nil { - return unknown - } - if name == "cmd.exe" { - p, _ = p.Parent() - name, err = p.Name() - } - if err != nil { - return unknown - } - // Cache the shell value to speed things up. - *env.args.Shell = strings.Trim(strings.Replace(name, ".exe", "", 1), " ") - return *env.args.Shell -} - -func (env *environment) doGet(url string) ([]byte, error) { - ctx, cncl := context.WithTimeout(context.Background(), time.Millisecond*20) - defer cncl() - request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, err - } - response, err := client.Do(request) - if err != nil { - return nil, err - } - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - return body, nil -} - -func (env *environment) hasParentFilePath(path string) (*fileInfo, error) { - currentFolder := env.getcwd() - for { - searchPath := filepath.Join(currentFolder, path) - info, err := os.Stat(searchPath) - if err == nil { - return &fileInfo{ - parentFolder: currentFolder, - path: searchPath, - isDir: info.IsDir(), - }, nil - } - if !os.IsNotExist(err) { - return nil, err - } - if dir := filepath.Dir(currentFolder); dir != currentFolder { - currentFolder = dir - continue - } - return nil, errors.New("no match at root level") - } -} - -func (env *environment) stackCount() int { - if *env.args.StackCount < 0 { - return 0 - } - return *env.args.StackCount -} - -func cleanHostName(hostName string) string { - garbage := []string{ - ".lan", - ".local", - ".localdomain", - } - for _, g := range garbage { - if strings.HasSuffix(hostName, g) { - hostName = strings.Replace(hostName, g, "", 1) - } - } - return hostName -} diff --git a/src/environment_test.go b/src/environment_test.go deleted file mode 100644 index b0217a5d565a..000000000000 --- a/src/environment_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNormalHostName(t *testing.T) { - hostName := "hello" - assert.Equal(t, hostName, cleanHostName(hostName)) -} - -func TestHostNameWithLocal(t *testing.T) { - hostName := "hello.local" - assert.Equal(t, "hello", cleanHostName(hostName)) -} - -func TestHostNameWithLan(t *testing.T) { - hostName := "hello.lan" - cleanHostName := cleanHostName(hostName) - assert.Equal(t, "hello", cleanHostName) -} diff --git a/src/environment_unix.go b/src/environment_unix.go deleted file mode 100644 index ffce7bc131fc..000000000000 --- a/src/environment_unix.go +++ /dev/null @@ -1,35 +0,0 @@ -// +build !windows - -package main - -import ( - "errors" - "os" - - terminal "github.com/wayneashleyberry/terminal-dimensions" -) - -func (env *environment) isRunningAsRoot() bool { - return os.Geteuid() == 0 -} - -func (env *environment) homeDir() string { - return os.Getenv("HOME") -} - -func (env *environment) getWindowTitle(imageName, windowTitleRegex string) (string, error) { - return "", errors.New("not implemented") -} - -func (env *environment) isWsl() bool { - // one way to check - // version := env.getFileContent("/proc/version") - // return strings.Contains(version, "microsoft") - // using env variable - return env.getenv("WSL_DISTRO_NAME") != "" -} - -func (env *environment) getTerminalWidth() (int, error) { - width, err := terminal.Width() - return int(width), err -} diff --git a/src/environment_windows.go b/src/environment_windows.go deleted file mode 100644 index 66c293d15a1f..000000000000 --- a/src/environment_windows.go +++ /dev/null @@ -1,68 +0,0 @@ -// +build windows - -package main - -import ( - "errors" - "os" - - "golang.org/x/sys/windows" -) - -func (env *environment) isRunningAsRoot() bool { - var sid *windows.SID - - // Although this looks scary, it is directly copied from the - // official windows documentation. The Go API for this is a - // direct wrap around the official C++ API. - // See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-checktokenmembership - err := windows.AllocateAndInitializeSid( - &windows.SECURITY_NT_AUTHORITY, - 2, - windows.SECURITY_BUILTIN_DOMAIN_RID, - windows.DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, - &sid) - if err != nil { - return false - } - defer func() { - _ = windows.FreeSid(sid) - }() - - // This appears to cast a null pointer so I'm not sure why this - // works, but this guy says it does and it Works for Me™: - // https://github.com/golang/go/issues/28804#issuecomment-438838144 - token := windows.Token(0) - - member, err := token.IsMember(sid) - if err != nil { - return false - } - - return member -} - -func (env *environment) homeDir() string { - // return the right HOME reference when using MSYS2 - if env.getShellName() == bash { - return os.Getenv("HOME") - } - home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") - if home == "" { - home = os.Getenv("USERPROFILE") - } - return home -} - -func (env *environment) getWindowTitle(imageName, windowTitleRegex string) (string, error) { - return getWindowTitle(imageName, windowTitleRegex) -} - -func (env *environment) isWsl() bool { - return false -} - -func (env *environment) getTerminalWidth() (int, error) { - return 0, errors.New("Unsupported on Windows") -} diff --git a/src/environment_windows_win32.go b/src/environment_windows_win32.go deleted file mode 100644 index 3da608869a0e..000000000000 --- a/src/environment_windows_win32.go +++ /dev/null @@ -1,181 +0,0 @@ -// +build windows - -package main - -import ( - "fmt" - "strings" - "syscall" - "unsafe" - - "golang.org/x/sys/windows" -) - -// WindowsProcess is an implementation of Process for Windows. -type WindowsProcess struct { - pid int - ppid int - exe string -} - -// getImagePid returns the -func getImagePid(imageName string) ([]int, error) { - processes, err := processes() - if err != nil { - return nil, err - } - var pids []int - for i := 0; i < len(processes); i++ { - if strings.ToLower(processes[i].exe) == imageName { - pids = append(pids, processes[i].pid) - } - } - return pids, nil -} - -// getWindowTitle returns the title of a window linked to a process name -func getWindowTitle(imageName, windowTitleRegex string) (string, error) { - processPid, err := getImagePid(imageName) - if err != nil { - return "", nil - } - - // is a spotify process running? - // no: returns an empty string - if len(processPid) == 0 { - return "", nil - } - - // returns the first window of the first pid - _, windowTitle := GetWindowTitle(processPid[0], windowTitleRegex) - - return windowTitle, nil -} - -func newWindowsProcess(e *windows.ProcessEntry32) *WindowsProcess { - // Find when the string ends for decoding - end := 0 - for { - if e.ExeFile[end] == 0 { - break - } - end++ - } - - return &WindowsProcess{ - pid: int(e.ProcessID), - ppid: int(e.ParentProcessID), - exe: syscall.UTF16ToString(e.ExeFile[:end]), - } -} - -// Processes returns a snapshot of all the processes -// Taken and adapted from https://github.com/mitchellh/go-ps -func processes() ([]WindowsProcess, error) { - // get process table snapshot - handle, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) - if err != nil { - return nil, syscall.GetLastError() - } - defer func() { - _ = windows.CloseHandle(handle) - }() - - // get process infor by looping through the snapshot - var entry windows.ProcessEntry32 - entry.Size = uint32(unsafe.Sizeof(entry)) - err = windows.Process32First(handle, &entry) - if err != nil { - return nil, fmt.Errorf("error retrieving process info") - } - - results := make([]WindowsProcess, 0, 50) - for { - results = append(results, *newWindowsProcess(&entry)) - err := windows.Process32Next(handle, &entry) - if err != nil { - if err == syscall.ERROR_NO_MORE_FILES { - break - } - return nil, fmt.Errorf("Fail to syscall Process32Next: %v", err) - } - } - - return results, nil -} - -// win32 specific code - -// win32 dll load and function definitions -var ( - user32 = syscall.NewLazyDLL("user32.dll") - procEnumWindows = user32.NewProc("EnumWindows") - procGetWindowTextW = user32.NewProc("GetWindowTextW") - procGetWindowThreadProcessID = user32.NewProc("GetWindowThreadProcessId") -) - -// EnumWindows call EnumWindows from user32 and returns all active windows -// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows -func EnumWindows(enumFunc, lparam uintptr) (err error) { - r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, enumFunc, lparam, 0) - if r1 == 0 { - if e1 != 0 { - err = error(e1) - } else { - err = syscall.EINVAL - } - } - return -} - -// GetWindowText returns the title and text of a window from a window handle -// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextw -func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (length int32, err error) { - r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount)) - length = int32(r0) - if length == 0 { - if e1 != 0 { - err = error(e1) - } else { - err = syscall.EINVAL - } - } - return -} - -// GetWindowTitle searches for a window attached to the pid -func GetWindowTitle(pid int, windowTitleRegex string) (syscall.Handle, string) { - var hwnd syscall.Handle - var title string - - // callback fro EnumWindows - cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr { - var prcsID int - // get pid - _, _, _ = procGetWindowThreadProcessID.Call(uintptr(h), uintptr(unsafe.Pointer(&prcsID))) - // check if pid matches spotify pid - if prcsID == pid { - b := make([]uint16, 200) - _, err := GetWindowText(h, &b[0], int32(len(b))) - if err != nil { - // ignore the error - return 1 // continue enumeration - } - title = syscall.UTF16ToString(b) - if matchString(windowTitleRegex, title) { - // will cause EnumWindows to return 0 (error) - // but we don't want to enumerate all windows since we got what we want - hwnd = h - return 0 - } - } - - return 1 // continue enumeration - }) - // Enumerates all top-level windows on the screen - // The error is not checked because if EnumWindows is stopped bofere enumerating all windows - // it returns 0(error occurred) instead of 1(success) - // In our case, title will equal "" or the title of the window anyway - _ = EnumWindows(cb, 0) - return hwnd, title -} diff --git a/src/font/VictorMono-Bold.ttf b/src/font/VictorMono-Bold.ttf deleted file mode 100644 index 6f34adb3ed98..000000000000 Binary files a/src/font/VictorMono-Bold.ttf and /dev/null differ diff --git a/src/font/VictorMono-Italic.ttf b/src/font/VictorMono-Italic.ttf deleted file mode 100644 index 21cacaaee655..000000000000 Binary files a/src/font/VictorMono-Italic.ttf and /dev/null differ diff --git a/src/font/VictorMono-Regular.ttf b/src/font/VictorMono-Regular.ttf deleted file mode 100644 index d2b1058f14e8..000000000000 Binary files a/src/font/VictorMono-Regular.ttf and /dev/null differ diff --git a/src/generics/convert.go b/src/generics/convert.go new file mode 100644 index 000000000000..1296df4cc1ef --- /dev/null +++ b/src/generics/convert.go @@ -0,0 +1,64 @@ +package generics + +import ( + "errors" + "reflect" + "strconv" +) + +type Numeric interface { + ~int | ~int64 | ~uint64 | ~float64 +} + +func toNumeric[T Numeric](value any) (T, error) { + switch v := value.(type) { + case string: + parsed, err := strconv.ParseFloat(v, 64) + if err == nil { + return T(parsed), nil + } + return T(0), err + case int: + return T(v), nil + case int64: + return T(v), nil + case uint64: + return T(v), nil + case float64: + return T(v), nil + case bool: + if v { + return T(1), nil + } + return T(0), nil + default: + // Handle named types with numeric underlying types (e.g. type Percentage int) + rv := reflect.ValueOf(value) + switch rv.Kind() { //nolint:exhaustive + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return T(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return T(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + return T(rv.Float()), nil + } + return T(0), errors.New("invalid numeric type") + } +} + +func TryParseInt[T ~int | ~int64](value any) (T, error) { + return toNumeric[T](value) +} + +func TryParseFloat[T ~float64](value any) (T, error) { + return toNumeric[T](value) +} + +func ToInt[T ~int | ~int64](value any) T { + result, err := toNumeric[T](value) + if err != nil { + return T(0) + } + + return result +} diff --git a/src/generics/pool.go b/src/generics/pool.go new file mode 100644 index 000000000000..8de017607b03 --- /dev/null +++ b/src/generics/pool.go @@ -0,0 +1,25 @@ +package generics + +import "sync" + +type Pool[T any] struct { + pool sync.Pool + new func() T +} + +func NewPool[T any](newFunc func() T) *Pool[T] { + return &Pool[T]{ + pool: sync.Pool{ + New: func() any { return newFunc() }, + }, + new: newFunc, + } +} + +func (p *Pool[T]) Get() T { + return p.pool.Get().(T) +} + +func (p *Pool[T]) Put(item T) { + p.pool.Put(item) +} diff --git a/src/generics/slices.go b/src/generics/slices.go new file mode 100644 index 000000000000..72ccd3b50633 --- /dev/null +++ b/src/generics/slices.go @@ -0,0 +1,29 @@ +package generics + +import "fmt" + +// ParseStringSlice converts any slice to a string slice +func ParseStringSlice(param any) []string { + return parseSlice(param, func(v any) string { return fmt.Sprint(v) }) +} + +// parseSlice converts any slice type to a typed slice using a converter function +func parseSlice[T any](param any, converter func(any) T) []T { + switch v := param.(type) { + case []any: + if len(v) == 0 { + return []T{} + } + + result := make([]T, len(v)) + for i, item := range v { + result[i] = converter(item) + } + + return result + case []T: + return v + default: + return []T{} + } +} diff --git a/src/go.mod b/src/go.mod index dc9fb55d15ad..a9d30bc7362a 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,39 +1,102 @@ -module github.com/jandedobbeleer/oh-my-posh +module github.com/jandedobbeleer/oh-my-posh/src -go 1.16 +go 1.26.0 require ( - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/Masterminds/sprig v2.22.0+incompatible - github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect - github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c + github.com/Masterminds/sprig/v3 v3.3.0 + github.com/alecthomas/assert v1.0.0 github.com/alecthomas/colour v0.1.0 // indirect - github.com/alecthomas/repr v0.0.0-20210301060118-828286944d6a // indirect - github.com/distatus/battery v0.10.0 - github.com/esimov/stackblur-go v1.0.0 + github.com/alecthomas/repr v0.5.2 // indirect + github.com/esimov/stackblur-go v1.1.1 github.com/fogleman/gg v1.3.0 - github.com/go-ole/go-ole v1.2.5 // indirect - github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/google/uuid v1.2.0 // indirect - github.com/gookit/color v1.4.2 - github.com/gookit/config/v2 v2.0.24 - github.com/gookit/goutil v0.3.14 // indirect - github.com/huandu/xstrings v1.3.2 // indirect - github.com/mattn/go-isatty v0.0.13 // indirect + github.com/google/uuid v1.6.0 + github.com/gookit/color v1.6.1 + github.com/huandu/xstrings v1.5.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.4.1 - github.com/sergi/go-diff v1.2.0 // indirect - github.com/shirou/gopsutil v3.21.5+incompatible - github.com/stretchr/objx v0.3.0 // indirect - github.com/stretchr/testify v1.7.0 - github.com/tklauser/go-sysconf v0.3.6 // indirect - github.com/wayneashleyberry/terminal-dimensions v1.0.0 - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect - golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 - golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 - golang.org/x/text v0.3.6 - howett.net/plist v0.0.0-20201203080718-1454fab16a06 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/stretchr/objx v0.5.3 // indirect + github.com/stretchr/testify v1.11.1 + github.com/wayneashleyberry/terminal-dimensions v1.1.0 + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/image v0.39.0 + golang.org/x/sys v0.43.0 + golang.org/x/text v0.36.0 + gopkg.in/ini.v1 v1.67.2 +) + +require ( + github.com/ConradIrwin/font v0.2.1 + github.com/charmbracelet/bubbles v1.0.0 + github.com/charmbracelet/bubbletea v1.3.10 + github.com/charmbracelet/lipgloss v1.1.0 + github.com/gookit/goutil v0.7.5 + github.com/hashicorp/hcl/v2 v2.24.0 + github.com/invopop/jsonschema v0.14.0 + github.com/mattn/go-runewidth v0.0.23 + github.com/pelletier/go-toml/v2 v2.3.1 + github.com/shirou/gopsutil/v4 v4.26.4 + github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 + go.yaml.in/yaml/v3 v3.0.4 + golang.org/x/mod v0.35.0 +) + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect +) + +require ( + dario.cat/mergo v1.0.2 // indirect + dmitri.shuralyov.com/font/woff2 v0.0.0-20180220214647-957792cbbdab // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.2 // indirect + github.com/charmbracelet/colorprofile v0.4.1 // indirect + github.com/charmbracelet/harmonica v0.2.0 // indirect + github.com/charmbracelet/x/ansi v0.11.6 // indirect + github.com/charmbracelet/x/cellbuf v0.0.15 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/clipperhouse/displaywidth v0.10.0 // indirect + github.com/clipperhouse/uax29/v2 v2.6.0 // indirect + github.com/dsnet/compress v0.0.1 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/pb33f/ordered-map/v2 v2.3.1 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zclconf/go-cty v1.17.0 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect + golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/tools v0.43.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/distatus/battery v0.10.0 => github.com/JanDeDobbeleer/battery v0.10.0-2 +replace github.com/atotto/clipboard v0.1.4 => github.com/jandedobbeleer/clipboard v0.1.4-1 diff --git a/src/go.sum b/src/go.sum index 29a849f7d55e..974fe0b8ab5d 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,169 +1,228 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/JanDeDobbeleer/battery v0.10.0-2 h1:nmFASq8Rmo0sHz6gLT3pXHh7uNABLEgEozKb2tRPgVY= -github.com/JanDeDobbeleer/battery v0.10.0-2/go.mod h1:STnSvFLX//eEpkaN7qWRxCWxrWOcssTDgnG4yqq9BRE= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +dmitri.shuralyov.com/font/woff2 v0.0.0-20180220214647-957792cbbdab h1:Ew70NL+wL6v9looOiJJthlqA41VzoJS+q9AyjHJe6/g= +dmitri.shuralyov.com/font/woff2 v0.0.0-20180220214647-957792cbbdab/go.mod h1:FvHgTMJanm43G7B3MVSjS/jim5ytVqAJNAOpRhnuHJc= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/ConradIrwin/font v0.2.1 h1:D4tWi7zyRAdVKOtOys5960HnAAfUSRx/syaf+J9JqlI= +github.com/ConradIrwin/font v0.2.1/go.mod h1:krTLO7JWu6g8RMxG8sl+T1Hf8W93XQacBKJmqFZ2MFY= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY= -github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= -github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/alecthomas/assert v1.0.0 h1:3XmGh/PSuLzDbK3W2gUbRXwgW5lqPkuqvRgeQ30FI5o= +github.com/alecthomas/assert v1.0.0/go.mod h1:va/d2JC+M7F6s+80kl/R3G7FUiW6JzUO+hPhLyJ36ZY= github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk= github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= -github.com/alecthomas/repr v0.0.0-20210301060118-828286944d6a h1:GY6ZI5mOHoiQ+g2ETFQGYu6fNgc6bCe4b4kr8tSrhpg= -github.com/alecthomas/repr v0.0.0-20210301060118-828286944d6a/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= -github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= +github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= +github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= +github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= +github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= +github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= +github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= +github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= +github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= +github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= +github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/esimov/stackblur-go v1.0.0 h1:PuXWrQ16VIh6Di+tn3CpKAyzkIIECo9DPGVecrzyDGc= -github.com/esimov/stackblur-go v1.0.0/go.mod h1:a3zzeKuJKUpCcReHmEsuPaEnq42D2b/bHoCI8UjIuMY= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/esimov/stackblur-go v1.1.1 h1:jZhuCbyFBp34SxkMwCuuNQ+d42w+CE/WOlcJLOlPEag= +github.com/esimov/stackblur-go v1.1.1/go.mod h1:m0T0MjHYbo4Lib/R33XDUMbLBwyGf1/K48ZdqtXUYDA= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gookit/color v1.3.6/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ= -github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk= -github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= -github.com/gookit/config/v2 v2.0.24 h1:v2K99gf/Esbgvy1OuJ0s8/fklEHJme9DMJZbT4VGzCI= -github.com/gookit/config/v2 v2.0.24/go.mod h1:TrsQSQfFyOe5ELsU9cXaEJpPV44Dm7zzK20oT+3eTTI= -github.com/gookit/goutil v0.3.8/go.mod h1:8pmV0itRPVrL6qWJ84RG+E7ZZBP4wvTl6cjjps9hgaE= -github.com/gookit/goutil v0.3.13/go.mod h1:DdrxLZc3yakbuElOtTH8F2SWu3XhaJohgvKHSP0JRak= -github.com/gookit/goutil v0.3.14 h1:ZEdZR+Vkvcjz0SSC0MpjtD+Kwlg/uagpiddh6L2ko+0= -github.com/gookit/goutil v0.3.14/go.mod h1:YdGV0ObqRUlRq4/RzAQBHcd1Wzl/jKw7cppDBtD3q+U= -github.com/gookit/ini/v2 v2.0.9/go.mod h1:qYxT/pBi+32lc0tps2dxKcgitv8g+47peszZi4NOEkM= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcl/v2 v2.10.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= +github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= +github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU= +github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= +github.com/gookit/goutil v0.7.5 h1:FXLTq+hVniw7UVMnr2i371yXqslgVpXqXszvXCJdEH8= +github.com/gookit/goutil v0.7.5/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg= +github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I= +github.com/jandedobbeleer/clipboard v0.1.4-1 h1:rJehm5W0a3hvjcxyB3snqLBV4yvMBBc12JyMP7ngNQw= +github.com/jandedobbeleer/clipboard v0.1.4-1/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= +github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= +github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= +github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc= -github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shirou/gopsutil/v4 v4.26.4 h1:B4SXVbcwTyrocPHEmWBC4uCYr4Xcu3MK1TXqbprAOWY= +github.com/shirou/gopsutil/v4 v4.26.4/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/gofontwoff v0.0.0-20181114050219-180f79e6909d h1:lvCTyBbr36+tqMccdGMwuEU+hjux/zL6xSmf5S9ITaA= +github.com/shurcooL/gofontwoff v0.0.0-20181114050219-180f79e6909d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= -github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4= -github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/wayneashleyberry/terminal-dimensions v1.0.0 h1:LawtS1nqKjAfqrmKOzkcrDLAjSzh38lEhC401JPjQVA= -github.com/wayneashleyberry/terminal-dimensions v1.0.0/go.mod h1:PW2XrtV6KmKOPhuf7wbtcmw1/IFnC39mryRET2XbxeE= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= -github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 h1:D0iM1dTCbD5Dg1CbuvLC/v/agLc79efSj/L35Q3Vqhs= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/wayneashleyberry/terminal-dimensions v1.1.0 h1:EB7cIzBdsOzAgmhTUtTTQXBByuPheP/Zv1zL2BRPY6g= +github.com/wayneashleyberry/terminal-dimensions v1.1.0/go.mod h1:2lc/0eWCObmhRczn2SdGSQtgBooLUzIotkkEGXqghyg= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0= +github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= +go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww= +golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 h1:C+AwYEtBp/VQwoLntUmQ/yx3MS9vmZaKNdw5eOpoQe8= -golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss= +gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= -howett.net/plist v0.0.0-20201203080718-1454fab16a06 h1:QDxUo/w2COstK1wIBYpzQlHX/NqaQTcf9jyz347nI58= -howett.net/plist v0.0.0-20201203080718-1454fab16a06/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/httpclient.go b/src/httpclient.go deleted file mode 100644 index dae29fdd25e6..000000000000 --- a/src/httpclient.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "net/http" -) - -// Inspired by: https://www.thegreatcodeadventure.com/mocking-http-requests-in-golang/ - -type httpClient interface { - Do(req *http.Request) (*http.Response, error) -} - -var ( - client httpClient = &http.Client{} -) diff --git a/src/image.go b/src/image.go deleted file mode 100644 index 7d26a5e4de55..000000000000 --- a/src/image.go +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright © 2020 The Homeport Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// https://github.com/homeport/termshot - -package main - -import ( - _ "embed" - "fmt" - "math" - "strconv" - "strings" - - "github.com/esimov/stackblur-go" - "github.com/fogleman/gg" - "github.com/golang/freetype/truetype" - "golang.org/x/image/font" -) - -const ( - red = "#ED655A" - yellow = "#E1C04C" - green = "#71BD47" - - // known ansi sequences - - fg = "FG" - bg = "BG" - str = "STR" - url = "URL" - invertedColor = "inverted" - invertedColorSingle = "invertedsingle" - fullColor = "full" - foreground = "foreground" - reset = "reset" - bold = "bold" - boldReset = "boldr" - italic = "italic" - italicReset = "italicr" - underline = "underline" - underlineReset = "underliner" - strikethrough = "strikethrough" - strikethroughReset = "strikethroughr" - color16 = "color16" - left = "left" - osc99 = "osc99" - lineChange = "linechange" - title = "title" - link = "link" -) - -//go:embed font/VictorMono-Bold.ttf -var victorMonoBold []byte - -//go:embed font/VictorMono-Regular.ttf -var victorMonoRegular []byte - -//go:embed font/VictorMono-Italic.ttf -var victorMonoItalic []byte - -type RGB struct { - r int - g int - b int -} - -func NewRGBColor(ansiColor string) *RGB { - colors := strings.Split(ansiColor, ";") - r, _ := strconv.Atoi(colors[0]) - g, _ := strconv.Atoi(colors[1]) - b, _ := strconv.Atoi(colors[2]) - return &RGB{ - r: r, - g: g, - b: b, - } -} - -type ImageRenderer struct { - ansiString string - author string - ansi *ansiUtils - - factor float64 - - columns int - rows int - - defaultForegroundColor *RGB - defaultBackgroundColor *RGB - - shadowBaseColor string - shadowRadius uint8 - shadowOffsetX float64 - shadowOffsetY float64 - - padding float64 - margin float64 - - regular font.Face - bold font.Face - italic font.Face - lineSpacing float64 - - // canvas switches - style string - backgroundColor *RGB - foregroundColor *RGB - ansiSequenceRegexMap map[string]string - rPromptOffset int - cursorPadding int -} - -func (ir *ImageRenderer) init() { - f := 2.0 - - ir.cleanContent() - - fontRegular, _ := truetype.Parse(victorMonoRegular) - fontBold, _ := truetype.Parse(victorMonoBold) - fontItalic, _ := truetype.Parse(victorMonoItalic) - fontFaceOptions := &truetype.Options{Size: f * 12, DPI: 144} - - ir.defaultForegroundColor = &RGB{255, 255, 255} - ir.defaultBackgroundColor = &RGB{21, 21, 21} - - ir.factor = f - - ir.columns = 80 - ir.rows = 25 - - ir.margin = f * 48 - ir.padding = f * 24 - - ir.shadowBaseColor = "#10101066" - ir.shadowRadius = uint8(math.Min(f*16, 255)) - ir.shadowOffsetX = f * 16 - ir.shadowOffsetY = f * 16 - - ir.regular = truetype.NewFace(fontRegular, fontFaceOptions) - ir.bold = truetype.NewFace(fontBold, fontFaceOptions) - ir.italic = truetype.NewFace(fontItalic, fontFaceOptions) - ir.lineSpacing = 1.2 - - ir.ansiSequenceRegexMap = map[string]string{ - invertedColor: `^(?P(\x1b\[38;2;(?P(\d+;?){3});49m){1}(\x1b\[7m))`, - invertedColorSingle: `^(?P\x1b\[(?P\d{2,3});49m\x1b\[7m)`, - fullColor: `^(?P(\x1b\[48;2;(?P(\d+;?){3})m)(\x1b\[38;2;(?P(\d+;?){3})m))`, - foreground: `^(?P(\x1b\[38;2;(?P(\d+;?){3})m))`, - reset: `^(?P\x1b\[0m)`, - bold: `^(?P\x1b\[1m)`, - boldReset: `^(?P\x1b\[22m)`, - italic: `^(?P\x1b\[3m)`, - italicReset: `^(?P\x1b\[23m)`, - underline: `^(?P\x1b\[4m)`, - underlineReset: `^(?P\x1b\[24m)`, - strikethrough: `^(?P\x1b\[9m)`, - strikethroughReset: `^(?P\x1b\[29m)`, - color16: `^(?P\x1b\[(?P\d{2,3})m)`, - left: `^(?P\x1b\[(\d{1,3})D)`, - osc99: `^(?P\x1b\]9;9;(.+)\x1b\\)`, - lineChange: `^(?P\x1b\[(\d)[FB])`, - title: `^(?P\x1b\]0;(.+)\007)`, - link: `^(?P\x1b]8;;file:\/\/(.+)\x1b\\(?P.+)\x1b]8;;\x1b\\)`, - } -} - -func (ir *ImageRenderer) fontHeight() float64 { - return float64(ir.regular.Metrics().Height >> 6) -} - -func (ir *ImageRenderer) calculateWidth() int { - longest := 0 - for _, line := range strings.Split(ir.ansiString, "\n") { - length := ir.ansi.lenWithoutANSI(line) - if length > longest { - longest = length - } - } - return longest -} - -func (ir *ImageRenderer) cleanContent() { - rPromptAnsi := "\x1b7\x1b[1000C" - hasRPrompt := strings.Contains(ir.ansiString, rPromptAnsi) - // clean abundance of empty lines - ir.ansiString = strings.Trim(ir.ansiString, "\n") - ir.ansiString = "\n" + ir.ansiString - // clean string before render - ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[m", "\x1b[0m") - ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[K", "") - ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[1F", "") - ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b8", "") - // replace rprompt with adding and mark right aligned blocks with a pointer - ir.ansiString = strings.ReplaceAll(ir.ansiString, rPromptAnsi, fmt.Sprintf("_%s", strings.Repeat(" ", ir.cursorPadding))) - ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[1000C", strings.Repeat(" ", ir.rPromptOffset)) - if !hasRPrompt { - ir.ansiString += fmt.Sprintf("_%s", strings.Repeat(" ", ir.cursorPadding)) - } - // add watermarks - ir.ansiString += "\n\n\x1b[1mhttps://ohmyposh.dev\x1b[22m" - if len(ir.author) > 0 { - createdBy := fmt.Sprintf(" by \x1b[1m%s\x1b[22m", ir.author) - ir.ansiString += createdBy - } -} - -func (ir *ImageRenderer) measureContent() (width, height float64) { - // get the longest line - linewidth := ir.calculateWidth() - // width, taken from the longest line - tmpDrawer := &font.Drawer{Face: ir.regular} - advance := tmpDrawer.MeasureString(strings.Repeat(" ", linewidth)) - width = float64(advance >> 6) - // height, lines times font height and line spacing - height = float64(len(strings.Split(ir.ansiString, "\n"))) * ir.fontHeight() * ir.lineSpacing - return width, height -} - -func (ir *ImageRenderer) SavePNG(path string) error { - var f = func(value float64) float64 { return ir.factor * value } - - var ( - corner = f(6) - radius = f(9) - distance = f(25) - ) - - contentWidth, contentHeight := ir.measureContent() - - // Make sure the output window is big enough in case no content or very few - // content will be rendered - contentWidth = math.Max(contentWidth, 3*distance+3*radius) - - marginX, marginY := ir.margin, ir.margin - paddingX, paddingY := ir.padding, ir.padding - - xOffset := marginX - yOffset := marginY - titleOffset := f(40) - - width := contentWidth + 2*marginX + 2*paddingX - height := contentHeight + 2*marginY + 2*paddingY + titleOffset - - dc := gg.NewContext(int(width), int(height)) - - xOffset -= ir.shadowOffsetX / 2 - yOffset -= ir.shadowOffsetY / 2 - - bc := gg.NewContext(int(width), int(height)) - bc.DrawRoundedRectangle(xOffset+ir.shadowOffsetX, yOffset+ir.shadowOffsetY, width-2*marginX, height-2*marginY, corner) - bc.SetHexColor(ir.shadowBaseColor) - bc.Fill() - - var done = make(chan struct{}, ir.shadowRadius) - shadow := stackblur.Process( - bc.Image(), - uint32(width), - uint32(height), - uint32(ir.shadowRadius), - done, - ) - - <-done - dc.DrawImage(shadow, 0, 0) - - // Draw rounded rectangle with outline and three button to produce the - // impression of a window with controls and a content area - dc.DrawRoundedRectangle(xOffset, yOffset, width-2*marginX, height-2*marginY, corner) - dc.SetHexColor("#151515") - dc.Fill() - - dc.DrawRoundedRectangle(xOffset, yOffset, width-2*marginX, height-2*marginY, corner) - dc.SetHexColor("#404040") - dc.SetLineWidth(f(1)) - dc.Stroke() - - for i, color := range []string{red, yellow, green} { - dc.DrawCircle(xOffset+paddingX+float64(i)*distance+f(4), yOffset+paddingY+f(4), radius) - dc.SetHexColor(color) - dc.Fill() - } - - // Apply the actual text into the prepared content area of the window - var x, y float64 = xOffset + paddingX, yOffset + paddingY + titleOffset + ir.fontHeight() - - for len(ir.ansiString) != 0 { - if !ir.shouldPrint() { - continue - } - runes := []rune(ir.ansiString) - str := string(runes[0:1]) - ir.ansiString = string(runes[1:]) - switch ir.style { - case bold: - dc.SetFontFace(ir.bold) - case italic: - dc.SetFontFace(ir.italic) - default: - dc.SetFontFace(ir.regular) - } - - w, h := dc.MeasureString(str) - if ir.backgroundColor != nil { - dc.SetRGB255(ir.backgroundColor.r, ir.backgroundColor.g, ir.backgroundColor.b) - dc.DrawRectangle(x, y-h, w, h+12) - dc.Fill() - } - if ir.foregroundColor != nil { - dc.SetRGB255(ir.foregroundColor.r, ir.foregroundColor.g, ir.foregroundColor.b) - } else { - dc.SetRGB255(ir.defaultForegroundColor.r, ir.defaultForegroundColor.g, ir.defaultForegroundColor.b) - } - - if str == "\n" { - x = xOffset + paddingX - y += h * ir.lineSpacing - continue - } - - dc.DrawString(str, x, y) - - if ir.style == underline { - dc.DrawLine(x, y+f(4), x+w, y+f(4)) - dc.SetLineWidth(f(1)) - dc.Stroke() - } - - x += w - } - - return dc.SavePNG(path) -} - -func (ir *ImageRenderer) shouldPrint() bool { - for sequence, regex := range ir.ansiSequenceRegexMap { - match := findNamedRegexMatch(regex, ir.ansiString) - if len(match) == 0 { - continue - } - ir.ansiString = strings.TrimPrefix(ir.ansiString, match[str]) - switch sequence { - case invertedColor: - ir.foregroundColor = ir.defaultBackgroundColor - ir.backgroundColor = NewRGBColor(match[bg]) - return false - case invertedColorSingle: - ir.foregroundColor = ir.defaultBackgroundColor - color, _ := strconv.Atoi(match[bg]) - color += 10 - ir.setBase16Color(fmt.Sprint(color)) - return false - case fullColor: - ir.foregroundColor = NewRGBColor(match[fg]) - ir.backgroundColor = NewRGBColor(match[bg]) - return false - case foreground: - ir.foregroundColor = NewRGBColor(match[fg]) - return false - case reset: - ir.foregroundColor = ir.defaultForegroundColor - ir.backgroundColor = nil - return false - case bold, italic, underline: - ir.style = sequence - return false - case boldReset, italicReset, underlineReset: - ir.style = "" - return false - case strikethrough, strikethroughReset, left, osc99, lineChange, title: - return false - case color16: - ir.setBase16Color(match[fg]) - return false - case link: - ir.ansiString = match[url] + ir.ansiString - } - } - return true -} - -func (ir *ImageRenderer) setBase16Color(colorStr string) { - color := ir.defaultForegroundColor - colorInt, err := strconv.Atoi(colorStr) - if err != nil { - ir.foregroundColor = color - } - switch colorInt { - case 30, 40: // Black - color = &RGB{1, 1, 1} - case 31, 41: // Red - color = &RGB{222, 56, 43} - case 32, 42: // Green - color = &RGB{57, 181, 74} - case 33, 43: // Yellow - color = &RGB{255, 199, 6} - case 34, 44: // Blue - color = &RGB{0, 111, 184} - case 35, 45: // Magenta - color = &RGB{118, 38, 113} - case 36, 46: // Cyan - color = &RGB{44, 181, 233} - case 37, 47: // White - color = &RGB{204, 204, 204} - case 90, 100: // Bright Black (Gray) - color = &RGB{128, 128, 128} - case 91, 101: // Bright Red - color = &RGB{255, 0, 0} - case 92, 102: // Bright Green - color = &RGB{0, 255, 0} - case 93, 103: // Bright Yellow - color = &RGB{255, 255, 0} - case 94, 104: // Bright Blue - color = &RGB{0, 0, 255} - case 95, 105: // Bright Magenta - color = &RGB{255, 0, 255} - case 96, 106: // Bright Cyan - color = &RGB{101, 194, 205} - case 97, 107: // Bright White - color = &RGB{255, 255, 255} - } - if colorInt < 40 || (colorInt >= 90 && colorInt < 100) { - ir.foregroundColor = color - return - } - ir.backgroundColor = color -} diff --git a/src/image_test.go b/src/image_test.go deleted file mode 100644 index 4292b69335c2..000000000000 --- a/src/image_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "io/ioutil" - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -func runImageTest(content string) error { - poshImagePath := "ohmyposh.png" - file, err := ioutil.TempFile("", poshImagePath) - if err != nil { - return err - } - defer os.Remove(file.Name()) - ansi := &ansiUtils{} - ansi.init(plain) - image := &ImageRenderer{ - ansiString: content, - ansi: ansi, - } - image.init() - err = image.SavePNG(poshImagePath) - return err -} - -func TestStringImageFileWithText(t *testing.T) { - err := runImageTest("foobar") - assert.NoError(t, err) -} - -func TestStringImageFileWithANSI(t *testing.T) { - prompt := ` oh-my-posh -  main ≡  ~4 -8 ?7  -  ` - err := runImageTest(prompt) - assert.NoError(t, err) -} diff --git a/src/init/omp.bash b/src/init/omp.bash deleted file mode 100644 index 40d176524c07..000000000000 --- a/src/init/omp.bash +++ /dev/null @@ -1,47 +0,0 @@ -export POSH_THEME=::CONFIG:: -export POWERLINE_COMMAND="oh-my-posh" -export CONDA_PROMPT_MODIFIER=false - -TIMER_START="/tmp/${USER}.start.$$" - -# some environments don't have the filesystem we'd expect -if [[ ! -d "/tmp" ]]; then - TIMER_START="${HOME}/.${USER}.start.$$" -fi - - PS0='$(::OMP:: --millis > $TIMER_START)' - -function _omp_hook() { - local ret=$? - - omp_stack_count=$((${#DIRSTACK[@]} - 1)) - omp_elapsed=-1 - if [[ -f "$TIMER_START" ]]; then - omp_now=$(::OMP:: --millis) - omp_start_time=$(cat "$TIMER_START") - omp_elapsed=$((omp_now-omp_start_time)) - rm -f "$TIMER_START" - fi - PS1="$(::OMP:: --config $POSH_THEME --shell bash --error $ret --execution-time $omp_elapsed --stack-count $omp_stack_count)" - - return $ret -} - -if [ "$TERM" != "linux" ] && [ -x "$(command -v ::OMP::)" ] && ! [[ "$PROMPT_COMMAND" =~ "_omp_hook" ]]; then - PROMPT_COMMAND="_omp_hook; $PROMPT_COMMAND" -fi - -function _omp_runonexit() { - [[ -f $TIMER_START ]] && rm -f "$TIMER_START" -} - -trap _omp_runonexit EXIT - -function export_poshconfig() { - [ $# -eq 0 ] && { echo "Usage: $0 \"filename\""; return; } - format=$2 - if [ -z "$format" ]; then - format="json" - fi - ::OMP:: --config $POSH_THEME --print-config --config-format $format > $1 -} diff --git a/src/init/omp.fish b/src/init/omp.fish deleted file mode 100644 index 7f9088fc8368..000000000000 --- a/src/init/omp.fish +++ /dev/null @@ -1,41 +0,0 @@ -set -g posh_theme ::CONFIG:: -set -g POWERLINE_COMMAND "oh-my-posh" -set -g CONDA_PROMPT_MODIFIER false - -function fish_prompt - set -l omp_stack_count (count $dirstack) - set -l omp_duration "$CMD_DURATION$cmd_duration" - # check if variable set, < 3.2 case - if set -q omp_lastcommand; and test "$omp_lastcommand" = "" - set omp_duration 0 - end - # works with fish >=3.2 - if set -q omp_last_status_generation; and test "$omp_last_status_generation" = "$status_generation" - set omp_duration 0 - end - if set -q status_generation - set -gx omp_last_status_generation $status_generation - end - - ::OMP:: --config $posh_theme --error $status --execution-time $omp_duration --stack-count $omp_stack_count -end - -function postexec_omp --on-event fish_postexec - # works with fish <3.2 - # pre and postexec not fired for empty command in fish >=3.2 - set -gx omp_lastcommand $argv -end - - -function export_poshconfig - set -l file_name $argv[1] - set -l format $argv[2] - if not test -n "$file_name" - echo "Usage: export_poshconfig \"filename\"" - return - end - if not test -n "$format" - set format "json" - end - ::OMP:: --config $posh_theme --print-config --config-format $format > $file_name -end diff --git a/src/init/omp.ps1 b/src/init/omp.ps1 deleted file mode 100644 index e257b716ad80..000000000000 --- a/src/init/omp.ps1 +++ /dev/null @@ -1,205 +0,0 @@ -# Powershell doesn't default to UTF8 just yet, so we're forcing it as there are too many problems -# that pop up when we don't -[console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding -$env:POWERLINE_COMMAND = "oh-my-posh" -$env:CONDA_PROMPT_MODIFIER = $false - -# specific module support (disabled by default) -function Set-DefaultEnvValue { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [string] - $Name - ) - - $value = [System.Environment]::GetEnvironmentVariable($Name) - if ($value -eq $null) { - [System.Environment]::SetEnvironmentVariable($Name, $false) - } -} -Set-DefaultEnvValue("AZ_ENABLED") -Set-DefaultEnvValue("POSH_GIT_ENABLED") - -$global:PoshSettings = New-Object -TypeName PSObject -Property @{ - Theme = ""; - EnableToolTips = $false; -} - -# used to detect empty hit -$global:omp_lastHistoryId = -1 - -$config = "::CONFIG::" -if (Test-Path $config) { - $global:PoshSettings.Theme = (Resolve-Path -Path $config).ProviderPath -} - -function global:Set-PoshContext {} - -function global:Initialize-ModuleSupport { - if ($env:POSH_GIT_ENABLED -eq $true -and (Get-Module -Name "posh-git")) { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideCommentHelp', '', Justification = 'Variable used later(not in this scope)')] - $global:GitStatus = Get-GitStatus - $env:POSH_GIT_STATUS = Write-GitStatus -Status $global:GitStatus - } - - $env:AZ_SUBSCRIPTION_NAME = $null - $env:AZ_SUBSCRIPTION_ID = $null - - if ($env:AZ_ENABLED -eq $true) { - try { - $subscription = Get-AzContext | Select-Object -ExpandProperty "Subscription" | Select-Object "Name", "Id", "Account" - if ($null -ne $subscription) { - $env:AZ_SUBSCRIPTION_NAME = $subscription.Name - $env:AZ_SUBSCRIPTION_ID = $subscription.Id - $env:AZ_SUBSCRIPTION_ACCOUNT = $subscription.Account - } - } - catch {} - } - - # Set the keyhandler to enable tooltips - if ($global:PoshSettings.EnableToolTips -eq $true) { - Set-PSReadlineKeyHandler -Key SpaceBar -ScriptBlock { - [Microsoft.PowerShell.PSConsoleReadLine]::Insert(' ') - $position = $host.UI.RawUI.CursorPosition - $omp = "::OMP::" - $config = $global:PoshSettings.Theme - $cleanPWD = $PWD.ProviderPath.TrimEnd("\") - $cleanPSWD = $PWD.ToString().TrimEnd("\") - $tooltip = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$tooltip, [ref]$cursor) - $standardOut = @(&$omp --pwd="$cleanPWD" --pswd="$cleanPSWD" --config="$config" --tooltip="$tooltip" 2>&1) - Write-Host $standardOut -NoNewline - $host.UI.RawUI.CursorPosition = $position - } - } -} - -[ScriptBlock]$Prompt = { - #store if the last command was successful - $lastCommandSuccess = $? - #store the last exit code for restore - $realLASTEXITCODE = $global:LASTEXITCODE - $errorCode = 0 - Initialize-ModuleSupport - Set-PoshContext - if ($lastCommandSuccess -eq $false) { - #native app exit code - if ($realLASTEXITCODE -is [int] -and $realLASTEXITCODE -gt 0) { - $errorCode = $realLASTEXITCODE - } - else { - $errorCode = 1 - } - } - - # read stack count from current stack(if invoked from profile=right value,otherwise use the global variable set in Set-PoshPrompt(stack scoped to module)) - $stackCount = (Get-Location -stack).Count - try { - if ($global:omp_global_sessionstate -ne $null) { - $stackCount = ($global:omp_global_sessionstate).path.locationstack('').count - } - } - catch {} - - $executionTime = -1 - $history = Get-History -ErrorAction Ignore -Count 1 - if ($null -ne $history -and $null -ne $history.EndExecutionTime -and $null -ne $history.StartExecutionTime -and $global:omp_lastHistoryId -ne $history.Id) { - $executionTime = ($history.EndExecutionTime - $history.StartExecutionTime).TotalMilliseconds - $global:omp_lastHistoryId = $history.Id - } - $omp = "::OMP::" - $config = $global:PoshSettings.Theme - $cleanPWD = $PWD.ProviderPath.TrimEnd("\") - $cleanPSWD = $PWD.ToString().TrimEnd("\") - $standardOut = @(&$omp --error="$errorCode" --pwd="$cleanPWD" --pswd="$cleanPSWD" --execution-time="$executionTime" --stack-count="$stackCount" --config="$config" 2>&1) - # the output can be multiline, joining these ensures proper rendering by adding line breaks with `n - $standardOut -join "`n" - $global:LASTEXITCODE = $realLASTEXITCODE - #remove temp variables - Remove-Variable realLASTEXITCODE -Confirm:$false - Remove-Variable lastCommandSuccess -Confirm:$false -} -Set-Item -Path Function:prompt -Value $Prompt -Force - -function global:Write-PoshDebug { - $omp = "::OMP::" - $config = $global:PoshSettings.Theme - $cleanPWD = $PWD.ProviderPath.TrimEnd("\") - $cleanPSWD = $PWD.ToString().TrimEnd("\") - $standardOut = @(&$omp --error=1337 --pwd="$cleanPWD" --pswd="$cleanPSWD" --execution-time=9001 --config="$config" --debug 2>&1) - $standardOut -join "`n" -} - -<# -.SYNOPSIS - Exports the current oh-my-posh theme -.DESCRIPTION - By default the config is exported in json to the clipboard -.EXAMPLE - Export-PoshTheme - Current theme exported in json to clipboard -.EXAMPLE - Export-PoshTheme -Format toml - Current theme exported in toml to clipboard -.EXAMPLE - Export-PoshTheme c:\temp\theme.toml toml - Current theme exported in toml to c:\temp\theme.toml -.EXAMPLE - Export-PoshTheme ~\theme.toml toml - Current theme exported in toml to your home\theme.toml -#> -function global:Export-PoshTheme { - param( - [Parameter(Mandatory = $false)] - [string] - # The file path where the theme will be exported. If not provided, the config is copied to the clipboard by default. - $FilePath, - [Parameter(Mandatory = $false)] - [ValidateSet('json', 'yaml', 'toml')] - [string] - # The format of the theme - $Format = 'json' - ) - - $config = $global:PoshSettings.Theme - $omp = "::OMP::" - $configString = @(&$omp --config="$config" --config-format="$Format" --print-config 2>&1) - # if no path, copy to clipboard by default - if ($FilePath -ne "") { - #https://stackoverflow.com/questions/3038337/powershell-resolve-path-that-might-not-exist - $FilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath) - [IO.File]::WriteAllLines($FilePath, $configString) - } - else { - Set-Clipboard $configString - Write-Output "Theme copied to clipboard" - } -} - -function global:Export-PoshImage { - param( - [Parameter(Mandatory = $false)] - [int] - $RPromptOffset = 40, - [Parameter(Mandatory = $false)] - [int] - $CursorPadding = 30, - [Parameter(Mandatory = $false)] - [string] - $Author - ) - - if ($Author) { - $Author = "--author=$Author" - } - - $omp = "::OMP::" - $config = $global:PoshSettings.Theme - $cleanPWD = $PWD.ProviderPath.TrimEnd("\") - $cleanPSWD = $PWD.ToString().TrimEnd("\") - $standardOut = @(&$omp --config="$config" --pwd="$cleanPWD" --pswd="$cleanPSWD" --export-png --rprompt-offset="$RPromptOffset" --cursor-padding="$CursorPadding" $Author 2>&1) - $standardOut -join "`n" -} diff --git a/src/init/omp.zsh b/src/init/omp.zsh deleted file mode 100644 index 4101083cba1d..000000000000 --- a/src/init/omp.zsh +++ /dev/null @@ -1,71 +0,0 @@ -export POSH_THEME=::CONFIG:: -export POWERLINE_COMMAND="oh-my-posh" -export CONDA_PROMPT_MODIFIER=false - -function omp_preexec() { - omp_start_time=$(::OMP:: --millis) -} - -function omp_precmd() { - omp_last_error=$? - omp_stack_count=${#dirstack[@]} - omp_elapsed=-1 - if [ $omp_start_time ]; then - omp_now=$(::OMP:: --millis) - omp_elapsed=$(($omp_now-$omp_start_time)) - fi - eval "$(::OMP:: --config $POSH_THEME --error $omp_last_error --execution-time $omp_elapsed --stack-count $omp_stack_count --eval --shell zsh)" - unset omp_start_time - unset omp_now - unset omp_elapsed - unset omp_last_error - unset omp_stack_count -} - -function install_omp_hooks() { - for s in "${preexec_functions[@]}"; do - if [ "$s" = "omp_preexec" ]; then - return - fi - done - preexec_functions+=(omp_preexec) - - for s in "${precmd_functions[@]}"; do - if [ "$s" = "omp_precmd" ]; then - return - fi - done - precmd_functions+=(omp_precmd) -} - -if [ "$TERM" != "linux" ]; then - install_omp_hooks -fi - -function export_poshconfig() { - [ $# -eq 0 ] && { echo "Usage: $0 \"filename\""; return; } - format=$2 - if [ -z "$format" ]; then - format="json" - fi - ::OMP:: --config $POSH_THEME --print-config --config-format $format > $1 -} - -function self-insert() { - # ignore an empty buffer - if [[ -z "$BUFFER" ]]; then - zle .self-insert - return - fi - tooltip=$(::OMP:: --config $POSH_THEME --shell zsh --tooltip $BUFFER) - # ignore an empty tooltip - if [[ ! -z "$tooltip" ]]; then - RPROMPT=$tooltip - zle reset-prompt - fi - zle .self-insert -} - -function enable_poshtooltips() { - zle -N self-insert -} diff --git a/src/log/log.go b/src/log/log.go new file mode 100644 index 000000000000..3a1d705720a6 --- /dev/null +++ b/src/log/log.go @@ -0,0 +1,126 @@ +package log + +import ( + "fmt" + "path/filepath" + "runtime" + "strings" + "time" +) + +var ( + enabled bool + raw bool + + log strings.Builder +) + +func Enable(plain bool) { + enabled = true + raw = plain + + Debugf("logging enabled, raw mode: %t", plain) +} + +func Trace(start time.Time, args ...string) { + if !enabled { + return + } + + elapsed := time.Since(start) + fn, _ := funcSpec() + + // Color-code elapsed time based on duration + var coloredElapsed Text + ms := elapsed.Milliseconds() + + switch { + case ms < 1: + coloredElapsed = Text(elapsed.String()).Green().Plain() + case ms >= 1 && ms < 10: + coloredElapsed = Text(elapsed.String()).Yellow().Plain() + case ms >= 10 && ms < 100: + coloredElapsed = Text(elapsed.String()).Orange().Plain() + default: // >= 100ms + coloredElapsed = Text(elapsed.String()).Red().Plain() + } + + header := fmt.Sprintf("%s(%s) - %s", fn, strings.Join(args, " "), coloredElapsed) + + printLn(trace, header) +} + +func Debug(message ...string) { + if !enabled { + return + } + + fn, line := funcSpec() + header := fmt.Sprintf("%s:%d", fn, line) + + printLn(debug, header, strings.Join(message, " ")) +} + +func Debugf(format string, args ...any) { + if !enabled { + return + } + + message := fmt.Sprintf(format, args...) + Debug(message) +} + +func Error(err error) { + if !enabled { + return + } + fn, line := funcSpec() + header := fmt.Sprintf("%s:%d", fn, line) + + printLn(bug, header, err.Error()) +} + +func Errorf(format string, args ...any) { + if !enabled { + return + } + + Error(fmt.Errorf(format, args...)) +} + +func String() string { + return log.String() +} + +func funcSpec() (string, int) { + pcs := make([]uintptr, 4) + n := runtime.Callers(3, pcs) + if n == 0 { + return "", 0 + } + + frames := runtime.CallersFrames(pcs[:n]) + var frame runtime.Frame + more := true + + // Loop through frames until we're out of log.go + for more { + frame, more = frames.Next() + if strings.Contains(frame.File, "log.go") { + continue + } + + // Found first non-log.go frame + fn := frame.Function + fn = fn[strings.LastIndex(fn, ".")+1:] + file := filepath.Base(frame.File) + + if strings.HasPrefix(fn, "func") { + return file, frame.Line + } + + return fmt.Sprintf("%s:%s", file, fn), frame.Line + } + + return "", 0 +} diff --git a/src/log/print.go b/src/log/print.go new file mode 100644 index 000000000000..b678138de095 --- /dev/null +++ b/src/log/print.go @@ -0,0 +1,120 @@ +package log + +import ( + "fmt" + "strings" + "time" +) + +type logType byte + +const ( + debug logType = 1 << iota + bug + trace +) + +type Text string + +func (t Text) Green() Text { + if raw { + return t + } + return "\x1b[38;2;191;207;240m" + t +} + +func (t Text) Red() Text { + if raw { + return t + } + return "\x1b[38;2;253;122;140m" + t +} + +func (t Text) Purple() Text { + if raw { + return t + } + return "\x1b[38;2;204;137;214m" + t +} + +func (t Text) Yellow() Text { + if raw { + return t + } + return "\x1b[38;2;156;231;201m" + t +} + +func (t Text) Orange() Text { + if raw { + return t + } + return "\x1b[38;2;253;184;109m" + t +} + +func (t Text) Bold() Text { + if raw { + return t + } + return "\x1b[1m" + t +} + +func (t Text) Plain() Text { + if raw { + return t + } + return t + "\033[0m" +} + +func (t Text) String() string { + return string(t) +} + +func printLn(lt logType, args ...string) { + if len(args) == 0 { + return + } + + var str Text + switch lt { + case debug: + str = Text("[DEBUG] ").Green() + case bug: + str = Text("[ERROR] ").Red() + case trace: + str = Text("[TRACE] ").Purple() + } + + // timestamp 156, 231, 201 + str += Text(time.Now().Format("15:04:05.000") + " ").Yellow().Plain() + str += Text(args[0]) + str += parseArgs(args...) + log.WriteString(str.String()) +} + +func parseArgs(args ...string) Text { + if len(args) == 1 { + return "\n" + } + + // display empty return values as NO DATA + if args[1] == "" { + text := Text(" \u2192").Yellow() + text += Text(" NO DATA\n").Red().Plain() + return text + } + + // print a single line for single output + splitted := strings.Split(args[1], "\n") + if len(splitted) == 1 { + text := Text(" \u2192").Yellow().Plain() + return Text(fmt.Sprintf("%s %s\n", text, args[1])) + } + + // indent multiline output with 4 spaces + var str Text + str += Text(" \u2193\n").Yellow().Plain() + for _, line := range splitted { + str += Text(fmt.Sprintf(" %s\n", line)) + } + return str +} diff --git a/src/main.go b/src/main.go index b406df365f64..34707ea5c7ef 100644 --- a/src/main.go +++ b/src/main.go @@ -1,294 +1,9 @@ package main import ( - _ "embed" - "flag" - "fmt" - "os" - "strings" - "time" - - "github.com/gookit/config/v2" -) - -// Version number of oh-my-posh -var Version = "development" - -//go:embed init/omp.ps1 -var pwshInit string - -//go:embed init/omp.fish -var fishInit string - -//go:embed init/omp.bash -var bashInit string - -//go:embed init/omp.zsh -var zshInit string - -const ( - noExe = "echo \"Unable to find Oh My Posh executable\"" - zsh = "zsh" - bash = "bash" - pwsh = "pwsh" - fish = "fish" - powershell5 = "powershell" - plain = "shell" + "github.com/jandedobbeleer/oh-my-posh/src/cli" ) -type args struct { - ErrorCode *int - PrintConfig *bool - ConfigFormat *string - PrintShell *bool - Config *string - Shell *string - PWD *string - PSWD *string - Version *bool - Debug *bool - ExecutionTime *float64 - Millis *bool - Eval *bool - Init *bool - PrintInit *bool - ExportPNG *bool - Author *string - CursorPadding *int - RPromptOffset *int - StackCount *int - ToolTip *string -} - func main() { - args := &args{ - ErrorCode: flag.Int( - "error", - 0, - "Error code of previously executed command"), - PrintConfig: flag.Bool( - "print-config", - false, - "Print the current config in json format"), - ConfigFormat: flag.String( - "config-format", - config.JSON, - "The format to print the config in. Valid options are:\n- json\n- yaml\n- toml\n"), - PrintShell: flag.Bool( - "print-shell", - false, - "Print the current shell name"), - Config: flag.String( - "config", - "", - "Add the path to a configuration you wish to load"), - Shell: flag.String( - "shell", - "", - "Override the shell you are working in"), - PWD: flag.String( - "pwd", - "", - "the path you are working in"), - PSWD: flag.String( - "pswd", - "", - "the powershell path you are working in, useful when working with drives"), - Version: flag.Bool( - "version", - false, - "Print the current version of the binary"), - Debug: flag.Bool( - "debug", - false, - "Print debug information"), - ExecutionTime: flag.Float64( - "execution-time", - 0, - "Execution time of the previously executed command"), - Millis: flag.Bool( - "millis", - false, - "Get the current time in milliseconds"), - Eval: flag.Bool( - "eval", - false, - "Run in eval mode"), - Init: flag.Bool( - "init", - false, - "Initialize the shell"), - PrintInit: flag.Bool( - "print-init", - false, - "Print the shell initialization script"), - ExportPNG: flag.Bool( - "export-png", - false, - "Create an image based on the current configuration"), - Author: flag.String( - "author", - "", - "Add the author to the exported image using --export-img"), - CursorPadding: flag.Int( - "cursor-padding", - 30, - "Pad the cursor with x when using --export-img"), - RPromptOffset: flag.Int( - "rprompt-offset", - 40, - "Offset the right prompt with x when using --export-img"), - StackCount: flag.Int( - "stack-count", - 0, - "The current location stack count"), - ToolTip: flag.String( - "tooltip", - "", - "Render a tooltip based on the string value"), - } - flag.Parse() - env := &environment{} - env.init(args) - if *args.Millis { - fmt.Print(time.Now().UnixNano() / 1000000) - return - } - if *args.Init { - init := initShell(*args.Shell, *args.Config) - fmt.Print(init) - return - } - if *args.PrintInit { - init := printShellInit(*args.Shell, *args.Config) - fmt.Print(init) - return - } - if *args.PrintConfig { - fmt.Print(exportConfig(*args.Config, *args.ConfigFormat)) - return - } - cfg := GetConfig(env) - if *args.PrintShell { - fmt.Println(env.getShellName()) - return - } - if *args.Version { - fmt.Println(Version) - return - } - - ansi := &ansiUtils{} - ansi.init(env.getShellName()) - colorer := &AnsiColor{ - ansi: ansi, - terminalBackground: getConsoleBackgroundColor(env, cfg.TerminalBackground), - } - title := &consoleTitle{ - env: env, - config: cfg, - ansi: ansi, - } - engine := &engine{ - config: cfg, - env: env, - colorWriter: colorer, - consoleTitle: title, - ansi: ansi, - } - if *args.Debug { - fmt.Print(engine.debug()) - return - } - if len(*args.ToolTip) != 0 { - fmt.Print(engine.renderTooltip(*args.ToolTip)) - return - } - prompt := engine.render() - if !*args.ExportPNG { - fmt.Print(prompt) - return - } - imageCreator := &ImageRenderer{ - ansiString: prompt, - author: *args.Author, - cursorPadding: *args.CursorPadding, - rPromptOffset: *args.RPromptOffset, - ansi: ansi, - } - imageCreator.init() - match := findNamedRegexMatch(`.*(\/|\\)(?P.+).omp.(json|yaml|toml)`, *args.Config) - err := imageCreator.SavePNG(fmt.Sprintf("%s.png", match[str])) - if err != nil { - fmt.Print(err.Error()) - } -} - -func initShell(shell, configFile string) string { - executable, err := os.Executable() - if err != nil { - return noExe - } - switch shell { - case pwsh: - return fmt.Sprintf("(@(&\"%s\" --print-init --shell=pwsh --config=\"%s\") -join \"`n\") | Invoke-Expression", executable, configFile) - case zsh, bash, fish: - return printShellInit(shell, configFile) - default: - return fmt.Sprintf("echo \"No initialization script available for %s\"", shell) - } -} - -func printShellInit(shell, configFile string) string { - executable, err := os.Executable() - // On Windows, it fails when the excutable is called in MSYS2 for example - // which uses unix style paths to resolve the executable's location. - // PowerShell knows how to resolve both, so we can swap this without any issue. - executable = strings.ReplaceAll(executable, "\\", "/") - if err != nil { - return noExe - } - switch shell { - case pwsh: - return getShellInitScript(executable, configFile, pwshInit) - case zsh: - return getShellInitScript(executable, configFile, zshInit) - case bash: - return getShellInitScript(executable, configFile, bashInit) - case fish: - return getShellInitScript(executable, configFile, fishInit) - default: - return fmt.Sprintf("echo \"No initialization script available for %s\"", shell) - } -} - -func getShellInitScript(executable, configFile, script string) string { - script = strings.ReplaceAll(script, "::OMP::", executable) - script = strings.ReplaceAll(script, "::CONFIG::", configFile) - return script -} - -func getConsoleBackgroundColor(env environmentInfo, backgroundColorTemplate string) string { - if len(backgroundColorTemplate) == 0 { - return backgroundColorTemplate - } - context := struct { - Env map[string]string - }{ - Env: map[string]string{}, - } - matches := findAllNamedRegexMatch(templateEnvRegex, backgroundColorTemplate) - for _, match := range matches { - context.Env[match["ENV"]] = env.getenv(match["ENV"]) - } - template := &textTemplate{ - Template: backgroundColorTemplate, - Context: context, - Env: env, - } - text, err := template.render() - if err != nil { - return err.Error() - } - return text + cli.Execute() } diff --git a/src/main_test.go b/src/main_test.go index 16b72b8364a6..78cb0f6f36ae 100644 --- a/src/main_test.go +++ b/src/main_test.go @@ -1,25 +1,36 @@ package main import ( + "bytes" + "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/jandedobbeleer/oh-my-posh/src/cli" + "github.com/jandedobbeleer/oh-my-posh/src/prompt" ) -func TestConsoleBackgroundColorTemplate(t *testing.T) { - cases := []struct { - Case string - Expected string - Term string - }{ - {Case: "Inside vscode", Expected: "#123456", Term: "vscode"}, - {Case: "Outside vscode", Expected: "", Term: "windowsterminal"}, +func BenchmarkInit(b *testing.B) { + cmd := cli.RootCmd + // needs to be a non-existing file as we panic otherwise + cmd.SetArgs([]string{"init", "fish", "--print", "--silent"}) + out := bytes.NewBufferString("") + cmd.SetOut(out) + + for b.Loop() { + _ = cmd.Execute() } +} - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getenv", "TERM_PROGRAM").Return(tc.Term) - color := getConsoleBackgroundColor(env, "{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}") - assert.Equal(t, tc.Expected, color, tc.Case) +func BenchmarkPrimary(b *testing.B) { + cmd := cli.RootCmd + // needs to be a non-existing file as we panic otherwise + cmd.SetArgs([]string{"print", prompt.PRIMARY, "--pwd", "/Users/jan/Code/oh-my-posh/src", "--shell", "fish", "--silent"}) + out := bytes.NewBufferString("") + cmd.SetOut(out) + + for b.Loop() { + _ = cmd.Execute() } + + fmt.Println("") } diff --git a/src/maps/concurrent.go b/src/maps/concurrent.go new file mode 100644 index 000000000000..13ad10930f1d --- /dev/null +++ b/src/maps/concurrent.go @@ -0,0 +1,65 @@ +package maps + +import ( + "fmt" + "sync" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +func NewConcurrent[V any]() *Concurrent[V] { + return &Concurrent[V]{} +} + +// Concurrent is a generic type-safe concurrent map +type Concurrent[V any] struct { + m sync.Map +} + +func (cm *Concurrent[V]) Set(key string, value V) { + cm.m.Store(key, value) +} + +func (cm *Concurrent[V]) Get(key string) (V, bool) { + val, ok := cm.m.Load(key) + if !ok { + var zero V + return zero, false + } + + return val.(V), true +} + +func (cm *Concurrent[V]) MustGet(key string) V { + val, ok := cm.m.Load(key) + if !ok { + log.Error(fmt.Errorf("key %s not found", key)) + var zero V + return zero + } + + return val.(V) +} + +func (cm *Concurrent[V]) Delete(key string) { + cm.m.Delete(key) +} + +func (cm *Concurrent[V]) Contains(key string) bool { + _, ok := cm.m.Load(key) + return ok +} + +func (cm *Concurrent[V]) ToSimple() Simple[V] { + result := make(Simple[V]) + + cm.m.Range(func(key, value any) bool { + if value == nil { + return true + } + result[key.(string)] = value.(V) + return true + }) + + return result +} diff --git a/src/maps/config.go b/src/maps/config.go new file mode 100644 index 000000000000..b5ee73c94921 --- /dev/null +++ b/src/maps/config.go @@ -0,0 +1,54 @@ +package maps + +import ( + "encoding/gob" +) + +func init() { + gob.Register(&Config{}) + gob.Register(&Map{}) +} + +type Config struct { + UserName *Map `json:"user_name,omitempty" toml:"user_name,omitempty" yaml:"user_name,omitempty"` + HostName *Map `json:"host_name,omitempty" toml:"host_name,omitempty" yaml:"host_name,omitempty"` + ShellName *Map `json:"shell_name,omitempty" toml:"shell_name,omitempty" yaml:"shell_name,omitempty"` +} + +func (c *Config) GetUserName(key string) string { + if c == nil || c.UserName == nil { + return key + } + + return c.UserName.Get(key) +} + +func (c *Config) GetHostName(key string) string { + if c == nil || c.HostName == nil { + return key + } + + return c.HostName.Get(key) +} + +func (c *Config) GetShellName(key string) string { + if c == nil || c.ShellName == nil { + return key + } + + return c.ShellName.Get(key) +} + +type Map map[string]string + +func (m *Map) Get(key string) string { + if m == nil { + return key + } + + if value, ok := (*m)[key]; ok { + return value + } + + return key +} diff --git a/src/maps/simple.go b/src/maps/simple.go new file mode 100644 index 000000000000..25dffe8c687d --- /dev/null +++ b/src/maps/simple.go @@ -0,0 +1,13 @@ +package maps + +// Simple is a generic map type that can be specialized for different value types +type Simple[V any] map[string]V + +func (m Simple[V]) ToConcurrent() *Concurrent[V] { + cm := NewConcurrent[V]() + for k, v := range m { + cm.Set(k, v) + } + + return cm +} diff --git a/src/metadata.json b/src/metadata.json new file mode 100644 index 000000000000..633b99610c82 --- /dev/null +++ b/src/metadata.json @@ -0,0 +1,14 @@ +{ + "Endpoint": "https://weu.codesigning.azure.net", + "CodeSigningAccountName": "oh-my-posh", + "CertificateProfileName": "oh-my-posh", + "ExcludeCredentials": [ + "AzureCliCredential", + "AzurePowerShellCredential", + "ManagedIdentityCredential", + "SharedTokenCacheCredential", + "VisualStudioCredential", + "VisualStudioCodeCredential", + "InteractiveBrowserCredential" + ] +} diff --git a/src/prompt/debug.go b/src/prompt/debug.go new file mode 100644 index 000000000000..367abcf398bb --- /dev/null +++ b/src/prompt/debug.go @@ -0,0 +1,82 @@ +package prompt + +import ( + "fmt" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +// debug will loop through your config file and output the timings for each segments +func (e *Engine) PrintDebug(startTime time.Time, version string) string { + e.write(fmt.Sprintf("\n%s %s\n", log.Text("Version:").Green().Bold().Plain(), version)) + sh := e.Env.Shell() + shellVersion := e.Env.Getenv("POSH_SHELL_VERSION") + if len(shellVersion) != 0 { + sh += fmt.Sprintf(" (%s)", shellVersion) + } + e.write(fmt.Sprintf("\n%s %s\n", log.Text("Shell:").Green().Bold().Plain(), sh)) + + // console title timing + titleStartTime := time.Now() + log.Debug("segment: Title") + consoleTitle := &config.Segment{ + Alias: "ConsoleTitle", + NameLength: 12, + Enabled: len(e.Config.ConsoleTitleTemplate) > 0, + Duration: time.Since(titleStartTime), + Type: config.TEXT, + } + _ = consoleTitle.MapSegmentWithWriter(e.Env) + consoleTitle.SetText(e.getTitleTemplateText()) + + largestSegmentNameLength := consoleTitle.NameLength + + // render prompt + e.write(log.Text("\nPrompt:\n\n").Green().Bold().Plain().String()) + e.write(e.Primary()) + + e.write(log.Text("\n\nSegments:\n\n").Green().Bold().Plain().String()) + + var segments []*config.Segment + segments = append(segments, consoleTitle) + + for _, block := range e.Config.Blocks { + for _, segment := range block.Segments { + segments = append(segments, segment) + if segment.NameLength > largestSegmentNameLength { + largestSegmentNameLength = segment.NameLength + } + } + } + + // 22 is the color for false/true and 7 is the reset color + largestSegmentNameLength += 22 + 7 + for _, segment := range segments { + duration := segment.Duration.Milliseconds() + var active log.Text + if segment.Enabled { + active = log.Text("true").Yellow() + } else { + active = log.Text("false").Purple() + } + segmentName := fmt.Sprintf("%s(%s)", segment.Name(), active.Plain()) + e.write(fmt.Sprintf("%-*s - %3d ms\n", largestSegmentNameLength, segmentName, duration)) + } + + e.write(fmt.Sprintf("\n%s %s\n", log.Text("Run duration:").Green().Bold().Plain(), time.Since(startTime))) + e.write(fmt.Sprintf("\n%s %s\n", log.Text("Cache path:").Green().Bold().Plain(), cache.Path())) + + cfg := e.Config.Source + if cfg == "" { + cfg = "no --config set, using default built-in configuration" + } + + e.write(fmt.Sprintf("\n%s %s\n", log.Text("Config path:").Green().Bold().Plain(), cfg)) + + e.write(log.Text("\nLogs:\n\n").Green().Bold().Plain().String()) + e.write(e.Env.Logs()) + return e.string() +} diff --git a/src/prompt/engine.go b/src/prompt/engine.go new file mode 100644 index 000000000000..e97fc6cc5d0e --- /dev/null +++ b/src/prompt/engine.go @@ -0,0 +1,610 @@ +package prompt + +import ( + "strings" + "sync" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +var cycle *color.Cycle = &color.Cycle{} + +type Engine struct { + Env runtime.Environment + streamingResults chan *config.Segment + Config *config.Config + activeSegment *config.Segment + previousActiveSegment *config.Segment + pendingSegments sync.Map + rprompt string + Overflow config.Overflow + prompt strings.Builder + allBlocks []*config.Block + currentLineLength int + Padding int + rpromptLength int + Plain bool + forceRender bool +} + +const ( + PRIMARY = "primary" + TRANSIENT = "transient" + DEBUG = "debug" + SECONDARY = "secondary" + RIGHT = "right" + TOOLTIP = "tooltip" + VALID = "valid" + ERROR = "error" + PREVIEW = "preview" +) + +func (e *Engine) write(txt string) { + // Grow capacity proactively if needed + if e.prompt.Cap() < e.prompt.Len()+len(txt) { + e.prompt.Grow(len(txt) * 2) // Grow by double the needed size to reduce future allocations + } + e.prompt.WriteString(txt) +} + +func (e *Engine) string() string { + txt := e.prompt.String() + e.prompt.Reset() + return txt +} + +func (e *Engine) canWriteRightBlock(length int, rprompt bool) (int, bool) { + if rprompt && (e.rprompt == "") { + return 0, false + } + + consoleWidth, err := e.Env.TerminalWidth() + if err != nil || consoleWidth == 0 { + return 0, false + } + + availableSpace := consoleWidth - e.currentLineLength + + // spanning multiple lines + if availableSpace < 0 { + overflow := e.currentLineLength % consoleWidth + availableSpace = consoleWidth - overflow + } + + availableSpace -= length + + promptBreathingRoom := 5 + if rprompt { + promptBreathingRoom = 30 + } + + canWrite := availableSpace >= promptBreathingRoom + + // reset the available space when we can't write so we can fill the line + if !canWrite { + availableSpace = consoleWidth - length + } + + return availableSpace, canWrite +} + +func (e *Engine) pwd() { + // only print when relevant + if e.Config.PWD == "" { + return + } + + // only print when supported + sh := e.Env.Shell() + if sh == shell.ELVISH || sh == shell.XONSH { + return + } + + pwd := e.Env.Pwd() + if e.Env.IsCygwin() { + pwd = strings.ReplaceAll(pwd, `\`, `/`) + } + + // Allow template logic to define when to enable the PWD (when supported) + pwdType, err := template.Render(e.Config.PWD, nil) + if err != nil || pwdType == "" { + return + } + + // Convert to Windows path when in WSL + if e.Env.IsWsl() { + pwd = e.Env.ConvertToWindowsPath(pwd) + } + + user := e.Env.User() + host, _ := e.Env.Host() + e.write(terminal.Pwd(pwdType, user, host, pwd)) +} + +func (e *Engine) getNewline() string { + newline := "\n" + + if e.Plain || e.Env.Flags().Debug { + return newline + } + + // Warp terminal will remove a newline character ('\n') from the prompt, so we hack it in. + if e.isWarp() { + return terminal.LineBreak() + } + + return newline +} + +func (e *Engine) writeNewline() { + defer func() { + e.currentLineLength = 0 + }() + + e.write(e.getNewline()) +} + +func (e *Engine) isWarp() bool { + return terminal.Program == terminal.Warp +} + +func (e *Engine) isIterm() bool { + return terminal.Program == terminal.ITerm +} + +func (e *Engine) shouldFill(filler string, padLength int) (string, bool) { + if filler == "" { + log.Debug("no filler specified") + return "", false + } + + e.Padding = padLength + + defer func() { + e.Padding = 0 + }() + + var err error + if filler, err = template.Render(filler, e); err != nil { + return "", false + } + + // allow for easy color overrides and templates + terminal.SetColors("default", "default") + terminal.Write("", "", filler) + filler, lenFiller := terminal.String() + if lenFiller == 0 { + log.Debug("filler has no length") + return "", false + } + + repeat := padLength / lenFiller + unfilled := padLength % lenFiller + txt := strings.Repeat(filler, repeat) + strings.Repeat(" ", unfilled) + log.Debug("filling with", txt) + return txt, true +} + +func (e *Engine) getTitleTemplateText() string { + if txt, err := template.Render(e.Config.ConsoleTitleTemplate, nil); err == nil { + return txt + } + + return "" +} + +func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool { + blockText, length := e.writeBlockSegments(block) + + // do not print anything when we don't have any text unless forced + if !block.Force && length == 0 { + return false + } + + return e.writeBlock(block, blockText, length, cancelNewline) +} + +// writeBlock handles the common logic for writing a block to the prompt +func (e *Engine) writeBlock(block *config.Block, blockText string, length int, cancelNewline bool) bool { + defer func() { + e.applyPowerShellBleedPatch() + }() + + // do not print a newline to avoid a leading space + // when we're printing the first primary prompt in + // the shell + if block.Newline && !cancelNewline { + e.writeNewline() + } + + switch block.Type { + case config.Prompt: + if block.Alignment == config.Left { + e.currentLineLength += length + e.write(blockText) + return true + } + + if block.Alignment != config.Right { + return false + } + + space, OK := e.canWriteRightBlock(length, false) + + // we can't print the right block as there's not enough room available + if !OK { + e.Overflow = block.Overflow + + switch e.Overflow { + case config.Break: + e.writeNewline() + case config.Hide: + // make sure to fill if needed + if padText, OK := e.shouldFill(block.Filler, space+length-e.currentLineLength); OK { + e.write(padText) + } + + e.currentLineLength = 0 + return true + } + } + + defer func() { + e.currentLineLength = 0 + e.Overflow = "" + }() + + // validate if we have a filler and fill if needed + if padText, OK := e.shouldFill(block.Filler, space); OK { + e.write(padText) + e.write(blockText) + return true + } + + if space > 0 { + e.write(strings.Repeat(" ", space)) + } + + e.write(blockText) + case config.RPrompt: + e.rprompt = blockText + e.rpromptLength = length + } + + return true +} + +// renderBlockFromCache re-renders a block using existing segment data without re-execution +func (e *Engine) renderBlockFromCache(block *config.Block, cancelNewline bool) bool { + // Re-render all segments in the block + for segmentIndex, segment := range block.Segments { + // Allow pending segments to render (they show "..." text) + if !segment.Pending && !segment.Enabled && segment.ResolveStyle() != config.Accordion { + continue + } + + // Render segment text (will use pending state if still pending) + if !segment.Render(segmentIndex, e.forceRender) { + continue + } + + if colors, newCycle := cycle.Loop(); colors != nil { + cycle = &newCycle + segment.Foreground = colors.Foreground + segment.Background = colors.Background + } + + if terminal.Len() == 0 && len(block.LeadingDiamond) > 0 { + segment.LeadingDiamond = block.LeadingDiamond + } + + e.setActiveSegment(segment) + e.renderActiveSegment() + } + + if e.activeSegment != nil && len(block.TrailingDiamond) > 0 { + e.activeSegment.TrailingDiamond = block.TrailingDiamond + } + + e.writeSeparator(true) + e.activeSegment = nil + e.previousActiveSegment = nil + + blockText, length := terminal.String() + + // do not print anything when we don't have any text unless forced + if !block.Force && length == 0 { + return false + } + + return e.writeBlock(block, blockText, length, cancelNewline) +} + +func (e *Engine) applyPowerShellBleedPatch() { + // when in PowerShell, we need to clear the line after the prompt + // to avoid the background being printed on the next line + // when at the end of the buffer. + // See https://github.com/JanDeDobbeleer/oh-my-posh/issues/65 + if e.Env.Shell() != shell.PWSH { + return + } + + // only do this when enabled + if !e.Config.PatchPwshBleed { + return + } + + e.write(terminal.ClearAfter()) +} + +func (e *Engine) setActiveSegment(segment *config.Segment) { + e.activeSegment = segment + terminal.Interactive = segment.Interactive + terminal.SetColors(segment.ResolveBackground(), segment.ResolveForeground()) +} + +func (e *Engine) renderActiveSegment() { + e.writeSeparator(false) + + switch e.activeSegment.ResolveStyle() { + case config.Plain, config.Powerline: + terminal.Write(color.Background, color.Foreground, e.activeSegment.Text()) + case config.Diamond: + background := color.Transparent + + if e.previousActiveSegment != nil && e.previousActiveSegment.HasEmptyDiamondAtEnd() { + background = e.previousActiveSegment.ResolveBackground() + } + + terminal.Write(background, color.Background, e.activeSegment.LeadingDiamond) + terminal.Write(color.Background, color.Foreground, e.activeSegment.Text()) + case config.Accordion: + // Render accordion segments if enabled OR pending (pending shows "..." text) + if e.activeSegment.Enabled || e.activeSegment.Pending { + terminal.Write(color.Background, color.Foreground, e.activeSegment.Text()) + } + } + + e.previousActiveSegment = e.activeSegment + + terminal.SetParentColors(e.previousActiveSegment.ResolveBackground(), e.previousActiveSegment.ResolveForeground()) +} + +func (e *Engine) writeSeparator(final bool) { + if e.activeSegment == nil { + return + } + + isCurrentDiamond := e.activeSegment.ResolveStyle() == config.Diamond + if final && isCurrentDiamond { + terminal.Write(color.Transparent, color.Background, e.activeSegment.TrailingDiamond) + return + } + + isPreviousDiamond := e.previousActiveSegment != nil && e.previousActiveSegment.ResolveStyle() == config.Diamond + if isPreviousDiamond { + e.adjustTrailingDiamondColorOverrides() + } + + if isPreviousDiamond && isCurrentDiamond && e.activeSegment.LeadingDiamond == "" { + terminal.Write(color.Background, color.ParentBackground, e.previousActiveSegment.TrailingDiamond) + return + } + + if isPreviousDiamond && len(e.previousActiveSegment.TrailingDiamond) > 0 { + terminal.Write(color.Transparent, color.ParentBackground, e.previousActiveSegment.TrailingDiamond) + } + + isPowerline := e.activeSegment.IsPowerline() + + shouldOverridePowerlineLeadingSymbol := func() bool { + if !isPowerline { + return false + } + + if isPowerline && e.activeSegment.LeadingPowerlineSymbol == "" { + return false + } + + if e.previousActiveSegment != nil && e.previousActiveSegment.IsPowerline() { + return false + } + + return true + } + + if shouldOverridePowerlineLeadingSymbol() { + terminal.Write(color.Transparent, color.Background, e.activeSegment.LeadingPowerlineSymbol) + return + } + + resolvePowerlineSymbol := func() string { + if isPowerline { + return e.activeSegment.PowerlineSymbol + } + + if e.previousActiveSegment != nil && e.previousActiveSegment.IsPowerline() { + return e.previousActiveSegment.PowerlineSymbol + } + + return "" + } + + symbol := resolvePowerlineSymbol() + if symbol == "" { + return + } + + bgColor := color.Background + if final || !isPowerline { + bgColor = color.Transparent + } + + if e.activeSegment.ResolveStyle() == config.Diamond && e.activeSegment.LeadingDiamond == "" { + bgColor = color.Background + } + + if e.activeSegment.InvertPowerline || (e.previousActiveSegment != nil && e.previousActiveSegment.InvertPowerline) { + terminal.Write(e.getPowerlineColor(), bgColor, symbol) + return + } + + terminal.Write(bgColor, e.getPowerlineColor(), symbol) +} + +func (e *Engine) getPowerlineColor() color.Ansi { + if e.previousActiveSegment == nil { + return color.Transparent + } + + if e.previousActiveSegment.ResolveStyle() == config.Diamond && e.previousActiveSegment.TrailingDiamond == "" { + return e.previousActiveSegment.ResolveBackground() + } + + if e.activeSegment.ResolveStyle() == config.Diamond && e.activeSegment.LeadingDiamond == "" { + return e.previousActiveSegment.ResolveBackground() + } + + if !e.previousActiveSegment.IsPowerline() { + return color.Transparent + } + + return e.previousActiveSegment.ResolveBackground() +} + +func (e *Engine) adjustTrailingDiamondColorOverrides() { + // as we now already adjusted the activeSegment, we need to change the value + // of background and foreground to parentBackground and parentForeground + // this will still break when using parentBackground and parentForeground as keywords + // in a trailing diamond, but let's fix that when it happens as it requires either a rewrite + // of the logic for diamonds or storing grandparents as well like one happy family. + if e.previousActiveSegment == nil || e.previousActiveSegment.TrailingDiamond == "" { + return + } + + trailingDiamond := e.previousActiveSegment.TrailingDiamond + // Optimize: check both conditions in a single pass + hasBg := strings.Contains(trailingDiamond, string(color.Background)) + hasFg := strings.Contains(trailingDiamond, string(color.Foreground)) + + if !hasBg && !hasFg { + return + } + + match := regex.FindNamedRegexMatch(terminal.AnchorRegex, trailingDiamond) + if len(match) == 0 { + return + } + + adjustOverride := func(anchor string, override color.Ansi) { + newOverride := override + switch override { //nolint:exhaustive + case color.Foreground: + newOverride = color.ParentForeground + case color.Background: + newOverride = color.ParentBackground + } + + if override == newOverride { + return + } + + newAnchor := strings.Replace(match[terminal.ANCHOR], string(override), string(newOverride), 1) + e.previousActiveSegment.TrailingDiamond = strings.Replace(e.previousActiveSegment.TrailingDiamond, anchor, newAnchor, 1) + } + + if len(match[terminal.BG]) > 0 { + adjustOverride(match[terminal.ANCHOR], color.Ansi(match[terminal.BG])) + } + + if len(match[terminal.FG]) > 0 { + adjustOverride(match[terminal.ANCHOR], color.Ansi(match[terminal.FG])) + } +} + +func (e *Engine) rectifyTerminalWidth(diff int) { + // Since the terminal width may not be given by the CLI flag, we should always call this here. + _, err := e.Env.TerminalWidth() + if err != nil { + // Skip when we're unable to determine the terminal width. + return + } + + e.Env.Flags().TerminalWidth += diff +} + +// New returns a prompt engine initialized with the +// given configuration options, and is ready to print any +// of the prompt components. +func New(flags *runtime.Flags) *Engine { + env := &runtime.Terminal{} + env.Init(flags) + + reload, _ := cache.Get[bool](cache.Device, config.RELOAD) + cfg := config.Get(flags.ConfigPath, reload) + + template.Init(env, cfg.Var, cfg.Maps) + + flags.HasExtra = cfg.DebugPrompt != nil || + cfg.SecondaryPrompt != nil || + cfg.TransientPrompt != nil || + cfg.ValidLine != nil || + cfg.ErrorLine != nil + + // when we print using https://github.com/akinomyoga/ble.sh, this needs to be unescaped for certain prompts + sh := env.Shell() + if sh == shell.BASH && !flags.Escape { + sh = shell.GENERIC + } + + terminal.Init(sh) + terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() + terminal.Colors = cfg.MakeColors(env) + terminal.Plain = flags.Plain + + eng := &Engine{ + Config: cfg, + Env: env, + Plain: flags.Plain, + forceRender: flags.Force || len(env.Getenv("POSH_FORCE_RENDER")) > 0, + prompt: strings.Builder{}, + } + + // Pre-allocate prompt builder capacity to reduce allocations during rendering + eng.prompt.Grow(512) // Start with 512 bytes capacity, will grow as needed + + switch env.Shell() { + case shell.XONSH: + // In Xonsh, the behavior of wrapping at the end of a prompt line is inconsistent across different operating systems. + // On Windows, it wraps before the last cell on the terminal screen, that is, the last cell is never available for a prompt line. + if env.GOOS() == runtime.WINDOWS { + eng.rectifyTerminalWidth(-1) + } + case shell.ELVISH: + // In Elvish, the case is similar to that in Xonsh. + // However, on Windows, we have to reduce the terminal width by 1 again to ensure that newlines are displayed correctly. + diff := -1 + if env.GOOS() == runtime.WINDOWS { + diff = -2 + } + eng.rectifyTerminalWidth(diff) + case shell.PWSH: + // when in PowerShell, and force patching the bleed bug + // we need to reduce the terminal width by 1 so the last + // character isn't cut off by the ANSI escape sequences + // See https://github.com/JanDeDobbeleer/oh-my-posh/issues/65 + if cfg.PatchPwshBleed { + eng.rectifyTerminalWidth(-1) + } + } + + return eng +} diff --git a/src/prompt/engine_test.go b/src/prompt/engine_test.go new file mode 100644 index 000000000000..92de5c1fc85d --- /dev/null +++ b/src/prompt/engine_test.go @@ -0,0 +1,479 @@ +package prompt + +import ( + "errors" + "strings" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/maps" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + + "github.com/stretchr/testify/assert" +) + +func TestCanWriteRPrompt(t *testing.T) { + cases := []struct { + TerminalWidthError error + Case string + TerminalWidth int + PromptLength int + RPromptLength int + Expected bool + }{ + {Case: "Width Error", Expected: false, TerminalWidthError: errors.New("burp")}, + {Case: "Terminal > Prompt enabled", Expected: true, TerminalWidth: 200, PromptLength: 100, RPromptLength: 10}, + {Case: "Terminal > Prompt enabled edge", Expected: true, TerminalWidth: 200, PromptLength: 100, RPromptLength: 70}, + {Case: "Prompt > Terminal enabled", Expected: true, TerminalWidth: 200, PromptLength: 300, RPromptLength: 70}, + {Case: "Terminal > Prompt disabled no breathing", Expected: false, TerminalWidth: 200, PromptLength: 100, RPromptLength: 71}, + {Case: "Prompt > Terminal disabled no breathing", Expected: false, TerminalWidth: 200, PromptLength: 300, RPromptLength: 80}, + {Case: "Prompt > Terminal disabled no room", Expected: true, TerminalWidth: 200, PromptLength: 400, RPromptLength: 80}, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("TerminalWidth").Return(tc.TerminalWidth, tc.TerminalWidthError) + engine := &Engine{ + Env: env, + rpromptLength: tc.RPromptLength, + currentLineLength: tc.PromptLength, + rprompt: "hello", + } + + _, got := engine.canWriteRightBlock(tc.RPromptLength, true) + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestPrintPWD(t *testing.T) { + cases := []struct { + Case string + Expected string + Config string + Pwd string + Shell string + Cygwin bool + }{ + {Case: "Empty PWD"}, + {Case: "OSC99", Config: terminal.OSC99, Expected: "\x1b]9;9;pwd\x1b\\"}, + {Case: "OSC99 - Elvish", Config: terminal.OSC99, Shell: shell.ELVISH}, + {Case: "OSC7", Config: terminal.OSC7, Expected: "\x1b]7;file://host/pwd\x1b\\"}, + {Case: "OSC51", Config: terminal.OSC51, Expected: "\x1b]51;Auser@host:pwd\x1b\\"}, + {Case: "Template (empty)", Config: "{{ if eq .Shell \"pwsh\" }}osc7{{ end }}"}, + {Case: "Template (non empty)", Shell: shell.GENERIC, Config: "{{ if eq .Shell \"shell\" }}osc7{{ end }}", Expected: "\x1b]7;file://host/pwd\x1b\\"}, + { + Case: "OSC99 Cygwin", + Pwd: `C:\Users\user\Documents\GitHub\oh-my-posh`, + Config: terminal.OSC99, + Cygwin: true, + Expected: "\x1b]9;9;C:/Users/user/Documents/GitHub/oh-my-posh\x1b\\", + }, + { + Case: "OSC99 Windows", + Pwd: `C:\Users\user\Documents\GitHub\oh-my-posh`, + Config: terminal.OSC99, + Expected: "\x1b]9;9;C:\\Users\\user\\Documents\\GitHub\\oh-my-posh\x1b\\", + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + if tc.Pwd == "" { + tc.Pwd = "pwd" + } + + env.On("Pwd").Return(tc.Pwd) + env.On("User").Return("user") + env.On("Shell").Return(tc.Shell) + env.On("IsCygwin").Return(tc.Cygwin) + env.On("IsWsl").Return(false) + env.On("Host").Return("host", nil) + + template.Cache = &cache.Template{ + SimpleTemplate: cache.SimpleTemplate{ + Shell: tc.Shell, + }, + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + + terminal.Init(shell.GENERIC) + + engine := &Engine{ + Env: env, + Config: &config.Config{ + PWD: tc.Config, + }, + } + + engine.pwd() + got := engine.string() + + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestPrintPWDWSL(t *testing.T) { + cases := []struct { + Case string + Expected string + Config string + Pwd string + Shell string + WinPath string + IsWsl bool + }{ + { + Case: "OSC99 WSL", + Pwd: "/home/user/projects", + Config: terminal.OSC99, + IsWsl: true, + WinPath: "//wsl.localhost/Ubuntu/home/user/projects", + Expected: "\x1b]9;9;//wsl.localhost/Ubuntu/home/user/projects\x1b\\", + }, + { + Case: "OSC99 Not WSL", + Pwd: "/home/user/projects", + Config: terminal.OSC99, + IsWsl: false, + Expected: "\x1b]9;9;/home/user/projects\x1b\\", + }, + { + Case: "OSC7 WSL (with conversion)", + Pwd: "/home/user/projects", + Config: terminal.OSC7, + IsWsl: true, + WinPath: "//wsl.localhost/Ubuntu/home/user/projects", + Expected: "\x1b]7;file://host///wsl.localhost/Ubuntu/home/user/projects\x1b\\", + }, + { + Case: "OSC51 WSL (with conversion)", + Pwd: "/home/user/projects", + Config: terminal.OSC51, + IsWsl: true, + WinPath: "//wsl.localhost/Ubuntu/home/user/projects", + Expected: "\x1b]51;Auser@host://wsl.localhost/Ubuntu/home/user/projects\x1b\\", + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Pwd").Return(tc.Pwd) + env.On("User").Return("user") + env.On("Shell").Return(tc.Shell) + env.On("IsCygwin").Return(false) + env.On("IsWsl").Return(tc.IsWsl) + env.On("Host").Return("host", nil) + + if tc.IsWsl { + if tc.WinPath == "" { + tc.WinPath = tc.Pwd + } + env.On("ConvertToWindowsPath", tc.Pwd).Return(tc.WinPath) + } + + template.Cache = &cache.Template{ + SimpleTemplate: cache.SimpleTemplate{ + Shell: tc.Shell, + }, + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + + terminal.Init(shell.GENERIC) + + engine := &Engine{ + Env: env, + Config: &config.Config{ + PWD: tc.Config, + }, + } + + engine.pwd() + got := engine.string() + + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func BenchmarkEngineRender(b *testing.B) { + for b.Loop() { + engineRender() + } +} + +func engineRender() { + cfg := config.Load("") + + env := &runtime.Terminal{} + env.Init(nil) + + template.Cache = &cache.Template{ + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + + terminal.Init(shell.GENERIC) + terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() + terminal.Colors = cfg.MakeColors(env) + + engine := &Engine{ + Config: cfg, + Env: env, + } + + engine.Primary() +} + +func TestGetTitle(t *testing.T) { + cases := []struct { + Template string + User string + Cwd string + PathSeparator string + ShellName string + Expected string + Root bool + }{ + { + Template: "{{.Env.USERDOMAIN}} :: {{.PWD}}{{if .Root}} :: Admin{{end}} :: {{.Shell}}", + Cwd: "C:\\vagrant", + PathSeparator: "\\", + ShellName: "PowerShell", + Root: true, + Expected: "\x1b]0;MyCompany :: C:\\vagrant :: Admin :: PowerShell\a", + }, + { + Template: "{{.Folder}}{{if .Root}} :: Admin{{end}} :: {{.Shell}}", + Cwd: "C:\\vagrant", + PathSeparator: "\\", + ShellName: "PowerShell", + Expected: "\x1b]0;vagrant :: PowerShell\a", + }, + { + Template: "{{.UserName}}@{{.HostName}}{{if .Root}} :: Admin{{end}} :: {{.Shell}}", + Root: true, + User: "MyUser", + PathSeparator: "\\", + ShellName: "PowerShell", + Expected: "\x1b]0;MyUser@MyHost :: Admin :: PowerShell\a", + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Pwd").Return(tc.Cwd) + env.On("Home").Return("/usr/home") + env.On("PathSeparator").Return(tc.PathSeparator) + env.On("Getenv", "USERDOMAIN").Return("MyCompany") + env.On("Shell").Return(tc.ShellName) + + terminal.Init(shell.GENERIC) + + template.Cache = &cache.Template{ + SimpleTemplate: cache.SimpleTemplate{ + Shell: tc.ShellName, + UserName: "MyUser", + Root: tc.Root, + HostName: "MyHost", + PWD: tc.Cwd, + Folder: "vagrant", + }, + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + + engine := &Engine{ + Config: &config.Config{ + ConsoleTitleTemplate: tc.Template, + }, + Env: env, + } + + title := engine.getTitleTemplateText() + got := terminal.FormatTitle(title) + + assert.Equal(t, tc.Expected, got) + } +} + +func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) { + cases := []struct { + Template string + User string + Cwd string + PathSeparator string + ShellName string + Expected string + Root bool + }{ + { + Template: "Not using Host only {{.UserName}} and {{.Shell}}", + User: "MyUser", + PathSeparator: "\\", + ShellName: "PowerShell", + Expected: "\x1b]0;Not using Host only MyUser and PowerShell\a", + }, + { + Template: "{{.UserName}}@{{.HostName}} :: {{.Shell}}", + User: "MyUser", + PathSeparator: "\\", + ShellName: "PowerShell", + Expected: "\x1b]0;MyUser@ :: PowerShell\a", + }, + { + Template: "\x1b[93m[\x1b[39m\x1b[96mconsole-title\x1b[39m\x1b[96m ≡\x1b[39m\x1b[31m +0\x1b[39m\x1b[31m ~1\x1b[39m\x1b[31m -0\x1b[39m\x1b[31m !\x1b[39m\x1b[93m]\x1b[39m", + Expected: "\x1b]0;[console-title ≡ +0 ~1 -0 !]\a", + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Pwd").Return(tc.Cwd) + env.On("Home").Return("/usr/home") + env.On("Getenv", "USERDOMAIN").Return("MyCompany") + env.On("Shell").Return(tc.ShellName) + + terminal.Init(shell.GENERIC) + + template.Cache = &cache.Template{ + SimpleTemplate: cache.SimpleTemplate{ + Shell: tc.ShellName, + UserName: "MyUser", + Root: tc.Root, + HostName: "", + }, + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + + engine := &Engine{ + Config: &config.Config{ + ConsoleTitleTemplate: tc.Template, + }, + Env: env, + } + + title := engine.getTitleTemplateText() + got := terminal.FormatTitle(title) + + assert.Equal(t, tc.Expected, got) + } +} + +func TestShouldFill(t *testing.T) { + cases := []struct { + Case string + Overflow config.Overflow + ExpectedFiller string + Block config.Block + Padding int + ExpectedBool bool + }{ + { + Case: "Plain single character with no padding", + Padding: 0, + ExpectedFiller: "", + ExpectedBool: true, + Block: config.Block{ + Overflow: config.Hide, + Filler: "-", + }, + }, + { + Case: "Plain single character with 1 padding", + Padding: 1, + ExpectedFiller: "-", + ExpectedBool: true, + Block: config.Block{ + Overflow: config.Hide, + Filler: "-", + }, + }, + { + Case: "Plain single character with lots of padding", + Padding: 200, + ExpectedFiller: strings.Repeat("-", 200), + ExpectedBool: true, + Block: config.Block{ + Overflow: config.Hide, + Filler: "-", + }, + }, + { + Case: "Plain multi-character with some padding", + Padding: 20, + ExpectedFiller: strings.Repeat("-^-", 6) + " ", + ExpectedBool: true, + Block: config.Block{ + Overflow: config.Hide, + Filler: "-^-", + }, + }, + { + Case: "Template conditional on overflow with no overflow", + Padding: 3, + ExpectedFiller: strings.Repeat("X", 3), + ExpectedBool: true, + Block: config.Block{ + Overflow: config.Hide, + Filler: "{{ if .Overflow -}} O {{- else -}} X {{- end }}", + }, + }, + { + Case: "Template conditional on overflow with an overflow", + Overflow: config.Break, + Padding: 3, + ExpectedFiller: strings.Repeat("O", 3), + ExpectedBool: true, + Block: config.Block{ + Overflow: config.Hide, + Filler: "{{ if .Overflow -}} O {{- else -}} X {{- end }}", + }, + }, + { + Case: "Template conditional on overflow break", + Overflow: config.Break, + Padding: 3, + ExpectedFiller: strings.Repeat("O", 3), + ExpectedBool: true, + Block: config.Block{ + Overflow: config.Break, + Filler: `{{ if eq .Overflow "break" -}} O {{- else -}} X {{- end }}`, + }, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Shell").Return(shell.GENERIC) + + engine := &Engine{ + Env: env, + Overflow: tc.Overflow, + } + + template.Cache = &cache.Template{ + SimpleTemplate: cache.SimpleTemplate{ + Shell: shell.GENERIC, + }, + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + + terminal.Init(shell.GENERIC) + terminal.Plain = true + terminal.Colors = &color.Defaults{} + + gotFiller, gotBool := engine.shouldFill(tc.Block.Filler, tc.Padding) + + assert.Equal(t, tc.ExpectedFiller, gotFiller, tc.Case) + assert.Equal(t, tc.ExpectedBool, gotBool, tc.Case) + } +} diff --git a/src/prompt/extra.go b/src/prompt/extra.go new file mode 100644 index 000000000000..5e120767c248 --- /dev/null +++ b/src/prompt/extra.go @@ -0,0 +1,111 @@ +package prompt + +import ( + "fmt" + + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +type ExtraPromptType int + +const ( + Transient ExtraPromptType = iota + Valid + Error + Secondary + Debug +) + +func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string { + var prompt *config.Segment + + switch promptType { + case Debug: + prompt = e.Config.DebugPrompt + case Transient: + prompt = e.Config.TransientPrompt + case Valid: + prompt = e.Config.ValidLine + case Error: + prompt = e.Config.ErrorLine + case Secondary: + prompt = e.Config.SecondaryPrompt + } + + if prompt == nil { + prompt = &config.Segment{} + } + + getTemplate := func(template string) string { + if len(template) != 0 { + return template + } + switch promptType { //nolint: exhaustive + case Debug: + return "[DBG]: " + case Transient: + return "{{ .Shell }}> " + case Secondary: + return "> " + default: + return "" + } + } + + promptText, err := template.Render(getTemplate(prompt.Template), nil) + if err != nil { + promptText = err.Error() + } + + if promptType == Transient && prompt.Newline { + promptText = fmt.Sprintf("%s%s", e.getNewline(), promptText) + } + + if promptType == Transient && e.Config.ShellIntegration { + exitCode, _ := e.Env.StatusCodes() + e.write(terminal.CommandFinished(exitCode, e.Env.Flags().NoExitCode)) + e.write(terminal.PromptStart()) + } + + foreground := color.Ansi(prompt.ForegroundTemplates.FirstMatch(nil, string(prompt.Foreground))) + background := color.Ansi(prompt.BackgroundTemplates.FirstMatch(nil, string(prompt.Background))) + terminal.SetColors(background, foreground) + terminal.Write(background, foreground, promptText) + + str, length := terminal.String() + + if promptType == Transient && len(prompt.Filler) != 0 { + consoleWidth, err := e.Env.TerminalWidth() + if err == nil || consoleWidth != 0 { + if padText, OK := e.shouldFill(prompt.Filler, consoleWidth-length); OK { + str += padText + } + } + } + + switch e.Env.Shell() { + case shell.ZSH: + if promptType == Transient { + if !e.Env.Flags().Eval { + break + } + + prompt := fmt.Sprintf("PS1=%s", shell.QuotePosixStr(str)) + // empty RPROMPT + prompt += "\nRPROMPT=''" + return prompt + } + case shell.PWSH: + if promptType == Transient { + // clear the line afterwards to prevent text from being written on the same line + // see https://github.com/JanDeDobbeleer/oh-my-posh/issues/3628 + return str + terminal.ClearAfter() + } + } + + return str +} diff --git a/src/prompt/preview.go b/src/prompt/preview.go new file mode 100644 index 000000000000..ce64f3653c30 --- /dev/null +++ b/src/prompt/preview.go @@ -0,0 +1,49 @@ +package prompt + +import ( + "fmt" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/text" +) + +func (e *Engine) Preview() string { + builder := text.NewBuilder() + + printPrompt := func(title, prompt string) { + builder.WriteString(log.Text(fmt.Sprintf("\n%s:\n\n", title)).Bold().Plain().String()) + builder.WriteString(prompt) + builder.WriteString("\n") + } + + printPrompt("Primary", e.Primary()) + + right := e.RPrompt() + if len(right) > 0 { + printPrompt("Right", right) + } + + if e.Config.SecondaryPrompt != nil { + printPrompt("Secondary", e.ExtraPrompt(Secondary)) + } + + if e.Config.TransientPrompt != nil { + printPrompt("Transient", e.ExtraPrompt(Transient)) + } + + if e.Config.DebugPrompt != nil { + printPrompt("Debug", e.ExtraPrompt(Debug)) + } + + if e.Config.ValidLine != nil { + printPrompt("Valid", e.ExtraPrompt(Valid)) + } + + if e.Config.ErrorLine != nil { + printPrompt("Error", e.ExtraPrompt(Error)) + } + + builder.WriteString("\n") + + return builder.String() +} diff --git a/src/prompt/primary.go b/src/prompt/primary.go new file mode 100644 index 000000000000..227b7e5b9bab --- /dev/null +++ b/src/prompt/primary.go @@ -0,0 +1,153 @@ +package prompt + +import ( + "fmt" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +func (e *Engine) Primary() string { + return e.primaryInternal(false) +} + +// primaryInternal handles both regular and streaming prompt rendering +func (e *Engine) primaryInternal(fromCache bool) string { + needsPrimaryRightPrompt := e.needsPrimaryRightPrompt() + + e.writePrimaryPromptInternal(needsPrimaryRightPrompt, fromCache) + + switch e.Env.Shell() { + case shell.ZSH: + if !e.Env.Flags().Eval { + break + } + + // Warp doesn't support RPROMPT so we need to write it manually + if e.isWarp() { + e.writePrimaryRightPrompt() + prompt := fmt.Sprintf("PS1=%s", shell.QuotePosixStr(e.string())) + return prompt + } + + prompt := fmt.Sprintf("PS1=%s", shell.QuotePosixStr(e.string())) + prompt += fmt.Sprintf("\nRPROMPT=%s", shell.QuotePosixStr(e.rprompt)) + + return prompt + default: + if !needsPrimaryRightPrompt { + break + } + + e.writePrimaryRightPrompt() + } + + return e.string() +} + +func (e *Engine) writePrimaryPrompt(needsPrimaryRPrompt bool) { + e.writePrimaryPromptInternal(needsPrimaryRPrompt, false) +} + +// writePrimaryPromptInternal handles both regular and streaming prompt rendering +func (e *Engine) writePrimaryPromptInternal(needsPrimaryRPrompt, fromCache bool) { + if e.Config.ShellIntegration { + exitCode, _ := e.Env.StatusCodes() + e.write(terminal.CommandFinished(exitCode, e.Env.Flags().NoExitCode)) + e.write(terminal.PromptStart()) + } + + // cache a pointer to the color cycle + cycle = &e.Config.Cycle + var cancelNewline, didRender bool + + // Choose block source based on whether we're rendering from cache + blocks := e.Config.Blocks + if fromCache { + blocks = e.allBlocks + } + + for i, block := range blocks { + // do not print a leading newline when we're at the first row and the prompt is cleared + if i == 0 { + row, _ := e.Env.CursorPosition() + cancelNewline = e.Env.Flags().Cleared || e.Env.Flags().PromptCount == 1 || row == 1 + } + + // skip setting a newline when we didn't print anything yet + if i != 0 { + cancelNewline = !didRender + } + + if block.Type == config.RPrompt && !needsPrimaryRPrompt { + continue + } + + // Choose render method based on whether we're rendering from cache + var rendered bool + if fromCache { + rendered = e.renderBlockFromCache(block, cancelNewline) + } else { + rendered = e.renderBlock(block, cancelNewline) + } + + if rendered { + didRender = true + } + + // Only handle tooltip caching in regular (non-cached) rendering + if !fromCache && !e.Config.ToolTipsAction.IsDefault() { + cache.Set(cache.Session, RPromptKey, e.rprompt, cache.INFINITE) + cache.Set(cache.Session, RPromptLengthKey, e.rpromptLength, cache.INFINITE) + } + } + + if len(e.Config.ConsoleTitleTemplate) > 0 && !e.Env.Flags().Plain { + title := e.getTitleTemplateText() + e.write(terminal.FormatTitle(title)) + } + + if e.Config.FinalSpace { + e.write(" ") + e.currentLineLength++ + } + + if e.Config.ITermFeatures != nil && e.isIterm() { + host, _ := e.Env.Host() + e.write(terminal.RenderItermFeatures(e.Config.ITermFeatures, e.Env.Shell(), e.Env.Pwd(), e.Env.User(), host)) + } + + if e.Config.ShellIntegration { + e.write(terminal.CommandStart()) + } + + e.pwd() +} + +func (e *Engine) needsPrimaryRightPrompt() bool { + if e.Env.Flags().Debug { + return true + } + + switch e.Env.Shell() { + case shell.PWSH, shell.GENERIC, shell.ZSH: + return true + default: + return false + } +} + +func (e *Engine) writePrimaryRightPrompt() { + space, OK := e.canWriteRightBlock(e.rpromptLength, true) + if !OK { + return + } + + e.write(terminal.SaveCursorPosition()) + e.write(strings.Repeat(" ", space)) + e.write(e.rprompt) + e.write(terminal.RestoreCursorPosition()) +} diff --git a/src/prompt/rprompt.go b/src/prompt/rprompt.go new file mode 100644 index 000000000000..a57f2fb16ddb --- /dev/null +++ b/src/prompt/rprompt.go @@ -0,0 +1,51 @@ +package prompt + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/shell" +) + +const ( + RPromptKey = "rprompt" + RPromptLengthKey = "rprompt_length" +) + +func (e *Engine) RPrompt() string { + var rprompt *config.Block + + for _, block := range e.Config.Blocks { + if block.Type != config.RPrompt { + continue + } + + rprompt = block + break + } + + if rprompt == nil { + return "" + } + + text, length := e.writeBlockSegments(rprompt) + + // do not print anything when we don't have any text + if length == 0 { + return "" + } + + e.rpromptLength = length + + if e.Env.Shell() == shell.ELVISH && e.Env.GOOS() != runtime.WINDOWS { + // Workaround to align with a right-aligned block on non-Windows systems. + text += " " + } + + if !e.Config.ToolTipsAction.IsDefault() { + cache.Set(cache.Session, RPromptKey, text, cache.INFINITE) + cache.Set(cache.Session, RPromptLengthKey, e.rpromptLength, cache.INFINITE) + } + + return text +} diff --git a/src/prompt/segments.go b/src/prompt/segments.go new file mode 100644 index 000000000000..b89404ab04c6 --- /dev/null +++ b/src/prompt/segments.go @@ -0,0 +1,180 @@ +package prompt + +import ( + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/log" + runjobs "github.com/jandedobbeleer/oh-my-posh/src/runtime/jobs" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +type result struct { + segment *config.Segment + index int +} + +func (e *Engine) writeBlockSegments(block *config.Block) (string, int) { + length := len(block.Segments) + + if length == 0 { + return "", 0 + } + + out := make(chan result, length) + + e.writeSegmentsConcurrently(block.Segments, out) + + e.writeSegments(out, block) + + if e.activeSegment != nil && len(block.TrailingDiamond) > 0 { + e.activeSegment.TrailingDiamond = block.TrailingDiamond + } + + e.writeSeparator(true) + + e.activeSegment = nil + e.previousActiveSegment = nil + + return terminal.String() +} + +// writeSegmentsConcurrently uses individual goroutines for each segment +func (e *Engine) writeSegmentsConcurrently(segments []*config.Segment, out chan result) { + for i, segment := range segments { + // In streaming mode, pre-register all segments as pending + // This ensures countPendingSegments() sees them before timeout occurs + if e.Env.Flags().Streaming { + segment.Timeout = e.Config.Streaming + e.pendingSegments.Store(segment.Name(), true) + } + + go func(segment *config.Segment, index int) { + if segment.Timeout > 0 { + e.executeSegmentWithTimeout(segment) + } else { + segment.Execute(e.Env) + } + + out <- result{segment, index} + + // In streaming mode, clean up pre-registered segments that completed before timeout + if e.Env.Flags().Streaming && segment.Timeout > 0 && !segment.Pending { + e.pendingSegments.Delete(segment.Name()) + } + }(segment, i) + } +} + +// executeSegmentWithTimeout handles segment execution with timeout logic +func (e *Engine) executeSegmentWithTimeout(segment *config.Segment) { + done := make(chan bool) + gidChan := make(chan uint64, 1) + + go func() { + gidChan <- runjobs.CurrentGID() + segment.Execute(e.Env) + close(done) + }() + + gid := <-gidChan + + select { + case <-done: + // Completed before timeout - nothing extra to do + case <-time.After(time.Duration(segment.Timeout) * time.Millisecond): + log.Errorf("timeout after %dms for segment: %s", segment.Timeout, segment.Name()) + + // When streaming is enabled, don't kill goroutines - let them continue executing + if e.Env.Flags().Streaming { + segment.Pending = true + // Note: Do NOT set segment.Enabled here - that would race with Execute() + // Rendering logic handles Pending state to display "..." text + + // Track this segment as pending and continue execution in background + e.trackPendingSegment(segment, done) + return + } + + // For non-streaming mode, kill the goroutine + if err := runjobs.KillGoroutineChildren(gid); err != nil { + log.Errorf("failed to kill child processes for goroutine %d (segment: %s): %v", gid, segment.Name(), err) + } + } +} + +func (e *Engine) writeSegments(out chan result, block *config.Block) { + count := len(block.Segments) + current := 0 + executedCount := 0 + results := make([]*config.Segment, count) + // Pre-allocate map with known capacity to reduce allocations + executed := make(map[string]bool, count) + segmentIndex := 0 + + // Process results as they come in, eliminating busy waiting + for executedCount < count { + res := <-out // Block until result is available + executedCount++ + + results[res.index] = res.segment + executed[res.segment.Name()] = true + + // Process segments that can now be rendered + for current < count && results[current] != nil { + segment := results[current] + if !e.canRenderSegment(segment, executed) { + break + } + + if segment.Render(segmentIndex, e.forceRender) { + segmentIndex++ + } + + e.writeSegment(block, segment) + current++ + } + } + + // render all remaining segments where the needs can't be resolved + for current < executedCount { + segment := results[current] + if segment.Render(segmentIndex, e.forceRender) { + segmentIndex++ + } + + e.writeSegment(block, segment) + current++ + } +} + +func (e *Engine) writeSegment(block *config.Block, segment *config.Segment) { + // Allow pending segments to render (they show "..." text) + if !segment.Pending && !segment.Enabled && segment.ResolveStyle() != config.Accordion { + return + } + + if colors, newCycle := cycle.Loop(); colors != nil { + cycle = &newCycle + segment.Foreground = colors.Foreground + segment.Background = colors.Background + } + + if terminal.Len() == 0 && len(block.LeadingDiamond) > 0 { + segment.LeadingDiamond = block.LeadingDiamond + } + + e.setActiveSegment(segment) + e.renderActiveSegment() +} + +// canRenderSegment now uses map for O(1) lookups instead of O(n) slice search +func (e *Engine) canRenderSegment(segment *config.Segment, executed map[string]bool) bool { + for _, name := range segment.Needs { + if !executed[name] { + return false + } + } + + return true +} diff --git a/src/prompt/segments_test.go b/src/prompt/segments_test.go new file mode 100644 index 000000000000..a04bce454312 --- /dev/null +++ b/src/prompt/segments_test.go @@ -0,0 +1,180 @@ +package prompt + +import ( + "testing" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + + "github.com/stretchr/testify/assert" +) + +func TestRenderBlock(t *testing.T) { + engine := New(&runtime.Flags{ + IsPrimary: true, + }) + block := &config.Block{ + Segments: []*config.Segment{ + { + Type: "text", + Template: "Hello", + Foreground: "red", + Background: "blue", + }, + { + Type: "text", + Template: "World", + Foreground: "red", + Background: "blue", + }, + }, + } + + prompt, length := engine.writeBlockSegments(block) + assert.Equal(t, "\x1b[44m\x1b[31mHello\x1b[0m\x1b[44m\x1b[31mWorld\x1b[0m", prompt) + assert.Equal(t, 10, length) +} + +func TestCanRenderSegment(t *testing.T) { + cases := []struct { + Case string + Executed map[string]bool + Needs []string + Expected bool + }{ + { + Case: "No cross segment dependencies", + Expected: true, + }, + { + Case: "Cross segment dependencies, nothing executed", + Expected: false, + Needs: []string{"Foo"}, + }, + { + Case: "Cross segment dependencies, available", + Expected: true, + Executed: map[string]bool{ + "Foo": true, + }, + Needs: []string{"Foo"}, + }, + } + for _, c := range cases { + segment := &config.Segment{ + Type: "text", + Needs: c.Needs, + } + + engine := &Engine{} + got := engine.canRenderSegment(segment, c.Executed) + + assert.Equal(t, c.Expected, got, c.Case) + } +} + +func TestExecuteSegmentWithTimeout_Streaming(t *testing.T) { + // This test verifies that when streaming is enabled and a timeout occurs, + // the segment is marked as pending and tracked for later completion + env := new(mock.Environment) + env.On("Flags").Return(&runtime.Flags{Streaming: true}) + + segment := &config.Segment{ + Type: "text", + Timeout: 1, // Very short timeout to ensure it triggers + } + + engine := &Engine{ + Env: env, + streamingResults: make(chan *config.Segment, 10), + } + + // Create a mock segment that will definitely timeout + // We'll use the actual timeout mechanism by making the execution slow + done := make(chan bool) + go func() { + time.Sleep(100 * time.Millisecond) // Longer than timeout + close(done) + }() + + // Pre-register segment as pending (this happens in writeSegmentsConcurrently) + engine.pendingSegments.Store(segment.Name(), true) + + // Mark as pending and track (simulating what executeSegmentWithTimeout does) + segment.Pending = true + engine.trackPendingSegment(segment, done) + + // Verify it was tracked as pending + _, exists := engine.pendingSegments.Load(segment.Name()) + assert.True(t, exists, "Segment should be tracked as pending") + + // Wait for completion notification + select { + case completed := <-engine.streamingResults: + assert.Equal(t, segment, completed) + assert.False(t, completed.Pending, "Segment should no longer be pending after completion") + case <-time.After(200 * time.Millisecond): + t.Error("Expected segment completion notification") + } +} + +func TestExecuteSegmentWithTimeout_NonStreaming(t *testing.T) { + // This test verifies that when streaming is disabled, + // trackPendingSegment returns early without tracking when streamingResults is nil + segment := &config.Segment{ + Type: "text", + Timeout: 10, + } + + engine := &Engine{ + // streamingResults is nil (non-streaming mode) + } + + done := make(chan bool) + + // Pre-register segment (simulating what happens in concurrent execution) + engine.pendingSegments.Store(segment.Name(), true) + + // trackPendingSegment should not track when streamingResults is nil + engine.trackPendingSegment(segment, done) + + // Signal completion + close(done) + + // Give time for any goroutine to run (shouldn't be one) + time.Sleep(50 * time.Millisecond) + + // Segment should still be in pendingSegments because notifySegmentCompletion + // was never called (trackPendingSegment returns early when streamingResults is nil) + _, exists := engine.pendingSegments.Load(segment.Name()) + assert.True(t, exists, "Segment should remain in pendingSegments when streaming is disabled") +} + +func TestExecuteSegmentWithTimeout_CachedValueFallback(t *testing.T) { + // This test verifies that a pending segment's Text() returns "..." placeholder + env := new(mock.Environment) + env.On("Flags").Return(&runtime.Flags{}) + + segment := &config.Segment{ + Type: "text", + Pending: true, + Template: "actual content", + } + + // Initialize the segment writer + err := segment.MapSegmentWithWriter(env) + assert.NoError(t, err) + + // Render with pending state - should show "..." + segment.Render(0, true) + text := segment.Text() + assert.Equal(t, "...", text, "Pending segment should show ...") + + // After completion, render again with actual content + segment.Pending = false + segment.Render(0, true) + text = segment.Text() + assert.NotEqual(t, "...", text, "Non-pending segment should show actual content") +} diff --git a/src/prompt/status.go b/src/prompt/status.go new file mode 100644 index 000000000000..a5a9f9a81af9 --- /dev/null +++ b/src/prompt/status.go @@ -0,0 +1,6 @@ +package prompt + +func (e *Engine) Status() string { + e.writePrimaryPrompt(false) + return e.string() +} diff --git a/src/prompt/streaming.go b/src/prompt/streaming.go new file mode 100644 index 000000000000..2b3d4d085028 --- /dev/null +++ b/src/prompt/streaming.go @@ -0,0 +1,93 @@ +package prompt + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/config" +) + +// StreamPrimary returns a channel that yields prompt updates as segments complete. +func (e *Engine) StreamPrimary() <-chan string { + // Initialize streaming infrastructure BEFORE launching goroutine + // This ensures the channel exists when segments start timing out + e.streamingResults = make(chan *config.Segment, 100) + e.allBlocks = e.Config.Blocks + + out := make(chan string, 10) + + go func() { + defer close(out) + defer close(e.streamingResults) + + // Render and send initial prompt with pending segments + initialPrompt := e.Primary() + out <- initialPrompt + + if e.countPendingSegments() == 0 { + return + } + + // Listen for segment completions + for range e.streamingResults { + out <- e.renderFromBlocks() + + if e.countPendingSegments() == 0 { + return + } + } + }() + + return out +} + +// countPendingSegments counts how many segments are marked as pending +func (e *Engine) countPendingSegments() int { + count := 0 + e.pendingSegments.Range(func(_, _ any) bool { + count++ + return true + }) + return count +} + +// renderFromBlocks re-renders the complete prompt using stored block data +func (e *Engine) renderFromBlocks() string { + // Reset prompt builder + e.prompt.Reset() + e.currentLineLength = 0 + e.activeSegment = nil + e.previousActiveSegment = nil + e.rprompt = "" + e.rpromptLength = 0 + + return e.primaryInternal(true) +} + +// trackPendingSegment continues execution for a timed-out segment in the background +func (e *Engine) trackPendingSegment(segment *config.Segment, done chan bool) { + if e.streamingResults == nil { + return + } + + // Segment is already pre-registered in pendingSegments map + go func() { + <-done + segment.Pending = false + e.notifySegmentCompletion(segment) + }() +} + +// notifySegmentCompletion sends completed segment to the streaming results channel +func (e *Engine) notifySegmentCompletion(segment *config.Segment) { + if e.streamingResults == nil { + return + } + + if _, ok := e.pendingSegments.LoadAndDelete(segment.Name()); ok { + select { + case e.streamingResults <- segment: + // Successfully notified consumer + default: + // Consumer not ready or already exited + // This can happen if segment completes after consumer finishes + } + } +} diff --git a/src/prompt/streaming_test.go b/src/prompt/streaming_test.go new file mode 100644 index 000000000000..24f55f373453 --- /dev/null +++ b/src/prompt/streaming_test.go @@ -0,0 +1,614 @@ +package prompt + +import ( + "testing" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/maps" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + + "github.com/stretchr/testify/assert" + testifymock "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestStreamPrimary_NoSegments(t *testing.T) { + env := new(mock.Environment) + env.On("Pwd").Return("/test") + env.On("Home").Return("/home") + env.On("Shell").Return(shell.PWSH) + env.On("Flags").Return(&runtime.Flags{Streaming: true}) + env.On("CursorPosition").Return(1, 1) + env.On("StatusCodes").Return(0, "0") + + template.Cache = &cache.Template{ + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + terminal.Init(shell.PWSH) + + engine := &Engine{ + Config: &config.Config{ + Blocks: []*config.Block{}, + }, + Env: env, + } + + out := engine.StreamPrimary() + prompts := collectChannelOutput(out, 100*time.Millisecond) + + // Should get exactly one prompt (initial) with no pending segments + assert.Len(t, prompts, 1) +} + +func TestStreamPrimary_WithPendingSegments(t *testing.T) { + engine := &Engine{ + streamingResults: make(chan *config.Segment, 10), + } + + segment := &config.Segment{ + Type: "text", + Pending: true, + } + + // Track as pending + engine.pendingSegments.Store(segment.Name(), true) + + // Simulate segment completion in background + go func() { + time.Sleep(50 * time.Millisecond) + segment.Pending = false + engine.notifySegmentCompletion(segment) + }() + + // Verify notification is received + select { + case completed := <-engine.streamingResults: + assert.Equal(t, segment, completed) + assert.False(t, completed.Pending) + case <-time.After(200 * time.Millisecond): + t.Error("Expected segment completion notification") + } +} + +func TestCountPendingSegments(t *testing.T) { + cases := []struct { + Case string + Segments []string + Count int + }{ + {Case: "No pending segments", Count: 0, Segments: []string{}}, + {Case: "One pending segment", Count: 1, Segments: []string{"segment1"}}, + {Case: "Multiple pending segments", Count: 3, Segments: []string{"segment1", "segment2", "segment3"}}, + } + + for _, tc := range cases { + engine := &Engine{} + + for _, name := range tc.Segments { + engine.pendingSegments.Store(name, true) + } + + count := engine.countPendingSegments() + assert.Equal(t, tc.Count, count, tc.Case) + } +} + +func TestNotifySegmentCompletion(t *testing.T) { + cases := []struct { + Case string + StreamingSetup bool + SegmentPending bool + ExpectNotify bool + }{ + {Case: "No streaming channel", StreamingSetup: false, SegmentPending: true, ExpectNotify: false}, + {Case: "Segment not pending", StreamingSetup: true, SegmentPending: false, ExpectNotify: false}, + {Case: "Valid notification", StreamingSetup: true, SegmentPending: true, ExpectNotify: true}, + } + + for _, tc := range cases { + engine := &Engine{} + segment := &config.Segment{Type: "test"} + + if tc.StreamingSetup { + engine.streamingResults = make(chan *config.Segment, 10) + } + + if tc.SegmentPending { + engine.pendingSegments.Store(segment.Name(), true) + } + + engine.notifySegmentCompletion(segment) + + if tc.ExpectNotify { + select { + case received := <-engine.streamingResults: + assert.Equal(t, segment, received, tc.Case) + case <-time.After(100 * time.Millisecond): + t.Errorf("%s: Expected notification but got timeout", tc.Case) + } + } else if tc.StreamingSetup { + select { + case <-engine.streamingResults: + t.Errorf("%s: Unexpected notification received", tc.Case) + case <-time.After(50 * time.Millisecond): + // Expected - no notification + } + } + } +} + +func TestTrackPendingSegment(t *testing.T) { + engine := &Engine{ + streamingResults: make(chan *config.Segment, 10), + } + + segment := &config.Segment{ + Type: "test", + Pending: true, + } + + done := make(chan bool) + + // Pre-register segment as pending (this happens in writeSegmentsConcurrently in real code) + engine.pendingSegments.Store(segment.Name(), true) + + // Start tracking + engine.trackPendingSegment(segment, done) + + // Verify segment is tracked + _, ok := engine.pendingSegments.Load(segment.Name()) + assert.True(t, ok, "Segment should be tracked") + + // Simulate completion + close(done) + + // Wait for goroutine to process + select { + case completed := <-engine.streamingResults: + assert.Equal(t, segment, completed) + assert.False(t, segment.Pending, "Segment should no longer be pending") + case <-time.After(100 * time.Millisecond): + t.Error("Expected segment completion notification") + } + + // Verify segment is no longer tracked + _, ok = engine.pendingSegments.Load(segment.Name()) + assert.False(t, ok, "Segment should no longer be tracked") +} + +func TestRenderFromBlocks(_ *testing.T) { + env := new(mock.Environment) + env.On("Shell").Return(shell.PWSH) + env.On("Flags").Return(&runtime.Flags{}) + + // This test validates that renderFromBlocks properly delegates to primaryInternal + engine := &Engine{ + Config: &config.Config{ + Blocks: []*config.Block{}, + }, + Env: env, + allBlocks: []*config.Block{}, + } + + // Just verify it doesn't panic - full integration tested elsewhere + _ = engine.renderFromBlocks() +} + +func TestPrimaryInternal_FromCache(_ *testing.T) { + env := new(mock.Environment) + env.On("Shell").Return(shell.PWSH) + env.On("Flags").Return(&runtime.Flags{}) + + // This test validates the fromCache parameter is handled correctly + engine := &Engine{ + Config: &config.Config{ + Blocks: []*config.Block{}, + }, + Env: env, + allBlocks: []*config.Block{}, + } + + // Just verify it doesn't panic - full integration tested elsewhere + _ = engine.primaryInternal(true) +} + +func TestRenderBlockFromCache(t *testing.T) { + // This test validates renderBlockFromCache handles segments correctly + segment := &config.Segment{ + Type: "text", + Enabled: false, + } + + block := &config.Block{ + Type: config.Prompt, + Alignment: config.Left, + Segments: []*config.Segment{segment}, + } + + engine := &Engine{ + Config: &config.Config{}, + } + + terminal.Init(shell.PWSH) + + // Should not render when segment is disabled and not forced + result := engine.renderBlockFromCache(block, false) + assert.False(t, result, "Block should not render with disabled segment") +} + +func TestSegmentPendingState(t *testing.T) { + env := new(mock.Environment) + env.On("Shell").Return(shell.PWSH) + env.On("Flags").Return(&runtime.Flags{}) + + template.Cache = &cache.Template{ + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + + segment := &config.Segment{ + Type: "text", + Pending: true, + Template: "test template", + } + err := segment.MapSegmentWithWriter(env) + require.NoError(t, err) + + // Render with pending state - should show "..." + segment.Render(0, true) + text := segment.Text() + assert.Equal(t, "...", text, "Pending segment should show ...") + + // After completion + segment.Pending = false + segment.Render(0, true) + text = segment.Text() + assert.NotEqual(t, "...", text, "Non-pending segment should show actual content") +} + +// Helper function to collect all output from a channel with timeout +func collectChannelOutput(ch <-chan string, timeout time.Duration) []string { + var results []string + timer := time.NewTimer(timeout) + defer timer.Stop() + + for { + select { + case result, ok := <-ch: + if !ok { + return results + } + results = append(results, result) + case <-timer.C: + return results + } + } +} + +func TestStreamingWithTimeout(t *testing.T) { + engine := &Engine{ + streamingResults: make(chan *config.Segment, 10), + } + + segment := &config.Segment{ + Type: "test", + Timeout: 10, + } + + // Pre-register segment as pending (this happens in writeSegmentsConcurrently in real code) + engine.pendingSegments.Store(segment.Name(), true) + + // Test that timeout with streaming enabled marks segment as pending + done := make(chan bool) + + go func() { + time.Sleep(50 * time.Millisecond) + close(done) + }() + + engine.trackPendingSegment(segment, done) + + // Verify pending state + _, isPending := engine.pendingSegments.Load(segment.Name()) + require.True(t, isPending, "Segment should be pending") + + // Wait for completion + select { + case <-engine.streamingResults: + // Success + case <-time.After(200 * time.Millisecond): + t.Error("Timeout waiting for segment completion") + } + + // Verify no longer pending + _, isPending = engine.pendingSegments.Load(segment.Name()) + assert.False(t, isPending, "Segment should no longer be pending") +} + +func setupStreamingTestEnv() *mock.Environment { + env := new(mock.Environment) + env.On("Pwd").Return("/test") + env.On("Home").Return("/home") + env.On("Shell").Return(shell.PWSH) + env.On("Flags").Return(&runtime.Flags{Streaming: true}) + env.On("CursorPosition").Return(1, 1) + env.On("StatusCodes").Return(0, "0") + env.On("DirMatchesOneOf", testifymock.Anything, testifymock.Anything).Return(false) + // Mock accent color retrieval for both Windows and macOS + env.On("RunCommand", testifymock.Anything, testifymock.Anything, testifymock.Anything, testifymock.Anything).Return("4", nil) + env.On("WindowsRegistryKeyValue", testifymock.Anything).Return(&runtime.WindowsRegistryValue{ValueType: runtime.DWORD, DWord: 0xFF0078D7}, nil) + + template.Cache = &cache.Template{ + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + terminal.Init(shell.PWSH) + terminal.Colors = color.MakeColors(nil, false, "", env) + + return env +} + +func TestStreamPrimary_FullFlow_WithRendering(t *testing.T) { + env := setupStreamingTestEnv() + + // Create segments with different speeds + fastSegment := &config.Segment{ + Type: "text", + Template: "FAST", + Foreground: "#ffffff", + Background: "#000000", + } + + slowSegment := &config.Segment{ + Type: "text", + Template: "SLOW", + Pending: true, // Initially pending + Foreground: "#ffffff", + Background: "#000000", + } + + engine := &Engine{ + Config: &config.Config{ + Blocks: []*config.Block{ + { + Type: config.Prompt, + Alignment: config.Left, + Segments: []*config.Segment{fastSegment, slowSegment}, + }, + }, + }, + Env: env, + streamingResults: make(chan *config.Segment, 10), + } + + // Map segment writers + err := fastSegment.MapSegmentWithWriter(env) + require.NoError(t, err) + err = slowSegment.MapSegmentWithWriter(env) + require.NoError(t, err) + + // Track slow segment as pending + engine.pendingSegments.Store(slowSegment.Name(), true) + + // Start streaming + out := engine.StreamPrimary() + + // Simulate slow segment completion after delay + go func() { + time.Sleep(50 * time.Millisecond) + slowSegment.Pending = false + engine.notifySegmentCompletion(slowSegment) + }() + + // Collect all prompts + prompts := collectChannelOutput(out, 200*time.Millisecond) + + // Should have at least 2 prompts: initial (with "...") and final (with "SLOW") + assert.GreaterOrEqual(t, len(prompts), 1, "Should have at least initial prompt") + + // First prompt should contain "..." for pending segment + if len(prompts) > 0 { + assert.Contains(t, prompts[0], "...", "Initial prompt should show pending text") + } + + // If we got multiple prompts, last one should not have "..." + if len(prompts) > 1 { + assert.NotContains(t, prompts[len(prompts)-1], "...", "Final prompt should not show pending text") + } +} + +func TestStreamPrimary_MultipleBlocks_MixedSpeed(t *testing.T) { + env := setupStreamingTestEnv() + + // Block 1: Fast segment + fast1 := &config.Segment{ + Type: "text", + Template: "FAST1", + } + + // Block 2: Slow segment + slow1 := &config.Segment{ + Type: "text", + Template: "SLOW1", + Pending: true, + } + + // Block 3: Another fast segment + fast2 := &config.Segment{ + Type: "text", + Template: "FAST2", + } + + engine := &Engine{ + Config: &config.Config{ + Blocks: []*config.Block{ + {Type: config.Prompt, Alignment: config.Left, Segments: []*config.Segment{fast1}}, + {Type: config.Prompt, Alignment: config.Left, Segments: []*config.Segment{slow1}}, + {Type: config.Prompt, Alignment: config.Left, Segments: []*config.Segment{fast2}}, + }, + }, + Env: env, + streamingResults: make(chan *config.Segment, 10), + } + + // Map segments + require.NoError(t, fast1.MapSegmentWithWriter(env)) + require.NoError(t, slow1.MapSegmentWithWriter(env)) + require.NoError(t, fast2.MapSegmentWithWriter(env)) + + // Track slow segment + engine.pendingSegments.Store(slow1.Name(), true) + + // Start streaming + out := engine.StreamPrimary() + + // Simulate completion + go func() { + time.Sleep(50 * time.Millisecond) + slow1.Pending = false + engine.notifySegmentCompletion(slow1) + }() + + prompts := collectChannelOutput(out, 200*time.Millisecond) + + // Should receive prompts + assert.NotEmpty(t, prompts, "Should receive streaming prompts") +} + +func setupBasicStreamingTestEnv() *Engine { + env := new(mock.Environment) + env.On("Pwd").Return("/test") + env.On("Home").Return("/home") + env.On("Shell").Return(shell.PWSH) + env.On("Flags").Return(&runtime.Flags{Streaming: true}) + env.On("CursorPosition").Return(1, 1) + env.On("StatusCodes").Return(0, "0") + + template.Cache = &cache.Template{ + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + terminal.Init(shell.PWSH) + + engine := &Engine{ + Config: &config.Config{ + Blocks: []*config.Block{}, + }, + Env: env, + } + + return engine +} + +func TestStreamPrimary_EarlyChannelClosure(t *testing.T) { + engine := setupBasicStreamingTestEnv() + + // Start streaming with no pending segments + // The goroutine should complete quickly and close channels properly + out := engine.StreamPrimary() + + // Should be able to read from output channel without panic + prompts := collectChannelOutput(out, 100*time.Millisecond) + + // Should get exactly one prompt (initial) with no pending segments + assert.Len(t, prompts, 1, "Should receive initial prompt") +} + +func TestStreamPrimary_NoStreamingResults_Channel(t *testing.T) { + engine := setupBasicStreamingTestEnv() + + // Engine without streamingResults channel (edge case) + // No streamingResults channel set + + // Should not panic + out := engine.StreamPrimary() + prompts := collectChannelOutput(out, 100*time.Millisecond) + + assert.Len(t, prompts, 1, "Should get exactly one prompt with no pending segments") +} + +// TestStreamPrimary_RaceConditionFix validates that the streaming loop +// correctly handles segments that complete after Primary() but before/during +// the counting phase. This tests the fix for the race where pendingCount +// could get out of sync with actual pending segments. +func TestStreamPrimary_RaceConditionFix(t *testing.T) { + env := new(mock.Environment) + env.On("Pwd").Return("/test") + env.On("Home").Return("/home") + env.On("Shell").Return(shell.PWSH) + env.On("Flags").Return(&runtime.Flags{Streaming: true}) + env.On("CursorPosition").Return(1, 1) + env.On("StatusCodes").Return(0, "0") + + template.Cache = &cache.Template{ + Segments: maps.NewConcurrent[any](), + } + template.Init(env, nil, nil) + terminal.Init(shell.PWSH) + + engine := &Engine{ + Config: &config.Config{ + Blocks: []*config.Block{}, + }, + Env: env, + streamingResults: make(chan *config.Segment, 10), + } + + // Create three segments, simulating the race scenario: + // - segmentA: Completes quickly after Primary() + // - segmentB: Completes during loop + // - segmentC: Completes last + segmentA := &config.Segment{Type: "test-a", Pending: true} + segmentB := &config.Segment{Type: "test-b", Pending: true} + segmentC := &config.Segment{Type: "test-c", Pending: true} + + // Pre-register all three as pending (simulates timeout during Primary()) + engine.pendingSegments.Store(segmentA.Name(), true) + engine.pendingSegments.Store(segmentB.Name(), true) + engine.pendingSegments.Store(segmentC.Name(), true) + + // Simulate segmentA completing immediately after Primary() but before countPendingSegments() + // This is the race condition - notification sent but segment removed from map + go func() { + // Small delay to ensure StreamPrimary has been called but before counting + time.Sleep(5 * time.Millisecond) + segmentA.Pending = false + engine.notifySegmentCompletion(segmentA) + }() + + // Simulate segmentB and segmentC completing during the loop + go func() { + time.Sleep(30 * time.Millisecond) + segmentB.Pending = false + engine.notifySegmentCompletion(segmentB) + }() + + go func() { + time.Sleep(50 * time.Millisecond) + segmentC.Pending = false + engine.notifySegmentCompletion(segmentC) + }() + + // Start streaming + out := engine.StreamPrimary() + + // Collect all prompts with sufficient timeout + prompts := collectChannelOutput(out, 200*time.Millisecond) + + // With the fix, we should receive updates for all three segments + // Initial prompt + 3 updates (A, B, C) = 4 total + // Without the fix, we might only get Initial + 2 updates and exit early + assert.GreaterOrEqual(t, len(prompts), 3, "Should receive updates for all pending segments") + + // Verify all segments were properly cleaned up + count := engine.countPendingSegments() + assert.Equal(t, 0, count, "All pending segments should be cleared") +} diff --git a/src/prompt/tooltip.go b/src/prompt/tooltip.go new file mode 100644 index 000000000000..2d3df98aaf48 --- /dev/null +++ b/src/prompt/tooltip.go @@ -0,0 +1,126 @@ +package prompt + +import ( + "slices" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +func (e *Engine) tooltipFallback() string { + rprompt, OK := cache.Get[string](cache.Session, RPromptKey) + if !OK { + return "" + } + + rpromptLength, OK := cache.Get[int](cache.Session, RPromptLengthKey) + if !OK { + return rprompt + } + + switch e.Env.Shell() { + case shell.PWSH: + e.rprompt = rprompt + e.currentLineLength = e.Env.Flags().Column + + space, ok := e.canWriteRightBlock(rpromptLength, true) + if !ok { + return "" + } + + e.write(terminal.SaveCursorPosition()) + e.write(strings.Repeat(" ", space)) + e.write(rprompt) + e.write(terminal.RestoreCursorPosition()) + return e.string() + default: + return rprompt + } +} + +func (e *Engine) Tooltip(tip string) string { + tip = strings.Trim(tip, " ") + tooltips := make([]*config.Segment, 0, 1) + + for _, tooltip := range e.Config.Tooltips { + if !slices.Contains(tooltip.Tips, tip) { + continue + } + + tooltip.Execute(e.Env) + + if !tooltip.Enabled { + continue + } + + tooltips = append(tooltips, tooltip) + } + + if len(tooltips) == 0 { + return e.tooltipFallback() + } + + // little hack to reuse the current logic + block := &config.Block{ + Alignment: config.Right, + Segments: tooltips, + } + + text, length := e.writeBlockSegments(block) + + // do not print anything when we don't have any text + if length == 0 { + return "" + } + + text, length = e.handleToolTipAction(text, length) + + switch e.Env.Shell() { + case shell.PWSH: + e.rprompt = text + e.currentLineLength = e.Env.Flags().Column + + space, ok := e.canWriteRightBlock(length, true) + if !ok { + return "" + } + + e.write(terminal.SaveCursorPosition()) + e.write(strings.Repeat(" ", space)) + e.write(text) + e.write(terminal.RestoreCursorPosition()) + return e.string() + default: + return text + } +} + +func (e *Engine) handleToolTipAction(text string, length int) (string, int) { + if e.Config.ToolTipsAction.IsDefault() { + return text, length + } + + rprompt, OK := cache.Get[string](cache.Session, RPromptKey) + if !OK { + return text, length + } + + rpromptLength, OK := cache.Get[int](cache.Session, RPromptLengthKey) + if !OK { + return text, length + } + + length += rpromptLength + + switch e.Config.ToolTipsAction { + case config.Extend: + text = rprompt + text + case config.Prepend: + text += rprompt + } + + return text, length +} diff --git a/src/prompt/tooltip_test.go b/src/prompt/tooltip_test.go new file mode 100644 index 000000000000..f8ba54efdb3b --- /dev/null +++ b/src/prompt/tooltip_test.go @@ -0,0 +1,99 @@ +package prompt + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/config" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + + "github.com/stretchr/testify/assert" +) + +func TestTooltipFallback_NoCacheReturnsEmpty(t *testing.T) { + cache.Delete(cache.Session, RPromptKey) + cache.Delete(cache.Session, RPromptLengthKey) + + env := new(mock.Environment) + env.On("Shell").Return(shell.ZSH) + + terminal.Init(shell.ZSH) + + engine := &Engine{ + Env: env, + Config: &config.Config{}, + } + + got := engine.Tooltip("unknown-command") + assert.Empty(t, got) +} + +func TestTooltipFallback_NoMatchReturnsRPromptText(t *testing.T) { + cache.Delete(cache.Session, RPromptKey) + cache.Delete(cache.Session, RPromptLengthKey) + cache.Set(cache.Session, RPromptKey, "my-rprompt", cache.INFINITE) + cache.Set(cache.Session, RPromptLengthKey, 10, cache.INFINITE) + + env := new(mock.Environment) + env.On("Shell").Return(shell.ZSH) + + terminal.Init(shell.ZSH) + + engine := &Engine{ + Env: env, + Config: &config.Config{}, + } + + got := engine.Tooltip("unknown-command") + assert.Equal(t, "my-rprompt", got) +} + +func TestTooltipFallback_PwshNoMatchReturnsCursorPositionedRPrompt(t *testing.T) { + cache.Delete(cache.Session, RPromptKey) + cache.Delete(cache.Session, RPromptLengthKey) + cache.Set(cache.Session, RPromptKey, "my-rprompt", cache.INFINITE) + cache.Set(cache.Session, RPromptLengthKey, 10, cache.INFINITE) + + env := new(mock.Environment) + env.On("Shell").Return(shell.PWSH) + env.On("Flags").Return(&runtime.Flags{Column: 5}) + env.On("TerminalWidth").Return(200, nil) + + terminal.Init(shell.PWSH) + + engine := &Engine{ + Env: env, + Config: &config.Config{}, + } + + got := engine.Tooltip("unknown-command") + assert.Contains(t, got, "my-rprompt") + assert.Contains(t, got, terminal.SaveCursorPosition()) + assert.Contains(t, got, terminal.RestoreCursorPosition()) +} + +func TestTooltipFallback_PwshNoRoomReturnsEmpty(t *testing.T) { + cache.Delete(cache.Session, RPromptKey) + cache.Delete(cache.Session, RPromptLengthKey) + cache.Set(cache.Session, RPromptKey, "my-rprompt", cache.INFINITE) + // rprompt length exceeds available terminal space + cache.Set(cache.Session, RPromptLengthKey, 200, cache.INFINITE) + + env := new(mock.Environment) + env.On("Shell").Return(shell.PWSH) + env.On("Flags").Return(&runtime.Flags{Column: 100}) + env.On("TerminalWidth").Return(200, nil) + + terminal.Init(shell.PWSH) + + engine := &Engine{ + Env: env, + Config: &config.Config{}, + } + + got := engine.Tooltip("unknown-command") + assert.Empty(t, got) +} diff --git a/src/properties.go b/src/properties.go deleted file mode 100644 index 03aaf7c1eaa3..000000000000 --- a/src/properties.go +++ /dev/null @@ -1,186 +0,0 @@ -package main - -import ( - "fmt" -) - -// Property defines one property of a segment for context -type Property string - -// general Properties used across Segments -const ( - // Style indicates with style to use - Style Property = "style" - // Prefix adds a text prefix to the segment - Prefix Property = "prefix" - // Postfix adds a text postfix to the segment - Postfix Property = "postfix" - // ColorBackground color the background or foreground when a specific color is set - ColorBackground Property = "color_background" - // IncludeFolders folders to be included for the segment logic - IncludeFolders Property = "include_folders" - // ExcludeFolders folders to be excluded for the segment logic - ExcludeFolders Property = "exclude_folders" - // IgnoreFolders duplicate of ExcludeFolders - IgnoreFolders Property = "ignore_folders" - // DisplayVersion show the version number or not - DisplayVersion Property = "display_version" - // AlwaysEnabled decides whether or not to always display the info - AlwaysEnabled Property = "always_enabled" - // SegmentTemplate is the template to use to render the information - SegmentTemplate Property = "template" - // DisplayError to display when an error occurs or not - DisplayError Property = "display_error" - // DisplayDefault hides or shows the default - DisplayDefault Property = "display_default" -) - -type properties struct { - values map[Property]interface{} - foreground string - background string -} - -func (p *properties) getString(property Property, defaultValue string) string { - if p == nil || p.values == nil { - return defaultValue - } - val, found := p.values[property] - if !found { - return defaultValue - } - return parseString(val, defaultValue) -} - -func parseString(value interface{}, defaultValue string) string { - stringValue, ok := value.(string) - if !ok { - return defaultValue - } - return stringValue -} - -func (p *properties) getColor(property Property, defaultValue string) string { - if p == nil || p.values == nil { - return defaultValue - } - val, found := p.values[property] - if !found { - return defaultValue - } - colorString := parseString(val, defaultValue) - _, err := getColorFromName(colorString, false) - if err == nil { - return colorString - } - values := findNamedRegexMatch(`(?P#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})`, colorString) - if values != nil && values["color"] != "" { - return values["color"] - } - return defaultValue -} - -func (p *properties) getBool(property Property, defaultValue bool) bool { - if p == nil || p.values == nil { - return defaultValue - } - val, found := p.values[property] - if !found { - return defaultValue - } - boolValue, ok := val.(bool) - if !ok { - return defaultValue - } - return boolValue -} - -func (p *properties) getFloat64(property Property, defaultValue float64) float64 { - if p == nil || p.values == nil { - return defaultValue - } - val, found := p.values[property] - if !found { - return defaultValue - } - - floatValue, ok := val.(float64) - if !ok { - return defaultValue - } - - return floatValue -} - -func (p *properties) getInt(property Property, defaultValue int) int { - if p == nil || p.values == nil { - return defaultValue - } - val, found := p.values[property] - if !found { - return defaultValue - } - - intValue, ok := val.(int) - if !ok { - return defaultValue - } - - return intValue -} - -func (p *properties) getKeyValueMap(property Property, defaultValue map[string]string) map[string]string { - if p == nil || p.values == nil { - return defaultValue - } - val, found := p.values[property] - if !found { - return defaultValue - } - - keyValues := parseKeyValueArray(val) - - return keyValues -} - -func parseStringArray(param interface{}) []string { - switch v := param.(type) { - default: - return []string{} - case []interface{}: - list := make([]string, len(v)) - for i, v := range v { - list[i] = fmt.Sprint(v) - } - return list - case []string: - return v - } -} - -func parseKeyValueArray(param interface{}) map[string]string { - switch v := param.(type) { - default: - return map[string]string{} - case map[string]interface{}: - keyValueArray := make(map[string]string) - for key, value := range v { - val := value.(string) - keyValueArray[key] = val - } - return keyValueArray - case []interface{}: - keyValueArray := make(map[string]string) - for _, s := range v { - l := parseStringArray(s) - if len(l) == 2 { - key := l[0] - val := l[1] - keyValueArray[key] = val - } - } - return keyValueArray - case map[string]string: - return v - } -} diff --git a/src/properties_test.go b/src/properties_test.go deleted file mode 100644 index a39d63263d5a..000000000000 --- a/src/properties_test.go +++ /dev/null @@ -1,147 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -const ( - expected = "expected" - expectedColor = "#768954" -) - -func TestGetString(t *testing.T) { - values := map[Property]interface{}{TextProperty: expected} - properties := properties{ - values: values, - } - value := properties.getString(TextProperty, "err") - assert.Equal(t, expected, value) -} - -func TestGetStringNoEntry(t *testing.T) { - values := map[Property]interface{}{} - properties := properties{ - values: values, - } - value := properties.getString(TextProperty, expected) - assert.Equal(t, expected, value) -} - -func TestGetStringNoTextEntry(t *testing.T) { - values := map[Property]interface{}{TextProperty: true} - properties := properties{ - values: values, - } - value := properties.getString(TextProperty, expected) - assert.Equal(t, expected, value) -} - -func TestGetHexColor(t *testing.T) { - expected := expectedColor - values := map[Property]interface{}{UserColor: expected} - properties := properties{ - values: values, - } - value := properties.getColor(UserColor, "#789123") - assert.Equal(t, expected, value) -} - -func TestGetColor(t *testing.T) { - expected := "yellow" - values := map[Property]interface{}{UserColor: expected} - properties := properties{ - values: values, - } - value := properties.getColor(UserColor, "#789123") - assert.Equal(t, expected, value) -} - -func TestDefaultColorWithInvalidColorCode(t *testing.T) { - expected := expectedColor - values := map[Property]interface{}{UserColor: "invalid"} - properties := properties{ - values: values, - } - value := properties.getColor(UserColor, expected) - assert.Equal(t, expected, value) -} - -func TestDefaultColorWithUnavailableProperty(t *testing.T) { - expected := expectedColor - values := map[Property]interface{}{} - properties := properties{ - values: values, - } - value := properties.getColor(UserColor, expected) - assert.Equal(t, expected, value) -} - -func TestGetBool(t *testing.T) { - expected := true - values := map[Property]interface{}{DisplayHost: expected} - properties := properties{ - values: values, - } - value := properties.getBool(DisplayHost, false) - assert.True(t, value) -} - -func TestGetBoolPropertyNotInMap(t *testing.T) { - values := map[Property]interface{}{} - properties := properties{ - values: values, - } - value := properties.getBool(DisplayHost, false) - assert.False(t, value) -} - -func TestGetBoolInvalidProperty(t *testing.T) { - values := map[Property]interface{}{DisplayHost: "borked"} - properties := properties{ - values: values, - } - value := properties.getBool(DisplayHost, false) - assert.False(t, value) -} - -func TestGetFloat64(t *testing.T) { - expected := float64(1337) - values := map[Property]interface{}{"myfloat": expected} - properties := properties{ - values: values, - } - value := properties.getFloat64("myfloat", 9001) - assert.Equal(t, expected, value) -} - -func TestGetFloat64PropertyNotInMap(t *testing.T) { - expected := float64(1337) - values := map[Property]interface{}{} - properties := properties{ - values: values, - } - value := properties.getFloat64(ThresholdProperty, expected) - assert.Equal(t, expected, value) -} - -func TestGetFloat64InvalidStringProperty(t *testing.T) { - expected := float64(1337) - values := map[Property]interface{}{ThresholdProperty: "invalid"} - properties := properties{ - values: values, - } - value := properties.getFloat64(ThresholdProperty, expected) - assert.Equal(t, expected, value) -} - -func TestGetFloat64InvalidBoolProperty(t *testing.T) { - expected := float64(1337) - values := map[Property]interface{}{ThresholdProperty: true} - properties := properties{ - values: values, - } - value := properties.getFloat64(ThresholdProperty, expected) - assert.Equal(t, expected, value) -} diff --git a/src/regex.go b/src/regex.go deleted file mode 100644 index b151d4d44992..000000000000 --- a/src/regex.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "regexp" - "sync" -) - -var ( - regexCache = make(map[string]*regexp.Regexp) - regexCacheLock = sync.RWMutex{} -) - -func getCompiledRegex(pattern string) *regexp.Regexp { - // try in cache first - regexCacheLock.RLock() - re := regexCache[pattern] - regexCacheLock.RUnlock() - if re != nil { - return re - } - - // should we panic or return the error? - re = regexp.MustCompile(pattern) - - // lock for concurrent access and save the compiled expression in cache - regexCacheLock.Lock() - regexCache[pattern] = re - regexCacheLock.Unlock() - - return re -} - -func findNamedRegexMatch(pattern, text string) map[string]string { - // error ignored because mustCompile will cause a panic - re := getCompiledRegex(pattern) - match := re.FindStringSubmatch(text) - result := make(map[string]string) - if len(match) == 0 { - return result - } - for i, name := range re.SubexpNames() { - if i == 0 { - continue - } - result[name] = match[i] - } - return result -} - -func findAllNamedRegexMatch(pattern, text string) []map[string]string { - re := getCompiledRegex(pattern) - match := re.FindAllStringSubmatch(text, -1) - var results []map[string]string - if len(match) == 0 { - return results - } - for _, set := range match { - result := make(map[string]string) - for i, name := range re.SubexpNames() { - if i == 0 { - result["text"] = set[i] - continue - } - result[name] = set[i] - } - results = append(results, result) - } - return results -} - -func replaceAllString(pattern, text, replaceText string) string { - re := getCompiledRegex(pattern) - return re.ReplaceAllString(text, replaceText) -} - -func matchString(pattern, text string) bool { - re := getCompiledRegex(pattern) - return re.MatchString(text) -} diff --git a/src/regex/regex.go b/src/regex/regex.go new file mode 100644 index 000000000000..5c43a9b1c6df --- /dev/null +++ b/src/regex/regex.go @@ -0,0 +1,130 @@ +package regex + +import ( + "regexp" + "sync" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +var ( + regexCache = make(map[string]*regexp.Regexp) + regexCacheLock = sync.RWMutex{} +) + +const ( + LINK = `(?P\x1b]8;;(.+)\x1b\\(?P.+)\x1b]8;;\x1b\\)` +) + +func GetCompiledRegex(pattern string) (*regexp.Regexp, error) { + // try in cache first + regexCacheLock.RLock() + re := regexCache[pattern] + regexCacheLock.RUnlock() + if re != nil { + return re, nil + } + + // should we panic or return the error? + re, err := regexp.Compile(pattern) + if err != nil { + log.Error(err) + return nil, err + } + + // lock for concurrent access and save the compiled expression in cache + regexCacheLock.Lock() + regexCache[pattern] = re + regexCacheLock.Unlock() + + return re, nil +} + +func FindNamedRegexMatch(pattern, text string) map[string]string { + result := make(map[string]string) + + re, err := GetCompiledRegex(pattern) + if err != nil { + return result + } + + match := re.FindStringSubmatch(text) + if len(match) == 0 { + return result + } + + for i, name := range re.SubexpNames() { + if i == 0 { + continue + } + result[name] = match[i] + } + + return result +} + +func FindAllNamedRegexMatch(pattern, text string) []map[string]string { + var results []map[string]string + + re, err := GetCompiledRegex(pattern) + if err != nil { + return results + } + + match := re.FindAllStringSubmatch(text, -1) + + if len(match) == 0 { + return results + } + + for _, set := range match { + result := make(map[string]string) + for i, name := range re.SubexpNames() { + if i == 0 { + result["text"] = set[i] + continue + } + result[name] = set[i] + } + results = append(results, result) + } + + return results +} + +func ReplaceAllString(pattern, text, replaceText string) string { + re, err := GetCompiledRegex(pattern) + if err != nil { + return text + } + + return re.ReplaceAllString(text, replaceText) +} + +func MatchString(pattern, text string) bool { + re, err := GetCompiledRegex(pattern) + if err != nil { + return false + } + + return re.MatchString(text) +} + +func FindStringMatch(pattern, text string, index int) (string, bool) { + re, err := GetCompiledRegex(pattern) + if err != nil { + return text, false + } + + matches := re.FindStringSubmatch(text) + if len(matches) <= index { + return text, false + } + + match := matches[index] + if len(match) == 0 { + return text, false + } + + return match, true +} diff --git a/src/regex/regex_test.go b/src/regex/regex_test.go new file mode 100644 index 000000000000..e49b42c88cfd --- /dev/null +++ b/src/regex/regex_test.go @@ -0,0 +1,72 @@ +package regex + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFindStringMatch(t *testing.T) { + cases := []struct { + Case string + Pattern string + Text string + Expected string + Index int + }{ + { + Case: "Full match at index 0", + Pattern: `\w+`, + Text: "hello", + Index: 0, + Expected: "hello", + }, + { + Case: "Capture group at index 1", + Pattern: `hello (\w+)`, + Text: "hello world", + Index: 1, + Expected: "world", + }, + { + Case: "No matches returns original text", + Pattern: `\d+`, + Text: "hello", + Index: 0, + Expected: "hello", + }, + { + Case: "Invalid pattern returns original text", + Pattern: `[invalid`, + Text: "hello", + Index: 0, + Expected: "hello", + }, + { + Case: "Empty text returns empty string", + Pattern: `\w+`, + Text: "", + Index: 0, + Expected: "", + }, + { + Case: "Index out of bounds returns original text", + Pattern: `(\w+)`, + Text: "hello", + Index: 2, + Expected: "hello", + }, + { + Case: "Multiple capture groups", + Pattern: `(\w+)\s(\w+)`, + Text: "hello world", + Index: 2, + Expected: "world", + }, + } + + for _, tc := range cases { + got, _ := FindStringMatch(tc.Pattern, tc.Text, tc.Index) + assert.Equal(t, tc.Expected, got, tc.Case) + } +} diff --git a/src/runtime/battery/battery.go b/src/runtime/battery/battery.go new file mode 100644 index 000000000000..9396a6f5ef96 --- /dev/null +++ b/src/runtime/battery/battery.go @@ -0,0 +1,60 @@ +// Copyright (C) 2016-2017 Karol 'Kenji Takahashi' Woźniak +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package battery + +type Info struct { + Percentage int + State State +} + +type NoBatteryError struct{} + +func (m *NoBatteryError) Error() string { + return "no battery" +} + +// State type enumerates possible battery states. +type State int + +var states = [...]string{ + Unknown: "Unknown", + Empty: "Empty", + Full: "Full", + Charging: "Charging", + Discharging: "Discharging", + NotCharging: "Not Charging", +} + +func (s State) String() string { + return states[s] +} + +// Possible state values. +// Unknown can mean either controller returned unknown, or +// not able to retrieve state due to some error. +const ( + Unknown State = iota + Empty + Full + Charging + Discharging + NotCharging +) diff --git a/src/runtime/battery/battery_darwin.go b/src/runtime/battery/battery_darwin.go new file mode 100644 index 000000000000..083108c300ce --- /dev/null +++ b/src/runtime/battery/battery_darwin.go @@ -0,0 +1,67 @@ +package battery + +import ( + "errors" + "strconv" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/cmd" +) + +func mapMostLogicalState(state string) State { + switch state { + case "charging": + return Charging + case "discharging": + return Discharging + case "AC attached": + return NotCharging + case "full": + return Full + case "empty": + return Empty + case "charged": + return Full + default: + return Unknown + } +} + +func parseBatteryOutput(output string) (*Info, error) { + matches := regex.FindNamedRegexMatch(`(?P[0-9]{1,3})%; (?P[a-zA-Z\s]+);`, output) + if len(matches) != 2 { + return nil, errors.New("unable to find battery state based on output") + } + + var percentage int + var err error + if percentage, err = strconv.Atoi(matches["PERCENTAGE"]); err != nil { + return nil, errors.New("unable to parse battery percentage") + } + + // sometimes it reports discharging when at 100, so let's force it to Full + // https://github.com/JanDeDobbeleer/oh-my-posh/issues/3729 + if percentage == 100 { + return &Info{ + Percentage: percentage, + State: Full, + }, nil + } + + return &Info{ + Percentage: percentage, + State: mapMostLogicalState(matches["STATE"]), + }, nil +} + +func Get() (*Info, error) { + output, err := cmd.Run("pmset", "-g", "batt") + if err != nil { + return nil, err + } + if !strings.Contains(output, "Battery") { + return nil, ErrNotFound + } + return parseBatteryOutput(output) +} diff --git a/src/runtime/battery/battery_darwin_test.go b/src/runtime/battery/battery_darwin_test.go new file mode 100644 index 000000000000..2540c90ffb7a --- /dev/null +++ b/src/runtime/battery/battery_darwin_test.go @@ -0,0 +1,57 @@ +package battery + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseBatteryOutput(t *testing.T) { + cases := []struct { + Case string + Output string + ExpectedState State + ExpectedPercentage int + ExpectError bool + }{ + { + Case: "charging", + Output: "99%; charging;", + ExpectedState: Charging, + ExpectedPercentage: 99, + }, + { + Case: "charging 1%", + Output: "1%; charging;", + ExpectedState: Charging, + ExpectedPercentage: 1, + }, + { + Case: "not charging 80%", + Output: "81%; AC attached;", + ExpectedState: NotCharging, + ExpectedPercentage: 81, + }, + { + Case: "charged", + Output: "100%; charged;", + ExpectedState: Full, + ExpectedPercentage: 100, + }, + { + Case: "discharging, but not", + Output: "100%; discharging;", + ExpectedState: Full, + ExpectedPercentage: 100, + }, + } + for _, tc := range cases { + info, err := parseBatteryOutput(tc.Output) + if tc.ExpectError { + assert.Error(t, err, tc.Case) + return + } + assert.Equal(t, tc.ExpectedState, info.State, tc.Case) + assert.Equal(t, tc.ExpectedPercentage, info.Percentage, tc.Case) + } +} diff --git a/src/runtime/battery/battery_linux.go b/src/runtime/battery/battery_linux.go new file mode 100644 index 000000000000..295d6743d349 --- /dev/null +++ b/src/runtime/battery/battery_linux.go @@ -0,0 +1,160 @@ +// battery +// Copyright (C) 2016-2017 Karol 'Kenji Takahashi' Woźniak +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package battery + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" +) + +const sysfs = "/sys/class/power_supply" + +func newState(name string) (State, error) { + for i, state := range states { + if strings.EqualFold(name, state) { + return State(i), nil + } + } + + return Unknown, fmt.Errorf("invalid state `%s`", name) +} + +func readFloat(path, filename string) (float64, error) { + str, err := os.ReadFile(filepath.Join(path, filename)) + if err != nil { + return 0, err + } + + if len(str) == 0 { + return 0, ErrNotFound + } + + num, err := strconv.ParseFloat(string(str[:len(str)-1]), 64) + if err != nil { + return 0, err + } + + return num / 1000, nil // Convert micro->milli +} + +func readAmp(path, filename string, volts float64) (float64, error) { + val, err := readFloat(path, filename) + if err != nil { + return 0, err + } + + return val * volts, nil +} + +func isBattery(path string) bool { + t, err := os.ReadFile(filepath.Join(path, "type")) + return err == nil && string(t) == "Battery\n" +} + +func getBatteryFiles() ([]string, error) { + files, err := os.ReadDir(sysfs) + if err != nil { + return nil, err + } + + var bFiles []string + for _, file := range files { + path := filepath.Join(sysfs, file.Name()) + if isBattery(path) { + bFiles = append(bFiles, path) + } + } + + if len(bFiles) == 0 { + return nil, &NoBatteryError{} + } + + return bFiles, nil +} + +func getByPath(path string) (*battery, error) { + b := &battery{} + var err error + + if b.Current, err = readFloat(path, "energy_now"); err == nil { + if b.Full, err = readFloat(path, "energy_full"); err != nil { + return nil, errors.New("unable to parse energy_full") + } + } else { + currentDoesNotExist := os.IsNotExist(err) + if b.Voltage, err = readFloat(path, "voltage_now"); err != nil { + return nil, errors.New("unable to parse voltage_now") + } + b.Voltage /= 1000 + if currentDoesNotExist { + if b.Current, err = readAmp(path, "charge_now", b.Voltage); err != nil { + return nil, errors.New("unable to parse charge_now") + } + if b.Full, err = readAmp(path, "charge_full", b.Voltage); err != nil { + return nil, errors.New("unable to parse charge_full") + } + } else { + if b.Full, err = readFloat(path, "energy_full"); err != nil { + return nil, errors.New("unable to parse energy_full") + } + } + } + + state, err := os.ReadFile(filepath.Join(path, "status")) + if err != nil || len(state) == 0 { + return nil, errors.New("unable to parse or invalid status") + } + if b.State, err = newState(string(state[:len(state)-1])); err != nil { + return nil, errors.New("unable to map to new state") + } + + return b, nil +} + +func systemGetAll() ([]*battery, error) { + bFiles, err := getBatteryFiles() + if err != nil { + return nil, err + } + + var batteries []*battery + var errs Errors + + for _, bFile := range bFiles { + b, err := getByPath(bFile) + if err != nil { + errs = append(errs, err) + continue + } + batteries = append(batteries, b) + } + + if len(batteries) == 0 { + return nil, errs + } + + return batteries, nil +} diff --git a/src/runtime/battery/battery_netbsd.go b/src/runtime/battery/battery_netbsd.go new file mode 100644 index 000000000000..0fb776ec3682 --- /dev/null +++ b/src/runtime/battery/battery_netbsd.go @@ -0,0 +1,24 @@ +package battery + +import ( + "errors" + "strconv" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/cmd" +) + +func Get() (*Info, error) { + output, err := cmd.Run("envstat", "-s", "acpibat0:charge", "-n") + if err != nil { + return nil, err + } + percentage, err := strconv.Atoi(strings.TrimSpace(output)) + if err != nil { + return nil, errors.New("unable to parse battery percentage") + } + return &Info{ + Percentage: percentage, + State: Unknown, + }, nil +} diff --git a/src/runtime/battery/battery_openandfreebsd.go b/src/runtime/battery/battery_openandfreebsd.go new file mode 100644 index 000000000000..42bb43aeb996 --- /dev/null +++ b/src/runtime/battery/battery_openandfreebsd.go @@ -0,0 +1,59 @@ +//go:build openbsd || freebsd + +package battery + +import ( + "errors" + "strconv" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/cmd" +) + +// See https://man.openbsd.org/man8/apm.8 +func mapMostLogicalState(state string) State { + switch state { + case "3": + return Charging + case "0", "1": + return Discharging + case "2": + return Empty + default: + return Unknown + } +} + +func parseBatteryOutput(apm_percentage string, apm_status string) (*Info, error) { + percentage, err := strconv.Atoi(strings.TrimSpace(apm_percentage)) + if err != nil { + return nil, errors.New("unable to parse battery percentage") + } + + if percentage == 100 { + return &Info{ + Percentage: percentage, + State: Full, + }, nil + } + + return &Info{ + Percentage: percentage, + State: mapMostLogicalState(apm_status), + }, nil +} + +func Get() (*Info, error) { + apm_percentage, err := cmd.Run("apm", "-l") + if err != nil { + return nil, err + } + + apm_status, err := cmd.Run("apm", "-b") + if err != nil { + return nil, err + } + + return parseBatteryOutput(apm_percentage, apm_status) + +} diff --git a/src/runtime/battery/battery_openandfreebsd_test.go b/src/runtime/battery/battery_openandfreebsd_test.go new file mode 100644 index 000000000000..da102e5d8654 --- /dev/null +++ b/src/runtime/battery/battery_openandfreebsd_test.go @@ -0,0 +1,65 @@ +//go:build openbsd || freebsd + +package battery + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseBatteryOutput(t *testing.T) { + cases := []struct { + Case string + PercentOutput string + StatusOutput string + ExpectedState State + ExpectedPercentage int + ExpectError bool + }{ + { + Case: "charging", + PercentOutput: "99", + StatusOutput: "3", + ExpectedState: Charging, + ExpectedPercentage: 99, + }, + { + Case: "charging 1%", + PercentOutput: "1", + StatusOutput: "3", + ExpectedState: Charging, + ExpectedPercentage: 1, + }, + { + Case: "removed", + PercentOutput: "0", + StatusOutput: "4", + ExpectedState: Unknown, + ExpectedPercentage: 0, + }, + { + Case: "charged", + PercentOutput: "100", + StatusOutput: "0", + ExpectedState: Full, + ExpectedPercentage: 100, + }, + { + Case: "discharging", + PercentOutput: "25", + StatusOutput: "1", + ExpectedState: Discharging, + ExpectedPercentage: 25, + }, + } + for _, tc := range cases { + info, err := parseBatteryOutput(tc.PercentOutput, tc.StatusOutput) + if tc.ExpectError { + assert.Error(t, err, tc.Case) + return + } + assert.Equal(t, tc.ExpectedState, info.State, tc.Case) + assert.Equal(t, tc.ExpectedPercentage, info.Percentage, tc.Case) + } +} diff --git a/src/runtime/battery/battery_windows.go b/src/runtime/battery/battery_windows.go new file mode 100644 index 000000000000..2efa2345d701 --- /dev/null +++ b/src/runtime/battery/battery_windows.go @@ -0,0 +1,338 @@ +// battery +// Copyright (C) 2016-2017 Karol 'Kenji Takahashi' Woźniak +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package battery + +import ( + "errors" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type batteryQueryInformation struct { + BatteryTag uint32 + InformationLevel int32 + AtRate int32 +} + +type batteryInformation struct { + Capabilities uint32 + Technology uint8 + Reserved [3]uint8 + Chemistry [4]uint8 + DesignedCapacity uint32 + FullChargedCapacity uint32 + DefaultAlert1 uint32 + DefaultAlert2 uint32 + CriticalBias uint32 + CycleCount uint32 +} + +type batteryWaitStatus struct { + BatteryTag uint32 + Timeout uint32 + PowerState uint32 + LowCapacity uint32 + HighCapacity uint32 +} + +type batteryStatus struct { + PowerState uint32 + Capacity uint32 + Voltage uint32 + Rate int32 +} + +type guid struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} + +type spDeviceInterfaceData struct { + cbSize uint32 + InterfaceClassGuid guid + Flags uint32 + Reserved uint +} + +var guidDeviceBattery = guid{ + 0x72631e54, + 0x78A4, + 0x11d0, + [8]byte{0xbc, 0xf7, 0x00, 0xaa, 0x00, 0xb7, 0xb3, 0x2a}, +} + +func uint32ToFloat64(num uint32) (float64, error) { + if num == 0xffffffff { // BATTERY_UNKNOWN_CAPACITY + return 0, errors.New("unknown value received") + } + return float64(num), nil +} + +func setupDiSetup(proc *windows.LazyProc, args ...uintptr) (uintptr, error) { + r1, _, errno := syscall.SyscallN(proc.Addr(), args...) + if windows.Handle(r1) == windows.InvalidHandle { + if errno != 0 { + return 0, error(errno) + } + return 0, syscall.EINVAL + } + return r1, nil +} + +func setupDiCall(proc *windows.LazyProc, args ...uintptr) syscall.Errno { + r1, _, errno := syscall.SyscallN(proc.Addr(), args...) + if r1 == 0 { + if errno != 0 { + return errno + } + return syscall.EINVAL + } + return 0 +} + +var setupapi = &windows.LazyDLL{Name: "setupapi.dll", System: true} +var setupDiGetClassDevsW = setupapi.NewProc("SetupDiGetClassDevsW") +var setupDiEnumDeviceInterfaces = setupapi.NewProc("SetupDiEnumDeviceInterfaces") +var setupDiGetDeviceInterfaceDetailW = setupapi.NewProc("SetupDiGetDeviceInterfaceDetailW") +var setupDiDestroyDeviceInfoList = setupapi.NewProc("SetupDiDestroyDeviceInfoList") + +func readState(powerState uint32) State { + switch { + case powerState&0x00000004 != 0: + return Charging + case powerState&0x00000008 != 0: + return Empty + case powerState&0x00000002 != 0: + return Discharging + case powerState&0x00000001 != 0: + return Full + default: + return Unknown + } +} + +func systemGet(idx int) (*battery, error) { + hdev, err := setupDiSetup( + setupDiGetClassDevsW, + uintptr(unsafe.Pointer(&guidDeviceBattery)), + 0, + 0, + 2|16, // DIGCF_PRESENT|DIGCF_DEVICEINTERFACE + ) + + if err != nil { + return nil, err + } + + defer func() { + _, _, _ = syscall.SyscallN(setupDiDestroyDeviceInfoList.Addr(), hdev) + }() + + var did spDeviceInterfaceData + did.cbSize = uint32(unsafe.Sizeof(did)) + errno := setupDiCall( + setupDiEnumDeviceInterfaces, + hdev, + 0, + uintptr(unsafe.Pointer(&guidDeviceBattery)), + uintptr(idx), + uintptr(unsafe.Pointer(&did)), + ) + + if errno == 259 { // ERROR_NO_MORE_ITEMS + return nil, ErrNotFound + } + + if errno != 0 { + return nil, errno + } + + var cbRequired uint32 + errno = setupDiCall( + setupDiGetDeviceInterfaceDetailW, + hdev, + uintptr(unsafe.Pointer(&did)), + 0, + 0, + uintptr(unsafe.Pointer(&cbRequired)), + 0, + ) + + if errno != 0 && errno != 122 { // ERROR_INSUFFICIENT_BUFFER + return nil, errno + } + + if cbRequired == 0 { + return nil, errors.New("no buffer information returned") + } + + // The god damn struct with ANYSIZE_ARRAY of utf16 in it is crazy. + // So... let's emulate it with array of uint16 ;-D. + // Keep in mind that the first two elements are actually cbSize. + didd := make([]uint16, cbRequired/2) + cbSize := (*uint32)(unsafe.Pointer(&didd[0])) + if unsafe.Sizeof(uint(0)) == 8 { + *cbSize = 8 + } else { + *cbSize = 6 + } + + errno = setupDiCall( + setupDiGetDeviceInterfaceDetailW, + hdev, + uintptr(unsafe.Pointer(&did)), + uintptr(unsafe.Pointer(&didd[0])), + uintptr(cbRequired), + uintptr(unsafe.Pointer(&cbRequired)), + 0, + ) + + if errno != 0 { + return nil, errno + } + + devicePath := &didd[2:][0] + + handle, err := windows.CreateFile( + devicePath, + windows.GENERIC_READ|windows.GENERIC_WRITE, + windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, + nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL, + 0, + ) + + if err != nil { + return nil, err + } + + defer func() { + _ = windows.CloseHandle(handle) + }() + + var dwOut uint32 + + var dwWait uint32 + var bqi batteryQueryInformation + err = windows.DeviceIoControl( + handle, + 2703424, // IOCTL_BATTERY_QUERY_TAG + (*byte)(unsafe.Pointer(&dwWait)), + uint32(unsafe.Sizeof(dwWait)), + (*byte)(unsafe.Pointer(&bqi.BatteryTag)), + uint32(unsafe.Sizeof(bqi.BatteryTag)), + &dwOut, + nil, + ) + + if err != nil { + return nil, err + } + + if bqi.BatteryTag == 0 { + return nil, errors.New("battery tag not returned") + } + + b := &battery{} + + var bi batteryInformation + err = windows.DeviceIoControl( + handle, + 2703428, // IOCTL_BATTERY_QUERY_INFORMATION + (*byte)(unsafe.Pointer(&bqi)), + uint32(unsafe.Sizeof(bqi)), + (*byte)(unsafe.Pointer(&bi)), + uint32(unsafe.Sizeof(bi)), + &dwOut, + nil, + ) + + if err != nil { + return nil, err + } + + b.Full = float64(bi.FullChargedCapacity) + + bws := batteryWaitStatus{BatteryTag: bqi.BatteryTag} + + var bs batteryStatus + err = windows.DeviceIoControl( + handle, + 2703436, // IOCTL_BATTERY_QUERY_STATUS + (*byte)(unsafe.Pointer(&bws)), + uint32(unsafe.Sizeof(bws)), + (*byte)(unsafe.Pointer(&bs)), + uint32(unsafe.Sizeof(bs)), + &dwOut, + nil, + ) + + if err != nil { + return nil, err + } + + if b.Current, err = uint32ToFloat64(bs.Capacity); err != nil { + return nil, err + } + + if b.Voltage, err = uint32ToFloat64(bs.Voltage); err != nil { + return nil, err + } + + b.Voltage /= 1000 + b.State = readState(bs.PowerState) + + return b, nil +} + +func systemGetAll() ([]*battery, error) { + var batteries []*battery + var i int + var errs Errors + + for i = 0; ; i++ { + b, err := systemGet(i) + if err == ErrNotFound { + break + } + if err != nil { + errs = append(errs, err) + continue + } + batteries = append(batteries, b) + } + + if i == 0 { + return nil, &NoBatteryError{} + } + + if len(batteries) == 0 { + return nil, errs + } + + return batteries, nil +} diff --git a/src/runtime/battery/battery_windows_nix.go b/src/runtime/battery/battery_windows_nix.go new file mode 100644 index 000000000000..b2fb16716e2d --- /dev/null +++ b/src/runtime/battery/battery_windows_nix.go @@ -0,0 +1,65 @@ +//go:build !darwin && !netbsd && !openbsd && !freebsd + +package battery + +import ( + "math" +) + +// battery type represents a single battery entry information. +type battery struct { + // Current battery state. + State State + // Current (momentary) capacity (in mWh). + Current float64 + // Last known full capacity (in mWh). + Full float64 + // Current voltage (in V). + Voltage float64 +} + +func mapMostLogicalState(currentState, newState State) State { + switch currentState { + case Discharging, NotCharging: + return Discharging + case Empty: + return newState + case Charging: + if newState == Discharging { + return Discharging + } + return Charging + case Unknown: + return newState + case Full: + return newState + } + return newState +} + +// Get returns information about all batteries in the system. +// +// If error != nil, it will be either ErrFatal or Errors. +// If error is of type Errors, it is guaranteed that length of both returned slices is the same and that i-th error corresponds with i-th battery structure. +func Get() (*Info, error) { + parseBatteryInfo := func(batteries []*battery) *Info { + var info Info + var current, total float64 + var state State + for _, bt := range batteries { + current += bt.Current + total += bt.Full + state = mapMostLogicalState(state, bt.State) + } + batteryPercentage := current / total * 100 + info.Percentage = int(math.Min(100, batteryPercentage)) + info.State = state + return &info + } + + batteries, err := systemGetAll() + if err != nil { + return nil, err + } + return parseBatteryInfo(batteries), nil +} diff --git a/src/runtime/battery/battery_windows_nix_test.go b/src/runtime/battery/battery_windows_nix_test.go new file mode 100644 index 000000000000..027395235d5f --- /dev/null +++ b/src/runtime/battery/battery_windows_nix_test.go @@ -0,0 +1,51 @@ +//go:build !darwin && !netbsd && !openbsd + +// battery +// Copyright (C) 2016-2017 Karol 'Kenji Takahashi' Woźniak +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package battery + +import ( + "testing" + + "github.com/alecthomas/assert" +) + +func TestMapBatteriesState(t *testing.T) { + cases := []struct { + Case string + ExpectedState State + CurrentState State + NewState State + }{ + {Case: "charging > charged", ExpectedState: Charging, CurrentState: Full, NewState: Charging}, + {Case: "charging < discharging", ExpectedState: Discharging, CurrentState: Discharging, NewState: Charging}, + {Case: "charging == charging", ExpectedState: Charging, CurrentState: Charging, NewState: Charging}, + {Case: "discharging > charged", ExpectedState: Discharging, CurrentState: Full, NewState: Discharging}, + {Case: "discharging > unknown", ExpectedState: Discharging, CurrentState: Unknown, NewState: Discharging}, + {Case: "discharging > full", ExpectedState: Discharging, CurrentState: Full, NewState: Discharging}, + {Case: "discharging > charging 2", ExpectedState: Discharging, CurrentState: Charging, NewState: Discharging}, + {Case: "discharging > empty", ExpectedState: Discharging, CurrentState: Empty, NewState: Discharging}, + } + for _, tc := range cases { + assert.Equal(t, tc.ExpectedState, mapMostLogicalState(tc.CurrentState, tc.NewState), tc.Case) + } +} diff --git a/src/runtime/battery/errors.go b/src/runtime/battery/errors.go new file mode 100644 index 000000000000..0333f6f51e9e --- /dev/null +++ b/src/runtime/battery/errors.go @@ -0,0 +1,42 @@ +// battery +// Copyright (C) 2016-2017 Karol 'Kenji Takahashi' Woźniak +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package battery + +import "fmt" + +var ErrNotFound = fmt.Errorf("not found") + +type Errors []error + +func (e Errors) Error() string { + var s string + for _, err := range e { + if err != nil { + s += err.Error() + ", " + } + } + // strip trailing colon/space + if len(s) > 1 { + s = s[:len(s)-2] + } + return s +} diff --git a/src/runtime/battery/errors_test.go b/src/runtime/battery/errors_test.go new file mode 100644 index 000000000000..9054420032bf --- /dev/null +++ b/src/runtime/battery/errors_test.go @@ -0,0 +1,48 @@ +// battery +// Copyright (C) 2016-2017 Karol 'Kenji Takahashi' Woźniak +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package battery + +import ( + "errors" + "testing" +) + +func TestErrors(t *testing.T) { + cases := []struct { + str string + in Errors + }{ + {"", Errors{nil}}, + {"", Errors{errors.New("")}}, + {"t1", Errors{errors.New("t1")}}, + {"t2, t3", Errors{errors.New("t2"), errors.New("t3")}}, + {"t4, t5", Errors{errors.New("t4"), errors.New("t5")}}, + } + + for i, c := range cases { + str := c.in.Error() + + if str != c.str { + t.Errorf("%d: %v != %v", i, str, c.str) + } + } +} diff --git a/src/runtime/cmd/run.go b/src/runtime/cmd/run.go new file mode 100644 index 000000000000..e3d2bf0cc7d3 --- /dev/null +++ b/src/runtime/cmd/run.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "bytes" + "context" + "os/exec" + "strings" + + runjobs "github.com/jandedobbeleer/oh-my-posh/src/runtime/jobs" +) + +// Run executes a command while ensuring the OS process is started in its own +// process group; the started process is recorded so callers can request a +// cleanup (KillGoroutineChildren) if they decide to abort waiting for the +// goroutine that spawned it. +func Run(command string, args ...string) (string, error) { + cmd := exec.CommandContext(context.Background(), command, args...) + var out bytes.Buffer + var errb bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &errb + + // ensure child runs in its own process group so we can kill the tree if + // needed. Implementation is provided by the runtime/jobs package which is + // platform aware. + runjobs.SetProcessGroup(cmd) + + if err := cmd.Start(); err != nil { + return "", err + } + + // register the started process under the current goroutine + runjobs.RegisterProcess(cmd.Process.Pid) + defer runjobs.UnregisterProcess(cmd.Process.Pid) + + if err := cmd.Wait(); err != nil { + // Prefer stderr if available + output := strings.TrimSpace(errb.String()) + if output == "" { + output = strings.TrimSpace(out.String()) + } + return output, err + } + + result := strings.TrimSpace(out.String()) + if result == "" { + result = strings.TrimSpace(errb.String()) + } + return result, nil +} diff --git a/src/runtime/cmd/run_test.go b/src/runtime/cmd/run_test.go new file mode 100644 index 000000000000..9e1e408b14b3 --- /dev/null +++ b/src/runtime/cmd/run_test.go @@ -0,0 +1,13 @@ +package cmd + +import ( + "testing" + + runjobs "github.com/jandedobbeleer/oh-my-posh/src/runtime/jobs" +) + +func TestCurrentGID(t *testing.T) { + if gid := runjobs.CurrentGID(); gid == 0 { + t.Fatalf("CurrentGID returned 0") + } +} diff --git a/src/runtime/environment.go b/src/runtime/environment.go new file mode 100644 index 000000000000..d6204c49eeca --- /dev/null +++ b/src/runtime/environment.go @@ -0,0 +1,172 @@ +package runtime + +import ( + "io" + "io/fs" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/battery" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" + + disk "github.com/shirou/gopsutil/v4/disk" +) + +const ( + UNKNOWN = "unknown" + WINDOWS = "windows" + DARWIN = "darwin" + LINUX = "linux" + FREEBSD = "freebsd" + CMD = "cmd" + ANDROID = "android" + + PRIMARY = "primary" +) + +type Environment interface { + Getenv(key string) string + Pwd() string + Home() string + User() string + Root() bool + Host() (string, error) + GOOS() string + Shell() string + Platform() string + StatusCodes() (int, string) + HasFiles(pattern string) bool + HasFilesInDir(dir, pattern string) bool + HasFolder(folder string) bool + HasParentFilePath(input string, followSymlinks bool) (fileInfo *FileInfo, err error) + HasFileInParentDirs(pattern string, depth uint) bool + ResolveSymlink(input string) (string, error) + DirMatchesOneOf(dir string, regexes []string) bool + DirIsWritable(input string) bool + CommandPath(command string) string + HasCommand(command string) bool + FileContent(file string) string + LsDir(input string) []fs.DirEntry + RunCommand(command string, args ...string) (string, error) + RunShellCommand(shell, command string) string + ExecutionTime() float64 + Flags() *Flags + BatteryState() (*battery.Info, error) + QueryWindowTitles(processName, windowTitleRegex string) (string, error) + WindowsRegistryKeyValue(key string) (*WindowsRegistryValue, error) + HTTPRequest(url string, body io.Reader, timeout int, requestModifiers ...http.RequestModifier) ([]byte, error) + IsWsl() bool + IsWsl2() bool + IsCygwin() bool + StackCount() int + TerminalWidth() (int, error) + Logs() string + InWSLSharedDrive() bool + ConvertToLinuxPath(input string) string + ConvertToWindowsPath(input string) string + Connection(connectionType ConnectionType) (*Connection, error) + CursorPosition() (row, col int) + SystemInfo() (*SystemInfo, error) +} + +type Flags struct { + Type string + PipeStatus string + ConfigPath string + PSWD string + Shell string + ShellVersion string + PWD string + AbsolutePWD string + ErrorCode int + PromptCount int + Column int + TerminalWidth int + ExecutionTime float64 + StackCount int + ConfigHash uint64 + JobCount int + HasExtra bool + Strict bool + Debug bool + Cleared bool + NoExitCode bool + Init bool + Migrate bool + Eval bool + Escape bool + IsPrimary bool + Plain bool + Force bool + Streaming bool +} + +type CommandError struct { + Err string + ExitCode int +} + +func (e *CommandError) Error() string { + return e.Err +} + +type FileInfo struct { + ParentFolder string + Path string + IsDir bool +} + +type WindowsRegistryValueType string + +const ( + DWORD = "DWORD" + QWORD = "QWORD" + BINARY = "BINARY" + STRING = "STRING" +) + +type WindowsRegistryValue struct { + ValueType WindowsRegistryValueType + String string + DWord uint64 + QWord uint64 +} + +type NotImplemented struct{} + +func (n *NotImplemented) Error() string { + return "not implemented" +} + +type ConnectionType string + +const ( + ETHERNET ConnectionType = "ethernet" + WIFI ConnectionType = "wifi" + CELLULAR ConnectionType = "cellular" + BLUETOOTH ConnectionType = "bluetooth" +) + +type Connection struct { + Name string + Type ConnectionType + SSID string + TransmitRate uint64 + ReceiveRate uint64 +} + +type Memory struct { + PhysicalTotalMemory uint64 + PhysicalAvailableMemory uint64 + PhysicalFreeMemory uint64 + PhysicalPercentUsed float64 + SwapTotalMemory uint64 + SwapFreeMemory uint64 + SwapPercentUsed float64 +} + +type SystemInfo struct { + Disks map[string]disk.IOCountersStat + Memory + Load1 float64 + Load5 float64 + Load15 float64 +} diff --git a/src/runtime/http/connection.go b/src/runtime/http/connection.go new file mode 100644 index 000000000000..a6b968041f95 --- /dev/null +++ b/src/runtime/http/connection.go @@ -0,0 +1,26 @@ +//revive:disable:var-naming // package intentionally mirrors standard name for compatibility across runtime +package http + +import ( + "context" + "net" + "time" +) + +// IsConnected checks if we can connect to ohmyposh within 200ms. +// Exposed as a variable so it can be replaced in tests. +var IsConnected = func() bool { + timeout := 200 * time.Millisecond + dialer := &net.Dialer{ + Timeout: timeout, + } + + ctx := context.Background() + conn, err := dialer.DialContext(ctx, "tcp", "ohmyposh.dev:80") + if err != nil { + return false + } + + conn.Close() + return true +} diff --git a/src/runtime/http/download.go b/src/runtime/http/download.go new file mode 100644 index 000000000000..9eeb7f9a0dd1 --- /dev/null +++ b/src/runtime/http/download.go @@ -0,0 +1,92 @@ +//revive:disable:var-naming // package intentionally mirrors standard name for compatibility across runtime +package http + +import ( + "context" + "fmt" + "io" + httplib "net/http" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +func Download(url string, isCacheEnabled bool) ([]byte, error) { + defer log.Trace(time.Now(), url) + + // some users use the blob url, we need to convert it to the raw url + themeBlob := "https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/" + url = strings.Replace(url, themeBlob, "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/", 1) + + ctx, cncl := context.WithTimeout(context.Background(), time.Second*time.Duration(5)) + defer cncl() + + request, err := httplib.NewRequestWithContext(ctx, httplib.MethodGet, url, nil) + if err != nil { + log.Error(err) + return nil, err + } + + request.Header.Add("User-Agent", "oh-my-posh") + // if we have an etag, add it to the request to check if the file changed + etag, OK := cache.Get[string](cache.Device, etagKey(url)) + if OK { + log.Debugf("found etag in cache: %s", etag) + request.Header.Set("If-None-Match", etag) + } + + cachedData := func() ([]byte, error) { + cachedData, OK := cache.Get[[]byte](cache.Device, dataKey(url)) + if OK { + return cachedData, nil + } + + return nil, fmt.Errorf("resource not modified but no cached data found") + } + + response, err := HTTPClient.Do(request) + if err != nil { + log.Error(err) + return cachedData() + } + + defer response.Body.Close() + + if response.StatusCode == httplib.StatusNotModified { + log.Debug("resource not modified, using cached version") + return cachedData() + } + + if response.StatusCode != httplib.StatusOK { + err := fmt.Errorf("status code: %d", response.StatusCode) + log.Error(err) + return cachedData() + } + + etag = response.Header.Get("ETag") + if etag != "" && isCacheEnabled { + cache.Set(cache.Device, etagKey(url), etag, cache.INFINITE) + } + + data, err := io.ReadAll(response.Body) + if err != nil { + log.Error(err) + return cachedData() + } + + if isCacheEnabled { + cache.Set(cache.Device, dataKey(url), data, cache.INFINITE) + } + + return data, nil +} + +func etagKey(url string) string { + return fmt.Sprintf("%s.etag", url) +} + +func dataKey(url string) string { + return fmt.Sprintf("%s.data", url) +} diff --git a/src/runtime/http/http.go b/src/runtime/http/http.go new file mode 100644 index 000000000000..2a38abd22164 --- /dev/null +++ b/src/runtime/http/http.go @@ -0,0 +1,35 @@ +//revive:disable:var-naming // package intentionally mirrors standard name for compatibility across runtime +package http + +import ( + "net" + "net/http" + "time" +) + +// Inspired by: https://www.thegreatcodeadventure.com/mocking-http-requests-in-golang/ + +type httpClient interface { + Do(req *http.Request) (*http.Response, error) +} + +var ( + defaultTransport http.RoundTripper = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 10 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + ResponseHeaderTimeout: 10 * time.Second, + } + + HTTPClient httpClient = &http.Client{Transport: defaultTransport} +) + +type Error struct { + StatusCode int +} + +func (e *Error) Error() string { + return http.StatusText(e.StatusCode) +} diff --git a/src/runtime/http/oauth.go b/src/runtime/http/oauth.go new file mode 100644 index 000000000000..1da6d8bc2f05 --- /dev/null +++ b/src/runtime/http/oauth.go @@ -0,0 +1,117 @@ +//revive:disable:var-naming // package intentionally mirrors standard name for compatibility across runtime +package http + +import ( + "encoding/json" + "fmt" + "io" + httplib "net/http" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" +) + +const ( + Timeout = "timeout" + InvalidRefreshToken = "invalid refresh token" + TokenRefreshFailed = "token refresh error" + DefaultRefreshToken = "111111111111111111111111111111" +) + +type tokenExchange struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` +} + +type OAuthError struct { + message string +} + +func (a *OAuthError) Error() string { + return a.message +} + +type OAuthRequest struct { + AccessTokenKey string + RefreshTokenKey string + SegmentName string + RefreshToken string + AccessToken string + Request +} + +func (o *OAuthRequest) getAccessToken() (string, error) { + // get directly from cache + if accessToken, OK := cache.Get[string](cache.Device, o.AccessTokenKey); OK && len(accessToken) != 0 { + return accessToken, nil + } + + // use cached refresh token to get new access token + if refreshToken, OK := cache.Get[string](cache.Device, o.RefreshTokenKey); OK && len(refreshToken) != 0 { + if accessToken, err := o.refreshToken(refreshToken); err == nil { + return accessToken, nil + } + } + + // use initial refresh token from property + // refreshToken := o.props.GetString(options.RefreshToken, "") + // ignore an empty or default refresh token + if o.RefreshToken == "" || o.RefreshToken == DefaultRefreshToken { + return "", &OAuthError{ + message: InvalidRefreshToken, + } + } + + // no need to let the user provide access token, we'll always verify the refresh token + accessToken, err := o.refreshToken(o.RefreshToken) + return accessToken, err +} + +func (o *OAuthRequest) refreshToken(refreshToken string) (string, error) { + if o.HTTPTimeout == 0 { + o.HTTPTimeout = 20 + } + + url := fmt.Sprintf("https://ohmyposh.dev/api/refresh?segment=%s&token=%s", o.SegmentName, refreshToken) + body, err := o.Env.HTTPRequest(url, nil, o.HTTPTimeout) + if err != nil { + return "", &OAuthError{ + // This might happen if /api was asleep. Assume the user will just retry + message: Timeout, + } + } + + tokens := &tokenExchange{} + err = json.Unmarshal(body, &tokens) + if err != nil { + return "", &OAuthError{ + message: TokenRefreshFailed, + } + } + + // add tokens to cache + cache.Set(cache.Device, o.AccessTokenKey, tokens.AccessToken, cache.ToDuration(tokens.ExpiresIn)) + cache.Set(cache.Device, o.RefreshTokenKey, tokens.RefreshToken, cache.TWOYEARS) + return tokens.AccessToken, nil +} + +func OauthResult[a any](o *OAuthRequest, url string, body io.Reader, requestModifiers ...RequestModifier) (a, error) { + accessToken, err := o.getAccessToken() + if err != nil { + var data a + return data, err + } + + // add token to header for authentication + addAuthHeader := func(request *httplib.Request) { + request.Header.Add("Authorization", "Bearer "+accessToken) + } + + if requestModifiers == nil { + requestModifiers = []RequestModifier{} + } + + requestModifiers = append(requestModifiers, addAuthHeader) + + return Do[a](&o.Request, url, body, requestModifiers...) +} diff --git a/src/runtime/http/oauth_test.go b/src/runtime/http/oauth_test.go new file mode 100644 index 000000000000..87bd8fa36b55 --- /dev/null +++ b/src/runtime/http/oauth_test.go @@ -0,0 +1,163 @@ +//revive:disable:var-naming // test package matches implementation; lint warning is intentional +package http + +import ( + "fmt" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/stretchr/testify/assert" +) + +type data struct { + Hello string `json:"hello"` +} + +func TestOauthResult(t *testing.T) { + accessTokenKey := "test_access_token" + refreshTokenKey := "test_refresh_token" + tokenResponse := `{ "access_token":"NEW_ACCESSTOKEN","refresh_token":"NEW_REFRESHTOKEN", "expires_in":1234 }` + jsonResponse := `{ "hello":"world" }` + successData := &data{Hello: "world"} + + cases := []struct { + Error error + ExpectedData *data + AccessToken string + RefreshToken string + TokenResponse string + JSONResponse string + CacheJSONResponse string + Case string + ExpectedErrorMessage string + CacheTimeout int + ResponseCacheMiss bool + AccessTokenFromCache bool + RefreshTokenFromCache bool + }{ + { + Case: "No initial tokens", + ExpectedErrorMessage: InvalidRefreshToken, + }, + { + Case: "Use config tokens", + AccessToken: "INITIAL_ACCESSTOKEN", + RefreshToken: "INITIAL_REFRESHTOKEN", + TokenResponse: tokenResponse, + JSONResponse: jsonResponse, + ExpectedData: successData, + }, + { + Case: "Access token from cache", + AccessToken: "ACCESSTOKEN", + AccessTokenFromCache: true, + JSONResponse: jsonResponse, + ExpectedData: successData, + }, + { + Case: "Refresh token from cache", + RefreshToken: "REFRESH_TOKEN", + RefreshTokenFromCache: true, + JSONResponse: jsonResponse, + TokenResponse: tokenResponse, + ExpectedData: successData, + }, + { + Case: "Refresh token from cache, success", + RefreshToken: "REFRESH_TOKEN", + RefreshTokenFromCache: true, + JSONResponse: jsonResponse, + TokenResponse: tokenResponse, + ExpectedData: successData, + }, + { + Case: "Refresh API error", + RefreshToken: "REFRESH_TOKEN", + RefreshTokenFromCache: true, + Error: fmt.Errorf("API error"), + ExpectedErrorMessage: Timeout, + }, + { + Case: "Refresh API parse error", + RefreshToken: "REFRESH_TOKEN", + RefreshTokenFromCache: true, + TokenResponse: "INVALID_JSON", + ExpectedErrorMessage: TokenRefreshFailed, + }, + { + Case: "Default config token", + RefreshToken: DefaultRefreshToken, + ExpectedErrorMessage: InvalidRefreshToken, + }, + { + Case: "Cache data, invalid data", + RefreshToken: "REFRESH_TOKEN", + TokenResponse: tokenResponse, + JSONResponse: jsonResponse, + ExpectedData: successData, + }, + { + Case: "Cache data, no cache", + RefreshToken: "REFRESH_TOKEN", + TokenResponse: tokenResponse, + JSONResponse: jsonResponse, + ExpectedData: successData, + }, + { + Case: "API body failure", + AccessToken: "ACCESSTOKEN", + AccessTokenFromCache: true, + JSONResponse: "ERR", + ExpectedErrorMessage: "invalid character 'E' looking for beginning of value", + }, + { + Case: "API request failure", + AccessToken: "ACCESSTOKEN", + AccessTokenFromCache: true, + JSONResponse: "ERR", + Error: fmt.Errorf("no response"), + ExpectedErrorMessage: "no response", + }, + } + + for _, tc := range cases { + url := "https://www.strava.com/api/v3/athlete/activities?page=1&per_page=1" + tokenURL := fmt.Sprintf("https://ohmyposh.dev/api/refresh?segment=test&token=%s", tc.RefreshToken) + + if tc.AccessTokenFromCache { + cache.Set(cache.Device, accessTokenKey, tc.AccessToken, cache.INFINITE) + } + + if tc.RefreshTokenFromCache { + cache.Set(cache.Device, refreshTokenKey, tc.RefreshToken, cache.INFINITE) + } + + env := &MockedEnvironment{} + + env.On("HTTPRequest", url).Return([]byte(tc.JSONResponse), tc.Error) + env.On("HTTPRequest", tokenURL).Return([]byte(tc.TokenResponse), tc.Error) + + oauth := &OAuthRequest{ + AccessTokenKey: accessTokenKey, + RefreshTokenKey: refreshTokenKey, + SegmentName: "test", + AccessToken: tc.AccessToken, + RefreshToken: tc.RefreshToken, + Request: Request{ + Env: env, + HTTPTimeout: 20, + }, + } + + got, err := OauthResult[*data](oauth, url, nil) + assert.Equal(t, tc.ExpectedData, got, tc.Case) + + if tc.ExpectedErrorMessage == "" { + assert.Nil(t, err, tc.Case) + } else { + assert.Equal(t, tc.ExpectedErrorMessage, err.Error(), tc.Case) + } + + cache.DeleteAll(cache.Device) + } +} diff --git a/src/runtime/http/request.go b/src/runtime/http/request.go new file mode 100644 index 000000000000..6890ccfb2fc3 --- /dev/null +++ b/src/runtime/http/request.go @@ -0,0 +1,40 @@ +//revive:disable:var-naming // package intentionally mirrors standard name for compatibility across runtime +package http + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +type RequestModifier func(request *http.Request) + +type Request struct { + Env Environment + HTTPTimeout int +} + +type Environment interface { + HTTPRequest(url string, body io.Reader, timeout int, requestModifiers ...RequestModifier) ([]byte, error) +} + +func Do[a any](r *Request, url string, body io.Reader, requestModifiers ...RequestModifier) (a, error) { + var data a + httpTimeout := r.HTTPTimeout // r.props.GetInt(options.HTTPTimeout, options.DefaultHTTPTimeout) + + responseBody, err := r.Env.HTTPRequest(url, body, httpTimeout, requestModifiers...) + if err != nil { + log.Error(err) + return data, err + } + + err = json.Unmarshal(responseBody, &data) + if err != nil { + log.Error(err) + return data, err + } + + return data, nil +} diff --git a/src/runtime/http/request_test.go b/src/runtime/http/request_test.go new file mode 100644 index 000000000000..92ee9c6a1dc0 --- /dev/null +++ b/src/runtime/http/request_test.go @@ -0,0 +1,71 @@ +//revive:disable:var-naming // test package matches implementation; lint warning is intentional +package http + +import ( + "io" + "net" + "testing" + + "github.com/stretchr/testify/assert" + testify_ "github.com/stretchr/testify/mock" +) + +type MockedEnvironment struct { + testify_.Mock +} + +func (env *MockedEnvironment) HTTPRequest(url string, _ io.Reader, _ int, _ ...RequestModifier) ([]byte, error) { + args := env.Called(url) + return args.Get(0).([]byte), args.Error(1) +} + +func TestRequestResult(t *testing.T) { + successData := &data{Hello: "world"} + jsonResponse := `{ "hello":"world" }` + url := "https://google.com?q=hello" + + cases := []struct { + Error error + ExpectedData *data + Case string + JSONResponse string + CacheJSONResponse string + ExpectedErrorMessage string + CacheTimeout int + ResponseCacheMiss bool + }{ + { + Case: "No cache", + JSONResponse: jsonResponse, + ExpectedData: successData, + }, + { + Case: "DNS error", + Error: &net.DNSError{IsNotFound: true}, + ExpectedErrorMessage: "lookup : ", + }, + { + Case: "Response incorrect", + JSONResponse: `[`, + ExpectedErrorMessage: "unexpected end of JSON input", + }, + } + + for _, tc := range cases { + env := &MockedEnvironment{} + env.On("HTTPRequest", url).Return([]byte(tc.JSONResponse), tc.Error) + + request := &Request{ + Env: env, + HTTPTimeout: 0, + } + + got, err := Do[*data](request, url, nil) + assert.Equal(t, tc.ExpectedData, got, tc.Case) + if tc.ExpectedErrorMessage == "" { + assert.Nil(t, err, tc.Case) + } else { + assert.Equal(t, tc.ExpectedErrorMessage, err.Error(), tc.Case) + } + } +} diff --git a/src/runtime/jobs/jobs_common.go b/src/runtime/jobs/jobs_common.go new file mode 100644 index 000000000000..fe6ee98bf408 --- /dev/null +++ b/src/runtime/jobs/jobs_common.go @@ -0,0 +1,25 @@ +package jobs + +import ( + "runtime" + "strconv" + "strings" +) + +// CurrentGID returns the current goroutine's id. We expose this here so +// callers can register PIDs without parsing runtime.Stack in multiple +// places. +func CurrentGID() uint64 { + buf := make([]byte, 64) + n := runtime.Stack(buf, false) + s := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine ")) + if len(s) == 0 { + return 0 + } + idStr := s[0] + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + return 0 + } + return id +} diff --git a/src/runtime/jobs/jobs_other.go b/src/runtime/jobs/jobs_other.go new file mode 100644 index 000000000000..1171fe204e42 --- /dev/null +++ b/src/runtime/jobs/jobs_other.go @@ -0,0 +1,82 @@ +//go:build !windows + +package jobs + +import ( + "fmt" + "os/exec" + "strings" + "sync" + "syscall" +) + +var ( + processesMu sync.Mutex + processes = map[uint64]map[int]struct{}{} +) + +func CreateJobForGoroutine(_ string) error { return nil } +func AssignPidToGoroutineJob(_ int) error { return nil } + +// setProcessGroup ensures the child process runs in its own process group so +// it can be killed with a group kill (negative pid). +func SetProcessGroup(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} +} + +// registerProcessWithGID keeps track of a started child process for the +// given goroutine id. +func RegisterProcess(pid int) { + gid := CurrentGID() + processesMu.Lock() + m := processes[gid] + if m == nil { + m = map[int]struct{}{} + processes[gid] = m + } + m[pid] = struct{}{} + processesMu.Unlock() +} + +func UnregisterProcess(pid int) { + gid := CurrentGID() + processesMu.Lock() + if m, ok := processes[gid]; ok { + delete(m, pid) + if len(m) == 0 { + delete(processes, gid) + } + } + processesMu.Unlock() +} + +// KillGoroutineChildren attempts to kill all child processes started by the +// goroutine identified by gid using process groups (PGID). This mirrors the +// previous behavior performed in runtime/cmd. +func KillGoroutineChildren(gid uint64) error { + processesMu.Lock() + pidsMap, ok := processes[gid] + if !ok || len(pidsMap) == 0 { + processesMu.Unlock() + return nil + } + pids := make([]int, 0, len(pidsMap)) + for pid := range pidsMap { + pids = append(pids, pid) + } + delete(processes, gid) + processesMu.Unlock() + + var errs []string + for _, pid := range pids { + // negative pid kills the process group + if err := syscall.Kill(-pid, syscall.SIGKILL); err != nil { + errs = append(errs, fmt.Sprintf("kill -%d: %v", pid, err)) + } + } + + if len(errs) > 0 { + return fmt.Errorf("failed to kill child processes: %s", strings.Join(errs, "; ")) + } + return nil +} diff --git a/src/runtime/jobs/jobs_windows.go b/src/runtime/jobs/jobs_windows.go new file mode 100644 index 000000000000..6149f8a671d7 --- /dev/null +++ b/src/runtime/jobs/jobs_windows.go @@ -0,0 +1,171 @@ +//go:build windows + +package jobs + +import ( + "context" + "fmt" + "os/exec" + "strconv" + "strings" + "sync" + "syscall" + "time" + "unsafe" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "golang.org/x/sys/windows" +) + +var ( + jobsMu sync.Mutex + jobs = map[uint64]windows.Handle{} + processesMu sync.Mutex + processes = map[uint64]map[int]struct{}{} +) + +// CreateJobForGoroutine creates a Job object for gid and sets the +// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag so closing/terminating the job +// kills all assigned processes. +func CreateJobForGoroutine(label string) error { + gid := CurrentGID() + defer log.Trace(time.Now(), fmt.Sprintf("creating job for goroutine(%s): %d", label, gid)) + + jobsMu.Lock() + if _, ok := jobs[gid]; ok { + jobsMu.Unlock() + return nil + } + jobsMu.Unlock() + + job, err := windows.CreateJobObject(nil, nil) + if err != nil { + return err + } + + var info windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION + info.BasicLimitInformation.LimitFlags = windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + + size := uint32(unsafe.Sizeof(info)) + if _, err := windows.SetInformationJobObject(job, windows.JobObjectExtendedLimitInformation, uintptr(unsafe.Pointer(&info)), size); err != nil { + _ = windows.CloseHandle(job) + } + + jobsMu.Lock() + jobs[gid] = job + jobsMu.Unlock() + + return nil +} + +// registerProcessWithGID keeps track of a started child process for the +// given goroutine id and attempts to assign it to the Job object if present. +func RegisterProcess(pid int) { + gid := CurrentGID() + processesMu.Lock() + m := processes[gid] + if m == nil { + m = map[int]struct{}{} + processes[gid] = m + } + + m[pid] = struct{}{} + processesMu.Unlock() + + // Try to assign to job if exists (best-effort) + jobsMu.Lock() + job, ok := jobs[gid] + jobsMu.Unlock() + if !ok { + log.Debugf("no job found for goroutine %d when assigning pid %d", gid, pid) + return + } + + proc, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE, false, uint32(pid)) + if err != nil { + log.Error(err) + return + } + + defer func() { + err = windows.CloseHandle(proc) + if err != nil { + log.Error(err) + } + }() + + if err = windows.AssignProcessToJobObject(job, proc); err != nil { + log.Error(err) + } + + log.Debugf("successfully added process to job for goroutine: %d, pid: %d", gid, pid) +} + +func UnregisterProcess(pid int) { + gid := CurrentGID() + processesMu.Lock() + + if m, ok := processes[gid]; ok { + delete(m, pid) + if len(m) == 0 { + delete(processes, gid) + } + } + + processesMu.Unlock() +} + +// KillGoroutineChildren will first try to terminate a Job if present, and +// otherwise will fall back to taskkill for each recorded pid. +func KillGoroutineChildren(gid uint64) error { + // if Job exists, prefer terminating the Job + jobsMu.Lock() + job, hasJob := jobs[gid] + if hasJob { + delete(jobs, gid) + } + jobsMu.Unlock() + if hasJob { + // Terminate the job which kills all processes in it + if err := windows.TerminateJobObject(job, 1); err == nil { + // cleanup recorded pids as well + processesMu.Lock() + delete(processes, gid) + processesMu.Unlock() + log.Debugf("successfully terminated job object for goroutine: %d", gid) + return nil + } + } + + // No job or terminate failed; fall back to per-pid taskkill + processesMu.Lock() + pidsMap, ok := processes[gid] + if !ok || len(pidsMap) == 0 { + processesMu.Unlock() + return nil + } + pids := make([]int, 0, len(pidsMap)) + for pid := range pidsMap { + pids = append(pids, pid) + } + delete(processes, gid) + processesMu.Unlock() + + var errs []string + for _, pid := range pids { + if err := exec.CommandContext(context.Background(), "taskkill", "/T", "/F", "/PID", strconv.Itoa(pid)).Run(); err != nil { + errs = append(errs, fmt.Sprintf("taskkill %d: %v", pid, err)) + } + } + + if len(errs) > 0 { + return fmt.Errorf("failed to kill child processes: %s", strings.Join(errs, "; ")) + } + return nil +} + +// setProcessGroup ensures the child process runs in its own process group +// (CREATE_NEW_PROCESS_GROUP) so it can be terminated as a group. +func SetProcessGroup(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP} +} diff --git a/src/runtime/mock/environment.go b/src/runtime/mock/environment.go new file mode 100644 index 000000000000..231d8ad0f86a --- /dev/null +++ b/src/runtime/mock/environment.go @@ -0,0 +1,264 @@ +package mock + +import ( + "io" + "io/fs" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/battery" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" + + mock "github.com/stretchr/testify/mock" +) + +type Environment struct { + mock.Mock +} + +func (env *Environment) Getenv(key string) string { + args := env.Called(key) + return args.String(0) +} + +func (env *Environment) Pwd() string { + args := env.Called() + return args.String(0) +} + +func (env *Environment) Home() string { + args := env.Called() + return args.String(0) +} + +func (env *Environment) HasFiles(pattern string) bool { + args := env.Called(pattern) + return args.Bool(0) +} + +func (env *Environment) HasFilesInDir(dir, pattern string) bool { + args := env.Called(dir, pattern) + return args.Bool(0) +} + +func (env *Environment) HasFolder(folder string) bool { + args := env.Called(folder) + return args.Bool(0) +} + +func (env *Environment) ResolveSymlink(input string) (string, error) { + args := env.Called(input) + return args.String(0), args.Error(1) +} + +func (env *Environment) FileContent(file string) string { + args := env.Called(file) + return args.String(0) +} + +func (env *Environment) LsDir(input string) []fs.DirEntry { + args := env.Called(input) + return args.Get(0).([]fs.DirEntry) +} + +func (env *Environment) User() string { + args := env.Called() + return args.String(0) +} + +func (env *Environment) Host() (string, error) { + args := env.Called() + return args.String(0), args.Error(1) +} + +func (env *Environment) GOOS() string { + args := env.Called() + return args.String(0) +} + +func (env *Environment) Platform() string { + args := env.Called() + return args.String(0) +} + +func (env *Environment) CommandPath(command string) string { + args := env.Called(command) + return args.String(0) +} + +func (env *Environment) HasCommand(command string) bool { + args := env.Called(command) + return args.Bool(0) +} + +func (env *Environment) RunCommand(command string, args ...string) (string, error) { + arguments := env.Called(command, args) + return arguments.String(0), arguments.Error(1) +} + +func (env *Environment) RunShellCommand(shell, command string) string { + args := env.Called(shell, command) + return args.String(0) +} + +func (env *Environment) StatusCodes() (int, string) { + args := env.Called() + return args.Int(0), args.String(1) +} + +func (env *Environment) ExecutionTime() float64 { + args := env.Called() + return float64(args.Int(0)) +} + +func (env *Environment) Root() bool { + args := env.Called() + return args.Bool(0) +} + +func (env *Environment) Flags() *runtime.Flags { + arguments := env.Called() + return arguments.Get(0).(*runtime.Flags) +} + +func (env *Environment) BatteryState() (*battery.Info, error) { + args := env.Called() + return args.Get(0).(*battery.Info), args.Error(1) +} + +func (env *Environment) Shell() string { + args := env.Called() + return args.String(0) +} + +func (env *Environment) QueryWindowTitles(processName, windowTitleRegex string) (string, error) { + args := env.Called(processName, windowTitleRegex) + return args.String(0), args.Error(1) +} + +func (env *Environment) WindowsRegistryKeyValue(path string) (*runtime.WindowsRegistryValue, error) { + args := env.Called(path) + return args.Get(0).(*runtime.WindowsRegistryValue), args.Error(1) +} + +func (env *Environment) HTTPRequest(url string, _ io.Reader, _ int, _ ...http.RequestModifier) ([]byte, error) { + args := env.Called(url) + return args.Get(0).([]byte), args.Error(1) +} + +func (env *Environment) HasParentFilePath(parent string, followSymlinks bool) (*runtime.FileInfo, error) { + args := env.Called(parent, followSymlinks) + return args.Get(0).(*runtime.FileInfo), args.Error(1) +} + +func (env *Environment) StackCount() int { + args := env.Called() + return args.Int(0) +} + +func (env *Environment) IsWsl() bool { + args := env.Called() + return args.Bool(0) +} + +func (env *Environment) IsWsl2() bool { + args := env.Called() + return args.Bool(0) +} + +func (env *Environment) IsCygwin() bool { + args := env.Called() + return args.Bool(0) +} + +func (env *Environment) TerminalWidth() (int, error) { + args := env.Called() + return args.Int(0), args.Error(1) +} + +func (env *Environment) CachePath() string { + args := env.Called() + return args.String(0) +} + +func (env *Environment) Close() { + _ = env.Called() +} + +func (env *Environment) Logs() string { + args := env.Called() + return args.String(0) +} + +func (env *Environment) InWSLSharedDrive() bool { + args := env.Called() + return args.Bool(0) +} + +func (env *Environment) ConvertToWindowsPath(input string) string { + args := env.Called(input) + return args.String(0) +} + +func (env *Environment) ConvertToLinuxPath(_ string) string { + args := env.Called() + return args.String(0) +} + +func (env *Environment) Connection(connectionType runtime.ConnectionType) (*runtime.Connection, error) { + args := env.Called(connectionType) + return args.Get(0).(*runtime.Connection), args.Error(1) +} + +func (env *Environment) MockGitCommand(dir, returnValue string, args ...string) { + args = append([]string{"-C", dir, "--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...) + env.On("RunCommand", "git", args).Return(returnValue, nil) +} + +func (env *Environment) MockHgCommand(dir, returnValue string, args ...string) { + args = append([]string{"-R", dir}, args...) + env.On("RunCommand", "hg", args).Return(returnValue, nil) +} + +func (env *Environment) MockJjCommand(dir, returnValue string, args ...string) { + args = append([]string{"--repository", dir, "--no-pager", "--color", "never", "--ignore-working-copy"}, args...) + env.On("RunCommand", "jj", args).Return(returnValue, nil) +} + +func (env *Environment) MockSvnCommand(dir, returnValue string, args ...string) { + args = append([]string{"-C", dir, "--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...) + env.On("RunCommand", "svn", args).Return(returnValue, nil) +} + +func (env *Environment) HasFileInParentDirs(pattern string, depth uint) bool { + args := env.Called(pattern, depth) + return args.Bool(0) +} + +func (env *Environment) DirMatchesOneOf(dir string, regexes []string) bool { + args := env.Called(dir, regexes) + return args.Bool(0) +} + +func (env *Environment) DirIsWritable(path string) bool { + args := env.Called(path) + return args.Bool(0) +} + +func (env *Environment) CursorPosition() (int, int) { + args := env.Called() + return args.Int(0), args.Int(1) +} + +func (env *Environment) SystemInfo() (*runtime.SystemInfo, error) { + args := env.Called() + return args.Get(0).(*runtime.SystemInfo), args.Error(1) +} + +func (env *Environment) Unset(name string) { + for i := 0; i < len(env.ExpectedCalls); i++ { + f := env.ExpectedCalls[i] + if f.Method == name { + f.Unset() + } + } +} diff --git a/src/runtime/networks_windows.go b/src/runtime/networks_windows.go new file mode 100644 index 000000000000..f86561bfb719 --- /dev/null +++ b/src/runtime/networks_windows.go @@ -0,0 +1,270 @@ +package runtime + +import ( + "errors" + "strings" + "syscall" + "time" + "unsafe" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "golang.org/x/sys/windows" +) + +var ( + wlanapi = syscall.NewLazyDLL("wlanapi.dll") + hWlanOpenHandle = wlanapi.NewProc("WlanOpenHandle") + hWlanCloseHandle = wlanapi.NewProc("WlanCloseHandle") + hWlanQueryInterface = wlanapi.NewProc("WlanQueryInterface") + hWlanEnumInterfaces = wlanapi.NewProc("WlanEnumInterfaces") +) + +type MIN_IF_TABLE2 struct { + NumEntries uint64 + Table [256]MIB_IF_ROW2 +} + +const ( + IF_MAX_STRING_SIZE uint64 = 256 + IF_MAX_PHYS_ADDRESS_LENGTH uint64 = 32 +) + +type MIB_IF_ROW2 struct { + InterfaceLuid uint64 + InterfaceIndex uint32 + InterfaceGUID windows.GUID + Alias [IF_MAX_STRING_SIZE + 1]uint16 + Description [IF_MAX_STRING_SIZE + 1]uint16 + PhysicalAddressLength uint32 + PhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]uint8 + PermanentPhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]uint8 + + Mtu uint32 + Type uint32 + TunnelType uint32 + MediaType uint32 + PhysicalMediumType uint32 + AccessType uint32 + DirectionType uint32 + + InterfaceAndOperStatusFlags struct { + HardwareInterface bool + FilterInterface bool + ConnectorPresent bool + NotAuthenticated bool + NotMediaConnected bool + Paused bool + LowPower bool + EndPointInterface bool + } + + OperStatus uint32 + AdminStatus uint32 + MediaConnectState uint32 + NetworkGUID windows.GUID + ConnectionType uint32 + + TransmitLinkSpeed uint64 + ReceiveLinkSpeed uint64 + + InOctets uint64 + InUcastPkts uint64 + InNUcastPkts uint64 + InDiscards uint64 + InErrors uint64 + InUnknownProtos uint64 + InUcastOctets uint64 + InMulticastOctets uint64 + InBroadcastOctets uint64 + OutOctets uint64 + OutUcastPkts uint64 + OutNUcastPkts uint64 + OutDiscards uint64 + OutErrors uint64 + OutUcastOctets uint64 + OutMulticastOctets uint64 + OutBroadcastOctets uint64 + OutQLen uint64 +} + +//nolint:unused +type WLAN_INTERFACE_INFO_LIST struct { + dwNumberOfItems uint32 + dwIndex uint32 + InterfaceInfo [1]WLAN_INTERFACE_INFO +} + +type WLAN_INTERFACE_INFO struct { + InterfaceGuid syscall.GUID + strInterfaceDescription [256]uint16 + isState uint32 +} + +const ( + WLAN_MAX_NAME_LENGTH int64 = 256 + DOT11_SSID_MAX_LENGTH int64 = 32 +) + +//nolint:unused +type WLAN_CONNECTION_ATTRIBUTES struct { + isState uint32 + wlanConnectionMode uint32 + strProfileName [WLAN_MAX_NAME_LENGTH]uint16 + wlanAssociationAttributes WLAN_ASSOCIATION_ATTRIBUTES + wlanSecurityAttributes WLAN_SECURITY_ATTRIBUTES +} + +//nolint:unused +type WLAN_ASSOCIATION_ATTRIBUTES struct { + dot11Ssid DOT11_SSID + dot11BssType uint32 + dot11Bssid [6]uint8 + dot11PhyType uint32 + uDot11PhyIndex uint32 + wlanSignalQuality uint32 + ulRxRate uint32 + ulTxRate uint32 +} + +//nolint:unused +type WLAN_SECURITY_ATTRIBUTES struct { + bSecurityEnabled uint32 + bOneXEnabled uint32 + dot11AuthAlgorithm uint32 + dot11CipherAlgorithm uint32 +} + +type DOT11_SSID struct { + uSSIDLength uint32 + ucSSID [DOT11_SSID_MAX_LENGTH]uint8 +} + +func (term *Terminal) getConnections() []*Connection { + var pIFTable2 *MIN_IF_TABLE2 + _, _, _ = hGetIfTable2.Call(uintptr(unsafe.Pointer(&pIFTable2))) + + networks := make([]*Connection, 0) + + for i := 0; i < int(pIFTable2.NumEntries); i++ { + networkInterface := pIFTable2.Table[i] + alias := strings.TrimRight(syscall.UTF16ToString(networkInterface.Alias[:]), "\x00") + + if networkInterface.OperStatus != 1 || // not connected or functional + !networkInterface.InterfaceAndOperStatusFlags.HardwareInterface || // rule out software interfaces + strings.HasPrefix(alias, "Local Area Connection") || // not relevant + strings.Index(alias, "-") >= 3 { // rule out parts of Ethernet filter interfaces + // e.g. : "Ethernet-WFP Native MAC Layer LightWeight Filter-0000" + continue + } + + var connectionType ConnectionType + var ssid string + switch networkInterface.Type { + case 6: + connectionType = ETHERNET + case 237, 234, 244: + connectionType = CELLULAR + } + + if networkInterface.PhysicalMediumType == 10 { + connectionType = BLUETOOTH + } + + // skip connections which aren't relevant + if connectionType == "" { + continue + } + + log.Debugf("Found network interface: %s", alias) + + network := &Connection{ + Type: connectionType, + Name: alias, + TransmitRate: networkInterface.TransmitLinkSpeed, + ReceiveRate: networkInterface.ReceiveLinkSpeed, + SSID: ssid, + } + + networks = append(networks, network) + } + + wifi, err := term.wifiNetwork() + if err == nil { + networks = append(networks, wifi) + return networks + } + + log.Error(err) + + return networks +} + +func (term *Terminal) wifiNetwork() (*Connection, error) { + defer log.Trace(time.Now()) + // Open handle + var pdwNegotiatedVersion uint32 + var phClientHandle uint32 + e, _, err := hWlanOpenHandle.Call(uintptr(uint32(2)), uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&pdwNegotiatedVersion)), uintptr(unsafe.Pointer(&phClientHandle))) + if e != 0 { + return nil, err + } + + defer func() { + _, _, _ = hWlanCloseHandle.Call(uintptr(phClientHandle), uintptr(unsafe.Pointer(nil))) + }() + + // list interfaces + var interfaceList *WLAN_INTERFACE_INFO_LIST + e, _, err = hWlanEnumInterfaces.Call(uintptr(phClientHandle), uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&interfaceList))) + if e != 0 { + return nil, err + } + + // use first interface that is connected + numberOfInterfaces := int(interfaceList.dwNumberOfItems) + infoSize := unsafe.Sizeof(interfaceList.InterfaceInfo[0]) + for i := range numberOfInterfaces { + network := (*WLAN_INTERFACE_INFO)(unsafe.Add(unsafe.Pointer(&interfaceList.InterfaceInfo[0]), uintptr(i)*infoSize)) + if network.isState != 1 { + log.Debug("Skipping non-connected wifi interface") + continue + } + + return term.parseNetworkInterface(network, phClientHandle) + } + + return nil, errors.New("not connected") +} + +func (term *Terminal) parseNetworkInterface(network *WLAN_INTERFACE_INFO, clientHandle uint32) (*Connection, error) { + info := Connection{ + Type: WIFI, + } + + // Query wifi connection state + var dataSize uint32 + var wlanAttr *WLAN_CONNECTION_ATTRIBUTES + e, _, err := hWlanQueryInterface.Call(uintptr(clientHandle), + uintptr(unsafe.Pointer(&network.InterfaceGuid)), + uintptr(7), // wlan_intf_opcode_current_connection + uintptr(unsafe.Pointer(nil)), + uintptr(unsafe.Pointer(&dataSize)), + uintptr(unsafe.Pointer(&wlanAttr)), + uintptr(unsafe.Pointer(nil))) + if e != 0 { + return &info, err + } + + // SSID + ssid := wlanAttr.wlanAssociationAttributes.dot11Ssid + if ssid.uSSIDLength > 0 { + info.SSID = string(ssid.ucSSID[0:ssid.uSSIDLength]) + info.Name = info.SSID + log.Debugf("Found wifi interface: %s", info.SSID) + } + + info.TransmitRate = uint64(wlanAttr.wlanAssociationAttributes.ulTxRate / 1024) + info.ReceiveRate = uint64(wlanAttr.wlanAssociationAttributes.ulRxRate / 1024) + + return &info, nil +} diff --git a/src/runtime/path/clean.go b/src/runtime/path/clean.go new file mode 100644 index 000000000000..a9a3b8bdefa9 --- /dev/null +++ b/src/runtime/path/clean.go @@ -0,0 +1,127 @@ +package path + +import ( + "fmt" + "path/filepath" + "runtime" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/text" +) + +// Base returns the last element of path. +// Trailing path separators are removed before extracting the last element. +// If the path consists entirely of separators, Base returns a single separator. +func Base(input string) string { + volumeName := filepath.VolumeName(input) + // Strip trailing slashes. + for len(input) > 0 && IsSeparator(input[len(input)-1]) { + input = input[0 : len(input)-1] + } + + if input == "" { + return Separator() + } + + if volumeName == input { + return input + } + + // Throw away volume name + input = input[len(filepath.VolumeName(input)):] + // Find the last element + i := len(input) - 1 + for i >= 0 && !IsSeparator(input[i]) { + i-- + } + + if i >= 0 { + input = input[i+1:] + } + + // If empty now, it had only slashes. + if input == "" { + return Separator() + } + + return input +} + +func Clean(input string) string { + if input == "" { + return input + } + + cleaned := input + separator := Separator() + + // The prefix can be empty for a relative path. + var prefix string + if IsSeparator(cleaned[0]) { + prefix = separator + } + + if runtime.GOOS == windows { + // Normalize (forward) slashes to backslashes on Windows. + cleaned = strings.ReplaceAll(cleaned, "/", `\`) + + // Clean the prefix for a UNC path, if any. + if regex.MatchString(`^\\{2}[^\\]+`, cleaned) { + cleaned = strings.TrimPrefix(cleaned, `\\.\UNC\`) + if cleaned == "" { + return cleaned + } + prefix = `\\` + } + + // Always use an uppercase drive letter on Windows. + driveLetter, err := regex.GetCompiledRegex(`^[a-z]:`) + if err == nil { + cleaned = driveLetter.ReplaceAllStringFunc(cleaned, strings.ToUpper) + } + } + + sb := text.NewBuilder() + + sb.WriteString(prefix) + + // Clean slashes. + matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P[^\%s]+)`, separator), cleaned) + n := len(matches) - 1 + for i, m := range matches { + sb.WriteString(m["element"]) + if i != n { + sb.WriteString(separator) + } + } + + return sb.String() +} + +func ReplaceHomeDirPrefixWithTilde(path string) string { + home := Home() + if !strings.HasPrefix(path, home) { + return path + } + + rem := path[len(home):] + if rem == "" || IsSeparator(rem[0]) { + return "~" + rem + } + + return path +} + +func ReplaceTildePrefixWithHomeDir(path string) string { + if !strings.HasPrefix(path, "~") { + return path + } + + rem := path[1:] + if rem == "" || IsSeparator(rem[0]) { + return Home() + rem + } + + return path +} diff --git a/src/runtime/path/home.go b/src/runtime/path/home.go new file mode 100644 index 000000000000..89f838b89b5e --- /dev/null +++ b/src/runtime/path/home.go @@ -0,0 +1,27 @@ +package path + +import ( + "os" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +func Home() string { + home := os.Getenv("HOME") + defer func() { + log.Debug(home) + }() + + if len(home) > 0 { + return home + } + + // fallback to older implementations on Windows + home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + + if home == "" { + home = os.Getenv("USERPROFILE") + } + + return home +} diff --git a/src/runtime/path/separator.go b/src/runtime/path/separator.go new file mode 100644 index 000000000000..fe5f0c85bdd0 --- /dev/null +++ b/src/runtime/path/separator.go @@ -0,0 +1,34 @@ +package path + +import ( + "runtime" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +const ( + windows = "windows" +) + +func Separator() string { + defer log.Trace(time.Now()) + + if runtime.GOOS == windows { + return `\` + } + + return "/" +} + +func IsSeparator(c uint8) bool { + if c == '/' { + return true + } + + if runtime.GOOS == windows && c == '\\' { + return true + } + + return false +} diff --git a/src/runtime/terminal.go b/src/runtime/terminal.go new file mode 100644 index 000000000000..ffbe9d177b7f --- /dev/null +++ b/src/runtime/terminal.go @@ -0,0 +1,591 @@ +package runtime + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + httplib "net/http" + "net/http/httputil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/maps" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/cmd" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" + + disk "github.com/shirou/gopsutil/v4/disk" + load "github.com/shirou/gopsutil/v4/load" + process "github.com/shirou/gopsutil/v4/process" +) + +type Terminal struct { + CmdFlags *Flags + cmdCache *cache.Command + lsDirMap *maps.Concurrent[[]fs.DirEntry] + cwd string + host string + networks []*Connection +} + +func (term *Terminal) Init(flags *Flags) { + defer log.Trace(time.Now()) + + term.CmdFlags = flags + + if term.CmdFlags == nil { + term.CmdFlags = &Flags{} + } + + term.lsDirMap = maps.NewConcurrent[[]fs.DirEntry]() + + term.setPromptCount() + + term.setPwd() + + term.cmdCache = &cache.Command{ + Commands: maps.NewConcurrent[string](), + } +} + +func (term *Terminal) Getenv(key string) string { + defer log.Trace(time.Now(), key) + val := os.Getenv(key) + log.Debug(val) + return val +} + +func (term *Terminal) Pwd() string { + return term.cwd +} + +func (term *Terminal) setPwd() { + defer log.Trace(time.Now()) + + correctPath := func(pwd string) string { + if term.GOOS() != WINDOWS { + return pwd + } + + // on Windows, and being case sensitive and not consistent and all, this gives silly issues + driveLetter, err := regex.GetCompiledRegex(`^[a-z]:`) + if err == nil { + return driveLetter.ReplaceAllStringFunc(pwd, strings.ToUpper) + } + + return pwd + } + + if term.CmdFlags != nil && term.CmdFlags.PWD != "" { + term.cwd = path.Clean(term.CmdFlags.PWD) + log.Debug(term.cwd) + return + } + + dir, err := os.Getwd() + if err != nil { + log.Error(err) + return + } + + term.cwd = correctPath(dir) + log.Debug(term.cwd) +} + +func (term *Terminal) HasFiles(pattern string) bool { + return term.HasFilesInDir(term.Pwd(), pattern) +} + +func (term *Terminal) HasFilesInDir(dir, pattern string) bool { + defer log.Trace(time.Now(), pattern) + + fileSystem := os.DirFS(dir) + var dirEntries []fs.DirEntry + + if files, OK := term.lsDirMap.Get(dir); OK { + dirEntries = files + } + + if len(dirEntries) == 0 { + var err error + dirEntries, err = fs.ReadDir(fileSystem, ".") + if err != nil { + log.Error(err) + log.Debug("false") + return false + } + + term.lsDirMap.Set(dir, dirEntries) + } + + pattern = strings.ToLower(pattern) + + for _, match := range dirEntries { + if match.IsDir() { + continue + } + + matchFileName, err := filepath.Match(pattern, strings.ToLower(match.Name())) + if err != nil { + log.Error(err) + log.Debug("false") + return false + } + + if matchFileName { + log.Debug("true") + return true + } + } + + log.Debug("false") + return false +} + +func (term *Terminal) HasFileInParentDirs(pattern string, depth uint) bool { + defer log.Trace(time.Now(), pattern, fmt.Sprint(depth)) + currentFolder := term.Pwd() + + for c := 0; c < int(depth); c++ { + if term.HasFilesInDir(currentFolder, pattern) { + log.Debug("true") + return true + } + + if dir := filepath.Dir(currentFolder); dir != currentFolder { + currentFolder = dir + } else { + log.Debug("false") + return false + } + } + log.Debug("false") + return false +} + +func (term *Terminal) HasFolder(folder string) bool { + defer log.Trace(time.Now(), folder) + f, err := os.Stat(folder) + if err != nil { + log.Debug("false") + return false + } + isDir := f.IsDir() + log.Debugf("%t", isDir) + return isDir +} + +func (term *Terminal) ResolveSymlink(input string) (string, error) { + defer log.Trace(time.Now(), input) + link, err := filepath.EvalSymlinks(input) + if err != nil { + log.Error(err) + return "", err + } + log.Debug(link) + return link, nil +} + +func (term *Terminal) FileContent(file string) string { + defer log.Trace(time.Now(), file) + if !filepath.IsAbs(file) { + file = filepath.Join(term.Pwd(), file) + } + + content, err := os.ReadFile(file) + if err != nil { + log.Error(err) + return "" + } + + fileContent := string(content) + log.Debug(fileContent) + + return fileContent +} + +func (term *Terminal) LsDir(input string) []fs.DirEntry { + defer log.Trace(time.Now(), input) + + entries, err := os.ReadDir(input) + if err != nil { + log.Error(err) + return nil + } + + log.Debugf("%v", entries) + return entries +} + +func (term *Terminal) User() string { + defer log.Trace(time.Now()) + user := os.Getenv("USER") + if user == "" { + user = os.Getenv("USERNAME") + } + log.Debug(user) + return user +} + +func (term *Terminal) Host() (string, error) { + defer log.Trace(time.Now()) + if len(term.host) != 0 { + return term.host, nil + } + + hostName, err := os.Hostname() + if err != nil { + log.Error(err) + return "", err + } + + hostName = cleanHostName(hostName) + log.Debug(hostName) + term.host = hostName + + return hostName, nil +} + +func (term *Terminal) GOOS() string { + defer log.Trace(time.Now()) + return runtime.GOOS +} + +func (term *Terminal) Home() string { + return path.Home() +} + +func (term *Terminal) RunCommand(command string, args ...string) (string, error) { + defer log.Trace(time.Now(), append([]string{command}, args...)...) + + if cacheCommand, ok := term.cmdCache.Get(command); ok { + command = cacheCommand + } + + output, err := cmd.Run(command, args...) + if err != nil { + log.Error(err) + } + + log.Debug(output) + return output, err +} + +func (term *Terminal) RunShellCommand(shell, command string) string { + defer log.Trace(time.Now()) + + if out, err := term.RunCommand(shell, "-c", command); err == nil { + return out + } + + return "" +} + +func (term *Terminal) CommandPath(command string) string { + defer log.Trace(time.Now(), command) + if cmdPath, ok := term.cmdCache.Get(command); ok { + log.Debug(cmdPath) + return cmdPath + } + + cmdPath, err := exec.LookPath(command) + if err == nil { + term.cmdCache.Set(command, cmdPath) + log.Debug(cmdPath) + return cmdPath + } + + log.Error(err) + return "" +} + +func (term *Terminal) HasCommand(command string) bool { + defer log.Trace(time.Now(), command) + + if cmdPath := term.CommandPath(command); cmdPath != "" { + return true + } + + return false +} + +func (term *Terminal) StatusCodes() (int, string) { + defer log.Trace(time.Now()) + + if term.CmdFlags.Shell != CMD || !term.CmdFlags.NoExitCode { + return term.CmdFlags.ErrorCode, term.CmdFlags.PipeStatus + } + + errorCode := term.Getenv("=ExitCode") + log.Debug(errorCode) + term.CmdFlags.ErrorCode, _ = strconv.Atoi(errorCode) + + return term.CmdFlags.ErrorCode, term.CmdFlags.PipeStatus +} + +func (term *Terminal) ExecutionTime() float64 { + defer log.Trace(time.Now()) + if term.CmdFlags.ExecutionTime < 0 { + return 0 + } + return term.CmdFlags.ExecutionTime +} + +func (term *Terminal) Flags() *Flags { + defer log.Trace(time.Now()) + return term.CmdFlags +} + +func (term *Terminal) Shell() string { + defer log.Trace(time.Now()) + if len(term.CmdFlags.Shell) != 0 { + return term.CmdFlags.Shell + } + + log.Debug("no shell name provided in flags, trying to detect it") + + pid := os.Getppid() + p, _ := process.NewProcess(int32(pid)) + + name, err := p.Name() + if err != nil { + log.Error(err) + return UNKNOWN + } + + log.Debug("process name: " + name) + + // Cache the shell value to speed things up. + term.CmdFlags.Shell = strings.Trim(strings.TrimSuffix(name, ".exe"), " ") + return term.CmdFlags.Shell +} + +func (term *Terminal) unWrapError(err error) error { + cause := err + for { + type nested interface{ Unwrap() error } + unwrap, ok := cause.(nested) + if !ok { + break + } + cause = unwrap.Unwrap() + } + return cause +} + +func (term *Terminal) HTTPRequest(targetURL string, body io.Reader, timeout int, requestModifiers ...http.RequestModifier) ([]byte, error) { + defer log.Trace(time.Now(), targetURL) + + ctx, cncl := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) + defer cncl() + + request, err := httplib.NewRequestWithContext(ctx, httplib.MethodGet, targetURL, body) + if err != nil { + return nil, err + } + + for _, modifier := range requestModifiers { + modifier(request) + } + + if term.CmdFlags.Debug { + dump, _ := httputil.DumpRequestOut(request, true) + log.Debug(string(dump)) + } + + response, err := http.HTTPClient.Do(request) + if err != nil { + log.Error(err) + return nil, term.unWrapError(err) + } + + // anything inside the range [200, 299] is considered a success + if response.StatusCode < 200 || response.StatusCode >= 300 { + err := &http.Error{ + StatusCode: response.StatusCode, + } + log.Error(err) + return nil, err + } + + defer response.Body.Close() + + responseBody, err := io.ReadAll(response.Body) + if err != nil { + log.Error(err) + return nil, err + } + + log.Debug(string(responseBody)) + + return responseBody, nil +} + +func (term *Terminal) HasParentFilePath(parent string, followSymlinks bool) (*FileInfo, error) { + defer log.Trace(time.Now(), parent) + + pwd := term.Pwd() + if followSymlinks { + if actual, err := term.ResolveSymlink(pwd); err == nil { + pwd = actual + } + } + + for { + fileSystem := os.DirFS(pwd) + info, err := fs.Stat(fileSystem, parent) + if err == nil { + return &FileInfo{ + ParentFolder: pwd, + Path: filepath.Join(pwd, parent), + IsDir: info.IsDir(), + }, nil + } + + if !os.IsNotExist(err) { + return nil, err + } + + if dir := filepath.Dir(pwd); dir != pwd { + pwd = dir + continue + } + + log.Error(err) + return nil, errors.New("no match at root level") + } +} + +func (term *Terminal) StackCount() int { + defer log.Trace(time.Now()) + + if term.CmdFlags.StackCount < 0 { + return 0 + } + + return term.CmdFlags.StackCount +} + +func (term *Terminal) Logs() string { + return log.String() +} + +func (term *Terminal) DirMatchesOneOf(dir string, regexes []string) (match bool) { + // sometimes the function panics inside golang, we want to silence that error + // and assume that there's no match. Not perfect, but better than crashing + // for the time being until we figure out what the actual root cause is + defer func() { + if err := recover(); err != nil { + log.Error(errors.New("panic")) + match = false + } + }() + match = dirMatchesOneOf(dir, term.Home(), term.GOOS(), regexes) + return +} + +func dirMatchesOneOf(dir, home, goos string, regexes []string) bool { + if len(regexes) == 0 { + return false + } + + if goos == WINDOWS { + dir = strings.ReplaceAll(dir, "\\", "/") + home = strings.ReplaceAll(home, "\\", "/") + } + + for _, element := range regexes { + normalized := strings.ReplaceAll(element, "\\\\", "/") + if strings.HasPrefix(normalized, "~") { + rem := normalized[1:] + if rem == "" || rem[0] == '/' { + normalized = home + rem + } + } + pattern := fmt.Sprintf("^%s$", normalized) + if goos == WINDOWS || goos == DARWIN { + pattern = "(?i)" + pattern + } + matched := regex.MatchString(pattern, dir) + if matched { + return true + } + } + return false +} + +func (term *Terminal) setPromptCount() { + defer log.Trace(time.Now()) + + var count int + if val, found := cache.Get[int](cache.Session, cache.PROMPTCOUNTCACHE); found { + count = val + } + + // Only update the count if we're generating a primary prompt. + if term.CmdFlags.Type == PRIMARY { + count++ + cache.Set(cache.Session, cache.PROMPTCOUNTCACHE, count, cache.ONEDAY) + } + + term.CmdFlags.PromptCount = count +} + +func (term *Terminal) CursorPosition() (row, col int) { + if number, err := strconv.Atoi(term.Getenv("POSH_CURSOR_LINE")); err == nil { + row = number + } + + if number, err := strconv.Atoi(term.Getenv("POSH_CURSOR_COLUMN")); err != nil { + col = number + } + + return +} + +func (term *Terminal) SystemInfo() (*SystemInfo, error) { + s := &SystemInfo{} + + mem, err := term.Memory() + if err != nil { + return nil, err + } + s.Memory = *mem + + loadStat, err := load.Avg() + if err == nil { + s.Load1 = loadStat.Load1 + s.Load5 = loadStat.Load5 + s.Load15 = loadStat.Load15 + } + + diskIO, err := disk.IOCounters() + if err == nil { + s.Disks = diskIO + } + return s, nil +} + +func cleanHostName(hostName string) string { + garbage := []string{ + ".lan", + ".local", + ".localdomain", + } + for _, g := range garbage { + if strings.HasSuffix(hostName, g) { + hostName = strings.Replace(hostName, g, "", 1) + } + } + return hostName +} diff --git a/src/runtime/terminal_darwin.go b/src/runtime/terminal_darwin.go new file mode 100644 index 000000000000..fcedf290c58d --- /dev/null +++ b/src/runtime/terminal_darwin.go @@ -0,0 +1,64 @@ +package runtime + +import ( + "errors" + "strconv" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/battery" +) + +func mapMostLogicalState(state string) battery.State { + switch state { + case "charging": + return battery.Charging + case "discharging": + return battery.Discharging + case "AC attached": + return battery.NotCharging + case "full": + return battery.Full + case "empty": + return battery.Empty + case "charged": + return battery.Full + default: + return battery.Unknown + } +} + +func (term *Terminal) parseBatteryOutput(output string) (*battery.Info, error) { + matches := regex.FindNamedRegexMatch(`(?P[0-9]{1,3})%; (?P[a-zA-Z\s]+);`, output) + if len(matches) != 2 { + err := errors.New("unable to find battery state based on output") + log.Error(err) + return nil, err + } + var percentage int + var err error + if percentage, err = strconv.Atoi(matches["PERCENTAGE"]); err != nil { + log.Error(err) + return nil, errors.New("unable to parse battery percentage") + } + return &battery.Info{ + Percentage: percentage, + State: mapMostLogicalState(matches["STATE"]), + }, nil +} + +func (term *Terminal) BatteryState() (*battery.Info, error) { + defer log.Trace(time.Now()) + output, err := term.RunCommand("pmset", "-g", "batt") + if err != nil { + log.Error(err) + return nil, err + } + if !strings.Contains(output, "Battery") { + return nil, errors.New("no battery found") + } + return term.parseBatteryOutput(output) +} diff --git a/src/runtime/terminal_test.go b/src/runtime/terminal_test.go new file mode 100644 index 000000000000..316162fe2f75 --- /dev/null +++ b/src/runtime/terminal_test.go @@ -0,0 +1,76 @@ +package runtime + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNormalHostName(t *testing.T) { + hostName := "hello" + assert.Equal(t, hostName, cleanHostName(hostName)) +} + +func TestHostNameWithLocal(t *testing.T) { + hostName := "hello.local" + assert.Equal(t, "hello", cleanHostName(hostName)) +} + +func TestHostNameWithLan(t *testing.T) { + hostName := "hello.lan" + cleanHostName := cleanHostName(hostName) + assert.Equal(t, "hello", cleanHostName) +} + +func TestDirMatchesOneOf(t *testing.T) { + cases := []struct { + GOOS string + HomeDir string + Dir string + Pattern string + Expected bool + }{ + {GOOS: LINUX, HomeDir: "/home/bill", Dir: "/home/bill", Pattern: "/home/bill", Expected: true}, + {GOOS: LINUX, HomeDir: "/home/bill", Dir: "/home/bill/foo", Pattern: "~/foo", Expected: true}, + {GOOS: LINUX, HomeDir: "/home/bill", Dir: "/home/bill/foo", Pattern: "~/Foo", Expected: false}, + {GOOS: LINUX, HomeDir: "/home/bill", Dir: "/home/bill/foo", Pattern: "~\\\\foo", Expected: true}, + {GOOS: LINUX, HomeDir: "/home/bill", Dir: "/home/bill/foo/bar", Pattern: "~/fo.*", Expected: true}, + {GOOS: LINUX, HomeDir: "/home/bill", Dir: "/home/bill/foo", Pattern: "~/fo\\w", Expected: true}, + + {GOOS: WINDOWS, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill", Pattern: "C:\\\\Users\\\\Bill", Expected: true}, + {GOOS: WINDOWS, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill", Pattern: "C:/Users/Bill", Expected: true}, + {GOOS: WINDOWS, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill", Pattern: "c:/users/bill", Expected: true}, + {GOOS: WINDOWS, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill", Pattern: "~", Expected: true}, + {GOOS: WINDOWS, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill\\Foo", Pattern: "~/Foo", Expected: true}, + {GOOS: WINDOWS, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill\\Foo", Pattern: "~/foo", Expected: true}, + {GOOS: WINDOWS, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill\\Foo\\Bar", Pattern: "~/fo.*", Expected: true}, + {GOOS: WINDOWS, HomeDir: "C:\\Users\\Bill", Dir: "C:\\Users\\Bill\\Foo", Pattern: "~/fo\\w", Expected: true}, + } + + for _, tc := range cases { + got := dirMatchesOneOf(tc.Dir, tc.HomeDir, tc.GOOS, []string{tc.Pattern}) + assert.Equal(t, tc.Expected, got) + } +} + +func TestDirMatchesOneOfRegexInverted(t *testing.T) { + // detect panic(thrown by MustCompile) + defer func() { + if err := recover(); err != nil { + // display a message explaining omp failed(with the err) + assert.Equal(t, "regexp: Compile(`^(?!Projects[\\/]).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err) + } + }() + _ = dirMatchesOneOf("Projects/oh-my-posh", "", LINUX, []string{"(?!Projects[\\/]).*"}) +} + +func TestDirMatchesOneOfRegexInvertedNonEscaped(t *testing.T) { + // detect panic(thrown by MustCompile) + defer func() { + if err := recover(); err != nil { + // display a message explaining omp failed(with the err) + assert.Equal(t, "regexp: Compile(`^(?!Projects/).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err) + } + }() + _ = dirMatchesOneOf("Projects/oh-my-posh", "", LINUX, []string{"(?!Projects/).*"}) +} diff --git a/src/runtime/terminal_unix.go b/src/runtime/terminal_unix.go new file mode 100644 index 000000000000..3bd35b0e1a8b --- /dev/null +++ b/src/runtime/terminal_unix.go @@ -0,0 +1,205 @@ +//go:build !windows + +package runtime + +import ( + "os" + "strconv" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/shirou/gopsutil/v4/host" + mem "github.com/shirou/gopsutil/v4/mem" + terminal "github.com/wayneashleyberry/terminal-dimensions" + "golang.org/x/sys/unix" +) + +func (term *Terminal) Root() bool { + defer log.Trace(time.Now()) + return os.Geteuid() == 0 +} + +func (term *Terminal) QueryWindowTitles(_, _ string) (string, error) { + return "", &NotImplemented{} +} + +func (term *Terminal) IsWsl() bool { + defer log.Trace(time.Now()) + const key = "is_wsl" + if val, found := cache.Get[bool](cache.Device, key); found { + return val + } + + var val bool + defer func() { + cache.Set(cache.Device, key, val, cache.INFINITE) + }() + + val = term.HasCommand("wslpath") + + return val +} + +func (term *Terminal) IsWsl2() bool { + defer log.Trace(time.Now()) + if !term.IsWsl() { + return false + } + uname := term.FileContent("/proc/sys/kernel/osrelease") + return strings.Contains(uname, "WSL2") +} + +func (term *Terminal) IsCygwin() bool { + defer log.Trace(time.Now()) + return false +} + +func (term *Terminal) TerminalWidth() (int, error) { + defer log.Trace(time.Now()) + + if term.CmdFlags.TerminalWidth > 0 { + log.Debugf("terminal width: %d", term.CmdFlags.TerminalWidth) + return term.CmdFlags.TerminalWidth, nil + } + + width, err := terminal.Width() + if err != nil { + log.Error(err) + } + + // fetch width from the environment variable + // in case the terminal width is not available + if width == 0 { + i, err := strconv.Atoi(term.Getenv("COLUMNS")) + if err != nil { + log.Error(err) + } + width = uint(i) + } + + term.CmdFlags.TerminalWidth = int(width) + log.Debugf("terminal width: %d", term.CmdFlags.TerminalWidth) + + // Claude CLI has a 2 character padding on both sides + if term.CmdFlags.Shell == "claude" { + log.Debug("adjusting terminal width for Claude CLI") + term.CmdFlags.TerminalWidth -= 4 + } + + return term.CmdFlags.TerminalWidth, err +} + +func (term *Terminal) Platform() string { + const key = "environment_platform" + if val, found := cache.Get[string](cache.Device, key); found { + return val + } + + var platform string + defer func() { + cache.Set(cache.Device, key, platform, cache.INFINITE) + }() + + if wsl := term.Getenv("WSL_DISTRO_NAME"); len(wsl) != 0 { + platform, _, _ = strings.Cut(wsl, "-") + platform = strings.ToLower(platform) + log.Debug(platform) + return platform + } + + platform, _, _, _ = host.PlatformInformation() + platform = term.getSpecialLinuxDistros(platform) + + log.Debug(platform) + return platform +} + +func (term *Terminal) getSpecialLinuxDistros(platform string) string { + lsbInfo := term.FileContent("/etc/lsb-release") + + if platform == "arch" && strings.Contains(strings.ToLower(lsbInfo), "manjaro") { + // validate for Manjaro + return "manjaro" + } + + if platform == "debian" && strings.Contains(strings.ToLower(lsbInfo), "zorin") { + // validate for Zorin OS + return "zorin" + } + + return platform +} + +func (term *Terminal) WindowsRegistryKeyValue(_ string) (*WindowsRegistryValue, error) { + return nil, &NotImplemented{} +} + +func (term *Terminal) InWSLSharedDrive() bool { + if !term.IsWsl2() { + return false + } + windowsPath := term.ConvertToWindowsPath(term.Pwd()) + return !strings.HasPrefix(windowsPath, `//wsl.localhost/`) && !strings.HasPrefix(windowsPath, `//wsl$/`) +} + +func (term *Terminal) ConvertToWindowsPath(input string) string { + windowsPath, err := term.RunCommand("wslpath", "-m", input) + if err == nil { + return windowsPath + } + return input +} + +func (term *Terminal) ConvertToLinuxPath(input string) string { + if linuxPath, err := term.RunCommand("wslpath", "-u", input); err == nil { + return linuxPath + } + return input +} + +func (term *Terminal) DirIsWritable(input string) bool { + defer log.Trace(time.Now(), input) + return unix.Access(input, unix.W_OK) == nil +} + +func (term *Terminal) Connection(_ ConnectionType) (*Connection, error) { + // added to disable the linting error, we can implement this later + if len(term.networks) == 0 { + return nil, &NotImplemented{} + } + + return nil, &NotImplemented{} +} + +func (term *Terminal) Memory() (*Memory, error) { + m := &Memory{} + memStat, err := mem.VirtualMemory() + if err != nil { + log.Error(err) + return nil, err + } + + m.PhysicalTotalMemory = memStat.Total + m.PhysicalAvailableMemory = memStat.Available + m.PhysicalFreeMemory = memStat.Free + + if memStat.Total > 0 { + used := float64(memStat.Total) - float64(memStat.Available) + if used < 0 { + used = 0 + } + m.PhysicalPercentUsed = used / float64(memStat.Total) * 100 + } + + swapStat, err := mem.SwapMemory() + if err != nil { + log.Error(err) + } + + m.SwapTotalMemory = swapStat.Total + m.SwapFreeMemory = swapStat.Free + m.SwapPercentUsed = swapStat.UsedPercent + return m, nil +} diff --git a/src/runtime/terminal_unix_test.go b/src/runtime/terminal_unix_test.go new file mode 100644 index 000000000000..7b88c73f0541 --- /dev/null +++ b/src/runtime/terminal_unix_test.go @@ -0,0 +1,66 @@ +//go:build !windows + +package runtime + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMemoryPercentageCalculation(t *testing.T) { + cases := []struct { + Name string + Total uint64 + Available uint64 + ExpectedPercent float64 + }{ + { + Name: "50% usage", + Total: 8 * 1024 * 1024 * 1024, + Available: 4 * 1024 * 1024 * 1024, + ExpectedPercent: 50.0, + }, + { + Name: "37% usage (from issue)", + Total: 8079691776, + Available: 5093384192, + ExpectedPercent: 36.96, + }, + { + Name: "25% usage", + Total: 16 * 1024 * 1024 * 1024, + Available: 12 * 1024 * 1024 * 1024, + ExpectedPercent: 25.0, + }, + { + Name: "75% usage", + Total: 8 * 1024 * 1024 * 1024, + Available: 2 * 1024 * 1024 * 1024, + ExpectedPercent: 75.0, + }, + { + Name: "0% usage", + Total: 8 * 1024 * 1024 * 1024, + Available: 8 * 1024 * 1024 * 1024, + ExpectedPercent: 0.0, + }, + { + Name: "100% usage", + Total: 8 * 1024 * 1024 * 1024, + Available: 0, + ExpectedPercent: 100.0, + }, + } + + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + var percentUsed float64 + if tc.Total > 0 { + percentUsed = float64(tc.Total-tc.Available) / float64(tc.Total) * 100 + } + + assert.InDelta(t, tc.ExpectedPercent, percentUsed, 0.01, tc.Name) + }) + } +} diff --git a/src/runtime/terminal_windows.go b/src/runtime/terminal_windows.go new file mode 100644 index 000000000000..e4fb5dc8a652 --- /dev/null +++ b/src/runtime/terminal_windows.go @@ -0,0 +1,244 @@ +package runtime + +import ( + "errors" + "fmt" + "strings" + "syscall" + "time" + + "github.com/Azure/go-ansiterm/winterm" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +func (term *Terminal) Root() bool { + defer log.Trace(time.Now()) + var sid *windows.SID + + // Although this looks scary, it is directly copied from the + // official windows documentation. The Go API for this is a + // direct wrap around the official C++ API. + // See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-checktokenmembership + err := windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, + 2, + windows.SECURITY_BUILTIN_DOMAIN_RID, + windows.DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid) + if err != nil { + log.Error(err) + return false + } + defer func() { + _ = windows.FreeSid(sid) + }() + + // This appears to cast a null pointer so I'm not sure why this + // works, but this guy says it does and it Works for Me™: + // https://github.com/golang/go/issues/28804#issuecomment-438838144 + token := windows.Token(0) + + member, err := token.IsMember(sid) + if err != nil { + log.Error(err) + return false + } + + return member +} + +func (term *Terminal) QueryWindowTitles(processName, windowTitleRegex string) (string, error) { + defer log.Trace(time.Now(), windowTitleRegex) + title, err := queryWindowTitles(processName, windowTitleRegex) + if err != nil { + log.Error(err) + } + return title, err +} + +func (term *Terminal) IsWsl() bool { + defer log.Trace(time.Now()) + return false +} + +func (term *Terminal) IsWsl2() bool { + defer log.Trace(time.Now()) + return false +} + +func (term *Terminal) IsCygwin() bool { + defer log.Trace(time.Now()) + return len(term.Getenv("OSTYPE")) != 0 || term.Getenv("BROWSER") == "cygstart" +} + +func (term *Terminal) TerminalWidth() (int, error) { + defer log.Trace(time.Now()) + + if term.CmdFlags.TerminalWidth > 0 { + log.Debugf("terminal width: %d", term.CmdFlags.TerminalWidth) + return term.CmdFlags.TerminalWidth, nil + } + + handle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0) + if err != nil { + log.Error(err) + return 0, err + } + + info, err := winterm.GetConsoleScreenBufferInfo(uintptr(handle)) + if err != nil { + log.Error(err) + return 0, err + } + + term.CmdFlags.TerminalWidth = int(info.Size.X) + log.Debugf("terminal width: %d", term.CmdFlags.TerminalWidth) + + // Claude CLI has a 2 character padding on both sides + if term.CmdFlags.Shell == "claude" { + log.Debug("adjusting terminal width for Claude CLI") + term.CmdFlags.TerminalWidth -= 4 + } + + return term.CmdFlags.TerminalWidth, nil +} + +func (term *Terminal) Platform() string { + return WINDOWS +} + +// Takes a registry path to a key like +// +// "HKLM\Software\Microsoft\Windows NT\CurrentVersion\EditionID" +// +// The last part of the path is the key to retrieve. +// +// If the path ends in "\", the "(Default)" key in that path is retrieved. +// +// Returns a variant type if successful; nil and an error if not. +func (term *Terminal) WindowsRegistryKeyValue(input string) (*WindowsRegistryValue, error) { + defer log.Trace(time.Now(), input) + + // Format: + // "HKLM\Software\Microsoft\Windows NT\CurrentVersion\EditionID" + // 1 | 2 | 3 + // + // Split into: + // + // 1. Root key - extract the root HKEY string and turn this into a handle to get started + // 2. Path - open this path + // 3. Key - get this key value + // + // If 3 is "" (i.e. the path ends with "\"), then get (Default) key. + // + rootKey, regPath, found := strings.Cut(input, `\`) + if !found { + err := fmt.Errorf("Error, malformed registry path: '%s'", input) + log.Error(err) + return nil, err + } + + var regKey string + if !strings.HasSuffix(regPath, `\`) { + regKey = path.Base(regPath) + if len(regKey) != 0 { + regPath = strings.TrimSuffix(regPath, `\`+regKey) + } + } + + var key registry.Key + switch rootKey { + case "HKCR", "HKEY_CLASSES_ROOT": + key = windows.HKEY_CLASSES_ROOT + case "HKCC", "HKEY_CURRENT_CONFIG": + key = windows.HKEY_CURRENT_CONFIG + case "HKCU", "HKEY_CURRENT_USER": + key = windows.HKEY_CURRENT_USER + case "HKLM", "HKEY_LOCAL_MACHINE": + key = windows.HKEY_LOCAL_MACHINE + case "HKU", "HKEY_USERS": + key = windows.HKEY_USERS + default: + err := fmt.Errorf("Error, unknown registry key: '%s", rootKey) + log.Error(err) + return nil, err + } + + k, err := registry.OpenKey(key, regPath, registry.READ) + if err != nil { + log.Error(err) + return nil, err + } + + _, valType, err := k.GetValue(regKey, nil) + if err != nil { + log.Error(err) + return nil, err + } + + var regValue *WindowsRegistryValue + + switch valType { + case windows.REG_SZ, windows.REG_EXPAND_SZ: + value, _, _ := k.GetStringValue(regKey) + regValue = &WindowsRegistryValue{ValueType: STRING, String: value} + case windows.REG_DWORD: + value, _, _ := k.GetIntegerValue(regKey) + regValue = &WindowsRegistryValue{ValueType: DWORD, DWord: value, String: fmt.Sprintf("0x%08X", value)} + case windows.REG_QWORD: + value, _, _ := k.GetIntegerValue(regKey) + regValue = &WindowsRegistryValue{ValueType: QWORD, QWord: value, String: fmt.Sprintf("0x%016X", value)} + case windows.REG_BINARY: + value, _, _ := k.GetBinaryValue(regKey) + regValue = &WindowsRegistryValue{ValueType: BINARY, String: string(value)} + } + + if regValue == nil { + errorLogMsg := fmt.Sprintf("Error, no formatter for type: %d", valType) + return nil, errors.New(errorLogMsg) + } + + log.Debug(fmt.Sprintf("%s(%s): %s", regKey, regValue.ValueType, regValue.String)) + return regValue, nil +} + +func (term *Terminal) InWSLSharedDrive() bool { + return false +} + +func (term *Terminal) ConvertToWindowsPath(input string) string { + return strings.ReplaceAll(input, `\`, "/") +} + +func (term *Terminal) ConvertToLinuxPath(input string) string { + return input +} + +func (term *Terminal) DirIsWritable(input string) bool { + defer log.Trace(time.Now()) + return term.isWriteable(input) +} + +func (term *Terminal) Connection(connectionType ConnectionType) (*Connection, error) { + if term.networks == nil { + networks := term.getConnections() + if len(networks) == 0 { + return nil, errors.New("no connections found") + } + + term.networks = networks + } + + for _, network := range term.networks { + if network.Type == connectionType { + return network, nil + } + } + + log.Error(fmt.Errorf("network type '%s' not found", connectionType)) + return nil, &NotImplemented{} +} diff --git a/src/runtime/terminal_windows_nix.go b/src/runtime/terminal_windows_nix.go new file mode 100644 index 000000000000..82a7abf24c7c --- /dev/null +++ b/src/runtime/terminal_windows_nix.go @@ -0,0 +1,20 @@ +//go:build !darwin + +package runtime + +import ( + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/battery" +) + +func (term *Terminal) BatteryState() (*battery.Info, error) { + defer log.Trace(time.Now()) + info, err := battery.Get() + if err != nil { + log.Error(err) + return nil, err + } + return info, nil +} diff --git a/src/runtime/win32_windows.go b/src/runtime/win32_windows.go new file mode 100644 index 000000000000..455795bb898f --- /dev/null +++ b/src/runtime/win32_windows.go @@ -0,0 +1,312 @@ +package runtime + +import ( + "errors" + "fmt" + "reflect" + "strings" + "syscall" + "unsafe" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + + "golang.org/x/sys/windows" +) + +// win32 specific code + +// win32 dll load and function definitions +var ( + user32 = syscall.NewLazyDLL("user32.dll") + procEnumWindows = user32.NewProc("EnumWindows") + procGetWindowTextW = user32.NewProc("GetWindowTextW") + procGetWindowThreadProcessID = user32.NewProc("GetWindowThreadProcessId") + + psapi = syscall.NewLazyDLL("psapi.dll") + getModuleBaseNameA = psapi.NewProc("GetModuleBaseNameA") + + iphlpapi = syscall.NewLazyDLL("iphlpapi.dll") + hGetIfTable2 = iphlpapi.NewProc("GetIfTable2") +) + +// enumWindows call enumWindows from user32 and returns all active windows +// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows +func enumWindows(enumFunc, lparam uintptr) (err error) { + r1, _, e1 := syscall.SyscallN(procEnumWindows.Addr(), enumFunc, lparam, 0) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +// getWindowText returns the title and text of a window from a window handle +// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextw +func getWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (length int32, err error) { + r0, _, e1 := syscall.SyscallN(procGetWindowTextW.Addr(), uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount)) + length = int32(r0) + if length == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func getWindowFileName(handle syscall.Handle) (string, error) { + var pid int + _, _, _ = procGetWindowThreadProcessID.Call(uintptr(handle), uintptr(unsafe.Pointer(&pid))) + const query = windows.PROCESS_QUERY_INFORMATION | windows.PROCESS_VM_READ + h, err := windows.OpenProcess(query, false, uint32(pid)) + if err != nil { + return "", errors.New("unable to open window process") + } + buf := [1024]byte{} + length, _, _ := getModuleBaseNameA.Call(uintptr(h), 0, uintptr(unsafe.Pointer(&buf)), 1024) + filename := string(buf[:length]) + return strings.ToLower(filename), nil +} + +// GetWindowTitle searches for a window attached to the pid +func queryWindowTitles(processName, windowTitleRegex string) (string, error) { + var title string + // callback for EnumWindows + cb := syscall.NewCallback(func(handle syscall.Handle, _ uintptr) uintptr { + fileName, err := getWindowFileName(handle) + if err != nil { + // ignore the error and continue enumeration + return 1 + } + if processName != fileName { + // ignore the error and continue enumeration + return 1 + } + b := make([]uint16, 200) + _, err = getWindowText(handle, &b[0], int32(len(b))) + if err != nil { + // ignore the error and continue enumeration + return 1 + } + title = syscall.UTF16ToString(b) + if regex.MatchString(windowTitleRegex, title) { + // will cause EnumWindows to return 0 (error) + // but we don't want to enumerate all windows since we got what we want + return 0 + } + return 1 // continue enumeration + }) + // Enumerates all top-level windows on the screen + // The error is not checked because if EnumWindows is stopped before enumerating all windows + // it returns 0(error occurred) instead of 1(success) + // In our case, title will equal "" or the title of the window anyway + err := enumWindows(cb, 0) + if title == "" { + var message string + if err != nil { + message = err.Error() + } + return "", errors.New("no matching window title found\n" + message) + } + return title, nil +} + +var ( + advapi = syscall.NewLazyDLL("advapi32.dll") + procGetAce = advapi.NewProc("GetAce") +) + +const ( + ACCESS_DENIED_ACE_TYPE = 1 +) + +type accessMask uint32 + +func (m accessMask) canWrite() bool { + allowed := []int{windows.GENERIC_WRITE, windows.WRITE_DAC, windows.WRITE_OWNER} + for _, v := range allowed { + if m&accessMask(v) != 0 { + return true + } + } + return false +} + +func (m accessMask) permissions() string { + var permissions []string + if m&windows.GENERIC_READ != 0 { + permissions = append(permissions, "GENERIC_READ") + } + if m&windows.GENERIC_WRITE != 0 { + permissions = append(permissions, "GENERIC_WRITE") + } + if m&windows.GENERIC_EXECUTE != 0 { + permissions = append(permissions, "GENERIC_EXECUTE") + } + if m&windows.GENERIC_ALL != 0 { + permissions = append(permissions, "GENERIC_ALL") + } + if m&windows.WRITE_DAC != 0 { + permissions = append(permissions, "WRITE_DAC") + } + if m&windows.WRITE_OWNER != 0 { + permissions = append(permissions, "WRITE_OWNER") + } + if m&windows.SYNCHRONIZE != 0 { + permissions = append(permissions, "SYNCHRONIZE") + } + if m&windows.DELETE != 0 { + permissions = append(permissions, "DELETE") + } + if m&windows.READ_CONTROL != 0 { + permissions = append(permissions, "READ_CONTROL") + } + if m&windows.ACCESS_SYSTEM_SECURITY != 0 { + permissions = append(permissions, "ACCESS_SYSTEM_SECURITY") + } + if m&windows.MAXIMUM_ALLOWED != 0 { + permissions = append(permissions, "MAXIMUM_ALLOWED") + } + return strings.Join(permissions, "\n") +} + +type AccessAllowedAce struct { + AceType uint8 + AceFlags uint8 + AceSize uint16 + AccessMask accessMask + SidStart uint32 +} + +func getCurrentUser() (user *tokenUser, err error) { + token := windows.GetCurrentProcessToken() + defer token.Close() + + tokenuser, err := token.GetTokenUser() + if err != nil { + return + } + tokenGroups, err := token.GetTokenGroups() + if err != nil { + return + } + user = &tokenUser{ + sid: tokenuser.User.Sid, + groups: tokenGroups.AllGroups(), + } + return +} + +type tokenUser struct { + sid *windows.SID + groups []windows.SIDAndAttributes +} + +func (u *tokenUser) isMemberOf(sid *windows.SID) bool { + if u.sid.Equals(sid) { + return true + } + for _, g := range u.groups { + if g.Sid.Equals(sid) { + return true + } + } + return false +} + +func (env *Terminal) isWriteable(folder string) bool { + cu, err := getCurrentUser() + + if err != nil { + // unable to get current user + log.Error(err) + return false + } + + si, err := windows.GetNamedSecurityInfo(folder, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION) + if err != nil { + log.Error(err) + return false + } + + dacl, _, err := si.DACL() + if err != nil || dacl == nil { + // no dacl implies full access + log.Debug("no dacl") + return true + } + + rs := reflect.ValueOf(dacl).Elem() + aceCount := rs.Field(3).Uint() + + for i := range aceCount { + ace := &AccessAllowedAce{} + + ret, _, _ := procGetAce.Call(uintptr(unsafe.Pointer(dacl)), uintptr(i), uintptr(unsafe.Pointer(&ace))) + if ret == 0 { + log.Debug("no ace found") + return false + } + + aceSid := (*windows.SID)(unsafe.Pointer(&ace.SidStart)) + + if !cu.isMemberOf(aceSid) { + log.Debug("not current user or in group") + continue + } + + log.Debug(fmt.Sprintf("current user is member of %s", aceSid.String())) + + // this gets priority over the other access types + if ace.AceType == ACCESS_DENIED_ACE_TYPE { + log.Debug("ACCESS_DENIED_ACE_TYPE") + return false + } + + log.Debugf("%v", ace.AccessMask.permissions()) + if ace.AccessMask.canWrite() { + log.Debug("user has write access") + return true + } + } + log.Debug("no write access") + return false +} + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + globalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx") +) + +type memoryStatusEx struct { + Length uint32 + MemoryLoad uint32 + TotalPhys uint64 + AvailPhys uint64 + TotalPageFile uint64 + AvailPageFile uint64 + TotalVirtual uint64 + AvailVirtual uint64 + AvailExtendedVirtual uint64 +} + +func (env *Terminal) Memory() (*Memory, error) { + var memStat memoryStatusEx + memStat.Length = uint32(unsafe.Sizeof(memStat)) + r0, _, err := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memStat))) + if r0 == 0 { + log.Error(err) + return nil, err + } + return &Memory{ + PhysicalTotalMemory: memStat.TotalPhys, + PhysicalFreeMemory: memStat.AvailPhys, + PhysicalAvailableMemory: memStat.AvailPhys, + PhysicalPercentUsed: float64(memStat.MemoryLoad), + }, nil +} diff --git a/src/segment.go b/src/segment.go deleted file mode 100644 index 6f477ab4f241..000000000000 --- a/src/segment.go +++ /dev/null @@ -1,295 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "time" -) - -// Segment represent a single segment and it's configuration -type Segment struct { - Type SegmentType `config:"type"` - Tips []string `config:"tips"` - Style SegmentStyle `config:"style"` - PowerlineSymbol string `config:"powerline_symbol"` - InvertPowerline bool `config:"invert_powerline"` - Foreground string `config:"foreground"` - ForegroundTemplates []string `config:"foreground_templates"` - Background string `config:"background"` - BackgroundTemplates []string `config:"background_templates"` - LeadingDiamond string `config:"leading_diamond"` - TrailingDiamond string `config:"trailing_diamond"` - Properties map[Property]interface{} `config:"properties"` - props *properties - writer SegmentWriter - stringValue string - active bool - env environmentInfo -} - -// SegmentTiming holds the timing context for a segment -type SegmentTiming struct { - name string - nameLength int - enabled bool - stringValue string - enabledDuration time.Duration - stringDuration time.Duration -} - -// SegmentWriter is the interface used to define what and if to write to the prompt -type SegmentWriter interface { - enabled() bool - string() string - init(props *properties, env environmentInfo) -} - -// SegmentStyle the syle of segment, for more information, see the constants -type SegmentStyle string - -// SegmentType the type of segment, for more information, see the constants -type SegmentType string - -const ( - // Session represents the user info segment - Session SegmentType = "session" - // Path represents the current path segment - Path SegmentType = "path" - // Git represents the git status and information - Git SegmentType = "git" - // Exit writes the last exit code - Exit SegmentType = "exit" - // Python writes the virtual env name - Python SegmentType = "python" - // Root writes root symbol - Root SegmentType = "root" - // Time writes the current timestamp - Time SegmentType = "time" - // Text writes a text - Text SegmentType = "text" - // Cmd writes the output of a shell command - Cmd SegmentType = "command" - // Battery writes the battery percentage - Battery SegmentType = "battery" - // Spotify writes the Spotify status for Mac - Spotify SegmentType = "spotify" - // ShellInfo writes which shell we're currently in - ShellInfo SegmentType = "shell" - // Node writes which node version is currently active - Node SegmentType = "node" - // Os write os specific icon - Os SegmentType = "os" - // EnvVar writes the content of an environment variable - EnvVar SegmentType = "envvar" - // Az writes the Azure subscription info we're currently in - Az SegmentType = "az" - // Kubectl writes the Kubernetes context we're currently in - Kubectl SegmentType = "kubectl" - // Dotnet writes which dotnet version is currently active - Dotnet SegmentType = "dotnet" - // Terraform writes the terraform workspace we're currently in - Terraform SegmentType = "terraform" - // Golang writes which go version is currently active - Golang SegmentType = "go" - // Julia writes which julia version is currently active - Julia SegmentType = "julia" - // Powerline writes it Powerline style - Powerline SegmentStyle = "powerline" - // Plain writes it without ornaments - Plain SegmentStyle = "plain" - // Diamond writes the prompt shaped with a leading and trailing symbol - Diamond SegmentStyle = "diamond" - // YTM writes YouTube Music information and status - YTM SegmentType = "ytm" - // ExecutionTime writes the execution time of the last run command - ExecutionTime SegmentType = "executiontime" - // Ruby writes which ruby version is currently active - Ruby SegmentType = "ruby" - // Aws writes the active aws context - Aws SegmentType = "aws" - // Java writes the active java version - Java SegmentType = "java" - // PoshGit writes the posh git prompt - PoshGit SegmentType = "poshgit" - // AZFunc writes current AZ func version - AZFunc SegmentType = "azfunc" - // Crystal writes the active crystal version - Crystal SegmentType = "crystal" - // Dart writes the active dart version - Dart SegmentType = "dart" - // Nbgv writes the nbgv version information - Nbgv SegmentType = "nbgv" -) - -func (segment *Segment) string() string { - return segment.writer.string() -} - -func (segment *Segment) enabled() bool { - segment.active = segment.writer.enabled() - return segment.active -} - -func (segment *Segment) getValue(property Property, defaultValue string) string { - if value, ok := segment.Properties[property]; ok { - return parseString(value, defaultValue) - } - return defaultValue -} - -func (segment *Segment) shouldIncludeFolder(cwd string) bool { - cwdIncluded := segment.cwdIncluded(cwd) - cwdExcluded := segment.cwdExcluded(cwd) - return (cwdIncluded && !cwdExcluded) -} - -func (segment *Segment) cwdIncluded(cwd string) bool { - value, ok := segment.Properties[IncludeFolders] - if !ok { - // IncludeFolders isn't specified, everything is included - return true - } - - list := parseStringArray(value) - - if len(list) == 0 { - // IncludeFolders is an empty array, everything is included - return true - } - - return segment.cwdMatchesOneOf(cwd, list) -} - -func (segment *Segment) cwdExcluded(cwd string) bool { - value, ok := segment.Properties[ExcludeFolders] - if !ok { - value = segment.Properties[IgnoreFolders] - } - list := parseStringArray(value) - return segment.cwdMatchesOneOf(cwd, list) -} - -func (segment *Segment) cwdMatchesOneOf(cwd string, regexes []string) bool { - for _, element := range regexes { - pattern := fmt.Sprintf("^%s$", element) - matched := matchString(pattern, cwd) - if matched { - return true - } - } - return false -} - -func (segment *Segment) getColor(templates []string, defaultColor string) string { - if len(templates) == 0 { - return defaultColor - } - txtTemplate := &textTemplate{ - Context: segment.writer, - Env: segment.env, - } - for _, template := range templates { - txtTemplate.Template = template - value, err := txtTemplate.render() - if err != nil || value == "" { - continue - } - return value - } - return defaultColor -} - -func (segment *Segment) shouldInvokeWithTip(tip string) bool { - for _, t := range segment.Tips { - if t == tip { - return true - } - } - return false -} - -func (segment *Segment) foreground() string { - color := segment.Foreground - if segment.props != nil { - color = segment.props.foreground - } - return segment.getColor(segment.ForegroundTemplates, color) -} - -func (segment *Segment) background() string { - color := segment.Background - if segment.props != nil { - color = segment.props.background - } - return segment.getColor(segment.BackgroundTemplates, color) -} - -func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error { - segment.env = env - functions := map[SegmentType]SegmentWriter{ - Session: &session{}, - Path: &path{}, - Git: &git{}, - Exit: &exit{}, - Python: &python{}, - Root: &root{}, - Text: &text{}, - Time: &tempus{}, - Cmd: &command{}, - Battery: &batt{}, - Spotify: &spotify{}, - ShellInfo: &shell{}, - Node: &node{}, - Os: &osInfo{}, - EnvVar: &envvar{}, - Az: &az{}, - Kubectl: &kubectl{}, - Dotnet: &dotnet{}, - Terraform: &terraform{}, - Golang: &golang{}, - Julia: &julia{}, - YTM: &ytm{}, - ExecutionTime: &executiontime{}, - Ruby: &ruby{}, - Aws: &aws{}, - Java: &java{}, - PoshGit: &poshgit{}, - AZFunc: &azfunc{}, - Crystal: &crystal{}, - Dart: &dart{}, - Nbgv: &nbgv{}, - } - if writer, ok := functions[segment.Type]; ok { - props := &properties{ - values: segment.Properties, - foreground: segment.Foreground, - background: segment.Background, - } - writer.init(props, env) - segment.writer = writer - segment.props = props - return nil - } - return errors.New("unable to map writer") -} - -func (segment *Segment) setStringValue(env environmentInfo, cwd string) { - defer func() { - err := recover() - if err == nil { - return - } - // display a message explaining omp failed(with the err) - message := fmt.Sprintf("oh-my-posh fatal error rendering %s segment:%s", segment.Type, err) - fmt.Println(message) - segment.stringValue = "error" - segment.active = true - }() - err := segment.mapSegmentWithWriter(env) - if err != nil || !segment.shouldIncludeFolder(cwd) { - return - } - if segment.enabled() { - segment.stringValue = segment.string() - } -} diff --git a/src/segment_aws.go b/src/segment_aws.go deleted file mode 100644 index 5864bd405958..000000000000 --- a/src/segment_aws.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -type aws struct { - props *properties - env environmentInfo - Profile string - Region string -} - -const ( - defaultUser = "default" -) - -func (a *aws) init(props *properties, env environmentInfo) { - a.props = props - a.env = env -} - -func (a *aws) enabled() bool { - getEnvFirstMatch := func(envs ...string) string { - for _, env := range envs { - value := a.env.getenv(env) - if value != "" { - return value - } - } - return "" - } - displayDefaultUser := a.props.getBool(DisplayDefault, true) - a.Profile = getEnvFirstMatch("AWS_VAULT", "AWS_PROFILE") - if !displayDefaultUser && a.Profile == defaultUser { - return false - } - a.Region = getEnvFirstMatch("AWS_DEFAULT_REGION", "AWS_REGION") - if a.Profile != "" && a.Region != "" { - return true - } - if a.Profile == "" && a.Region != "" && displayDefaultUser { - a.Profile = defaultUser - return true - } - a.getConfigFileInfo() - if !displayDefaultUser && a.Profile == defaultUser { - return false - } - return a.Profile != "" -} - -func (a *aws) getConfigFileInfo() { - configPath := a.env.getenv("AWS_CONFIG_FILE") - if configPath == "" { - configPath = fmt.Sprintf("%s/.aws/config", a.env.homeDir()) - } - config := a.env.getFileContent(configPath) - configSection := "[default]" - if a.Profile != "" { - configSection = fmt.Sprintf("[profile %s]", a.Profile) - } - configLines := strings.Split(config, "\n") - var sectionActive bool - for _, line := range configLines { - if strings.HasPrefix(line, configSection) { - sectionActive = true - continue - } - if sectionActive && strings.HasPrefix(line, "region") { - a.Region = strings.TrimSpace(strings.Split(line, "=")[1]) - break - } - } - if a.Profile == "" && a.Region != "" { - a.Profile = defaultUser - } -} - -func (a *aws) string() string { - segmentTemplate := a.props.getString(SegmentTemplate, "{{.Profile}}{{if .Region}}@{{.Region}}{{end}}") - template := &textTemplate{ - Template: segmentTemplate, - Context: a, - Env: a.env, - } - text, err := template.render() - if err != nil { - return err.Error() - } - return text -} diff --git a/src/segment_aws_test.go b/src/segment_aws_test.go deleted file mode 100644 index cffb43ffb736..000000000000 --- a/src/segment_aws_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAWSSegment(t *testing.T) { - cases := []struct { - Case string - ExpectedString string - ExpectedEnabled bool - Profile string - Vault string - Region string - DefaultRegion string - ConfigFile string - Template string - DisplayDefault bool - }{ - {Case: "enabled with default user", ExpectedString: "default@eu-west", Region: "eu-west", ExpectedEnabled: true, DisplayDefault: true}, - {Case: "disabled with default user", ExpectedString: "default@eu-west", Region: "eu-west", ExpectedEnabled: false, DisplayDefault: false}, - {Case: "disabled", ExpectedString: "", ExpectedEnabled: false}, - {Case: "enabled with default user", ExpectedString: "default@eu-west", Profile: "default", Region: "eu-west", ExpectedEnabled: true, DisplayDefault: true}, - {Case: "disabled with default user", ExpectedString: "default", Profile: "default", Region: "eu-west", ExpectedEnabled: false, DisplayDefault: false}, - {Case: "enabled no region", ExpectedString: "company", ExpectedEnabled: true, Profile: "company"}, - {Case: "enabled with region", ExpectedString: "company@eu-west", ExpectedEnabled: true, Profile: "company", Region: "eu-west"}, - { - Case: "template: enabled no region", - ExpectedString: "profile: company", - ExpectedEnabled: true, - Profile: "company", - Template: "profile: {{.Profile}}{{if .Region}} in {{.Region}}{{end}}", - }, - { - Case: "template: enabled with region", - ExpectedString: "profile: company in eu-west", - ExpectedEnabled: true, - Profile: "company", - Region: "eu-west", - Template: "profile: {{.Profile}}{{if .Region}} in {{.Region}}{{end}}", - }, - {Case: "template: invalid", ExpectedString: invalidTemplate, ExpectedEnabled: true, Profile: "c", Template: "{{ .Burp"}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getenv", "AWS_VAULT").Return(tc.Vault) - env.On("getenv", "AWS_PROFILE").Return(tc.Profile) - env.On("getenv", "AWS_REGION").Return(tc.Region) - env.On("getenv", "AWS_DEFAULT_REGION").Return(tc.DefaultRegion) - env.On("getenv", "AWS_CONFIG_FILE").Return(tc.ConfigFile) - env.On("getFileContent", "/usr/home/.aws/config").Return("") - env.On("homeDir", nil).Return("/usr/home") - props := &properties{ - values: map[Property]interface{}{ - DisplayDefault: tc.DisplayDefault, - }, - } - if tc.Template != "" { - props.values[SegmentTemplate] = tc.Template - } - - aws := &aws{ - env: env, - props: props, - } - assert.Equal(t, tc.ExpectedEnabled, aws.enabled(), tc.Case) - assert.Equal(t, tc.ExpectedString, aws.string(), tc.Case) - } -} diff --git a/src/segment_az.go b/src/segment_az.go deleted file mode 100644 index 04a40c3c2487..000000000000 --- a/src/segment_az.go +++ /dev/null @@ -1,109 +0,0 @@ -package main - -import ( - "strings" -) - -type az struct { - props *properties - env environmentInfo - name string - id string - account string - builder strings.Builder - separator string -} - -const ( - // SubscriptionInfoSeparator is put between the name and ID - SubscriptionInfoSeparator Property = "info_separator" - // DisplaySubscriptionID hides or show the subscription GUID - DisplaySubscriptionID Property = "display_id" - // DisplaySubscriptionName hides or shows the subscription display name - DisplaySubscriptionName Property = "display_name" - // DisplaySubscriptionAccount hides or shows the subscription account name - DisplaySubscriptionAccount Property = "display_account" - - updateConsentNeeded = "Do you want to continue?" - updateMessage = "AZ CLI: Update needed!" - updateForeground = "#ffffff" - updateBackground = "#ff5349" -) - -func (a *az) string() string { - a.separator = a.props.getString(SubscriptionInfoSeparator, " | ") - writeValue := func(value string) { - if len(value) == 0 { - return - } - if a.builder.Len() > 0 { - a.builder.WriteString(a.separator) - } - a.builder.WriteString(value) - } - if a.props.getBool(DisplaySubscriptionAccount, false) { - writeValue(a.account) - } - if a.props.getBool(DisplaySubscriptionName, true) { - writeValue(a.name) - } - if a.props.getBool(DisplaySubscriptionID, false) { - writeValue(a.id) - } - - return a.builder.String() -} - -func (a *az) init(props *properties, env environmentInfo) { - a.props = props - a.env = env -} - -func (a *az) enabled() bool { - if a.getFromEnvVars() { - return true - } - - return a.getFromAzCli() -} - -func (a *az) getFromEnvVars() bool { - a.name = a.env.getenv("AZ_SUBSCRIPTION_NAME") - a.id = a.env.getenv("AZ_SUBSCRIPTION_ID") - a.account = a.env.getenv("AZ_SUBSCRIPTION_ACCOUNT") - - if a.name == "" && a.id == "" { - return false - } - - return true -} - -func (a *az) getFromAzCli() bool { - cmd := "az" - if !a.env.hasCommand(cmd) { - return false - } - - output, _ := a.env.runCommand(cmd, "account", "show", "--query=[name,id,user.name]", "-o=tsv") - if len(output) == 0 { - return false - } - - if strings.Contains(output, updateConsentNeeded) { - a.props.foreground = updateForeground - a.props.background = updateBackground - a.name = updateMessage - return true - } - - splittedOutput := strings.Split(output, "\n") - if len(splittedOutput) < 3 { - return false - } - - a.name = strings.TrimSpace(splittedOutput[0]) - a.id = strings.TrimSpace(splittedOutput[1]) - a.account = strings.TrimSpace(splittedOutput[2]) - return true -} diff --git a/src/segment_az_functions.go b/src/segment_az_functions.go deleted file mode 100644 index 6f8c685a5b03..000000000000 --- a/src/segment_az_functions.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -type azfunc struct { - language *language -} - -func (az *azfunc) string() string { - return az.language.string() -} - -func (az *azfunc) init(props *properties, env environmentInfo) { - az.language = &language{ - env: env, - props: props, - extensions: []string{"host.json", "local.settings.json"}, - commands: []*cmd{ - { - executable: "func", - args: []string{"--version"}, - regex: `(?P.+)`, - }, - }, - } -} - -func (az *azfunc) enabled() bool { - return az.language.enabled() -} diff --git a/src/segment_az_test.go b/src/segment_az_test.go deleted file mode 100644 index 68400912de24..000000000000 --- a/src/segment_az_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAzSegment(t *testing.T) { - cases := []struct { - Case string - ExpectedEnabled bool - ExpectedString string - EnvSubName string - EnvSubID string - EnvSubAccount string - CliExists bool - CliSubName string - CliSubID string - CliSubAccount string - InfoSeparator string - DisplayID bool - DisplayName bool - DisplayAccount bool - }{ - { - Case: "print only account", - ExpectedEnabled: true, - ExpectedString: "foobar", - CliExists: true, - CliSubName: "foo", - CliSubID: "bar", - CliSubAccount: "foobar", - InfoSeparator: "$", - DisplayID: false, - DisplayName: false, - DisplayAccount: true, - }, - { - Case: "envvars present", - ExpectedEnabled: true, - ExpectedString: "foo$bar", - EnvSubName: "foo", - EnvSubID: "bar", - CliExists: false, - InfoSeparator: "$", - DisplayID: true, - DisplayName: true, - }, - { - Case: "envvar name present", - ExpectedEnabled: true, - ExpectedString: "foo", - EnvSubName: "foo", - CliExists: false, - InfoSeparator: "$", - DisplayID: true, - DisplayName: true, - }, - { - Case: "envvar id present", - ExpectedEnabled: true, - ExpectedString: "bar", - EnvSubID: "bar", - CliExists: false, - InfoSeparator: "$", - DisplayID: true, - DisplayName: true, - }, - { - Case: "envvar account present", - ExpectedEnabled: true, - ExpectedString: "foobar", - EnvSubAccount: "foobar", - EnvSubID: "bar", - CliExists: false, - InfoSeparator: "$", - DisplayAccount: true, - }, - { - Case: "cli not found", - ExpectedEnabled: false, - ExpectedString: "", - CliExists: false, - InfoSeparator: "$", - DisplayID: true, - DisplayName: true, - }, - { - Case: "cli contains data", - ExpectedEnabled: true, - ExpectedString: "foo$bar", - CliExists: true, - CliSubName: "foo", - CliSubID: "bar", - InfoSeparator: "$", - DisplayID: true, - DisplayName: true, - }, - { - Case: "print only name", - ExpectedEnabled: true, - ExpectedString: "foo", - CliExists: true, - CliSubName: "foo", - CliSubID: "bar", - InfoSeparator: "$", - DisplayID: false, - DisplayName: true, - }, - { - Case: "print only id", - ExpectedEnabled: true, - ExpectedString: "bar", - CliExists: true, - CliSubName: "foo", - CliSubID: "bar", - InfoSeparator: "$", - DisplayID: true, - DisplayName: false, - }, - { - Case: "print none", - ExpectedEnabled: true, - CliExists: true, - CliSubName: "foo", - CliSubID: "bar", - InfoSeparator: "$", - }, - { - Case: "update needed", - ExpectedEnabled: true, - ExpectedString: updateMessage, - CliExists: true, - CliSubName: "Do you want to continue? (Y/n): Visual Studio Enterprise", - DisplayID: false, - DisplayName: true, - }, - { - Case: "account info", - ExpectedEnabled: true, - ExpectedString: updateMessage, - CliExists: true, - CliSubName: "Do you want to continue? (Y/n): Visual Studio Enterprise", - DisplayID: false, - DisplayName: true, - DisplayAccount: true, - }, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getenv", "AZ_SUBSCRIPTION_NAME").Return(tc.EnvSubName) - env.On("getenv", "AZ_SUBSCRIPTION_ID").Return(tc.EnvSubID) - env.On("getenv", "AZ_SUBSCRIPTION_ACCOUNT").Return(tc.EnvSubAccount) - env.On("hasCommand", "az").Return(tc.CliExists) - env.On("runCommand", "az", []string{"account", "show", "--query=[name,id,user.name]", "-o=tsv"}).Return( - fmt.Sprintf("%s\n%s\n%s\n", tc.CliSubName, tc.CliSubID, tc.CliSubAccount), - nil, - ) - props := &properties{ - values: map[Property]interface{}{ - SubscriptionInfoSeparator: tc.InfoSeparator, - DisplaySubscriptionID: tc.DisplayID, - DisplaySubscriptionName: tc.DisplayName, - DisplaySubscriptionAccount: tc.DisplayAccount, - }, - } - - az := &az{ - env: env, - props: props, - } - assert.Equal(t, tc.ExpectedEnabled, az.enabled(), tc.Case) - assert.Equal(t, tc.ExpectedString, az.string(), tc.Case) - } -} diff --git a/src/segment_battery.go b/src/segment_battery.go deleted file mode 100644 index 28f7e3acb59d..000000000000 --- a/src/segment_battery.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "math" - - "github.com/distatus/battery" -) - -type batt struct { - props *properties - env environmentInfo - Battery *battery.Battery - Percentage int - Error string - Icon string -} - -const ( - // ChargingIcon to display when charging - ChargingIcon Property = "charging_icon" - // DischargingIcon o display when discharging - DischargingIcon Property = "discharging_icon" - // ChargedIcon to display when fully charged - ChargedIcon Property = "charged_icon" - // ChargedColor to display when fully charged - ChargedColor Property = "charged_color" - // ChargingColor to display when charging - ChargingColor Property = "charging_color" - // DischargingColor to display when discharging - DischargingColor Property = "discharging_color" - // DisplayCharging Hide the battery icon while it's charging - DisplayCharging Property = "display_charging" -) - -func (b *batt) enabled() bool { - batteries, err := b.env.getBatteryInfo() - - if !b.enabledWhileError(err) { - return false - } - - // case on computer without batteries(no error, empty array) - if err == nil && len(batteries) == 0 { - return false - } - - b.Battery = &battery.Battery{} - for _, bt := range batteries { - b.Battery.Current += bt.Current - b.Battery.Full += bt.Full - b.Battery.State = b.mapMostLogicalState(b.Battery.State, bt.State) - } - - display := b.props.getBool(DisplayCharging, true) - if !display && (b.Battery.State == battery.Charging || b.Battery.State == battery.Full) { - return false - } - - batteryPercentage := b.Battery.Current / b.Battery.Full * 100 - b.Percentage = int(math.Min(100, batteryPercentage)) - var colorPorperty Property - switch b.Battery.State { - case battery.Discharging, battery.NotCharging: - colorPorperty = DischargingColor - b.Icon = b.props.getString(DischargingIcon, "") - case battery.Charging: - colorPorperty = ChargingColor - b.Icon = b.props.getString(ChargingIcon, "") - case battery.Full: - colorPorperty = ChargedColor - b.Icon = b.props.getString(ChargedIcon, "") - case battery.Empty, battery.Unknown: - return true - } - colorBackground := b.props.getBool(ColorBackground, false) - if colorBackground { - b.props.background = b.props.getColor(colorPorperty, b.props.background) - } else { - b.props.foreground = b.props.getColor(colorPorperty, b.props.foreground) - } - return true -} - -func (b *batt) enabledWhileError(err error) bool { - if err == nil { - return true - } - if _, ok := err.(*noBatteryError); ok { - return false - } - displayError := b.props.getBool(DisplayError, false) - if !displayError { - return false - } - b.Error = err.Error() - // On Windows, it sometimes errors when the battery is full. - // This hack ensures we display a fully charged battery, even if - // that state can be incorrect. It's better to "ignore" the error - // than to not display the segment at all as that will confuse users. - b.Battery = &battery.Battery{ - Current: 100, - Full: 100, - State: battery.Full, - } - return true -} - -func (b *batt) mapMostLogicalState(currentState, newState battery.State) battery.State { - switch currentState { - case battery.Discharging, battery.NotCharging: - return battery.Discharging - case battery.Empty: - return newState - case battery.Charging: - if newState == battery.Discharging { - return battery.Discharging - } - return battery.Charging - case battery.Unknown: - return newState - case battery.Full: - return newState - } - return newState -} - -func (b *batt) string() string { - segmentTemplate := b.props.getString(SegmentTemplate, "{{.Icon}}{{ if not .Error }}{{.Percentage}}{{ end }}{{.Error}}") - template := &textTemplate{ - Template: segmentTemplate, - Context: b, - Env: b.env, - } - text, err := template.render() - if err != nil { - return err.Error() - } - return text -} - -func (b *batt) init(props *properties, env environmentInfo) { - b.props = props - b.env = env -} diff --git a/src/segment_battery_test.go b/src/segment_battery_test.go deleted file mode 100644 index dc1a254d880b..000000000000 --- a/src/segment_battery_test.go +++ /dev/null @@ -1,223 +0,0 @@ -package main - -import ( - "errors" - "testing" - - "github.com/distatus/battery" - "github.com/stretchr/testify/assert" -) - -const ( - chargingColor = "#123456" - dischargingColor = "#765432" - chargedColor = "#248644" -) - -func TestBatterySegmentSingle(t *testing.T) { - cases := []struct { - Case string - Batteries []*battery.Battery - ExpectedString string - ExpectedEnabled bool - ExpectedColor string - ColorBackground bool - DisplayError bool - Error error - DisableCharging bool - }{ - {Case: "80% charging", Batteries: []*battery.Battery{{Full: 100, State: battery.Charging, Current: 80}}, ExpectedString: "charging 80", ExpectedEnabled: true}, - {Case: "battery full", Batteries: []*battery.Battery{{Full: 100, State: battery.Full, Current: 100}}, ExpectedString: "charged 100", ExpectedEnabled: true}, - {Case: "70% discharging", Batteries: []*battery.Battery{{Full: 100, State: battery.Discharging, Current: 70}}, ExpectedString: "going down 70", ExpectedEnabled: true}, - { - Case: "discharging background color", - Batteries: []*battery.Battery{{Full: 100, State: battery.Discharging, Current: 70}}, - ExpectedString: "going down 70", - ExpectedEnabled: true, - ColorBackground: true, - ExpectedColor: dischargingColor, - }, - { - Case: "charging background color", - Batteries: []*battery.Battery{{Full: 100, State: battery.Charging, Current: 70}}, - ExpectedString: "charging 70", - ExpectedEnabled: true, - ColorBackground: true, - ExpectedColor: chargingColor, - }, - { - Case: "charged background color", - Batteries: []*battery.Battery{{Full: 100, State: battery.Full, Current: 70}}, - ExpectedString: "charged 70", - ExpectedEnabled: true, - ColorBackground: true, - ExpectedColor: chargedColor, - }, - { - Case: "discharging foreground color", - Batteries: []*battery.Battery{{Full: 100, State: battery.Discharging, Current: 70}}, - ExpectedString: "going down 70", - ExpectedEnabled: true, - ExpectedColor: dischargingColor, - }, - { - Case: "charging foreground color", - Batteries: []*battery.Battery{{Full: 100, State: battery.Charging, Current: 70}}, - ExpectedString: "charging 70", - ExpectedEnabled: true, - ExpectedColor: chargingColor, - }, - { - Case: "charged foreground color", - Batteries: []*battery.Battery{{Full: 100, State: battery.Full, Current: 70}}, - ExpectedString: "charged 70", - ExpectedEnabled: true, - ExpectedColor: chargedColor, - }, - {Case: "battery error", DisplayError: true, Error: errors.New("oh snap"), ExpectedString: "oh snap", ExpectedEnabled: true}, - {Case: "battery error disabled", Error: errors.New("oh snap")}, - {Case: "no batteries", DisplayError: true, Error: &noBatteryError{}}, - {Case: "no batteries without error"}, - {Case: "display charging disabled: charging", Batteries: []*battery.Battery{{Full: 100, State: battery.Charging}}, DisableCharging: true}, - {Case: "display charging disabled: charged", Batteries: []*battery.Battery{{Full: 100, State: battery.Full}}, DisableCharging: true}, - { - Case: "display charging disabled: discharging", - Batteries: []*battery.Battery{{Full: 100, State: battery.Discharging, Current: 70}}, - ExpectedString: "going down 70", - ExpectedEnabled: true, - DisableCharging: true, - }, - } - - for _, tc := range cases { - env := &MockedEnvironment{} - props := &properties{ - background: "#111111", - foreground: "#ffffff", - values: map[Property]interface{}{ - ChargingIcon: "charging ", - ChargedIcon: "charged ", - DischargingIcon: "going down ", - DischargingColor: dischargingColor, - ChargedColor: chargedColor, - ChargingColor: chargingColor, - ColorBackground: tc.ColorBackground, - DisplayError: tc.DisplayError, - }, - } - if tc.DisableCharging { - props.values[DisplayCharging] = false - } - env.On("getBatteryInfo", nil).Return(tc.Batteries, tc.Error) - b := &batt{ - props: props, - env: env, - } - enabled := b.enabled() - assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) - if !enabled { - continue - } - assert.Equal(t, tc.ExpectedString, b.string(), tc.Case) - if len(tc.ExpectedColor) == 0 { - continue - } - actualColor := b.props.foreground - if tc.ColorBackground { - actualColor = b.props.background - } - assert.Equal(t, tc.ExpectedColor, actualColor, tc.Case) - } -} - -func TestGetBatteryColors(t *testing.T) { - cases := []struct { - Case string - ExpectedColor string - Templates []string - DefaultColor string - Battery *battery.Battery - Percentage int - }{ - { - Case: "Percentage lower", - ExpectedColor: "color2", - DefaultColor: "color", - Templates: []string{ - "{{if (lt .Percentage 60)}}color2{{end}}", - "{{if (gt .Percentage 60)}}color3{{end}}", - }, - Percentage: 50, - }, - { - Case: "Percentage higher", - ExpectedColor: "color3", - DefaultColor: "color", - Templates: []string{ - "{{if (lt .Percentage 60)}}color2{{end}}", - "{{if (gt .Percentage 60)}}color3{{end}}", - }, - Percentage: 70, - }, - { - Case: "Charging", - ExpectedColor: "color2", - DefaultColor: "color", - Templates: []string{ - "{{if eq \"Charging\" .Battery.State.String}}color2{{end}}", - "{{if eq \"Discharging\" .Battery.State.String}}color3{{end}}", - "{{if eq \"Full\" .Battery.State.String}}color4{{end}}", - }, - Battery: &battery.Battery{ - State: battery.Charging, - }, - }, - { - Case: "Discharging", - ExpectedColor: "color3", - DefaultColor: "color", - Templates: []string{ - "{{if eq \"Charging\" .Battery.State.String}}color2{{end}}", - "{{if eq \"Discharging\" .Battery.State.String}}color3{{end}}", - "{{if eq \"Full\" .Battery.State.String}}color2{{end}}", - }, - Battery: &battery.Battery{ - State: battery.Discharging, - }, - }, - } - for _, tc := range cases { - segment := &Segment{ - writer: &batt{ - Percentage: tc.Percentage, - Battery: tc.Battery, - }, - } - segment.Foreground = tc.DefaultColor - segment.ForegroundTemplates = tc.Templates - color := segment.foreground() - assert.Equal(t, tc.ExpectedColor, color, tc.Case) - } -} - -func TestMapBatteriesState(t *testing.T) { - cases := []struct { - Case string - ExpectedState battery.State - CurrentState battery.State - NewState battery.State - }{ - {Case: "charging > charged", ExpectedState: battery.Charging, CurrentState: battery.Full, NewState: battery.Charging}, - {Case: "charging < discharging", ExpectedState: battery.Discharging, CurrentState: battery.Discharging, NewState: battery.Charging}, - {Case: "charging == charging", ExpectedState: battery.Charging, CurrentState: battery.Charging, NewState: battery.Charging}, - {Case: "discharging > charged", ExpectedState: battery.Discharging, CurrentState: battery.Full, NewState: battery.Discharging}, - {Case: "discharging > unknown", ExpectedState: battery.Discharging, CurrentState: battery.Unknown, NewState: battery.Discharging}, - {Case: "discharging > full", ExpectedState: battery.Discharging, CurrentState: battery.Full, NewState: battery.Discharging}, - {Case: "discharging > charging 2", ExpectedState: battery.Discharging, CurrentState: battery.Charging, NewState: battery.Discharging}, - {Case: "discharging > empty", ExpectedState: battery.Discharging, CurrentState: battery.Empty, NewState: battery.Discharging}, - } - for _, tc := range cases { - batt := &batt{} - assert.Equal(t, tc.ExpectedState, batt.mapMostLogicalState(tc.CurrentState, tc.NewState), tc.Case) - } -} diff --git a/src/segment_command.go b/src/segment_command.go deleted file mode 100644 index bbc26ac06f48..000000000000 --- a/src/segment_command.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import "strings" - -type command struct { - props *properties - env environmentInfo - value string -} - -const ( - // ExecutableShell to execute command in - ExecutableShell Property = "shell" - // Command to execute - Command Property = "command" -) - -func (c *command) enabled() bool { - shell := c.props.getString(ExecutableShell, "bash") - if !c.env.hasCommand(shell) { - return false - } - command := c.props.getString(Command, "echo no command specified") - if strings.Contains(command, "||") { - commands := strings.Split(command, "||") - for _, cmd := range commands { - output := c.env.runShellCommand(shell, cmd) - if output != "" { - c.value = output - return true - } - } - } - if strings.Contains(command, "&&") { - var output string - commands := strings.Split(command, "&&") - for _, cmd := range commands { - output += c.env.runShellCommand(shell, cmd) - } - c.value = output - return c.value != "" - } - c.value = c.env.runShellCommand(shell, command) - return c.value != "" -} - -func (c *command) string() string { - return c.value -} - -func (c *command) init(props *properties, env environmentInfo) { - c.props = props - c.env = env -} diff --git a/src/segment_command_test.go b/src/segment_command_test.go deleted file mode 100644 index ebe8be4e04c7..000000000000 --- a/src/segment_command_test.go +++ /dev/null @@ -1,138 +0,0 @@ -// +build !windows - -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestExecuteCommand(t *testing.T) { - env := &environment{} - env.init(nil) - props := &properties{ - values: map[Property]interface{}{ - Command: "echo hello", - }, - } - c := &command{ - props: props, - env: env, - } - enabled := c.enabled() - assert.True(t, enabled) - assert.Equal(t, "hello", c.string()) -} - -func TestExecuteMultipleCommandsOrFirst(t *testing.T) { - env := &environment{} - env.init(nil) - props := &properties{ - values: map[Property]interface{}{ - Command: "exit 1 || echo hello", - }, - } - c := &command{ - props: props, - env: env, - } - enabled := c.enabled() - assert.True(t, enabled) - assert.Equal(t, "hello", c.string()) -} - -func TestExecuteMultipleCommandsOrSecond(t *testing.T) { - env := &environment{} - env.init(nil) - props := &properties{ - values: map[Property]interface{}{ - Command: "echo hello || echo world", - }, - } - c := &command{ - props: props, - env: env, - } - enabled := c.enabled() - assert.True(t, enabled) - assert.Equal(t, "hello", c.string()) -} - -func TestExecuteMultipleCommandsAnd(t *testing.T) { - env := &environment{} - env.init(nil) - props := &properties{ - values: map[Property]interface{}{ - Command: "echo hello && echo world", - }, - } - c := &command{ - props: props, - env: env, - } - enabled := c.enabled() - assert.True(t, enabled) - assert.Equal(t, "helloworld", c.string()) -} - -func TestExecuteSingleCommandEmpty(t *testing.T) { - env := &environment{} - env.init(nil) - props := &properties{ - values: map[Property]interface{}{ - Command: "", - }, - } - c := &command{ - props: props, - env: env, - } - enabled := c.enabled() - assert.False(t, enabled) -} - -func TestExecuteSingleCommandNoCommandProperty(t *testing.T) { - env := &environment{} - env.init(nil) - props := &properties{} - c := &command{ - props: props, - env: env, - } - enabled := c.enabled() - assert.True(t, enabled) - assert.Equal(t, "no command specified", c.value) -} - -func TestExecuteMultipleCommandsAndDisabled(t *testing.T) { - env := &environment{} - env.init(nil) - props := &properties{ - values: map[Property]interface{}{ - Command: "echo && echo", - }, - } - c := &command{ - props: props, - env: env, - } - enabled := c.enabled() - assert.False(t, enabled) -} - -func TestExecuteMultipleCommandsOrDisabled(t *testing.T) { - env := &environment{} - env.init(nil) - props := &properties{ - values: map[Property]interface{}{ - Command: "echo|| echo", - }, - } - c := &command{ - props: props, - env: env, - } - enabled := c.enabled() - assert.False(t, enabled) -} diff --git a/src/segment_crystal.go b/src/segment_crystal.go deleted file mode 100644 index 3d08cc58f470..000000000000 --- a/src/segment_crystal.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -type crystal struct { - language *language -} - -func (c *crystal) string() string { - return c.language.string() -} - -func (c *crystal) init(props *properties, env environmentInfo) { - c.language = &language{ - env: env, - props: props, - extensions: []string{"*.cr", "shard.yml"}, - commands: []*cmd{ - { - executable: "crystal", - args: []string{"--version"}, - regex: `Crystal (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - }, - versionURLTemplate: "[%s](https://github.com/crystal-lang/crystal/releases/tag/%s.%s.%s)", - } -} - -func (c *crystal) enabled() bool { - return c.language.enabled() -} diff --git a/src/segment_dart.go b/src/segment_dart.go deleted file mode 100644 index 80434c474489..000000000000 --- a/src/segment_dart.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -type dart struct { - language *language -} - -func (d *dart) string() string { - return d.language.string() -} - -func (d *dart) init(props *properties, env environmentInfo) { - d.language = &language{ - env: env, - props: props, - extensions: []string{"*.dart", "pubspec.yaml", "pubspec.yml", "pubspec.lock", ".dart_tool"}, - commands: []*cmd{ - { - executable: "dart", - args: []string{"--version"}, - regex: `Dart SDK version: (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - }, - versionURLTemplate: "[%s](https://dart.dev/guides/language/evolution#dart-%s%s)", - } -} - -func (d *dart) enabled() bool { - return d.language.enabled() -} diff --git a/src/segment_dotnet.go b/src/segment_dotnet.go deleted file mode 100644 index f3b88845e9c4..000000000000 --- a/src/segment_dotnet.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -type dotnet struct { - language *language -} - -const ( - // UnsupportedDotnetVersionIcon is displayed when the dotnet version in - // the current folder isn't supported by the installed dotnet SDK set. - UnsupportedDotnetVersionIcon Property = "unsupported_version_icon" -) - -func (d *dotnet) string() string { - version := d.language.string() - - // Exit code 145 is a special indicator that dotnet - // ran, but the current project config settings specify - // use of an SDK that isn't installed. - if d.language.exitCode == 145 { - return d.language.props.getString(UnsupportedDotnetVersionIcon, "\uf071 ") - } - - return version -} - -func (d *dotnet) init(props *properties, env environmentInfo) { - d.language = &language{ - env: env, - props: props, - extensions: []string{"*.cs", "*.csx", "*.vb", "*.sln", "*.csproj", "*.vbproj", "*.fs", "*.fsx", "*.fsproj"}, - commands: []*cmd{ - { - executable: "dotnet", - args: []string{"--version"}, - regex: `(?:(?P((?P[0-9]+).(?P[0-9]+).(?:\d{2})(?P[0-9]{1}))))`, - }, - }, - versionURLTemplate: "[%1s](https://github.com/dotnet/core/blob/master/release-notes/%[2]s.%[3]s/%[2]s.%[3]s.%[4]s/%[2]s.%[3]s.%[4]s.md)", - } -} - -func (d *dotnet) enabled() bool { - return d.language.enabled() -} diff --git a/src/segment_dotnet_test.go b/src/segment_dotnet_test.go deleted file mode 100644 index edcb244e4293..000000000000 --- a/src/segment_dotnet_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -type dotnetArgs struct { - enabled bool - version string - unsupported bool - unsupportedIcon string - displayVersion bool -} - -func bootStrapDotnetTest(args *dotnetArgs) *dotnet { - env := new(MockedEnvironment) - env.On("hasCommand", "dotnet").Return(args.enabled) - if args.unsupported { - err := &commandError{exitCode: 145} - env.On("runCommand", "dotnet", []string{"--version"}).Return("", err) - } else { - env.On("runCommand", "dotnet", []string{"--version"}).Return(args.version, nil) - } - - env.On("hasFiles", "*.cs").Return(true) - env.On("getPathSeperator", nil).Return("") - env.On("getcwd", nil).Return("/usr/home/project") - env.On("homeDir", nil).Return("/usr/home") - props := &properties{ - values: map[Property]interface{}{ - DisplayVersion: args.displayVersion, - UnsupportedDotnetVersionIcon: args.unsupportedIcon, - }, - } - dotnet := &dotnet{} - dotnet.init(props, env) - return dotnet -} - -func TestEnabledDotnetNotFound(t *testing.T) { - args := &dotnetArgs{ - enabled: false, - } - dotnet := bootStrapDotnetTest(args) - assert.True(t, dotnet.enabled()) -} - -func TestDotnetVersionNotDisplayed(t *testing.T) { - args := &dotnetArgs{ - enabled: true, - displayVersion: false, - version: "3.1.402", - } - dotnet := bootStrapDotnetTest(args) - assert.True(t, dotnet.enabled()) - assert.Equal(t, "", dotnet.string()) -} - -func TestDotnetVersionDisplayed(t *testing.T) { - expected := "3.1.402" - args := &dotnetArgs{ - enabled: true, - displayVersion: true, - version: expected, - } - dotnet := bootStrapDotnetTest(args) - assert.True(t, dotnet.enabled()) - assert.Equal(t, expected, dotnet.string()) -} - -func TestDotnetVersionUnsupported(t *testing.T) { - expected := "x" - args := &dotnetArgs{ - enabled: true, - displayVersion: true, - unsupported: true, - unsupportedIcon: expected, - } - dotnet := bootStrapDotnetTest(args) - assert.True(t, dotnet.enabled()) - assert.Equal(t, expected, dotnet.string()) -} diff --git a/src/segment_envar.go b/src/segment_envar.go deleted file mode 100644 index d403ab2639df..000000000000 --- a/src/segment_envar.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -type envvar struct { - props *properties - env environmentInfo - content string -} - -const ( - // VarName name of the variable - VarName Property = "var_name" -) - -func (e *envvar) enabled() bool { - name := e.props.getString(VarName, "") - e.content = e.env.getenv(name) - return e.content != "" -} - -func (e *envvar) string() string { - return e.content -} - -func (e *envvar) init(props *properties, env environmentInfo) { - e.props = props - e.env = env -} diff --git a/src/segment_envar_test.go b/src/segment_envar_test.go deleted file mode 100644 index 99c2bf643d87..000000000000 --- a/src/segment_envar_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestEnvvarAvailable(t *testing.T) { - name := "HERP" - expected := "derp" - env := new(MockedEnvironment) - env.On("getenv", name).Return(expected) - props := &properties{ - values: map[Property]interface{}{ - VarName: name, - }, - } - e := &envvar{ - env: env, - props: props, - } - assert.True(t, e.enabled()) - assert.Equal(t, expected, e.string()) -} - -func TestEnvvarNotAvailable(t *testing.T) { - name := "HERP" - expected := "" - env := new(MockedEnvironment) - env.On("getenv", name).Return(expected) - props := &properties{ - values: map[Property]interface{}{ - VarName: name, - }, - } - e := &envvar{ - env: env, - props: props, - } - assert.False(t, e.enabled()) -} diff --git a/src/segment_executiontime.go b/src/segment_executiontime.go deleted file mode 100644 index 6a32dbaa1d2d..000000000000 --- a/src/segment_executiontime.go +++ /dev/null @@ -1,206 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - - lang "golang.org/x/text/language" - "golang.org/x/text/message" -) - -type executiontime struct { - props *properties - env environmentInfo - output string -} - -// DurationStyle how to display the time -type DurationStyle string - -const ( - // ThresholdProperty represents minimum duration (milliseconds) required to enable this segment - ThresholdProperty Property = "threshold" - // Austin milliseconds short - Austin DurationStyle = "austin" - // Roundrock milliseconds long - Roundrock DurationStyle = "roundrock" - // Dallas milliseconds full - Dallas DurationStyle = "dallas" - // Galveston hour - Galveston DurationStyle = "galveston" - // Houston hour and milliseconds - Houston DurationStyle = "houston" - // Amarillo seconds - Amarillo DurationStyle = "amarillo" - // Round will round the output of the format - Round DurationStyle = "round" - - second = 1000 - minute = 60000 - hour = 3600000 - day = 86400000 - secondsPerMinute = 60 - minutesPerHour = 60 - hoursPerDay = 24 -) - -func (t *executiontime) enabled() bool { - alwaysEnabled := t.props.getBool(AlwaysEnabled, false) - executionTimeMs := t.env.executionTime() - thresholdMs := t.props.getFloat64(ThresholdProperty, float64(500)) - if !alwaysEnabled && executionTimeMs < thresholdMs { - return false - } - style := DurationStyle(t.props.getString(Style, string(Austin))) - t.output = t.formatDuration(int64(executionTimeMs), style) - - return t.output != "" -} - -func (t *executiontime) string() string { - return t.output -} - -func (t *executiontime) init(props *properties, env environmentInfo) { - t.props = props - t.env = env -} - -func (t *executiontime) formatDuration(ms int64, style DurationStyle) string { - switch style { - case Austin: - return t.formatDurationAustin(ms) - case Roundrock: - return t.formatDurationRoundrock(ms) - case Dallas: - return t.formatDurationDallas(ms) - case Galveston: - return t.formatDurationGalveston(ms) - case Houston: - return t.formatDurationHouston(ms) - case Amarillo: - return t.formatDurationAmarillo(ms) - case Round: - return t.formatDurationRound(ms) - default: - return fmt.Sprintf("Style: %s is not available", style) - } -} - -func (t *executiontime) formatDurationAustin(ms int64) string { - if ms < second { - return fmt.Sprintf("%dms", ms%second) - } - - seconds := float64(ms%minute) / second - result := strconv.FormatFloat(seconds, 'f', -1, 64) + "s" - - if ms >= minute { - result = fmt.Sprintf("%dm %s", ms/minute%secondsPerMinute, result) - } - if ms >= hour { - result = fmt.Sprintf("%dh %s", ms/hour%hoursPerDay, result) - } - if ms >= day { - result = fmt.Sprintf("%dd %s", ms/day, result) - } - return result -} - -func (t *executiontime) formatDurationRoundrock(ms int64) string { - result := fmt.Sprintf("%dms", ms%second) - if ms >= second { - result = fmt.Sprintf("%ds %s", ms/second%secondsPerMinute, result) - } - if ms >= minute { - result = fmt.Sprintf("%dm %s", ms/minute%minutesPerHour, result) - } - if ms >= hour { - result = fmt.Sprintf("%dh %s", ms/hour%hoursPerDay, result) - } - if ms >= day { - result = fmt.Sprintf("%dd %s", ms/day, result) - } - return result -} - -func (t *executiontime) formatDurationDallas(ms int64) string { - seconds := float64(ms%minute) / second - result := strconv.FormatFloat(seconds, 'f', -1, 64) - - if ms >= minute { - result = fmt.Sprintf("%d:%s", ms/minute%minutesPerHour, result) - } - if ms >= hour { - result = fmt.Sprintf("%d:%s", ms/hour%hoursPerDay, result) - } - if ms >= day { - result = fmt.Sprintf("%d:%s", ms/day, result) - } - return result -} - -func (t *executiontime) formatDurationGalveston(ms int64) string { - result := fmt.Sprintf("%02d:%02d:%02d", ms/hour, ms/minute%minutesPerHour, ms%minute/second) - return result -} - -func (t *executiontime) formatDurationHouston(ms int64) string { - milliseconds := ".0" - if ms%second > 0 { - // format milliseconds as a string with truncated trailing zeros - milliseconds = strconv.FormatFloat(float64(ms%second)/second, 'f', -1, 64) - // at this point milliseconds looks like "0.5". remove the leading "0" - milliseconds = milliseconds[1:] - } - - result := fmt.Sprintf("%02d:%02d:%02d%s", ms/hour, ms/minute%minutesPerHour, ms%minute/second, milliseconds) - return result -} - -func (t *executiontime) formatDurationAmarillo(ms int64) string { - // wholeNumber represents the value to the left of the decimal point (seconds) - wholeNumber := ms / second - // decimalNumber represents the value to the right of the decimal point (milliseconds) - decimalNumber := float64(ms%second) / second - - // format wholeNumber as a string with thousands separators - printer := message.NewPrinter(lang.English) - result := printer.Sprintf("%d", wholeNumber) - - if decimalNumber > 0 { - // format decimalNumber as a string with truncated trailing zeros - decimalResult := strconv.FormatFloat(decimalNumber, 'f', -1, 64) - // at this point decimalResult looks like "0.5" - // remove the leading "0" and append - result += decimalResult[1:] - } - result += "s" - - return result -} - -func (t *executiontime) formatDurationRound(ms int64) string { - toRoundString := func(one, two int64, oneText, twoText string) string { - if two == 0 { - return fmt.Sprintf("%d%s", one, oneText) - } - return fmt.Sprintf("%d%s %d%s", one, oneText, two, twoText) - } - hours := ms / hour % hoursPerDay - if ms >= day { - return toRoundString(ms/day, hours, "d", "h") - } - minutes := ms / minute % secondsPerMinute - if ms >= hour { - return toRoundString(hours, minutes, "h", "m") - } - seconds := (ms % minute) / second - if ms >= minute { - return toRoundString(minutes, seconds, "m", "s") - } - if ms >= second { - return fmt.Sprintf("%ds", seconds) - } - return fmt.Sprintf("%dms", ms%second) -} diff --git a/src/segment_executiontime_test.go b/src/segment_executiontime_test.go deleted file mode 100644 index ab0693901656..000000000000 --- a/src/segment_executiontime_test.go +++ /dev/null @@ -1,255 +0,0 @@ -package main - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestExecutionTimeWriterDefaultThresholdEnabled(t *testing.T) { - env := new(MockedEnvironment) - env.On("executionTime", nil).Return(1337) - executionTime := &executiontime{ - env: env, - } - assert.True(t, executionTime.enabled()) -} - -func TestExecutionTimeWriterDefaultThresholdDisabled(t *testing.T) { - env := new(MockedEnvironment) - env.On("executionTime", nil).Return(1) - executionTime := &executiontime{ - env: env, - } - assert.False(t, executionTime.enabled()) -} - -func TestExecutionTimeWriterCustomThresholdEnabled(t *testing.T) { - env := new(MockedEnvironment) - env.On("executionTime", nil).Return(99) - props := &properties{ - values: map[Property]interface{}{ - ThresholdProperty: float64(10), - }, - } - executionTime := &executiontime{ - env: env, - props: props, - } - assert.True(t, executionTime.enabled()) -} - -func TestExecutionTimeWriterCustomThresholdDisabled(t *testing.T) { - env := new(MockedEnvironment) - env.On("executionTime", nil).Return(99) - props := &properties{ - values: map[Property]interface{}{ - ThresholdProperty: float64(100), - }, - } - executionTime := &executiontime{ - env: env, - props: props, - } - assert.False(t, executionTime.enabled()) -} - -func TestExecutionTimeWriterDuration(t *testing.T) { - input := 1337 - expected := "1.337s" - env := new(MockedEnvironment) - env.On("executionTime", nil).Return(input) - executionTime := &executiontime{ - env: env, - } - executionTime.enabled() - assert.Equal(t, expected, executionTime.output) -} - -func TestExecutionTimeWriterDuration2(t *testing.T) { - input := 13371337 - expected := "3h 42m 51.337s" - env := new(MockedEnvironment) - env.On("executionTime", nil).Return(input) - executionTime := &executiontime{ - env: env, - } - executionTime.enabled() - assert.Equal(t, expected, executionTime.output) -} - -func TestExecutionTimeFormatDurationAustin(t *testing.T) { - cases := []struct { - Input string - Expected string - }{ - {Input: "0.001s", Expected: "1ms"}, - {Input: "0.1s", Expected: "100ms"}, - {Input: "1s", Expected: "1s"}, - {Input: "2.1s", Expected: "2.1s"}, - {Input: "1m", Expected: "1m 0s"}, - {Input: "3m2.1s", Expected: "3m 2.1s"}, - {Input: "1h", Expected: "1h 0m 0s"}, - {Input: "4h3m2.1s", Expected: "4h 3m 2.1s"}, - {Input: "124h3m2.1s", Expected: "5d 4h 3m 2.1s"}, - {Input: "124h3m2.0s", Expected: "5d 4h 3m 2s"}, - } - - for _, tc := range cases { - duration, _ := time.ParseDuration(tc.Input) - executionTime := &executiontime{} - output := executionTime.formatDurationAustin(duration.Milliseconds()) - assert.Equal(t, tc.Expected, output) - } -} - -func TestExecutionTimeFormatDurationRoundrock(t *testing.T) { - cases := []struct { - Input string - Expected string - }{ - {Input: "0.001s", Expected: "1ms"}, - {Input: "0.1s", Expected: "100ms"}, - {Input: "1s", Expected: "1s 0ms"}, - {Input: "2.1s", Expected: "2s 100ms"}, - {Input: "1m", Expected: "1m 0s 0ms"}, - {Input: "3m2.1s", Expected: "3m 2s 100ms"}, - {Input: "1h", Expected: "1h 0m 0s 0ms"}, - {Input: "4h3m2.1s", Expected: "4h 3m 2s 100ms"}, - {Input: "124h3m2.1s", Expected: "5d 4h 3m 2s 100ms"}, - {Input: "124h3m2.0s", Expected: "5d 4h 3m 2s 0ms"}, - } - - for _, tc := range cases { - duration, _ := time.ParseDuration(tc.Input) - executionTime := &executiontime{} - output := executionTime.formatDurationRoundrock(duration.Milliseconds()) - assert.Equal(t, tc.Expected, output) - } -} - -func TestExecutionTimeFormatDallas(t *testing.T) { - cases := []struct { - Input string - Expected string - }{ - {Input: "0.001s", Expected: "0.001"}, - {Input: "0.1s", Expected: "0.1"}, - {Input: "1s", Expected: "1"}, - {Input: "2.1s", Expected: "2.1"}, - {Input: "1m", Expected: "1:0"}, - {Input: "3m2.1s", Expected: "3:2.1"}, - {Input: "1h", Expected: "1:0:0"}, - {Input: "4h3m2.1s", Expected: "4:3:2.1"}, - {Input: "124h3m2.1s", Expected: "5:4:3:2.1"}, - {Input: "124h3m2.0s", Expected: "5:4:3:2"}, - } - - for _, tc := range cases { - duration, _ := time.ParseDuration(tc.Input) - executionTime := &executiontime{} - output := executionTime.formatDurationDallas(duration.Milliseconds()) - assert.Equal(t, tc.Expected, output) - } -} - -func TestExecutionTimeFormatGalveston(t *testing.T) { - cases := []struct { - Input string - Expected string - }{ - {Input: "0.001s", Expected: "00:00:00"}, - {Input: "0.1s", Expected: "00:00:00"}, - {Input: "1s", Expected: "00:00:01"}, - {Input: "2.1s", Expected: "00:00:02"}, - {Input: "1m", Expected: "00:01:00"}, - {Input: "3m2.1s", Expected: "00:03:02"}, - {Input: "1h", Expected: "01:00:00"}, - {Input: "4h3m2.1s", Expected: "04:03:02"}, - {Input: "124h3m2.1s", Expected: "124:03:02"}, - {Input: "124h3m2.0s", Expected: "124:03:02"}, - } - - for _, tc := range cases { - duration, _ := time.ParseDuration(tc.Input) - executionTime := &executiontime{} - output := executionTime.formatDurationGalveston(duration.Milliseconds()) - assert.Equal(t, tc.Expected, output) - } -} - -func TestExecutionTimeFormatHouston(t *testing.T) { - cases := []struct { - Input string - Expected string - }{ - {Input: "0.001s", Expected: "00:00:00.001"}, - {Input: "0.1s", Expected: "00:00:00.1"}, - {Input: "1s", Expected: "00:00:01.0"}, - {Input: "2.1s", Expected: "00:00:02.1"}, - {Input: "1m", Expected: "00:01:00.0"}, - {Input: "3m2.1s", Expected: "00:03:02.1"}, - {Input: "1h", Expected: "01:00:00.0"}, - {Input: "4h3m2.1s", Expected: "04:03:02.1"}, - {Input: "124h3m2.1s", Expected: "124:03:02.1"}, - {Input: "124h3m2.0s", Expected: "124:03:02.0"}, - } - - for _, tc := range cases { - duration, _ := time.ParseDuration(tc.Input) - executionTime := &executiontime{} - output := executionTime.formatDurationHouston(duration.Milliseconds()) - assert.Equal(t, tc.Expected, output) - } -} - -func TestExecutionTimeFormatAmarillo(t *testing.T) { - cases := []struct { - Input string - Expected string - }{ - {Input: "0.001s", Expected: "0.001s"}, - {Input: "0.1s", Expected: "0.1s"}, - {Input: "1s", Expected: "1s"}, - {Input: "2.1s", Expected: "2.1s"}, - {Input: "1m", Expected: "60s"}, - {Input: "3m2.1s", Expected: "182.1s"}, - {Input: "1h", Expected: "3,600s"}, - {Input: "4h3m2.1s", Expected: "14,582.1s"}, - {Input: "124h3m2.1s", Expected: "446,582.1s"}, - {Input: "124h3m2.0s", Expected: "446,582s"}, - } - - for _, tc := range cases { - duration, _ := time.ParseDuration(tc.Input) - executionTime := &executiontime{} - output := executionTime.formatDurationAmarillo(duration.Milliseconds()) - assert.Equal(t, tc.Expected, output) - } -} - -func TestExecutionTimeFormatDurationRound(t *testing.T) { - cases := []struct { - Input string - Expected string - }{ - {Input: "0.001s", Expected: "1ms"}, - {Input: "0.1s", Expected: "100ms"}, - {Input: "1s", Expected: "1s"}, - {Input: "2.1s", Expected: "2s"}, - {Input: "1m", Expected: "1m"}, - {Input: "3m2.1s", Expected: "3m 2s"}, - {Input: "1h", Expected: "1h"}, - {Input: "4h3m2.1s", Expected: "4h 3m"}, - {Input: "124h3m2.1s", Expected: "5d 4h"}, - {Input: "124h3m2.0s", Expected: "5d 4h"}, - } - - for _, tc := range cases { - duration, _ := time.ParseDuration(tc.Input) - executionTime := &executiontime{} - output := executionTime.formatDurationRound(duration.Milliseconds()) - assert.Equal(t, tc.Expected, output) - } -} diff --git a/src/segment_exit.go b/src/segment_exit.go deleted file mode 100644 index 6cd9171d26c0..000000000000 --- a/src/segment_exit.go +++ /dev/null @@ -1,117 +0,0 @@ -package main - -import "fmt" - -type exit struct { - props *properties - env environmentInfo -} - -const ( - // DisplayExitCode shows or hides the error code - DisplayExitCode Property = "display_exit_code" - // ErrorColor specify a different foreground color for the error text when using always_show = true - ErrorColor Property = "error_color" - // AlwaysNumeric shows error codes as numbers - AlwaysNumeric Property = "always_numeric" - // SuccessIcon displays when there's no error and AlwaysEnabled = true - SuccessIcon Property = "success_icon" - // ErrorIcon displays when there's an error - ErrorIcon Property = "error_icon" -) - -func (e *exit) enabled() bool { - if e.props.getBool(AlwaysEnabled, false) { - return true - } - return e.env.lastErrorCode() != 0 -} - -func (e *exit) string() string { - return e.getFormattedText() -} - -func (e *exit) init(props *properties, env environmentInfo) { - e.props = props - e.env = env -} - -func (e *exit) getFormattedText() string { - exitCode := e.getMeaningFromExitCode() - colorBackground := e.props.getBool(ColorBackground, false) - if e.env.lastErrorCode() != 0 && !colorBackground { - e.props.foreground = e.props.getColor(ErrorColor, e.props.foreground) - } - if e.env.lastErrorCode() != 0 && colorBackground { - e.props.background = e.props.getColor(ErrorColor, e.props.background) - } - if e.env.lastErrorCode() == 0 { - return e.props.getString(SuccessIcon, "") - } - return fmt.Sprintf("%s%s", e.props.getString(ErrorIcon, ""), exitCode) -} - -func (e *exit) getMeaningFromExitCode() string { - if !e.props.getBool(DisplayExitCode, true) { - return "" - } - if e.props.getBool(AlwaysNumeric, false) { - return fmt.Sprintf("%d", e.env.lastErrorCode()) - } - switch e.env.lastErrorCode() { - case 1: - return "ERROR" - case 2: - return "USAGE" - case 126: - return "NOPERM" - case 127: - return "NOTFOUND" - case 128 + 1: - return "SIGHUP" - case 128 + 2: - return "SIGINT" - case 128 + 3: - return "SIGQUIT" - case 128 + 4: - return "SIGILL" - case 128 + 5: - return "SIGTRAP" - case 128 + 6: - return "SIGIOT" - case 128 + 7: - return "SIGBUS" - case 128 + 8: - return "SIGFPE" - case 128 + 9: - return "SIGKILL" - case 128 + 10: - return "SIGUSR1" - case 128 + 11: - return "SIGSEGV" - case 128 + 12: - return "SIGUSR2" - case 128 + 13: - return "SIGPIPE" - case 128 + 14: - return "SIGALRM" - case 128 + 15: - return "SIGTERM" - case 128 + 16: - return "SIGSTKFLT" - case 128 + 17: - return "SIGCHLD" - case 128 + 18: - return "SIGCONT" - case 128 + 19: - return "SIGSTOP" - case 128 + 20: - return "SIGTSTP" - case 128 + 21: - return "SIGTTIN" - case 128 + 22: - return "SIGTTOU" - default: - return fmt.Sprintf("%d", e.env.lastErrorCode()) - } -} diff --git a/src/segment_exit_test.go b/src/segment_exit_test.go deleted file mode 100644 index e78dfa1be4c4..000000000000 --- a/src/segment_exit_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestExitWriterEnabled(t *testing.T) { - cases := []struct { - ExitCode int - Expected bool - }{ - {ExitCode: 102, Expected: true}, - {ExitCode: 0, Expected: false}, - {ExitCode: -1, Expected: true}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("lastErrorCode", nil).Return(tc.ExitCode) - e := &exit{ - env: env, - } - assert.Equal(t, tc.Expected, e.enabled()) - } -} - -func TestExitWriterFormattedText(t *testing.T) { - cases := []struct { - ExitCode int - Expected string - SuccessIcon string - ErrorIcon string - DisplayExitCode bool - AlwaysNumeric bool - }{ - {ExitCode: 129, Expected: "SIGHUP", DisplayExitCode: true}, - {ExitCode: 5001, Expected: "5001", DisplayExitCode: true}, - {ExitCode: 147, Expected: "SIGSTOP", DisplayExitCode: true}, - {ExitCode: 147, Expected: "", DisplayExitCode: false}, - {ExitCode: 147, Expected: "147", DisplayExitCode: true, AlwaysNumeric: true}, - {ExitCode: 0, Expected: "wooopie", SuccessIcon: "wooopie"}, - {ExitCode: 129, Expected: "err SIGHUP", ErrorIcon: "err ", DisplayExitCode: true}, - {ExitCode: 129, Expected: "err", ErrorIcon: "err", DisplayExitCode: false}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("lastErrorCode", nil).Return(tc.ExitCode) - props := &properties{ - foreground: "#111111", - background: "#ffffff", - values: map[Property]interface{}{ - SuccessIcon: tc.SuccessIcon, - ErrorIcon: tc.ErrorIcon, - DisplayExitCode: tc.DisplayExitCode, - AlwaysNumeric: tc.AlwaysNumeric, - }, - } - e := &exit{ - env: env, - props: props, - } - assert.Equal(t, tc.Expected, e.getFormattedText()) - } -} - -func TestGetMeaningFromExitCode(t *testing.T) { - errorMap := make(map[int]string) - errorMap[1] = "ERROR" - errorMap[2] = "USAGE" - errorMap[126] = "NOPERM" - errorMap[127] = "NOTFOUND" - errorMap[129] = "SIGHUP" - errorMap[130] = "SIGINT" - errorMap[131] = "SIGQUIT" - errorMap[132] = "SIGILL" - errorMap[133] = "SIGTRAP" - errorMap[134] = "SIGIOT" - errorMap[135] = "SIGBUS" - errorMap[136] = "SIGFPE" - errorMap[137] = "SIGKILL" - errorMap[138] = "SIGUSR1" - errorMap[139] = "SIGSEGV" - errorMap[140] = "SIGUSR2" - errorMap[141] = "SIGPIPE" - errorMap[142] = "SIGALRM" - errorMap[143] = "SIGTERM" - errorMap[144] = "SIGSTKFLT" - errorMap[145] = "SIGCHLD" - errorMap[146] = "SIGCONT" - errorMap[147] = "SIGSTOP" - errorMap[148] = "SIGTSTP" - errorMap[149] = "SIGTTIN" - errorMap[150] = "SIGTTOU" - errorMap[151] = "151" - errorMap[7000] = "7000" - for exitcode, want := range errorMap { - env := new(MockedEnvironment) - env.On("lastErrorCode", nil).Return(exitcode) - e := &exit{ - env: env, - } - assert.Equal(t, want, e.getMeaningFromExitCode()) - } -} - -func TestAlwaysNumericExitCode(t *testing.T) { - env := new(MockedEnvironment) - env.On("lastErrorCode", nil).Return(1) - props := &properties{ - values: map[Property]interface{}{ - AlwaysNumeric: true, - }, - } - e := &exit{ - env: env, - props: props, - } - assert.Equal(t, "1", e.getMeaningFromExitCode()) -} diff --git a/src/segment_git.go b/src/segment_git.go deleted file mode 100644 index 37540a67388b..000000000000 --- a/src/segment_git.go +++ /dev/null @@ -1,434 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "strconv" - "strings" -) - -type gitRepo struct { - working *gitStatus - staging *gitStatus - ahead int - behind int - HEAD string - upstream string - stashCount int - gitFolder string -} - -type gitStatus struct { - unmerged int - deleted int - added int - modified int - changed bool -} - -func (s *gitStatus) string() string { - var status string - stringIfValue := func(value int, prefix string) string { - if value > 0 { - return fmt.Sprintf(" %s%d", prefix, value) - } - return "" - } - status += stringIfValue(s.added, "+") - status += stringIfValue(s.modified, "~") - status += stringIfValue(s.deleted, "-") - status += stringIfValue(s.unmerged, "x") - return status -} - -type git struct { - props *properties - env environmentInfo - repo *gitRepo -} - -const ( - // BranchIcon the icon to use as branch indicator - BranchIcon Property = "branch_icon" - // DisplayBranchStatus show branch status or not - DisplayBranchStatus Property = "display_branch_status" - // BranchIdenticalIcon the icon to display when the remote and local branch are identical - BranchIdenticalIcon Property = "branch_identical_icon" - // BranchAheadIcon the icon to display when the local branch is ahead of the remote - BranchAheadIcon Property = "branch_ahead_icon" - // BranchBehindIcon the icon to display when the local branch is behind the remote - BranchBehindIcon Property = "branch_behind_icon" - // BranchGoneIcon the icon to use when ther's no remote - BranchGoneIcon Property = "branch_gone_icon" - // LocalWorkingIcon the icon to use as the local working area changes indicator - LocalWorkingIcon Property = "local_working_icon" - // LocalStagingIcon the icon to use as the local staging area changes indicator - LocalStagingIcon Property = "local_staged_icon" - // DisplayStatus shows the status of the repository - DisplayStatus Property = "display_status" - // DisplayStatusDetail shows the detailed status of the repository - DisplayStatusDetail Property = "display_status_detail" - // RebaseIcon shows before the rebase context - RebaseIcon Property = "rebase_icon" - // CherryPickIcon shows before the cherry-pick context - CherryPickIcon Property = "cherry_pick_icon" - // CommitIcon shows before the detached context - CommitIcon Property = "commit_icon" - // NoCommitsIcon shows when there are no commits in the repo yet - NoCommitsIcon Property = "no_commits_icon" - // TagIcon shows before the tag context - TagIcon Property = "tag_icon" - // DisplayStashCount show stash count or not - DisplayStashCount Property = "display_stash_count" - // StashCountIcon shows before the stash context - StashCountIcon Property = "stash_count_icon" - // StatusSeparatorIcon shows between staging and working area - StatusSeparatorIcon Property = "status_separator_icon" - // MergeIcon shows before the merge context - MergeIcon Property = "merge_icon" - // DisplayUpstreamIcon show or hide the upstream icon - DisplayUpstreamIcon Property = "display_upstream_icon" - // GithubIcon shows√ when upstream is github - GithubIcon Property = "github_icon" - // BitbucketIcon shows when upstream is bitbucket - BitbucketIcon Property = "bitbucket_icon" - // AzureDevOpsIcon shows when upstream is azure devops - AzureDevOpsIcon Property = "azure_devops_icon" - // GitlabIcon shows when upstream is gitlab - GitlabIcon Property = "gitlab_icon" - // GitIcon shows when the upstream can't be identified - GitIcon Property = "git_icon" - // WorkingColor if set, the color to use on the working area - WorkingColor Property = "working_color" - // StagingColor if set, the color to use on the staging area - StagingColor Property = "staging_color" - // StatusColorsEnabled enables status colors - StatusColorsEnabled Property = "status_colors_enabled" - // LocalChangesColor if set, the color to use when there are local changes - LocalChangesColor Property = "local_changes_color" - // AheadAndBehindColor if set, the color to use when the branch is ahead and behind the remote - AheadAndBehindColor Property = "ahead_and_behind_color" - // BehindColor if set, the color to use when the branch is ahead and behind the remote - BehindColor Property = "behind_color" - // AheadColor if set, the color to use when the branch is ahead and behind the remote - AheadColor Property = "ahead_color" - // BranchMaxLength truncates the length of the branch name - BranchMaxLength Property = "branch_max_length" -) - -func (g *git) enabled() bool { - if !g.env.hasCommand("git") { - return false - } - gitdir, err := g.env.hasParentFilePath(".git") - if err != nil { - return false - } - g.repo = &gitRepo{} - if gitdir.isDir { - g.repo.gitFolder = gitdir.path - return true - } - // handle worktree - dirPointer := g.env.getFileContent(gitdir.path) - dirPointer = strings.Trim(dirPointer, " \r\n") - matches := findNamedRegexMatch(`^gitdir: (?P.*)$`, dirPointer) - if matches != nil && matches["dir"] != "" { - g.repo.gitFolder = matches["dir"] - return true - } - return false -} - -func (g *git) string() string { - statusColorsEnabled := g.props.getBool(StatusColorsEnabled, false) - displayStatus := g.props.getBool(DisplayStatus, false) - - if displayStatus || statusColorsEnabled { - g.setGitStatus() - } - if statusColorsEnabled { - g.SetStatusColor() - } - if !displayStatus { - return g.getPrettyHEADName() - } - buffer := new(bytes.Buffer) - // remote (if available) - if g.repo.upstream != "" && g.props.getBool(DisplayUpstreamIcon, false) { - fmt.Fprintf(buffer, "%s", g.getUpstreamSymbol()) - } - // branchName - fmt.Fprintf(buffer, "%s", g.repo.HEAD) - if g.props.getBool(DisplayBranchStatus, true) { - buffer.WriteString(g.getBranchStatus()) - } - if g.repo.staging.changed { - fmt.Fprint(buffer, g.getStatusDetailString(g.repo.staging, StagingColor, LocalStagingIcon, " \uF046")) - } - if g.repo.staging.changed && g.repo.working.changed { - fmt.Fprint(buffer, g.props.getString(StatusSeparatorIcon, " |")) - } - if g.repo.working.changed { - fmt.Fprint(buffer, g.getStatusDetailString(g.repo.working, WorkingColor, LocalWorkingIcon, " \uF044")) - } - if g.repo.stashCount != 0 { - fmt.Fprintf(buffer, " %s%d", g.props.getString(StashCountIcon, "\uF692 "), g.repo.stashCount) - } - return buffer.String() -} - -func (g *git) init(props *properties, env environmentInfo) { - g.props = props - g.env = env -} - -func (g *git) getBranchStatus() string { - if g.repo.ahead > 0 && g.repo.behind > 0 { - return fmt.Sprintf(" %s%d %s%d", g.props.getString(BranchAheadIcon, "\u2191"), g.repo.ahead, g.props.getString(BranchBehindIcon, "\u2193"), g.repo.behind) - } - if g.repo.ahead > 0 { - return fmt.Sprintf(" %s%d", g.props.getString(BranchAheadIcon, "\u2191"), g.repo.ahead) - } - if g.repo.behind > 0 { - return fmt.Sprintf(" %s%d", g.props.getString(BranchBehindIcon, "\u2193"), g.repo.behind) - } - if g.repo.behind == 0 && g.repo.ahead == 0 && g.repo.upstream != "" { - return fmt.Sprintf(" %s", g.props.getString(BranchIdenticalIcon, "\u2261")) - } - if g.repo.upstream == "" { - return fmt.Sprintf(" %s", g.props.getString(BranchGoneIcon, "\u2262")) - } - return "" -} - -func (g *git) getStatusDetailString(status *gitStatus, color, icon Property, defaultIcon string) string { - prefix := g.props.getString(icon, defaultIcon) - foregroundColor := g.props.getColor(color, g.props.foreground) - if !g.props.getBool(DisplayStatusDetail, true) { - return g.colorStatusString(prefix, "", foregroundColor) - } - return g.colorStatusString(prefix, status.string(), foregroundColor) -} - -func (g *git) colorStatusString(prefix, status, color string) string { - if color == g.props.foreground { - return fmt.Sprintf("%s%s", prefix, status) - } - if strings.Contains(prefix, "") { - return fmt.Sprintf("%s<%s>%s", prefix, color, status) - } - return fmt.Sprintf("<%s>%s%s", color, prefix, status) -} - -func (g *git) getUpstreamSymbol() string { - upstream := replaceAllString("/.*", g.repo.upstream, "") - url := g.getGitCommandOutput("remote", "get-url", upstream) - if strings.Contains(url, "github") { - return g.props.getString(GithubIcon, "\uF408 ") - } - if strings.Contains(url, "gitlab") { - return g.props.getString(GitlabIcon, "\uF296 ") - } - if strings.Contains(url, "bitbucket") { - return g.props.getString(BitbucketIcon, "\uF171 ") - } - if strings.Contains(url, "dev.azure.com") || strings.Contains(url, "visualstudio.com") { - return g.props.getString(AzureDevOpsIcon, "\uFD03 ") - } - return g.props.getString(GitIcon, "\uE5FB ") -} - -func (g *git) setGitStatus() { - output := g.getGitCommandOutput("status", "-unormal", "--short", "--branch") - splittedOutput := strings.Split(output, "\n") - g.repo.working = g.parseGitStats(splittedOutput, true) - g.repo.staging = g.parseGitStats(splittedOutput, false) - status := g.parseGitStatusInfo(splittedOutput[0]) - if status["local"] != "" { - g.repo.ahead, _ = strconv.Atoi(status["ahead"]) - g.repo.behind, _ = strconv.Atoi(status["behind"]) - if status["upstream_status"] != "gone" { - g.repo.upstream = status["upstream"] - } - } - g.repo.HEAD = g.getGitHEADContext(status["local"]) - if g.props.getBool(DisplayStashCount, false) { - g.repo.stashCount = g.getStashContext() - } -} - -func (g *git) SetStatusColor() { - if g.props.getBool(ColorBackground, true) { - g.props.background = g.getStatusColor(g.props.background) - } else { - g.props.foreground = g.getStatusColor(g.props.foreground) - } -} - -func (g *git) getStatusColor(defaultValue string) string { - if g.repo.staging.changed || g.repo.working.changed { - return g.props.getColor(LocalChangesColor, defaultValue) - } else if g.repo.ahead > 0 && g.repo.behind > 0 { - return g.props.getColor(AheadAndBehindColor, defaultValue) - } else if g.repo.ahead > 0 { - return g.props.getColor(AheadColor, defaultValue) - } else if g.repo.behind > 0 { - return g.props.getColor(BehindColor, defaultValue) - } - return defaultValue -} - -func (g *git) getGitCommandOutput(args ...string) string { - inWSLSharedDrive := func(env environmentInfo) bool { - return env.isWsl() && strings.HasPrefix(env.getcwd(), "/mnt/") - } - gitCommand := "git" - if g.env.getRuntimeGOOS() == windowsPlatform || inWSLSharedDrive(g.env) { - gitCommand = "git.exe" - } - args = append([]string{"--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...) - val, _ := g.env.runCommand(gitCommand, args...) - return val -} - -func (g *git) getGitHEADContext(ref string) string { - branchIcon := g.props.getString(BranchIcon, "\uE0A0") - if ref == "" { - ref = g.getPrettyHEADName() - } else { - ref = g.truncateBranch(ref) - ref = fmt.Sprintf("%s%s", branchIcon, ref) - } - // rebase - if g.hasGitFolder("rebase-merge") { - head := g.getGitFileContents("rebase-merge/head-name") - origin := strings.Replace(head, "refs/heads/", "", 1) - origin = g.truncateBranch(origin) - onto := g.getGitRefFileSymbolicName("rebase-merge/onto") - onto = g.truncateBranch(onto) - step := g.getGitFileContents("rebase-merge/msgnum") - total := g.getGitFileContents("rebase-merge/end") - icon := g.props.getString(RebaseIcon, "\uE728 ") - return fmt.Sprintf("%s%s%s onto %s%s (%s/%s) at %s", icon, branchIcon, origin, branchIcon, onto, step, total, ref) - } - if g.hasGitFolder("rebase-apply") { - head := g.getGitFileContents("rebase-apply/head-name") - origin := strings.Replace(head, "refs/heads/", "", 1) - origin = g.truncateBranch(origin) - step := g.getGitFileContents("rebase-apply/next") - total := g.getGitFileContents("rebase-apply/last") - icon := g.props.getString(RebaseIcon, "\uE728 ") - return fmt.Sprintf("%s%s%s (%s/%s) at %s", icon, branchIcon, origin, step, total, ref) - } - // merge - if g.hasGitFile("MERGE_MSG") && g.hasGitFile("MERGE_HEAD") { - icon := g.props.getString(MergeIcon, "\uE727 ") - mergeContext := g.getGitFileContents("MERGE_MSG") - matches := findNamedRegexMatch(`Merge branch '(?P.*)' into`, mergeContext) - if matches != nil && matches["head"] != "" { - branch := g.truncateBranch(matches["head"]) - return fmt.Sprintf("%s%s%s into %s", icon, branchIcon, branch, ref) - } - } - // cherry-pick - if g.hasGitFile("CHERRY_PICK_HEAD") { - sha := g.getGitFileContents("CHERRY_PICK_HEAD") - icon := g.props.getString(CherryPickIcon, "\uE29B ") - return fmt.Sprintf("%s%s onto %s", icon, sha[0:6], ref) - } - return ref -} - -func (g *git) truncateBranch(branch string) string { - maxLength := g.props.getInt(BranchMaxLength, 0) - if maxLength == 0 { - return branch - } - return branch[0:maxLength] -} - -func (g *git) hasGitFile(file string) bool { - return g.env.hasFilesInDir(g.repo.gitFolder, file) -} - -func (g *git) hasGitFolder(folder string) bool { - path := g.repo.gitFolder + "/" + folder - return g.env.hasFolder(path) -} - -func (g *git) getGitFileContents(file string) string { - path := g.repo.gitFolder + "/" + file - content := g.env.getFileContent(path) - return strings.Trim(content, " \r\n") -} - -func (g *git) getGitRefFileSymbolicName(refFile string) string { - ref := g.getGitFileContents(refFile) - return g.getGitCommandOutput("name-rev", "--name-only", "--exclude=tags/*", ref) -} - -func (g *git) getPrettyHEADName() string { - ref := g.getGitCommandOutput("branch", "--show-current") - if ref != "" { - ref = g.truncateBranch(ref) - return fmt.Sprintf("%s%s", g.props.getString(BranchIcon, "\uE0A0"), ref) - } - // check for tag - ref = g.getGitCommandOutput("describe", "--tags", "--exact-match") - if ref != "" { - return fmt.Sprintf("%s%s", g.props.getString(TagIcon, "\uF412"), ref) - } - // fallback to commit - ref = g.getGitCommandOutput("rev-parse", "--short", "HEAD") - if ref == "" { - return g.props.getString(NoCommitsIcon, "\uF594 ") - } - return fmt.Sprintf("%s%s", g.props.getString(CommitIcon, "\uF417"), ref) -} - -func (g *git) parseGitStats(output []string, working bool) *gitStatus { - status := gitStatus{} - if len(output) <= 1 { - return &status - } - for _, line := range output[1:] { - if len(line) < 2 { - continue - } - code := line[0:1] - if working { - code = line[1:2] - } - switch code { - case "?": - if working { - status.added++ - } - case "D": - status.deleted++ - case "A": - status.added++ - case "U": - status.unmerged++ - case "M", "R", "C", "m": - status.modified++ - } - } - status.changed = status.added > 0 || status.deleted > 0 || status.modified > 0 || status.unmerged > 0 - return &status -} - -func (g *git) getStashContext() int { - stashContent := g.getGitFileContents("logs/refs/stash") - if stashContent == "" { - return 0 - } - lines := strings.Split(stashContent, "\n") - return len(lines) -} - -func (g *git) parseGitStatusInfo(branchInfo string) map[string]string { - var branchRegex = `^## (?P\S+?)(\.{3}(?P\S+?)( \[(?P(ahead (?P\d+)(, )?)?(behind (?P\d+))?(gone)?)])?)?$` - return findNamedRegexMatch(branchRegex, branchInfo) -} diff --git a/src/segment_git_test.go b/src/segment_git_test.go deleted file mode 100644 index 857125ede0c3..000000000000 --- a/src/segment_git_test.go +++ /dev/null @@ -1,796 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -const ( - changesColor = "#BD8BDE" -) - -func TestEnabledGitNotFound(t *testing.T) { - env := new(MockedEnvironment) - env.On("hasCommand", "git").Return(false) - g := &git{ - env: env, - } - assert.False(t, g.enabled()) -} - -func TestEnabledInWorkingDirectory(t *testing.T) { - env := new(MockedEnvironment) - env.On("hasCommand", "git").Return(true) - fileInfo := &fileInfo{ - path: "/dir/hello", - parentFolder: "/dir", - isDir: true, - } - env.On("hasParentFilePath", ".git").Return(fileInfo, nil) - g := &git{ - env: env, - } - assert.True(t, g.enabled()) - assert.Equal(t, fileInfo.path, g.repo.gitFolder) -} - -func TestEnabledInWorkingTree(t *testing.T) { - env := new(MockedEnvironment) - env.On("hasCommand", "git").Return(true) - fileInfo := &fileInfo{ - path: "/dir/hello", - parentFolder: "/dir", - isDir: false, - } - env.On("hasParentFilePath", ".git").Return(fileInfo, nil) - env.On("getFileContent", "/dir/hello").Return("gitdir: /dir/hello/burp/burp") - g := &git{ - env: env, - } - assert.True(t, g.enabled()) - assert.Equal(t, "/dir/hello/burp/burp", g.repo.gitFolder) -} - -func TestGetGitOutputForCommand(t *testing.T) { - args := []string{"--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"} - commandArgs := []string{"symbolic-ref", "--short", "HEAD"} - want := "je suis le output" - env := new(MockedEnvironment) - env.On("runCommand", "git", append(args, commandArgs...)).Return(want, nil) - env.On("getRuntimeGOOS", nil).Return("unix") - g := &git{ - env: env, - } - got := g.getGitCommandOutput(commandArgs...) - assert.Equal(t, want, got) -} - -type detachedContext struct { - currentCommit string - rebase string - rebaseMerge bool - rebaseApply bool - origin string - onto string - step string - total string - branchName string - tagName string - cherryPick bool - cherryPickSHA string - merge bool - mergeHEAD string - status string -} - -func setupHEADContextEnv(context *detachedContext) *git { - env := new(MockedEnvironment) - env.On("hasFolder", "/rebase-merge").Return(context.rebaseMerge) - env.On("hasFolder", "/rebase-apply").Return(context.rebaseApply) - env.On("getFileContent", "/rebase-merge/head-name").Return(context.origin) - env.On("getFileContent", "/rebase-merge/onto").Return(context.onto) - env.On("getFileContent", "/rebase-merge/msgnum").Return(context.step) - env.On("getFileContent", "/rebase-apply/next").Return(context.step) - env.On("getFileContent", "/rebase-merge/end").Return(context.total) - env.On("getFileContent", "/rebase-apply/last").Return(context.total) - env.On("getFileContent", "/rebase-apply/head-name").Return(context.origin) - env.On("getFileContent", "/CHERRY_PICK_HEAD").Return(context.cherryPickSHA) - env.On("getFileContent", "/MERGE_MSG").Return(fmt.Sprintf("Merge branch '%s' into %s", context.mergeHEAD, context.onto)) - env.On("hasFilesInDir", "", "CHERRY_PICK_HEAD").Return(context.cherryPick) - env.On("hasFilesInDir", "", "MERGE_MSG").Return(context.merge) - env.On("hasFilesInDir", "", "MERGE_HEAD").Return(context.merge) - env.mockGitCommand(context.currentCommit, "rev-parse", "--short", "HEAD") - env.mockGitCommand(context.tagName, "describe", "--tags", "--exact-match") - env.mockGitCommand(context.origin, "name-rev", "--name-only", "--exclude=tags/*", context.origin) - env.mockGitCommand(context.onto, "name-rev", "--name-only", "--exclude=tags/*", context.onto) - env.mockGitCommand(context.branchName, "branch", "--show-current") - env.mockGitCommand(context.status, "status", "-unormal", "--short", "--branch") - env.On("getRuntimeGOOS", nil).Return("unix") - g := &git{ - env: env, - repo: &gitRepo{ - gitFolder: "", - }, - } - return g -} - -func (m *MockedEnvironment) mockGitCommand(returnValue string, args ...string) { - args = append([]string{"--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...) - m.On("runCommand", "git", args).Return(returnValue, nil) -} - -func TestGetGitDetachedCommitHash(t *testing.T) { - want := "\uf417lalasha1" - context := &detachedContext{ - currentCommit: "lalasha1", - } - g := setupHEADContextEnv(context) - got := g.getGitHEADContext("") - assert.Equal(t, want, got) -} - -func TestGetGitHEADContextTagName(t *testing.T) { - want := "\uf412lalasha1" - context := &detachedContext{ - currentCommit: "whatever", - tagName: "lalasha1", - } - g := setupHEADContextEnv(context) - got := g.getGitHEADContext("") - assert.Equal(t, want, got) -} - -func TestGetGitHEADContextRebaseMerge(t *testing.T) { - want := "\ue728 \ue0a0cool-feature-bro onto \ue0a0main (2/3) at \uf417whatever" - context := &detachedContext{ - currentCommit: "whatever", - rebase: "true", - rebaseMerge: true, - origin: "cool-feature-bro", - onto: "main", - step: "2", - total: "3", - } - g := setupHEADContextEnv(context) - got := g.getGitHEADContext("") - assert.Equal(t, want, got) -} - -func TestGetGitHEADContextRebaseApply(t *testing.T) { - want := "\ue728 \ue0a0cool-feature-bro (2/3) at \uf417whatever" - context := &detachedContext{ - currentCommit: "whatever", - rebase: "true", - rebaseApply: true, - origin: "cool-feature-bro", - step: "2", - total: "3", - } - g := setupHEADContextEnv(context) - got := g.getGitHEADContext("") - assert.Equal(t, want, got) -} - -func TestGetGitHEADContextRebaseUnknown(t *testing.T) { - want := "\uf417whatever" - context := &detachedContext{ - currentCommit: "whatever", - rebase: "true", - } - g := setupHEADContextEnv(context) - got := g.getGitHEADContext("") - assert.Equal(t, want, got) -} - -func TestGetGitHEADContextCherryPickOnBranch(t *testing.T) { - want := "\ue29b pickme onto \ue0a0main" - context := &detachedContext{ - currentCommit: "whatever", - branchName: "main", - cherryPick: true, - cherryPickSHA: "pickme", - } - g := setupHEADContextEnv(context) - got := g.getGitHEADContext("main") - assert.Equal(t, want, got) -} - -func TestGetGitHEADContextCherryPickOnTag(t *testing.T) { - want := "\ue29b pickme onto \uf412v3.4.6" - context := &detachedContext{ - currentCommit: "whatever", - tagName: "v3.4.6", - cherryPick: true, - cherryPickSHA: "pickme", - } - g := setupHEADContextEnv(context) - got := g.getGitHEADContext("") - assert.Equal(t, want, got) -} - -func TestGetGitHEADContextMerge(t *testing.T) { - want := "\ue727 \ue0a0feat into \ue0a0main" - context := &detachedContext{ - merge: true, - mergeHEAD: "feat", - } - g := setupHEADContextEnv(context) - got := g.getGitHEADContext("main") - assert.Equal(t, want, got) -} - -func TestGetGitHEADContextMergeTag(t *testing.T) { - want := "\ue727 \ue0a0feat into \uf412v3.4.6" - context := &detachedContext{ - tagName: "v3.4.6", - merge: true, - mergeHEAD: "feat", - } - g := setupHEADContextEnv(context) - got := g.getGitHEADContext("") - assert.Equal(t, want, got) -} - -func TestGetStashContextZeroEntries(t *testing.T) { - cases := []struct { - Expected int - StashContent string - }{ - {Expected: 0, StashContent: ""}, - {Expected: 2, StashContent: "1\n2\n"}, - {Expected: 4, StashContent: "1\n2\n3\n4\n\n"}, - } - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getFileContent", "/logs/refs/stash").Return(tc.StashContent) - g := &git{ - repo: &gitRepo{ - gitFolder: "", - }, - env: env, - } - got := g.getStashContext() - assert.Equal(t, tc.Expected, got) - } -} - -func TestParseGitBranchInfoEqual(t *testing.T) { - g := git{} - branchInfo := "## master...origin/master" - got := g.parseGitStatusInfo(branchInfo) - assert.Equal(t, "master", got["local"]) - assert.Equal(t, "origin/master", got["upstream"]) - assert.Empty(t, got["ahead"]) - assert.Empty(t, got["behind"]) -} - -func TestParseGitBranchInfoAhead(t *testing.T) { - g := git{} - branchInfo := "## master...origin/master [ahead 1]" - got := g.parseGitStatusInfo(branchInfo) - assert.Equal(t, "master", got["local"]) - assert.Equal(t, "origin/master", got["upstream"]) - assert.Equal(t, "1", got["ahead"]) - assert.Empty(t, got["behind"]) -} - -func TestParseGitBranchInfoBehind(t *testing.T) { - g := git{} - branchInfo := "## master...origin/master [behind 1]" - got := g.parseGitStatusInfo(branchInfo) - assert.Equal(t, "master", got["local"]) - assert.Equal(t, "origin/master", got["upstream"]) - assert.Equal(t, "1", got["behind"]) - assert.Empty(t, got["ahead"]) -} - -func TestParseGitBranchInfoBehindandAhead(t *testing.T) { - g := git{} - branchInfo := "## master...origin/master [ahead 1, behind 2]" - got := g.parseGitStatusInfo(branchInfo) - assert.Equal(t, "master", got["local"]) - assert.Equal(t, "origin/master", got["upstream"]) - assert.Equal(t, "2", got["behind"]) - assert.Equal(t, "1", got["ahead"]) -} - -func TestParseGitBranchInfoNoRemote(t *testing.T) { - g := git{} - branchInfo := "## master" - got := g.parseGitStatusInfo(branchInfo) - assert.Equal(t, "master", got["local"]) - assert.Empty(t, got["upstream"]) -} - -func TestParseGitBranchInfoRemoteGone(t *testing.T) { - g := git{} - branchInfo := "## test-branch...origin/test-branch [gone]" - got := g.parseGitStatusInfo(branchInfo) - assert.Equal(t, "test-branch", got["local"]) - assert.Equal(t, "gone", got["upstream_status"]) -} - -func TestGitStatusUnmerged(t *testing.T) { - expected := " x1" - status := &gitStatus{ - unmerged: 1, - } - assert.Equal(t, expected, status.string()) -} - -func TestGitStatusUnmergedModified(t *testing.T) { - expected := " ~3 x1" - status := &gitStatus{ - unmerged: 1, - modified: 3, - } - assert.Equal(t, expected, status.string()) -} - -func TestGitStatusEmpty(t *testing.T) { - expected := "" - status := &gitStatus{} - assert.Equal(t, expected, status.string()) -} - -func TestParseGitStatsWorking(t *testing.T) { - g := &git{} - output := []string{ - "## amazing-feat", - " M change.go", - "DD change.go", - " ? change.go", - " ? change.go", - " A change.go", - " U change.go", - " R change.go", - " C change.go", - } - status := g.parseGitStats(output, true) - assert.Equal(t, 3, status.modified) - assert.Equal(t, 1, status.unmerged) - assert.Equal(t, 3, status.added) - assert.Equal(t, 1, status.deleted) - assert.True(t, status.changed) -} - -func TestParseGitStatsStaging(t *testing.T) { - g := &git{} - output := []string{ - "## amazing-feat", - " M change.go", - "DD change.go", - " ? change.go", - "?? change.go", - " A change.go", - "DU change.go", - "MR change.go", - "AC change.go", - } - status := g.parseGitStats(output, false) - assert.Equal(t, 1, status.modified) - assert.Equal(t, 0, status.unmerged) - assert.Equal(t, 1, status.added) - assert.Equal(t, 2, status.deleted) - assert.True(t, status.changed) -} - -func TestParseGitStatsNoChanges(t *testing.T) { - g := &git{} - expected := &gitStatus{} - output := []string{ - "## amazing-feat", - } - status := g.parseGitStats(output, false) - assert.Equal(t, expected, status) - assert.False(t, status.changed) -} - -func TestParseGitStatsInvalidLine(t *testing.T) { - g := &git{} - expected := &gitStatus{} - output := []string{ - "## amazing-feat", - "#", - } - status := g.parseGitStats(output, false) - assert.Equal(t, expected, status) - assert.False(t, status.changed) -} - -func bootstrapUpstreamTest(upstream string) *git { - env := &MockedEnvironment{} - env.On("runCommand", "git", []string{"--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false", "remote", "get-url", "origin"}).Return(upstream, nil) - env.On("getRuntimeGOOS", nil).Return("unix") - props := &properties{ - values: map[Property]interface{}{ - GithubIcon: "GH", - GitlabIcon: "GL", - BitbucketIcon: "BB", - AzureDevOpsIcon: "AD", - GitIcon: "G", - }, - } - g := &git{ - env: env, - repo: &gitRepo{ - upstream: "origin/main", - }, - props: props, - } - return g -} - -func TestGetUpstreamSymbolGitHub(t *testing.T) { - g := bootstrapUpstreamTest("github.com/test") - upstreamIcon := g.getUpstreamSymbol() - assert.Equal(t, "GH", upstreamIcon) -} - -func TestGetUpstreamSymbolGitLab(t *testing.T) { - g := bootstrapUpstreamTest("gitlab.com/test") - upstreamIcon := g.getUpstreamSymbol() - assert.Equal(t, "GL", upstreamIcon) -} - -func TestGetUpstreamSymbolBitBucket(t *testing.T) { - g := bootstrapUpstreamTest("bitbucket.org/test") - upstreamIcon := g.getUpstreamSymbol() - assert.Equal(t, "BB", upstreamIcon) -} - -func TestGetUpstreamSymbolAzureDevOps(t *testing.T) { - g := bootstrapUpstreamTest("dev.azure.com/test") - upstreamIcon := g.getUpstreamSymbol() - assert.Equal(t, "AD", upstreamIcon) - - g = bootstrapUpstreamTest("test.visualstudio.com") - upstreamIcon = g.getUpstreamSymbol() - assert.Equal(t, "AD", upstreamIcon) -} - -func TestGetUpstreamSymbolGit(t *testing.T) { - g := bootstrapUpstreamTest("gitstash.com/test") - upstreamIcon := g.getUpstreamSymbol() - assert.Equal(t, "G", upstreamIcon) -} - -func TestGetStatusColorLocalChangesStaging(t *testing.T) { - expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{ - changed: true, - }, - } - g := &git{ - repo: repo, - props: &properties{ - values: map[Property]interface{}{ - LocalChangesColor: expected, - }, - }, - } - assert.Equal(t, expected, g.getStatusColor("#fg1111")) -} - -func TestGetStatusColorLocalChangesWorking(t *testing.T) { - expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{ - changed: true, - }, - } - g := &git{ - repo: repo, - props: &properties{ - values: map[Property]interface{}{ - LocalChangesColor: expected, - }, - }, - } - assert.Equal(t, expected, g.getStatusColor("#fg1111")) -} - -func TestGetStatusColorAheadAndBehind(t *testing.T) { - expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{}, - ahead: 1, - behind: 3, - } - g := &git{ - repo: repo, - props: &properties{ - values: map[Property]interface{}{ - AheadAndBehindColor: expected, - }, - }, - } - assert.Equal(t, expected, g.getStatusColor("#fg1111")) -} - -func TestGetStatusColorAhead(t *testing.T) { - expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{}, - ahead: 1, - behind: 0, - } - g := &git{ - repo: repo, - props: &properties{ - values: map[Property]interface{}{ - AheadColor: expected, - }, - }, - } - assert.Equal(t, expected, g.getStatusColor("#fg1111")) -} - -func TestGetStatusColorBehind(t *testing.T) { - expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{}, - ahead: 0, - behind: 5, - } - g := &git{ - repo: repo, - props: &properties{ - values: map[Property]interface{}{ - BehindColor: expected, - }, - }, - } - assert.Equal(t, expected, g.getStatusColor("#fg1111")) -} - -func TestGetStatusColorDefault(t *testing.T) { - expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{}, - working: &gitStatus{}, - ahead: 0, - behind: 0, - } - g := &git{ - repo: repo, - props: &properties{ - values: map[Property]interface{}{ - BehindColor: changesColor, - }, - }, - } - assert.Equal(t, expected, g.getStatusColor(expected)) -} - -func TestSetStatusColorForeground(t *testing.T) { - expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{ - changed: true, - }, - } - g := &git{ - repo: repo, - props: &properties{ - values: map[Property]interface{}{ - LocalChangesColor: changesColor, - ColorBackground: false, - }, - foreground: "#ffffff", - background: "#111111", - }, - } - g.SetStatusColor() - assert.Equal(t, expected, g.props.foreground) -} - -func TestSetStatusColorBackground(t *testing.T) { - expected := changesColor - repo := &gitRepo{ - staging: &gitStatus{ - changed: true, - }, - } - g := &git{ - repo: repo, - props: &properties{ - values: map[Property]interface{}{ - LocalChangesColor: changesColor, - ColorBackground: true, - }, - foreground: "#ffffff", - background: "#111111", - }, - } - g.SetStatusColor() - assert.Equal(t, expected, g.props.background) -} - -func TestStatusColorsWithoutDisplayStatus(t *testing.T) { - expected := changesColor - context := &detachedContext{ - status: "## main...origin/main [ahead 33]\n M myfile", - } - g := setupHEADContextEnv(context) - g.props = &properties{ - values: map[Property]interface{}{ - DisplayStatus: false, - StatusColorsEnabled: true, - LocalChangesColor: expected, - }, - } - g.string() - assert.Equal(t, expected, g.props.background) -} - -func TestGetStatusDetailStringDefault(t *testing.T) { - expected := "icon +1" - status := &gitStatus{ - changed: true, - added: 1, - } - g := &git{ - props: &properties{ - foreground: "#111111", - }, - } - assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon")) -} - -func TestGetStatusDetailStringDefaultColorOverride(t *testing.T) { - expected := "<#123456>icon +1" - status := &gitStatus{ - changed: true, - added: 1, - } - g := &git{ - props: &properties{ - values: map[Property]interface{}{ - WorkingColor: "#123456", - }, - foreground: "#111111", - }, - } - assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon")) -} - -func TestGetStatusDetailStringDefaultColorOverrideAndIconColorOverride(t *testing.T) { - expected := "<#789123>work<#123456> +1" - status := &gitStatus{ - changed: true, - added: 1, - } - g := &git{ - props: &properties{ - values: map[Property]interface{}{ - WorkingColor: "#123456", - LocalWorkingIcon: "<#789123>work", - }, - foreground: "#111111", - }, - } - assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon")) -} - -func TestGetStatusDetailStringDefaultColorOverrideNoIconColorOverride(t *testing.T) { - expected := "<#123456>work +1" - status := &gitStatus{ - changed: true, - added: 1, - } - g := &git{ - props: &properties{ - values: map[Property]interface{}{ - WorkingColor: "#123456", - LocalWorkingIcon: "work", - }, - foreground: "#111111", - }, - } - assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon")) -} - -func TestGetStatusDetailStringNoStatus(t *testing.T) { - expected := "icon" - status := &gitStatus{ - changed: true, - added: 1, - } - g := &git{ - props: &properties{ - values: map[Property]interface{}{ - DisplayStatusDetail: false, - }, - foreground: "#111111", - }, - } - assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon")) -} - -func TestGetStatusDetailStringNoStatusColorOverride(t *testing.T) { - expected := "<#123456>icon" - status := &gitStatus{ - changed: true, - added: 1, - } - g := &git{ - props: &properties{ - values: map[Property]interface{}{ - DisplayStatusDetail: false, - WorkingColor: "#123456", - }, - foreground: "#111111", - }, - } - assert.Equal(t, expected, g.getStatusDetailString(status, WorkingColor, LocalWorkingIcon, "icon")) -} - -func TestGetBranchStatus(t *testing.T) { - cases := []struct { - Case string - Expected string - Ahead int - Behind int - Upstream string - }{ - {Case: "Equal with remote", Expected: " equal", Upstream: "main"}, - {Case: "Ahead", Expected: " up2", Ahead: 2}, - {Case: "Behind", Expected: " down8", Behind: 8}, - {Case: "Behind and ahead", Expected: " up7 down8", Behind: 8, Ahead: 7}, - {Case: "Gone", Expected: " gone"}, - {Case: "Default (bug)", Expected: "", Behind: -8, Upstream: "wonky"}, - } - - for _, tc := range cases { - g := &git{ - props: &properties{ - values: map[Property]interface{}{ - BranchAheadIcon: "up", - BranchBehindIcon: "down", - BranchIdenticalIcon: "equal", - BranchGoneIcon: "gone", - }, - }, - repo: &gitRepo{ - ahead: tc.Ahead, - behind: tc.Behind, - upstream: tc.Upstream, - }, - } - assert.Equal(t, tc.Expected, g.getBranchStatus(), tc.Case) - } -} - -func TestTruncateBranch(t *testing.T) { - cases := []struct { - Case string - Expected string - Branch string - MaxLength interface{} - }{ - {Case: "No limit", Expected: "all-your-base-are-belong-to-us", Branch: "all-your-base-are-belong-to-us"}, - {Case: "No limit - larger", Expected: "all-your-base", Branch: "all-your-base-are-belong-to-us", MaxLength: 13}, - {Case: "No limit - smaller", Expected: "all-your-base", Branch: "all-your-base", MaxLength: 13}, - {Case: "Invalid setting", Expected: "all-your-base", Branch: "all-your-base", MaxLength: "burp"}, - } - - for _, tc := range cases { - g := &git{ - props: &properties{ - values: map[Property]interface{}{ - BranchMaxLength: tc.MaxLength, - }, - }, - } - assert.Equal(t, tc.Expected, g.truncateBranch(tc.Branch), tc.Case) - } -} diff --git a/src/segment_golang.go b/src/segment_golang.go deleted file mode 100644 index 2651065c63b3..000000000000 --- a/src/segment_golang.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -type golang struct { - language *language -} - -func (g *golang) string() string { - return g.language.string() -} - -func (g *golang) init(props *properties, env environmentInfo) { - g.language = &language{ - env: env, - props: props, - extensions: []string{"*.go", "go.mod"}, - commands: []*cmd{ - { - executable: "go", - args: []string{"version"}, - regex: `(?:go(?P((?P[0-9]+).(?P[0-9]+)(.(?P[0-9]+))?)))`, - }, - }, - versionURLTemplate: "[%s](https://golang.org/doc/go%s.%s)", - } -} - -func (g *golang) enabled() bool { - return g.language.enabled() -} diff --git a/src/segment_golang_test.go b/src/segment_golang_test.go deleted file mode 100644 index 4ac29f9098ff..000000000000 --- a/src/segment_golang_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -type mockedLanguageParams struct { - cmd string - versionParam string - versionOutput string - extension string -} - -func getMockedLanguageEnv(params *mockedLanguageParams) (*MockedEnvironment, *properties) { - env := new(MockedEnvironment) - env.On("hasCommand", params.cmd).Return(true) - env.On("runCommand", params.cmd, []string{params.versionParam}).Return(params.versionOutput, nil) - env.On("hasFiles", params.extension).Return(true) - env.On("getcwd", nil).Return("/usr/home/project") - env.On("homeDir", nil).Return("/usr/home") - props := &properties{ - values: map[Property]interface{}{ - DisplayVersion: true, - }, - } - return env, props -} - -func TestGolang(t *testing.T) { - cases := []struct { - Case string - ExpectedString string - Version string - }{ - {Case: "Go 1.15", ExpectedString: "1.15.8", Version: "go version go1.15.8 darwin/amd64"}, - {Case: "Go 1.16", ExpectedString: "1.16", Version: "go version go1.16 darwin/amd64"}, - } - for _, tc := range cases { - params := &mockedLanguageParams{ - cmd: "go", - versionParam: "version", - versionOutput: tc.Version, - extension: "*.go", - } - env, props := getMockedLanguageEnv(params) - g := &golang{} - g.init(props, env) - assert.True(t, g.enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) - assert.Equal(t, tc.ExpectedString, g.string(), fmt.Sprintf("Failed in case: %s", tc.Case)) - } -} diff --git a/src/segment_java.go b/src/segment_java.go deleted file mode 100644 index 073ffc8f1bf5..000000000000 --- a/src/segment_java.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import "fmt" - -type java struct { - language *language -} - -func (j *java) string() string { - return j.language.string() -} - -func (j *java) init(props *properties, env environmentInfo) { - javaRegex := `(?: JRE) \((?P(?P[0-9]+)(?:\.(?P[0-9]+))?(?:\.(?P[0-9]+))?).*\),` - javaCmd := &cmd{ - executable: "java", - args: []string{"-Xinternalversion"}, - regex: javaRegex, - } - j.language = &language{ - env: env, - props: props, - extensions: []string{ - "pom.xml", - "build.gradle.kts", - "build.sbt", - ".java-version", - ".deps.edn", - "project.clj", - "build.boot", - "*.java", - "*.class", - "*.gradle", - "*.jar", - "*.clj", - "*.cljc", - }, - } - javaHome := j.language.env.getenv("JAVA_HOME") - if len(javaHome) > 0 { - java := fmt.Sprintf("%s/bin/java", javaHome) - j.language.commands = []*cmd{ - { - executable: java, - args: []string{"-Xinternalversion"}, - regex: javaRegex, - }, - javaCmd, - } - return - } - j.language.commands = []*cmd{javaCmd} -} - -func (j *java) enabled() bool { - return j.language.enabled() -} diff --git a/src/segment_julia.go b/src/segment_julia.go deleted file mode 100644 index 01a8b2fa435e..000000000000 --- a/src/segment_julia.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -type julia struct { - language *language -} - -func (j *julia) string() string { - return j.language.string() -} - -func (j *julia) init(props *properties, env environmentInfo) { - j.language = &language{ - env: env, - props: props, - extensions: []string{"*.jl"}, - commands: []*cmd{ - { - executable: "julia", - args: []string{"--version"}, - regex: `julia version (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - }, - versionURLTemplate: "[%s](https://github.com/JuliaLang/julia/releases/tag/v%s.%s.%s)", - } -} - -func (j *julia) enabled() bool { - return j.language.enabled() -} diff --git a/src/segment_kubectl.go b/src/segment_kubectl.go deleted file mode 100644 index ffa543b436d2..000000000000 --- a/src/segment_kubectl.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "strings" -) - -type kubectl struct { - props *properties - env environmentInfo - Context string - Namespace string -} - -func (k *kubectl) string() string { - segmentTemplate := k.props.getString(SegmentTemplate, "{{.Context}}{{if .Namespace}} :: {{.Namespace}}{{end}}") - template := &textTemplate{ - Template: segmentTemplate, - Context: k, - Env: k.env, - } - text, err := template.render() - if err != nil { - return err.Error() - } - return text -} - -func (k *kubectl) init(props *properties, env environmentInfo) { - k.props = props - k.env = env -} - -func (k *kubectl) enabled() bool { - cmd := "kubectl" - if !k.env.hasCommand(cmd) { - return false - } - result, err := k.env.runCommand(cmd, "config", "view", "--minify", "--output", "jsonpath={..current-context},{..namespace}") - displayError := k.props.getBool(DisplayError, false) - if err != nil && displayError { - k.Context = "KUBECTL ERR" - k.Namespace = k.Context - return true - } - if err != nil { - return false - } - - values := strings.Split(result, ",") - k.Context = values[0] - k.Namespace = values[1] - return k.Context != "" -} diff --git a/src/segment_kubectl_test.go b/src/segment_kubectl_test.go deleted file mode 100644 index 5b5d5f0093ab..000000000000 --- a/src/segment_kubectl_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -type kubectlArgs struct { - kubectlExists bool - kubectlErr bool - template string - displayError bool - context string - namespace string -} - -func bootStrapKubectlTest(args *kubectlArgs) *kubectl { - env := new(MockedEnvironment) - env.On("hasCommand", "kubectl").Return(args.kubectlExists) - kubectlOut := args.context + "," + args.namespace - var kubectlErr error - if args.kubectlErr { - kubectlErr = &commandError{ - err: "oops", - exitCode: 1, - } - } - env.On("runCommand", "kubectl", []string{"config", "view", "--minify", "--output", "jsonpath={..current-context},{..namespace}"}).Return(kubectlOut, kubectlErr) - k := &kubectl{ - env: env, - props: &properties{ - values: map[Property]interface{}{ - SegmentTemplate: args.template, - DisplayError: args.displayError, - }, - }, - } - return k -} - -func TestKubectlSegment(t *testing.T) { - standardTemplate := "{{.Context}}{{if .Namespace}} :: {{.Namespace}}{{end}}" - cases := []struct { - Case string - Template string - DisplayError bool - KubectlExists bool - Context string - Namespace string - KubectlErr bool - ExpectedEnabled bool - ExpectedString string - }{ - {Case: "disabled", Template: standardTemplate, KubectlExists: false, Context: "aaa", Namespace: "bbb", ExpectedString: "", ExpectedEnabled: false}, - {Case: "normal", Template: standardTemplate, KubectlExists: true, Context: "aaa", Namespace: "bbb", ExpectedString: "aaa :: bbb", ExpectedEnabled: true}, - {Case: "no namespace", Template: standardTemplate, KubectlExists: true, Context: "aaa", Namespace: "", ExpectedString: "aaa", ExpectedEnabled: true}, - {Case: "kubectl error", Template: standardTemplate, DisplayError: true, KubectlExists: true, Context: "aaa", Namespace: "bbb", KubectlErr: true, - ExpectedString: "KUBECTL ERR :: KUBECTL ERR", ExpectedEnabled: true}, - {Case: "kubectl error hidden", Template: standardTemplate, DisplayError: false, KubectlExists: true, Context: "aaa", Namespace: "bbb", KubectlErr: true, - ExpectedString: "", ExpectedEnabled: false}, - } - - for _, tc := range cases { - args := &kubectlArgs{ - kubectlExists: tc.KubectlExists, - template: tc.Template, - displayError: tc.DisplayError, - context: tc.Context, - namespace: tc.Namespace, - kubectlErr: tc.KubectlErr, - } - kubectl := bootStrapKubectlTest(args) - assert.Equal(t, tc.ExpectedEnabled, kubectl.enabled(), tc.Case) - assert.Equal(t, tc.ExpectedString, kubectl.string(), tc.Case) - } -} diff --git a/src/segment_language.go b/src/segment_language.go deleted file mode 100644 index 7e0648f11a8a..000000000000 --- a/src/segment_language.go +++ /dev/null @@ -1,202 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "strings" -) - -type loadContext func() - -type inContext func() bool - -type matchesVersionFile func() bool - -type version struct { - full string - major string - minor string - patch string -} - -type cmd struct { - executable string - args []string - regex string - version *version -} - -func (c *cmd) parse(versionInfo string) error { - values := findNamedRegexMatch(c.regex, versionInfo) - if len(values) == 0 { - return errors.New("cannot parse version string") - } - c.version = &version{} - c.version.full = values["version"] - c.version.major = values["major"] - c.version.minor = values["minor"] - c.version.patch = values["patch"] - return nil -} - -func (c *cmd) buildVersionURL(template string) string { - if template == "" { - return c.version.full - } - truncatingSprintf := func(str string, args ...interface{}) (string, error) { - n := strings.Count(str, "%s") - if n > len(args) { - return "", errors.New("Too many parameters") - } - if n == 0 { - return fmt.Sprintf(str, args...), nil - } - return fmt.Sprintf(str, args[:n]...), nil - } - version, err := truncatingSprintf(template, c.version.full, c.version.major, c.version.minor, c.version.patch) - if err != nil { - return c.version.full - } - return version -} - -type language struct { - props *properties - env environmentInfo - extensions []string - commands []*cmd - versionURLTemplate string - activeCommand *cmd - exitCode int - loadContext loadContext - inContext inContext - matchesVersionFile matchesVersionFile -} - -const ( - // DisplayMode sets the display mode (always, when_in_context, never) - DisplayMode Property = "display_mode" - // DisplayModeAlways displays the segment always - DisplayModeAlways string = "always" - // DisplayModeFiles displays the segment when the current folder contains certain extensions - DisplayModeFiles string = "files" - // DisplayModeEnvironment displays the segment when the environment has a language's context - DisplayModeEnvironment string = "environment" - // DisplayModeContext displays the segment when the environment or files is active - DisplayModeContext string = "context" - // MissingCommandText sets the text to display when the command is not present in the system - MissingCommandText Property = "missing_command_text" - // VersionMismatchColor displays empty string by default - VersionMismatchColor Property = "version_mismatch_color" - // EnableVersionMismatch displays empty string by default - EnableVersionMismatch Property = "enable_version_mismatch" -) - -func (l *language) string() string { - if !l.props.getBool(DisplayVersion, true) { - return "" - } - - err := l.setVersion() - displayError := l.props.getBool(DisplayError, true) - if err != nil && displayError { - return err.Error() - } - if err != nil { - return "" - } - - if l.props.getBool(EnableHyperlink, false) { - return l.activeCommand.buildVersionURL(l.versionURLTemplate) - } - if l.props.getBool(EnableVersionMismatch, false) { - l.setVersionFileMismatch() - } - return l.activeCommand.version.full -} - -func (l *language) enabled() bool { - inHomeDir := func() bool { - return l.env.getcwd() == l.env.homeDir() - } - displayMode := l.props.getString(DisplayMode, DisplayModeFiles) - if inHomeDir() && displayMode != DisplayModeAlways { - return false - } - l.loadLanguageContext() - switch displayMode { - case DisplayModeAlways: - return true - case DisplayModeEnvironment: - return l.inLanguageContext() - case DisplayModeFiles: - return l.hasLanguageFiles() - case DisplayModeContext: - fallthrough - default: - return l.hasLanguageFiles() || l.inLanguageContext() - } -} - -// hasLanguageFiles will return true at least one file matching the extensions is found -func (l *language) hasLanguageFiles() bool { - for i, extension := range l.extensions { - if l.env.hasFiles(extension) { - break - } - if i == len(l.extensions)-1 { - return false - } - } - - return true -} - -// setVersion parses the version string returned by the command -func (l *language) setVersion() error { - for _, command := range l.commands { - if !l.env.hasCommand(command.executable) { - continue - } - version, err := l.env.runCommand(command.executable, command.args...) - if exitErr, ok := err.(*commandError); ok { - l.exitCode = exitErr.exitCode - return fmt.Errorf("err executing %s with %s", command.executable, command.args) - } - if version == "" { - continue - } - err = command.parse(version) - if err != nil { - return fmt.Errorf("err parsing info from %s with %s", command.executable, version) - } - l.activeCommand = command - return nil - } - return errors.New(l.props.getString(MissingCommandText, "")) -} - -func (l *language) loadLanguageContext() { - if l.loadContext == nil { - return - } - l.loadContext() -} - -func (l *language) inLanguageContext() bool { - if l.inContext == nil { - return false - } - return l.inContext() -} - -func (l *language) setVersionFileMismatch() { - if l.matchesVersionFile == nil || l.matchesVersionFile() { - return - } - if l.props.getBool(ColorBackground, false) { - l.props.background = l.props.getColor(VersionMismatchColor, l.props.background) - return - } - l.props.foreground = l.props.getColor(VersionMismatchColor, l.props.foreground) -} diff --git a/src/segment_language_test.go b/src/segment_language_test.go deleted file mode 100644 index afbb7ae80986..000000000000 --- a/src/segment_language_test.go +++ /dev/null @@ -1,557 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -const ( - universion = "1.3.307" - uni = "*.uni" - corn = "*.corn" -) - -type languageArgs struct { - version string - extensions []string - enabledExtensions []string - commands []*cmd - enabledCommands []string - versionURLTemplate string - expectedError error - properties map[Property]interface{} - matchesVersionFile matchesVersionFile - inHome bool -} - -func (l *languageArgs) hasvalue(value string, list []string) bool { - for _, element := range list { - if element == value { - return true - } - } - return false -} - -func bootStrapLanguageTest(args *languageArgs) *language { - env := new(MockedEnvironment) - for _, command := range args.commands { - env.On("hasCommand", command.executable).Return(args.hasvalue(command.executable, args.enabledCommands)) - env.On("runCommand", command.executable, command.args).Return(args.version, args.expectedError) - } - for _, extension := range args.extensions { - env.On("hasFiles", extension).Return(args.hasvalue(extension, args.enabledExtensions)) - } - home := "/usr/home" - cwd := "/usr/home/project" - if args.inHome { - cwd = home - } - env.On("getcwd", nil).Return(cwd) - env.On("homeDir", nil).Return(home) - props := &properties{ - values: args.properties, - } - l := &language{ - props: props, - env: env, - extensions: args.extensions, - commands: args.commands, - versionURLTemplate: args.versionURLTemplate, - matchesVersionFile: args.matchesVersionFile, - } - return l -} - -func TestLanguageFilesFoundButNoCommandAndVersionAndDisplayVersion(t *testing.T) { - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - }, - }, - extensions: []string{uni}, - enabledExtensions: []string{uni}, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "", lang.string(), "unicorn is not available") -} - -func TestLanguageFilesFoundButNoCommandAndVersionAndDontDisplayVersion(t *testing.T) { - props := map[Property]interface{}{ - DisplayVersion: false, - } - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - }, - }, - extensions: []string{uni}, - enabledExtensions: []string{uni}, - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled(), "unicorn is not available") -} - -func TestLanguageFilesFoundButNoCommandAndNoVersion(t *testing.T) { - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - }, - }, - extensions: []string{uni}, - enabledExtensions: []string{uni}, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled(), "unicorn is not available") -} - -func TestLanguageDisabledNoFiles(t *testing.T) { - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - }, - }, - extensions: []string{uni}, - enabledExtensions: []string{}, - enabledCommands: []string{"unicorn"}, - } - lang := bootStrapLanguageTest(args) - assert.False(t, lang.enabled(), "no files in the current directory") -} - -func TestLanguageEnabledOneExtensionFound(t *testing.T) { - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - regex: "(?P.*)", - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni}, - enabledCommands: []string{"unicorn"}, - version: universion, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, universion, lang.string(), "unicorn is available and uni files are found") -} - -func TestLanguageDisabledInHome(t *testing.T) { - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - regex: "(?P.*)", - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni}, - enabledCommands: []string{"unicorn"}, - version: universion, - inHome: true, - } - lang := bootStrapLanguageTest(args) - assert.False(t, lang.enabled()) -} - -func TestLanguageEnabledSecondExtensionFound(t *testing.T) { - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - regex: "(?P.*)", - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{corn}, - enabledCommands: []string{"unicorn"}, - version: universion, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, universion, lang.string(), "unicorn is available and corn files are found") -} - -func TestLanguageEnabledSecondCommand(t *testing.T) { - args := &languageArgs{ - commands: []*cmd{ - { - executable: "uni", - args: []string{"--version"}, - regex: "(?P.*)", - }, - { - executable: "corn", - args: []string{"--version"}, - regex: "(?P.*)", - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{corn}, - enabledCommands: []string{"corn"}, - version: universion, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, universion, lang.string(), "unicorn is available and corn files are found") -} - -func TestLanguageEnabledAllExtensionsFound(t *testing.T) { - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - regex: "(?P.*)", - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni, corn}, - enabledCommands: []string{"unicorn"}, - version: universion, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, universion, lang.string(), "unicorn is available and uni and corn files are found") -} - -func TestLanguageEnabledNoVersion(t *testing.T) { - props := map[Property]interface{}{ - DisplayVersion: false, - } - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - regex: "(?P.*)", - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni, corn}, - enabledCommands: []string{"unicorn"}, - version: universion, - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "", lang.string(), "unicorn is available and uni and corn files are found") -} - -func TestLanguageEnabledMissingCommand(t *testing.T) { - props := map[Property]interface{}{ - DisplayVersion: false, - } - args := &languageArgs{ - commands: []*cmd{}, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni, corn}, - enabledCommands: []string{"unicorn"}, - version: universion, - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "", lang.string(), "unicorn is available and uni and corn files are found") -} - -func TestLanguageEnabledNoVersionData(t *testing.T) { - props := map[Property]interface{}{ - DisplayVersion: true, - } - args := &languageArgs{ - commands: []*cmd{ - { - executable: "uni", - args: []string{"--version"}, - regex: `(?:Python (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni, corn}, - enabledCommands: []string{"uni"}, - version: "", - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "", lang.string()) -} - -func TestLanguageEnabledMissingCommandCustomText(t *testing.T) { - expected := "missing" - props := map[Property]interface{}{ - MissingCommandText: expected, - } - args := &languageArgs{ - commands: []*cmd{}, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni, corn}, - enabledCommands: []string{"unicorn"}, - version: universion, - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, expected, lang.string(), "unicorn is available and uni and corn files are found") -} - -func TestLanguageEnabledMissingCommandCustomTextHideError(t *testing.T) { - props := map[Property]interface{}{ - MissingCommandText: "missing", - DisplayError: false, - } - args := &languageArgs{ - commands: []*cmd{}, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni, corn}, - enabledCommands: []string{"unicorn"}, - version: universion, - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "", lang.string()) -} - -func TestLanguageEnabledCommandExitCode(t *testing.T) { - expected := 200 - args := &languageArgs{ - commands: []*cmd{ - { - executable: "uni", - args: []string{"--version"}, - regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni, corn}, - enabledCommands: []string{"uni"}, - version: universion, - expectedError: &commandError{exitCode: expected}, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "err executing uni with [--version]", lang.string()) - assert.Equal(t, expected, lang.exitCode) -} - -func TestLanguageHyperlinkEnabled(t *testing.T) { - props := map[Property]interface{}{ - EnableHyperlink: true, - } - args := &languageArgs{ - commands: []*cmd{ - { - executable: "uni", - args: []string{"--version"}, - regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - { - executable: "corn", - args: []string{"--version"}, - regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - }, - versionURLTemplate: "[%s](https://unicor.org/doc/%s.%s.%s)", - extensions: []string{uni, corn}, - enabledExtensions: []string{corn}, - enabledCommands: []string{"corn"}, - version: universion, - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "[1.3.307](https://unicor.org/doc/1.3.307)", lang.string()) -} - -func TestLanguageHyperlinkEnabledWrongRegex(t *testing.T) { - props := map[Property]interface{}{ - EnableHyperlink: true, - } - args := &languageArgs{ - commands: []*cmd{ - { - executable: "uni", - args: []string{"--version"}, - regex: `wrong`, - }, - { - executable: "corn", - args: []string{"--version"}, - regex: `wrong`, - }, - }, - versionURLTemplate: "[%s](https://unicor.org/doc/%s.%s.%s)", - extensions: []string{uni, corn}, - enabledExtensions: []string{corn}, - enabledCommands: []string{"corn"}, - version: universion, - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "err parsing info from corn with 1.3.307", lang.string()) -} - -func TestLanguageHyperlinkEnabledLessParamInTemplate(t *testing.T) { - props := map[Property]interface{}{ - EnableHyperlink: true, - } - args := &languageArgs{ - commands: []*cmd{ - { - executable: "uni", - args: []string{"--version"}, - regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - { - executable: "corn", - args: []string{"--version"}, - regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - }, - versionURLTemplate: "[%s](https://unicor.org/doc/%s)", - extensions: []string{uni, corn}, - enabledExtensions: []string{corn}, - enabledCommands: []string{"corn"}, - version: universion, - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "[1.3.307](https://unicor.org/doc/1)", lang.string()) -} - -func TestLanguageEnabledInHome(t *testing.T) { - cases := []struct { - Case string - DisplayMode string - ExpectedEnabled bool - }{ - {Case: "Always enabled", DisplayMode: DisplayModeAlways, ExpectedEnabled: true}, - {Case: "Context disabled", DisplayMode: DisplayModeContext, ExpectedEnabled: false}, - } - for _, tc := range cases { - props := map[Property]interface{}{ - DisplayMode: tc.DisplayMode, - } - args := &languageArgs{ - commands: []*cmd{ - { - executable: "uni", - args: []string{"--version"}, - regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{corn}, - enabledCommands: []string{"corn"}, - version: universion, - properties: props, - inHome: true, - } - lang := bootStrapLanguageTest(args) - assert.Equal(t, tc.ExpectedEnabled, lang.enabled(), tc.Case) - } -} - -func TestLanguageHyperlinkEnabledMoreParamInTemplate(t *testing.T) { - props := map[Property]interface{}{ - EnableHyperlink: true, - } - args := &languageArgs{ - commands: []*cmd{ - { - executable: "uni", - args: []string{"--version"}, - regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - { - executable: "corn", - args: []string{"--version"}, - regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, - }, - }, - versionURLTemplate: "[%s](https://unicor.org/doc/%s.%s.%s.%s)", - extensions: []string{uni, corn}, - enabledExtensions: []string{corn}, - enabledCommands: []string{"corn"}, - version: universion, - properties: props, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled()) - assert.Equal(t, "1.3.307", lang.string()) -} - -func TestLanguageVersionMismatch(t *testing.T) { - cases := []struct { - Case string - Enabled bool - Mismatch bool - ExpectedColor string - ColorBackground bool - }{ - {Case: "Disabled", Enabled: false}, - {Case: "Mismatch - Foreground color", Enabled: true, Mismatch: true, ExpectedColor: "#566777"}, - {Case: "Mismatch - Background color", Enabled: true, Mismatch: true, ExpectedColor: "#566777", ColorBackground: true}, - {Case: "No mismatch", Enabled: true, Mismatch: false}, - } - for _, tc := range cases { - props := map[Property]interface{}{ - EnableVersionMismatch: tc.Enabled, - VersionMismatchColor: tc.ExpectedColor, - ColorBackground: tc.ColorBackground, - } - var matchesVersionFile func() bool - switch tc.Mismatch { - case true: - matchesVersionFile = func() bool { - return false - } - default: - matchesVersionFile = func() bool { - return true - } - } - args := &languageArgs{ - commands: []*cmd{ - { - executable: "unicorn", - args: []string{"--version"}, - regex: "(?P.*)", - }, - }, - extensions: []string{uni, corn}, - enabledExtensions: []string{uni, corn}, - enabledCommands: []string{"unicorn"}, - version: universion, - properties: props, - matchesVersionFile: matchesVersionFile, - } - lang := bootStrapLanguageTest(args) - assert.True(t, lang.enabled(), tc.Case) - assert.Equal(t, universion, lang.string(), tc.Case) - if tc.ColorBackground { - assert.Equal(t, tc.ExpectedColor, lang.props.background, tc.Case) - return - } - assert.Equal(t, tc.ExpectedColor, lang.props.foreground, tc.Case) - } -} diff --git a/src/segment_nbgv.go b/src/segment_nbgv.go deleted file mode 100644 index cf83a01e58f0..000000000000 --- a/src/segment_nbgv.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "encoding/json" -) - -type nbgv struct { - props *properties - env environmentInfo - nbgv *versionInfo -} - -type versionInfo struct { - VersionFileFound bool `json:"VersionFileFound"` - Version string `json:"Version"` - AssemblyVersion string `json:"AssemblyVersion"` - AssemblyInformationalVersion string `json:"AssemblyInformationalVersion"` - NuGetPackageVersion string `json:"NuGetPackageVersion"` - ChocolateyPackageVersion string `json:"ChocolateyPackageVersion"` - NpmPackageVersion string `json:"NpmPackageVersion"` - SimpleVersion string `json:"SimpleVersion"` -} - -func (n *nbgv) enabled() bool { - nbgv := "nbgv" - if !n.env.hasCommand(nbgv) { - return false - } - response, err := n.env.runCommand(nbgv, "get-version", "--format=json") - if err != nil { - return false - } - n.nbgv = new(versionInfo) - err = json.Unmarshal([]byte(response), n.nbgv) - if err != nil { - return false - } - return n.nbgv.VersionFileFound -} - -func (n *nbgv) string() string { - segmentTemplate := n.props.getString(SegmentTemplate, "{{ .Version }}") - template := &textTemplate{ - Template: segmentTemplate, - Context: n.nbgv, - Env: n.env, - } - text, err := template.render() - if err != nil { - return err.Error() - } - return text -} - -func (n *nbgv) init(props *properties, env environmentInfo) { - n.props = props - n.env = env -} diff --git a/src/segment_node.go b/src/segment_node.go deleted file mode 100644 index 70c411a7df49..000000000000 --- a/src/segment_node.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import "fmt" - -type node struct { - language *language - packageManagerIcon string -} - -const ( - // YarnIcon illustrates Yarn is used - YarnIcon Property = "yarn_icon" - // NPMIcon illustrates NPM is used - NPMIcon Property = "npm_icon" - // DisplayPackageManager shows if NPM or Yarn is used - DisplayPackageManager Property = "display_package_manager" -) - -func (n *node) string() string { - version := n.language.string() - return fmt.Sprintf("%s%s", version, n.packageManagerIcon) -} - -func (n *node) init(props *properties, env environmentInfo) { - n.language = &language{ - env: env, - props: props, - extensions: []string{"*.js", "*.ts", "package.json", ".nvmrc"}, - commands: []*cmd{ - { - executable: "node", - args: []string{"--version"}, - regex: `(?:v(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, - }, - }, - versionURLTemplate: "[%[1]s](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V%[2]s.md#%[1]s)", - matchesVersionFile: n.matchesVersionFile, - loadContext: n.loadContext, - } -} - -func (n *node) enabled() bool { - return n.language.enabled() -} - -func (n *node) loadContext() { - if !n.language.props.getBool(DisplayPackageManager, false) { - return - } - if n.language.env.hasFiles("yarn.lock") { - n.packageManagerIcon = n.language.props.getString(YarnIcon, " \uF61A") - return - } - if n.language.env.hasFiles("package-lock.json") || n.language.env.hasFiles("package.json") { - n.packageManagerIcon = n.language.props.getString(NPMIcon, " \uE71E") - } -} - -func (n *node) matchesVersionFile() bool { - fileVersion := n.language.env.getFileContent(".nvmrc") - if len(fileVersion) == 0 { - return true - } - return fileVersion == n.language.activeCommand.version.full -} diff --git a/src/segment_node_test.go b/src/segment_node_test.go deleted file mode 100644 index 8cb5b593dbd9..000000000000 --- a/src/segment_node_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alecthomas/assert" -) - -func TestNodeMatchesVersionFile(t *testing.T) { - cases := []struct { - Case string - Expected bool - RCVersion string - Version string - }{ - {Case: "no file context", Expected: true, RCVersion: "", Version: "durp"}, - {Case: "version match", Expected: true, RCVersion: "durp", Version: "durp"}, - {Case: "version mismatch", Expected: false, RCVersion: "werp", Version: "durp"}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getFileContent", ".nvmrc").Return(tc.RCVersion) - node := &node{ - language: &language{ - env: env, - activeCommand: &cmd{ - version: &version{ - full: tc.Version, - }, - }, - }, - } - assert.Equal(t, tc.Expected, node.matchesVersionFile(), tc.Case) - } -} - -func TestNodeInContext(t *testing.T) { - cases := []struct { - Case string - HasYarn bool - hasNPM bool - hasDefault bool - PkgMgrEnabled bool - ExpectedString string - }{ - {Case: "no package manager file", ExpectedString: "", PkgMgrEnabled: true}, - {Case: "yarn", HasYarn: true, ExpectedString: "yarn", PkgMgrEnabled: true}, - {Case: "npm", hasNPM: true, ExpectedString: "npm", PkgMgrEnabled: true}, - {Case: "default", hasDefault: true, ExpectedString: "npm", PkgMgrEnabled: true}, - {Case: "disabled", HasYarn: true, ExpectedString: "", PkgMgrEnabled: false}, - {Case: "yarn and npm", HasYarn: true, hasNPM: true, ExpectedString: "yarn", PkgMgrEnabled: true}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("hasFiles", "yarn.lock").Return(tc.HasYarn) - env.On("hasFiles", "package-lock.json").Return(tc.hasNPM) - env.On("hasFiles", "package.json").Return(tc.hasDefault) - node := &node{ - language: &language{ - env: env, - props: &properties{ - values: map[Property]interface{}{ - YarnIcon: "yarn", - NPMIcon: "npm", - DisplayPackageManager: tc.PkgMgrEnabled, - }, - }, - }, - } - node.loadContext() - assert.Equal(t, tc.ExpectedString, node.packageManagerIcon, tc.Case) - } -} diff --git a/src/segment_os.go b/src/segment_os.go deleted file mode 100644 index 5161a52caf69..000000000000 --- a/src/segment_os.go +++ /dev/null @@ -1,151 +0,0 @@ -package main - -import ( - "fmt" -) - -type osInfo struct { - props *properties - env environmentInfo - OS string -} - -const ( - // MacOS the string/icon to use for MacOS - MacOS Property = "macos" - // Linux the string/icon to use for linux - Linux Property = "linux" - // Windows the string/icon to use for windows - Windows Property = "windows" - // WSL the string/icon to use for WSL - WSL Property = "wsl" - // WSLSeparator shows between WSL and Linux properties when WSL is detected - WSLSeparator Property = "wsl_separator" - // Alpine the string/icon to use for Alpine - Alpine Property = "alpine" - // Aosc the string/icon to use for Aosc - Aosc Property = "aosc" - // Arch the string/icon to use for Arch - Arch Property = "arch" - // Centos the string/icon to use for Centos - Centos Property = "centos" - // Coreos the string/icon to use for Coreos - Coreos Property = "coreos" - // Debian the string/icon to use for Debian - Debian Property = "debian" - // Devuan the string/icon to use for Devuan - Devuan Property = "devuan" - // Raspbian the string/icon to use for Raspbian - Raspbian Property = "raspbian" - // Elementary the string/icon to use for Elementary - Elementary Property = "elementary" - // Fedora the string/icon to use for Fedora - Fedora Property = "fedora" - // Gentoo the string/icon to use for Gentoo - Gentoo Property = "gentoo" - // Mageia the string/icon to use for Mageia - Mageia Property = "mageia" - // Manjaro the string/icon to use for Manjaro - Manjaro Property = "manjaro" - // Mint the string/icon to use for Mint - Mint Property = "mint" - // Nixos the string/icon to use for Nixos - Nixos Property = "nixos" - // Opensuse the string/icon to use for Opensuse - Opensuse Property = "opensuse" - // Sabayon the string/icon to use for Sabayon - Sabayon Property = "sabayon" - // Slackware the string/icon to use for Slackware - Slackware Property = "slackware" - // Ubuntu the string/icon to use for Ubuntu - Ubuntu Property = "ubuntu" - // DisplayDistroName display the distro name or not - DisplayDistroName Property = "display_distro_name" -) - -func (n *osInfo) enabled() bool { - return true -} - -func (n *osInfo) string() string { - goos := n.env.getRuntimeGOOS() - switch goos { - case windowsPlatform: - n.OS = windowsPlatform - return n.props.getString(Windows, "\uE62A") - case darwinPlatform: - n.OS = darwinPlatform - return n.props.getString(MacOS, "\uF179") - case linuxPlatform: - wsl := n.env.getenv("WSL_DISTRO_NAME") - p := n.env.getPlatform() - if len(wsl) == 0 { - n.OS = p - return n.getDistroName(p, "") - } - n.OS = wsl - return fmt.Sprintf("%s%s%s", - n.props.getString(WSL, "WSL"), - n.props.getString(WSLSeparator, " - "), - n.getDistroName(p, wsl)) - default: - n.OS = goos - return goos - } -} - -func (n *osInfo) getDistroName(distro, defaultName string) string { - displayDistroName := n.props.getBool(DisplayDistroName, false) - if displayDistroName && len(defaultName) > 0 { - return defaultName - } - if displayDistroName { - return distro - } - switch distro { - case "alpine": - return n.props.getString(Alpine, "\uF300") - case "aosc": - return n.props.getString(Aosc, "\uF301") - case "arch": - return n.props.getString(Arch, "\uF303") - case "centos": - return n.props.getString(Centos, "\uF304") - case "coreos": - return n.props.getString(Coreos, "\uF305") - case "debian": - return n.props.getString(Debian, "\uF306") - case "devuan": - return n.props.getString(Devuan, "\uF307") - case "raspbian": - return n.props.getString(Raspbian, "\uF315") - case "elementary": - return n.props.getString(Elementary, "\uF309") - case "fedora": - return n.props.getString(Fedora, "\uF30a") - case "gentoo": - return n.props.getString(Gentoo, "\uF30d") - case "mageia": - return n.props.getString(Mageia, "\uF310") - case "manjaro": - return n.props.getString(Manjaro, "\uF312") - case "mint": - return n.props.getString(Mint, "\uF30e") - case "nixos": - return n.props.getString(Nixos, "\uF313") - case "opensuse": - return n.props.getString(Opensuse, "\uF314") - case "sabayon": - return n.props.getString(Sabayon, "\uF317") - case "slackware": - return n.props.getString(Slackware, "\uF319") - case "ubuntu": - return n.props.getString(Ubuntu, "\uF31b") - } - return n.props.getString(Linux, "\uF17C") -} - -func (n *osInfo) init(props *properties, env environmentInfo) { - n.props = props - n.env = env -} diff --git a/src/segment_os_test.go b/src/segment_os_test.go deleted file mode 100644 index 0cd54756a7dc..000000000000 --- a/src/segment_os_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestOSInfo(t *testing.T) { - cases := []struct { - Case string - ExpectedString string - GOOS string - WSLDistro string - Platform string - DisplayDistroName bool - }{ - { - Case: "WSL debian - icon", - ExpectedString: "WSL at \uf306", - GOOS: "linux", - WSLDistro: "debian", - Platform: "debian", - }, - { - Case: "WSL debian - name", - ExpectedString: "WSL at burps", - GOOS: "linux", - WSLDistro: "burps", - Platform: "debian", - DisplayDistroName: true, - }, - { - Case: "plain linux - icon", - ExpectedString: "\uf306", - GOOS: "linux", - Platform: "debian", - }, - { - Case: "plain linux - name", - ExpectedString: "debian", - GOOS: "linux", - Platform: "debian", - DisplayDistroName: true, - }, - { - Case: "windows", - ExpectedString: "windows", - GOOS: "windows", - }, - { - Case: "darwin", - ExpectedString: "darwin", - GOOS: "darwin", - }, - { - Case: "unknown", - ExpectedString: "unknown", - GOOS: "unknown", - }, - } - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getRuntimeGOOS", nil).Return(tc.GOOS) - env.On("getenv", "WSL_DISTRO_NAME").Return(tc.WSLDistro) - env.On("getPlatform", nil).Return(tc.Platform) - props := &properties{ - values: map[Property]interface{}{ - WSL: "WSL", - WSLSeparator: " at ", - DisplayDistroName: tc.DisplayDistroName, - Windows: "windows", - MacOS: "darwin", - }, - } - osInfo := &osInfo{ - env: env, - props: props, - } - assert.Equal(t, tc.ExpectedString, osInfo.string(), tc.Case) - if tc.WSLDistro != "" { - assert.Equal(t, tc.WSLDistro, osInfo.OS, tc.Case) - } else if tc.Platform != "" { - assert.Equal(t, tc.Platform, osInfo.OS, tc.Case) - } else { - assert.Equal(t, tc.GOOS, osInfo.OS, tc.Case) - } - } -} diff --git a/src/segment_path.go b/src/segment_path.go deleted file mode 100644 index 8b3f04f6614e..000000000000 --- a/src/segment_path.go +++ /dev/null @@ -1,291 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "sort" - "strings" -) - -type path struct { - props *properties - env environmentInfo -} - -const ( - // FolderSeparatorIcon the path which is split will be separated by this icon - FolderSeparatorIcon Property = "folder_separator_icon" - // HomeIcon indicates the $HOME location - HomeIcon Property = "home_icon" - // FolderIcon identifies one folder - FolderIcon Property = "folder_icon" - // WindowsRegistryIcon indicates the registry location on Windows - WindowsRegistryIcon Property = "windows_registry_icon" - // Agnoster displays a short path with separator icon, this the default style - Agnoster string = "agnoster" - // AgnosterFull displays all the folder names with the folder_separator_icon - AgnosterFull string = "agnoster_full" - // AgnosterShort displays the folder names with one folder_separator_icon, regardless of depth - AgnosterShort string = "agnoster_short" - // Short displays a shorter path - Short string = "short" - // Full displays the full path - Full string = "full" - // Folder displays the current folder - Folder string = "folder" - // Mixed like agnoster, but if the path is short it displays it - Mixed string = "mixed" - // MixedThreshold the threshold of the length of the path Mixed will display - MixedThreshold Property = "mixed_threshold" - // MappedLocations allows overriding certain location with an icon - MappedLocations Property = "mapped_locations" - // MappedLocationsEnabled enables overriding certain locations with an icon - MappedLocationsEnabled Property = "mapped_locations_enabled" - // StackCountEnabled enables the stack count display - StackCountEnabled Property = "stack_count_enabled" -) - -func (pt *path) enabled() bool { - return true -} - -func (pt *path) string() string { - cwd := pt.env.getcwd() - var formattedPath string - switch style := pt.props.getString(Style, Agnoster); style { - case Agnoster: - formattedPath = pt.getAgnosterPath() - case AgnosterFull: - formattedPath = pt.getAgnosterFullPath() - case AgnosterShort: - formattedPath = pt.getAgnosterShortPath() - case Mixed: - formattedPath = pt.getMixedPath() - case Short: - // "short" is a duplicate of "full", just here for backwards compatibility - fallthrough - case Full: - formattedPath = pt.getFullPath() - case Folder: - formattedPath = pt.getFolderPath() - default: - return fmt.Sprintf("Path style: %s is not available", style) - } - formattedPath = pt.formatWindowsDrive(formattedPath) - if pt.props.getBool(EnableHyperlink, false) { - // wsl check - if pt.env.isWsl() { - cwd, _ = pt.env.runCommand("wslpath", "-m", cwd) - } - return fmt.Sprintf("[%s](file://%s)", formattedPath, cwd) - } - - if pt.props.getBool(StackCountEnabled, false) && pt.env.stackCount() > 0 { - return fmt.Sprintf("%d %s", pt.env.stackCount(), formattedPath) - } - - return formattedPath -} - -func (pt *path) formatWindowsDrive(pwd string) string { - if pt.env.getRuntimeGOOS() != windowsPlatform || !strings.HasSuffix(pwd, ":") { - return pwd - } - return pwd + "\\" -} - -func (pt *path) init(props *properties, env environmentInfo) { - pt.props = props - pt.env = env -} - -func (pt *path) getMixedPath() string { - var buffer strings.Builder - pwd := pt.getPwd() - splitted := strings.Split(pwd, pt.env.getPathSeperator()) - threshold := int(pt.props.getFloat64(MixedThreshold, 4)) - for i, part := range splitted { - if part == "" { - continue - } - - folder := part - if len(part) > threshold && i != 0 && i != len(splitted)-1 { - folder = pt.props.getString(FolderIcon, "..") - } - separator := pt.props.getString(FolderSeparatorIcon, pt.env.getPathSeperator()) - if i == 0 { - separator = "" - } - buffer.WriteString(fmt.Sprintf("%s%s", separator, folder)) - } - - return buffer.String() -} - -func (pt *path) getAgnosterPath() string { - var buffer strings.Builder - pwd := pt.getPwd() - buffer.WriteString(pt.rootLocation()) - pathDepth := pt.pathDepth(pwd) - for i := 1; i < pathDepth; i++ { - buffer.WriteString(fmt.Sprintf("%s%s", pt.props.getString(FolderSeparatorIcon, pt.env.getPathSeperator()), pt.props.getString(FolderIcon, ".."))) - } - if pathDepth > 0 { - buffer.WriteString(fmt.Sprintf("%s%s", pt.props.getString(FolderSeparatorIcon, pt.env.getPathSeperator()), base(pwd, pt.env))) - } - return buffer.String() -} - -func (pt *path) getAgnosterFullPath() string { - pwd := pt.getPwd() - if string(pwd[0]) == pt.env.getPathSeperator() { - pwd = pwd[1:] - } - return pt.replaceFolderSeparators(pwd) -} - -func (pt *path) getAgnosterShortPath() string { - pathSeparator := pt.env.getPathSeperator() - folderSeparator := pt.props.getString(FolderSeparatorIcon, pathSeparator) - folderIcon := pt.props.getString(FolderIcon, "..") - root := pt.rootLocation() - pwd := pt.getPwd() - base := base(pwd, pt.env) - pathDepth := pt.pathDepth(pwd) - if pathDepth <= 0 { - return root - } - if pathDepth == 1 { - return fmt.Sprintf("%s%s%s", root, folderSeparator, base) - } - return fmt.Sprintf("%s%s%s%s%s", root, folderSeparator, folderIcon, folderSeparator, base) -} - -func (pt *path) getFullPath() string { - pwd := pt.getPwd() - return pt.replaceFolderSeparators(pwd) -} - -func (pt *path) getFolderPath() string { - pwd := pt.getPwd() - pwd = base(pwd, pt.env) - return pt.replaceFolderSeparators(pwd) -} - -func (pt *path) getPwd() string { - pwd := *pt.env.getArgs().PSWD - if pwd == "" { - pwd = pt.env.getcwd() - } - if pt.props.getBool(MappedLocationsEnabled, true) { - pwd = pt.replaceMappedLocations(pwd) - } - return pwd -} - -func (pt *path) replaceMappedLocations(pwd string) string { - if strings.HasPrefix(pwd, "Microsoft.PowerShell.Core\\FileSystem::") { - pwd = strings.Replace(pwd, "Microsoft.PowerShell.Core\\FileSystem::", "", 1) - } - - mappedLocations := map[string]string{ - "HKCU:": pt.props.getString(WindowsRegistryIcon, "\uF013"), - "HKLM:": pt.props.getString(WindowsRegistryIcon, "\uF013"), - pt.env.homeDir(): pt.props.getString(HomeIcon, "~"), - } - - // merge custom locations with mapped locations - // mapped locations can override predefined locations - keyValues := pt.props.getKeyValueMap(MappedLocations, make(map[string]string)) - for key, val := range keyValues { - mappedLocations[key] = val - } - - // sort map keys in reverse order - // fixes case when a subfoder and its parent are mapped - // ex /users/test and /users/test/dev - keys := make([]string, len(mappedLocations)) - i := 0 - for k := range mappedLocations { - keys[i] = k - i++ - } - sort.Sort(sort.Reverse(sort.StringSlice(keys))) - - for _, value := range keys { - if strings.HasPrefix(pwd, value) { - return strings.Replace(pwd, value, mappedLocations[value], 1) - } - } - return pwd -} - -func (pt *path) replaceFolderSeparators(pwd string) string { - defaultSeparator := pt.env.getPathSeperator() - if pwd == defaultSeparator { - return pwd - } - folderSeparator := pt.props.getString(FolderSeparatorIcon, defaultSeparator) - if folderSeparator == defaultSeparator { - return pwd - } - - pwd = strings.ReplaceAll(pwd, defaultSeparator, folderSeparator) - return pwd -} - -func (pt *path) inHomeDir(pwd string) bool { - return strings.HasPrefix(pwd, pt.env.homeDir()) -} - -func (pt *path) rootLocation() string { - pwd := pt.getPwd() - pwd = strings.TrimPrefix(pwd, pt.env.getPathSeperator()) - splitted := strings.Split(pwd, pt.env.getPathSeperator()) - rootLocation := splitted[0] - return rootLocation -} - -func (pt *path) pathDepth(pwd string) int { - splitted := strings.Split(pwd, pt.env.getPathSeperator()) - depth := 0 - for _, part := range splitted { - if part != "" { - depth++ - } - } - return depth - 1 -} - -// Base returns the last element of path. -// Trailing path separators are removed before extracting the last element. -// If the path consists entirely of separators, Base returns a single separator. -func base(path string, env environmentInfo) string { - if path == "/" { - return path - } - volumeName := filepath.VolumeName(path) - // Strip trailing slashes. - for len(path) > 0 && string(path[len(path)-1]) == env.getPathSeperator() { - path = path[0 : len(path)-1] - } - if volumeName == path { - return path - } - // Throw away volume name - path = path[len(filepath.VolumeName(path)):] - // Find the last element - i := len(path) - 1 - for i >= 0 && string(path[i]) != env.getPathSeperator() { - i-- - } - if i >= 0 { - path = path[i+1:] - } - // If empty now, it had only slashes. - if path == "" { - return env.getPathSeperator() - } - return path -} diff --git a/src/segment_path_test.go b/src/segment_path_test.go deleted file mode 100644 index 85d6e371366d..000000000000 --- a/src/segment_path_test.go +++ /dev/null @@ -1,628 +0,0 @@ -package main - -import ( - "testing" - - "github.com/distatus/battery" - "github.com/gookit/config/v2" - "github.com/mitchellh/mapstructure" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -type MockedEnvironment struct { - mock.Mock -} - -func (env *MockedEnvironment) getenv(key string) string { - args := env.Called(key) - return args.String(0) -} - -func (env *MockedEnvironment) getcwd() string { - args := env.Called(nil) - return args.String(0) -} - -func (env *MockedEnvironment) homeDir() string { - args := env.Called(nil) - return args.String(0) -} - -func (env *MockedEnvironment) hasFiles(pattern string) bool { - args := env.Called(pattern) - return args.Bool(0) -} - -func (env *MockedEnvironment) hasFilesInDir(dir, pattern string) bool { - args := env.Called(dir, pattern) - return args.Bool(0) -} - -func (env *MockedEnvironment) hasFolder(folder string) bool { - args := env.Called(folder) - return args.Bool(0) -} - -func (env *MockedEnvironment) getFileContent(file string) string { - args := env.Called(file) - return args.String(0) -} - -func (env *MockedEnvironment) getPathSeperator() string { - args := env.Called(nil) - return args.String(0) -} - -func (env *MockedEnvironment) getCurrentUser() string { - args := env.Called(nil) - return args.String(0) -} - -func (env *MockedEnvironment) getHostName() (string, error) { - args := env.Called(nil) - return args.String(0), args.Error(1) -} - -func (env *MockedEnvironment) getRuntimeGOOS() string { - args := env.Called(nil) - return args.String(0) -} - -func (env *MockedEnvironment) getPlatform() string { - args := env.Called(nil) - return args.String(0) -} - -func (env *MockedEnvironment) hasCommand(command string) bool { - args := env.Called(command) - return args.Bool(0) -} - -func (env *MockedEnvironment) runCommand(command string, args ...string) (string, error) { - arguments := env.Called(command, args) - return arguments.String(0), arguments.Error(1) -} - -func (env *MockedEnvironment) runShellCommand(shell, command string) string { - args := env.Called(shell, command) - return args.String(0) -} - -func (env *MockedEnvironment) lastErrorCode() int { - args := env.Called(nil) - return args.Int(0) -} - -func (env *MockedEnvironment) executionTime() float64 { - args := env.Called(nil) - return float64(args.Int(0)) -} - -func (env *MockedEnvironment) isRunningAsRoot() bool { - args := env.Called(nil) - return args.Bool(0) -} - -func (env *MockedEnvironment) getArgs() *args { - arguments := env.Called(nil) - return arguments.Get(0).(*args) -} - -func (env *MockedEnvironment) getBatteryInfo() ([]*battery.Battery, error) { - args := env.Called(nil) - return args.Get(0).([]*battery.Battery), args.Error(1) -} - -func (env *MockedEnvironment) getShellName() string { - args := env.Called(nil) - return args.String(0) -} - -func (env *MockedEnvironment) getWindowTitle(imageName, windowTitleRegex string) (string, error) { - args := env.Called(imageName) - return args.String(0), args.Error(1) -} - -func (env *MockedEnvironment) doGet(url string) ([]byte, error) { - args := env.Called(url) - return args.Get(0).([]byte), args.Error(1) -} - -func (env *MockedEnvironment) hasParentFilePath(path string) (*fileInfo, error) { - args := env.Called(path) - return args.Get(0).(*fileInfo), args.Error(1) -} - -func (env *MockedEnvironment) stackCount() int { - args := env.Called(nil) - return args.Int(0) -} - -func (env *MockedEnvironment) isWsl() bool { - return false -} - -func (env *MockedEnvironment) getTerminalWidth() (int, error) { - args := env.Called(nil) - return args.Int(0), args.Error(1) -} - -const ( - homeBill = "/home/bill" - homeJan = "/usr/home/jan" - homeBillWindows = "C:\\Users\\Bill" - levelDir = "/level" -) - -func TestIsInHomeDirTrue(t *testing.T) { - home := homeBill - env := new(MockedEnvironment) - env.On("homeDir", nil).Return(home) - path := &path{ - env: env, - } - got := path.inHomeDir(home) - assert.True(t, got) -} - -func TestIsInHomeDirLevelTrue(t *testing.T) { - home := homeBill - pwd := home - for i := 0; i < 99; i++ { - pwd += levelDir - } - env := new(MockedEnvironment) - env.On("homeDir", nil).Return(home) - path := &path{ - env: env, - } - got := path.inHomeDir(pwd) - assert.True(t, got) -} - -func TestRootLocationHome(t *testing.T) { - cases := []struct { - Expected string - HomePath string - Pswd string - Pwd string - PathSeperator string - HomeIcon string - RegistryIcon string - }{ - {Expected: "~", HomeIcon: "~", HomePath: "/home/bill/", Pwd: "/home/bill/", PathSeperator: "/"}, - {Expected: "usr", HomePath: "/home/bill/", Pwd: "/usr/error/what", PathSeperator: "/"}, - {Expected: "C:", HomePath: "C:\\Users\\Bill", Pwd: "C:\\Program Files\\Go", PathSeperator: "\\"}, - {Expected: "REG", RegistryIcon: "REG", HomePath: "C:\\Users\\Bill", Pwd: "HKCU:\\Program Files\\Go", PathSeperator: "\\"}, - {Expected: "~", HomeIcon: "~", HomePath: "C:\\Users\\Bill", Pwd: "Microsoft.PowerShell.Core\\FileSystem::C:\\Users\\Bill", PathSeperator: "\\"}, - {Expected: "C:", HomePath: "C:\\Users\\Jack", Pwd: "Microsoft.PowerShell.Core\\FileSystem::C:\\Users\\Bill", PathSeperator: "\\"}, - {Expected: "", HomePath: "C:\\Users\\Jack", Pwd: "", PathSeperator: "\\"}, - {Expected: "DRIVE:", HomePath: "/home/bill/", Pwd: "/usr/error/what", Pswd: "DRIVE:", PathSeperator: "/"}, - } - for _, tc := range cases { - props := &properties{ - values: map[Property]interface{}{ - HomeIcon: tc.HomeIcon, - WindowsRegistryIcon: tc.RegistryIcon, - }, - } - env := new(MockedEnvironment) - env.On("homeDir", nil).Return(tc.HomePath) - env.On("getcwd", nil).Return(tc.Pwd) - args := &args{ - PSWD: &tc.Pswd, - } - env.On("getArgs", nil).Return(args) - env.On("getPathSeperator", nil).Return(tc.PathSeperator) - path := &path{ - env: env, - props: props, - } - got := path.rootLocation() - assert.EqualValues(t, tc.Expected, got) - } -} - -func TestIsInHomeDirFalse(t *testing.T) { - home := homeBill - env := new(MockedEnvironment) - env.On("homeDir", nil).Return(home) - path := &path{ - env: env, - } - got := path.inHomeDir("/usr/error") - assert.False(t, got) -} - -func TestPathDepthMultipleLevelsDeep(t *testing.T) { - pwd := "/usr" - for i := 0; i < 99; i++ { - pwd += levelDir - } - env := new(MockedEnvironment) - env.On("getPathSeperator", nil).Return("/") - path := &path{ - env: env, - } - got := path.pathDepth(pwd) - assert.Equal(t, 99, got) -} - -func TestPathDepthZeroLevelsDeep(t *testing.T) { - pwd := "/usr/" - env := new(MockedEnvironment) - env.On("getPathSeperator", nil).Return("/") - path := &path{ - env: env, - } - got := path.pathDepth(pwd) - assert.Equal(t, 0, got) -} - -func TestPathDepthOneLevelDeep(t *testing.T) { - pwd := "/usr/location" - env := new(MockedEnvironment) - env.On("getPathSeperator", nil).Return("/") - path := &path{ - env: env, - } - got := path.pathDepth(pwd) - assert.Equal(t, 1, got) -} - -func TestAgnosterPathStyles(t *testing.T) { - cases := []struct { - Expected string - HomePath string - Pswd string - Pwd string - PathSeperator string - HomeIcon string - FolderSeparatorIcon string - Style string - GOOS string - }{ - {Style: AgnosterFull, Expected: "usr > location > whatever", HomePath: "/usr/home", Pwd: "/usr/location/whatever", PathSeperator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "usr > .. > man", HomePath: "/usr/home", Pwd: "/usr/location/whatever/man", PathSeperator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "~ > .. > man", HomePath: "/usr/home", Pwd: "/usr/home/whatever/man", PathSeperator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "~ > projects", HomePath: "/usr/home", Pwd: "/usr/home/projects", PathSeperator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "C:", HomePath: homeBillWindows, Pwd: "C:", PathSeperator: "\\", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "", HomePath: homeBillWindows, Pwd: "/", PathSeperator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "foo", HomePath: homeBillWindows, Pwd: "/foo", PathSeperator: "/", FolderSeparatorIcon: " > "}, - - {Style: AgnosterFull, Expected: "PSDRIVE: | src", HomePath: homeBillWindows, Pwd: "/foo", Pswd: "PSDRIVE:/src", PathSeperator: "/", FolderSeparatorIcon: " | "}, - {Style: AgnosterShort, Expected: "PSDRIVE: | .. | init", HomePath: homeBillWindows, Pwd: "/foo", Pswd: "PSDRIVE:/src/init", PathSeperator: "/", FolderSeparatorIcon: " | "}, - - {Style: Mixed, Expected: "~ > .. > man", HomePath: "/usr/home", Pwd: "/usr/home/whatever/man", PathSeperator: "/", FolderSeparatorIcon: " > "}, - {Style: Mixed, Expected: "~ > ab > .. > man", HomePath: "/usr/home", Pwd: "/usr/home/ab/whatever/man", PathSeperator: "/", FolderSeparatorIcon: " > "}, - } - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getPathSeperator", nil).Return(tc.PathSeperator) - env.On("homeDir", nil).Return(tc.HomePath) - env.On("getcwd", nil).Return(tc.Pwd) - env.On("getRuntimeGOOS", nil).Return(tc.GOOS) - args := &args{ - PSWD: &tc.Pswd, - } - env.On("getArgs", nil).Return(args) - path := &path{ - env: env, - props: &properties{ - values: map[Property]interface{}{ - FolderSeparatorIcon: tc.FolderSeparatorIcon, - Style: tc.Style, - }, - }, - } - got := path.string() - assert.Equal(t, tc.Expected, got) - } -} - -func TestGetFullPath(t *testing.T) { - cases := []struct { - Style string - FolderSeparatorIcon string - Pwd string - Pswd string - Expected string - DisableMappedLocations bool - GOOS string - PathSeparator string - StackCount int - StackCountEnabled bool - }{ - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"}, - {Style: Full, Pwd: "", Expected: ""}, - {Style: Full, Pwd: "/", Expected: "/"}, - {Style: Full, Pwd: "/usr/home", Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", Expected: "~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", Expected: "/usr/home/abc", DisableMappedLocations: true}, - {Style: Full, Pwd: "/a/b/c/d", Expected: "/a/b/c/d"}, - - {Style: Full, FolderSeparatorIcon: "|", Pwd: "", Expected: ""}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/usr/home", Expected: "~"}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/usr/home", Expected: "|usr|home", DisableMappedLocations: true}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/usr/home/abc", Expected: "~|abc"}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/a/b/c/d", Expected: "|a|b|c|d"}, - - {Style: Folder, Pwd: "", Expected: ""}, - {Style: Folder, Pwd: "/", Expected: "/"}, - {Style: Folder, Pwd: "/usr/home", Expected: "~"}, - {Style: Folder, Pwd: "/usr/home", Expected: "home", DisableMappedLocations: true}, - {Style: Folder, Pwd: "/usr/home/abc", Expected: "abc"}, - {Style: Folder, Pwd: "/a/b/c/d", Expected: "d"}, - - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "", Expected: ""}, - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"}, - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/usr/home", Expected: "~"}, - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/usr/home", Expected: "home", DisableMappedLocations: true}, - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/usr/home/abc", Expected: "abc"}, - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/a/b/c/d", Expected: "d"}, - - {Style: Folder, FolderSeparatorIcon: "\\", Pwd: "C:\\", Expected: "C:\\", PathSeparator: "\\", GOOS: windowsPlatform}, - {Style: Full, FolderSeparatorIcon: "\\", Pwd: "C:\\Users\\Jan", Expected: "C:\\Users\\Jan", PathSeparator: "\\", GOOS: windowsPlatform}, - - // StackCountEnabled=true and StackCount=2 - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: true, StackCount: 2, Expected: "2 /"}, - {Style: Full, Pwd: "", StackCountEnabled: true, StackCount: 2, Expected: "2 "}, - {Style: Full, Pwd: "/", StackCountEnabled: true, StackCount: 2, Expected: "2 /"}, - {Style: Full, Pwd: "/usr/home", StackCountEnabled: true, StackCount: 2, Expected: "2 ~"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: 2, Expected: "2 ~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: 2, Expected: "2 /usr/home/abc", DisableMappedLocations: true}, - {Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: true, StackCount: 2, Expected: "2 /a/b/c/d"}, - - // StackCountEnabled=false and StackCount=2 - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: false, StackCount: 2, Expected: "/"}, - {Style: Full, Pwd: "", StackCountEnabled: false, StackCount: 2, Expected: ""}, - {Style: Full, Pwd: "/", StackCountEnabled: false, StackCount: 2, Expected: "/"}, - {Style: Full, Pwd: "/usr/home", StackCountEnabled: false, StackCount: 2, Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: false, StackCount: 2, Expected: "~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: false, StackCount: 2, Expected: "/usr/home/abc", DisableMappedLocations: true}, - {Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: false, StackCount: 2, Expected: "/a/b/c/d"}, - - // StackCountEnabled=true and StackCount=0 - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: true, StackCount: 0, Expected: "/"}, - {Style: Full, Pwd: "", StackCountEnabled: true, StackCount: 0, Expected: ""}, - {Style: Full, Pwd: "/", StackCountEnabled: true, StackCount: 0, Expected: "/"}, - {Style: Full, Pwd: "/usr/home", StackCountEnabled: true, StackCount: 0, Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: 0, Expected: "~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: 0, Expected: "/usr/home/abc", DisableMappedLocations: true}, - {Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: true, StackCount: 0, Expected: "/a/b/c/d"}, - - // StackCountEnabled=true and StackCount<0 - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: true, StackCount: -1, Expected: "/"}, - {Style: Full, Pwd: "", StackCountEnabled: true, StackCount: -1, Expected: ""}, - {Style: Full, Pwd: "/", StackCountEnabled: true, StackCount: -1, Expected: "/"}, - {Style: Full, Pwd: "/usr/home", StackCountEnabled: true, StackCount: -1, Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: -1, Expected: "~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: -1, Expected: "/usr/home/abc", DisableMappedLocations: true}, - {Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: true, StackCount: -1, Expected: "/a/b/c/d"}, - - // StackCountEnabled=true and StackCount not set - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: true, Expected: "/"}, - {Style: Full, Pwd: "", StackCountEnabled: true, Expected: ""}, - {Style: Full, Pwd: "/", StackCountEnabled: true, Expected: "/"}, - {Style: Full, Pwd: "/usr/home", StackCountEnabled: true, Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, Expected: "~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, Expected: "/usr/home/abc", DisableMappedLocations: true}, - {Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: true, Expected: "/a/b/c/d"}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - if len(tc.PathSeparator) == 0 { - tc.PathSeparator = "/" - } - env.On("getPathSeperator", nil).Return(tc.PathSeparator) - env.On("homeDir", nil).Return("/usr/home") - env.On("getcwd", nil).Return(tc.Pwd) - env.On("getRuntimeGOOS", nil).Return(tc.GOOS) - env.On("stackCount", nil).Return(tc.StackCount) - args := &args{ - PSWD: &tc.Pswd, - } - env.On("getArgs", nil).Return(args) - props := &properties{ - values: map[Property]interface{}{ - Style: tc.Style, - StackCountEnabled: tc.StackCountEnabled, - }, - } - if tc.FolderSeparatorIcon != "" { - props.values[FolderSeparatorIcon] = tc.FolderSeparatorIcon - } - if tc.DisableMappedLocations { - props.values[MappedLocationsEnabled] = false - } - path := &path{ - env: env, - props: props, - } - got := path.string() - assert.Equal(t, tc.Expected, got) - } -} - -func TestGetFolderPathCustomMappedLocations(t *testing.T) { - pwd := "/a/b/c/d" - env := new(MockedEnvironment) - env.On("getPathSeperator", nil).Return("/") - env.On("homeDir", nil).Return("/usr/home") - env.On("getcwd", nil).Return(pwd) - args := &args{ - PSWD: &pwd, - } - env.On("getArgs", nil).Return(args) - path := &path{ - env: env, - props: &properties{ - values: map[Property]interface{}{ - MappedLocations: map[string]string{ - "/a/b/c/d": "#", - }, - }, - }, - } - got := path.getFolderPath() - assert.Equal(t, "#", got) -} - -func testWritePathInfo(home, pwd, pathSeparator string) string { - props := &properties{ - values: map[Property]interface{}{ - FolderSeparatorIcon: " > ", - FolderIcon: "f", - HomeIcon: "~", - }, - } - env := new(MockedEnvironment) - env.On("homeDir", nil).Return(home) - env.On("getPathSeperator", nil).Return(pathSeparator) - env.On("getcwd", nil).Return(pwd) - args := &args{ - PSWD: &pwd, - } - env.On("getArgs", nil).Return(args) - path := &path{ - env: env, - props: props, - } - return path.getAgnosterPath() -} - -func TestWritePathInfoWindowsOutsideHome(t *testing.T) { - home := homeBillWindows - want := "C: > f > f > location" - got := testWritePathInfo(home, "C:\\Program Files\\Go\\location", "\\") - assert.Equal(t, want, got) -} - -func TestWritePathInfoWindowsInsideHome(t *testing.T) { - home := homeBillWindows - location := home + "\\Documents\\Bill\\location" - want := "~ > f > f > location" - got := testWritePathInfo(home, location, "\\") - assert.Equal(t, want, got) -} - -func TestWritePathInfoWindowsOutsideHomeZeroLevels(t *testing.T) { - home := homeBillWindows - want := "C: > location" - got := testWritePathInfo(home, "C:\\location", "\\") - assert.Equal(t, want, got) -} - -func TestWritePathInfoWindowsOutsideHomeOneLevels(t *testing.T) { - home := homeBillWindows - want := "C: > f > location" - got := testWritePathInfo(home, "C:\\Program Files\\location", "\\") - assert.Equal(t, want, got) -} - -func TestWritePathInfoUnixOutsideHome(t *testing.T) { - home := homeJan - want := "mnt > f > f > location" - got := testWritePathInfo(home, "/mnt/go/test/location", "/") - assert.Equal(t, want, got) -} - -func TestWritePathInfoUnixInsideHome(t *testing.T) { - home := homeJan - location := home + "/docs/jan/location" - want := "~ > f > f > location" - got := testWritePathInfo(home, location, "/") - assert.Equal(t, want, got) -} - -func TestWritePathInfoUnixOutsideHomeZeroLevels(t *testing.T) { - home := homeJan - want := "mnt > location" - got := testWritePathInfo(home, "/mnt/location", "/") - assert.Equal(t, want, got) -} - -func TestWritePathInfoUnixOutsideHomeOneLevels(t *testing.T) { - home := homeJan - want := "mnt > f > location" - got := testWritePathInfo(home, "/mnt/folder/location", "/") - assert.Equal(t, want, got) -} - -func TestGetPwd(t *testing.T) { - cases := []struct { - MappedLocationsEnabled bool - Pwd string - Pswd string - Expected string - }{ - {MappedLocationsEnabled: true, Pwd: "", Expected: ""}, - {MappedLocationsEnabled: true, Pwd: "/usr", Expected: "/usr"}, - {MappedLocationsEnabled: true, Pwd: "/usr/home", Expected: "~"}, - {MappedLocationsEnabled: true, Pwd: "/usr/home/abc", Expected: "~/abc"}, - {MappedLocationsEnabled: true, Pwd: "/a/b/c/d", Expected: "#"}, - {MappedLocationsEnabled: true, Pwd: "/a/b/c/d/e/f/g", Expected: "#/e/f/g"}, - {MappedLocationsEnabled: true, Pwd: "/z/y/x/w", Expected: "/z/y/x/w"}, - - {MappedLocationsEnabled: false, Pwd: "", Expected: ""}, - {MappedLocationsEnabled: false, Pwd: "/usr/home/abc", Expected: "/usr/home/abc"}, - {MappedLocationsEnabled: false, Pwd: "/a/b/c/d/e/f/g", Expected: "/a/b/c/d/e/f/g"}, - - {MappedLocationsEnabled: true, Pwd: "/w/d/x/w", Pswd: "/z/y/x/w", Expected: "/z/y/x/w"}, - {MappedLocationsEnabled: false, Pwd: "/f/g/k/d/e/f/g", Pswd: "/a/b/c/d/e/f/g", Expected: "/a/b/c/d/e/f/g"}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getPathSeperator", nil).Return("/") - env.On("homeDir", nil).Return("/usr/home") - env.On("getcwd", nil).Return(tc.Pwd) - args := &args{ - PSWD: &tc.Pswd, - } - env.On("getArgs", nil).Return(args) - path := &path{ - env: env, - props: &properties{ - values: map[Property]interface{}{ - MappedLocationsEnabled: tc.MappedLocationsEnabled, - MappedLocations: map[string]string{ - "/a/b/c/d": "#", - }, - }, - }, - } - got := path.getPwd() - assert.Equal(t, tc.Expected, got) - } -} - -func TestParseMappedLocations(t *testing.T) { - cases := []struct { - Case string - JSON string - }{ - {Case: "new format", JSON: `{ "properties": { "mapped_locations": {"folder1": "one","folder2": "two"} } }`}, - {Case: "old format", JSON: `{ "properties": { "mapped_locations": [["folder1", "one"], ["folder2", "two"]] } }`}, - } - for _, tc := range cases { - config.ClearAll() - config.WithOptions(func(opt *config.Options) { - opt.DecoderConfig = &mapstructure.DecoderConfig{ - TagName: "config", - } - }) - err := config.LoadStrings(config.JSON, tc.JSON) - assert.NoError(t, err) - var segment Segment - err = config.BindStruct("", &segment) - assert.NoError(t, err) - props := &properties{ - values: segment.Properties, - } - mappedLocations := props.getKeyValueMap(MappedLocations, make(map[string]string)) - assert.Equal(t, "two", mappedLocations["folder2"]) - } -} diff --git a/src/segment_posh_git.go b/src/segment_posh_git.go deleted file mode 100644 index 32ee1ef53bd4..000000000000 --- a/src/segment_posh_git.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import "strings" - -type poshgit struct { - props *properties - env environmentInfo - gitStatus string -} - -const ( - poshGitEnv = "POSH_GIT_STATUS" -) - -func (p *poshgit) enabled() bool { - status := p.env.getenv(poshGitEnv) - p.gitStatus = strings.TrimSpace(status) - return p.gitStatus != "" -} - -func (p *poshgit) string() string { - return p.gitStatus -} - -func (p *poshgit) init(props *properties, env environmentInfo) { - p.props = props - p.env = env -} diff --git a/src/segment_posh_git_test.go b/src/segment_posh_git_test.go deleted file mode 100644 index d8e3b3b71d21..000000000000 --- a/src/segment_posh_git_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPoshGitSegment(t *testing.T) { - cases := []struct { - Case string - PoshGitPrompt string - Expected string - Enabled bool - }{ - {Case: "regular prompt", PoshGitPrompt: "my prompt", Expected: "my prompt", Enabled: true}, - {Case: "prompt with spaces", PoshGitPrompt: " my prompt", Expected: "my prompt", Enabled: true}, - {Case: "no prompt", PoshGitPrompt: "", Enabled: false}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getenv", poshGitEnv).Return(tc.PoshGitPrompt) - p := &poshgit{ - env: env, - } - assert.Equal(t, tc.Enabled, p.enabled()) - if tc.Enabled { - assert.Equal(t, tc.Expected, p.string()) - } - } -} diff --git a/src/segment_python.go b/src/segment_python.go deleted file mode 100644 index 895564e0bb2a..000000000000 --- a/src/segment_python.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import "fmt" - -type python struct { - language *language - venvName string -} - -const ( - // DisplayVirtualEnv shows or hides the virtual env - DisplayVirtualEnv Property = "display_virtual_env" -) - -func (p *python) string() string { - if p.venvName == "" { - return p.language.string() - } - version := p.language.string() - if version == "" { - return p.venvName - } - return fmt.Sprintf("%s %s", p.venvName, version) -} - -func (p *python) init(props *properties, env environmentInfo) { - p.language = &language{ - env: env, - props: props, - extensions: []string{"*.py", "*.ipynb", "pyproject.toml", "venv.bak", "venv", ".venv"}, - loadContext: p.loadContext, - inContext: p.inContext, - commands: []*cmd{ - { - executable: "python", - args: []string{"--version"}, - regex: `(?:Python (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, - }, - { - executable: "python3", - args: []string{"--version"}, - regex: `(?:Python (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, - }, - }, - versionURLTemplate: "[%s](https://www.python.org/downloads/release/python-%s%s%s/)", - } -} - -func (p *python) enabled() bool { - return p.language.enabled() -} - -func (p *python) loadContext() { - if !p.language.props.getBool(DisplayVirtualEnv, true) { - return - } - venvVars := []string{ - "VIRTUAL_ENV", - "CONDA_ENV_PATH", - "CONDA_DEFAULT_ENV", - "PYENV_VERSION", - } - var venv string - for _, venvVar := range venvVars { - venv = p.language.env.getenv(venvVar) - name := base(venv, p.language.env) - if p.canUseVenvName(name) { - p.venvName = name - break - } - } -} - -func (p *python) inContext() bool { - return p.venvName != "" -} - -func (p *python) canUseVenvName(name string) bool { - if name == "" || name == "." { - return false - } - if p.language.props.getBool(DisplayDefault, true) { - return true - } - invalidNames := [2]string{"system", "base"} - for _, a := range invalidNames { - if a == name { - return false - } - } - return true -} diff --git a/src/segment_python_test.go b/src/segment_python_test.go deleted file mode 100644 index a2d197dc86ad..000000000000 --- a/src/segment_python_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alecthomas/assert" -) - -func TestPythonVirtualEnv(t *testing.T) { - cases := []struct { - Expected string - VirtualEnvName string - CondaEnvName string - CondaDefaultEnvName string - PyEnvName string - DisplayVersion bool - DisplayDefault bool - }{ - {Expected: "VENV", VirtualEnvName: "VENV"}, - {Expected: "CONDA", CondaEnvName: "CONDA"}, - {Expected: "CONDA", CondaDefaultEnvName: "CONDA"}, - {Expected: "", CondaDefaultEnvName: "base"}, - {Expected: "base", CondaDefaultEnvName: "base", DisplayDefault: true}, - {Expected: "PYENV", PyEnvName: "PYENV"}, - {Expected: "PYENV 3.8.4", PyEnvName: "PYENV", DisplayVersion: true}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("hasCommand", "python").Return(true) - env.On("runCommand", "python", []string{"--version"}).Return("Python 3.8.4", nil) - env.On("hasFiles", "*.py").Return(true) - env.On("getenv", "VIRTUAL_ENV").Return(tc.VirtualEnvName) - env.On("getenv", "CONDA_ENV_PATH").Return(tc.CondaEnvName) - env.On("getenv", "CONDA_DEFAULT_ENV").Return(tc.CondaDefaultEnvName) - env.On("getenv", "PYENV_VERSION").Return(tc.PyEnvName) - env.On("getPathSeperator", nil).Return("") - env.On("getcwd", nil).Return("/usr/home/project") - env.On("homeDir", nil).Return("/usr/home") - props := &properties{ - values: map[Property]interface{}{ - DisplayVersion: tc.DisplayVersion, - DisplayVirtualEnv: true, - DisplayDefault: tc.DisplayDefault, - }, - } - python := &python{} - python.init(props, env) - assert.True(t, python.enabled()) - assert.Equal(t, tc.Expected, python.string()) - } -} - -func TestPythonPythonInContext(t *testing.T) { - cases := []struct { - Expected bool - VirtualEnvName string - }{ - {Expected: true, VirtualEnvName: "VENV"}, - {Expected: false, VirtualEnvName: ""}, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getPathSeperator", nil).Return("") - env.On("getenv", "VIRTUAL_ENV").Return(tc.VirtualEnvName) - env.On("getenv", "CONDA_ENV_PATH").Return("") - env.On("getenv", "CONDA_DEFAULT_ENV").Return("") - env.On("getenv", "PYENV_VERSION").Return("") - python := &python{} - python.init(nil, env) - python.loadContext() - assert.Equal(t, tc.Expected, python.inContext()) - } -} diff --git a/src/segment_root.go b/src/segment_root.go deleted file mode 100644 index a844ef53a7ad..000000000000 --- a/src/segment_root.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -type root struct { - props *properties - env environmentInfo -} - -const ( - // RootIcon indicates the root user - RootIcon Property = "root_icon" -) - -func (rt *root) enabled() bool { - return rt.env.isRunningAsRoot() -} - -func (rt *root) string() string { - return rt.props.getString(RootIcon, "\uF0E7") -} - -func (rt *root) init(props *properties, env environmentInfo) { - rt.props = props - rt.env = env -} diff --git a/src/segment_ruby.go b/src/segment_ruby.go deleted file mode 100644 index 383972ea2c4d..000000000000 --- a/src/segment_ruby.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -type ruby struct { - language *language -} - -func (r *ruby) string() string { - version := r.language.string() - // asdf default non-set version - if version == "______" { - return "" - } - return version -} - -func (r *ruby) init(props *properties, env environmentInfo) { - r.language = &language{ - env: env, - props: props, - extensions: []string{"*.rb", "Rakefile", "Gemfile"}, - commands: []*cmd{ - { - executable: "rbenv", - args: []string{"version-name"}, - regex: `(?P.+)`, - }, - { - executable: "rvm-prompt", - args: []string{"i", "v", "g"}, - regex: `(?P.+)`, - }, - { - executable: "chruby", - args: []string(nil), - regex: `\* (?P.+)\n`, - }, - { - executable: "asdf", - args: []string{"current", "ruby"}, - regex: `ruby\s+(?P[^\s]+)\s+`, - }, - { - executable: "ruby", - args: []string{"--version"}, - regex: `ruby\s+(?P[^\s]+)\s+`, - }, - }, - } -} - -func (r *ruby) enabled() bool { - return r.language.enabled() -} diff --git a/src/segment_ruby_test.go b/src/segment_ruby_test.go deleted file mode 100644 index dbc3f8ec0ded..000000000000 --- a/src/segment_ruby_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRuby(t *testing.T) { - cases := []struct { - Case string - ExpectedString string - ExpectedEnabled bool - HasRbenv bool - HasRvmprompt bool - HasChruby bool - HasAsdf bool - HasRuby bool - Version string - HasRubyFiles bool - HasRakeFile bool - HasGemFile bool - DisplayVersion bool - }{ - {Case: "No files", ExpectedString: "", ExpectedEnabled: false}, - {Case: "Ruby files", ExpectedString: "", ExpectedEnabled: true, DisplayVersion: false, HasRubyFiles: true}, - {Case: "Rakefile", ExpectedString: "", ExpectedEnabled: true, DisplayVersion: false, HasRakeFile: true}, - {Case: "Gemfile", ExpectedString: "", ExpectedEnabled: true, DisplayVersion: false, HasGemFile: true}, - {Case: "Gemfile with version", ExpectedString: "", ExpectedEnabled: true, DisplayVersion: true, HasGemFile: true}, - {Case: "No files with version", ExpectedString: "", ExpectedEnabled: false, DisplayVersion: true}, - { - Case: "Version with chruby", - ExpectedString: "ruby-2.6.3", - ExpectedEnabled: true, - DisplayVersion: true, - HasRubyFiles: true, - HasChruby: true, - Version: ` * ruby-2.6.3 - ruby-1.9.3-p392 - jruby-1.7.0 - rubinius-2.0.0-rc1`, - }, - { - Case: "Version with chruby line 2", - ExpectedString: "ruby-1.9.3-p392", - ExpectedEnabled: true, - DisplayVersion: true, - HasRubyFiles: true, - HasChruby: true, - Version: ` ruby-2.6.3 - * ruby-1.9.3-p392 - jruby-1.7.0 - rubinius-2.0.0-rc1`, - }, - { - Case: "Version with asdf", - ExpectedString: "2.6.3", - ExpectedEnabled: true, - DisplayVersion: true, - HasRubyFiles: true, - HasAsdf: true, - Version: "ruby 2.6.3 /Users/jan/Projects/oh-my-posh/.tool-versions", - }, - { - Case: "Version with asdf not set", - ExpectedString: "", - ExpectedEnabled: true, - DisplayVersion: true, - HasRubyFiles: true, - HasAsdf: true, - Version: "ruby ______ No version set. Run \"asdf ruby \"", - }, - { - Case: "Version with ruby", - ExpectedString: "2.6.3", - ExpectedEnabled: true, - DisplayVersion: true, - HasRubyFiles: true, - HasRuby: true, - Version: "ruby 2.6.3 (2019-04-16 revision 67580) [universal.x86_64-darwin20]", - }, - } - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("hasCommand", "rbenv").Return(tc.HasRbenv) - env.On("runCommand", "rbenv", []string{"version-name"}).Return(tc.Version, nil) - env.On("hasCommand", "rvm-prompt").Return(tc.HasRvmprompt) - env.On("runCommand", "rvm-prompt", []string{"i", "v", "g"}).Return(tc.Version, nil) - env.On("hasCommand", "chruby").Return(tc.HasChruby) - env.On("runCommand", "chruby", []string(nil)).Return(tc.Version, nil) - env.On("hasCommand", "asdf").Return(tc.HasAsdf) - env.On("runCommand", "asdf", []string{"current", "ruby"}).Return(tc.Version, nil) - env.On("hasCommand", "ruby").Return(tc.HasRuby) - env.On("runCommand", "ruby", []string{"--version"}).Return(tc.Version, nil) - env.On("hasFiles", "*.rb").Return(tc.HasRubyFiles) - env.On("hasFiles", "Rakefile").Return(tc.HasRakeFile) - env.On("hasFiles", "Gemfile").Return(tc.HasGemFile) - env.On("getcwd", nil).Return("/usr/home/project") - env.On("homeDir", nil).Return("/usr/home") - props := &properties{ - values: map[Property]interface{}{ - DisplayVersion: tc.DisplayVersion, - }, - } - ruby := &ruby{} - ruby.init(props, env) - assert.Equal(t, tc.ExpectedEnabled, ruby.enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) - assert.Equal(t, tc.ExpectedString, ruby.string(), fmt.Sprintf("Failed in case: %s", tc.Case)) - } -} diff --git a/src/segment_session.go b/src/segment_session.go deleted file mode 100644 index 7a0b8e02d0f4..000000000000 --- a/src/segment_session.go +++ /dev/null @@ -1,143 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -type session struct { - props *properties - env environmentInfo - UserName string - DefaultUserName string - ComputerName string - SSHSession bool - Root bool - templateText string -} - -const ( - // UserInfoSeparator is put between the user and computer name - UserInfoSeparator Property = "user_info_separator" - // UserColor if set, is used to color the user name - UserColor Property = "user_color" - // HostColor if set, is used to color the computer name - HostColor Property = "host_color" - // DisplayHost hides or show the computer name - DisplayHost Property = "display_host" - // DisplayUser hides or shows the user name - DisplayUser Property = "display_user" - // SSHIcon shows when in an SSH session - SSHIcon Property = "ssh_icon" - // DefaultUserName holds the default user of the platform - DefaultUserName Property = "default_user_name" - - defaultUserEnvVar = "POSH_SESSION_DEFAULT_USER" -) - -func (s *session) enabled() bool { - s.UserName = s.getUserName() - s.ComputerName = s.getComputerName() - s.SSHSession = s.activeSSHSession() - s.DefaultUserName = s.getDefaultUser() - segmentTemplate := s.props.getString(SegmentTemplate, "") - if segmentTemplate != "" { - s.Root = s.env.isRunningAsRoot() - template := &textTemplate{ - Template: segmentTemplate, - Context: s, - Env: s.env, - } - var err error - s.templateText, err = template.render() - if err != nil { - s.templateText = err.Error() - } - return len(s.templateText) > 0 - } - showDefaultUser := s.props.getBool(DisplayDefault, true) - if !showDefaultUser && s.DefaultUserName == s.UserName { - return false - } - return true -} - -func (s *session) string() string { - return s.getFormattedText() -} - -func (s *session) init(props *properties, env environmentInfo) { - s.props = props - s.env = env -} - -func (s *session) getFormattedText() string { - if len(s.templateText) > 0 { - return s.templateText - } - separator := "" - if s.props.getBool(DisplayHost, true) && s.props.getBool(DisplayUser, true) { - separator = s.props.getString(UserInfoSeparator, "@") - } - var sshIcon string - if s.SSHSession { - sshIcon = s.props.getString(SSHIcon, "\uF817 ") - } - userColor := s.props.getColor(UserColor, s.props.foreground) - hostColor := s.props.getColor(HostColor, s.props.foreground) - if len(userColor) > 0 && len(hostColor) > 0 { - return fmt.Sprintf("%s<%s>%s%s<%s>%s", sshIcon, userColor, s.UserName, separator, hostColor, s.ComputerName) - } - if len(userColor) > 0 { - return fmt.Sprintf("%s<%s>%s%s%s", sshIcon, userColor, s.UserName, separator, s.ComputerName) - } - if len(hostColor) > 0 { - return fmt.Sprintf("%s%s%s<%s>%s", sshIcon, s.UserName, separator, hostColor, s.ComputerName) - } - return fmt.Sprintf("%s%s%s%s", sshIcon, s.UserName, separator, s.ComputerName) -} - -func (s *session) getComputerName() string { - if !s.props.getBool(DisplayHost, true) { - return "" - } - computername, err := s.env.getHostName() - if err != nil { - computername = "unknown" - } - return strings.TrimSpace(computername) -} - -func (s *session) getUserName() string { - if !s.props.getBool(DisplayUser, true) { - return "" - } - user := s.env.getCurrentUser() - username := strings.TrimSpace(user) - if s.env.getRuntimeGOOS() == "windows" && strings.Contains(username, "\\") { - username = strings.Split(username, "\\")[1] - } - return username -} - -func (s *session) getDefaultUser() string { - user := s.env.getenv(defaultUserEnvVar) - if len(user) == 0 { - user = s.props.getString(DefaultUserName, "") - } - return user -} - -func (s *session) activeSSHSession() bool { - keys := []string{ - "SSH_CONNECTION", - "SSH_CLIENT", - } - for _, key := range keys { - content := s.env.getenv(key) - if content != "" { - return true - } - } - return false -} diff --git a/src/segment_session_test.go b/src/segment_session_test.go deleted file mode 100644 index 5593b09e4947..000000000000 --- a/src/segment_session_test.go +++ /dev/null @@ -1,320 +0,0 @@ -package main - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPropertySessionSegment(t *testing.T) { - cases := []struct { - Case string - ExpectedEnabled bool - ExpectedString string - UserName string - Host string - DefaultUserName string - DefaultUserNameEnv string - SSHSession bool - SSHClient bool - Root bool - DisplayUser bool - DisplayHost bool - DisplayDefault bool - HostColor string - UserColor string - GOOS string - HostError bool - }{ - { - Case: "user and computer", - ExpectedString: "john at company-laptop", - Host: "company-laptop", - DisplayUser: true, - DisplayHost: true, - UserName: "john", - ExpectedEnabled: true, - }, - { - Case: "user and computer with host color", - ExpectedString: "john at company-laptop", - Host: "company-laptop", - DisplayUser: true, - DisplayHost: true, - UserName: "john", - HostColor: "yellow", - ExpectedEnabled: true, - }, - { - Case: "user and computer with user color", - ExpectedString: "john at company-laptop", - Host: "company-laptop", - DisplayUser: true, - DisplayHost: true, - UserName: "john", - UserColor: "yellow", - ExpectedEnabled: true, - }, - { - Case: "user and computer with both colors", - ExpectedString: "john at company-laptop", - Host: "company-laptop", - DisplayUser: true, - DisplayHost: true, - UserName: "john", - UserColor: "yellow", - HostColor: "green", - ExpectedEnabled: true, - }, - { - Case: "SSH Session", - ExpectedString: "ssh john at company-laptop", - Host: "company-laptop", - DisplayUser: true, - DisplayHost: true, - UserName: "john", - SSHSession: true, - ExpectedEnabled: true, - }, - { - Case: "SSH Client", - ExpectedString: "ssh john at company-laptop", - Host: "company-laptop", - DisplayUser: true, - DisplayHost: true, - UserName: "john", - SSHClient: true, - ExpectedEnabled: true, - }, - { - Case: "SSH Client", - ExpectedString: "ssh john at company-laptop", - Host: "company-laptop", - DisplayUser: true, - DisplayHost: true, - UserName: "john", - SSHClient: true, - ExpectedEnabled: true, - }, - { - Case: "only user name", - ExpectedString: "john", - Host: "company-laptop", - UserName: "john", - DisplayUser: true, - ExpectedEnabled: true, - }, - { - Case: "windows user name", - ExpectedString: "john at company-laptop", - Host: "company-laptop", - UserName: "surface\\john", - DisplayHost: true, - DisplayUser: true, - ExpectedEnabled: true, - GOOS: string(Windows), - }, - { - Case: "only host name", - ExpectedString: "company-laptop", - Host: "company-laptop", - UserName: "john", - DisplayDefault: true, - DisplayHost: true, - ExpectedEnabled: true, - }, - { - Case: "display default - hidden", - Host: "company-laptop", - UserName: "john", - DefaultUserName: "john", - DisplayDefault: false, - DisplayHost: true, - DisplayUser: true, - ExpectedEnabled: false, - }, - { - Case: "display default with env var - hidden", - Host: "company-laptop", - UserName: "john", - DefaultUserNameEnv: "john", - DefaultUserName: "jake", - DisplayDefault: false, - DisplayHost: true, - DisplayUser: true, - ExpectedEnabled: false, - }, - { - Case: "host error", - ExpectedString: "john at unknown", - Host: "company-laptop", - HostError: true, - UserName: "john", - DisplayHost: true, - DisplayUser: true, - ExpectedEnabled: true, - }, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getCurrentUser", nil).Return(tc.UserName) - env.On("getRuntimeGOOS", nil).Return(tc.GOOS) - if tc.HostError { - env.On("getHostName", nil).Return(tc.Host, errors.New("oh snap")) - } else { - env.On("getHostName", nil).Return(tc.Host, nil) - } - var SSHSession string - if tc.SSHSession { - SSHSession = "zezzion" - } - var SSHClient string - if tc.SSHClient { - SSHClient = "clientz" - } - env.On("getenv", "SSH_CONNECTION").Return(SSHSession) - env.On("getenv", "SSH_CLIENT").Return(SSHClient) - env.On("getenv", "SSH_CLIENT").Return(SSHSession) - env.On("getenv", defaultUserEnvVar).Return(tc.DefaultUserNameEnv) - env.On("isRunningAsRoot", nil).Return(tc.Root) - props := &properties{ - values: map[Property]interface{}{ - UserInfoSeparator: " at ", - SSHIcon: "ssh ", - DefaultUserName: tc.DefaultUserName, - DisplayDefault: tc.DisplayDefault, - DisplayUser: tc.DisplayUser, - DisplayHost: tc.DisplayHost, - HostColor: tc.HostColor, - UserColor: tc.UserColor, - }, - } - session := &session{ - env: env, - props: props, - } - assert.Equal(t, tc.ExpectedEnabled, session.enabled(), tc.Case) - if tc.ExpectedEnabled { - assert.Equal(t, tc.ExpectedString, session.string(), tc.Case) - } - } -} - -func TestSessionSegmentTemplate(t *testing.T) { - cases := []struct { - Case string - ExpectedEnabled bool - ExpectedString string - UserName string - DefaultUserName string - ComputerName string - SSHSession bool - Root bool - Template string - }{ - { - Case: "user and computer", - ExpectedString: "john@company-laptop", - ComputerName: "company-laptop", - UserName: "john", - Template: "{{.UserName}}{{if .ComputerName}}@{{.ComputerName}}{{end}}", - ExpectedEnabled: true, - }, - { - Case: "user only", - ExpectedString: "john", - UserName: "john", - Template: "{{.UserName}}{{if .ComputerName}}@{{.ComputerName}}{{end}}", - ExpectedEnabled: true, - }, - { - Case: "user with ssh", - ExpectedString: "john on remote", - UserName: "john", - SSHSession: true, - ComputerName: "remote", - Template: "{{.UserName}}{{if .SSHSession}} on {{.ComputerName}}{{end}}", - ExpectedEnabled: true, - }, - { - Case: "user without ssh", - ExpectedString: "john", - UserName: "john", - SSHSession: false, - ComputerName: "remote", - Template: "{{.UserName}}{{if .SSHSession}} on {{.ComputerName}}{{end}}", - ExpectedEnabled: true, - }, - { - Case: "user with root and ssh", - ExpectedString: "super john on remote", - UserName: "john", - SSHSession: true, - ComputerName: "remote", - Root: true, - Template: "{{if .Root}}super {{end}}{{.UserName}}{{if .SSHSession}} on {{.ComputerName}}{{end}}", - ExpectedEnabled: true, - }, - { - Case: "no template", - ExpectedString: "\uf817 john@remote", - UserName: "john", - SSHSession: true, - ComputerName: "remote", - Root: true, - ExpectedEnabled: true, - }, - { - Case: "default user not equal", - ExpectedString: "john", - UserName: "john", - DefaultUserName: "jack", - SSHSession: true, - ComputerName: "remote", - Root: true, - Template: "{{if ne .DefaultUserName .UserName}}{{.UserName}}{{end}}", - ExpectedEnabled: true, - }, - { - Case: "default user equal", - ExpectedString: "", - UserName: "john", - DefaultUserName: "john", - SSHSession: true, - ComputerName: "remote", - Root: true, - Template: "{{if ne .DefaultUserName .UserName}}{{.UserName}}{{end}}", - ExpectedEnabled: false, - }, - } - - for _, tc := range cases { - env := new(MockedEnvironment) - env.On("getCurrentUser", nil).Return(tc.UserName) - env.On("getRuntimeGOOS", nil).Return("burp") - env.On("getHostName", nil).Return(tc.ComputerName, nil) - var SSHSession string - if tc.SSHSession { - SSHSession = "zezzion" - } - env.On("getenv", "SSH_CONNECTION").Return(SSHSession) - env.On("getenv", "SSH_CLIENT").Return(SSHSession) - env.On("isRunningAsRoot", nil).Return(tc.Root) - env.On("getenv", defaultUserEnvVar).Return(tc.DefaultUserName) - props := &properties{ - values: map[Property]interface{}{ - SegmentTemplate: tc.Template, - }, - } - session := &session{ - env: env, - props: props, - } - assert.Equal(t, tc.ExpectedEnabled, session.enabled(), tc.Case) - if tc.ExpectedEnabled { - assert.Equal(t, tc.ExpectedString, session.string(), tc.Case) - } - } -} diff --git a/src/segment_shell.go b/src/segment_shell.go deleted file mode 100644 index c3fcc057d84b..000000000000 --- a/src/segment_shell.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -type shell struct { - props *properties - env environmentInfo -} - -func (s *shell) enabled() bool { - return true -} - -func (s *shell) string() string { - return s.env.getShellName() -} - -func (s *shell) init(props *properties, env environmentInfo) { - s.props = props - s.env = env -} diff --git a/src/segment_shell_test.go b/src/segment_shell_test.go deleted file mode 100644 index f808c6923b62..000000000000 --- a/src/segment_shell_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestWriteCurrentShell(t *testing.T) { - expected := "zsh" - env := new(MockedEnvironment) - env.On("getShellName", nil).Return(expected, nil) - props := &properties{} - s := &shell{ - env: env, - props: props, - } - assert.Equal(t, expected, s.string()) -} diff --git a/src/segment_spotify.go b/src/segment_spotify.go deleted file mode 100644 index 0d8e93af127d..000000000000 --- a/src/segment_spotify.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "fmt" -) - -type spotify struct { - props *properties - env environmentInfo - status string - artist string - track string -} - -const ( - // PlayingIcon indicates a song is playing - PlayingIcon Property = "playing_icon" - // PausedIcon indicates a song is paused - PausedIcon Property = "paused_icon" - // StoppedIcon indicates a song is stopped - StoppedIcon Property = "stopped_icon" - // TrackSeparator is put between the artist and the track - TrackSeparator Property = "track_separator" -) - -func (s *spotify) string() string { - icon := "" - switch s.status { - case "stopped": - // in this case, no artist or track info - icon = s.props.getString(StoppedIcon, "\uF04D ") - return icon - case "paused": - icon = s.props.getString(PausedIcon, "\uF8E3 ") - case "playing": - icon = s.props.getString(PlayingIcon, "\uE602 ") - } - separator := s.props.getString(TrackSeparator, " - ") - return fmt.Sprintf("%s%s%s%s", icon, s.artist, separator, s.track) -} - -func (s *spotify) init(props *properties, env environmentInfo) { - s.props = props - s.env = env -} diff --git a/src/segment_spotify_darwin.go b/src/segment_spotify_darwin.go deleted file mode 100644 index ea4f6ac7b28e..000000000000 --- a/src/segment_spotify_darwin.go +++ /dev/null @@ -1,27 +0,0 @@ -// +build darwin - -package main - -func (s *spotify) enabled() bool { - var err error - // Check if running - running := s.runAppleScriptCommand("application \"Spotify\" is running") - if running == "false" || running == "" { - return false - } - s.status = s.runAppleScriptCommand("tell application \"Spotify\" to player state as string") - if err != nil { - return false - } - if s.status == "stopped" { - return false - } - s.artist = s.runAppleScriptCommand("tell application \"Spotify\" to artist of current track as string") - s.track = s.runAppleScriptCommand("tell application \"Spotify\" to name of current track as string") - return true -} - -func (s *spotify) runAppleScriptCommand(command string) string { - val, _ := s.env.runCommand("osascript", "-e", command) - return val -} diff --git a/src/segment_spotify_darwin_test.go b/src/segment_spotify_darwin_test.go deleted file mode 100644 index 830386cfc1a5..000000000000 --- a/src/segment_spotify_darwin_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// +build darwin - -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -type spotifyArgs struct { - running string - status string - artist string - track string - runError error -} - -func bootStrapSpotifyDarwinTest(args *spotifyArgs) *spotify { - env := new(MockedEnvironment) - env.On("runCommand", "osascript", []string{"-e", "application \"Spotify\" is running"}).Return(args.running, args.runError) - env.On("runCommand", "osascript", []string{"-e", "tell application \"Spotify\" to player state as string"}).Return(args.status, nil) - env.On("runCommand", "osascript", []string{"-e", "tell application \"Spotify\" to artist of current track as string"}).Return(args.artist, nil) - env.On("runCommand", "osascript", []string{"-e", "tell application \"Spotify\" to name of current track as string"}).Return(args.track, nil) - props := &properties{} - s := &spotify{ - env: env, - props: props, - } - return s -} - -func TestSpotifyDarwinEnabledAndSpotifyNotRunning(t *testing.T) { - args := &spotifyArgs{ - running: "false", - } - s := bootStrapSpotifyDarwinTest(args) - assert.Equal(t, false, s.enabled()) -} - -func TestSpotifyDarwinEnabledAndSpotifyPlaying(t *testing.T) { - args := &spotifyArgs{ - running: "true", - status: "playing", - artist: "Candlemass", - track: "Spellbreaker", - } - s := bootStrapSpotifyDarwinTest(args) - assert.Equal(t, true, s.enabled()) - assert.Equal(t, "\ue602 Candlemass - Spellbreaker", s.string()) -} - -func TestSpotifyDarwinEnabledAndSpotifyPaused(t *testing.T) { - args := &spotifyArgs{ - running: "true", - status: "paused", - artist: "Candlemass", - track: "Spellbreaker", - } - s := bootStrapSpotifyDarwinTest(args) - assert.Equal(t, true, s.enabled()) - assert.Equal(t, "\uF8E3 Candlemass - Spellbreaker", s.string()) -} diff --git a/src/segment_spotify_others.go b/src/segment_spotify_others.go deleted file mode 100644 index 7e2c3c032afa..000000000000 --- a/src/segment_spotify_others.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build !darwin -// +build !windows - -package main - -func (s *spotify) enabled() bool { - return false -} diff --git a/src/segment_spotify_test.go b/src/segment_spotify_test.go deleted file mode 100644 index fe7c3b14a774..000000000000 --- a/src/segment_spotify_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSpotifyStringPlayingSong(t *testing.T) { - expected := "\ue602 Candlemass - Spellbreaker" - s := &spotify{ - artist: "Candlemass", - track: "Spellbreaker", - status: "playing", - } - assert.Equal(t, expected, s.string()) -} - -func TestSpotifyStringPausedSong(t *testing.T) { - expected := "\uF8E3 Candlemass - Spellbreaker" - s := &spotify{ - artist: "Candlemass", - track: "Spellbreaker", - status: "paused", - } - assert.Equal(t, expected, s.string()) -} - -func TestSpotifyStringStoppedSong(t *testing.T) { - expected := "\uf04d " - s := &spotify{ - artist: "Candlemass", - track: "Spellbreaker", - status: "stopped", - } - assert.Equal(t, expected, s.string()) -} diff --git a/src/segment_spotify_windows.go b/src/segment_spotify_windows.go deleted file mode 100644 index 138448d65871..000000000000 --- a/src/segment_spotify_windows.go +++ /dev/null @@ -1,28 +0,0 @@ -// +build windows - -package main - -import ( - "strings" -) - -func (s *spotify) enabled() bool { - // search for spotify window to retrieve the title - // Can be either "Spotify xxx" or the song name "Candlemass - Spellbreaker" - spotifyWindowTitle, err := s.env.getWindowTitle("spotify.exe", "^(Spotify.*)|(.*\\s-\\s.*)$") - if err != nil { - return false - } - - if !strings.Contains(spotifyWindowTitle, " - ") { - s.status = "stopped" - return false - } - - infos := strings.Split(spotifyWindowTitle, " - ") - s.artist = infos[0] - // remove first element and concat others(a song can contains also a " - ") - s.track = strings.Join(infos[1:], " - ") - s.status = "playing" - return true -} diff --git a/src/segment_spotify_windows_test.go b/src/segment_spotify_windows_test.go deleted file mode 100644 index 6b63b8caf8fd..000000000000 --- a/src/segment_spotify_windows_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// +build windows - -package main - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -type spotifyArgs struct { - title string - runError error -} - -func bootStrapSpotifyWindowsTest(args *spotifyArgs) *spotify { - env := new(MockedEnvironment) - env.On("getWindowTitle", "spotify.exe").Return(args.title, args.runError) - props := &properties{} - s := &spotify{ - env: env, - props: props, - } - return s -} - -func TestSpotifyWindowsEnabledAndSpotifyNotRunning(t *testing.T) { - args := &spotifyArgs{ - runError: errors.New(""), - } - s := bootStrapSpotifyWindowsTest(args) - assert.Equal(t, false, s.enabled()) -} - -func TestSpotifyWindowsEnabledAndSpotifyPlaying(t *testing.T) { - args := &spotifyArgs{ - title: "Candlemass - Spellbreaker", - } - s := bootStrapSpotifyWindowsTest(args) - assert.Equal(t, true, s.enabled()) - assert.Equal(t, "\ue602 Candlemass - Spellbreaker", s.string()) -} - -func TestSpotifyWindowsEnabledAndSpotifyStopped(t *testing.T) { - args := &spotifyArgs{ - title: "Spotify premium", - } - s := bootStrapSpotifyWindowsTest(args) - assert.Equal(t, false, s.enabled()) -} diff --git a/src/segment_terraform.go b/src/segment_terraform.go deleted file mode 100644 index 0188ad7533de..000000000000 --- a/src/segment_terraform.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -type terraform struct { - props *properties - env environmentInfo - workspaceName string -} - -func (tf *terraform) string() string { - return tf.workspaceName -} - -func (tf *terraform) init(props *properties, env environmentInfo) { - tf.props = props - tf.env = env -} - -func (tf *terraform) enabled() bool { - cmd := "terraform" - if !tf.env.hasCommand(cmd) || !tf.env.hasFolder(".terraform") { - return false - } - tf.workspaceName, _ = tf.env.runCommand(cmd, "workspace", "show") - return true -} diff --git a/src/segment_terraform_test.go b/src/segment_terraform_test.go deleted file mode 100644 index dc98ec998027..000000000000 --- a/src/segment_terraform_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -type terraformArgs struct { - hasTfCommand bool - hasTfFolder bool - workspaceName string -} - -func bootStrapTerraformTest(args *terraformArgs) *terraform { - env := new(MockedEnvironment) - env.On("hasCommand", "terraform").Return(args.hasTfCommand) - env.On("hasFolder", ".terraform").Return(args.hasTfFolder) - env.On("runCommand", "terraform", []string{"workspace", "show"}).Return(args.workspaceName, nil) - k := &terraform{ - env: env, - props: &properties{}, - } - return k -} - -func TestTerraformWriterDisabled(t *testing.T) { - args := &terraformArgs{ - hasTfCommand: false, - hasTfFolder: false, - } - terraform := bootStrapTerraformTest(args) - assert.False(t, terraform.enabled()) -} - -func TestTerraformMissingDir(t *testing.T) { - args := &terraformArgs{ - hasTfCommand: true, - hasTfFolder: false, - } - terraform := bootStrapTerraformTest(args) - assert.False(t, terraform.enabled()) -} - -func TestTerraformMissingBinary(t *testing.T) { - args := &terraformArgs{ - hasTfCommand: false, - hasTfFolder: true, - } - terraform := bootStrapTerraformTest(args) - assert.False(t, terraform.enabled()) -} - -func TestTerraformEnabled(t *testing.T) { - expected := "default" - args := &terraformArgs{ - hasTfCommand: true, - hasTfFolder: true, - workspaceName: expected, - } - terraform := bootStrapTerraformTest(args) - assert.True(t, terraform.enabled()) - assert.Equal(t, expected, terraform.string()) -} diff --git a/src/segment_test.go b/src/segment_test.go deleted file mode 100644 index 7765162db465..000000000000 --- a/src/segment_test.go +++ /dev/null @@ -1,198 +0,0 @@ -package main - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" -) - -const ( - cwd = "Projects/oh-my-posh" -) - -func TestMapSegmentWriterCanMap(t *testing.T) { - sc := &Segment{ - Type: Session, - } - env := new(MockedEnvironment) - err := sc.mapSegmentWithWriter(env) - assert.NotNil(t, sc.props) - assert.NoError(t, err) - assert.NotNil(t, sc.writer) -} - -func TestMapSegmentWriterCannotMap(t *testing.T) { - sc := &Segment{ - Type: "nilwriter", - } - env := new(MockedEnvironment) - err := sc.mapSegmentWithWriter(env) - assert.Nil(t, sc.props) - assert.Error(t, err) -} - -func TestParseTestConfig(t *testing.T) { - segmentJSON := - ` - { - "type": "path", - "style": "powerline", - "powerline_symbol": "\uE0B0", - "foreground": "#ffffff", - "background": "#61AFEF", - "properties": { - "prefix": " \uE5FF ", - "style": "folder", - "exclude_folders": [ - "/super/secret/project" - ] - } - } - ` - segment := &Segment{} - err := json.Unmarshal([]byte(segmentJSON), segment) - assert.NoError(t, err) -} - -func TestShouldIncludeFolder(t *testing.T) { - cases := []struct { - Case string - IncludeFolders []string - ExcludeFolders []string - Expected bool - }{ - {Case: "Base Case", IncludeFolders: nil, ExcludeFolders: nil, Expected: true}, - {Case: "Base Case Empty Arrays", IncludeFolders: []string{}, ExcludeFolders: []string{}, Expected: true}, - - {Case: "Include", IncludeFolders: []string{"Projects/oh-my-posh"}, ExcludeFolders: nil, Expected: true}, - {Case: "Include Regex", IncludeFolders: []string{"Projects.*"}, ExcludeFolders: nil, Expected: true}, - {Case: "Include Mismatch", IncludeFolders: []string{"Projects/nope"}, ExcludeFolders: nil, Expected: false}, - {Case: "Include Regex Mismatch", IncludeFolders: []string{"zProjects.*"}, ExcludeFolders: nil, Expected: false}, - - {Case: "Exclude", IncludeFolders: nil, ExcludeFolders: []string{"Projects/oh-my-posh"}, Expected: false}, - {Case: "Exclude Regex", IncludeFolders: nil, ExcludeFolders: []string{"Projects.*"}, Expected: false}, - {Case: "Exclude Mismatch", IncludeFolders: nil, ExcludeFolders: []string{"Projects/nope"}, Expected: true}, - {Case: "Exclude Regex Mismatch", IncludeFolders: nil, ExcludeFolders: []string{"zProjects.*"}, Expected: true}, - - {Case: "Include Match / Exclude Match", IncludeFolders: []string{"Projects.*"}, ExcludeFolders: []string{"Projects/oh-my-posh"}, Expected: false}, - {Case: "Include Match / Exclude Mismatch", IncludeFolders: []string{"Projects.*"}, ExcludeFolders: []string{"Projects/nope"}, Expected: true}, - {Case: "Include Mismatch / Exclude Match", IncludeFolders: []string{"zProjects.*"}, ExcludeFolders: []string{"Projects/oh-my-posh"}, Expected: false}, - {Case: "Include Mismatch / Exclude Mismatch", IncludeFolders: []string{"zProjects.*"}, ExcludeFolders: []string{"Projects/nope"}, Expected: false}, - } - for _, tc := range cases { - segment := &Segment{ - Properties: map[Property]interface{}{ - IncludeFolders: tc.IncludeFolders, - ExcludeFolders: tc.ExcludeFolders, - }, - } - got := segment.shouldIncludeFolder(cwd) - assert.Equal(t, tc.Expected, got, tc.Case) - } -} - -func TestShouldIncludeFolderRegexInverted(t *testing.T) { - segment := &Segment{ - Properties: map[Property]interface{}{ - ExcludeFolders: []string{"(?!Projects[\\/]).*"}, - }, - } - // detect panic(thrown by MustCompile) - defer func() { - if err := recover(); err != nil { - // display a message explaining omp failed(with the err) - assert.Equal(t, "regexp: Compile(`^(?!Projects[\\/]).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err) - } - }() - segment.shouldIncludeFolder(cwd) -} - -func TestShouldIncludeFolderRegexInvertedNonEscaped(t *testing.T) { - segment := &Segment{ - Properties: map[Property]interface{}{ - ExcludeFolders: []string{"(?!Projects/).*"}, - }, - } - // detect panic(thrown by MustCompile) - defer func() { - if err := recover(); err != nil { - // display a message explaining omp failed(with the err) - assert.Equal(t, "regexp: Compile(`^(?!Projects/).*$`): error parsing regexp: invalid or unsupported Perl syntax: `(?!`", err) - } - }() - segment.shouldIncludeFolder(cwd) -} - -func TestGetColors(t *testing.T) { - cases := []struct { - Case string - Background bool - ExpectedColor string - Templates []string - DefaultColor string - Region string - Profile string - }{ - {Case: "No template - foreground", ExpectedColor: "color", Background: false, DefaultColor: "color"}, - {Case: "No template - background", ExpectedColor: "color", Background: true, DefaultColor: "color"}, - {Case: "Nil template", ExpectedColor: "color", DefaultColor: "color", Templates: nil}, - { - Case: "Template - default", - ExpectedColor: "color", - DefaultColor: "color", - Templates: []string{ - "{{if contains \"john\" .Profile}}color2{{end}}", - }, - Profile: "doe", - }, - { - Case: "Template - override", - ExpectedColor: "color2", - DefaultColor: "color", - Templates: []string{ - "{{if contains \"john\" .Profile}}color2{{end}}", - }, - Profile: "john", - }, - { - Case: "Template - override multiple", - ExpectedColor: "color3", - DefaultColor: "color", - Templates: []string{ - "{{if contains \"doe\" .Profile}}color2{{end}}", - "{{if contains \"john\" .Profile}}color3{{end}}", - }, - Profile: "john", - }, - { - Case: "Template - override multiple no match", - ExpectedColor: "color", - DefaultColor: "color", - Templates: []string{ - "{{if contains \"doe\" .Profile}}color2{{end}}", - "{{if contains \"philip\" .Profile}}color3{{end}}", - }, - Profile: "john", - }, - } - for _, tc := range cases { - segment := &Segment{ - writer: &aws{ - Profile: tc.Profile, - Region: tc.Region, - }, - } - if tc.Background { - segment.Background = tc.DefaultColor - segment.BackgroundTemplates = tc.Templates - color := segment.background() - assert.Equal(t, tc.ExpectedColor, color, tc.Case) - continue - } - segment.Foreground = tc.DefaultColor - segment.ForegroundTemplates = tc.Templates - color := segment.foreground() - assert.Equal(t, tc.ExpectedColor, color, tc.Case) - } -} diff --git a/src/segment_text.go b/src/segment_text.go deleted file mode 100644 index d35d0d72d9dd..000000000000 --- a/src/segment_text.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -type text struct { - props *properties - env environmentInfo -} - -const ( - // TextProperty represents text to write - TextProperty Property = "text" -) - -func (t *text) enabled() bool { - return true -} - -func (t *text) string() string { - textProperty := t.props.getString(TextProperty, "!!text property not defined!!") - return textProperty -} - -func (t *text) init(props *properties, env environmentInfo) { - t.props = props - t.env = env -} diff --git a/src/segment_time.go b/src/segment_time.go deleted file mode 100644 index 1404d5c02227..000000000000 --- a/src/segment_time.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import "time" - -type tempus struct { - props *properties - env environmentInfo - templateText string - CurrentDate time.Time -} - -const ( - // TimeFormat uses the reference time Mon Jan 2 15:04:05 MST 2006 to show the pattern with which to format the current time - TimeFormat Property = "time_format" -) - -func (t *tempus) enabled() bool { - // if no date set, use now(unit testing) - if t.CurrentDate.IsZero() { - t.CurrentDate = time.Now() - } - segmentTemplate := t.props.getString(SegmentTemplate, "") - if segmentTemplate != "" { - template := &textTemplate{ - Template: segmentTemplate, - Context: t, - Env: t.env, - } - var err error - t.templateText, err = template.render() - if err != nil { - t.templateText = err.Error() - } - return len(t.templateText) > 0 - } - return true -} - -func (t *tempus) string() string { - return t.getFormattedText() -} - -func (t *tempus) init(props *properties, env environmentInfo) { - t.props = props - t.env = env -} - -func (t *tempus) getFormattedText() string { - if len(t.templateText) > 0 { - return t.templateText - } - // keep old behaviour if no template - timeFormatProperty := t.props.getString(TimeFormat, "15:04:05") - return t.CurrentDate.Format(timeFormatProperty) -} diff --git a/src/segment_ytm.go b/src/segment_ytm.go deleted file mode 100644 index 6912ec945040..000000000000 --- a/src/segment_ytm.go +++ /dev/null @@ -1,106 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" -) - -type ytm struct { - props *properties - env environmentInfo - status playStatus - artist string - track string -} - -const ( - // APIURL is the YTMDA Remote Control API URL property. - APIURL Property = "api_url" -) - -func (y *ytm) string() string { - icon := "" - separator := y.props.getString(TrackSeparator, " - ") - switch y.status { - case paused: - icon = y.props.getString(PausedIcon, "\uF8E3 ") - case playing: - icon = y.props.getString(PlayingIcon, "\uE602 ") - case stopped: - return y.props.getString(StoppedIcon, "\uF04D ") - } - return fmt.Sprintf("%s%s%s%s", icon, y.artist, separator, y.track) -} - -func (y *ytm) enabled() bool { - err := y.setStatus() - // If we don't get a response back (error), the user isn't running - // YTMDA, or they don't have the RC API enabled. - return err == nil -} - -func (y *ytm) init(props *properties, env environmentInfo) { - y.props = props - y.env = env -} - -type playStatus int - -const ( - playing playStatus = iota - paused - stopped -) - -type ytmdaStatusResponse struct { - player `json:"player"` - track `json:"track"` -} - -type player struct { - HasSong bool `json:"hasSong"` - IsPaused bool `json:"isPaused"` - VolumePercent int `json:"volumePercent"` - SeekbarCurrentPosition int `json:"seekbarCurrentPosition"` - SeekbarCurrentPositionHuman string `json:"seekbarCurrentPositionHuman"` - StatePercent float64 `json:"statePercent"` - LikeStatus string `json:"likeStatus"` - RepeatType string `json:"repeatType"` -} - -type track struct { - Author string `json:"author"` - Title string `json:"title"` - Album string `json:"album"` - Cover string `json:"cover"` - Duration int `json:"duration"` - DurationHuman string `json:"durationHuman"` - URL string `json:"url"` - ID string `json:"id"` - IsVideo bool `json:"isVideo"` - IsAdvertisement bool `json:"isAdvertisement"` - InLibrary bool `json:"inLibrary"` -} - -func (y *ytm) setStatus() error { - // https://github.com/ytmdesktop/ytmdesktop/wiki/Remote-Control-API - url := y.props.getString(APIURL, "http://127.0.0.1:9863") - body, err := y.env.doGet(url + "/query") - if err != nil { - return err - } - q := new(ytmdaStatusResponse) - err = json.Unmarshal(body, &q) - if err != nil { - return err - } - y.status = playing - if !q.player.HasSong { - y.status = stopped - } else if q.player.IsPaused { - y.status = paused - } - y.artist = q.track.Author - y.track = q.track.Title - return nil -} diff --git a/src/segment_ytm_test.go b/src/segment_ytm_test.go deleted file mode 100644 index c0f98bac6613..000000000000 --- a/src/segment_ytm_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package main - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestYTMStringPlayingSong(t *testing.T) { - expected := "\ue602 Candlemass - Spellbreaker" - y := &ytm{ - artist: "Candlemass", - track: "Spellbreaker", - status: playing, - } - assert.Equal(t, expected, y.string()) -} - -func TestYTMStringPausedSong(t *testing.T) { - expected := "\uF8E3 Candlemass - Spellbreaker" - y := &ytm{ - artist: "Candlemass", - track: "Spellbreaker", - status: paused, - } - assert.Equal(t, expected, y.string()) -} - -func TestYTMStringStoppedSong(t *testing.T) { - expected := "\uf04d " - y := &ytm{ - artist: "Candlemass", - track: "Spellbreaker", - status: stopped, - } - assert.Equal(t, expected, y.string()) -} - -func bootstrapYTMDATest(json string, err error) *ytm { - url := "http://127.0.0.1:9863" - env := new(MockedEnvironment) - env.On("doGet", url+"/query").Return([]byte(json), err) - props := &properties{ - values: map[Property]interface{}{ - APIURL: url, - }, - } - ytm := &ytm{ - env: env, - props: props, - } - return ytm -} - -func TestYTMDAPlaying(t *testing.T) { - json := `{ "player": { "hasSong": true, "isPaused": false }, "track": { "author": "Candlemass", "title": "Spellbreaker" } }` - ytm := bootstrapYTMDATest(json, nil) - err := ytm.setStatus() - assert.NoError(t, err) - assert.Equal(t, playing, ytm.status) - assert.Equal(t, "Candlemass", ytm.artist) - assert.Equal(t, "Spellbreaker", ytm.track) -} - -func TestYTMDAPaused(t *testing.T) { - json := `{ "player": { "hasSong": true, "isPaused": true }, "track": { "author": "Candlemass", "title": "Spellbreaker" } }` - ytm := bootstrapYTMDATest(json, nil) - err := ytm.setStatus() - assert.NoError(t, err) - assert.Equal(t, paused, ytm.status) - assert.Equal(t, "Candlemass", ytm.artist) - assert.Equal(t, "Spellbreaker", ytm.track) -} - -func TestYTMDAStopped(t *testing.T) { - json := `{ "player": { "hasSong": false }, "track": { "author": "", "title": "" } }` - ytm := bootstrapYTMDATest(json, nil) - err := ytm.setStatus() - assert.NoError(t, err) - assert.Equal(t, stopped, ytm.status) - assert.Equal(t, "", ytm.artist) - assert.Equal(t, "", ytm.track) -} - -func TestYTMDAError(t *testing.T) { - json := `{ "player": { "hasSong": false }, "track": { "author": "", "title": "" } }` - ytm := bootstrapYTMDATest(json, errors.New("Oh noes")) - enabled := ytm.enabled() - assert.False(t, enabled) -} diff --git a/src/segments/angular.go b/src/segments/angular.go new file mode 100644 index 000000000000..ac04e45636cb --- /dev/null +++ b/src/segments/angular.go @@ -0,0 +1,31 @@ +package segments + +import ( + "path/filepath" +) + +type Angular struct { + Language +} + +func (a *Angular) Template() string { + return languageTemplate +} + +func (a *Angular) Enabled() bool { + a.extensions = []string{"angular.json"} + a.tooling = map[string]*cmd{ + "angular": { + regex: `(?:(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, + getVersion: a.getVersion, + }, + } + a.defaultTooling = []string{"angular"} + a.versionURLTemplate = "https://github.com/angular/angular/releases/tag/{{.Full}}" + + return a.Language.Enabled() +} + +func (a *Angular) getVersion() (string, error) { + return a.nodePackageVersion(filepath.Join("@angular", "core")) +} diff --git a/src/segments/argocd.go b/src/segments/argocd.go new file mode 100644 index 000000000000..9479f197ddee --- /dev/null +++ b/src/segments/argocd.go @@ -0,0 +1,102 @@ +package segments + +import ( + "errors" + "os" + "path" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/spf13/pflag" + yaml "go.yaml.in/yaml/v3" +) + +const ( + argocdOptsEnv = "ARGOCD_OPTS" + argocdInvalidYaml = "invalid yaml" + argocdNoCurrent = "no current context" + + NameTemplate = " {{ .Name }} " +) + +type ArgocdContext struct { + Name string `yaml:"name"` + Server string `yaml:"server"` + User string `yaml:"user"` +} + +type ArgocdConfig struct { + CurrentContext string `yaml:"current-context"` + Contexts []*ArgocdContext `yaml:"contexts"` +} + +type Argocd struct { + Base + + ArgocdContext +} + +func (a *Argocd) Template() string { + return NameTemplate +} + +func (a *Argocd) Enabled() bool { + // always parse config instead of using cli to save time + configPath := a.getConfigPath() + succeeded, err := a.parseConfig(configPath) + if err != nil { + log.Error(err) + return false + } + return succeeded +} + +func (a *Argocd) getConfigPath() string { + cp := path.Join(a.env.Home(), ".config", "argocd", "config") + cpo := a.getConfigFromOpts() + if len(cpo) > 0 { + cp = cpo + } + return cp +} + +func (a *Argocd) getConfigFromOpts() string { + // don't exit/panic when encountering invalid flags + flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError) + // ignore other valid and invalid flags + flags.ParseErrorsAllowlist.UnknownFlags = true + // only care about config + flags.String("config", "", "get config from opts") + + opts := a.env.Getenv(argocdOptsEnv) + _ = flags.Parse(strings.Split(opts, " ")) + return flags.Lookup("config").Value.String() +} + +func (a *Argocd) parseConfig(file string) (bool, error) { + config := a.env.FileContent(file) + // missing or empty file content + if config == "" { + return false, errors.New(argocdInvalidYaml) + } + + var data ArgocdConfig + err := yaml.Unmarshal([]byte(config), &data) + if err != nil { + log.Error(err) + return false, errors.New(argocdInvalidYaml) + } + a.Name = data.CurrentContext + for _, context := range data.Contexts { + if context.Name == a.Name { + // mandatory fields in yaml + if context.Server == "" || context.User == "" { + return false, errors.New(argocdInvalidYaml) + } + a.Server = context.Server + a.User = context.User + return true, nil + } + } + return false, errors.New(argocdNoCurrent) +} diff --git a/src/segments/argocd_test.go b/src/segments/argocd_test.go new file mode 100644 index 000000000000..51da74265820 --- /dev/null +++ b/src/segments/argocd_test.go @@ -0,0 +1,276 @@ +package segments + +import ( + "fmt" + "path" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/stretchr/testify/assert" +) + +const ( + poshHome = "/Users/posh" +) + +func TestArgocdGetConfigFromOpts(t *testing.T) { + configFile := "/Users/posh/.config/argocd/config" + cases := []struct { + Case string + Opts string + Expected string + }{ + {Case: "invalid flag in opts", Opts: "--invalid", Expected: ""}, + {Case: "no config in opts", Opts: "--grpc-web", Expected: ""}, + { + Case: "config in opts", + Opts: fmt.Sprintf("--grpc-web --config %s --plaintext", configFile), + Expected: configFile, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Getenv", argocdOptsEnv).Return(tc.Opts) + + argocd := &Argocd{ + Base: Base{ + env: env, + options: options.Map{}, + }, + } + config := argocd.getConfigFromOpts() + assert.Equal(t, tc.Expected, config, tc.Case) + } +} + +func TestArgocdGetConfigPath(t *testing.T) { + configFile := path.Join(poshHome, ".config", "argocd", "config") + cases := []struct { + Case string + Opts string + Expected string + ExpectedError string + }{ + {Case: "without opts", Expected: configFile}, + {Case: "with opts", Opts: "--config /etc/argocd/config", Expected: "/etc/argocd/config"}, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Home").Return(poshHome) + env.On("Getenv", argocdOptsEnv).Return(tc.Opts) + + argocd := &Argocd{ + Base: Base{ + env: env, + options: options.Map{}, + }, + } + assert.Equal(t, tc.Expected, argocd.getConfigPath()) + } +} + +func TestArgocdParseConfig(t *testing.T) { + configFile := "/Users/posh/.config/argocd/config" + cases := []struct { + ExpectedContext ArgocdContext + Case string + Config string + ExpectedError string + Expected bool + }{ + {Case: "missing or empty yaml", Config: "", ExpectedError: argocdInvalidYaml}, + { + Case: "invalid yaml", + ExpectedError: argocdInvalidYaml, + Config: ` +[context] +context +`, + }, + { + Case: "invalid config", + ExpectedError: argocdInvalidYaml, + Config: ` +contexts: + - name: context1 + server: server1 + user: user1 + - name: context2 + server: server2 + userr: user2 +current-context: context2 +servers: + - grpc-web: true + server: server1 + - grpc-web: false + server: serve2 +`, + }, + { + Case: "no current context found", + ExpectedError: argocdNoCurrent, + Config: ` +contexts: + - name: context1 + server: server1 + user: user1 + - name: context2 + server: server2 + user: user2 +`, + }, + { + Case: "current context found", + Expected: true, + Config: ` +contexts: + - name: context1 + server: server1 + user: user1 + - name: context2 + server: server2 + user: user2 +current-context: context2 +servers: + - grpc-web: true + server: server1 + - grpc-web: false + server: serve2 +users: + - auth-token: authtoken1 + name: user1 + refresh-token: refreshtoken1 + - auth-token: authtoken2 + name: user2 + refresh-token: refreshtoken2 +`, + ExpectedContext: ArgocdContext{ + Name: "context2", + Server: "server2", + User: "user2", + }, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("FileContent", configFile).Return(tc.Config) + + argocd := &Argocd{ + Base: Base{ + env: env, + options: options.Map{}, + }, + } + if len(tc.ExpectedError) > 0 { + _, err := argocd.parseConfig(configFile) + assert.EqualError(t, err, tc.ExpectedError, tc.Case) + continue + } + config, err := argocd.parseConfig(configFile) + assert.NoErrorf(t, err, tc.Case) + assert.Equal(t, tc.Expected, config, tc.Case) + assert.Equal(t, tc.ExpectedContext, argocd.ArgocdContext, tc.Case) + } +} + +func TestArgocdSegment(t *testing.T) { + configFile := path.Join(poshHome, ".config", "argocd", "config") + cases := []struct { + ExpectedContext ArgocdContext + Case string + Opts string + Config string + Template string + ExpectedString string + ExpectedError string + ExpectedEnabled bool + }{ + { + Case: "default template", + Opts: "", + Config: ` +contexts: + - name: context1 + server: server1 + user: user1 + - name: context2 + server: server2 + user: user2 +current-context: context2 +servers: + - grpc-web: true + server: server1 + - grpc-web: false + server: serve2 +`, + ExpectedString: "context2", + ExpectedEnabled: true, + ExpectedContext: ArgocdContext{ + Name: "context2", + Server: "server2", + User: "user2", + }, + }, + { + Case: "full template", + Opts: "", + Config: ` +contexts: + - name: context1 + server: server1 + user: user1 + - name: context2 + server: server2 + user: user2 +current-context: context2 +servers: + - grpc-web: true + server: server1 + - grpc-web: false + server: serve2 +`, + Template: "{{ .Name }}:{{ .User}}@{{ .Server }}", + ExpectedString: "context2:user2@server2", + ExpectedEnabled: true, + ExpectedContext: ArgocdContext{ + Name: "context2", + Server: "server2", + User: "user2", + }, + }, + { + Case: "broken config", + Config: `}`, + ExpectedEnabled: false, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Home").Return(poshHome) + env.On("Getenv", argocdOptsEnv).Return(tc.Opts) + env.On("FileContent", configFile).Return(tc.Config) + env.On("Flags").Return(&runtime.Flags{}) + + argocd := &Argocd{} + argocd.Init(options.Map{}, env) + + assert.Equal(t, tc.ExpectedEnabled, argocd.Enabled(), tc.Case) + + if !tc.ExpectedEnabled { + continue + } + + assert.Equal(t, tc.ExpectedContext, argocd.ArgocdContext, tc.Case) + if len(tc.Template) > 0 { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, argocd), tc.Case) + } else { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, argocd.Template(), argocd), tc.Case) + } + } +} diff --git a/src/segments/aurelia.go b/src/segments/aurelia.go new file mode 100644 index 000000000000..54b72b51826c --- /dev/null +++ b/src/segments/aurelia.go @@ -0,0 +1,31 @@ +package segments + +type Aurelia struct { + Language +} + +func (a *Aurelia) Template() string { + return languageTemplate +} + +func (a *Aurelia) Enabled() bool { + a.extensions = []string{fileName} + a.tooling = map[string]*cmd{ + "aurelia": { + regex: versionRegexSemver, + getVersion: a.getVersion, + }, + } + a.defaultTooling = []string{"aurelia"} + a.versionURLTemplate = "https://github.com/aurelia/aurelia/releases/tag/v{{ .Full }}" + + if !a.hasNodePackage("aurelia") { + return false + } + + return a.Language.Enabled() +} + +func (a *Aurelia) getVersion() (string, error) { + return a.nodePackageVersion("aurelia") +} diff --git a/src/segments/aws.go b/src/segments/aws.go new file mode 100644 index 000000000000..3aa9fa81cdf7 --- /dev/null +++ b/src/segments/aws.go @@ -0,0 +1,108 @@ +package segments + +import ( + "fmt" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type Aws struct { + Base + + Profile string + Region string +} + +const ( + defaultUser = "default" +) + +func (a *Aws) Template() string { + return " {{ .Profile }}{{ if .Region }}@{{ .Region }}{{ end }} " +} + +func (a *Aws) Enabled() bool { + getEnvFirstMatch := func(envs ...string) string { + for _, env := range envs { + value := a.env.Getenv(env) + if len(value) != 0 { + return value + } + } + + return "" + } + + displayDefaultUser := a.options.Bool(options.DisplayDefault, true) + a.Profile = getEnvFirstMatch("AWS_VAULT", "AWS_DEFAULT_PROFILE", "AWS_PROFILE") + if !displayDefaultUser && a.Profile == defaultUser { + return false + } + + a.Region = getEnvFirstMatch("AWS_REGION", "AWS_DEFAULT_REGION") + if len(a.Profile) != 0 && len(a.Region) != 0 { + return true + } + + if a.Profile == "" && len(a.Region) != 0 && displayDefaultUser { + a.Profile = defaultUser + return true + } + + a.getConfigFileInfo() + if !displayDefaultUser && a.Profile == defaultUser { + return false + } + + return len(a.Profile) != 0 +} + +func (a *Aws) getConfigFileInfo() { + configPath := a.env.Getenv("AWS_CONFIG_FILE") + if configPath == "" { + configPath = fmt.Sprintf("%s/.aws/config", a.env.Home()) + } + + config := a.env.FileContent(configPath) + configSection := "[default]" + if len(a.Profile) != 0 { + configSection = fmt.Sprintf("[profile %s]", a.Profile) + } + + configLines := strings.SplitSeq(config, "\n") + var sectionActive bool + for line := range configLines { + if strings.HasPrefix(line, configSection) { + sectionActive = true + continue + } + + if sectionActive && strings.HasPrefix(line, "region") { + splitted := strings.SplitN(line, "=", 3) + if len(splitted) >= 2 { + a.Region = strings.TrimSpace(splitted[1]) + break + } + } + } + + if a.Profile == "" && len(a.Region) != 0 { + a.Profile = defaultUser + } +} + +func (a *Aws) RegionAlias() string { + if a.Region == "" { + return "" + } + + splitted := strings.Split(a.Region, "-") + if len(splitted) < 2 { + return a.Region + } + + splitted[1] = regex.ReplaceAllString(`orth|outh|ast|est|entral`, splitted[1], "") + return strings.Join(splitted, "") +} diff --git a/src/segments/aws_test.go b/src/segments/aws_test.go new file mode 100644 index 000000000000..1a07030935f5 --- /dev/null +++ b/src/segments/aws_test.go @@ -0,0 +1,87 @@ +package segments + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +func TestAWSSegment(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Profile string + DefaultProfile string + Vault string + Region string + DefaultRegion string + ConfigFile string + Template string + ExpectedEnabled bool + DisplayDefault bool + }{ + {Case: "enabled with default user", ExpectedString: "default@eu-west", Region: "eu-west", ExpectedEnabled: true, DisplayDefault: true}, + {Case: "disabled with default user", ExpectedString: "default@eu-west", Region: "eu-west", ExpectedEnabled: false, DisplayDefault: false}, + {Case: "disabled", ExpectedString: "", ExpectedEnabled: false}, + {Case: "enabled with default user", ExpectedString: "default@eu-west", Profile: "default", Region: "eu-west", ExpectedEnabled: true, DisplayDefault: true}, + {Case: "enabled with default profile", ExpectedString: "default@eu-west", DefaultProfile: "default", Region: "eu-west", ExpectedEnabled: true, DisplayDefault: true}, + {Case: "disabled with default user", ExpectedString: "default", Profile: "default", Region: "eu-west", ExpectedEnabled: false, DisplayDefault: false}, + {Case: "enabled no region", ExpectedString: "company", ExpectedEnabled: true, Profile: "company"}, + {Case: "enabled with region", ExpectedString: "company@eu-west", ExpectedEnabled: true, Profile: "company", Region: "eu-west", DefaultRegion: "us-west"}, + {Case: "enabled with default region", ExpectedString: "company@us-west", ExpectedEnabled: true, Profile: "company", DefaultRegion: "us-west"}, + { + Case: "template: enabled no region", + ExpectedString: "profile: company", + ExpectedEnabled: true, + Profile: "company", + Template: "profile: {{.Profile}}{{if .Region}} in {{.Region}}{{end}}", + }, + { + Case: "template: enabled with region", + ExpectedString: "profile: company in eu-west", + ExpectedEnabled: true, + Profile: "company", + Region: "eu-west", + Template: "profile: {{.Profile}}{{if .Region}} in {{.Region}}{{end}}", + }, + { + Case: "template: enabled with region alias that has compound cardinal direction", + ExpectedString: "profile: company in apne3", + ExpectedEnabled: true, + Profile: "company", + Region: "ap-northeast-3", + Template: "profile: {{.Profile}}{{if .Region}} in {{.RegionAlias}}{{end}}", + }, + {Case: "template: invalid", ExpectedString: "{{ .Burp", ExpectedEnabled: true, Profile: "c", Template: "{{ .Burp"}, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Getenv", "AWS_VAULT").Return(tc.Vault) + env.On("Getenv", "AWS_PROFILE").Return(tc.Profile) + env.On("Getenv", "AWS_DEFAULT_PROFILE").Return(tc.DefaultProfile) + env.On("Getenv", "AWS_REGION").Return(tc.Region) + env.On("Getenv", "AWS_DEFAULT_REGION").Return(tc.DefaultRegion) + env.On("Getenv", "AWS_CONFIG_FILE").Return(tc.ConfigFile) + env.On("FileContent", "/usr/home/.aws/config").Return("") + env.On("Home").Return("/usr/home") + props := options.Map{ + options.DisplayDefault: tc.DisplayDefault, + } + env.On("Flags").Return(&runtime.Flags{}) + + aws := &Aws{} + aws.Init(props, env) + + if tc.Template == "" { + tc.Template = aws.Template() + } + + assert.Equal(t, tc.ExpectedEnabled, aws.Enabled(), tc.Case) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, aws), tc.Case) + } +} diff --git a/src/segments/az.go b/src/segments/az.go new file mode 100644 index 000000000000..3b201091f8a6 --- /dev/null +++ b/src/segments/az.go @@ -0,0 +1,173 @@ +package segments + +import ( + "encoding/json" + "errors" + "path/filepath" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type Az struct { + Base + + Origin string + AzureSubscription +} + +const ( + Source options.Option = "source" + + Pwsh = "pwsh" + Cli = "cli" + // this deprecated value is used to support the old behavior of first_match + FirstMatch = "cli|pwsh" + azureEnv = "POSH_AZURE_SUBSCRIPTION" +) + +type AzureConfig struct { + InstallationID string `json:"installationId"` + Subscriptions []*AzureSubscription `json:"subscriptions"` +} + +type AzureSubscription struct { + User *AzureUser `json:"user"` + ID string `json:"id"` + Name string `json:"name"` + State string `json:"state"` + TenantID string `json:"tenantId"` + TenantDisplayName string `json:"tenantDisplayName"` + EnvironmentName string `json:"environmentName"` + HomeTenantID string `json:"homeTenantId"` + ManagedByTenants []any `json:"managedByTenants"` + IsDefault bool `json:"isDefault"` +} + +type AzureUser struct { + Name string `json:"name"` + Type string `json:"type"` +} + +type AzurePowerShellSubscription struct { + Name string `json:"Name"` + Account struct { + Type string `json:"Type"` + } `json:"Account"` + Environment struct { + Name string `json:"Name"` + } `json:"Environment"` + Subscription struct { + ID string `json:"Id"` + Name string `json:"Name"` + State string `json:"State"` + ExtendedProperties struct { + Account string `json:"Account"` + } `json:"ExtendedProperties"` + } `json:"Subscription"` + Tenant struct { + ID string `json:"Id"` + Name string `json:"Name"` + } `json:"Tenant"` +} + +func (a *Az) Template() string { + return NameTemplate +} + +func (a *Az) Enabled() bool { + source := a.options.String(Source, FirstMatch) + + // migrate first_match + if source == "first_match" { + source = FirstMatch + } + + sources := strings.SplitSeq(source, "|") + + for source := range sources { + switch source { + case Pwsh: + if OK := a.getModuleSubscription(); OK { + return OK + } + case Cli: + if OK := a.getCLISubscription(); OK { + return OK + } + } + } + + return false +} + +func (a *Az) FileContentWithoutBom(file string) string { + config := a.env.FileContent(file) + const ByteOrderMark = "\ufeff" + return strings.TrimLeft(config, ByteOrderMark) +} + +func (a *Az) getCLISubscription() bool { + cfg, err := a.findConfig("azureProfile.json") + if err != nil { + return false + } + content := a.FileContentWithoutBom(cfg) + if content == "" { + return false + } + var config AzureConfig + if err := json.Unmarshal([]byte(content), &config); err != nil { + return false + } + for _, subscription := range config.Subscriptions { + if subscription.IsDefault { + a.AzureSubscription = *subscription + a.Origin = "CLI" + return true + } + } + return false +} + +func (a *Az) getModuleSubscription() bool { + envSubscription := a.env.Getenv(azureEnv) + if envSubscription == "" { + return false + } + + var config AzurePowerShellSubscription + if err := json.Unmarshal([]byte(envSubscription), &config); err != nil { + return false + } + + a.IsDefault = true + a.EnvironmentName = config.Environment.Name + a.TenantID = config.Tenant.ID + a.ID = config.Subscription.ID + a.Name = config.Subscription.Name + a.State = config.Subscription.State + a.User = &AzureUser{ + Name: config.Subscription.ExtendedProperties.Account, + Type: config.Account.Type, + } + a.TenantDisplayName = config.Tenant.Name + + a.Origin = "PWSH" + + return true +} + +func (a *Az) findConfig(fileName string) (string, error) { + configDirs := []string{ + a.env.Getenv("AZURE_CONFIG_DIR"), + filepath.Join(a.env.Home(), ".azure"), + filepath.Join(a.env.Home(), ".Azure"), + } + for _, dir := range configDirs { + if len(dir) != 0 && a.env.HasFilesInDir(dir, fileName) { + return filepath.Join(dir, fileName), nil + } + } + return "", errors.New("azure config dir not found") +} diff --git a/src/segments/az_functions.go b/src/segments/az_functions.go new file mode 100644 index 000000000000..1f9ec232c96e --- /dev/null +++ b/src/segments/az_functions.go @@ -0,0 +1,25 @@ +package segments + +type AzFunc struct { + Language +} + +func (az *AzFunc) Template() string { + return languageTemplate +} + +const azFuncToolName = "func" + +func (az *AzFunc) Enabled() bool { + az.extensions = []string{"host.json", "local.settings.json", "function.json"} + az.tooling = map[string]*cmd{ + azFuncToolName: { + executable: azFuncToolName, + args: []string{versionFlagArg}, + regex: `(?P[0-9.]+)`, + }, + } + az.defaultTooling = []string{azFuncToolName} + + return az.Language.Enabled() +} diff --git a/src/segments/az_test.go b/src/segments/az_test.go new file mode 100644 index 000000000000..979594a87edc --- /dev/null +++ b/src/segments/az_test.go @@ -0,0 +1,149 @@ +package segments + +import ( + "os" + "path/filepath" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/template" + + "github.com/stretchr/testify/assert" +) + +func TestAzSegment(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Template string + Source string + ExpectedEnabled bool + HasCLI bool + HasPowerShell bool + }{ + { + Case: "no config files found", + ExpectedEnabled: false, + }, + { + Case: "Az CLI Profile", + ExpectedEnabled: true, + ExpectedString: "AzureCliCloud", + Template: "{{ .EnvironmentName }}", + HasCLI: true, + }, + { + Case: "Az Pwsh Profile", + ExpectedEnabled: true, + ExpectedString: "AzurePoshCloud", + Template: "{{ .EnvironmentName }}", + HasPowerShell: true, + }, + { + Case: "Az Pwsh Profile", + ExpectedEnabled: true, + ExpectedString: "AzurePoshCloud", + Template: "{{ .EnvironmentName }}", + HasPowerShell: true, + }, + { + Case: "Faulty template", + ExpectedEnabled: true, + ExpectedString: template.IncorrectTemplate, + Template: "{{ .Burp }}", + HasPowerShell: true, + }, + { + Case: "PWSH", + ExpectedEnabled: true, + ExpectedString: "PWSH", + Template: "{{ .Origin }}", + HasPowerShell: true, + }, + { + Case: "CLI", + ExpectedEnabled: true, + ExpectedString: "CLI", + Template: "{{ .Origin }}", + HasCLI: true, + }, + { + Case: "Az CLI Profile only", + ExpectedEnabled: true, + ExpectedString: "AzureCliCloud", + Template: "{{ .EnvironmentName }}", + HasCLI: true, + Source: Cli, + }, + { + Case: "Az CLI Profile only - disabled", + ExpectedEnabled: false, + Template: "{{ .EnvironmentName }}", + HasCLI: false, + Source: Cli, + }, + { + Case: "PowerShell Profile only", + ExpectedEnabled: true, + ExpectedString: "AzurePoshCloud", + Template: "{{ .EnvironmentName }}", + HasPowerShell: true, + Source: Pwsh, + }, + { + Case: "Az CLI Profile only - disabled", + ExpectedEnabled: false, + Template: "{{ .EnvironmentName }}", + Source: Pwsh, + }, + { + Case: "Az CLI account type", + ExpectedEnabled: true, + ExpectedString: "user", + Template: "{{ .User.Type }}", + HasCLI: true, + Source: Cli, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Home").Return(poshHome) + env.On("Flags").Return(&runtime.Flags{}) + + var azureProfile, azureRmContext string + + if tc.HasCLI { + content, _ := os.ReadFile("../test/azureProfile.json") + azureProfile = string(content) + } + if tc.HasPowerShell { + content, _ := os.ReadFile("../test/AzureRmContext.json") + azureRmContext = string(content) + } + + env.On("GOOS").Return(runtime.LINUX) + env.On("FileContent", filepath.Join(poshHome, ".azure", "azureProfile.json")).Return(azureProfile) + env.On("Getenv", "POSH_AZURE_SUBSCRIPTION").Return(azureRmContext) + env.On("Getenv", "AZURE_CONFIG_DIR").Return("") + + if tc.HasCLI { + env.On("HasFilesInDir", filepath.Clean("/Users/posh/.azure"), "azureProfile.json").Return(true) + } else { + env.On("HasFilesInDir", filepath.Clean("/Users/posh/.azure"), "azureProfile.json").Return(false) + env.On("HasFilesInDir", filepath.Clean("/Users/posh/.Azure"), "azureProfile.json").Return(false) + } + + if tc.Source == "" { + tc.Source = FirstMatch + } + + az := &Az{} + az.Init(options.Map{}, env) + + assert.Equal(t, tc.ExpectedEnabled, az.Enabled(), tc.Case) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, az), tc.Case) + } +} diff --git a/src/segments/azd.go b/src/segments/azd.go new file mode 100644 index 000000000000..369337e3bf12 --- /dev/null +++ b/src/segments/azd.go @@ -0,0 +1,70 @@ +package segments + +import ( + "encoding/json" + "path/filepath" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +type Azd struct { + Base + AzdConfig +} + +type AzdConfig struct { + DefaultEnvironment string `json:"defaultEnvironment"` + Version int `json:"version"` +} + +func (t *Azd) Template() string { + return " \uebd8 {{ .DefaultEnvironment }} " +} + +func (t *Azd) Enabled() bool { + var parentFilePath string + + folders := t.options.StringArray(LanguageFolders, []string{".azure"}) + for _, folder := range folders { + if file, err := t.env.HasParentFilePath(folder, false); err == nil { + parentFilePath = file.ParentFolder + break + } + } + + if parentFilePath == "" { + log.Debug("no .azure folder found in parent directories") + return false + } + + dotAzureFolder := filepath.Join(parentFilePath, ".azure") + files := t.env.LsDir(dotAzureFolder) + + for _, file := range files { + if file.IsDir() { + continue + } + + if strings.EqualFold(file.Name(), "config.json") { + return t.TryReadConfigJSON(filepath.Join(dotAzureFolder, file.Name())) + } + } + + return false +} + +func (t *Azd) TryReadConfigJSON(file string) bool { + if file == "" { + return false + } + + content := t.env.FileContent(file) + var config AzdConfig + if err := json.Unmarshal([]byte(content), &config); err != nil { + return false + } + + t.AzdConfig = config + return true +} diff --git a/src/segments/azd_test.go b/src/segments/azd_test.go new file mode 100644 index 000000000000..1e01a8a345fc --- /dev/null +++ b/src/segments/azd_test.go @@ -0,0 +1,69 @@ +package segments + +import ( + "errors" + "io/fs" + "path/filepath" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/stretchr/testify/assert" +) + +func TestAzdSegment(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Template string + ExpectedEnabled bool + IsInited bool + }{ + { + Case: "no .azure directory found", + ExpectedEnabled: false, + }, + { + Case: "Environment located", + ExpectedEnabled: true, + ExpectedString: "TestEnvironment", + Template: "{{ .DefaultEnvironment }}", + IsInited: true, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Flags").Return(&runtime.Flags{}) + + if tc.IsInited { + fileInfo := &runtime.FileInfo{ + Path: "test/.azure", + ParentFolder: "test", + IsDir: true, + } + env.On("HasParentFilePath", ".azure", false).Return(fileInfo, nil) + dirEntries := []fs.DirEntry{ + &MockDirEntry{ + name: "config.json", + isDir: false, + }, &MockDirEntry{ + name: "TestEnvironment", + isDir: true, + }, + } + env.On("LsDir", filepath.Join("test", ".azure")).Return(dirEntries, nil) + + env.On("FileContent", filepath.Join("test", ".azure", "config.json")).Return(`{"version": 1, "defaultEnvironment": "TestEnvironment"}`, nil) + } else { + env.On("HasParentFilePath", ".azure", false).Return(&runtime.FileInfo{}, errors.New("no such file or directory")) + } + + azd := Azd{} + azd.Init(options.Map{}, env) + + assert.Equal(t, tc.ExpectedEnabled, azd.Enabled(), tc.Case) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, azd), tc.Case) + } +} diff --git a/src/segments/base.go b/src/segments/base.go new file mode 100644 index 000000000000..3a209f83cc53 --- /dev/null +++ b/src/segments/base.go @@ -0,0 +1,40 @@ +package segments + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type Base struct { + options options.Provider + env runtime.Environment + + Segment *Segment +} + +type Segment struct { + Text string + Index int +} + +func (b *Base) Text() string { + return b.Segment.Text +} + +func (b *Base) SetText(text string) { + b.Segment.Text = text +} + +func (b *Base) SetIndex(index int) { + b.Segment.Index = index +} + +func (b *Base) Init(opts options.Provider, env runtime.Environment) { + b.Segment = &Segment{} + b.options = opts + b.env = env +} + +func (b *Base) CacheKey() (string, bool) { + return "", false +} diff --git a/src/segments/battery.go b/src/segments/battery.go new file mode 100644 index 000000000000..0307af5e73fa --- /dev/null +++ b/src/segments/battery.go @@ -0,0 +1,83 @@ +package segments + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/runtime/battery" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type Battery struct { + Base + Error string + Icon string + battery.Info +} + +const ( + // ChargingIcon to display when charging + ChargingIcon options.Option = "charging_icon" + // DischargingIcon o display when discharging + DischargingIcon options.Option = "discharging_icon" + // ChargedIcon to display when fully charged + ChargedIcon options.Option = "charged_icon" + // NotChargingIcon to display when on AC power + NotChargingIcon options.Option = "not_charging_icon" +) + +func (b *Battery) Template() string { + return " {{ if not .Error }}{{ .Icon }}{{ .Percentage }}{{ end }}{{ .Error }} " +} + +func (b *Battery) Enabled() bool { + // disable in WSL1 + if b.env.IsWsl() && !b.env.IsWsl2() { + return false + } + + info, err := b.env.BatteryState() + + if !b.enabledWhileError(err) { + return false + } + + b.Info = *info + + // case on computer without batteries(no error, empty array) + if err == nil && b.Percentage == 0 { + return false + } + + switch b.State { + case battery.Discharging: + b.Icon = b.options.String(DischargingIcon, "") + case battery.NotCharging: + b.Icon = b.options.String(NotChargingIcon, "") + case battery.Charging: + b.Icon = b.options.String(ChargingIcon, "") + case battery.Full: + b.Icon = b.options.String(ChargedIcon, "") + case battery.Empty, battery.Unknown: + return true + } + return true +} + +func (b *Battery) enabledWhileError(err error) bool { + if err == nil { + return true + } + if _, ok := err.(*battery.NoBatteryError); ok { + return false + } + displayError := b.options.Bool(options.DisplayError, false) + if !displayError { + return false + } + b.Error = err.Error() + // On Windows, it sometimes errors when the battery is full. + // This hack ensures we display a fully charged battery, even if + // that state can be incorrect. It's better to "ignore" the error + // than to not display the segment at all as that will confuse users. + b.Percentage = 100 + b.State = battery.Full + return true +} diff --git a/src/segments/bazel.go b/src/segments/bazel.go new file mode 100644 index 000000000000..1ba1bbd6770c --- /dev/null +++ b/src/segments/bazel.go @@ -0,0 +1,38 @@ +package segments + +import "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + +type Bazel struct { + Icon string + Language +} + +const ( + // Bazel's icon + Icon options.Option = "icon" +) + +func (b *Bazel) Template() string { + return " {{ if .Error }}{{ .Icon }} {{ .Error }}{{ else }}{{ url .Icon .URL }} {{ .Full }}{{ end }} " +} + +const bazelToolName = "bazel" + +func (b *Bazel) Enabled() bool { + b.extensions = []string{"*.bazel", "*.bzl", "BUILD", "WORKSPACE", ".bazelrc", ".bazelversion"} + b.folders = []string{"bazel-bin", "bazel-out", "bazel-testlogs"} + b.tooling = map[string]*cmd{ + bazelToolName: { + executable: bazelToolName, + args: []string{versionFlagArg}, + regex: `bazel ` + versionRegex, + }, + } + b.defaultTooling = []string{bazelToolName} + // Use the correct URL for Bazel >5.4.1, since they do not have the docs subdomain. + b.versionURLTemplate = "https://{{ if lt .Major 6 }}docs.{{ end }}bazel.build/versions/{{ .Major }}.{{ .Minor }}.{{ .Patch }}" + + b.Icon = b.options.String(Icon, "\ue63a") + + return b.Language.Enabled() +} diff --git a/src/segments/bazel_test.go b/src/segments/bazel_test.go new file mode 100644 index 000000000000..72ff213193ff --- /dev/null +++ b/src/segments/bazel_test.go @@ -0,0 +1,39 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBazel(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + Template string + }{ + {Case: "bazel 4.0.0", ExpectedString: "https://docs.bazel.build/versions/4.0.0\ue63a 4.0.0", Version: "bazel 4.0.0", Template: ""}, + {Case: "bazel 5.4.1", ExpectedString: "https://docs.bazel.build/versions/5.4.1\ue63a 5.4.1", Version: "bazel 5.4.1", Template: ""}, + {Case: "bazel 6.4.0", ExpectedString: "https://bazel.build/versions/6.4.0\ue63a 6.4.0", Version: "bazel 6.4.0", Template: ""}, + {Case: "bazel 7.1.1", ExpectedString: "https://bazel.build/versions/7.1.1\ue63a 7.1.1", Version: "bazel 7.1.1", Template: ""}, + {Case: "bazel 10.11.12", ExpectedString: "https://bazel.build/versions/10.11.12\ue63a 10.11.12", Version: "bazel 10.11.12", Template: ""}, + {Case: "", ExpectedString: "\ue63a err parsing info from bazel with", Version: "", Template: ""}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "bazel", + versionParam: "--version", + versionOutput: tc.Version, + extension: "*.bazel", + } + env, props := getMockedLanguageEnv(params) + props[Icon] = "\ue63a" + b := &Bazel{} + b.Init(props, env) + failMsg := fmt.Sprintf("Failed in case: %s", tc.Case) + assert.True(t, b.Enabled(), failMsg) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, b.Template(), b), failMsg) + } +} diff --git a/src/segments/brewfather.go b/src/segments/brewfather.go new file mode 100644 index 000000000000..a2bb6449c115 --- /dev/null +++ b/src/segments/brewfather.go @@ -0,0 +1,277 @@ +package segments + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "math" + "net/http" + "sort" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +// segment struct, makes templating easier +type Brewfather struct { + Base + + DaysBottledOrFermented *uint + TemperatureTrendIcon string + StatusIcon string + DayIcon string + URL string + Batch + ReadingAge int + DaysFermenting uint + DaysBottled uint +} + +const ( + BFUserID options.Option = "user_id" + BFBatchID options.Option = "batch_id" + + BFDoubleUpIcon options.Option = "doubleup_icon" + BFSingleUpIcon options.Option = "singleup_icon" + BFFortyFiveUpIcon options.Option = "fortyfiveup_icon" + BFFlatIcon options.Option = "flat_icon" + BFFortyFiveDownIcon options.Option = "fortyfivedown_icon" + BFSingleDownIcon options.Option = "singledown_icon" + BFDoubleDownIcon options.Option = "doubledown_icon" + + BFPlanningStatusIcon options.Option = "planning_status_icon" + BFBrewingStatusIcon options.Option = "brewing_status_icon" + BFFermentingStatusIcon options.Option = "fermenting_status_icon" + BFConditioningStatusIcon options.Option = "conditioning_status_icon" + BFCompletedStatusIcon options.Option = "completed_status_icon" + BFArchivedStatusIcon options.Option = "archived_status_icon" + + BFDayIcon options.Option = "day_icon" + + BFStatusPlanning string = "Planning" + BFStatusBrewing string = "Brewing" + BFStatusFermenting string = "Fermenting" + BFStatusConditioning string = "Conditioning" + BFStatusCompleted string = "Completed" + BFStatusArchived string = "Archived" +) + +// Returned from https://api.brewfather.app/v1/batches/batch_id/readings +type BatchReading struct { + Comment string `json:"comment"` + DeviceType string `json:"type"` + DeviceID string `json:"id"` + Gravity float64 `json:"sg"` + Temperature float64 `json:"temp"` + Timepoint int64 `json:"timepoint"` + Time int64 `json:"time"` +} +type Batch struct { + Reading *BatchReading + Status string `json:"status"` + BatchName string `json:"name"` + Recipe struct { + Name string `json:"name"` + } `json:"recipe"` + BatchNumber int `json:"batchNo"` + BrewDate int64 `json:"brewDate"` + FermentStartDate int64 `json:"fermentationStartDate"` + BottlingDate int64 `json:"bottlingDate"` + MeasuredOg float64 `json:"measuredOg"` + MeasuredFg float64 `json:"measuredFg"` + MeasuredAbv float64 `json:"measuredAbv"` + TemperatureTrend float64 +} + +func (bf *Brewfather) Template() string { + return " {{ .StatusIcon }} {{ if .DaysBottledOrFermented }}{{ .DaysBottledOrFermented }}{{ .DayIcon }} {{ end }}{{ url .Recipe.Name .URL }} {{ printf \"%.1f\" .MeasuredAbv }}%{{ if and (.Reading) (eq .Status \"Fermenting\") }} {{ printf \"%.3f\" .Reading.Gravity }} {{ .Reading.Temperature }}\u00b0 {{ .TemperatureTrendIcon }}{{ end }} " //nolint:lll +} + +func (bf *Brewfather) Enabled() bool { + data, err := bf.getResult() + if err != nil { + return false + } + bf.Batch = *data + + if bf.Reading != nil { + readingDate := time.UnixMilli(bf.Reading.Time) + bf.ReadingAge = int(time.Since(readingDate).Hours()) + } else { + bf.ReadingAge = -1 + } + + bf.TemperatureTrendIcon = bf.getTrendIcon(bf.TemperatureTrend) + bf.StatusIcon = bf.getBatchStatusIcon(data.Status) + + fermStartDate := time.UnixMilli(bf.FermentStartDate) + bottlingDate := time.UnixMilli(bf.BottlingDate) + + switch bf.Status { + case BFStatusFermenting: + // in the fermenter now, so relative to today. + bf.DaysFermenting = uint(time.Since(fermStartDate).Hours() / 24) + bf.DaysBottled = 0 + bf.DaysBottledOrFermented = &bf.DaysFermenting + case BFStatusConditioning, BFStatusCompleted, BFStatusArchived: + bf.DaysFermenting = uint(bottlingDate.Sub(fermStartDate).Hours() / 24) + bf.DaysBottled = uint(time.Since(bottlingDate).Hours() / 24) + bf.DaysBottledOrFermented = &bf.DaysBottled + default: + bf.DaysFermenting = 0 + bf.DaysBottled = 0 + bf.DaysBottledOrFermented = nil + } + + // URL property set to weblink to the full batch page + batchID := bf.options.String(BFBatchID, "") + if len(batchID) > 0 { + bf.URL = fmt.Sprintf("https://web.brewfather.app/tabs/batches/batch/%s", batchID) + } + + bf.DayIcon = bf.options.String(BFDayIcon, "d") + + return true +} + +func (bf *Brewfather) getTrendIcon(trend float64) string { + // Not a fan of this logic - wondering if Go lets us do something cleaner... + if trend >= 0 { + if trend > 4 { + return bf.options.String(BFDoubleUpIcon, "↑↑") + } + + if trend > 2 { + return bf.options.String(BFSingleUpIcon, "↑") + } + + if trend > 0.5 { + return bf.options.String(BFFortyFiveUpIcon, "↗") + } + + return bf.options.String(BFFlatIcon, "→") + } + + if trend < -4 { + return bf.options.String(BFDoubleDownIcon, "↓↓") + } + + if trend < -2 { + return bf.options.String(BFSingleDownIcon, "↓") + } + + if trend < -0.5 { + return bf.options.String(BFFortyFiveDownIcon, "↘") + } + + return bf.options.String(BFFlatIcon, "→") +} + +func (bf *Brewfather) getBatchStatusIcon(batchStatus string) string { + switch batchStatus { + case BFStatusPlanning: + return bf.options.String(BFPlanningStatusIcon, "\uF8EA") + case BFStatusBrewing: + return bf.options.String(BFBrewingStatusIcon, "\uF7DE") + case BFStatusFermenting: + return bf.options.String(BFFermentingStatusIcon, "\uF499") + case BFStatusConditioning: + return bf.options.String(BFConditioningStatusIcon, "\uE372") + case BFStatusCompleted: + return bf.options.String(BFCompletedStatusIcon, "\uF7A5") + case BFStatusArchived: + return bf.options.String(BFArchivedStatusIcon, "\uF187") + default: + return "" + } +} + +func (bf *Brewfather) getResult() (*Batch, error) { + userID := bf.options.Template(BFUserID, "", bf) + if userID == "" { + return nil, errors.New("missing Brewfather user id (user_id)") + } + + apiKey := bf.options.Template(APIKey, "", bf) + if apiKey == "" { + return nil, errors.New("missing Brewfather api key (api_key)") + } + + batchID := bf.options.Template(BFBatchID, "", bf) + if batchID == "" { + return nil, errors.New("missing Brewfather batch id (batch_id)") + } + + authString := fmt.Sprintf("%s:%s", userID, apiKey) + authStringb64 := base64.StdEncoding.EncodeToString([]byte(authString)) + authHeader := fmt.Sprintf("Basic %s", authStringb64) + batchURL := fmt.Sprintf("https://api.brewfather.app/v1/batches/%s", batchID) + batchReadingsURL := fmt.Sprintf("https://api.brewfather.app/v1/batches/%s/readings", batchID) + + httpTimeout := bf.options.Int(options.HTTPTimeout, options.DefaultHTTPTimeout) + + // batch + addAuthHeader := func(request *http.Request) { + request.Header.Add("authorization", authHeader) + } + + body, err := bf.env.HTTPRequest(batchURL, nil, httpTimeout, addAuthHeader) + if err != nil { + return nil, err + } + + var batch Batch + err = json.Unmarshal(body, &batch) + if err != nil { + return nil, err + } + + // readings + body, err = bf.env.HTTPRequest(batchReadingsURL, nil, httpTimeout, addAuthHeader) + if err != nil { + return nil, err + } + + var arr []*BatchReading + err = json.Unmarshal(body, &arr) + if err != nil { + return nil, err + } + + if len(arr) > 0 { + // could just take latest reading using their API, but that won't allow us to see trend - get 'em all and sort by time, + // using two most recent for trend + sort.Slice(arr, func(i, j int) bool { + return arr[i].Time > arr[j].Time + }) + + // Keep the latest one + batch.Reading = arr[0] + + if len(arr) > 1 { + batch.TemperatureTrend = arr[0].Temperature - arr[1].Temperature + } + } + + return &batch, nil +} + +// Unit conversion functions available to template. +func (bf *Brewfather) DegCToF(degreesC float64) float64 { + return math.Round(10*((degreesC*1.8)+32)) / 10 // 1 decimal place +} + +func (bf *Brewfather) DegCToKelvin(degreesC float64) float64 { + return math.Round(10*(degreesC+273.15)) / 10 // 1 decimal place, only addition, but just to be sure +} + +func (bf *Brewfather) SGToBrix(sg float64) float64 { + // from https://en.wikipedia.org/wiki/Brix#Specific_gravity_2 + return math.Round(100*((182.4601*sg*sg*sg)-(775.6821*sg*sg)+(1262.7794*sg)-669.5622)) / 100 +} + +func (bf *Brewfather) SGToPlato(sg float64) float64 { + // from https://en.wikipedia.org/wiki/Brix#Specific_gravity_2 + return math.Round(100*((135.997*sg*sg*sg)-(630.272*sg*sg)+(1111.14*sg)-616.868)) / 100 // 2 decimal places +} diff --git a/src/segments/brewfather_test.go b/src/segments/brewfather_test.go new file mode 100644 index 000000000000..ac8e934bb919 --- /dev/null +++ b/src/segments/brewfather_test.go @@ -0,0 +1,223 @@ +package segments + +import ( + "encoding/json" + "testing" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +const ( + BFFakeBatchID = "FAKE" + BFBatchURL = "https://api.brewfather.app/v1/batches/" + BFFakeBatchID + BFCacheKey = BFBatchURL + BFBatchReadingsURL = "https://api.brewfather.app/v1/batches/" + BFFakeBatchID + "/readings" +) + +var ( + TimeNow = time.Now() + // Create a fake timeline for the fake json, all in Unix milliseconds, to be used in all fake json responses + FakeBrewDate = TimeNow.Add(-time.Hour * 24 * 20) + FakeFermentationStartDate = FakeBrewDate.Add(time.Hour * 24) // 1 day after brew date = 19 days ago + FakeReading1Date = FakeFermentationStartDate.Add(time.Minute * 35) // first reading 35 minutes + FakeReading2Date = FakeReading1Date.Add(time.Hour) // second reading 1 hour later + FakeReading3Date = FakeReading2Date.Add(time.Hour * 3) // 3 hours after last reading, 454 hours ago + FakeBottlingDate = FakeFermentationStartDate.Add(time.Hour * 24 * 14) // 14 days after ferm date = 5 days ago + + BrewDateMillis = FakeBrewDate.UnixMilli() + FermStartDateMillis = FakeFermentationStartDate.UnixMilli() + Reading1DateMillis = FakeReading1Date.UnixMilli() + Reading2DateMillis = FakeReading2Date.UnixMilli() + Reading3DateMillis = FakeReading3Date.UnixMilli() + BottlingDateMillis = FakeBottlingDate.UnixMilli() + + BatchNumber = 18 + BatchName = "Batch" + RecipeName = "Fake Beer" + MeasuredAbv = 1.3 +) + +func createBatch(status string) *Batch { + return &Batch{ + Status: status, + BatchNumber: BatchNumber, + BrewDate: BrewDateMillis, + FermentStartDate: FermStartDateMillis, + BottlingDate: BottlingDateMillis, + BatchName: BatchName, + MeasuredAbv: MeasuredAbv, + Recipe: struct { + Name string `json:"name"` + }{ + Name: RecipeName, + }, + } +} + +func createReading(temp, gravity float64, millis int64) *BatchReading { + return &BatchReading{ + DeviceID: "manual", + Temperature: temp, + Comment: "", + Gravity: gravity, + Time: millis, + DeviceType: "manual", + } +} + +func TestBrewfatherSegment(t *testing.T) { + cases := []struct { + Error error + BatchResponse *Batch + Case string + ExpectedString string + Template string + BatchReadingsResponse []*BatchReading + CacheTimeout int + ExpectedEnabled bool + CacheFoundFail bool + }{ + { + Case: "Planning Status", + BatchResponse: createBatch(BFStatusPlanning), + BatchReadingsResponse: []*BatchReading{}, + Template: "{{.StatusIcon}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d {{end}}{{.Recipe.Name}} {{.MeasuredAbv}}%{{ if and (.Reading) (eq .Status \"Fermenting\")}}: {{.Reading.Gravity}} {{.Reading.Temperature}}° {{.TemperatureTrendIcon}}{{end}}", //nolint:lll + ExpectedString: " Fake Beer 1.3%", + ExpectedEnabled: true, + }, + { + Case: "Brewing Status", + BatchResponse: createBatch(BFStatusBrewing), + BatchReadingsResponse: []*BatchReading{}, + Template: "{{.StatusIcon}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d {{end}}{{.Recipe.Name}} {{.MeasuredAbv}}%{{ if and (.Reading) (eq .Status \"Fermenting\")}}: {{.Reading.Gravity}} {{.Reading.Temperature}}° {{.TemperatureTrendIcon}}{{end}}", //nolint:lll + ExpectedString: " Fake Beer 1.3%", + ExpectedEnabled: true, + }, + { + Case: "Fermenting Status, no readings", + BatchResponse: createBatch(BFStatusFermenting), + BatchReadingsResponse: []*BatchReading{}, + Template: "{{.StatusIcon}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d {{end}}{{.Recipe.Name}} {{.MeasuredAbv}}%{{ if and (.Reading) (eq .Status \"Fermenting\")}}: {{.Reading.Gravity}} {{.Reading.Temperature}}° {{.TemperatureTrendIcon}}{{end}}", //nolint:lll + ExpectedString: " 19d Fake Beer 1.3%", + ExpectedEnabled: true, + }, + { + Case: "Fermenting Status, one reading", + BatchResponse: createBatch(BFStatusFermenting), + BatchReadingsResponse: []*BatchReading{ + createReading(19.5, 1.066, Reading1DateMillis), + }, + Template: "{{.StatusIcon}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d {{end}}{{.Recipe.Name}} {{.MeasuredAbv}}%{{ if and (.Reading) (eq .Status \"Fermenting\")}}: {{.Reading.Gravity}} {{.Reading.Temperature}}° {{.TemperatureTrendIcon}}{{end}}", //nolint:lll + ExpectedString: " 19d Fake Beer 1.3%: 1.066 19.5° →", + ExpectedEnabled: true, + }, + { + Case: "Fermenting Status, two readings, temp trending up", + BatchResponse: createBatch(BFStatusFermenting), + BatchReadingsResponse: []*BatchReading{ + createReading(21.0, 1.063, Reading2DateMillis), + createReading(19.5, 1.066, Reading1DateMillis), + }, + Template: "{{.StatusIcon}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d {{end}}{{.Recipe.Name}} {{.MeasuredAbv}}%{{ if and (.Reading) (eq .Status \"Fermenting\")}}: {{.Reading.Gravity}} {{.Reading.Temperature}}° {{.TemperatureTrendIcon}}{{end}}", //nolint:lll + ExpectedString: " 19d Fake Beer 1.3%: 1.063 21° ↗", + ExpectedEnabled: true, + }, + { + Case: "Fermenting Status, three readings, temp trending hard down, include age of most recent reading", + BatchResponse: createBatch(BFStatusFermenting), + BatchReadingsResponse: []*BatchReading{ + createReading(15.0, 1.050, Reading3DateMillis), + createReading(21.0, 1.063, Reading2DateMillis), + createReading(19.5, 1.066, Reading1DateMillis), + }, + Template: "{{.StatusIcon}} {{.ReadingAge}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d {{end}}{{.Recipe.Name}} {{.MeasuredAbv}}%{{ if and (.Reading) (eq .Status \"Fermenting\")}}: {{.Reading.Gravity}} {{.Reading.Temperature}}° {{.TemperatureTrendIcon}}{{end}}", //nolint:lll + ExpectedString: " 451 19d Fake Beer 1.3%: 1.05 15° ↓↓", + ExpectedEnabled: true, + }, + { + Case: "Bad batch json, readings fine", + BatchResponse: nil, + BatchReadingsResponse: []*BatchReading{ + createReading(15.0, 1.050, Reading3DateMillis), + }, + Template: "{{.StatusIcon}} {{.ReadingAge}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d {{end}}{{.Recipe.Name}} {{.MeasuredAbv}}%{{ if and (.Reading) (eq .Status \"Fermenting\")}}: {{.Reading.Gravity}} {{.Reading.Temperature}}° {{.TemperatureTrendIcon}}{{end}}", //nolint:lll + ExpectedString: "", + ExpectedEnabled: false, + }, + { + Case: "Conditioning Status", + BatchResponse: createBatch(BFStatusConditioning), + BatchReadingsResponse: []*BatchReading{ + createReading(15.0, 1.050, Reading3DateMillis), + }, + Template: "{{.StatusIcon}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d {{end}}{{.Recipe.Name}} {{.MeasuredAbv}}%{{ if and (.Reading) (eq .Status \"Fermenting\")}}: {{.Reading.Gravity}} {{.Reading.Temperature}}° {{.TemperatureTrendIcon}}{{end}}", //nolint:lll + ExpectedString: " 5d Fake Beer 1.3%", + ExpectedEnabled: true, + }, + { + Case: "Fermenting Status, test all unit conversions", + BatchResponse: createBatch(BFStatusFermenting), + BatchReadingsResponse: []*BatchReading{ + createReading(34.5, 1.066, Reading1DateMillis), + }, + Template: "{{ if and (.Reading) (eq .Status \"Fermenting\") }}SG: ({{.Reading.Gravity}} Bx:{{.SGToBrix .Reading.Gravity}} P:{{.SGToPlato .Reading.Gravity}}), Temp: (C:{{.Reading.Temperature}} F:{{.DegCToF .Reading.Temperature}} K:{{.DegCToKelvin .Reading.Temperature}}){{end}}", //nolint:lll + ExpectedString: "SG: (1.066 Bx:16.13 P:16.13), Temp: (C:34.5 F:94.1 K:307.7)", + ExpectedEnabled: true, + }, + { + Case: "Fermenting Status, test all unit conversions 2", + BatchResponse: createBatch(BFStatusFermenting), + BatchReadingsResponse: []*BatchReading{ + createReading(3.5, 1.004, Reading1DateMillis), + }, + Template: "{{ if and (.Reading) (eq .Status \"Fermenting\") }}SG: ({{.Reading.Gravity}} Bx:{{.SGToBrix .Reading.Gravity}} P:{{.SGToPlato .Reading.Gravity}}), Temp: (C:{{.Reading.Temperature}} F:{{.DegCToF .Reading.Temperature}} K:{{.DegCToKelvin .Reading.Temperature}}){{end}}", //nolint:lll + ExpectedString: "SG: (1.004 Bx:1.03 P:1.03), Temp: (C:3.5 F:38.3 K:276.7)", + ExpectedEnabled: true, + }, + } + + for _, tc := range cases { + env := &mock.Environment{} + props := options.Map{ + BFBatchID: BFFakeBatchID, + APIKey: "FAKE", + BFUserID: "FAKE", + } + + var batchJSON []byte + var err error + if tc.BatchResponse != nil { + batchJSON, err = json.Marshal(tc.BatchResponse) + assert.NoError(t, err) + } else { + // bad JSON + batchJSON = []byte("invalid json") + } + + batchReadingsJSON, err := json.Marshal(tc.BatchReadingsResponse) + assert.NoError(t, err) + + env.On("HTTPRequest", BFBatchURL).Return(batchJSON, tc.Error) + env.On("HTTPRequest", BFBatchReadingsURL).Return(batchReadingsJSON, tc.Error) + env.On("Flags").Return(&runtime.Flags{}) + + brew := &Brewfather{} + brew.Init(props, env) + + enabled := brew.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + if !enabled { + continue + } + + if tc.Template == "" { + tc.Template = brew.Template() + } + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, brew), tc.Case) + } +} diff --git a/src/segments/buf.go b/src/segments/buf.go new file mode 100644 index 000000000000..e2c0f1a5a3f4 --- /dev/null +++ b/src/segments/buf.go @@ -0,0 +1,26 @@ +package segments + +type Buf struct { + Language +} + +func (b *Buf) Template() string { + return languageTemplate +} + +const bufToolName = "buf" + +func (b *Buf) Enabled() bool { + b.extensions = []string{"buf.yaml", "buf.gen.yaml", "buf.work.yaml"} + b.tooling = map[string]*cmd{ + bufToolName: { + executable: bufToolName, + args: []string{versionFlagArg}, + regex: versionRegexPrefixed, + }, + } + b.defaultTooling = []string{bufToolName} + b.versionURLTemplate = "https://github.com/bufbuild/buf/releases/tag/v{{.Full}}" + + return b.Language.Enabled() +} diff --git a/src/segments/buf_test.go b/src/segments/buf_test.go new file mode 100644 index 000000000000..e51094249b8a --- /dev/null +++ b/src/segments/buf_test.go @@ -0,0 +1,31 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuf(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + {Case: "Buf 1.12.0", ExpectedString: "1.12.0", Version: "1.12.0"}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "buf", + versionParam: "--version", + versionOutput: tc.Version, + extension: "buf.yaml", + } + env, props := getMockedLanguageEnv(params) + b := &Buf{} + b.Init(props, env) + assert.True(t, b.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, b.Template(), b), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/bun.go b/src/segments/bun.go new file mode 100644 index 000000000000..b3259d493ff3 --- /dev/null +++ b/src/segments/bun.go @@ -0,0 +1,24 @@ +package segments + +type Bun struct { + Language +} + +func (b *Bun) Template() string { + return languageTemplate +} + +func (b *Bun) Enabled() bool { + b.extensions = []string{"bun.lockb", "bun.lock"} + b.tooling = map[string]*cmd{ + bunToolName: { + executable: bunToolName, + args: []string{versionFlagArg}, + regex: versionRegexPrefixed, + }, + } + b.defaultTooling = []string{bunToolName} + b.versionURLTemplate = "https://github.com/oven-sh/bun/releases/tag/bun-v{{.Full}}" + + return b.Language.Enabled() +} diff --git a/src/segments/bun_test.go b/src/segments/bun_test.go new file mode 100644 index 000000000000..99b91d70a6cd --- /dev/null +++ b/src/segments/bun_test.go @@ -0,0 +1,49 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/stretchr/testify/assert" +) + +func TestBun(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + Extension string + }{ + {Case: "Bun 1.1.8 with bun.lockb", ExpectedString: "1.1.8", Version: "1.1.8", Extension: "bun.lockb"}, + {Case: "Bun 1.3.10 with bun.lock", ExpectedString: "1.3.10", Version: "1.3.10", Extension: "bun.lock"}, + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("HasCommand", "bun").Return(true) + env.On("RunCommand", "bun", []string{"--version"}).Return(tc.Version, nil) + env.On("HasFiles", "bun.lockb").Return(tc.Extension == "bun.lockb") + env.On("HasFiles", "bun.lock").Return(tc.Extension == "bun.lock") + env.On("Pwd").Return("/usr/home/project") + env.On("Home").Return("/usr/home") + env.On("Shell").Return("foo") + + if template.Cache == nil { + template.Cache = &cache.Template{} + } + template.Init(env, nil, nil) + + props := options.Map{ + options.FetchVersion: true, + } + + b := &Bun{} + b.Init(props, env) + + assert.True(t, b.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, b.Template(), b), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/carbon_intensity.go b/src/segments/carbon_intensity.go new file mode 100644 index 000000000000..8f0be603c37b --- /dev/null +++ b/src/segments/carbon_intensity.go @@ -0,0 +1,127 @@ +package segments + +import ( + "encoding/json" + "fmt" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type CarbonIntensity struct { + Base + + TrendIcon string + + CarbonIntensityData +} + +type CarbonIntensityResponse struct { + Data []CarbonIntensityPeriod `json:"data"` +} + +type CarbonIntensityPeriod struct { + Intensity *CarbonIntensityData `json:"intensity"` + From string `json:"from"` + To string `json:"to"` +} + +type CarbonIntensityData struct { + Index Index `json:"index"` + Forecast Number `json:"forecast"` + Actual Number `json:"actual"` +} + +type Number int + +func (n Number) String() string { + if n == 0 { + return "??" + } + + return fmt.Sprintf("%d", n) +} + +type Index string + +func (i Index) Icon() string { + switch i { + case "very low": + return "↓↓" + case "low": + return "↓" + case "moderate": + return "•" + case "high": + return "↑" + case "very high": + return "↑↑" + default: + return "" + } +} + +func (d *CarbonIntensity) Enabled() bool { + err := d.setStatus() + + if err != nil { + log.Error(err) + return false + } + + return true +} + +func (d *CarbonIntensity) Template() string { + return " CO₂ {{ .Index.Icon }}{{ .Actual.String }} {{ .TrendIcon }} {{ .Forecast.String }} " +} + +func (d *CarbonIntensity) getResult() (*CarbonIntensityResponse, error) { + response := new(CarbonIntensityResponse) + url := "https://api.carbonintensity.org.uk/intensity" + + httpTimeout := d.options.Int(options.HTTPTimeout, options.DefaultHTTPTimeout) + + body, err := d.env.HTTPRequest(url, nil, httpTimeout) + if err != nil { + return new(CarbonIntensityResponse), err + } + + err = json.Unmarshal(body, &response) + if err != nil { + return new(CarbonIntensityResponse), err + } + + return response, nil +} + +func (d *CarbonIntensity) setStatus() error { + response, err := d.getResult() + if err != nil { + return err + } + + if len(response.Data) == 0 { + d.Actual = 0 + d.Forecast = 0 + d.Index = "??" + d.TrendIcon = "→" + return nil + } + + d.CarbonIntensityData = *response.Data[0].Intensity + + if d.Forecast > d.Actual { + d.TrendIcon = "↗" + } + + if d.Forecast < d.Actual { + d.TrendIcon = "↘" + } + + if d.Forecast == d.Actual || d.Actual == 0 || d.Forecast == 0 { + d.TrendIcon = "→" + } + + return nil +} diff --git a/src/segments/carbon_intensity_test.go b/src/segments/carbon_intensity_test.go new file mode 100644 index 000000000000..305b0afeb979 --- /dev/null +++ b/src/segments/carbon_intensity_test.go @@ -0,0 +1,244 @@ +package segments + +import ( + "errors" + "fmt" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +const ( + CARBONINTENSITYURL = "https://api.carbonintensity.org.uk/intensity" +) + +func TestCarbonIntensitySegmentSingle(t *testing.T) { + cases := []struct { + Case string + Index string + ExpectedString string + Template string + Actual int + Forecast int + HasError bool + HasData bool + ExpectedEnabled bool + }{ + { + Case: "Very Low, Going Down", + HasError: false, + HasData: true, + Actual: 20, + Forecast: 10, + Index: "very low", + ExpectedString: "CO₂ ↓↓20 ↘ 10", + ExpectedEnabled: true, + }, + { + Case: "Very Low, Staying Same", + HasError: false, + HasData: true, + Actual: 20, + Forecast: 20, + Index: "very low", + ExpectedString: "CO₂ ↓↓20 → 20", + ExpectedEnabled: true, + }, + { + Case: "Very Low, Going Up", + HasError: false, + HasData: true, + Actual: 20, + Forecast: 30, + Index: "very low", + ExpectedString: "CO₂ ↓↓20 ↗ 30", + ExpectedEnabled: true, + }, + { + Case: "Low, Going Down", + HasError: false, + HasData: true, + Actual: 100, + Forecast: 50, + Index: "low", + ExpectedString: "CO₂ ↓100 ↘ 50", + ExpectedEnabled: true, + }, + { + Case: "Low, Staying Same", + HasError: false, + HasData: true, + Actual: 100, + Forecast: 100, + Index: "low", + ExpectedString: "CO₂ ↓100 → 100", + ExpectedEnabled: true, + }, + { + Case: "Low, Going Up", + HasError: false, + HasData: true, + Actual: 100, + Forecast: 150, + Index: "low", + ExpectedString: "CO₂ ↓100 ↗ 150", + ExpectedEnabled: true, + }, + { + Case: "Moderate, Going Down", + HasError: false, + HasData: true, + Actual: 150, + Forecast: 100, + Index: "moderate", + ExpectedString: "CO₂ •150 ↘ 100", + ExpectedEnabled: true, + }, + { + Case: "Moderate, Staying Same", + HasError: false, + HasData: true, + Actual: 150, + Forecast: 150, + Index: "moderate", + ExpectedString: "CO₂ •150 → 150", + ExpectedEnabled: true, + }, + { + Case: "Moderate, Going Up", + HasError: false, + HasData: true, + Actual: 150, + Forecast: 200, + Index: "moderate", + ExpectedString: "CO₂ •150 ↗ 200", + ExpectedEnabled: true, + }, + { + Case: "High, Going Down", + HasError: false, + HasData: true, + Actual: 200, + Forecast: 150, + Index: "high", + ExpectedString: "CO₂ ↑200 ↘ 150", + ExpectedEnabled: true, + }, + { + Case: "High, Staying Same", + HasError: false, + HasData: true, + Actual: 200, + Forecast: 200, + Index: "high", + ExpectedString: "CO₂ ↑200 → 200", + ExpectedEnabled: true, + }, + { + Case: "High, Going Up", + HasError: false, + HasData: true, + Actual: 200, + Forecast: 300, + Index: "high", + ExpectedString: "CO₂ ↑200 ↗ 300", + ExpectedEnabled: true, + }, + { + Case: "Missing Actual", + HasError: false, + HasData: true, + Actual: 0, // Missing data will be parsed to the default value of 0 + Forecast: 300, + Index: "high", + ExpectedString: "CO₂ ↑?? → 300", + ExpectedEnabled: true, + }, + { + Case: "Missing Forecast", + HasError: false, + HasData: true, + Actual: 200, + Forecast: 0, // Missing data will be parsed to the default value of 0 + Index: "high", + ExpectedString: "CO₂ ↑200 → ??", + ExpectedEnabled: true, + }, + { + Case: "Missing Index", + HasError: false, + HasData: true, + Actual: 200, + Forecast: 300, + Index: "", // Missing data will be parsed to the default value of "" + ExpectedString: "CO₂ 200 ↗ 300", + ExpectedEnabled: true, + }, + { + Case: "Missing Data", + HasError: false, + HasData: false, + Actual: 0, + Forecast: 0, + Index: "", + ExpectedString: "CO₂ ?? → ??", + ExpectedEnabled: true, + }, + { + Case: "Error", + HasError: true, + HasData: false, + Actual: 0, + Forecast: 0, + Index: "", + ExpectedString: "", + ExpectedEnabled: false, + }, + } + + for _, tc := range cases { + env := &mock.Environment{} + var props = options.Map{ + options.HTTPTimeout: 5000, + } + + jsonResponse := fmt.Sprintf( + `{ "data": [ { "from": "2023-10-27T12:30Z", "to": "2023-10-27T13:00Z", "intensity": { "forecast": %d, "actual": %d, "index": "%s" } } ] }`, + tc.Forecast, tc.Actual, tc.Index, + ) + + if !tc.HasData { + jsonResponse = `{ "data": [] }` + } + + if tc.HasError { + jsonResponse = `{ "error": "Something went wrong" }` + } + + responseError := errors.New("Something went wrong") + if !tc.HasError { + responseError = nil + } + + env.On("HTTPRequest", CARBONINTENSITYURL).Return([]byte(jsonResponse), responseError) + env.On("Flags").Return(&runtime.Flags{}) + + d := &CarbonIntensity{} + d.Init(props, env) + + enabled := d.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + if !enabled { + continue + } + + if tc.Template == "" { + tc.Template = d.Template() + } + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, d), tc.Case) + } +} diff --git a/src/segments/cds.go b/src/segments/cds.go new file mode 100644 index 000000000000..2443c7a993be --- /dev/null +++ b/src/segments/cds.go @@ -0,0 +1,41 @@ +package segments + +type Cds struct { + Language + HasDependency bool +} + +func (c *Cds) Template() string { + return languageTemplate +} + +const cdsToolName = "cds" + +func (c *Cds) Enabled() bool { + c.extensions = []string{".cdsrc.json", ".cdsrc-private.json", "*.cds"} + c.tooling = map[string]*cmd{ + cdsToolName: { + executable: cdsToolName, + args: []string{versionFlagArg}, + regex: `@sap/cds: ` + versionRegexPrefixed, + }, + } + c.defaultTooling = []string{cdsToolName} + c.Language.loadContext = c.loadContext + c.Language.inContext = c.inContext + c.displayMode = c.options.String(DisplayMode, DisplayModeContext) + + return c.Language.Enabled() +} + +func (c *Cds) loadContext() { + if !c.hasNodePackage("@sap/cds") { + return + } + + c.HasDependency = true +} + +func (c *Cds) inContext() bool { + return c.HasDependency +} diff --git a/src/segments/cds_test.go b/src/segments/cds_test.go new file mode 100644 index 000000000000..5cd5c47ef4b7 --- /dev/null +++ b/src/segments/cds_test.go @@ -0,0 +1,89 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCdsSegment(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Template string + Version string + PackageJSON string + DisplayMode string + }{ + { + Case: "1) cds 5.5.0 - file .cdsrc.json present", + ExpectedString: "5.5.0", + Version: "@sap/cds: 5.5.0\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3", + DisplayMode: DisplayModeFiles, + }, + { + Case: "2) cds 5.5.1 - file some.cds", + ExpectedString: "5.5.1", + Version: "@sap/cds: 5.5.1\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3", + DisplayMode: DisplayModeFiles, + }, + { + Case: "4) cds 5.5.3 - package.json dependency", + ExpectedString: "5.5.3", + Version: "@sap/cds: 5.5.3\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3", + PackageJSON: "{ \"name\": \"my-app\",\"dependencies\": { \"@sap/cds\": \"^5\" } }", + DisplayMode: DisplayModeContext, + }, + { + Case: "4) cds 5.5.4 - package.json dependency, major + minor", + ExpectedString: "5.5", + Template: "{{ .Major }}.{{ .Minor }}", + Version: "@sap/cds: 5.5.4\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3", + PackageJSON: "{ \"name\": \"my-app\",\"dependencies\": { \"@sap/cds\": \"^5\" } }", + DisplayMode: DisplayModeContext, + }, + { + Case: "6) cds 5.5.9 - display always", + ExpectedString: "5.5.9", + Version: "@sap/cds: 5.5.9\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3", + PackageJSON: "{ \"name\": \"my-app\",\"dependencies\": { \"@sap/cds\": \"^5\" } }", + DisplayMode: DisplayModeAlways, + }, + { + Case: "8) cds 5.5.0 - file .cdsrc-private.json present", + ExpectedString: "5.5.0", + Version: "@sap/cds: 5.5.0\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3", + DisplayMode: DisplayModeFiles, + }, + } + + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "cds", + versionParam: "--version", + versionOutput: tc.Version, + extension: ".cdsrc.json", + } + env, props := getMockedLanguageEnv(params) + + if tc.DisplayMode == "" { + tc.DisplayMode = DisplayModeContext + } + props[DisplayMode] = tc.DisplayMode + + env.On("HasFiles", "package.json").Return(len(tc.PackageJSON) != 0) + env.On("FileContent", "package.json").Return(tc.PackageJSON) + + cds := &Cds{} + cds.Init(props, env) + + if tc.Template == "" { + tc.Template = cds.Template() + } + + failMsg := fmt.Sprintf("Failed in case: %s", tc.Case) + assert.True(t, cds.Enabled(), failMsg) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, cds), failMsg) + } +} diff --git a/src/segments/cf.go b/src/segments/cf.go new file mode 100644 index 000000000000..04cba2773d1b --- /dev/null +++ b/src/segments/cf.go @@ -0,0 +1,25 @@ +package segments + +type Cf struct { + Language +} + +func (c *Cf) Template() string { + return languageTemplate +} + +func (c *Cf) Enabled() bool { + c.extensions = []string{"manifest.yml", "mta.yaml"} + c.tooling = map[string]*cmd{ + "cf": { + executable: "cf", + args: []string{versionArg}, + regex: versionRegexPrefixed, + }, + } + c.defaultTooling = []string{"cf"} + c.displayMode = c.options.String(DisplayMode, DisplayModeFiles) + c.versionURLTemplate = "https://github.com/cloudfoundry/cli/releases/tag/v{{ .Full }}" + + return c.Language.Enabled() +} diff --git a/src/segments/cf_target.go b/src/segments/cf_target.go new file mode 100644 index 000000000000..674bb461f531 --- /dev/null +++ b/src/segments/cf_target.go @@ -0,0 +1,91 @@ +package segments + +import ( + "errors" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type CfTarget struct { + Base + + CfTargetDetails +} + +type CfTargetDetails struct { + URL string + User string + Org string + Space string +} + +func (c *CfTarget) Template() string { + return "{{if .Org }}{{ .Org }}{{ end }}{{if .Space }}/{{ .Space }}{{ end }}" +} + +func (c *CfTarget) Enabled() bool { + if !c.env.HasCommand("cf") { + return false + } + + displayMode := c.options.String(DisplayMode, DisplayModeAlways) + if displayMode != DisplayModeFiles { + return c.setCFTargetStatus() + } + + files := c.options.StringArray(options.Files, []string{"manifest.yml"}) + for _, file := range files { + manifest, err := c.env.HasParentFilePath(file, false) + if err != nil || manifest.IsDir { + continue + } + + return c.setCFTargetStatus() + } + + return false +} + +func (c *CfTarget) setCFTargetStatus() bool { + output, err := c.getCFTargetCommandOutput() + + if err != nil { + return false + } + + lines := strings.SplitSeq(output, "\n") + for line := range lines { + key, value, found := strings.Cut(line, ":") + if !found { + continue + } + value = strings.TrimSpace(value) + switch key { + case "API endpoint": + c.URL = value + case "user": + c.User = value + case "org": + c.Org = value + case "space": + c.Space = value + } + } + + return true +} + +func (c *CfTarget) getCFTargetCommandOutput() (string, error) { + output, err := c.env.RunCommand("cf", "target") + + if err != nil { + return "", err + } + + if output == "" { + return "", errors.New("cf command output is empty") + } + + return output, nil +} diff --git a/src/segments/cf_target_test.go b/src/segments/cf_target_test.go new file mode 100644 index 000000000000..904c7ea0b5a3 --- /dev/null +++ b/src/segments/cf_target_test.go @@ -0,0 +1,93 @@ +package segments + +import ( + "errors" + "fmt" + "os/exec" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +func TestCFTargetSegment(t *testing.T) { + cases := []struct { + CommandError error + FileInfo *runtime.FileInfo + Case string + Template string + ExpectedString string + DisplayMode string + TargetOutput string + }{ + { + Case: "not logged in to CF account", + TargetOutput: `Not logged in`, + CommandError: &exec.ExitError{}, + }, + { + Case: "logged in, default template", + ExpectedString: "12345678trial/dev", + TargetOutput: "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev", + }, + { + Case: "no output from command", + }, + { + Case: "logged in, full template", + Template: "{{.URL}} {{.User}} {{.Org}} {{.Space}}", + ExpectedString: "https://api.cf.eu10.hana.ondemand.com user@some.com 12345678trial dev", + TargetOutput: "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev", + }, + { + Case: "files and no manifest file", + DisplayMode: DisplayModeFiles, + TargetOutput: "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev", + }, + { + Case: "files and a manifest file", + ExpectedString: "12345678trial/dev", + DisplayMode: DisplayModeFiles, + FileInfo: &runtime.FileInfo{}, + TargetOutput: "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev", + }, + { + Case: "files and a manifest directory", + DisplayMode: DisplayModeFiles, + FileInfo: &runtime.FileInfo{ + IsDir: true, + }, + }, + } + + for _, tc := range cases { + var env = new(mock.Environment) + env.On("HasCommand", "cf").Return(true) + env.On("RunCommand", "cf", []string{"target"}).Return(tc.TargetOutput, tc.CommandError) + env.On("Pwd", nil).Return("/usr/home/dev/my-app") + env.On("Home", nil).Return("/usr/home") + var err error + if tc.FileInfo == nil { + err = errors.New("no such file or directory") + } + env.On("HasParentFilePath", "manifest.yml", false).Return(tc.FileInfo, err) + + cfTarget := &CfTarget{} + props := options.Map{ + DisplayMode: tc.DisplayMode, + } + + if tc.Template == "" { + tc.Template = cfTarget.Template() + } + + cfTarget.Init(props, env) + + failMsg := fmt.Sprintf("Failed in case: %s", tc.Case) + assert.Equal(t, len(tc.ExpectedString) > 0, cfTarget.Enabled(), failMsg) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, cfTarget), failMsg) + } +} diff --git a/src/segments/cf_test.go b/src/segments/cf_test.go new file mode 100644 index 000000000000..4e89648ca96b --- /dev/null +++ b/src/segments/cf_test.go @@ -0,0 +1,65 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCFSegment(t *testing.T) { + cases := []struct { + Case string + Template string + ExpectedString string + CfYamlFile string + Version string + DisplayMode string + }{ + { + Case: "1) cf 2.12.1 - file manifest.yml", + ExpectedString: "2.12.1", + CfYamlFile: "manifest.yml", + Version: `cf.exe version 2.12.1+645c3ce6a.2021-08-16`, + DisplayMode: DisplayModeFiles, + }, + { + Case: "2) cf 11.0.0-rc1 - file mta.yaml", + Template: "{{ .Major }}", + ExpectedString: "11", + CfYamlFile: "mta.yaml", + Version: `cf version 11.0.0-rc1`, + DisplayMode: DisplayModeFiles, + }, + { + Case: "4) cf 11.1.0-rc1 - mode always", + Template: "{{ .Major }}.{{ .Minor }}", + ExpectedString: "11.1", + Version: `cf.exe version 11.1.0-rc1`, + DisplayMode: DisplayModeAlways, + }, + } + + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "cf", + versionParam: "version", + versionOutput: tc.Version, + extension: "manifest.yml", + } + env, props := getMockedLanguageEnv(params) + + props[DisplayMode] = tc.DisplayMode + + cf := &Cf{} + cf.Init(props, env) + + if tc.Template == "" { + tc.Template = cf.Template() + } + + failMsg := fmt.Sprintf("Failed in case: %s", tc.Case) + assert.True(t, cf.Enabled(), failMsg) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, cf), failMsg) + } +} diff --git a/src/segments/claude.go b/src/segments/claude.go new file mode 100644 index 000000000000..964f024c1e84 --- /dev/null +++ b/src/segments/claude.go @@ -0,0 +1,378 @@ +package segments + +import ( + "fmt" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/text" +) + +// Claude segment displays Claude Code session information +type Claude struct { + Base + markedChar string + unmarkedChar string + ClaudeData +} + +// ClaudeData represents the parsed Claude JSON data +type ClaudeData struct { + RateLimits *ClaudeRateLimits `json:"rate_limits"` + Worktree ClaudeWorktree `json:"worktree"` + Model ClaudeModel `json:"model"` + OutputStyle ClaudeOutputStyle `json:"output_style"` + Vim ClaudeVim `json:"vim"` + TranscriptPath string `json:"transcript_path"` + CWD string `json:"cwd"` + Version string `json:"version"` + SessionID string `json:"session_id"` + Effort ClaudeEffort `json:"effort"` + SessionName string `json:"session_name"` + Agent ClaudeAgent `json:"agent"` + Workspace ClaudeWorkspace `json:"workspace"` + ContextWindow ClaudeContextWindow `json:"context_window"` + Cost ClaudeCost `json:"cost"` + Exceeds200KTokens bool `json:"exceeds_200k_tokens"` + Thinking ClaudeThinking `json:"thinking"` + FastMode bool `json:"fast_mode"` +} + +// ClaudeModel represents the AI model information +type ClaudeModel struct { + ID string `json:"id"` + DisplayName string `json:"display_name"` +} + +// ClaudeWorkspace represents workspace directory information +type ClaudeWorkspace struct { + CurrentDir string `json:"current_dir"` + ProjectDir string `json:"project_dir"` + GitWorktree string `json:"git_worktree"` + AddedDirs []string `json:"added_dirs"` +} + +// ClaudeOutputStyle represents the current output style. +type ClaudeOutputStyle struct { + Name string `json:"name"` +} + +// ClaudeEffort represents reasoning effort information for the current session. +// Level is empty when the active model does not support reasoning effort. +type ClaudeEffort struct { + Level string `json:"level"` +} + +// ClaudeThinking represents extended thinking state for the current session. +type ClaudeThinking struct { + Enabled bool `json:"enabled"` +} + +// ClaudeVim represents vim mode state. +type ClaudeVim struct { + Mode string `json:"mode"` +} + +// ClaudeAgent represents the active agent. +type ClaudeAgent struct { + Name string `json:"name"` +} + +// ClaudeWorktree represents Claude Code --worktree session information. +type ClaudeWorktree struct { + Name string `json:"name"` + Path string `json:"path"` + Branch string `json:"branch"` + OriginalCWD string `json:"original_cwd"` + OriginalBranch string `json:"original_branch"` +} + +// DurationMS is a duration in milliseconds that formats as "Xm Ys". +type DurationMS int64 + +func (d DurationMS) String() string { + totalSeconds := int64(d) / 1000 + minutes := totalSeconds / 60 + seconds := totalSeconds % 60 + return fmt.Sprintf("%dm %ds", minutes, seconds) +} + +// ClaudeCost represents cost and duration information +type ClaudeCost struct { + TotalCostUSD float64 `json:"total_cost_usd"` + TotalDurationMS DurationMS `json:"total_duration_ms"` + TotalAPIDurationMS DurationMS `json:"total_api_duration_ms"` + TotalLinesAdded int `json:"total_lines_added"` + TotalLinesRemoved int `json:"total_lines_removed"` +} + +// ClaudeRateLimitWindow represents a single rate limit time window. +type ClaudeRateLimitWindow struct { + UsedPercentage *float64 `json:"used_percentage"` + ResetsAt *int64 `json:"resets_at"` +} + +// ClaudeRateLimits represents rate limit information across time windows. +type ClaudeRateLimits struct { + FiveHour *ClaudeRateLimitWindow `json:"five_hour"` + SevenDay *ClaudeRateLimitWindow `json:"seven_day"` +} + +// ClaudeContextWindow represents token usage information +type ClaudeContextWindow struct { + UsedPercentage *int `json:"used_percentage"` + RemainingPercentage *int `json:"remaining_percentage"` + CurrentUsage *ClaudeCurrentUsage `json:"current_usage"` + TotalInputTokens int `json:"total_input_tokens"` + TotalOutputTokens int `json:"total_output_tokens"` + ContextWindowSize int `json:"context_window_size"` +} + +// ClaudeCurrentUsage represents current context window usage from the last API call +type ClaudeCurrentUsage struct { + InputTokens int `json:"input_tokens"` + OutputTokens int `json:"output_tokens"` + CacheCreationInputTokens int `json:"cache_creation_input_tokens"` + CacheReadInputTokens int `json:"cache_read_input_tokens"` +} + +const ( + thousand = 1000.0 + million = 1000000.0 + + gaugeMarkedChar options.Option = "gauge_marked_char" + gaugeUnmarkedChar options.Option = "gauge_unmarked_char" +) + +func (c *Claude) Template() string { + return " \U000f0bc9 {{ .Model.DisplayName }} \uf2d0 {{ .TokenGauge }} " +} + +func (c *Claude) Enabled() bool { + log.Debug("claude segment: checking if enabled") + + // Try to get Claude data from session cache + claudeData, found := cache.Get[ClaudeData](cache.Session, cache.CLAUDECACHE) + if !found { + log.Debug("claude segment: no Claude data found in session cache") + return false + } + + log.Debug("claude segment: found Claude data in session cache") + log.Debugf("claude segment: model=%s, session=%s", claudeData.Model.DisplayName, claudeData.SessionID) + + // Copy the data to our embedded struct + c.ClaudeData = claudeData + + c.markedChar = c.options.String(gaugeMarkedChar, "▰") + c.unmarkedChar = c.options.String(gaugeUnmarkedChar, "▱") + + return true +} + +// TokenUsagePercent returns the percentage of context window used. +// Uses pre-calculated UsedPercentage when available (resets on compact/clear), +// falls back to calculating from CurrentUsage, then to total tokens for backwards compatibility. +func (c *Claude) TokenUsagePercent() text.Percentage { + // Prefer pre-calculated UsedPercentage - most accurate and resets on compact/clear + // When UsedPercentage is nil (null in JSON), context was reset - return 0 + if c.ContextWindow.UsedPercentage != nil { + if *c.ContextWindow.UsedPercentage > 100 { + return 100 + } + return text.Percentage(*c.ContextWindow.UsedPercentage) + } + + // UsedPercentage is nil - check if CurrentUsage is also nil (indicates reset/clear) + if c.ContextWindow.CurrentUsage == nil { + return 0 + } + + if c.ContextWindow.ContextWindowSize <= 0 { + return 0 + } + + // Calculate from CurrentUsage (includes cache tokens for accurate context measurement) + currentTokens := c.ContextWindow.CurrentUsage.InputTokens + + c.ContextWindow.CurrentUsage.CacheCreationInputTokens + + c.ContextWindow.CurrentUsage.CacheReadInputTokens + + // Fallback to total tokens if CurrentUsage is not provided (backwards compatibility) + if currentTokens <= 0 { + currentTokens = c.ContextWindow.TotalInputTokens + c.ContextWindow.TotalOutputTokens + } + + if currentTokens <= 0 { + return 0 + } + + // Use floating-point arithmetic for accurate percentage calculation + percent := (float64(currentTokens) * 100.0) / float64(c.ContextWindow.ContextWindowSize) + + // Round to nearest integer and cap at 100 + roundedPercent := int(percent + 0.5) + if roundedPercent > 100 { + return 100 + } + + return text.Percentage(roundedPercent) +} + +// TokenGauge returns a 5-block gauge showing remaining context window capacity using the configured characters. +func (c *Claude) TokenGauge() string { + return c.TokenUsagePercent().GaugeWith(c.markedChar, c.unmarkedChar) +} + +// TokenGaugeUsed returns a 5-block gauge showing used context window capacity using the configured characters. +func (c *Claude) TokenGaugeUsed() string { + return c.TokenUsagePercent().GaugeUsedWith(c.markedChar, c.unmarkedChar) +} + +// FiveHourGauge returns a 5-block gauge showing 5-hour rate limit usage using the configured characters. +func (c *Claude) FiveHourGauge() string { + return c.FiveHourUsage().GaugeUsedWith(c.markedChar, c.unmarkedChar) +} + +// SevenDayGauge returns a 5-block gauge showing 7-day rate limit usage using the configured characters. +func (c *Claude) SevenDayGauge() string { + return c.SevenDayUsage().GaugeUsedWith(c.markedChar, c.unmarkedChar) +} + +// FormattedCost returns the cost formatted as a currency string +func (c *Claude) FormattedCost() string { + if c.Cost.TotalCostUSD < 0.01 { + return fmt.Sprintf("$%.4f", c.Cost.TotalCostUSD) + } + + return fmt.Sprintf("$%.2f", c.Cost.TotalCostUSD) +} + +// FormattedDuration returns total session duration as "Xm Ys". +func (c *Claude) FormattedDuration() string { + return c.Cost.TotalDurationMS.String() +} + +// FormattedAPIDuration returns API wait time as "Xm Ys". +func (c *Claude) FormattedAPIDuration() string { + return c.Cost.TotalAPIDurationMS.String() +} + +// rateLimitPercentage extracts a percentage from a rate limit window with nil-safety. +func rateLimitPercentage(limits *ClaudeRateLimits, window func(*ClaudeRateLimits) *ClaudeRateLimitWindow) text.Percentage { + if limits == nil { + return 0 + } + + w := window(limits) + if w == nil || w.UsedPercentage == nil { + return 0 + } + + percent := int(*w.UsedPercentage + 0.5) + if percent > 100 { + return 100 + } + + return text.Percentage(percent) +} + +// FiveHourUsage returns the 5-hour rolling window rate limit usage as a Percentage. +func (c *Claude) FiveHourUsage() text.Percentage { + return rateLimitPercentage(c.RateLimits, func(r *ClaudeRateLimits) *ClaudeRateLimitWindow { + return r.FiveHour + }) +} + +// SevenDayUsage returns the 7-day window rate limit usage as a Percentage. +func (c *Claude) SevenDayUsage() text.Percentage { + return rateLimitPercentage(c.RateLimits, func(r *ClaudeRateLimits) *ClaudeRateLimitWindow { + return r.SevenDay + }) +} + +// rateLimitResetsAt extracts the reset time from a rate limit window with nil-safety. +func rateLimitResetsAt(limits *ClaudeRateLimits, window func(*ClaudeRateLimits) *ClaudeRateLimitWindow) time.Time { + if limits == nil || window == nil { + return time.Time{} + } + + w := window(limits) + if w == nil || w.ResetsAt == nil { + return time.Time{} + } + + return time.Unix(*w.ResetsAt, 0) +} + +// FiveHourResetsAt returns the reset time for the 5-hour rolling window, or zero if unavailable. +func (c *Claude) FiveHourResetsAt() time.Time { + return rateLimitResetsAt(c.RateLimits, func(r *ClaudeRateLimits) *ClaudeRateLimitWindow { + return r.FiveHour + }) +} + +// SevenDayResetsAt returns the reset time for the 7-day rolling window, or zero if unavailable. +func (c *Claude) SevenDayResetsAt() time.Time { + return rateLimitResetsAt(c.RateLimits, func(r *ClaudeRateLimits) *ClaudeRateLimitWindow { + return r.SevenDay + }) +} + +// rateLimitResetsIn returns the signed duration until a rate limit window resets. +// Returns 0 when data is unavailable, a negative value when the window already reset, and a positive value otherwise. +func rateLimitResetsIn(limits *ClaudeRateLimits, window func(*ClaudeRateLimits) *ClaudeRateLimitWindow) time.Duration { + t := rateLimitResetsAt(limits, window) + if t.IsZero() { + return 0 + } + + return time.Until(t) +} + +// FiveHourResetsIn returns the signed duration until the 5-hour rolling window resets. +// Returns 0 when unavailable, negative when the window already reset. +func (c *Claude) FiveHourResetsIn() time.Duration { + return rateLimitResetsIn(c.RateLimits, func(r *ClaudeRateLimits) *ClaudeRateLimitWindow { + return r.FiveHour + }) +} + +// SevenDayResetsIn returns the signed duration until the 7-day rolling window resets. +// Returns 0 when unavailable, negative when the window already reset. +func (c *Claude) SevenDayResetsIn() time.Duration { + return rateLimitResetsIn(c.RateLimits, func(r *ClaudeRateLimits) *ClaudeRateLimitWindow { + return r.SevenDay + }) +} + +// FormattedTokens returns a human-readable string of current context tokens. +// Uses CurrentUsage (which represents actual context and resets on compact/clear) +// with fallback to total tokens for backwards compatibility. +func (c *Claude) FormattedTokens() string { + var currentTokens int + + // Use CurrentUsage for display - includes cache tokens for accurate context measurement + // When CurrentUsage is nil (context reset), fall back to total tokens + if c.ContextWindow.CurrentUsage != nil { + currentTokens = c.ContextWindow.CurrentUsage.InputTokens + + c.ContextWindow.CurrentUsage.CacheCreationInputTokens + + c.ContextWindow.CurrentUsage.CacheReadInputTokens + } + + // Fallback to total tokens if CurrentUsage is not provided (backwards compatibility) + if currentTokens <= 0 { + currentTokens = c.ContextWindow.TotalInputTokens + c.ContextWindow.TotalOutputTokens + } + + if currentTokens < int(thousand) { + return fmt.Sprintf("%d", currentTokens) + } + + if currentTokens < int(million) { + return fmt.Sprintf("%.1fK", float64(currentTokens)/thousand) + } + + return fmt.Sprintf("%.1fM", float64(currentTokens)/million) +} diff --git a/src/segments/claude_test.go b/src/segments/claude_test.go new file mode 100644 index 000000000000..f01a2c7e2c32 --- /dev/null +++ b/src/segments/claude_test.go @@ -0,0 +1,1007 @@ +package segments + +import ( + "encoding/json" + "testing" + libtime "time" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/text" + + "github.com/stretchr/testify/assert" +) + +func TestClaudeSegment(t *testing.T) { + cases := []struct { + Case string + ClaudeData *ClaudeData + ExpectedModel string + ExpectedSession string + ExpectedGitWorktree string + ExpectedEnabled bool + }{ + { + Case: "No cache data", + ClaudeData: nil, + ExpectedEnabled: false, + }, + { + Case: "Valid cache data with all fields", + ClaudeData: &ClaudeData{ + SessionID: "abc123", + Model: ClaudeModel{ + ID: "claude-opus-4-1", + DisplayName: "Opus", + }, + Workspace: ClaudeWorkspace{ + CurrentDir: "/repo/project/.worktrees/feature", + ProjectDir: "/repo/project", + GitWorktree: "/repo/project/.worktrees/feature", + }, + Cost: ClaudeCost{ + TotalCostUSD: 0.01, + TotalDurationMS: 45000, + TotalAPIDurationMS: 30000, + TotalLinesAdded: 156, + TotalLinesRemoved: 23, + }, + ContextWindow: ClaudeContextWindow{ + TotalInputTokens: 15234, + TotalOutputTokens: 4521, + ContextWindowSize: 200000, + CurrentUsage: &ClaudeCurrentUsage{ + InputTokens: 8500, + OutputTokens: 1200, + }, + }, + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{ + UsedPercentage: new(24.5), + ResetsAt: new(int64(1711180800)), + }, + SevenDay: &ClaudeRateLimitWindow{ + UsedPercentage: new(45.0), + ResetsAt: new(int64(1711612800)), + }, + }, + }, + ExpectedEnabled: true, + ExpectedModel: "Opus", + ExpectedSession: "abc123", + ExpectedGitWorktree: "/repo/project/.worktrees/feature", + }, + { + Case: "Valid cache data with partial fields", + ClaudeData: &ClaudeData{ + SessionID: "xyz789", + Model: ClaudeModel{ + ID: "claude-sonnet-3-5", + DisplayName: "Sonnet 3.5", + }, + ContextWindow: ClaudeContextWindow{ + TotalInputTokens: 1000, + TotalOutputTokens: 500, + ContextWindowSize: 100000, + }, + }, + ExpectedEnabled: true, + ExpectedModel: "Sonnet 3.5", + ExpectedSession: "xyz789", + }, + } + + for _, tc := range cases { + // Setup cache for test + if tc.ClaudeData != nil { + cache.Set(cache.Session, cache.CLAUDECACHE, *tc.ClaudeData, cache.INFINITE) + } else { + cache.Delete(cache.Session, cache.CLAUDECACHE) + } + + env := new(mock.Environment) + claude := &Claude{ + Base: Base{ + env: env, + options: options.Map{}, + }, + } + + enabled := claude.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedModel, claude.Model.DisplayName, tc.Case) + assert.Equal(t, tc.ExpectedSession, claude.SessionID, tc.Case) + assert.Equal(t, tc.ExpectedGitWorktree, claude.Workspace.GitWorktree, tc.Case) + } + } +} + +func TestClaudeWorkspaceGitWorktree(t *testing.T) { + t.Cleanup(func() { + cache.Delete(cache.Session, cache.CLAUDECACHE) + }) + + cases := []struct { + Case string + ExpectedGitWorktree string + Workspace ClaudeWorkspace + }{ + { + Case: "Inside a linked worktree", + Workspace: ClaudeWorkspace{ + CurrentDir: "/repo/project/.worktrees/feature", + ProjectDir: "/repo/project", + GitWorktree: "/repo/project/.worktrees/feature", + }, + ExpectedGitWorktree: "/repo/project/.worktrees/feature", + }, + { + Case: "Outside a linked worktree", + Workspace: ClaudeWorkspace{ + CurrentDir: "/repo/project", + ProjectDir: "/repo/project", + }, + ExpectedGitWorktree: "", + }, + } + + for _, tc := range cases { + cache.Set(cache.Session, cache.CLAUDECACHE, ClaudeData{Workspace: tc.Workspace}, cache.INFINITE) + + env := new(mock.Environment) + claude := &Claude{ + Base: Base{ + env: env, + options: options.Map{}, + }, + } + + assert.True(t, claude.Enabled(), tc.Case) + assert.Equal(t, tc.ExpectedGitWorktree, claude.Workspace.GitWorktree, tc.Case) + } +} + +func TestClaudeEffortAndThinking(t *testing.T) { + t.Cleanup(func() { + cache.Delete(cache.Session, cache.CLAUDECACHE) + }) + + cases := []struct { + Case string + Effort ClaudeEffort + ExpectedLevel string + Thinking ClaudeThinking + ExpectedThinking bool + }{ + { + Case: "Reasoning effort active, thinking enabled", + Effort: ClaudeEffort{Level: "xhigh"}, + ExpectedLevel: "xhigh", + Thinking: ClaudeThinking{Enabled: true}, + ExpectedThinking: true, + }, + { + Case: "Reasoning effort active, thinking disabled", + Effort: ClaudeEffort{Level: "high"}, + ExpectedLevel: "high", + ExpectedThinking: false, + }, + { + Case: "Reasoning effort absent, thinking enabled", + ExpectedLevel: "", + Thinking: ClaudeThinking{Enabled: true}, + ExpectedThinking: true, + }, + { + Case: "Reasoning effort absent, thinking disabled", + ExpectedLevel: "", + ExpectedThinking: false, + }, + } + + for _, tc := range cases { + cache.Set(cache.Session, cache.CLAUDECACHE, ClaudeData{ + Effort: tc.Effort, + Thinking: tc.Thinking, + }, cache.INFINITE) + + env := new(mock.Environment) + claude := &Claude{ + Base: Base{ + env: env, + options: options.Map{}, + }, + } + + assert.True(t, claude.Enabled(), tc.Case) + assert.Equal(t, tc.ExpectedLevel, claude.Effort.Level, tc.Case) + assert.Equal(t, tc.ExpectedThinking, claude.Thinking.Enabled, tc.Case) + } +} + +func TestClaudeEffortAndThinkingJSONShape(t *testing.T) { + cases := []struct { + Case string + JSON string + ExpectedLevel string + ExpectedThinking bool + }{ + { + Case: "Both fields present", + JSON: `{"effort":{"level":"xhigh"},"thinking":{"enabled":true}}`, + ExpectedLevel: "xhigh", + ExpectedThinking: true, + }, + { + Case: "Both fields absent", + JSON: `{}`, + ExpectedLevel: "", + ExpectedThinking: false, + }, + { + Case: "Effort object empty, thinking disabled", + JSON: `{"effort":{},"thinking":{"enabled":false}}`, + ExpectedLevel: "", + ExpectedThinking: false, + }, + } + + for _, tc := range cases { + var data ClaudeData + err := json.Unmarshal([]byte(tc.JSON), &data) + assert.NoError(t, err, tc.Case) + assert.Equal(t, tc.ExpectedLevel, data.Effort.Level, tc.Case) + assert.Equal(t, tc.ExpectedThinking, data.Thinking.Enabled, tc.Case) + } +} + +func TestClaudeAdditionalStatusLineFieldsJSONShape(t *testing.T) { + const ( + defaultOutputStyleName = "default" + originalBranchName = "main" + ) + + cases := []struct { + Case string + JSON string + Expected ClaudeData + ExpectedAddedDirsNil bool + }{ + { + Case: "All fields present", + JSON: `{ + "cwd": "/repo/project", + "session_name": "release-check", + "transcript_path": "/repo/project/.claude/transcript.jsonl", + "version": "2.1.123", + "output_style": { + "name": "default" + }, + "workspace": { + "added_dirs": ["/repo/shared", "/repo/docs"] + }, + "exceeds_200k_tokens": true, + "vim": { + "mode": "NORMAL" + }, + "agent": { + "name": "security-reviewer" + }, + "worktree": { + "name": "my-feature", + "path": "/repo/project/.claude/worktrees/my-feature", + "branch": "worktree-my-feature", + "original_cwd": "/repo/project", + "original_branch": "main" + }, + "fast_mode": true + }`, + Expected: ClaudeData{ + CWD: "/repo/project", + SessionName: "release-check", + TranscriptPath: "/repo/project/.claude/transcript.jsonl", + Version: "2.1.123", + OutputStyle: ClaudeOutputStyle{Name: defaultOutputStyleName}, + Workspace: ClaudeWorkspace{AddedDirs: []string{"/repo/shared", "/repo/docs"}}, + Exceeds200KTokens: true, + Vim: ClaudeVim{Mode: "NORMAL"}, + Agent: ClaudeAgent{Name: "security-reviewer"}, + Worktree: ClaudeWorktree{ + Name: "my-feature", + Path: "/repo/project/.claude/worktrees/my-feature", + Branch: "worktree-my-feature", + OriginalCWD: "/repo/project", + OriginalBranch: originalBranchName, + }, + FastMode: true, + }, + }, + { + Case: "All fields absent", + JSON: `{}`, + Expected: ClaudeData{}, + ExpectedAddedDirsNil: true, + }, + { + Case: "Nested objects empty", + JSON: `{ + "output_style": {}, + "workspace": {}, + "vim": {}, + "agent": {}, + "worktree": {} + }`, + Expected: ClaudeData{}, + ExpectedAddedDirsNil: true, + }, + { + Case: "Partial nested fields", + JSON: `{ + "workspace": { + "added_dirs": ["/repo/shared"] + }, + "worktree": { + "name": "review", + "path": "/repo/project/.claude/worktrees/review" + } + }`, + Expected: ClaudeData{ + Workspace: ClaudeWorkspace{AddedDirs: []string{"/repo/shared"}}, + Worktree: ClaudeWorktree{ + Name: "review", + Path: "/repo/project/.claude/worktrees/review", + }, + }, + }, + { + Case: "Added dirs explicitly empty", + JSON: `{ + "workspace": { + "added_dirs": [] + } + }`, + Expected: ClaudeData{ + Workspace: ClaudeWorkspace{AddedDirs: []string{}}, + }, + }, + } + + for _, tc := range cases { + // Act + var data ClaudeData + err := json.Unmarshal([]byte(tc.JSON), &data) + + // Assert + assert.NoError(t, err, tc.Case) + assert.Equal(t, tc.Expected.CWD, data.CWD, tc.Case) + assert.Equal(t, tc.Expected.SessionName, data.SessionName, tc.Case) + assert.Equal(t, tc.Expected.TranscriptPath, data.TranscriptPath, tc.Case) + assert.Equal(t, tc.Expected.Version, data.Version, tc.Case) + assert.Equal(t, tc.Expected.OutputStyle.Name, data.OutputStyle.Name, tc.Case) + if tc.ExpectedAddedDirsNil { + assert.Nil(t, data.Workspace.AddedDirs, tc.Case) + } else { + assert.Equal(t, tc.Expected.Workspace.AddedDirs, data.Workspace.AddedDirs, tc.Case) + } + assert.Equal(t, tc.Expected.Exceeds200KTokens, data.Exceeds200KTokens, tc.Case) + assert.Equal(t, tc.Expected.Vim.Mode, data.Vim.Mode, tc.Case) + assert.Equal(t, tc.Expected.Agent.Name, data.Agent.Name, tc.Case) + assert.Equal(t, tc.Expected.Worktree.Name, data.Worktree.Name, tc.Case) + assert.Equal(t, tc.Expected.Worktree.Path, data.Worktree.Path, tc.Case) + assert.Equal(t, tc.Expected.Worktree.Branch, data.Worktree.Branch, tc.Case) + assert.Equal(t, tc.Expected.Worktree.OriginalCWD, data.Worktree.OriginalCWD, tc.Case) + assert.Equal(t, tc.Expected.Worktree.OriginalBranch, data.Worktree.OriginalBranch, tc.Case) + assert.Equal(t, tc.Expected.FastMode, data.FastMode, tc.Case) + } +} + +func TestClaudeTokenUsagePercent(t *testing.T) { + cases := []struct { + UsedPercentage *int + Case string + InputTokens int + OutputTokens int + CurrentInput int + CacheCreationInputTokens int + CacheReadInputTokens int + ContextWindow int + ExpectedPercent text.Percentage + HasCurrentUsage bool + }{ + { + Case: "Uses UsedPercentage when available", + UsedPercentage: new(42), + ContextWindow: 200000, + ExpectedPercent: 42, + }, + { + Case: "UsedPercentage capped at 100", + UsedPercentage: new(150), + ContextWindow: 200000, + ExpectedPercent: 100, + }, + { + Case: "UsedPercentage zero is valid", + UsedPercentage: new(0), + ContextWindow: 200000, + ExpectedPercent: 0, + }, + { + Case: "Context reset - both UsedPercentage and CurrentUsage nil", + UsedPercentage: nil, + HasCurrentUsage: false, + InputTokens: 50000, // High cumulative total - should be ignored + OutputTokens: 50000, + ContextWindow: 200000, + ExpectedPercent: 0, // Should return 0 after reset, not fallback to total + }, + { + Case: "Zero context window (no UsedPercentage)", + HasCurrentUsage: true, + InputTokens: 1000, + OutputTokens: 500, + ContextWindow: 0, + ExpectedPercent: 0, + }, + { + Case: "10% usage (fallback to total)", + HasCurrentUsage: true, + InputTokens: 8000, + OutputTokens: 2000, + ContextWindow: 100000, + ExpectedPercent: 10, + }, + { + Case: "50% usage (fallback to total)", + HasCurrentUsage: true, + InputTokens: 50000, + OutputTokens: 50000, + ContextWindow: 200000, + ExpectedPercent: 50, + }, + { + Case: "Over 100% usage (capped)", + HasCurrentUsage: true, + InputTokens: 100000, + OutputTokens: 50000, + ContextWindow: 100000, + ExpectedPercent: 100, + }, + { + Case: "Uses CurrentUsage input tokens", + HasCurrentUsage: true, + InputTokens: 100000, // High cumulative total + OutputTokens: 50000, + CurrentInput: 20000, // Current context input + ContextWindow: 200000, + ExpectedPercent: 10, // Should use current input (20000/200000 = 10%) + }, + { + Case: "Uses CurrentUsage with cache tokens", + HasCurrentUsage: true, + InputTokens: 100000, // High cumulative total + OutputTokens: 50000, + CurrentInput: 10000, + CacheCreationInputTokens: 5000, + CacheReadInputTokens: 5000, + ContextWindow: 200000, + ExpectedPercent: 10, // (10000+5000+5000)/200000 = 10% + }, + { + Case: "Uses CurrentUsage after compact (low current, high total)", + HasCurrentUsage: true, + InputTokens: 100000, // High cumulative total + OutputTokens: 50000, + CurrentInput: 6000, // Low current context (after compact) + ContextWindow: 200000, + ExpectedPercent: 3, // Should use current (6000/200000 = 3%) + }, + { + Case: "Fallback to total when CurrentUsage is zero", + HasCurrentUsage: true, + InputTokens: 20000, + OutputTokens: 10000, + CurrentInput: 0, + ContextWindow: 100000, + ExpectedPercent: 30, // Should fallback to total (30000/100000 = 30%) + }, + } + + for _, tc := range cases { + claude := &Claude{} + claude.ContextWindow.TotalInputTokens = tc.InputTokens + claude.ContextWindow.TotalOutputTokens = tc.OutputTokens + if tc.HasCurrentUsage { + claude.ContextWindow.CurrentUsage = &ClaudeCurrentUsage{ + InputTokens: tc.CurrentInput, + CacheCreationInputTokens: tc.CacheCreationInputTokens, + CacheReadInputTokens: tc.CacheReadInputTokens, + } + } + claude.ContextWindow.UsedPercentage = tc.UsedPercentage + claude.ContextWindow.ContextWindowSize = tc.ContextWindow + + percent := claude.TokenUsagePercent() + assert.Equal(t, tc.ExpectedPercent, percent, tc.Case) + } +} + +func TestClaudeFormattedCost(t *testing.T) { + cases := []struct { + Case string + ExpectedCost string + CostUSD float64 + }{ + { + Case: "Very small cost", + CostUSD: 0.0012, + ExpectedCost: "$0.0012", + }, + { + Case: "Small cost", + CostUSD: 0.0099, + ExpectedCost: "$0.0099", + }, + { + Case: "Regular cost", + CostUSD: 0.15, + ExpectedCost: "$0.15", + }, + { + Case: "Large cost", + CostUSD: 12.34, + ExpectedCost: "$12.34", + }, + } + + for _, tc := range cases { + claude := &Claude{} + claude.Cost.TotalCostUSD = tc.CostUSD + + formatted := claude.FormattedCost() + assert.Equal(t, tc.ExpectedCost, formatted, tc.Case) + } +} + +func TestClaudeFormattedDuration(t *testing.T) { + cases := []struct { + Case string + Expected string + MS DurationMS + }{ + {Case: "Zero", MS: 0, Expected: "0m 0s"}, + {Case: "Seconds only", MS: 45000, Expected: "0m 45s"}, + {Case: "Minutes and seconds", MS: 125000, Expected: "2m 5s"}, + {Case: "Exact minute", MS: 60000, Expected: "1m 0s"}, + } + + for _, tc := range cases { + claude := &Claude{} + claude.Cost.TotalDurationMS = tc.MS + assert.Equal(t, tc.Expected, claude.FormattedDuration(), tc.Case) + } +} + +func TestClaudeFormattedAPIDuration(t *testing.T) { + cases := []struct { + Case string + Expected string + MS DurationMS + }{ + {Case: "Zero", MS: 0, Expected: "0m 0s"}, + {Case: "Seconds only", MS: 30000, Expected: "0m 30s"}, + {Case: "Minutes and seconds", MS: 90000, Expected: "1m 30s"}, + } + + for _, tc := range cases { + claude := &Claude{} + claude.Cost.TotalAPIDurationMS = tc.MS + assert.Equal(t, tc.Expected, claude.FormattedAPIDuration(), tc.Case) + } +} + +func TestClaudeFormattedTokens(t *testing.T) { + cases := []struct { + Case string + ExpectedFormat string + InputTokens int + OutputTokens int + CurrentInput int + CacheCreationInputTokens int + CacheReadInputTokens int + HasCurrentUsage bool + }{ + { + Case: "Small token count (fallback to total)", + HasCurrentUsage: true, + InputTokens: 300, + OutputTokens: 200, + ExpectedFormat: "500", + }, + { + Case: "Thousands (fallback to total)", + HasCurrentUsage: true, + InputTokens: 8500, + OutputTokens: 1500, + ExpectedFormat: "10.0K", + }, + { + Case: "Tens of thousands (fallback to total)", + HasCurrentUsage: true, + InputTokens: 50000, + OutputTokens: 25000, + ExpectedFormat: "75.0K", + }, + { + Case: "Millions (fallback to total)", + HasCurrentUsage: true, + InputTokens: 1500000, + OutputTokens: 500000, + ExpectedFormat: "2.0M", + }, + { + Case: "Uses CurrentUsage input tokens", + HasCurrentUsage: true, + InputTokens: 100000, // High cumulative total + OutputTokens: 50000, + CurrentInput: 10000, // Current context input + ExpectedFormat: "10.0K", + }, + { + Case: "Uses CurrentUsage with cache tokens", + HasCurrentUsage: true, + InputTokens: 100000, // High cumulative total + OutputTokens: 50000, + CurrentInput: 5000, + CacheCreationInputTokens: 2500, + CacheReadInputTokens: 2500, + ExpectedFormat: "10.0K", // 5000+2500+2500 = 10000 + }, + { + Case: "Uses CurrentUsage after compact (low current)", + HasCurrentUsage: true, + InputTokens: 500000, // High cumulative total + OutputTokens: 200000, + CurrentInput: 500, // Low current context (after compact) + ExpectedFormat: "500", + }, + { + Case: "Fallback to total when CurrentUsage is zero", + HasCurrentUsage: true, + InputTokens: 50000, + OutputTokens: 25000, + CurrentInput: 0, + ExpectedFormat: "75.0K", // Should fallback to total + }, + { + Case: "Nil CurrentUsage falls back to total", + HasCurrentUsage: false, + InputTokens: 50000, + OutputTokens: 25000, + ExpectedFormat: "75.0K", // Should fallback to total + }, + } + + for _, tc := range cases { + claude := &Claude{} + claude.ContextWindow.TotalInputTokens = tc.InputTokens + claude.ContextWindow.TotalOutputTokens = tc.OutputTokens + if tc.HasCurrentUsage { + claude.ContextWindow.CurrentUsage = &ClaudeCurrentUsage{ + InputTokens: tc.CurrentInput, + CacheCreationInputTokens: tc.CacheCreationInputTokens, + CacheReadInputTokens: tc.CacheReadInputTokens, + } + } + + formatted := claude.FormattedTokens() + assert.Equal(t, tc.ExpectedFormat, formatted, tc.Case) + } +} + +func TestClaudeRateLimitUsage(t *testing.T) { + cases := []struct { + RateLimits *ClaudeRateLimits + Case string + ExpectedFive text.Percentage + ExpectedSeven text.Percentage + }{ + { + Case: "Nil RateLimits", + RateLimits: nil, + ExpectedFive: 0, + ExpectedSeven: 0, + }, + { + Case: "Nil FiveHour window", + RateLimits: &ClaudeRateLimits{ + SevenDay: &ClaudeRateLimitWindow{UsedPercentage: new(50.0)}, + }, + ExpectedFive: 0, + ExpectedSeven: 50, + }, + { + Case: "Nil SevenDay window", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{UsedPercentage: new(25.0)}, + }, + ExpectedFive: 25, + ExpectedSeven: 0, + }, + { + Case: "Nil UsedPercentage", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{UsedPercentage: nil}, + SevenDay: &ClaudeRateLimitWindow{UsedPercentage: nil}, + }, + ExpectedFive: 0, + ExpectedSeven: 0, + }, + { + Case: "Valid percentages", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{UsedPercentage: new(42.7)}, + SevenDay: &ClaudeRateLimitWindow{UsedPercentage: new(75.3)}, + }, + ExpectedFive: 43, + ExpectedSeven: 75, + }, + { + Case: "Value over 100 capped", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{UsedPercentage: new(150.0)}, + SevenDay: &ClaudeRateLimitWindow{UsedPercentage: new(200.0)}, + }, + ExpectedFive: 100, + ExpectedSeven: 100, + }, + { + Case: "Zero percentages", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{UsedPercentage: new(0.0)}, + SevenDay: &ClaudeRateLimitWindow{UsedPercentage: new(0.0)}, + }, + ExpectedFive: 0, + ExpectedSeven: 0, + }, + } + + for _, tc := range cases { + claude := &Claude{} + claude.RateLimits = tc.RateLimits + + assert.Equal(t, tc.ExpectedFive, claude.FiveHourUsage(), tc.Case+" (FiveHour)") + assert.Equal(t, tc.ExpectedSeven, claude.SevenDayUsage(), tc.Case+" (SevenDay)") + } +} + +func TestClaudeGaugeMethods(t *testing.T) { + cases := []struct { + RateLimits *ClaudeRateLimits + Case string + MarkedChar string + UnmarkedChar string + ExpectedTokenGauge string + ExpectedTokenGaugeUsed string + ExpectedFiveHourGauge string + ExpectedSevenDayGauge string + UsedPercentage int + }{ + { + Case: "Default chars (▰▱) at 40% used", + MarkedChar: "▰", + UnmarkedChar: "▱", + UsedPercentage: 40, + ExpectedTokenGauge: "▰▰▰▱▱", // 60% remaining = 3 blocks + ExpectedTokenGaugeUsed: "▰▰▱▱▱", // 40% used = 2 blocks + }, + { + Case: "Custom chars (█░) at 40% used", + MarkedChar: "█", + UnmarkedChar: "░", + UsedPercentage: 40, + ExpectedTokenGauge: "███░░", // 60% remaining = 3 blocks + ExpectedTokenGaugeUsed: "██░░░", // 40% used = 2 blocks + }, + { + Case: "Custom chars with rate limits", + MarkedChar: "█", + UnmarkedChar: "░", + UsedPercentage: 0, + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{UsedPercentage: new(60.0)}, + SevenDay: &ClaudeRateLimitWindow{UsedPercentage: new(20.0)}, + }, + ExpectedFiveHourGauge: "███░░", // 60% used = 3 blocks + ExpectedSevenDayGauge: "█░░░░", // 20% used = 1 block + }, + } + + for _, tc := range cases { + t.Run(tc.Case, func(t *testing.T) { + usedPct := tc.UsedPercentage + claude := &Claude{ + markedChar: tc.MarkedChar, + unmarkedChar: tc.UnmarkedChar, + } + claude.ContextWindow.UsedPercentage = &usedPct + claude.RateLimits = tc.RateLimits + + if tc.ExpectedTokenGauge != "" { + assert.Equal(t, tc.ExpectedTokenGauge, claude.TokenGauge(), tc.Case+" (TokenGauge)") + } + + if tc.ExpectedTokenGaugeUsed != "" { + assert.Equal(t, tc.ExpectedTokenGaugeUsed, claude.TokenGaugeUsed(), tc.Case+" (TokenGaugeUsed)") + } + + if tc.ExpectedFiveHourGauge != "" { + assert.Equal(t, tc.ExpectedFiveHourGauge, claude.FiveHourGauge(), tc.Case+" (FiveHourGauge)") + } + + if tc.ExpectedSevenDayGauge != "" { + assert.Equal(t, tc.ExpectedSevenDayGauge, claude.SevenDayGauge(), tc.Case+" (SevenDayGauge)") + } + }) + } +} + +func TestClaudeGaugeOptionsReadInEnabled(t *testing.T) { + t.Cleanup(func() { + cache.Delete(cache.Session, cache.CLAUDECACHE) + }) + + cache.Set(cache.Session, cache.CLAUDECACHE, ClaudeData{ + Model: ClaudeModel{DisplayName: "Opus"}, + }, cache.INFINITE) + + env := new(mock.Environment) + claude := &Claude{ + Base: Base{ + env: env, + options: options.Map{ + gaugeMarkedChar: "█", + gaugeUnmarkedChar: "░", + }, + }, + } + + assert.True(t, claude.Enabled()) + assert.Equal(t, "█", claude.markedChar) + assert.Equal(t, "░", claude.unmarkedChar) +} + +func TestClaudeRateLimitResetsAt(t *testing.T) { + fiveHourTS := int64(1711180800) // 2024-03-23 08:00:00 UTC + sevenDayTS := int64(1711612800) // 2024-03-28 08:00:00 UTC + + cases := []struct { + ExpectedFiveHour libtime.Time + ExpectedSevenDay libtime.Time + RateLimits *ClaudeRateLimits + Case string + }{ + { + Case: "Nil RateLimits", + RateLimits: nil, + ExpectedFiveHour: libtime.Time{}, + ExpectedSevenDay: libtime.Time{}, + }, + { + Case: "Nil FiveHour window", + RateLimits: &ClaudeRateLimits{ + SevenDay: &ClaudeRateLimitWindow{ResetsAt: &sevenDayTS}, + }, + ExpectedFiveHour: libtime.Time{}, + ExpectedSevenDay: libtime.Unix(sevenDayTS, 0), + }, + { + Case: "Nil SevenDay window", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{ResetsAt: &fiveHourTS}, + }, + ExpectedFiveHour: libtime.Unix(fiveHourTS, 0), + ExpectedSevenDay: libtime.Time{}, + }, + { + Case: "Nil ResetsAt pointers", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{ResetsAt: nil}, + SevenDay: &ClaudeRateLimitWindow{ResetsAt: nil}, + }, + ExpectedFiveHour: libtime.Time{}, + ExpectedSevenDay: libtime.Time{}, + }, + { + Case: "Valid timestamps for both windows", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{ResetsAt: &fiveHourTS}, + SevenDay: &ClaudeRateLimitWindow{ResetsAt: &sevenDayTS}, + }, + ExpectedFiveHour: libtime.Unix(fiveHourTS, 0), + ExpectedSevenDay: libtime.Unix(sevenDayTS, 0), + }, + } + + for _, tc := range cases { + claude := &Claude{} + claude.RateLimits = tc.RateLimits + + assert.Equal(t, tc.ExpectedFiveHour, claude.FiveHourResetsAt(), tc.Case+" (FiveHour)") + assert.Equal(t, tc.ExpectedSevenDay, claude.SevenDayResetsAt(), tc.Case+" (SevenDay)") + } +} + +func TestClaudeRateLimitResetsIn(t *testing.T) { + pastTS := libtime.Now().Add(-libtime.Hour).Unix() + futureTS := libtime.Now().Add(24 * libtime.Hour).Unix() + + cases := []struct { + RateLimits *ClaudeRateLimits + Case string + FiveHourSign int // -1=negative, 0=zero, 1=positive + SevenDaySign int + }{ + { + Case: "Nil RateLimits", + RateLimits: nil, + FiveHourSign: 0, + SevenDaySign: 0, + }, + { + Case: "Nil windows", + RateLimits: &ClaudeRateLimits{}, + FiveHourSign: 0, + SevenDaySign: 0, + }, + { + Case: "Nil ResetsAt", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{}, + SevenDay: &ClaudeRateLimitWindow{}, + }, + FiveHourSign: 0, + SevenDaySign: 0, + }, + { + Case: "Past timestamp", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{ResetsAt: &pastTS}, + SevenDay: &ClaudeRateLimitWindow{ResetsAt: &pastTS}, + }, + FiveHourSign: -1, + SevenDaySign: -1, + }, + { + Case: "Future timestamp", + RateLimits: &ClaudeRateLimits{ + FiveHour: &ClaudeRateLimitWindow{ResetsAt: &futureTS}, + SevenDay: &ClaudeRateLimitWindow{ResetsAt: &futureTS}, + }, + FiveHourSign: 1, + SevenDaySign: 1, + }, + } + + assertSign := func(t *testing.T, d libtime.Duration, sign int, name string) { + t.Helper() + switch sign { + case 0: + assert.Equal(t, libtime.Duration(0), d, name) + case 1: + assert.Positive(t, d, name) + case -1: + assert.Negative(t, d, name) + } + } + + for _, tc := range cases { + claude := &Claude{} + claude.RateLimits = tc.RateLimits + assertSign(t, claude.FiveHourResetsIn(), tc.FiveHourSign, tc.Case+" (FiveHour)") + assertSign(t, claude.SevenDayResetsIn(), tc.SevenDaySign, tc.Case+" (SevenDay)") + } +} diff --git a/src/segments/clojure.go b/src/segments/clojure.go new file mode 100644 index 000000000000..31616e4895b7 --- /dev/null +++ b/src/segments/clojure.go @@ -0,0 +1,48 @@ +package segments + +type Clojure struct { + Language +} + +func (c *Clojure) Template() string { + return languageTemplate +} + +func (c *Clojure) Enabled() bool { + c.init() + return c.Language.Enabled() +} + +const clojureToolName = "clojure" + +const leinToolName = "lein" + +func (c *Clojure) init() { + options := c.options.StringArray(Tooling, []string{}) + if len(options) == 0 { + c.defaultTooling = []string{clojureToolName, leinToolName} + } + + c.extensions = []string{ + "project.clj", + "deps.edn", + "build.boot", + "bb.edn", + "*.clj", + "*.cljc", + "*.cljs", + } + + c.tooling = map[string]*cmd{ + clojureToolName: { + executable: clojureToolName, + args: []string{versionFlagArg}, + regex: `Clojure CLI version (?P(?P[0-9]+)\.(?P[0-9]+)\.(?P[0-9]+)(?:\.(?P[0-9]+))?)`, + }, + leinToolName: { + executable: leinToolName, + args: []string{versionFlagArg}, + regex: `Leiningen (?P(?P[0-9]+)\.(?P[0-9]+)\.(?P[0-9]+))`, + }, + } +} diff --git a/src/segments/clojure_test.go b/src/segments/clojure_test.go new file mode 100644 index 000000000000..33479148bcb0 --- /dev/null +++ b/src/segments/clojure_test.go @@ -0,0 +1,47 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestClojure(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + Cmd string + }{ + { + Case: "Clojure CLI 1.11.1.1113", + ExpectedString: "1.11.1.1113", + Version: "Clojure CLI version 1.11.1.1113", + Cmd: "clojure", + }, + { + Case: "Leiningen 2.9.8", + ExpectedString: "2.9.8", + Version: "Leiningen 2.9.8 on Java 11.0.11 OpenJDK 64-Bit Server VM", + Cmd: "lein", + }, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: tc.Cmd, + versionParam: "--version", + versionOutput: tc.Version, + extension: "*.clj", + } + env, props := getMockedLanguageEnv(params) + props[LanguageExtensions] = []string{params.extension} + if tc.Cmd != "clojure" { + env.On("HasCommand", "clojure").Return(false) + } + c := &Clojure{} + c.Init(props, env) + assert.True(t, c.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, c.Template(), c), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/cmake.go b/src/segments/cmake.go new file mode 100644 index 000000000000..b1d0123f77f0 --- /dev/null +++ b/src/segments/cmake.go @@ -0,0 +1,26 @@ +package segments + +type Cmake struct { + Language +} + +func (c *Cmake) Template() string { + return languageTemplate +} + +const cmakeToolName = "cmake" + +func (c *Cmake) Enabled() bool { + c.extensions = []string{"*.cmake", "CMakeLists.txt"} + c.tooling = map[string]*cmd{ + cmakeToolName: { + executable: cmakeToolName, + args: []string{versionFlagArg}, + regex: `cmake version ` + versionRegex, + }, + } + c.defaultTooling = []string{cmakeToolName} + c.versionURLTemplate = "https://cmake.org/cmake/help/v{{ .Major }}.{{ .Minor }}" + + return c.Language.Enabled() +} diff --git a/src/segments/cmake_test.go b/src/segments/cmake_test.go new file mode 100644 index 000000000000..cb90d9e189b2 --- /dev/null +++ b/src/segments/cmake_test.go @@ -0,0 +1,34 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCmake(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + {Case: "Cmake 3.23.2", ExpectedString: "3.23.2", Version: "cmake version 3.23.2"}, + {Case: "Cmake 2.3.13", ExpectedString: "2.3.12", Version: "cmake version 2.3.12"}, + {Case: "", ExpectedString: "err parsing info from cmake with", Version: ""}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "cmake", + versionParam: "--version", + versionOutput: tc.Version, + extension: "*.cmake", + } + env, props := getMockedLanguageEnv(params) + c := &Cmake{} + c.Init(props, env) + failMsg := fmt.Sprintf("Failed in case: %s", tc.Case) + assert.True(t, c.Enabled(), failMsg) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, c.Template(), c), failMsg) + } +} diff --git a/src/segments/connection.go b/src/segments/connection.go new file mode 100644 index 000000000000..67a7a18884fd --- /dev/null +++ b/src/segments/connection.go @@ -0,0 +1,36 @@ +package segments + +import ( + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type Connection struct { + Base + + runtime.Connection +} + +const ( + Type options.Option = "type" +) + +func (c *Connection) Template() string { + return " {{ if eq .Type \"wifi\"}}\uf1eb{{ else if eq .Type \"ethernet\"}}\ueba9{{ end }} " +} + +func (c *Connection) Enabled() bool { + types := c.options.String(Type, "wifi|ethernet") + connectionTypes := strings.SplitSeq(types, "|") + for connectionType := range connectionTypes { + network, err := c.env.Connection(runtime.ConnectionType(connectionType)) + if err != nil { + continue + } + c.Connection = *network + return true + } + return false +} diff --git a/src/segments/connection_test.go b/src/segments/connection_test.go new file mode 100644 index 000000000000..b71517206827 --- /dev/null +++ b/src/segments/connection_test.go @@ -0,0 +1,108 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +func TestConnection(t *testing.T) { + type connectionResponse struct { + Connection *runtime.Connection + Error error + } + cases := []struct { + Case string + ExpectedString string + ConnectionType string + Connections []*connectionResponse + ExpectedEnabled bool + }{ + { + Case: "WiFi only, enabled", + ExpectedString: "\uf1eb", + ExpectedEnabled: true, + ConnectionType: "wifi", + Connections: []*connectionResponse{ + { + Connection: &runtime.Connection{ + Name: "WiFi", + Type: "wifi", + }, + }, + }, + }, + { + Case: "WiFi only, disabled", + ConnectionType: "wifi", + Connections: []*connectionResponse{ + { + Connection: &runtime.Connection{ + Type: runtime.WIFI, + }, + Error: fmt.Errorf("no connection"), + }, + }, + }, + { + Case: "WiFi and Ethernet, enabled", + ConnectionType: "wifi|ethernet", + ExpectedString: "\ueba9", + ExpectedEnabled: true, + Connections: []*connectionResponse{ + { + Connection: &runtime.Connection{ + Type: runtime.WIFI, + }, + Error: fmt.Errorf("no connection"), + }, + { + Connection: &runtime.Connection{ + Type: runtime.ETHERNET, + }, + }, + }, + }, + { + Case: "WiFi and Ethernet, disabled", + ConnectionType: "wifi|ethernet", + Connections: []*connectionResponse{ + { + Connection: &runtime.Connection{ + Type: runtime.WIFI, + }, + Error: fmt.Errorf("no connection"), + }, + { + Connection: &runtime.Connection{ + Type: runtime.ETHERNET, + }, + Error: fmt.Errorf("no connection"), + }, + }, + }, + } + for _, tc := range cases { + env := &mock.Environment{} + for _, con := range tc.Connections { + env.On("Connection", con.Connection.Type).Return(con.Connection, con.Error) + } + + props := &options.Map{ + Type: tc.ConnectionType, + } + + c := &Connection{} + c.Init(props, env) + + assert.Equal(t, tc.ExpectedEnabled, c.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, c.Template(), c), fmt.Sprintf("Failed in case: %s", tc.Case)) + } + } +} diff --git a/src/segments/copilot.go b/src/segments/copilot.go new file mode 100644 index 000000000000..f23b1285b325 --- /dev/null +++ b/src/segments/copilot.go @@ -0,0 +1,206 @@ +package segments + +import ( + "encoding/json" + "net/http" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/auth" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/text" +) + +// CopilotUsage represents usage statistics for a specific quota type. +type CopilotUsage struct { + Used int `json:"used"` + Limit int `json:"limit"` + Percent text.Percentage `json:"percent"` + Remaining text.Percentage `json:"remaining"` + Unlimited bool `json:"unlimited"` +} + +// Copilot displays GitHub Copilot usage statistics. +type Copilot struct { + Base + BillingCycleEnd string `json:"billing_cycle_end"` + Premium CopilotUsage `json:"premium"` + Inline CopilotUsage `json:"inline"` + Chat CopilotUsage `json:"chat"` +} + +const ( + copilotAPIURL = "https://api.github.com/copilot_internal/user" +) + +// copilotQuotaSnapshot represents a single quota type. +type copilotQuotaSnapshot struct { + Entitlement int `json:"entitlement"` + Remaining int `json:"remaining"` + Unlimited bool `json:"unlimited"` +} + +// copilotQuotaSnapshots represents the quota snapshots structure. +type copilotQuotaSnapshots struct { + PremiumInteractions copilotQuotaSnapshot `json:"premium_interactions"` + Completions copilotQuotaSnapshot `json:"completions"` + Chat copilotQuotaSnapshot `json:"chat"` +} + +// copilotAPIResponse represents the API response structure. +type copilotAPIResponse struct { + QuotaSnapshots *copilotQuotaSnapshots `json:"quota_snapshots"` + QuotaResetDate string `json:"quota_reset_date"` + QuotaResetDateUTC string `json:"quota_reset_date_utc"` +} + +func (c *Copilot) Template() string { + return " \uec1e {{ .Premium.Percent.Gauge }} " +} + +func (c *Copilot) Enabled() bool { + err := c.setStatus() + if err != nil { + log.Error(err) + return false + } + + return true +} + +func (c *Copilot) getAccessToken() string { + // Check cache from `oh-my-posh auth copilot` + if cachedToken, OK := cache.Get[string](cache.Device, auth.CopilotTokenKey); OK && len(cachedToken) != 0 { + return cachedToken + } + + return "" +} + +func (c *Copilot) getResult() (*copilotAPIResponse, error) { + accessToken := c.getAccessToken() + if len(accessToken) == 0 { + return nil, &noAccessTokenError{} + } + + log.Debug("found access token") + + httpTimeout := c.options.Int(options.HTTPTimeout, options.DefaultHTTPTimeout) + + addAuthHeader := func(request *http.Request) { + request.Header.Set("Authorization", "Bearer "+accessToken) + request.Header.Set("User-Agent", "GitHub-Copilot-Usage-Tray") + request.Header.Set("Accept", "application/json") + request.Header.Set("Content-Type", "application/json") + } + + body, err := c.env.HTTPRequest(copilotAPIURL, nil, httpTimeout, addAuthHeader) + if err != nil { + log.Error(err) + return nil, err + } + + log.Debug("executed HTTP request successfully") + + response := new(copilotAPIResponse) + err = json.Unmarshal(body, &response) + if err != nil { + return nil, err + } + + return response, nil +} + +func (c *Copilot) setStatus() error { + response, err := c.getResult() + if err != nil { + return err + } + + // Extract quota data from response - try different paths + quotaSnapshots := c.extractQuotaSnapshots(response) + if quotaSnapshots == nil { + return &noQuotaDataError{} + } + + // Calculate premium usage + c.Premium = c.calculateUsage(quotaSnapshots.PremiumInteractions) + + // Calculate inline usage (completions) + c.Inline = c.calculateUsage(quotaSnapshots.Completions) + + // Calculate chat usage + c.Chat = c.calculateUsage(quotaSnapshots.Chat) + + // Set billing cycle end date + c.BillingCycleEnd = response.QuotaResetDate + if c.BillingCycleEnd == "" { + c.BillingCycleEnd = response.QuotaResetDateUTC + } + + return nil +} + +func (c *Copilot) extractQuotaSnapshots(response *copilotAPIResponse) *copilotQuotaSnapshots { + if response == nil { + return nil + } + + // Use root-level quota_snapshots + if response.QuotaSnapshots != nil { + return response.QuotaSnapshots + } + + return nil +} + +func (c *Copilot) calculateUsage(snapshot copilotQuotaSnapshot) CopilotUsage { + if snapshot.Unlimited { + return CopilotUsage{ + Used: 0, + Limit: 0, + Percent: text.Percentage(0), + Remaining: text.Percentage(100), + Unlimited: true, + } + } + + used := max(snapshot.Entitlement-snapshot.Remaining, 0) + percent := c.calculatePercent(used, snapshot.Entitlement) + remainingPercent := 100 - percent + + return CopilotUsage{ + Used: used, + Limit: snapshot.Entitlement, + Percent: text.Percentage(percent), + Remaining: text.Percentage(remainingPercent), + Unlimited: false, + } +} + +func (c *Copilot) calculatePercent(used, limit int) int { + if limit <= 0 { + return 0 + } + + percent := (used * 100) / limit + if percent > 100 { + return 100 + } + + return percent +} + +// Custom error types for better error handling + +type noQuotaDataError struct{} + +func (e *noQuotaDataError) Error() string { + return "no quota data in response" +} + +type noAccessTokenError struct{} + +func (e *noAccessTokenError) Error() string { + return "no access token available, use 'oh-my-posh auth copilot' to authenticate" +} diff --git a/src/segments/copilot_test.go b/src/segments/copilot_test.go new file mode 100644 index 000000000000..96e7f80ec156 --- /dev/null +++ b/src/segments/copilot_test.go @@ -0,0 +1,346 @@ +package segments + +import ( + "errors" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/cli/auth" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/text" + + "github.com/stretchr/testify/assert" +) + +const ( + copilotTestURL = "https://api.github.com/copilot_internal/user" +) + +func TestCopilotSegment(t *testing.T) { + cases := []struct { + Case string + JSONResponse string + ExpectedString string + Template string + HasToken bool + ExpectedEnabled bool + HasError bool + }{ + { + Case: "Valid response with usage data", + JSONResponse: `{ + "quota_snapshots": { + "premium_interactions": { + "entitlement": 50, + "remaining": 35, + "unlimited": false + }, + "completions": { + "entitlement": 2000, + "remaining": 1500, + "unlimited": false + }, + "chat": { + "entitlement": 100, + "remaining": 80, + "unlimited": false + } + }, + "quota_reset_date": "2025-02-01T00:00:00Z" + }`, + Template: " \uec1e {{ .Premium.Used }}/{{ .Premium.Limit }} | {{ .Inline.Used }}/{{ .Inline.Limit }} | {{ .Chat.Used }}/{{ .Chat.Limit }} ", + ExpectedString: "\uec1e 15/50 | 500/2000 | 20/100", + ExpectedEnabled: true, + HasToken: true, + }, + { + Case: "Full premium usage", + JSONResponse: `{ + "quota_snapshots": { + "premium_interactions": { + "entitlement": 50, + "remaining": 0, + "unlimited": false + }, + "completions": { + "entitlement": 2000, + "remaining": 0, + "unlimited": false + }, + "chat": { + "entitlement": 100, + "remaining": 0, + "unlimited": false + } + }, + "quota_reset_date": "2025-02-01T00:00:00Z" + }`, + Template: " \uec1e {{ .Premium.Used }}/{{ .Premium.Limit }} | {{ .Inline.Used }}/{{ .Inline.Limit }} | {{ .Chat.Used }}/{{ .Chat.Limit }} ", + ExpectedString: "\uec1e 50/50 | 2000/2000 | 100/100", + ExpectedEnabled: true, + HasToken: true, + }, + { + Case: "No usage", + JSONResponse: `{ + "quota_snapshots": { + "premium_interactions": { + "entitlement": 50, + "remaining": 50, + "unlimited": false + }, + "completions": { + "entitlement": 2000, + "remaining": 2000, + "unlimited": false + }, + "chat": { + "entitlement": 0, + "remaining": 0, + "unlimited": true + } + }, + "quota_reset_date": "2025-02-01T00:00:00Z" + }`, + Template: " \uec1e {{ .Premium.Used }}/{{ .Premium.Limit }} | {{ .Inline.Used }}/{{ .Inline.Limit }} | {{ .Chat.Used }}/{{ .Chat.Limit }} ", + ExpectedString: "\uec1e 0/50 | 0/2000 | 0/0", + ExpectedEnabled: true, + HasToken: true, + }, + { + Case: "Custom template with percentages", + JSONResponse: `{ + "quota_snapshots": { + "premium_interactions": { + "entitlement": 100, + "remaining": 50, + "unlimited": false + }, + "completions": { + "entitlement": 1000, + "remaining": 750, + "unlimited": false + }, + "chat": { + "entitlement": 200, + "remaining": 100, + "unlimited": false + } + }, + "quota_reset_date": "2025-02-01T00:00:00Z" + }`, + Template: " {{ .Premium.Percent }}% | {{ .Inline.Percent }}% | {{ .Chat.Percent }}% ", + ExpectedString: "50% | 25% | 50%", + ExpectedEnabled: true, + HasToken: true, + }, + { + Case: "No access token", + ExpectedEnabled: false, + HasError: false, + }, + { + Case: "API error", + HasError: true, + ExpectedEnabled: false, + HasToken: true, + }, + { + Case: "Invalid JSON response", + JSONResponse: "invalid json", + ExpectedEnabled: false, + HasToken: true, + }, + { + Case: "Empty quota data", + JSONResponse: `{}`, + ExpectedEnabled: false, + HasToken: true, + }, + { + Case: "Null quota_snapshots", + JSONResponse: `{"quota_snapshots": null}`, + ExpectedEnabled: false, + HasToken: true, + }, + } + + for _, tc := range cases { + t.Run(tc.Case, func(t *testing.T) { + env := &mock.Environment{} + props := options.Map{} + + // Setup cached token mock + if tc.HasToken { + cache.Set(cache.Device, auth.CopilotTokenKey, "ghp_test_token", cache.INFINITE) + } else { + cache.Delete(cache.Device, auth.CopilotTokenKey) + } + + // Setup HTTP request mock + var httpErr error + if tc.HasError { + httpErr = errors.New("request failed") + } + + if tc.HasToken { + env.On("HTTPRequest", copilotTestURL).Return([]byte(tc.JSONResponse), httpErr) + } + + c := &Copilot{} + c.Init(props, env) + + enabled := c.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + + if !enabled { + return + } + + template := tc.Template + if template == "" { + template = c.Template() + } + + assert.Equal(t, tc.ExpectedString, renderTemplate(env, template, c), tc.Case) + }) + } +} + +func TestCopilotPercentageCalculation(t *testing.T) { + cases := []struct { + Case string + Used int + Limit int + ExpectedPercent int + }{ + { + Case: "50 percent", + Used: 50, + Limit: 100, + ExpectedPercent: 50, + }, + { + Case: "Zero limit", + Used: 10, + Limit: 0, + ExpectedPercent: 0, + }, + { + Case: "Negative limit", + Used: 10, + Limit: -5, + ExpectedPercent: 0, + }, + { + Case: "Over 100 percent caps at 100", + Used: 150, + Limit: 100, + ExpectedPercent: 100, + }, + { + Case: "Zero used", + Used: 0, + Limit: 100, + ExpectedPercent: 0, + }, + { + Case: "Full usage", + Used: 100, + Limit: 100, + ExpectedPercent: 100, + }, + } + + for _, tc := range cases { + t.Run(tc.Case, func(t *testing.T) { + c := &Copilot{} + result := c.calculatePercent(tc.Used, tc.Limit) + assert.Equal(t, tc.ExpectedPercent, result, tc.Case) + }) + } +} + +func TestCopilotRemainingPercentage(t *testing.T) { + env := &mock.Environment{} + props := options.Map{} + + jsonResponse := `{ + "quota_snapshots": { + "premium_interactions": { + "entitlement": 100, + "remaining": 75, + "unlimited": false + }, + "completions": { + "entitlement": 1000, + "remaining": 600, + "unlimited": false + }, + "chat": { + "entitlement": 200, + "remaining": 0, + "unlimited": false + } + }, + "quota_reset_date": "2025-02-15T00:00:00Z" + }` + + cache.Set(cache.Device, auth.CopilotTokenKey, "ghp_test_token", cache.INFINITE) + env.On("HTTPRequest", copilotTestURL).Return([]byte(jsonResponse), nil) + + c := &Copilot{} + c.Init(props, env) + + enabled := c.Enabled() + assert.True(t, enabled) + + // Test Premium: 100 entitlement - 75 remaining = 25 used (25% used, 75% remaining) + assert.Equal(t, text.Percentage(25), c.Premium.Percent) + assert.Equal(t, text.Percentage(75), c.Premium.Remaining) + + // Test Inline: 1000 entitlement - 600 remaining = 400 used (40% used, 60% remaining) + assert.Equal(t, text.Percentage(40), c.Inline.Percent) + assert.Equal(t, text.Percentage(60), c.Inline.Remaining) + + // Test Chat: 200 entitlement - 0 remaining = 200 used (100% used, 0% remaining) + assert.Equal(t, text.Percentage(100), c.Chat.Percent) + assert.Equal(t, text.Percentage(0), c.Chat.Remaining) +} + +func TestCopilotBillingCycleEnd(t *testing.T) { + env := &mock.Environment{} + props := options.Map{} + + jsonResponse := `{ + "quota_snapshots": { + "premium_interactions": { + "entitlement": 50, + "remaining": 35, + "unlimited": false + }, + "completions": { + "entitlement": 2000, + "remaining": 1500, + "unlimited": false + }, + "chat": { + "entitlement": 100, + "remaining": 80, + "unlimited": false + } + }, + "quota_reset_date": "2025-02-15T00:00:00Z" + }` + + cache.Set(cache.Device, auth.CopilotTokenKey, "ghp_test_token", cache.INFINITE) + env.On("HTTPRequest", copilotTestURL).Return([]byte(jsonResponse), nil) + + c := &Copilot{} + c.Init(props, env) + + enabled := c.Enabled() + assert.True(t, enabled) + assert.Equal(t, "2025-02-15T00:00:00Z", c.BillingCycleEnd) +} diff --git a/src/segments/crystal.go b/src/segments/crystal.go new file mode 100644 index 000000000000..758ff07619e5 --- /dev/null +++ b/src/segments/crystal.go @@ -0,0 +1,26 @@ +package segments + +type Crystal struct { + Language +} + +func (c *Crystal) Template() string { + return languageTemplate +} + +const crystalToolName = "crystal" + +func (c *Crystal) Enabled() bool { + c.extensions = []string{"*.cr", "shard.yml"} + c.tooling = map[string]*cmd{ + crystalToolName: { + executable: crystalToolName, + args: []string{versionFlagArg}, + regex: `Crystal ` + versionRegex, + }, + } + c.defaultTooling = []string{crystalToolName} + c.versionURLTemplate = "https://github.com/crystal-lang/crystal/releases/tag/{{ .Full }}" + + return c.Language.Enabled() +} diff --git a/src/segment_crystal_test.go b/src/segments/crystal_test.go similarity index 69% rename from src/segment_crystal_test.go rename to src/segments/crystal_test.go index 90a783bc1b70..ba2a68fbfb23 100644 --- a/src/segment_crystal_test.go +++ b/src/segments/crystal_test.go @@ -1,4 +1,4 @@ -package main +package segments import ( "fmt" @@ -23,9 +23,9 @@ func TestCrystal(t *testing.T) { extension: "*.cr", } env, props := getMockedLanguageEnv(params) - c := &crystal{} - c.init(props, env) - assert.True(t, c.enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) - assert.Equal(t, tc.ExpectedString, c.string(), fmt.Sprintf("Failed in case: %s", tc.Case)) + c := &Crystal{} + c.Init(props, env) + assert.True(t, c.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, c.Template(), c), fmt.Sprintf("Failed in case: %s", tc.Case)) } } diff --git a/src/segments/dart.go b/src/segments/dart.go new file mode 100644 index 000000000000..54543ecd4887 --- /dev/null +++ b/src/segments/dart.go @@ -0,0 +1,35 @@ +package segments + +var ( + dartExtensions = []string{"*.dart", "pubspec.yaml", "pubspec.yml", "pubspec.lock"} + dartFolders = []string{".dart_tool"} +) + +type Dart struct { + Language +} + +func (d *Dart) Template() string { + return languageTemplate +} + +func (d *Dart) Enabled() bool { + d.extensions = dartExtensions + d.folders = dartFolders + d.tooling = map[string]*cmd{ + fvmToolName: { + executable: fvmToolName, + args: []string{dartToolName, versionFlagArg}, + regex: `Dart SDK version: ` + versionRegex, + }, + dartToolName: { + executable: dartToolName, + args: []string{versionFlagArg}, + regex: `Dart SDK version: ` + versionRegex, + }, + } + d.defaultTooling = []string{fvmToolName, dartToolName} + d.versionURLTemplate = "https://dart.dev/guides/language/evolution#dart-{{ .Major }}{{ .Minor }}" + + return d.Language.Enabled() +} diff --git a/src/segment_dart_test.go b/src/segments/dart_test.go similarity index 67% rename from src/segment_dart_test.go rename to src/segments/dart_test.go index 21079a35b32b..7cb001dec726 100644 --- a/src/segment_dart_test.go +++ b/src/segments/dart_test.go @@ -1,4 +1,4 @@ -package main +package segments import ( "fmt" @@ -22,10 +22,14 @@ func TestDart(t *testing.T) { versionOutput: tc.Version, extension: "*.dart", } + env, props := getMockedLanguageEnv(params) - d := &dart{} - d.init(props, env) - assert.True(t, d.enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) - assert.Equal(t, tc.ExpectedString, d.string(), fmt.Sprintf("Failed in case: %s", tc.Case)) + env.On("HasCommand", "fvm").Return(false) + + d := &Dart{} + d.Init(props, env) + + assert.True(t, d.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, d.Template(), d), fmt.Sprintf("Failed in case: %s", tc.Case)) } } diff --git a/src/segments/deno.go b/src/segments/deno.go new file mode 100644 index 000000000000..28d00740021a --- /dev/null +++ b/src/segments/deno.go @@ -0,0 +1,24 @@ +package segments + +type Deno struct { + Language +} + +func (d *Deno) Template() string { + return languageTemplate +} + +func (d *Deno) Enabled() bool { + d.extensions = []string{"*.js", "*.ts", "deno.json"} + d.tooling = map[string]*cmd{ + denoToolName: { + executable: denoToolName, + args: []string{versionFlagArg}, + regex: versionRegexPrefixed, + }, + } + d.defaultTooling = []string{denoToolName} + d.versionURLTemplate = "https://github.com/denoland/deno/releases/tag/v{{.Full}}" + + return d.Language.Enabled() +} diff --git a/src/segments/deno_test.go b/src/segments/deno_test.go new file mode 100644 index 000000000000..967a1de4fe27 --- /dev/null +++ b/src/segments/deno_test.go @@ -0,0 +1,31 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDeno(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + {Case: "Deno 1.25.2", ExpectedString: "1.25.2", Version: "deno 1.25.2 (release, aarch64-apple-darwin)\nv8 10.6.194.5\ntypescript 4.7.4"}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "deno", + versionParam: "--version", + versionOutput: tc.Version, + extension: "*.js", + } + env, props := getMockedLanguageEnv(params) + d := &Deno{} + d.Init(props, env) + assert.True(t, d.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, d.Template(), d), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/docker.go b/src/segments/docker.go new file mode 100644 index 000000000000..dc7c591f0f19 --- /dev/null +++ b/src/segments/docker.go @@ -0,0 +1,113 @@ +package segments + +import ( + "encoding/json" + "path/filepath" + + "slices" + + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +const ( + // FetchContext is the property used to fetch the current docker context + FetchContext options.Option = "fetch_context" + + defaultDockerContext = "default" +) + +type DockerConfig struct { + CurrentContext string `json:"currentContext"` +} + +type Docker struct { + Base + + Context string +} + +func (d *Docker) Template() string { + return " \uf308 {{ .Context }} " +} + +func (d *Docker) envVars() []string { + return []string{"DOCKER_MACHINE_NAME", "DOCKER_HOST", "DOCKER_CONTEXT"} +} + +func (d *Docker) configFiles() []string { + files := []string{ + filepath.Join(d.env.Home(), ".docker/config.json"), + } + + dockerConfig := d.env.Getenv("DOCKER_CONFIG") + if len(dockerConfig) > 0 { + files = append(files, filepath.Join(dockerConfig, "config.json")) + } + + return files +} + +func (d *Docker) Enabled() bool { + extensions := []string{ + "compose.yml", + "compose.yaml", + "docker-compose.yml", + "docker-compose.yaml", + "Dockerfile", + } + + extensions = d.options.StringArray(LanguageExtensions, extensions) + + displayMode := d.options.String(DisplayMode, DisplayModeContext) + switch displayMode { + case DisplayModeContext: + return d.fetchContext() + case DisplayModeFiles: + if !slices.ContainsFunc(extensions, d.env.HasFiles) { + return false + } + + // always respect the context fetching + if d.options.Bool(FetchContext, true) { + _ = d.fetchContext() + } + + return true + } + + return false +} + +func (d *Docker) fetchContext() bool { + // Check if there is a non-empty environment variable named `DOCKER_HOST` or `DOCKER_CONTEXT` + // These variables are set by the docker CLI and override the config file + // Return the current context if it is not empty and not `default` + for _, v := range d.envVars() { + context := d.env.Getenv(v) + if len(context) > 0 && context != defaultDockerContext { + d.Context = context + return true + } + } + + // Check if there is a file named `$HOME/.docker/config.json` or `$DOCKER_CONFIG/config.json` + // Return the current context if it is not empty and not `default` + for _, f := range d.configFiles() { + data := d.env.FileContent(f) + if data == "" { + continue + } + + var cfg DockerConfig + if err := json.Unmarshal([]byte(data), &cfg); err != nil { + continue + } + + if len(cfg.CurrentContext) > 0 && cfg.CurrentContext != defaultDockerContext { + d.Context = cfg.CurrentContext + return true + } + } + + return false +} diff --git a/src/segments/docker_test.go b/src/segments/docker_test.go new file mode 100644 index 000000000000..7e4823acbc4d --- /dev/null +++ b/src/segments/docker_test.go @@ -0,0 +1,92 @@ +package segments + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" + mock_ "github.com/stretchr/testify/mock" +) + +func TestDockerContext(t *testing.T) { + type envVar struct { + name string + value string + } + cases := []struct { + EnvVar envVar + Case string + Expected string + ConfigFile string + ExpectedEnabled bool + HasFiles bool + }{ + {Case: "DOCKER_MACHINE_NAME", Expected: "alpine", ExpectedEnabled: true, EnvVar: envVar{name: "DOCKER_MACHINE_NAME", value: "alpine"}}, + {Case: "DOCKER_HOST", Expected: "alpine 2", ExpectedEnabled: true, EnvVar: envVar{name: "DOCKER_HOST", value: "alpine 2"}}, + {Case: "DOCKER_CONTEXT", Expected: "alpine 3", ExpectedEnabled: true, EnvVar: envVar{name: "DOCKER_HOST", value: "alpine 3"}}, + {Case: "DOCKER_CONTEXT - default", ExpectedEnabled: false, EnvVar: envVar{name: "DOCKER_HOST", value: "default"}}, + {Case: "no docker context active", ExpectedEnabled: false}, + {Case: "config file", Expected: "alpine", ExpectedEnabled: true, HasFiles: true, ConfigFile: `{"currentContext": "alpine"}`}, + {Case: "config file - default", ExpectedEnabled: false, HasFiles: true, ConfigFile: `{"currentContext": "default"}`}, + {Case: "config file - broken", ExpectedEnabled: false, HasFiles: true, ConfigFile: `{`}, + } + + for _, tc := range cases { + docker := &Docker{} + env := new(mock.Environment) + docker.Init(options.Map{}, env) + + for _, v := range docker.envVars() { + var value string + if v == tc.EnvVar.name { + value = tc.EnvVar.value + } + env.On("Getenv", v).Return(value) + } + + env.On("Home").Return("") + env.On("Getenv", "DOCKER_CONFIG").Return("") + for _, f := range docker.configFiles() { + env.On("HasFiles", f).Return(tc.HasFiles) + env.On("FileContent", f).Return(tc.ConfigFile) + } + + assert.Equal(t, tc.ExpectedEnabled, docker.Enabled(), tc.Case) + if tc.ExpectedEnabled { + assert.Equal(t, tc.Expected, renderTemplate(env, "{{ .Context }}", docker), tc.Case) + } + } +} + +func TestDockerFiles(t *testing.T) { + cases := []struct { + Case string + ExpectedEnabled bool + HasFiles bool + }{ + {Case: "compose.yml", ExpectedEnabled: true, HasFiles: true}, + {Case: "compose.yaml", ExpectedEnabled: true, HasFiles: true}, + {Case: "docker-compose.yml", ExpectedEnabled: true, HasFiles: true}, + {Case: "docker-compose.yaml", ExpectedEnabled: true, HasFiles: true}, + {Case: "Dockerfile", ExpectedEnabled: true, HasFiles: true}, + {Case: "docker-compose.yml - not found", ExpectedEnabled: false, HasFiles: false}, + } + + for _, tc := range cases { + docker := &Docker{} + env := new(mock.Environment) + props := options.Map{ + DisplayMode: DisplayModeFiles, + FetchContext: false, + } + + docker.Init(props, env) + + env.On("HasFiles", tc.Case).Return(true) + env.On("HasFiles", mock_.Anything).Return(false) + + assert.Equal(t, tc.ExpectedEnabled, docker.Enabled(), tc.Case) + } +} diff --git a/src/segments/dotnet.go b/src/segments/dotnet.go new file mode 100644 index 000000000000..17a2d01a7c71 --- /dev/null +++ b/src/segments/dotnet.go @@ -0,0 +1,81 @@ +package segments + +import ( + "encoding/json" + + "github.com/jandedobbeleer/oh-my-posh/src/constants" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type globalJSON struct { + Sdk struct { + Version string `json:"version"` + } `json:"sdk"` +} + +const ( + // FetchSDKVersion fetches the SDK version in global.json + FetchSDKVersion options.Option = "fetch_sdk_version" +) + +type Dotnet struct { + SDKVersion string + Language + Unsupported bool +} + +func (d *Dotnet) Template() string { + return " {{ if .Unsupported }}\uf071{{ else }}{{ .Full }}{{ end }} " +} + +func (d *Dotnet) Enabled() bool { + d.extensions = []string{ + "*.cs", + "*.csx", + "*.vb", + "*.sln", + "*.slnx", + "*.slnf", + "*.csproj", + "*.vbproj", + "*.fs", + "*.fsx", + "*.fsproj", + "global.json", + } + d.tooling = map[string]*cmd{ + dotnetToolName: { + executable: dotnetToolName, + args: []string{versionFlagArg}, + regex: `(?P((?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)` + + `(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?))`, + }, + } + d.defaultTooling = []string{dotnetToolName} + d.versionURLTemplate = "https://github.com/dotnet/core/blob/main/release-notes/{{ .Major }}.{{ .Minor }}/{{ .Major }}.{{ .Minor }}.{{ substr 0 1 .Patch }}/{{ .Major }}.{{ .Minor }}.{{ substr 0 1 .Patch }}.md" //nolint: lll + + enabled := d.Language.Enabled() + if !enabled { + return false + } + + d.Unsupported = d.exitCode == constants.DotnetExitCode + + if !d.options.Bool(FetchSDKVersion, false) { + return true + } + + file, err := d.env.HasParentFilePath("global.json", false) + if err != nil { + return true + } + + content := d.env.FileContent(file.Path) + + var globalJSON globalJSON + if err := json.Unmarshal([]byte(content), &globalJSON); err == nil { + d.SDKVersion = globalJSON.Sdk.Version + } + + return true +} diff --git a/src/segments/dotnet_test.go b/src/segments/dotnet_test.go new file mode 100644 index 000000000000..cf254ef0bba9 --- /dev/null +++ b/src/segments/dotnet_test.go @@ -0,0 +1,116 @@ +package segments + +import ( + "errors" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/constants" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +func TestDotnetSegment(t *testing.T) { + cases := []struct { + Case string + Expected string + Version string + ExitCode int + }{ + {Case: "Unsupported version", Expected: "\uf071", ExitCode: constants.DotnetExitCode, Version: "3.1.402"}, + {Case: "Regular version", Expected: "3.1.402", Version: "3.1.402"}, + } + + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "dotnet", + versionParam: "--version", + versionOutput: tc.Version, + extension: "*.cs", + } + env, props := getMockedLanguageEnv(params) + + if tc.ExitCode != 0 { + env.Unset("RunCommand") + err := &runtime.CommandError{ExitCode: tc.ExitCode} + env.On("RunCommand", "dotnet", []string{"--version"}).Return("", err) + } + + dotnet := &Dotnet{} + dotnet.Init(props, env) + + assert.True(t, dotnet.Enabled()) + assert.Equal(t, tc.Expected, renderTemplate(env, dotnet.Template(), dotnet), tc.Case) + } +} + +func TestDotnetSDKVersion(t *testing.T) { + cases := []struct { + Case string + GlobalJSON string + ExpectedSDK string + GlobalJSONPath string + FetchSDK bool + HasGlobalJSON bool + }{ + { + Case: "Do not fetch SDK version", + FetchSDK: false, + ExpectedSDK: "", + }, + { + Case: "No global.json found", + FetchSDK: true, + ExpectedSDK: "", + }, + { + Case: "Valid global.json", + FetchSDK: true, + GlobalJSON: `{"sdk": {"version": "6.0.100"}}`, + ExpectedSDK: "6.0.100", + HasGlobalJSON: true, + GlobalJSONPath: "/test/global.json", + }, + { + Case: "Invalid global.json", + FetchSDK: true, + GlobalJSON: `invalid json`, + ExpectedSDK: "", + HasGlobalJSON: true, + GlobalJSONPath: "/test/global.json", + }, + } + + params := &mockedLanguageParams{ + cmd: "dotnet", + versionParam: "--version", + versionOutput: "6.0.100", + extension: "*.cs", + } + + for _, tc := range cases { + props := options.Map{ + FetchSDKVersion: tc.FetchSDK, + options.FetchVersion: false, + } + + env, _ := getMockedLanguageEnv(params) + + if tc.HasGlobalJSON { + file := &runtime.FileInfo{ + Path: tc.GlobalJSONPath, + } + env.On("HasParentFilePath", "global.json", false).Return(file, nil) + env.On("FileContent", tc.GlobalJSONPath).Return(tc.GlobalJSON) + } else { + env.On("HasParentFilePath", "global.json", false).Return(&runtime.FileInfo{}, errors.New("file not found")) + } + + dotnet := &Dotnet{} + dotnet.Init(props, env) + + assert.True(t, dotnet.Enabled(), tc.Case) + assert.Equal(t, tc.ExpectedSDK, dotnet.SDKVersion, tc.Case) + } +} diff --git a/src/segments/elixir.go b/src/segments/elixir.go new file mode 100644 index 000000000000..51d864179ed0 --- /dev/null +++ b/src/segments/elixir.go @@ -0,0 +1,31 @@ +package segments + +type Elixir struct { + Language +} + +func (e *Elixir) Template() string { + return languageTemplate +} + +const elixirToolName = "elixir" + +func (e *Elixir) Enabled() bool { + e.extensions = []string{"*.ex", "*.exs"} + e.tooling = map[string]*cmd{ + asdfToolName: { + executable: asdfToolName, + args: []string{"current", elixirToolName}, + regex: `elixir\s+` + versionRegex + `[^\s]*\s+`, + }, + elixirToolName: { + executable: elixirToolName, + args: []string{versionFlagArg}, + regex: `Elixir ` + versionRegex, + }, + } + e.defaultTooling = []string{asdfToolName, elixirToolName} + e.versionURLTemplate = "https://github.com/elixir-lang/elixir/releases/tag/v{{ .Full }}" + + return e.Language.Enabled() +} diff --git a/src/segments/elixir_test.go b/src/segments/elixir_test.go new file mode 100644 index 000000000000..0679d686efd5 --- /dev/null +++ b/src/segments/elixir_test.go @@ -0,0 +1,62 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/stretchr/testify/assert" +) + +func TestElixir(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + ElixirVersionOutput string + AsdfVersionOutput string + HasAsdf bool + AsdfExitCode int + }{ + { + Case: "Version without asdf", + ExpectedString: "1.14.2", + ElixirVersionOutput: "Erlang/OTP 25 [erts-13.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]\n\nElixir 1.14.2 (compiled with Erlang/OTP 25)", + }, + { + Case: "Version with asdf", + ExpectedString: "1.14.2", + HasAsdf: true, + AsdfVersionOutput: "elixir 1.14.2-otp-25 /path/to/.tool-versions", + ElixirVersionOutput: "Should not be used", + }, + { + Case: "Version with asdf not set: should fall back to elixir --version", + ExpectedString: "1.14.2", + HasAsdf: true, + AsdfVersionOutput: "elixir ______ No version is set. Run \"asdf elixir \"", + AsdfExitCode: 126, + ElixirVersionOutput: "Erlang/OTP 25 [erts-13.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]\n\nElixir 1.14.2 (compiled with Erlang/OTP 25)", + }, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "elixir", + versionParam: "--version", + versionOutput: tc.ElixirVersionOutput, + extension: "*.ex", + } + env, props := getMockedLanguageEnv(params) + + env.On("HasCommand", "asdf").Return(tc.HasAsdf) + var asdfErr error + if tc.AsdfExitCode != 0 { + asdfErr = &runtime.CommandError{ExitCode: tc.AsdfExitCode} + } + env.On("RunCommand", "asdf", []string{"current", "elixir"}).Return(tc.AsdfVersionOutput, asdfErr) + + r := &Elixir{} + r.Init(props, env) + assert.True(t, r.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, r.Template(), r), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/executiontime.go b/src/segments/executiontime.go new file mode 100644 index 000000000000..c8edcdc4305e --- /dev/null +++ b/src/segments/executiontime.go @@ -0,0 +1,343 @@ +package segments + +import ( + "fmt" + "strconv" + + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + lang "golang.org/x/text/language" + "golang.org/x/text/message" +) + +type Executiontime struct { + Base + + FormattedMs string + Ms int64 +} + +// DurationStyle how to display the time +type DurationStyle string + +const ( + // ThresholdProperty represents minimum duration (milliseconds) required to enable this segment + ThresholdProperty options.Option = "threshold" + // Austin milliseconds short + Austin DurationStyle = "austin" + // Roundrock milliseconds long + Roundrock DurationStyle = "roundrock" + // Dallas milliseconds full + Dallas DurationStyle = "dallas" + // Galveston hour + Galveston DurationStyle = "galveston" + // Galveston hour + GalvestonMs DurationStyle = "galvestonms" + // Houston hour and milliseconds + Houston DurationStyle = "houston" + // Amarillo seconds + Amarillo DurationStyle = "amarillo" + // Round will round the output of the format + Round DurationStyle = "round" + // Always 7 character width + Lucky7 = "lucky7" + // ISO8601 ISO 8601 duration format (seconds) + ISO8601 DurationStyle = "iso8601" + // ISO8601Ms ISO 8601 duration format with milliseconds + ISO8601Ms DurationStyle = "iso8601ms" + + second = 1000 + minute = 60000 + hour = 3600000 + day = 86400000 + secondsPerMinute = 60 + minutesPerHour = 60 + hoursPerDay = 24 +) + +func (t *Executiontime) Enabled() bool { + alwaysEnabled := t.options.Bool(options.AlwaysEnabled, false) + executionTimeMs := t.env.ExecutionTime() + thresholdMs := t.options.Float64(ThresholdProperty, float64(500)) + if !alwaysEnabled && executionTimeMs < thresholdMs { + return false + } + style := DurationStyle(t.options.String(options.Style, string(Austin))) + t.Ms = int64(executionTimeMs) + t.FormattedMs = t.formatDuration(style) + return t.FormattedMs != "" +} + +func (t *Executiontime) Template() string { + return " {{ .FormattedMs }} " +} + +func (t *Executiontime) formatDuration(style DurationStyle) string { + switch style { + case Austin: + return t.formatDurationAustin() + case Roundrock: + return t.formatDurationRoundrock() + case Dallas: + return t.formatDurationDallas() + case Galveston: + return t.formatDurationGalveston() + case GalvestonMs: + return t.formatDurationGalvestonMs() + case Houston: + return t.formatDurationHouston() + case Amarillo: + return t.formatDurationAmarillo() + case Round: + return t.formatDurationRound() + case Lucky7: + return t.formatDurationLucky7() + case ISO8601: + return t.formatDurationISO8601() + case ISO8601Ms: + return t.formatDurationISO8601Ms() + default: + return fmt.Sprintf("Style: %s is not available", style) + } +} + +func (t *Executiontime) formatDurationAustin() string { + if t.Ms < second { + return fmt.Sprintf("%dms", t.Ms%second) + } + + seconds := float64(t.Ms%minute) / second + result := strconv.FormatFloat(seconds, 'f', -1, 64) + "s" + + if t.Ms >= minute { + result = fmt.Sprintf("%dm %s", t.Ms/minute%secondsPerMinute, result) + } + if t.Ms >= hour { + result = fmt.Sprintf("%dh %s", t.Ms/hour%hoursPerDay, result) + } + if t.Ms >= day { + result = fmt.Sprintf("%dd %s", t.Ms/day, result) + } + return result +} + +func (t *Executiontime) formatDurationRoundrock() string { + result := fmt.Sprintf("%dms", t.Ms%second) + if t.Ms >= second { + result = fmt.Sprintf("%ds %s", t.Ms/second%secondsPerMinute, result) + } + if t.Ms >= minute { + result = fmt.Sprintf("%dm %s", t.Ms/minute%minutesPerHour, result) + } + if t.Ms >= hour { + result = fmt.Sprintf("%dh %s", t.Ms/hour%hoursPerDay, result) + } + if t.Ms >= day { + result = fmt.Sprintf("%dd %s", t.Ms/day, result) + } + return result +} + +func (t *Executiontime) formatDurationDallas() string { + seconds := float64(t.Ms%minute) / second + result := strconv.FormatFloat(seconds, 'f', -1, 64) + + if t.Ms >= minute { + result = fmt.Sprintf("%d:%s", t.Ms/minute%minutesPerHour, result) + } + if t.Ms >= hour { + result = fmt.Sprintf("%d:%s", t.Ms/hour%hoursPerDay, result) + } + if t.Ms >= day { + result = fmt.Sprintf("%d:%s", t.Ms/day, result) + } + return result +} + +func (t *Executiontime) formatDurationGalveston() string { + result := fmt.Sprintf("%02d:%02d:%02d", t.Ms/hour, t.Ms/minute%minutesPerHour, t.Ms%minute/second) + return result +} + +func (t *Executiontime) formatDurationGalvestonMs() string { + millies := t.Ms % second + result := fmt.Sprintf("%02d:%02d:%02d:%03d", t.Ms/hour, t.Ms/minute%minutesPerHour, t.Ms%minute/second, millies) + return result +} + +func (t *Executiontime) formatDurationHouston() string { + milliseconds := ".0" + if t.Ms%second > 0 { + // format milliseconds as a string with truncated trailing zeros + milliseconds = strconv.FormatFloat(float64(t.Ms%second)/second, 'f', -1, 64) + // at this point milliseconds looks like "0.5". remove the leading "0" + if len(milliseconds) >= 1 { + milliseconds = milliseconds[1:] + } + } + + result := fmt.Sprintf("%02d:%02d:%02d%s", t.Ms/hour, t.Ms/minute%minutesPerHour, t.Ms%minute/second, milliseconds) + return result +} + +func (t *Executiontime) formatDurationAmarillo() string { + // wholeNumber represents the value to the left of the decimal point (seconds) + wholeNumber := t.Ms / second + // decimalNumber represents the value to the right of the decimal point (milliseconds) + decimalNumber := float64(t.Ms%second) / second + + // format wholeNumber as a string with thousands separators + printer := message.NewPrinter(lang.English) + result := printer.Sprintf("%d", wholeNumber) + + if decimalNumber > 0 { + // format decimalNumber as a string with truncated trailing zeros + decimalResult := strconv.FormatFloat(decimalNumber, 'f', -1, 64) + // at this point decimalResult looks like "0.5" + // remove the leading "0" and append + if len(decimalResult) >= 1 { + result += decimalResult[1:] + } + } + result += "s" + + return result +} + +func (t *Executiontime) formatDurationRound() string { + toRoundString := func(one, two int64, oneText, twoText string) string { + if two == 0 { + return fmt.Sprintf("%d%s", one, oneText) + } + return fmt.Sprintf("%d%s %d%s", one, oneText, two, twoText) + } + hours := t.Ms / hour % hoursPerDay + if t.Ms >= day { + return toRoundString(t.Ms/day, hours, "d", "h") + } + minutes := t.Ms / minute % secondsPerMinute + if t.Ms >= hour { + return toRoundString(hours, minutes, "h", "m") + } + seconds := (t.Ms % minute) / second + if t.Ms >= minute { + return toRoundString(minutes, seconds, "m", "s") + } + if t.Ms >= second { + return fmt.Sprintf("%ds", seconds) + } + return fmt.Sprintf("%dms", t.Ms%second) +} + +func (t *Executiontime) formatDurationLucky7() string { + // https://github.com/JanDeDobbeleer/oh-my-posh/issues/3970 + // execution time will always be 7 characters long + // decimal point will be at the same location (3rd space or str[2]) + // seconds and milliseconds will be aligned + // [m, s], [h, m], [d, h] will be aligned + if t.Ms < second { + // 999ms + // 1234567 + return fmt.Sprintf("%5dms", t.Ms%second) + } + + if t.Ms < minute { + // 12.34s + // 1234567 + + // 1.23s + // 1230 (= 1230ms) + // ^ use Sprintf pad left space + // 1230 + // from here, just take 1, 23 of 230, and append s and ' ' + + result := fmt.Sprintf("%5d", t.Ms) + + return result[:2] + "." + result[2:4] + "s " + } + + if t.Ms < hour { + m := t.Ms / minute + s := t.Ms % minute / second + + return fmt.Sprintf("%2dm %2ds", m, s) + } + + if t.Ms < day { + h := t.Ms / hour + m := t.Ms % hour / minute + + return fmt.Sprintf("%2dh %2dm", h, m) + } + + if t.Ms < 100*day { + d := t.Ms / day + h := t.Ms % day / hour + + return fmt.Sprintf("%2dd %2dh", d, h) + } + + // I have no Idea how you got here + // return " ∞ " + d := t.Ms / day + return fmt.Sprintf("%6dd", d) +} + +func (t *Executiontime) formatDurationISO8601() string { + // ISO 8601 duration format: PT[n]H[n]M[n]S + // Examples: PT13M12S, PT1H30M45S + result := "PT" + + hours := t.Ms / hour + minutes := (t.Ms % hour) / minute + seconds := float64(t.Ms%minute) / second + + roundedSeconds := int64(seconds) + if t.Ms%second >= second/2 { + roundedSeconds++ + } + + // Handle potential overflow from rounding + if roundedSeconds >= secondsPerMinute { + roundedSeconds = 0 + minutes++ + if minutes >= minutesPerHour { + minutes = 0 + hours++ + } + } + + if hours > 0 { + result += fmt.Sprintf("%dH", hours) + } + if minutes > 0 { + result += fmt.Sprintf("%dM", minutes) + } + if roundedSeconds > 0 || (hours == 0 && minutes == 0) { + result += fmt.Sprintf("%dS", roundedSeconds) + } + + return result +} + +func (t *Executiontime) formatDurationISO8601Ms() string { + // ISO 8601 duration format with milliseconds: PT[n]H[n]M[n]S + // Examples: PT13M12.1S, PT1H30M45.123S + result := "PT" + + hours := t.Ms / hour + minutes := (t.Ms % hour) / minute + seconds := float64(t.Ms%minute) / second + + if hours > 0 { + result += fmt.Sprintf("%dH", hours) + } + if minutes > 0 { + result += fmt.Sprintf("%dM", minutes) + } + if seconds > 0 || (hours == 0 && minutes == 0) { + secondsStr := strconv.FormatFloat(seconds, 'f', -1, 64) + result += fmt.Sprintf("%sS", secondsStr) + } + + return result +} diff --git a/src/segments/executiontime_test.go b/src/segments/executiontime_test.go new file mode 100644 index 000000000000..dbd225de47cf --- /dev/null +++ b/src/segments/executiontime_test.go @@ -0,0 +1,420 @@ +package segments + +import ( + "math" + "testing" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +func TestExecutionTimeWriterDefaultThresholdEnabled(t *testing.T) { + env := new(mock.Environment) + env.On("ExecutionTime").Return(1337) + + executionTime := &Executiontime{} + executionTime.Init(options.Map{}, env) + + assert.True(t, executionTime.Enabled()) +} + +func TestExecutionTimeWriterDefaultThresholdDisabled(t *testing.T) { + env := new(mock.Environment) + env.On("ExecutionTime").Return(1) + + executionTime := &Executiontime{} + executionTime.Init(options.Map{}, env) + + assert.False(t, executionTime.Enabled()) +} + +func TestExecutionTimeWriterCustomThresholdEnabled(t *testing.T) { + env := new(mock.Environment) + env.On("ExecutionTime").Return(99) + props := options.Map{ + ThresholdProperty: float64(10), + } + + executionTime := &Executiontime{} + executionTime.Init(props, env) + + assert.True(t, executionTime.Enabled()) +} + +func TestExecutionTimeWriterCustomThresholdDisabled(t *testing.T) { + env := new(mock.Environment) + env.On("ExecutionTime").Return(99) + props := options.Map{ + ThresholdProperty: float64(100), + } + + executionTime := &Executiontime{} + executionTime.Init(props, env) + + assert.False(t, executionTime.Enabled()) +} + +func TestExecutionTimeWriterDuration(t *testing.T) { + input := 1337 + expected := "1.337s" + env := new(mock.Environment) + env.On("ExecutionTime").Return(input) + + executionTime := &Executiontime{} + executionTime.Init(options.Map{}, env) + + executionTime.Enabled() + assert.Equal(t, expected, executionTime.FormattedMs) +} + +func TestExecutionTimeWriterDuration2(t *testing.T) { + input := 13371337 + expected := "3h 42m 51.337s" + env := new(mock.Environment) + env.On("ExecutionTime").Return(input) + + executionTime := &Executiontime{} + executionTime.Init(options.Map{}, env) + + executionTime.Enabled() + assert.Equal(t, expected, executionTime.FormattedMs) +} + +func TestExecutionTimeFormatDurationAustin(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "1ms"}, + {Input: "0.1s", Expected: "100ms"}, + {Input: "1s", Expected: "1s"}, + {Input: "2.1s", Expected: "2.1s"}, + {Input: "1m", Expected: "1m 0s"}, + {Input: "3m2.1s", Expected: "3m 2.1s"}, + {Input: "1h", Expected: "1h 0m 0s"}, + {Input: "4h3m2.1s", Expected: "4h 3m 2.1s"}, + {Input: "124h3m2.1s", Expected: "5d 4h 3m 2.1s"}, + {Input: "124h3m2.0s", Expected: "5d 4h 3m 2s"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationAustin() + assert.Equal(t, tc.Expected, output) + } +} + +func TestExecutionTimeFormatDurationRoundrock(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "1ms"}, + {Input: "0.1s", Expected: "100ms"}, + {Input: "1s", Expected: "1s 0ms"}, + {Input: "2.1s", Expected: "2s 100ms"}, + {Input: "1m", Expected: "1m 0s 0ms"}, + {Input: "3m2.1s", Expected: "3m 2s 100ms"}, + {Input: "1h", Expected: "1h 0m 0s 0ms"}, + {Input: "4h3m2.1s", Expected: "4h 3m 2s 100ms"}, + {Input: "124h3m2.1s", Expected: "5d 4h 3m 2s 100ms"}, + {Input: "124h3m2.0s", Expected: "5d 4h 3m 2s 0ms"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationRoundrock() + assert.Equal(t, tc.Expected, output) + } +} + +func TestExecutionTimeFormatDallas(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "0.001"}, + {Input: "0.1s", Expected: "0.1"}, + {Input: "1s", Expected: "1"}, + {Input: "2.1s", Expected: "2.1"}, + {Input: "1m", Expected: "1:0"}, + {Input: "3m2.1s", Expected: "3:2.1"}, + {Input: "1h", Expected: "1:0:0"}, + {Input: "4h3m2.1s", Expected: "4:3:2.1"}, + {Input: "124h3m2.1s", Expected: "5:4:3:2.1"}, + {Input: "124h3m2.0s", Expected: "5:4:3:2"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationDallas() + assert.Equal(t, tc.Expected, output) + } +} + +func TestExecutionTimeFormatGalveston(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "00:00:00"}, + {Input: "0.1s", Expected: "00:00:00"}, + {Input: "1s", Expected: "00:00:01"}, + {Input: "2.1s", Expected: "00:00:02"}, + {Input: "1m", Expected: "00:01:00"}, + {Input: "3m2.1s", Expected: "00:03:02"}, + {Input: "1h", Expected: "01:00:00"}, + {Input: "4h3m2.1s", Expected: "04:03:02"}, + {Input: "124h3m2.1s", Expected: "124:03:02"}, + {Input: "124h3m2.0s", Expected: "124:03:02"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationGalveston() + assert.Equal(t, tc.Expected, output) + } +} + +func TestExecutionTimeFormatGalvestonMs(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "00:00:00:001"}, + {Input: "0.1s", Expected: "00:00:00:100"}, + {Input: "1s", Expected: "00:00:01:000"}, + {Input: "2.1s", Expected: "00:00:02:100"}, + {Input: "1m", Expected: "00:01:00:000"}, + {Input: "3m2.1s", Expected: "00:03:02:100"}, + {Input: "1h", Expected: "01:00:00:000"}, + {Input: "4h3m2.1s", Expected: "04:03:02:100"}, + {Input: "124h3m2.1s", Expected: "124:03:02:100"}, + {Input: "124h3m2.0s", Expected: "124:03:02:000"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationGalvestonMs() + assert.Equal(t, tc.Expected, output, tc.Input) + } +} + +func TestExecutionTimeFormatHouston(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "00:00:00.001"}, + {Input: "0.1s", Expected: "00:00:00.1"}, + {Input: "1s", Expected: "00:00:01.0"}, + {Input: "2.1s", Expected: "00:00:02.1"}, + {Input: "1m", Expected: "00:01:00.0"}, + {Input: "3m2.1s", Expected: "00:03:02.1"}, + {Input: "1h", Expected: "01:00:00.0"}, + {Input: "4h3m2.1s", Expected: "04:03:02.1"}, + {Input: "124h3m2.1s", Expected: "124:03:02.1"}, + {Input: "124h3m2.0s", Expected: "124:03:02.0"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationHouston() + assert.Equal(t, tc.Expected, output) + } +} + +func TestExecutionTimeFormatAmarillo(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "0.001s"}, + {Input: "0.1s", Expected: "0.1s"}, + {Input: "1s", Expected: "1s"}, + {Input: "2.1s", Expected: "2.1s"}, + {Input: "1m", Expected: "60s"}, + {Input: "3m2.1s", Expected: "182.1s"}, + {Input: "1h", Expected: "3,600s"}, + {Input: "4h3m2.1s", Expected: "14,582.1s"}, + {Input: "124h3m2.1s", Expected: "446,582.1s"}, + {Input: "124h3m2.0s", Expected: "446,582s"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationAmarillo() + assert.Equal(t, tc.Expected, output) + } +} + +func TestExecutionTimeFormatDurationRound(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "1ms"}, + {Input: "0.1s", Expected: "100ms"}, + {Input: "1s", Expected: "1s"}, + {Input: "2.1s", Expected: "2s"}, + {Input: "1m", Expected: "1m"}, + {Input: "3m2.1s", Expected: "3m 2s"}, + {Input: "1h", Expected: "1h"}, + {Input: "4h3m2.1s", Expected: "4h 3m"}, + {Input: "124h3m2.1s", Expected: "5d 4h"}, + {Input: "124h3m2.0s", Expected: "5d 4h"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationRound() + assert.Equal(t, tc.Expected, output) + } +} + +func TestExecutionTimeFormatDurationLucky7(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + { + Input: "0.001s", + Expected: " 1ms", + }, + { + Input: "0.1s", + Expected: " 100ms", + }, + { + Input: "1s", + Expected: " 1.00s ", + }, + { + Input: "2.1s", + Expected: " 2.10s ", + }, + { + Input: "1m", + Expected: " 1m 0s", + }, + { + Input: "3m2.1s", + Expected: " 3m 2s", + }, + { + Input: "1h", + Expected: " 1h 0m", + }, + { + Input: "4h3m2.1s", + Expected: " 4h 3m", + }, + { + Input: "124h3m2.1s", + Expected: " 5d 4h", + }, + { + Input: "124h3m2.0s", + Expected: " 5d 4h", + }, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationLucky7() + assert.Equal(t, tc.Expected, output) + } + + // Extra fuzz test + var timestamp int64 = 1 + var ms1000days int64 = 1000 * 24 * 60 * 60 * 1000 + + // log(ms1000days, 1.5) is approx 62.1 + for timestamp < ms1000days { + timestamp = int64(math.Ceil(float64(timestamp) * 1.5)) + + executionTime := (&Executiontime{ + Ms: timestamp, + }).formatDurationLucky7() + + // Lucky 7!! + assert.Equal(t, len(executionTime), 7) + } +} + +func TestExecutionTimeFormatISO8601(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "PT0S"}, + {Input: "0.1s", Expected: "PT0S"}, + {Input: "0.5s", Expected: "PT1S"}, + {Input: "1s", Expected: "PT1S"}, + {Input: "2.1s", Expected: "PT2S"}, + {Input: "2.6s", Expected: "PT3S"}, + {Input: "1m", Expected: "PT1M"}, + {Input: "3m2.1s", Expected: "PT3M2S"}, + {Input: "3m2.6s", Expected: "PT3M3S"}, + {Input: "1h", Expected: "PT1H"}, + {Input: "4h3m2.1s", Expected: "PT4H3M2S"}, + {Input: "124h3m2.1s", Expected: "PT124H3M2S"}, + {Input: "124h3m2.0s", Expected: "PT124H3M2S"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationISO8601() + assert.Equal(t, tc.Expected, output, "Input: %s", tc.Input) + } +} + +func TestExecutionTimeFormatISO8601Ms(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "0.001s", Expected: "PT0.001S"}, + {Input: "0.1s", Expected: "PT0.1S"}, + {Input: "1s", Expected: "PT1S"}, + {Input: "2.1s", Expected: "PT2.1S"}, + {Input: "2.123s", Expected: "PT2.123S"}, + {Input: "1m", Expected: "PT1M"}, + {Input: "3m2.1s", Expected: "PT3M2.1S"}, + {Input: "3m2.123s", Expected: "PT3M2.123S"}, + {Input: "1h", Expected: "PT1H"}, + {Input: "4h3m2.1s", Expected: "PT4H3M2.1S"}, + {Input: "124h3m2.123s", Expected: "PT124H3M2.123S"}, + } + + for _, tc := range cases { + duration, _ := time.ParseDuration(tc.Input) + executionTime := &Executiontime{} + executionTime.Ms = duration.Milliseconds() + output := executionTime.formatDurationISO8601Ms() + assert.Equal(t, tc.Expected, output, "Input: %s", tc.Input) + } +} diff --git a/src/segments/firebase.go b/src/segments/firebase.go new file mode 100644 index 000000000000..0cfc8cb5a557 --- /dev/null +++ b/src/segments/firebase.go @@ -0,0 +1,77 @@ +package segments + +import ( + "encoding/json" + "errors" + "path/filepath" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/log" +) + +const ( + FIREBASENOACTIVECONFIG = "NO ACTIVE CONFIG FOUND" +) + +type Firebase struct { + Base + + Project string +} + +type FirebaseData struct { + ActiveProject map[string]string `json:"activeProjects"` +} + +func (f *Firebase) Template() string { + return " {{ .Project}} " +} + +func (f *Firebase) Enabled() bool { + cfgDir := filepath.Join(f.env.Home(), ".config", "configstore") + configFile, err := f.getActiveConfig(cfgDir) + if err != nil { + log.Error(err) + return false + } + + data, err := f.getFirebaseData(configFile) + if err != nil { + log.Error(err) + return false + } + + // Within the activeProjects is a key value pair + // of the path to the project and the project name + + // Test if the current directory is a project path + // and if it is, return the project name + for key, value := range data.ActiveProject { + if strings.HasPrefix(f.env.Pwd(), key) { + f.Project = value + return true + } + } + + return false +} + +func (f *Firebase) getActiveConfig(cfgDir string) (string, error) { + activeConfigFile := filepath.Join(cfgDir, "firebase-tools.json") + activeConfigData := f.env.FileContent(activeConfigFile) + if activeConfigData == "" { + return "", errors.New(FIREBASENOACTIVECONFIG) + } + return activeConfigData, nil +} + +func (f *Firebase) getFirebaseData(configFile string) (*FirebaseData, error) { + var data FirebaseData + + err := json.Unmarshal([]byte(configFile), &data) + if err != nil { + return nil, err + } + + return &data, nil +} diff --git a/src/segments/firebase_test.go b/src/segments/firebase_test.go new file mode 100644 index 000000000000..24a8416a73ba --- /dev/null +++ b/src/segments/firebase_test.go @@ -0,0 +1,117 @@ +package segments + +import ( + "path/filepath" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/stretchr/testify/assert" +) + +func TestFirebaseSegment(t *testing.T) { + config := `{ + "activeProjects": { + "path": "project-name" + } + }` + cases := []struct { + Case string + ActiveConfig string + ActivePath string + ExpectedString string + ExpectedEnabled bool + }{ + { + Case: "happy path", + ExpectedEnabled: true, + ActiveConfig: config, + ActivePath: "path", + ExpectedString: "project-name", + }, + { + Case: "happy subpath", + ExpectedEnabled: true, + ActiveConfig: config, + ActivePath: "path/subpath", + ExpectedString: "project-name", + }, + { + Case: "no active config", + ExpectedEnabled: false, + }, + { + Case: "empty config", + ActiveConfig: "{}", + ExpectedEnabled: false, + }, + { + Case: "bad config", + ActiveConfig: "{bad}", + ExpectedEnabled: false, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Home").Return("home") + env.On("Pwd").Return(tc.ActivePath) + fcPath := filepath.Join("home", ".config", "configstore", "firebase-tools.json") + env.On("FileContent", fcPath).Return(tc.ActiveConfig) + + f := &Firebase{} + f.Init(options.Map{}, env) + + f.Enabled() + + assert.Equal(t, tc.ExpectedEnabled, f.Enabled()) + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, f.Template(), f), tc.Case) + } + } +} + +func TestGetFirebaseActiveConfig(t *testing.T) { + data := + `{ + "activeProjects": { + "path": "project-name" + } + }` + cases := []struct { + Case string + ActiveConfig string + ExpectedString string + ExpectedError string + }{ + { + Case: "happy path", + ActiveConfig: data, + ExpectedString: data, + }, + { + Case: "no active config", + ActiveConfig: "", + ExpectedError: FIREBASENOACTIVECONFIG, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Home").Return("home") + configPath := filepath.Join("home", ".config", "configstore") + contentPath := filepath.Join(configPath, "firebase-tools.json") + env.On("FileContent", contentPath).Return(tc.ActiveConfig) + + f := &Firebase{} + f.Init(options.Map{}, env) + + got, err := f.getActiveConfig(configPath) + assert.Equal(t, tc.ExpectedString, got, tc.Case) + if len(tc.ExpectedError) > 0 { + assert.EqualError(t, err, tc.ExpectedError, tc.Case) + } else { + assert.NoError(t, err, tc.Case) + } + } +} diff --git a/src/segments/flutter.go b/src/segments/flutter.go new file mode 100644 index 000000000000..3bb2b13b8f9d --- /dev/null +++ b/src/segments/flutter.go @@ -0,0 +1,32 @@ +package segments + +type Flutter struct { + Language +} + +func (f *Flutter) Template() string { + return languageTemplate +} + +const flutterToolName = "flutter" + +func (f *Flutter) Enabled() bool { + f.extensions = dartExtensions + f.folders = dartFolders + f.tooling = map[string]*cmd{ + fvmToolName: { + executable: fvmToolName, + args: []string{flutterToolName, versionFlagArg}, + regex: `Flutter ` + versionRegex, + }, + flutterToolName: { + executable: flutterToolName, + args: []string{versionFlagArg}, + regex: `Flutter ` + versionRegex, + }, + } + f.defaultTooling = []string{fvmToolName, flutterToolName} + f.versionURLTemplate = "https://github.com/flutter/flutter/releases/tag/{{ .Major }}.{{ .Minor }}.{{ .Patch }}" + + return f.Language.Enabled() +} diff --git a/src/segments/flutter_test.go b/src/segments/flutter_test.go new file mode 100644 index 000000000000..f4f0bd34d289 --- /dev/null +++ b/src/segments/flutter_test.go @@ -0,0 +1,35 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFlutter(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + {Case: "Flutter 2.10.4", ExpectedString: "2.10.4", Version: "Flutter 2.10.4 • channel stable • https://github.com/flutter/flutter.git"}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "flutter", + versionParam: "--version", + versionOutput: tc.Version, + extension: "*.dart", + } + + env, props := getMockedLanguageEnv(params) + env.On("HasCommand", "fvm").Return(false) + + d := &Flutter{} + d.Init(props, env) + + assert.True(t, d.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, d.Template(), d), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/fortran.go b/src/segments/fortran.go new file mode 100644 index 000000000000..efcf1538bcfc --- /dev/null +++ b/src/segments/fortran.go @@ -0,0 +1,33 @@ +package segments + +type Fortran struct { + Language +} + +func (f *Fortran) Template() string { + return languageTemplate +} + +const gfortranToolName = "gfortran" + +func (f *Fortran) Enabled() bool { + f.extensions = []string{ + "*.f", "*.for", "*.fpp", + "*.f77", "*.f90", "*.f95", + "*.f03", "*.f08", + "*.F", "*.FOR", "*.FPP", + "*.F77", "*.F90", "*.F95", + "*.F03", "*.F08", + "fpm.toml", + } + f.tooling = map[string]*cmd{ + gfortranToolName: { + executable: gfortranToolName, + args: []string{versionFlagArg}, + regex: `GNU Fortran \(.*\) ` + versionRegex, + }, + } + f.defaultTooling = []string{gfortranToolName} + + return f.Language.Enabled() +} diff --git a/src/segments/fortran_test.go b/src/segments/fortran_test.go new file mode 100644 index 000000000000..a58ddf8eae7f --- /dev/null +++ b/src/segments/fortran_test.go @@ -0,0 +1,46 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFortran(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + { + Case: "GNU Fortran 10.2.1 Debian", + ExpectedString: "10.2.1", + Version: `GNU Fortran (Debian 10.2.1-6) 10.2.1 20210110 + Copyright (C) 2020 Free Software Foundation, Inc. + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.`, + }, + { + Case: "GNU Fortran 11.4.0 Ubuntu", + ExpectedString: "11.4.0", + Version: `GNU Fortran (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 + Copyright (C) 2021 Free Software Foundation, Inc. + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.`, + }, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "gfortran", + versionParam: "--version", + versionOutput: tc.Version, + extension: "*.f", + } + env, props := getMockedLanguageEnv(params) + f := &Fortran{} + f.Init(props, env) + assert.True(t, f.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, f.Template(), f), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/fossil.go b/src/segments/fossil.go new file mode 100644 index 000000000000..6bcfed1279ac --- /dev/null +++ b/src/segments/fossil.go @@ -0,0 +1,67 @@ +package segments + +import "strings" + +// FossilStatus represents part of the status of a Svn repository +type FossilStatus struct { + ScmStatus +} + +func (s *FossilStatus) add(code string) { + switch code { + case "CONFLICT": + s.Conflicted++ + case "DELETED", "MISSING": + s.Deleted++ + case "ADDED", "ADDED_BY_INTEGRATE", "ADDED_BY_MERGE": + s.Added++ + case "EDITED", "UPDATED", "UPDATED_BY_INTEGRATE", "UPDATED_BY_MERGE", "CHANGED": + s.Modified++ + case "RENAMED": + s.Moved++ + } +} + +const ( + FOSSILCOMMAND = "fossil" +) + +type Fossil struct { + Status *FossilStatus + Branch string + Scm +} + +func (f *Fossil) Template() string { + return " \ue725 {{.Branch}} {{.Status.String}} " +} + +func (f *Fossil) Enabled() bool { + if !f.hasCommand(FOSSILCOMMAND) { + return false + } + + // run fossil command + output, err := f.env.RunCommand(f.command, "status") + if err != nil { + return false + } + + f.Status = &FossilStatus{} + lines := strings.SplitSeq(output, "\n") + + for line := range lines { + key, value, found := strings.Cut(line, " ") + if !found { + continue + } + switch key { + case "tags:": + f.Branch = strings.TrimSpace(value) + default: + f.Status.add(key) + } + } + + return true +} diff --git a/src/segments/fossil_test.go b/src/segments/fossil_test.go new file mode 100644 index 000000000000..c7bb5d57b435 --- /dev/null +++ b/src/segments/fossil_test.go @@ -0,0 +1,83 @@ +package segments + +import ( + "fmt" + "strings" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +func TestFossilStatus(t *testing.T) { + cases := []struct { + OutputError error + Case string + Output string + ExpectedStatus string + ExpectedBranch string + HasCommand bool + ExpectedDisabled bool + }{ + { + Case: "not installed", + HasCommand: false, + ExpectedDisabled: true, + }, + { + Case: "command error", + HasCommand: true, + OutputError: fmt.Errorf("error"), + ExpectedDisabled: true, + }, + { + Case: "default status", + HasCommand: true, + Output: ` + repository: /Users/jan/Downloads/myclone.fossil + local-root: /Users/jan/Projects/fossil/ + config-db: /Users/jan/.config/fossil.db + checkout: 0fabc4f3566c7e7d9e528b17253de42e14dd5c7b 2022-06-05 04:06:17 UTC + parent: e8a051e6a943a26c9c33a30df8ceda069c06c174 2022-06-04 23:09:02 UTC + tags: trunk + comment: In the /setup_skin page, add a mention of/link to /skins, per request in the forum. (user: stephan) + CONFLICT test.tst + DELETED test.tst + MISSING test.tst + ADDED test.tst + ADDED_BY_INTEGRATE test.tst + ADDED_BY_MERGE test.tst + EDITED auto.def + UPDATED test.tst + UPDATED_BY_INTEGRATE test.tst + UPDATED_BY_MERGE test.tst + CHANGED test.tst + RENAMED test.tst + `, + ExpectedBranch: "trunk", + ExpectedStatus: "+3 ~5 -2 >1 !1", + }, + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("GOOS").Return("unix") + env.On("IsWsl").Return(false) + env.On("InWSLSharedDrive").Return(false) + env.On("HasCommand", FOSSILCOMMAND).Return(tc.HasCommand) + env.On("RunCommand", FOSSILCOMMAND, []string{"status"}).Return(strings.ReplaceAll(tc.Output, "\t", ""), tc.OutputError) + + f := &Fossil{} + f.Init(options.Map{}, env) + + got := f.Enabled() + + assert.Equal(t, !tc.ExpectedDisabled, got, tc.Case) + if tc.ExpectedDisabled { + continue + } + assert.Equal(t, tc.ExpectedStatus, f.Status.String(), tc.Case) + assert.Equal(t, tc.ExpectedBranch, f.Branch, tc.Case) + } +} diff --git a/src/segments/gcp.go b/src/segments/gcp.go new file mode 100644 index 000000000000..05491c2edf1d --- /dev/null +++ b/src/segments/gcp.go @@ -0,0 +1,86 @@ +package segments + +import ( + "errors" + "path" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + + "gopkg.in/ini.v1" +) + +const ( + GCPNOACTIVECONFIG = "NO ACTIVE CONFIG FOUND" +) + +type Gcp struct { + Base + + Account string + Project string + Region string + ActiveConfig string +} + +func (g *Gcp) Template() string { + return " {{ .Project }} " +} + +func (g *Gcp) Enabled() bool { + cfgDir := g.getConfigDirectory() + cfgName, err := g.getActiveConfig(cfgDir) + if err != nil { + log.Error(err) + return false + } + + g.ActiveConfig = cfgName + cfgPath := path.Join(cfgDir, "configurations", "config_"+cfgName) + cfg := g.env.FileContent(cfgPath) + + if cfg == "" { + log.Error(errors.New("config file is empty")) + return false + } + + data, err := ini.Load([]byte(cfg)) + if err != nil { + log.Error(err) + return false + } + + g.Project = data.Section("core").Key("project").String() + g.Account = data.Section("core").Key("account").String() + g.Region = data.Section("compute").Key("region").String() + + return true +} + +func (g *Gcp) getActiveConfig(cfgDir string) (string, error) { + activeCfg := g.env.Getenv("CLOUDSDK_ACTIVE_CONFIG_NAME") + if len(activeCfg) != 0 { + return activeCfg, nil + } + + ap := path.Join(cfgDir, "active_config") + activeCfg = g.env.FileContent(ap) + if activeCfg == "" { + return "", errors.New(GCPNOACTIVECONFIG) + } + + return activeCfg, nil +} + +func (g *Gcp) getConfigDirectory() string { + cfgDir := g.env.Getenv("CLOUDSDK_CONFIG") + if len(cfgDir) != 0 { + return cfgDir + } + + if g.env.GOOS() == runtime.WINDOWS { + return path.Join(g.env.Getenv("APPDATA"), "gcloud") + } + + return path.Join(g.env.Home(), ".config", "gcloud") +} diff --git a/src/segments/gcp_test.go b/src/segments/gcp_test.go new file mode 100644 index 000000000000..6d6f03eaed87 --- /dev/null +++ b/src/segments/gcp_test.go @@ -0,0 +1,183 @@ +package segments + +import ( + "path" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +func TestGcpSegment(t *testing.T) { + cases := []struct { + Case string + CfgData string + ActiveConfig string + EnvActiveConfig string + ExpectedString string + ExpectedEnabled bool + }{ + { + Case: "happy path", + ExpectedEnabled: true, + ActiveConfig: "production", + CfgData: ` + [core] + account = test@example.com + project = test-test-test + + [compute] + region = europe-test1 + `, + ExpectedString: "test-test-test :: europe-test1 :: test@example.com", + }, + { + Case: "no active config", + ExpectedEnabled: false, + }, + { + Case: "empty config", + ActiveConfig: "production", + ExpectedEnabled: false, + }, + { + Case: "bad config", + ActiveConfig: "production", + CfgData: "{bad}", + ExpectedEnabled: false, + }, + { + Case: "use CLOUDSDK_ACTIVE_CONFIG_NAME", + EnvActiveConfig: "myconfig", + ExpectedEnabled: true, + CfgData: ` + [core] + account = user@example.com + project = cloud-proj + + [compute] + region = us-west1 + `, + ExpectedString: "cloud-proj :: us-west1 :: user@example.com", + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Getenv", "CLOUDSDK_CONFIG").Return("config") + env.On("Getenv", "CLOUDSDK_ACTIVE_CONFIG_NAME").Return(tc.EnvActiveConfig) + + // Only use fallback file if env var is not set + if tc.EnvActiveConfig == "" { + fcPath := path.Join("config", "active_config") + env.On("FileContent", fcPath).Return(tc.ActiveConfig) + } + + // Resolve active config name + activeConfig := tc.EnvActiveConfig + if activeConfig == "" { + activeConfig = tc.ActiveConfig + } + + cfgpath := path.Join("config", "configurations", "config_"+activeConfig) + env.On("FileContent", cfgpath).Return(tc.CfgData) + + g := &Gcp{} + g.Init(options.Map{}, env) + + assert.Equal(t, tc.ExpectedEnabled, g.Enabled(), tc.Case) + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, "{{.Project}} :: {{.Region}} :: {{.Account}}", g), tc.Case) + } + } +} + +func TestGetConfigDirectory(t *testing.T) { + cases := []struct { + Case string + GOOS string + Home string + AppData string + CloudSDKConfig string + Expected string + }{ + { + Case: "CLOUDSDK_CONFIG", + CloudSDKConfig: "/Users/posh/.config/gcloud", + Expected: "/Users/posh/.config/gcloud", + }, + { + Case: "Windows", + GOOS: runtime.WINDOWS, + AppData: "/Users/posh/.config", + Expected: "/Users/posh/.config/gcloud", + }, + { + Case: "default", + Home: "/Users/posh2/", + Expected: "/Users/posh2/.config/gcloud", + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Getenv", "CLOUDSDK_CONFIG").Return(tc.CloudSDKConfig) + env.On("Getenv", "APPDATA").Return(tc.AppData) + env.On("Home").Return(tc.Home) + env.On("GOOS").Return(tc.GOOS) + + g := &Gcp{} + g.Init(options.Map{}, env) + + assert.Equal(t, tc.Expected, g.getConfigDirectory(), tc.Case) + } +} + +func TestGetActiveConfig(t *testing.T) { + cases := []struct { + Case string + EnvActiveConfigName string + FileActiveConfigContent string + ExpectedString string + ExpectedError string + }{ + { + Case: "CLOUDSDK_ACTIVE_CONFIG_NAME set", + EnvActiveConfigName: "envconfig", + ExpectedString: "envconfig", + }, + { + Case: "Fallback to file content", + FileActiveConfigContent: "fileconfig", + ExpectedString: "fileconfig", + }, + { + Case: "No config anywhere", + ExpectedError: GCPNOACTIVECONFIG, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Getenv", "CLOUDSDK_ACTIVE_CONFIG_NAME").Return(tc.EnvActiveConfigName) + + // If env var not set, mock file fallback + if tc.EnvActiveConfigName == "" { + env.On("FileContent", path.Join("", "active_config")).Return(tc.FileActiveConfigContent) + } + + g := &Gcp{} + g.Init(options.Map{}, env) + + got, err := g.getActiveConfig("") + assert.Equal(t, tc.ExpectedString, got, tc.Case) + if len(tc.ExpectedError) > 0 { + assert.EqualError(t, err, tc.ExpectedError, tc.Case) + } else { + assert.NoError(t, err, tc.Case) + } + } +} diff --git a/src/segments/git.go b/src/segments/git.go new file mode 100644 index 000000000000..13939bdba78d --- /dev/null +++ b/src/segments/git.go @@ -0,0 +1,1172 @@ +package segments + +import ( + "fmt" + url2 "net/url" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "gopkg.in/ini.v1" +) + +type Commit struct { + Timestamp time.Time + Author *User + Committer *User + Refs *Refs + Subject string + Sha string +} + +type Refs struct { + Heads []string + Tags []string + Remotes []string +} + +type User struct { + Name string + Email string +} + +// GitStatus represents part of the status of a git repository +type GitStatus struct { + ScmStatus +} + +func (s *GitStatus) add(code string) { + switch code { + case ".": + return + case "D": + s.Deleted++ + case "A": + s.Added++ + case "?": + s.Untracked++ + case "U", "AA": + s.Unmerged++ + case "M", "R", "C", "m": + s.Modified++ + } +} + +const ( + // FetchStatus fetches the status of the repository + FetchStatus options.Option = "fetch_status" + // FetchPushStatus fetches the push-remote status + FetchPushStatus options.Option = "fetch_push_status" + // IgnoreStatus allows to ignore certain repo's for status information + IgnoreStatus options.Option = "ignore_status" + // FetchUpstreamIcon fetches the upstream icon + FetchUpstreamIcon options.Option = "fetch_upstream_icon" + // FetchBareInfo fetches the bare repo status + FetchBareInfo options.Option = "fetch_bare_info" + // FetchUser fetches the current user for the repo + FetchUser options.Option = "fetch_user" + // UntrackedModes list the optional untracked files mode per repo + UntrackedModes options.Option = "untracked_modes" + // IgnoreSubmodules list the optional ignore-submodules mode per repo + IgnoreSubmodules options.Option = "ignore_submodules" + // MappedBranches allows overriding certain branches with an icon/text + MappedBranches options.Option = "mapped_branches" + // DisableWithJJ disables the git segment when there's a .jj directory in the parent file path + DisableWithJJ options.Option = "disable_with_jj" + + // BranchIcon the icon to use as branch indicator + BranchIcon options.Option = "branch_icon" + // BranchIdenticalIcon the icon to display when the remote and local branch are identical + BranchIdenticalIcon options.Option = "branch_identical_icon" + // BranchAheadIcon the icon to display when the local branch is ahead of the remote + BranchAheadIcon options.Option = "branch_ahead_icon" + // BranchBehindIcon the icon to display when the local branch is behind the remote + BranchBehindIcon options.Option = "branch_behind_icon" + // BranchGoneIcon the icon to use when ther's no remote + BranchGoneIcon options.Option = "branch_gone_icon" + // RebaseIcon shows before the rebase context + RebaseIcon options.Option = "rebase_icon" + // CherryPickIcon shows before the cherry-pick context + CherryPickIcon options.Option = "cherry_pick_icon" + // RevertIcon shows before the revert context + RevertIcon options.Option = "revert_icon" + // CommitIcon shows before the detached context + CommitIcon options.Option = "commit_icon" + // NoCommitsIcon shows when there are no commits in the repo yet + NoCommitsIcon options.Option = "no_commits_icon" + // TagIcon shows before the tag context + TagIcon options.Option = "tag_icon" + // MergeIcon shows before the merge context + MergeIcon options.Option = "merge_icon" + // UpstreamIcons allows to add custom upstream icons + UpstreamIcons options.Option = "upstream_icons" + // GithubIcon shows when upstream is github + GithubIcon options.Option = "github_icon" + // BitbucketIcon shows when upstream is bitbucket + BitbucketIcon options.Option = "bitbucket_icon" + // AzureDevOpsIcon shows when upstream is azure devops + AzureDevOpsIcon options.Option = "azure_devops_icon" + // CodeCommit shows when upstream is aws codecommit + CodeCommit options.Option = "codecommit_icon" + // CodebergIcon shows when upstream is codeberg + CodebergIcon options.Option = "codeberg_icon" + // GitlabIcon shows when upstream is gitlab + GitlabIcon options.Option = "gitlab_icon" + // GitIcon shows when the upstream can't be identified + GitIcon options.Option = "git_icon" + + DETACHED = "(detached)" + BRANCHPREFIX = "ref: refs/heads/" + GITCOMMAND = "git" + + trueStr = "true" + origin = "origin" +) + +type Rebase struct { + HEAD string + Onto string + Current int + Total int +} + +type Git struct { + configErr error + config *ini.File + Working *GitStatus + Staging *GitStatus + commit *Commit + Rebase *Rebase + User *User + ShortHash string + Hash string + BranchStatus string + HEAD string + UpstreamIcon string + UpstreamURL string + Ref string + RawUpstreamURL string + Scm + stashCount int + Ahead int + PushAhead int + PushBehind int + Behind int + worktreeCount int + configOnce sync.Once + IsWorkTree bool + Merge bool + CherryPick bool + Revert bool + poshgit bool + Detached bool + IsBare bool + UpstreamGone bool +} + +func (g *Git) Template() string { + return " {{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uF044 {{ .Working.String }}{{ end }}{{ if and (.Staging.Changed) (.Working.Changed) }} |{{ end }}{{ if .Staging.Changed }} \uF046 {{ .Staging.String }}{{ end }} " //nolint: lll +} + +func (g *Git) Enabled() bool { + g.User = &User{} + g.Working = &GitStatus{} + g.Staging = &GitStatus{} + + if !g.shouldDisplay() { + return false + } + + fetchUser := g.options.Bool(FetchUser, false) + g.RepoName = g.repoName() + + if g.IsBare { + if fetchUser { + g.setUser() + } + + g.getBareRepoInfo() + return true + } + + source := g.options.String(Source, Cli) + if source == Pwsh && g.hasPoshGitStatus() { + return true + } + + displayStatus := g.options.Bool(FetchStatus, false) + if displayStatus && g.shouldIgnoreStatus() { + displayStatus = false + } + + // Phase 1: setUser is independent, run it alongside setStatus + var wg sync.WaitGroup + + if fetchUser { + wg.Go(g.setUser) + } + + if displayStatus { + g.setStatus() + + // Phase 2: fan out work that depends only on setStatus results + wg.Go(g.setHEADStatus) + wg.Go(g.setPushStatus) + + if g.options.Bool(FetchUpstreamIcon, false) { + wg.Go(func() { + g.UpstreamIcon = g.getUpstreamIcon() + }) + } + + g.setBranchStatus() + } else { + g.updateHEADReference() + + if g.options.Bool(FetchUpstreamIcon, false) { + g.UpstreamIcon = g.getUpstreamIcon() + } + } + + wg.Wait() + return true +} + +func (g *Git) CacheKey() (string, bool) { + dir, err := g.env.HasParentFilePath(".git", true) + if err != nil { + return "", false + } + + if !g.isRepo(dir) { + return "", false + } + + ref := g.fileContent(g.mainSCMDir, "HEAD") + ref = strings.Replace(ref, "ref: refs/heads/", "", 1) + + // Use the repo clone in the cache key so the mapped path is consistent + // for primary and worktree repos. + return fmt.Sprintf("%s@%s", dir.Path, ref), true +} + +func (g *Git) Commit() *Commit { + if g.commit != nil { + return g.commit + } + + g.commit = &Commit{ + Author: &User{}, + Committer: &User{}, + Refs: &Refs{}, + } + + commitBody := g.getGitCommandOutput("log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s%nha:%H%nrf:%D", "--decorate=full") + splitted := strings.SplitSeq(strings.TrimSpace(commitBody), "\n") + for line := range splitted { + line = strings.TrimSpace(line) + if len(line) <= 3 { + continue + } + anchor := line[:3] + line = line[3:] + switch anchor { + case "an:": + g.commit.Author.Name = line + case "ae:": + g.commit.Author.Email = line + case "cn:": + g.commit.Committer.Name = line + case "ce:": + g.commit.Committer.Email = line + case "at:": + if t, err := strconv.ParseInt(line, 10, 64); err == nil { + g.commit.Timestamp = time.Unix(t, 0) + } + case "su:": + g.commit.Subject = line + case "ha:": + g.commit.Sha = line + case "rf:": + refs := strings.SplitSeq(line, ", ") + for ref := range refs { + ref = strings.TrimSpace(ref) + switch { + case strings.HasSuffix(ref, "HEAD"): + continue + case strings.HasPrefix(ref, "tag: refs/tags/"): + g.commit.Refs.Tags = append(g.commit.Refs.Tags, strings.TrimPrefix(ref, "tag: refs/tags/")) + case strings.HasPrefix(ref, "refs/remotes/"): + g.commit.Refs.Remotes = append(g.commit.Refs.Remotes, strings.TrimPrefix(ref, "refs/remotes/")) + case strings.HasPrefix(ref, "HEAD -> refs/heads/"): + g.commit.Refs.Heads = append(g.commit.Refs.Heads, strings.TrimPrefix(ref, "HEAD -> refs/heads/")) + case strings.HasPrefix(ref, "refs/heads/"): + g.commit.Refs.Heads = append(g.commit.Refs.Heads, strings.TrimPrefix(ref, "refs/heads/")) + default: + g.commit.Refs.Heads = append(g.commit.Refs.Heads, ref) + } + } + } + } + return g.commit +} + +func (g *Git) StashCount() int { + if g.poshgit || g.stashCount != 0 { + return g.stashCount + } + + stashContent := g.fileContent(g.scmDir, "logs/refs/stash") + if stashContent == "" { + return 0 + } + + g.stashCount = strings.Count(stashContent, "\n") + 1 // +1: fileContent() trims + return g.stashCount +} + +func (g *Git) Kraken() string { + root := g.getGitCommandOutput("rev-list", "--max-parents=0", "HEAD") + root, _, _ = strings.Cut(root, "\n") + + if g.RawUpstreamURL == "" { + if g.Upstream == "" { + g.Upstream = origin + } + g.RawUpstreamURL = g.getRemoteURL() + } + + if g.Hash == "" { + g.Hash = g.getGitCommandOutput("rev-parse", "HEAD") + } + + return fmt.Sprintf("gitkraken://repolink/%s/commit/%s?url=%s", root, g.Hash, url2.QueryEscape(g.RawUpstreamURL)) +} + +func (g *Git) LatestTag() string { + return g.getGitCommandOutput("describe", "--tags", "--abbrev=0") +} + +func (g *Git) shouldDisplay() bool { + // Check if disable_with_jj is enabled and .jj directory exists + if g.options.Bool(DisableWithJJ, false) { + if _, err := g.env.HasParentFilePath(".jj", false); err == nil { + return false + } + } + + gitdir, err := g.env.HasParentFilePath(".git", true) + if err != nil { + return false + } + + if g.options.Bool(FetchBareInfo, false) { + g.IsBare = g.isBareRepo(gitdir) + } + + if !g.hasCommand(GITCOMMAND) { + return false + } + + return g.isRepo(gitdir) +} + +func (g *Git) isRepo(gitdir *runtime.FileInfo) bool { + g.setDir(gitdir.Path) + + if !gitdir.IsDir { + if g.hasWorktree(gitdir) { + g.repoRootDir = g.convertToWindowsPath(g.repoRootDir) + return true + } + + return false + } + + g.mainSCMDir = gitdir.Path + g.scmDir = gitdir.Path + // convert the worktree file path to a windows one when in a WSL shared folder + g.repoRootDir = strings.TrimSuffix(g.convertToWindowsPath(gitdir.Path), "/.git") + return true +} + +func (g *Git) setUser() { + output := g.getGitCommandOutput("config", "--get-regexp", "^user\\.") + for line := range strings.SplitSeq(output, "\n") { + key, val, ok := strings.Cut(line, " ") + if !ok { + continue + } + + switch key { + case "user.name": + g.User.Name = val + case "user.email": + g.User.Email = val + } + } +} + +func (g *Git) isBareRepo(gitDir *runtime.FileInfo) bool { + defer log.Trace(time.Now()) + + if gitDir.IsDir { + g.mainSCMDir = gitDir.Path + } else { + content := g.fileContent(gitDir.ParentFolder, ".git") + dir := strings.TrimPrefix(content, "gitdir: ") + g.mainSCMDir = filepath.Join(gitDir.ParentFolder, dir) + } + + cfg, err := g.getGitConfig() + if err != nil { + log.Error(err) + return false + } + + coreSection := cfg.Section("core") + if coreSection == nil { + log.Debug("Git core section not found, not a bare repo") + return false + } + + bare := coreSection.Key("bare").String() + + return bare == trueStr +} + +func (g *Git) getBareRepoInfo() { + head := g.fileContent(g.mainSCMDir, "HEAD") + branchIcon := g.options.String(BranchIcon, "\uE0A0") + g.Ref = strings.Replace(head, "ref: refs/heads/", "", 1) + g.HEAD = fmt.Sprintf("%s%s", branchIcon, g.formatBranch(g.Ref)) + if !g.options.Bool(FetchUpstreamIcon, false) { + return + } + + g.Upstream = g.getGitCommandOutput("remote") + if len(g.Upstream) != 0 { + g.UpstreamIcon = g.getUpstreamIcon() + } +} + +func (g *Git) setDir(dir string) { + dir = path.ReplaceHomeDirPrefixWithTilde(dir) // align with template PWD + if g.env.GOOS() == runtime.WINDOWS { + g.Dir = strings.TrimSuffix(dir, `\.git`) + return + } + + g.Dir = strings.TrimSuffix(dir, "/.git") +} + +func (g *Git) hasWorktree(gitdir *runtime.FileInfo) bool { + g.scmDir = gitdir.Path + content := g.env.FileContent(gitdir.Path) + content = strings.Trim(content, " \r\n") + matches := regex.FindNamedRegexMatch(`^gitdir: (?P.*)$`, content) + + if len(matches) == 0 { + log.Debug("no matches found, directory isn't a worktree") + return false + } + + // if we open a worktree file in a WSL shared folder, we have to convert it back + // to the mounted path + g.mainSCMDir = g.convertToLinuxPath(matches["dir"]) + + // in worktrees, the path looks like this: gitdir: path/.git/worktrees/branch + // scmDir needs to become path/.git + // repoRootDir needs to become path + worktreeIndex := strings.LastIndex(g.mainSCMDir, "/worktrees/") + + // in submodules, the path looks like this: gitdir: ../.git/modules/test-submodule + // we need the parent folder to detect where the real .git folder is + if strings.Contains(g.mainSCMDir, "/modules/") { + g.scmDir = resolveGitPath(gitdir.ParentFolder, g.mainSCMDir) + // this might be both a worktree and a submodule, where the path would look like + // this: path/.git/modules/module/path/worktrees/location. We cannot distinguish + // between worktree and a module path containing the word 'worktree,' however. + worktreeIndex = strings.LastIndex(g.scmDir, "/worktrees/") + if worktreeIndex > -1 && g.env.HasFilesInDir(g.scmDir, "gitdir") { + gitDir := filepath.Join(g.scmDir, "gitdir") + realGitFolder := g.env.FileContent(gitDir) + g.repoRootDir = strings.TrimSuffix(realGitFolder, ".git\n") + // resolve relative paths (worktree.useRelativePaths = true) + g.repoRootDir = resolveGitPath(g.scmDir, g.repoRootDir) + g.scmDir = g.scmDir[:worktreeIndex] + g.mainSCMDir = g.scmDir + g.IsWorkTree = true + return true + } + + g.repoRootDir = g.scmDir + g.mainSCMDir = g.scmDir + return true + } + + // convert to absolute path for worktrees only + if strings.HasPrefix(g.mainSCMDir, "..") { + g.mainSCMDir = filepath.Join(gitdir.ParentFolder, g.mainSCMDir) + } + + if worktreeIndex > -1 { + gitDir := filepath.Join(g.mainSCMDir, "gitdir") + g.scmDir = g.mainSCMDir[:worktreeIndex] + gitDirContent := g.env.FileContent(gitDir) + g.repoRootDir = strings.TrimSuffix(gitDirContent, ".git\n") + // resolve relative paths (worktree.useRelativePaths = true) + g.repoRootDir = resolveGitPath(g.mainSCMDir, g.repoRootDir) + g.IsWorkTree = true + return true + } + + // check for separate git folder(--separate-git-dir) + // check if the folder contains a HEAD file + if g.env.HasFilesInDir(g.mainSCMDir, "HEAD") { + gitFolder := strings.TrimSuffix(g.scmDir, ".git") + g.scmDir = g.mainSCMDir + g.mainSCMDir = gitFolder + g.repoRootDir = gitFolder + return true + } + + return false +} + +func (g *Git) shouldIgnoreStatus() bool { + list := g.options.StringArray(IgnoreStatus, []string{}) + return g.env.DirMatchesOneOf(g.repoRootDir, list) +} + +func (g *Git) setBranchStatus() { + getBranchStatus := func() string { + if g.Ahead > 0 && g.Behind > 0 { + return fmt.Sprintf("%s%d %s%d", g.options.String(BranchAheadIcon, "\u2191"), g.Ahead, g.options.String(BranchBehindIcon, "\u2193"), g.Behind) + } + if g.Ahead > 0 { + return fmt.Sprintf("%s%d", g.options.String(BranchAheadIcon, "\u2191"), g.Ahead) + } + if g.Behind > 0 { + return fmt.Sprintf("%s%d", g.options.String(BranchBehindIcon, "\u2193"), g.Behind) + } + if g.UpstreamGone { + return g.options.String(BranchGoneIcon, "\u2262") + } + if g.Behind == 0 && g.Ahead == 0 && g.Upstream != "" { + return g.options.String(BranchIdenticalIcon, "\u2261") + } + return "" + } + g.BranchStatus = getBranchStatus() +} + +func (g *Git) setPushStatus() { + if !g.options.Bool(FetchPushStatus, false) { + return + } + + if g.Ref == "" || g.Ref == DETACHED { + return + } + + pushRemote := g.getPushRemote() + if pushRemote == "" { + return + } + + var wg sync.WaitGroup + + wg.Go(func() { + if v := g.getGitCommandOutput("rev-list", "--count", pushRemote+"..HEAD"); v != "" { + g.PushAhead, _ = strconv.Atoi(strings.TrimSpace(v)) + } + }) + + wg.Go(func() { + if v := g.getGitCommandOutput("rev-list", "--count", "HEAD.."+pushRemote); v != "" { + g.PushBehind, _ = strconv.Atoi(strings.TrimSpace(v)) + } + }) + + wg.Wait() +} + +func (g *Git) getPushRemote() string { + upstream := g.Upstream + if idx := strings.Index(upstream, "/"); idx != -1 { + upstream = upstream[:idx] + } + + if upstream == "" { + upstream = origin + } + + branch := g.Ref + if branch == "" { + return "" + } + + cfg, err := g.getGitConfig() + if err != nil { + pushRemote := g.getGitCommandOutput("config", "--get", "remote.pushDefault") + if pushRemote == "" { + pushRemote = upstream + } + + return strings.TrimSpace(pushRemote) + "/" + branch + } + + sectionName := fmt.Sprintf(`branch "%s"`, branch) + section := cfg.Section(sectionName) + pushRemote := section.Key("pushRemote").String() + if pushRemote == "" { + pushRemote = cfg.Section("remote").Key("pushDefault").String() + } + + if pushRemote == "" { + pushRemote = upstream + } + + return pushRemote + "/" + branch +} + +func (g *Git) getGitConfig() (*ini.File, error) { + g.configOnce.Do(func() { + configData := g.fileContent(g.mainSCMDir, "config") + if configData == "" { + log.Debug("git config file not found") + g.configErr = fmt.Errorf("git config file not found") + return + } + + // ini.Load expects []byte to parse content, not a file path + cfg, err := ini.Load([]byte(configData)) + if err != nil { + g.configErr = err + return + } + + g.config = cfg + }) + + return g.config, g.configErr +} + +func (g *Git) cleanUpstreamURL(url string) string { + // Azure DevOps + if strings.Contains(url, "dev.azure.com") { + match := regex.FindNamedRegexMatch(`^.*@(ssh.)?dev\.azure\.com(:v3)?/(?P[A-Za-z0-9_-]+)/(?P[A-Za-z0-9_-]+)/(_git/)?(?P[A-Za-z0-9_-]+)$`, url) + if len(match) == 4 { + return fmt.Sprintf("https://dev.azure.com/%s/%s/_git/%s", match["ORGANIZATION"], match["PROJECT"], match["REPOSITORY"]) + } + } + + if strings.HasPrefix(url, "http") { + return url + } + + // /path/to/repo.git/ + match := regex.FindNamedRegexMatch(`^(?P[a-z0-9./]+)$`, url) + if len(match) != 0 { + url := strings.Trim(match["URL"], "/") + url = strings.TrimSuffix(url, ".git") + return fmt.Sprintf("https://%s", strings.TrimPrefix(url, "/")) + } + + // ssh://user@host.xz:1234/path/to/repo.git/ + match = regex.FindNamedRegexMatch(`(ssh|ftp|git|rsync)://(.*@)?(?P[a-z0-9.-]+)(:[0-9]{1,5})?/(?P.*).git`, url) + if len(match) == 0 { + // host.xz:/path/to/repo.git/ + match = regex.FindNamedRegexMatch(`^(?P[a-z0-9.-]+):(?P[\w.\-~/@]+)$`, url) + } + + if len(match) != 0 { + repoPath := strings.Trim(match["PATH"], "/") + repoPath = strings.TrimSuffix(repoPath, ".git") + return fmt.Sprintf("https://%s/%s", match["URL"], repoPath) + } + + // codecommit::region-identifier-id://repo-name + match = regex.FindNamedRegexMatch(`codecommit::(?P[a-z0-9-]+)://(?P[\w\.@\:/\-~]+)`, url) + if len(match) != 0 { + return fmt.Sprintf("https://%s.console.aws.amazon.com/codesuite/codecommit/repositories/%s/browse?region=%s", match["URL"], match["PATH"], match["URL"]) + } + + // user@host.xz:/path/to/repo.git + match = regex.FindNamedRegexMatch(`.*@(?P.*):(?P.*)`, url) + if len(match) == 0 { + return "" + } + + return fmt.Sprintf("https://%s/%s", match["URL"], strings.TrimSuffix(match["PATH"], ".git")) +} + +func (g *Git) getUpstreamIcon() string { + fallback := g.options.String(GitIcon, "\uE5FB ") + + g.RawUpstreamURL = g.getRemoteURL() + if g.RawUpstreamURL == "" { + return fallback + } + + g.UpstreamURL = g.cleanUpstreamURL(g.RawUpstreamURL) + + // allow overrides first + custom := g.options.KeyValueMap(UpstreamIcons, map[string]string{}) + for key, value := range custom { + if strings.Contains(g.UpstreamURL, key) { + return value + } + } + + defaults := map[string]struct { + Icon options.Option + Default string + }{ + "github": {GithubIcon, "\uF408"}, + "gitlab": {GitlabIcon, "\uF296"}, + "bitbucket": {BitbucketIcon, "\uF171"}, + "dev.azure.com": {AzureDevOpsIcon, "\uEBE8"}, + "visualstudio.com": {AzureDevOpsIcon, "\uEBE8"}, + "codecommit": {CodeCommit, "\uF270"}, + "codeberg": {CodebergIcon, "\uF330"}, + } + + for key, value := range defaults { + if strings.Contains(g.UpstreamURL, key) { + return g.options.String(value.Icon, value.Default) + } + } + + return fallback +} + +func (g *Git) setStatus() { + addToStatus := func(status string) { + const UNTRACKED = "?" + if strings.HasPrefix(status, UNTRACKED) { + g.Working.add(UNTRACKED) + return + } + if len(status) <= 4 { + return + } + + // map conflicts separately when in a merge or rebase + if g.Rebase != nil || g.Merge { + conflict := "AA" + full := status[2:4] + if full == conflict { + g.Staging.add(conflict) + return + } + } + + workingCode := status[3:4] + stagingCode := status[2:3] + g.Working.add(workingCode) + g.Staging.add(stagingCode) + } + + const ( + HASH = "# branch.oid " + REF = "# branch.head " + UPSTREAM = "# branch.upstream " + BRANCHSTATUS = "# branch.ab " + ) + + // firstly assume that upstream is gone + g.UpstreamGone = true + statusFormats := g.options.KeyValueMap(StatusFormats, map[string]string{}) + + g.Working = &GitStatus{ScmStatus: ScmStatus{Formats: statusFormats}} + g.Staging = &GitStatus{ScmStatus: ScmStatus{Formats: statusFormats}} + + untrackedMode := g.getUntrackedFilesMode() + args := []string{"status", untrackedMode, "--branch", "--porcelain=2"} + ignoreSubmodulesMode := g.getIgnoreSubmodulesMode() + if len(ignoreSubmodulesMode) > 0 { + args = append(args, ignoreSubmodulesMode) + } + + output := g.getGitCommandOutput(args...) + for line := range strings.SplitSeq(output, "\n") { + if strings.HasPrefix(line, HASH) && len(line) >= len(HASH)+7 { + g.ShortHash = line[len(HASH) : len(HASH)+7] + g.Hash = line[len(HASH):] + continue + } + + if strings.HasPrefix(line, REF) && len(line) > len(REF) { + g.Ref = line[len(REF):] + continue + } + + if strings.HasPrefix(line, UPSTREAM) && len(line) > len(UPSTREAM) { + // status reports upstream, but upstream may be gone (must check BRANCHSTATUS) + g.Upstream = line[len(UPSTREAM):] + g.UpstreamGone = true + continue + } + + if strings.HasPrefix(line, BRANCHSTATUS) && len(line) > len(BRANCHSTATUS) { + status := line[len(BRANCHSTATUS):] + splitted := strings.SplitN(status, " ", 3) + if len(splitted) >= 2 { + g.Ahead, _ = strconv.Atoi(splitted[0]) + behind, _ := strconv.Atoi(splitted[1]) + g.Behind = -behind + } + // confirmed: upstream exists + g.UpstreamGone = false + continue + } + + addToStatus(line) + } +} + +func (g *Git) getGitCommandOutput(args ...string) string { + if g.command == "" { + return "" + } + + args = append([]string{"-C", g.repoRootDir, "--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...) + val, err := g.env.RunCommand(g.command, args...) + if err != nil { + return "" + } + + return val +} + +func (g *Git) setHEADStatus() { + branchIcon := g.options.String(BranchIcon, "\uE0A0") + if g.Ref == DETACHED { + g.Detached = true + g.resolveDetachedHEAD() + } else { + head := g.formatBranch(g.Ref) + g.HEAD = fmt.Sprintf("%s%s", branchIcon, head) + } + + formatDetached := func() string { + if g.Detached { + return fmt.Sprintf("%sdetached at %s", branchIcon, g.HEAD) + } + return g.HEAD + } + + getPrettyNameOrigin := func(file string) string { + var origin string + head := g.fileContent(g.mainSCMDir, file) + if head == "detached HEAD" { + origin = formatDetached() + } else { + head = strings.Replace(head, "refs/heads/", "", 1) + origin = branchIcon + g.formatBranch(head) + } + return origin + } + + parseInt := func(file string) int { + val, _ := strconv.Atoi(g.fileContent(g.mainSCMDir, file)) + return val + } + + if g.env.HasFolder(g.mainSCMDir + "/rebase-merge") { + head := getPrettyNameOrigin("rebase-merge/head-name") + onto := g.getGitRefFileSymbolicName("rebase-merge/onto") + onto = g.formatBranch(onto) + current := parseInt("rebase-merge/msgnum") + total := parseInt("rebase-merge/end") + icon := g.options.String(RebaseIcon, "\uE728 ") + + g.Rebase = &Rebase{ + HEAD: head, + Onto: onto, + Current: current, + Total: total, + } + + g.HEAD = fmt.Sprintf("%s%s onto %s%s (%d/%d) at %s", icon, head, branchIcon, onto, current, total, g.HEAD) + return + } + + if g.env.HasFolder(g.mainSCMDir + "/rebase-apply") { + head := getPrettyNameOrigin("rebase-apply/head-name") + current := parseInt("rebase-apply/next") + total := parseInt("rebase-apply/last") + icon := g.options.String(RebaseIcon, "\uE728 ") + + g.Rebase = &Rebase{ + HEAD: head, + Current: current, + Total: total, + } + + g.HEAD = fmt.Sprintf("%s%s (%d/%d) at %s", icon, head, current, total, g.HEAD) + return + } + + // merge + commitIcon := g.options.String(CommitIcon, "\uF417") + + if g.hasGitFile("MERGE_MSG") { + g.Merge = true + icon := g.options.String(MergeIcon, "\uE727 ") + mergeContext := g.fileContent(g.mainSCMDir, "MERGE_MSG") + matches := regex.FindNamedRegexMatch(`Merge (remote-tracking )?(?Pbranch|commit|tag) '(?P.*)'`, mergeContext) + // head := g.getGitRefFileSymbolicName("ORIG_HEAD") + if matches != nil && matches["theirs"] != "" { + var headIcon, theirs string + switch matches["type"] { + case "tag": + headIcon = g.options.String(TagIcon, "\uF412") + theirs = matches["theirs"] + case "commit": + headIcon = commitIcon + theirs = g.formatSHA(matches["theirs"]) + default: + headIcon = branchIcon + theirs = g.formatBranch(matches["theirs"]) + } + g.HEAD = fmt.Sprintf("%s%s%s into %s", icon, headIcon, theirs, formatDetached()) + return + } + } + + // sequencer status + // see if a cherry-pick or revert is in progress, if the user has committed a + // conflict resolution with 'git commit' in the middle of a sequence of picks or + // reverts then CHERRY_PICK_HEAD/REVERT_HEAD will not exist so we have to read + // the todo file. + if g.hasGitFile("CHERRY_PICK_HEAD") { + g.CherryPick = true + sha := g.fileContent(g.mainSCMDir, "CHERRY_PICK_HEAD") + cherry := g.options.String(CherryPickIcon, "\uE29B ") + g.HEAD = fmt.Sprintf("%s%s%s onto %s", cherry, commitIcon, g.formatSHA(sha), formatDetached()) + return + } + + if g.hasGitFile("REVERT_HEAD") { + g.Revert = true + sha := g.fileContent(g.mainSCMDir, "REVERT_HEAD") + revert := g.options.String(RevertIcon, "\uF0E2 ") + g.HEAD = fmt.Sprintf("%s%s%s onto %s", revert, commitIcon, g.formatSHA(sha), formatDetached()) + return + } + + if g.hasGitFile("sequencer/todo") { + todo := g.fileContent(g.mainSCMDir, "sequencer/todo") + matches := regex.FindNamedRegexMatch(`^(?Pp|pick|revert)\s+(?P\S+)`, todo) + if matches != nil && matches["sha"] != "" { + action := matches["action"] + sha := matches["sha"] + switch action { + case "p", "pick": + g.CherryPick = true + cherry := g.options.String(CherryPickIcon, "\uE29B ") + g.HEAD = fmt.Sprintf("%s%s%s onto %s", cherry, commitIcon, g.formatSHA(sha), formatDetached()) + return + case "revert": + g.Revert = true + revert := g.options.String(RevertIcon, "\uF0E2 ") + g.HEAD = fmt.Sprintf("%s%s%s onto %s", revert, commitIcon, g.formatSHA(sha), formatDetached()) + return + } + } + } + + g.HEAD = formatDetached() +} + +func (g *Git) formatSHA(sha string) string { + if len(sha) <= 7 { + return sha + } + return sha[0:7] +} + +func (g *Git) hasGitFile(file string) bool { + return g.env.HasFilesInDir(g.mainSCMDir, file) +} + +func (g *Git) getGitRefFileSymbolicName(refFile string) string { + ref := g.fileContent(g.mainSCMDir, refFile) + return g.getGitCommandOutput("name-rev", "--name-only", "--exclude=tags/*", ref) +} + +func (g *Git) updateHEADReference() { + HEADRef := g.fileContent(g.mainSCMDir, "HEAD") + log.Debug("HEADRef:", HEADRef) + + // check if we are in a repo using reftables + if HEADRef == "ref: refs/heads/.invalid" { + log.Debug("repo is using reftables") + + HEADRef = g.getGitCommandOutput("rev-parse", "--symbolic-full-name", "HEAD") + + // this is a detached head + if strings.HasPrefix(HEADRef, "fatal:") { + log.Debug("detached HEAD detected") + g.Detached = true + g.resolveDetachedHEAD() + return + } + + if strings.HasPrefix(HEADRef, "refs/heads/") { + HEADRef = "ref: " + HEADRef + } + + log.Debug("resolved HEADRef:", HEADRef) + } + + g.Detached = !strings.HasPrefix(HEADRef, "ref:") + if branchName, ok := strings.CutPrefix(HEADRef, BRANCHPREFIX); ok { + log.Debug("current HEAD is a branch:", branchName) + + g.Ref = branchName + g.HEAD = fmt.Sprintf("%s%s", g.options.String(BranchIcon, "\uE0A0"), g.formatBranch(branchName)) + + return + } + + g.resolveDetachedHEAD() +} + +func (g *Git) resolveDetachedHEAD() { + HEADRef := g.getGitCommandOutput("rev-parse", "HEAD") + + if len(HEADRef) >= 7 { + g.ShortHash = HEADRef[0:7] + g.Hash = HEADRef[0:] + } + g.Ref = g.ShortHash + + // check for tag + tagName := g.getGitCommandOutput("describe", "--tags", "--exact-match") + if len(tagName) > 0 { + g.Ref = tagName + g.HEAD = fmt.Sprintf("%s%s", g.options.String(TagIcon, "\uF412"), tagName) + return + } + + // fallback to no commits found + if g.ShortHash == "" { + g.HEAD = g.options.String(NoCommitsIcon, "\uF594 ") + return + } + + g.HEAD = fmt.Sprintf("%s%s", g.options.String(CommitIcon, "\uF417"), g.ShortHash) +} + +func (g *Git) WorktreeCount() int { + if g.worktreeCount > 0 { + return g.worktreeCount + } + + worktreesFolder := filepath.Join(g.mainSCMDir, "worktrees") + + if !g.env.HasFolder(worktreesFolder) { + return 0 + } + + worktreeFolders := g.env.LsDir(worktreesFolder) + var count int + for _, folder := range worktreeFolders { + if folder.IsDir() { + count++ + } + } + + return count +} + +func (g *Git) getRemoteURL() string { + upstream := regex.ReplaceAllString("/.*", g.Upstream, "") + if upstream == "" { + upstream = origin + } + + cfg, err := g.getGitConfig() + if err != nil { + return g.getGitCommandOutput("remote", "get-url", upstream) + } + + url := cfg.Section("remote \"" + upstream + "\"").Key("url").String() + if len(url) != 0 { + log.Debug("remote url found in config:", url) + return url + } + + return g.getGitCommandOutput("remote", "get-url", upstream) +} + +func (g *Git) Remotes() map[string]string { + var remotes = make(map[string]string) + + cfg, err := g.getGitConfig() + if err != nil { + return remotes + } + + for _, section := range cfg.Sections() { + if !strings.HasPrefix(section.Name(), "remote ") { + continue + } + + name := strings.TrimPrefix(section.Name(), "remote ") + name = strings.Trim(name, "\"") + url := section.Key("url").String() + url = g.cleanUpstreamURL(url) + remotes[name] = url + } + return remotes +} + +func (g *Git) getUntrackedFilesMode() string { + return g.getSwitchMode(UntrackedModes, "-u", "normal") +} + +func (g *Git) getIgnoreSubmodulesMode() string { + return g.getSwitchMode(IgnoreSubmodules, "--ignore-submodules=", "") +} + +func (g *Git) getSwitchMode(property options.Option, gitSwitch, mode string) string { + repoModes := g.options.KeyValueMap(property, map[string]string{}) + // make use of a wildcard for all repo's + if val := repoModes["*"]; len(val) != 0 { + mode = val + } + // get the specific repo mode + if val := repoModes[g.repoRootDir]; len(val) != 0 { + mode = val + } + if mode == "" { + return "" + } + return fmt.Sprintf("%s%s", gitSwitch, mode) +} + +func (g *Git) repoName() string { + if !g.IsWorkTree { + return path.Base(g.convertToLinuxPath(g.repoRootDir)) + } + + ind := strings.LastIndex(g.mainSCMDir, ".git/worktrees") + if ind > -1 { + return path.Base(g.mainSCMDir[:ind]) + } + + return "" +} diff --git a/src/segments/git_test.go b/src/segments/git_test.go new file mode 100644 index 000000000000..bb5a399e68bd --- /dev/null +++ b/src/segments/git_test.go @@ -0,0 +1,1459 @@ +package segments + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "gopkg.in/ini.v1" + + "github.com/stretchr/testify/assert" + testify_ "github.com/stretchr/testify/mock" +) + +const ( + branchName = "main" + dotGit = "dev/.git" + dotGitSubmodule = "dev/.git/modules/submodule" +) + +func TestEnabledGitNotFound(t *testing.T) { + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasParentFilePath", ".git", true).Return((*runtime.FileInfo)(nil), errors.New("no .git found (mock)")) + env.On("GOOS").Return("") + env.On("IsWsl").Return(false) + + g := &Git{} + g.Init(options.Map{}, env) + + assert.False(t, g.Enabled()) +} + +func TestEnabledInWorkingDirectory(t *testing.T) { + fileInfo := &runtime.FileInfo{ + Path: "/dir/hello", + ParentFolder: "/dir", + IsDir: true, + } + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasCommand", "git").Return(true) + env.On("GOOS").Return("") + env.On("FileContent", "/dir/hello/HEAD").Return("") + env.MockGitCommand(fileInfo.Path, "1234567890abcdef1234567890abcdef12345678", "rev-parse", "HEAD") + env.MockGitCommand(fileInfo.Path, "", "describe", "--tags", "--exact-match") + env.On("IsWsl").Return(false) + env.On("HasParentFilePath", ".git", true).Return(fileInfo, nil) + env.On("PathSeparator").Return("/") + env.On("Home").Return(poshHome) + env.On("Getenv", poshGitEnv).Return("") + env.On("DirMatchesOneOf", testify_.Anything, testify_.Anything).Return(false) + + g := &Git{} + g.Init(options.Map{}, env) + + assert.True(t, g.Enabled()) + assert.Equal(t, fileInfo.Path, g.mainSCMDir) +} + +func TestResolveEmptyGitPath(t *testing.T) { + base := "base" + assert.Equal(t, base, resolveGitPath(base, "")) +} + +func TestEnabledInWorktree(t *testing.T) { + cases := []struct { + Case string + WorkingFolder string + WorkingFolderAddon string + WorkingFolderContent string + ExpectedRealFolder string + ExpectedWorkingFolder string + ExpectedRootFolder string + ExpectedEnabled bool + }{ + { + Case: "worktree", + ExpectedEnabled: true, + WorkingFolder: TestRootPath + "dev/.git/worktrees/folder_worktree", + WorkingFolderAddon: "gitdir", + WorkingFolderContent: TestRootPath + "dev/worktree.git\n", + ExpectedWorkingFolder: TestRootPath + "dev/.git/worktrees/folder_worktree", + ExpectedRealFolder: TestRootPath + "dev/worktree", + ExpectedRootFolder: TestRootPath + dotGit, + }, + { + Case: "submodule", + ExpectedEnabled: true, + WorkingFolder: "./.git/modules/submodule", + ExpectedWorkingFolder: TestRootPath + dotGitSubmodule, + ExpectedRealFolder: TestRootPath + dotGitSubmodule, + ExpectedRootFolder: TestRootPath + dotGitSubmodule, + }, + { + Case: "submodule with root working folder", + ExpectedEnabled: true, + WorkingFolder: TestRootPath + dotGitSubmodule, + ExpectedWorkingFolder: TestRootPath + dotGitSubmodule, + ExpectedRealFolder: TestRootPath + dotGitSubmodule, + ExpectedRootFolder: TestRootPath + dotGitSubmodule, + }, + { + Case: "submodule with worktrees", + ExpectedEnabled: true, + WorkingFolder: TestRootPath + "dev/.git/modules/module/path/worktrees/location", + WorkingFolderAddon: "gitdir", + WorkingFolderContent: TestRootPath + "dev/worktree.git\n", + ExpectedWorkingFolder: TestRootPath + "dev/.git/modules/module/path", + ExpectedRealFolder: TestRootPath + "dev/worktree", + ExpectedRootFolder: TestRootPath + "dev/.git/modules/module/path", + }, + { + Case: "separate git dir", + ExpectedEnabled: true, + WorkingFolder: TestRootPath + "dev/separate/.git/posh", + ExpectedWorkingFolder: TestRootPath + "dev/", + ExpectedRealFolder: TestRootPath + "dev/", + ExpectedRootFolder: TestRootPath + "dev/separate/.git/posh", + }, + { + Case: "worktree with relative gitdir path", + ExpectedEnabled: true, + WorkingFolder: TestRootPath + "dev/.git/worktrees/folder_worktree", + WorkingFolderAddon: "gitdir", + WorkingFolderContent: "../../../worktree/.git\n", + ExpectedWorkingFolder: TestRootPath + "dev/.git/worktrees/folder_worktree", + ExpectedRealFolder: TestRootPath + "dev/worktree", + ExpectedRootFolder: TestRootPath + dotGit, + }, + } + fileInfo := &runtime.FileInfo{ + Path: TestRootPath + dotGit, + ParentFolder: TestRootPath + "dev", + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("FileContent", TestRootPath+dotGit).Return(fmt.Sprintf("gitdir: %s", tc.WorkingFolder)) + env.On("FileContent", filepath.Join(tc.WorkingFolder, tc.WorkingFolderAddon)).Return(tc.WorkingFolderContent) + env.On("HasFilesInDir", tc.WorkingFolder, tc.WorkingFolderAddon).Return(true) + env.On("HasFilesInDir", tc.WorkingFolder, "HEAD").Return(true) + env.On("PathSeparator").Return(string(os.PathSeparator)) + + g := &Git{} + g.Init(options.Map{}, env) + + assert.Equal(t, tc.ExpectedEnabled, g.hasWorktree(fileInfo), tc.Case) + assert.Equal(t, tc.ExpectedWorkingFolder, g.mainSCMDir, tc.Case) + assert.Equal(t, tc.ExpectedRealFolder, g.repoRootDir, tc.Case) + assert.Equal(t, tc.ExpectedRootFolder, g.scmDir, tc.Case) + } +} + +func TestEnabledInBareRepo(t *testing.T) { + cases := []struct { + Case string + HEAD string + IsBare bool + }{ + { + Case: "Bare repo on main", + IsBare: true, + HEAD: "ref: refs/heads/main", + }, + { + Case: "Not a bare repo", + HEAD: "ref: refs/heads/main", + IsBare: false, + }, + } + for _, tc := range cases { + path := "git" + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("GOOS").Return("") + env.On("HasCommand", "git").Return(true) + + configData := fmt.Sprintf(`[core] + bare = %s`, strconv.FormatBool(tc.IsBare)) + + env.On("HasParentFilePath", ".git", true).Return(&runtime.FileInfo{IsDir: true, Path: path}, nil) + env.On("FileContent", "git/HEAD").Return(tc.HEAD) + + props := options.Map{ + FetchBareInfo: true, + } + + g := &Git{} + g.Init(props, env) + + g.configOnce = sync.Once{} + g.configOnce.Do(func() { + g.config, g.configErr = ini.Load([]byte(configData)) + }) + + _ = g.Enabled() + + assert.Equal(t, tc.IsBare, g.IsBare, tc.Case) + } +} + +func TestGetGitOutputForCommand(t *testing.T) { + args := []string{"-C", "", "--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"} + commandArgs := []string{"symbolic-ref", "--short", "HEAD"} + want := "je suis le output" + env := new(mock.Environment) + env.On("IsWsl").Return(false) + env.On("RunCommand", "git", append(args, commandArgs...)).Return(want, nil) + env.On("GOOS").Return("unix") + + g := &Git{ + Scm: Scm{ + command: GITCOMMAND, + }, + } + g.Init(options.Map{}, env) + + got := g.getGitCommandOutput(commandArgs...) + assert.Equal(t, want, got) +} + +func TestSetGitHEADContextClean(t *testing.T) { + cases := []struct { + Ours string + Expected string + Ref string + Case string + Total string + Step string + Theirs string + RebaseMerge bool + Sequencer bool + Revert bool + CherryPick bool + Merge bool + RebaseApply bool + }{ + {Case: "detached on commit", Ref: DETACHED, Expected: "branch detached at commit 1234567"}, + {Case: "not detached, clean", Ref: "main", Expected: "branch main"}, + { + Case: "rebase merge", + Ref: DETACHED, + Expected: "rebase branch origin/main onto branch main (1/2) at commit 1234567", + RebaseMerge: true, + Ours: "refs/heads/origin/main", + Theirs: "main", + Step: "1", + Total: "2", + }, + { + Case: "rebase apply", + Ref: DETACHED, + Expected: "rebase branch origin/main (1/2) at commit 1234567", + RebaseApply: true, + Ours: "refs/heads/origin/main", + Step: "1", + Total: "2", + }, + { + Case: "merge branch", + Ref: "main", + Expected: "merge branch feat-1 into branch main", + Merge: true, + Theirs: "branch 'feat-1'", + Ours: "main", + }, + { + Case: "merge commit", + Ref: "main", + Expected: "merge commit 1234567 into branch main", + Merge: true, + Theirs: "commit '123456789101112'", + Ours: "main", + }, + { + Case: "merge tag", + Ref: "main", + Expected: "merge tag 1.2.4 into branch main", + Merge: true, + Theirs: "tag '1.2.4'", + Ours: "main", + }, + { + Case: "cherry pick", + Ref: "main", + Expected: "pick commit 1234567 onto branch main", + CherryPick: true, + Theirs: "123456789101012", + Ours: "main", + }, + { + Case: "revert", + Ref: "main", + Expected: "revert commit 1234567 onto branch main", + Revert: true, + Theirs: "123456789101012", + Ours: "main", + }, + { + Case: "sequencer cherry", + Ref: "main", + Expected: "pick commit 1234567 onto branch main", + Sequencer: true, + Theirs: "pick 123456789101012", + Ours: "main", + }, + { + Case: "sequencer cherry p", + Ref: "main", + Expected: "pick commit 1234567 onto branch main", + Sequencer: true, + Theirs: "p 123456789101012", + Ours: "main", + }, + { + Case: "sequencer revert", + Ref: "main", + Expected: "revert commit 1234567 onto branch main", + Sequencer: true, + Theirs: "revert 123456789101012", + Ours: "main", + }, + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("GOOS").Return("unix") + env.On("IsWsl").Return(false) + env.MockGitCommand("", "1234567890abcdef1234567890abcdef12345678", "rev-parse", "HEAD") + env.MockGitCommand("", "", "describe", "--tags", "--exact-match") + env.MockGitCommand("", tc.Theirs, "name-rev", "--name-only", "--exclude=tags/*", tc.Theirs) + env.MockGitCommand("", tc.Ours, "name-rev", "--name-only", "--exclude=tags/*", tc.Ours) + // rebase merge + env.On("HasFolder", "/rebase-merge").Return(tc.RebaseMerge) + env.On("FileContent", "/rebase-merge/head-name").Return(tc.Ours) + env.On("FileContent", "/rebase-merge/onto").Return(tc.Theirs) + env.On("FileContent", "/rebase-merge/msgnum").Return(tc.Step) + env.On("FileContent", "/rebase-merge/end").Return(tc.Total) + // rebase apply + env.On("HasFolder", "/rebase-apply").Return(tc.RebaseApply) + env.On("FileContent", "/rebase-apply/head-name").Return(tc.Ours) + env.On("FileContent", "/rebase-apply/next").Return(tc.Step) + env.On("FileContent", "/rebase-apply/last").Return(tc.Total) + // merge + env.On("HasFilesInDir", "", "MERGE_MSG").Return(tc.Merge) + env.On("FileContent", "/MERGE_MSG").Return(fmt.Sprintf("Merge %s into %s", tc.Theirs, tc.Ours)) + // cherry pick + env.On("HasFilesInDir", "", "CHERRY_PICK_HEAD").Return(tc.CherryPick) + env.On("FileContent", "/CHERRY_PICK_HEAD").Return(tc.Theirs) + // revert + env.On("HasFilesInDir", "", "REVERT_HEAD").Return(tc.Revert) + env.On("FileContent", "/REVERT_HEAD").Return(tc.Theirs) + // sequencer + env.On("HasFilesInDir", "", "sequencer/todo").Return(tc.Sequencer) + env.On("FileContent", "/sequencer/todo").Return(tc.Theirs) + + props := options.Map{ + BranchIcon: "branch ", + CommitIcon: "commit ", + RebaseIcon: "rebase ", + MergeIcon: "merge ", + CherryPickIcon: "pick ", + TagIcon: "tag ", + RevertIcon: "revert ", + } + + g := &Git{ + Scm: Scm{ + command: GITCOMMAND, + }, + ShortHash: "1234567", + Ref: tc.Ref, + } + g.Init(props, env) + g.mainSCMDir = "" + + g.setHEADStatus() + assert.Equal(t, tc.Expected, g.HEAD, tc.Case) + } +} + +func TestSetPrettyHEADName(t *testing.T) { + cases := []struct { + Case string + Expected string + ShortHash string + Tag string + HEAD string + SymbolicName string + }{ + {Case: "main", Expected: "branch main", HEAD: BRANCHPREFIX + "main"}, + {Case: "no hash", Expected: "commit 1234567", HEAD: "12345678910"}, + {Case: "hash on tag", ShortHash: "132312322321", Expected: "tag tag-1", HEAD: "12345678910", Tag: "tag-1"}, + {Case: "no hash on tag", Expected: "tag tag-1", Tag: "tag-1"}, + {Case: "hash on commit", ShortHash: "1234567", Expected: "commit 1234567"}, + {Case: "no hash on commit", Expected: "commit 1234567", HEAD: "12345678910"}, + {Case: "reftable main branch", Expected: "branch main", HEAD: "ref: refs/heads/.invalid", SymbolicName: "refs/heads/main"}, + {Case: "reftable detached head", Expected: "commit 1234567", HEAD: "ref: refs/heads/.invalid", SymbolicName: "fatal: ref HEAD is not a symbolic ref"}, + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("FileContent", "/HEAD").Return(tc.HEAD) + env.On("GOOS").Return("unix") + env.On("IsWsl").Return(false) + // Mock rev-parse HEAD for detached HEAD cases + headValue := tc.HEAD + if headValue == "" || strings.HasSuffix(tc.HEAD, ".invalid") { + headValue = "12345678910" + } + env.MockGitCommand("", headValue, "rev-parse", "HEAD") + env.MockGitCommand("", tc.Tag, "describe", "--tags", "--exact-match") + env.MockGitCommand("", tc.SymbolicName, "rev-parse", "--symbolic-full-name", "HEAD") + + props := options.Map{ + BranchIcon: "branch ", + CommitIcon: "commit ", + TagIcon: "tag ", + } + + g := &Git{ + Scm: Scm{ + command: GITCOMMAND, + }, + ShortHash: tc.ShortHash, + } + g.Init(props, env) + g.mainSCMDir = "" + + g.updateHEADReference() + assert.Equal(t, tc.Expected, g.HEAD, tc.Case) + } +} + +func TestSetGitStatus(t *testing.T) { + cases := []struct { + ExpectedWorking *GitStatus + ExpectedStaging *GitStatus + Case string + Output string + ExpectedHash string + ExpectedRef string + ExpectedUpstream string + ExpectedAhead int + ExpectedBehind int + ExpectedUpstreamGone bool + Rebase bool + Merge bool + }{ + { + Case: "all different options on working and staging, no remote", + Output: ` + # branch.oid 1234567891011121314 + # branch.head rework-git-status + 1 .R N... + 1 .C N... + 1 .M N... + 1 .m N... + 1 .A N... + 1 .D N... + 1 .A N... + 1 .U N... + 1 A. N... + `, + ExpectedWorking: &GitStatus{ScmStatus: ScmStatus{Modified: 4, Added: 2, Deleted: 1, Unmerged: 1}}, + ExpectedStaging: &GitStatus{ScmStatus: ScmStatus{Added: 1}}, + ExpectedHash: "1234567", + ExpectedRef: "rework-git-status", + ExpectedUpstreamGone: true, + }, + { + Case: "all different options on working and staging, with remote", + Output: ` + # branch.oid 1234567891011121314 + # branch.head rework-git-status + # branch.upstream origin/rework-git-status + # branch.ab +0 -0 + 1 .R N... + 1 .C N... + 1 .M N... + 1 .m N... + 1 .A N... + 1 .D N... + 1 .A N... + 1 .U N... + 1 A. N... + `, + ExpectedWorking: &GitStatus{ScmStatus: ScmStatus{Modified: 4, Added: 2, Deleted: 1, Unmerged: 1}}, + ExpectedStaging: &GitStatus{ScmStatus: ScmStatus{Added: 1}}, + ExpectedUpstream: "origin/rework-git-status", + ExpectedHash: "1234567", + ExpectedRef: "rework-git-status", + }, + { + Case: "remote with equal branch", + Output: ` + # branch.oid 1234567891011121314 + # branch.head rework-git-status + # branch.upstream origin/rework-git-status + # branch.ab +0 -0 + `, + ExpectedUpstream: "origin/rework-git-status", + ExpectedHash: "1234567", + ExpectedRef: "rework-git-status", + }, + { + Case: "remote with branch status", + Output: ` + # branch.oid 1234567891011121314 + # branch.head rework-git-status + # branch.upstream origin/rework-git-status + # branch.ab +2 -1 + `, + ExpectedUpstream: "origin/rework-git-status", + ExpectedHash: "1234567", + ExpectedRef: "rework-git-status", + ExpectedAhead: 2, + ExpectedBehind: 1, + }, + { + Case: "untracked files", + Output: ` + # branch.oid 1234567891011121314 + # branch.head main + # branch.upstream origin/main + # branch.ab +0 -0 + ? q + ? qq + ? qqq + `, + ExpectedUpstream: "origin/main", + ExpectedHash: "1234567", + ExpectedRef: "main", + ExpectedWorking: &GitStatus{ScmStatus: ScmStatus{Untracked: 3}}, + }, + { + Case: "remote branch was deleted", + Output: ` + # branch.oid 1234567891011121314 + # branch.head branch-is-gone + # branch.upstream origin/branch-is-gone + `, + ExpectedUpstream: "origin/branch-is-gone", + ExpectedHash: "1234567", + ExpectedRef: "branch-is-gone", + ExpectedUpstreamGone: true, + }, + { + Case: "rebase with 2 merge conflicts", + Output: ` + # branch.oid 1234567891011121314 + # branch.head rework-git-status + # branch.upstream origin/rework-git-status + # branch.ab +0 -0 + 1 AA N... + 1 AA N... + `, + ExpectedUpstream: "origin/rework-git-status", + ExpectedHash: "1234567", + ExpectedRef: "rework-git-status", + Rebase: true, + ExpectedStaging: &GitStatus{ScmStatus: ScmStatus{Unmerged: 2}}, + }, + { + Case: "merge with 4 merge conflicts", + Output: ` + # branch.oid 1234567891011121314 + # branch.head rework-git-status + # branch.upstream origin/rework-git-status + # branch.ab +0 -0 + 1 AA N... + 1 AA N... + 1 AA N... + 1 AA N... + `, + ExpectedUpstream: "origin/rework-git-status", + ExpectedHash: "1234567", + ExpectedRef: "rework-git-status", + Merge: true, + ExpectedStaging: &GitStatus{ScmStatus: ScmStatus{Unmerged: 4}}, + }, + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("GOOS").Return("unix") + env.On("IsWsl").Return(false) + env.MockGitCommand("", strings.ReplaceAll(tc.Output, "\t", ""), "status", "-unormal", "--branch", "--porcelain=2") + + g := &Git{ + Scm: Scm{ + command: GITCOMMAND, + }, + } + g.Init(options.Map{}, env) + + if tc.ExpectedWorking == nil { + tc.ExpectedWorking = &GitStatus{} + } + + if tc.ExpectedStaging == nil { + tc.ExpectedStaging = &GitStatus{} + } + + if tc.Rebase { + g.Rebase = &Rebase{} + } + + g.Merge = tc.Merge + tc.ExpectedStaging.Formats = map[string]string{} + tc.ExpectedWorking.Formats = map[string]string{} + g.setStatus() + assert.Equal(t, tc.ExpectedStaging, g.Staging, tc.Case) + assert.Equal(t, tc.ExpectedWorking, g.Working, tc.Case) + assert.Equal(t, tc.ExpectedHash, g.ShortHash, tc.Case) + assert.Equal(t, tc.ExpectedRef, g.Ref, tc.Case) + assert.Equal(t, tc.ExpectedUpstream, g.Upstream, tc.Case) + assert.Equal(t, tc.ExpectedUpstreamGone, g.UpstreamGone, tc.Case) + assert.Equal(t, tc.ExpectedAhead, g.Ahead, tc.Case) + assert.Equal(t, tc.ExpectedBehind, g.Behind, tc.Case) + } +} + +func TestGetStashContextZeroEntries(t *testing.T) { + cases := []struct { + StashContent string + Expected int + }{ + {Expected: 0, StashContent: ""}, + {Expected: 2, StashContent: "1\n2\n"}, + {Expected: 4, StashContent: "1\n2\n3\n4\n\n"}, + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("FileContent", "/logs/refs/stash").Return(tc.StashContent) + + g := &Git{ + Scm: Scm{ + mainSCMDir: "", + }, + } + g.Init(options.Map{}, env) + + got := g.StashCount() + assert.Equal(t, tc.Expected, got) + } +} + +func TestGitCleanSSHURL(t *testing.T) { + cases := []struct { + Case string + Expected string + Upstream string + }{ + {Case: "regular URL", Expected: "https://src.example.com/user/repo", Upstream: "/src.example.com/user/repo.git"}, + {Case: "domain:path", Expected: "https://host.xz/path/to/repo", Upstream: "host.xz:/path/to/repo.git/"}, + {Case: "ssh with port", Expected: "https://host.xz/path/to/repo", Upstream: "ssh://user@host.xz:1234/path/to/repo.git"}, + {Case: "ssh with 3-digit port", Expected: "https://host.xz/path/to/repo", Upstream: "ssh://user@host.xz:234/path/to/repo.git"}, + {Case: "ssh with port, trailing slash", Expected: "https://host.xz/path/to/repo", Upstream: "ssh://user@host.xz:1234/path/to/repo.git/"}, + {Case: "ssh without port", Expected: "https://host.xz/path/to/repo", Upstream: "ssh://user@host.xz/path/to/repo.git/"}, + {Case: "ssh port, no user", Expected: "https://host.xz/path/to/repo", Upstream: "ssh://host.xz:1234/path/to/repo.git"}, + {Case: "ssh no port, no user", Expected: "https://host.xz/path/to/repo", Upstream: "ssh://host.xz/path/to/repo.git"}, + {Case: "rsync no port, no user", Expected: "https://host.xz/path/to/repo", Upstream: "rsync://host.xz/path/to/repo.git/"}, + {Case: "git no port, no user", Expected: "https://host.xz/path/to/repo", Upstream: "git://host.xz/path/to/repo.git"}, + {Case: "gitea no port, no user", Expected: "https://src.example.com/user/repo", Upstream: "_gitea@src.example.com:user/repo.git"}, + {Case: "git@ with user", Expected: "https://github.com/JanDeDobbeleer/oh-my-posh", Upstream: "git@github.com:JanDeDobbeleer/oh-my-posh"}, + {Case: "unsupported", Upstream: "\\test\\repo.git"}, + {Case: "Azure DevOps, https", Expected: "https://dev.azure.com/posh/oh-my-posh/_git/website", Upstream: "https://posh@dev.azure.com/posh/oh-my-posh/_git/website"}, + {Case: "Azure DevOps, ssh", Expected: "https://dev.azure.com/posh/oh-my-posh/_git/website", Upstream: "git@ssh.dev.azure.com:v3/posh/oh-my-posh/website"}, + } + for _, tc := range cases { + g := &Git{} + upstreamURL := g.cleanUpstreamURL(tc.Upstream) + assert.Equal(t, tc.Expected, upstreamURL, tc.Case) + } +} + +func TestGitUpstream(t *testing.T) { + cases := []struct { + Case string + Expected string + Upstream string + }{ + {Case: "No upstream", Expected: "G", Upstream: ""}, + {Case: "SSH url", Expected: "G", Upstream: "ssh://git@git.my.domain:3001/ADIX7/dotconfig.git"}, + {Case: "Gitea", Expected: "EX", Upstream: "_gitea@src.example.com:user/repo.git"}, + {Case: "GitHub", Expected: "GH", Upstream: "github.com/test"}, + {Case: "GitLab", Expected: "GL", Upstream: "gitlab.com/test"}, + {Case: "Bitbucket", Expected: "BB", Upstream: "bitbucket.org/test"}, + {Case: "Azure DevOps", Expected: "AD", Upstream: "dev.azure.com/test"}, + {Case: "Azure DevOps Dos", Expected: "AD", Upstream: "test.visualstudio.com"}, + {Case: "CodeCommit", Expected: "AC", Upstream: "codecommit::eu-west-1://test-repository"}, + {Case: "Codeberg", Expected: "CB", Upstream: "codeberg.org:user/repo.git"}, + {Case: "Gitstash", Expected: "G", Upstream: "gitstash.com/test"}, + {Case: "My custom server", Expected: "CU", Upstream: "mycustom.server/test"}, + {Case: "GitHub with dash", Expected: "GH", Upstream: "github.com:pixel48/custom-reg"}, + } + for _, tc := range cases { + env := &mock.Environment{} + env.On("IsWsl").Return(false) + env.On("RunCommand", "git", []string{"-C", "", "--no-optional-locks", "-c", "core.quotepath=false", + "-c", "color.status=false", "remote", "get-url", origin}).Return(tc.Upstream, nil) + env.On("GOOS").Return("unix") + props := options.Map{ + GithubIcon: "GH", + GitlabIcon: "GL", + BitbucketIcon: "BB", + AzureDevOpsIcon: "AD", + CodeCommit: "AC", + CodebergIcon: "CB", + GitIcon: "G", + UpstreamIcons: map[string]string{ + "mycustom.server": "CU", + "src.example.com": "EX", + }, + } + + g := &Git{ + Scm: Scm{ + command: GITCOMMAND, + Upstream: "origin/main", + }, + } + g.Init(props, env) + + g.configOnce = sync.Once{} + g.configOnce.Do(func() { + g.configErr = errors.New("no config") + }) + + upstreamIcon := g.getUpstreamIcon() + assert.Equal(t, tc.Expected, upstreamIcon, tc.Case) + } +} + +func TestGetBranchStatus(t *testing.T) { + cases := []struct { + Case string + Expected string + Upstream string + Ahead int + Behind int + UpstreamGone bool + }{ + {Case: "Equal with remote", Expected: "equal", Upstream: branchName}, + {Case: "Ahead", Expected: "up2", Ahead: 2}, + {Case: "Behind", Expected: "down8", Behind: 8}, + {Case: "Behind and ahead", Expected: "up7 down8", Behind: 8, Ahead: 7}, + {Case: "Gone", Expected: "gone", Upstream: branchName, UpstreamGone: true}, + {Case: "No remote", Expected: "", Upstream: ""}, + {Case: "Default (bug)", Expected: "", Behind: -8, Upstream: "wonky"}, + } + + for _, tc := range cases { + props := options.Map{ + BranchAheadIcon: "up", + BranchBehindIcon: "down", + BranchIdenticalIcon: "equal", + BranchGoneIcon: "gone", + } + + g := &Git{ + Scm: Scm{ + Upstream: tc.Upstream, + }, + Ahead: tc.Ahead, + Behind: tc.Behind, + UpstreamGone: tc.UpstreamGone, + } + g.Init(props, new(mock.Environment)) + + g.setBranchStatus() + assert.Equal(t, tc.Expected, g.BranchStatus, tc.Case) + } +} + +func TestGitTemplateString(t *testing.T) { + cases := []struct { + Git *Git + Case string + Expected string + Template string + }{ + { + Case: "Only HEAD name", + Expected: branchName, + Template: "{{ .HEAD }}", + Git: &Git{ + HEAD: branchName, + Behind: 2, + }, + }, + { + Case: "Working area changes", + Expected: "main \uF044 +2 ~3", + Template: "{{ .HEAD }}{{ if .Working.Changed }} \uF044 {{ .Working.String }}{{ end }}", + Git: &Git{ + HEAD: branchName, + Working: &GitStatus{ + ScmStatus: ScmStatus{ + Added: 2, + Modified: 3, + }, + }, + }, + }, + { + Case: "No working area changes", + Expected: branchName, + Template: "{{ .HEAD }}{{ if .Working.Changed }} \uF044 {{ .Working.String }}{{ end }}", + Git: &Git{ + HEAD: branchName, + Working: &GitStatus{}, + }, + }, + { + Case: "Working and staging area changes", + Expected: "main \uF046 +5 ~1 \uF044 +2 ~3", + Template: "{{ .HEAD }}{{ if .Staging.Changed }} \uF046 {{ .Staging.String }}{{ end }}{{ if .Working.Changed }} \uF044 {{ .Working.String }}{{ end }}", + Git: &Git{ + HEAD: branchName, + Working: &GitStatus{ + ScmStatus: ScmStatus{ + Added: 2, + Modified: 3, + }, + }, + Staging: &GitStatus{ + ScmStatus: ScmStatus{ + Added: 5, + Modified: 1, + }, + }, + }, + }, + { + Case: "Working and staging area changes with separator", + Expected: "main \uF046 +5 ~1 | \uF044 +2 ~3", + Template: "{{ .HEAD }}{{ if .Staging.Changed }} \uF046 {{ .Staging.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Working.Changed }} \uF044 {{ .Working.String }}{{ end }}", //nolint:lll + Git: &Git{ + HEAD: branchName, + Working: &GitStatus{ + ScmStatus: ScmStatus{ + Added: 2, + Modified: 3, + }, + }, + Staging: &GitStatus{ + ScmStatus: ScmStatus{ + Added: 5, + Modified: 1, + }, + }, + }, + }, + { + Case: "Working and staging area changes with separator and stash count", + Expected: "main \uF046 +5 ~1 | \uF044 +2 ~3 \ueb4b 3", + Template: "{{ .HEAD }}{{ if .Staging.Changed }} \uF046 {{ .Staging.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Working.Changed }} \uF044 {{ .Working.String }}{{ end }}{{ if gt .StashCount 0 }} \ueb4b {{ .StashCount }}{{ end }}", //nolint:lll + Git: &Git{ + HEAD: branchName, + Working: &GitStatus{ + ScmStatus: ScmStatus{ + Added: 2, + Modified: 3, + }, + }, + Staging: &GitStatus{ + ScmStatus: ScmStatus{ + Added: 5, + Modified: 1, + }, + }, + stashCount: 3, + poshgit: true, + }, + }, + { + Case: "No local changes", + Expected: branchName, + Template: "{{ .HEAD }}{{ if .Staging.Changed }} \uF046{{ .Staging.String }}{{ end }}{{ if .Working.Changed }} \uF044{{ .Working.String }}{{ end }}", + Git: &Git{ + HEAD: branchName, + Staging: &GitStatus{}, + Working: &GitStatus{}, + }, + }, + { + Case: "Upstream Icon", + Expected: "from GitHub on main", + Template: "from {{ .UpstreamIcon }} on {{ .HEAD }}", + Git: &Git{ + HEAD: branchName, + Staging: &GitStatus{}, + Working: &GitStatus{}, + UpstreamIcon: "GitHub", + }, + }, + } + + for _, tc := range cases { + props := options.Map{ + FetchStatus: true, + } + env := new(mock.Environment) + tc.Git.env = env + tc.Git.options = props + assert.Equal(t, tc.Expected, renderTemplate(env, tc.Template, tc.Git), tc.Case) + } +} + +func TestGitUntrackedMode(t *testing.T) { + cases := []struct { + UntrackedModes map[string]string + Case string + Expected string + }{ + { + Case: "Default mode - no map", + Expected: "-unormal", + }, + { + Case: "Default mode - no match", + Expected: "-unormal", + UntrackedModes: map[string]string{ + "bar": "no", + }, + }, + { + Case: "No mode - match", + Expected: "-uno", + UntrackedModes: map[string]string{ + "foo": "no", + "bar": "normal", + }, + }, + { + Case: "Global mode", + Expected: "-uno", + UntrackedModes: map[string]string{ + "*": "no", + }, + }, + } + + for _, tc := range cases { + props := options.Map{ + UntrackedModes: tc.UntrackedModes, + } + + g := &Git{ + Scm: Scm{ + repoRootDir: "foo", + }, + } + g.Init(props, new(mock.Environment)) + + got := g.getUntrackedFilesMode() + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestGitIgnoreSubmodules(t *testing.T) { + cases := []struct { + IgnoreSubmodules map[string]string + Case string + Expected string + }{ + { + Case: "Override", + Expected: "--ignore-submodules=all", + IgnoreSubmodules: map[string]string{ + "foo": "all", + }, + }, + { + Case: "Default mode - empty", + IgnoreSubmodules: map[string]string{ + "bar": "no", + }, + }, + { + Case: "Global mode", + Expected: "--ignore-submodules=dirty", + IgnoreSubmodules: map[string]string{ + "*": "dirty", + }, + }, + } + + for _, tc := range cases { + props := options.Map{ + IgnoreSubmodules: tc.IgnoreSubmodules, + } + + g := &Git{ + Scm: Scm{ + repoRootDir: "foo", + }, + } + g.Init(props, new(mock.Environment)) + + got := g.getIgnoreSubmodulesMode() + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestGitCommit(t *testing.T) { + cases := []struct { + Case string + Expected *Commit + Output string + }{ + { + Case: "Clean commit", + Output: ` + an:Jan De Dobbeleer + ae:jan@ohmyposh.dev + cn:Jan De Dobbeleer + ce:jan@ohmyposh.dev + at:1673176335 + su:docs(error): you can't use cross segment properties + ha:1234567891011121314 + rf:HEAD -> refs/heads/main, tag: refs/tags/tag-1, tag: refs/tags/0.3.4, refs/remotes/origin/main, refs/remotes/origin/dev, refs/heads/dev, refs/remotes/origin/HEAD + `, + Expected: &Commit{ + Author: &User{ + Name: "Jan De Dobbeleer", + Email: "jan@ohmyposh.dev", + }, + Committer: &User{ + Name: "Jan De Dobbeleer", + Email: "jan@ohmyposh.dev", + }, + Subject: "docs(error): you can't use cross segment properties", + Timestamp: time.Unix(1673176335, 0), + Refs: &Refs{ + Tags: []string{"tag-1", "0.3.4"}, + Heads: []string{"main", "dev"}, + Remotes: []string{"origin/main", "origin/dev"}, + }, + Sha: "1234567891011121314", + }, + }, + { + Case: "No commit output", + Expected: &Commit{ + Author: &User{}, + Committer: &User{}, + Refs: &Refs{}, + }, + }, + { + Case: "No author", + Output: ` + an: + ae: + cn:Jan De Dobbeleer + ce:jan@ohmyposh.dev + at:1673176335 + su:docs(error): you can't use cross segment properties + `, + Expected: &Commit{ + Author: &User{}, + Committer: &User{ + Name: "Jan De Dobbeleer", + Email: "jan@ohmyposh.dev", + }, + Subject: "docs(error): you can't use cross segment properties", + Timestamp: time.Unix(1673176335, 0), + Refs: &Refs{}, + }, + }, + { + Case: "No refs", + Output: ` + rf:HEAD + `, + Expected: &Commit{ + Author: &User{}, + Committer: &User{}, + Refs: &Refs{}, + }, + }, + { + Case: "Just tag ref", + Output: ` + rf:HEAD, tag: refs/tags/tag-1 + `, + Expected: &Commit{ + Author: &User{}, + Committer: &User{}, + Refs: &Refs{ + Tags: []string{"tag-1"}, + }, + }, + }, + { + Case: "Feature branch including slash", + Output: ` + rf:HEAD, tag: refs/tags/feat/feat-1 + `, + Expected: &Commit{ + Author: &User{}, + Committer: &User{}, + Refs: &Refs{ + Tags: []string{"feat/feat-1"}, + }, + }, + }, + { + Case: "Bad timestamp", + Output: ` + at:err + `, + Expected: &Commit{ + Author: &User{}, + Committer: &User{}, + Refs: &Refs{}, + }, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.MockGitCommand("", tc.Output, "log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s%nha:%H%nrf:%D", "--decorate=full") + + g := &Git{ + Scm: Scm{ + command: GITCOMMAND, + }, + } + g.Init(options.Map{}, env) + + got := g.Commit() + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestGitRemotes(t *testing.T) { + cases := []struct { + ExpectedRemotes map[string]string + Case string + Config string + Expected int + }{ + { + Case: "Empty config file", + Expected: 0, + ExpectedRemotes: map[string]string{}, + }, + { + Case: "Two remotes", + Expected: 2, + Config: ` +[remote "origin"] + url = git@github.com:JanDeDobbeleer/test.git + fetch = +refs/heads/*:refs/remotes/origin/* +[remote "upstream"] + url = git@github.com:microsoft/test.git + fetch = +refs/heads/*:refs/remotes/upstream/* +`, + ExpectedRemotes: map[string]string{ + "origin": "https://github.com/JanDeDobbeleer/test", + "upstream": "https://github.com/microsoft/test", + }, + }, + { + Case: "One remote", + Expected: 1, + Config: ` +[remote "origin"] + url = git@github.com:JanDeDobbeleer/test.git + fetch = +refs/heads/*:refs/remotes/origin/* +`, + ExpectedRemotes: map[string]string{ + "origin": "https://github.com/JanDeDobbeleer/test", + }, + }, + { + Case: "Broken config", + Expected: 0, + Config: "{{}}", + ExpectedRemotes: map[string]string{}, + }, + { + Case: "Three remotes with different URL formats", + Expected: 3, + Config: ` +[remote "origin"] + url = git@github.com:JanDeDobbeleer/test.git + fetch = +refs/heads/*:refs/remotes/origin/* +[remote "upstream"] + url = https://github.com/microsoft/test.git + fetch = +refs/heads/*:refs/remotes/upstream/* +[remote "fork"] + url = git@gitlab.com:user/test.git + fetch = +refs/heads/*:refs/remotes/fork/* +`, + ExpectedRemotes: map[string]string{ + "origin": "https://github.com/JanDeDobbeleer/test", + "upstream": "https://github.com/microsoft/test.git", + "fork": "https://gitlab.com/user/test", + }, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + + g := &Git{ + Scm: Scm{ + repoRootDir: "foo", + }, + } + g.Init(options.Map{}, env) + + g.configOnce = sync.Once{} + g.configOnce.Do(func() { + g.config, g.configErr = ini.Load([]byte(tc.Config)) + }) + + got := g.Remotes() + assert.Equal(t, tc.Expected, len(got), tc.Case) + + // Verify the actual remote names and URLs + for name, expectedURL := range tc.ExpectedRemotes { + actualURL, exists := got[name] + assert.True(t, exists, "%s: expected remote '%s' to exist", tc.Case, name) + assert.Equal(t, expectedURL, actualURL, "%s: remote '%s' URL mismatch", tc.Case, name) + } + } +} + +func TestGitRepoName(t *testing.T) { + cases := []struct { + Case string + Expected string + WorkingDir string + RealDir string + IsWorkTree bool + }{ + { + Case: "In worktree", + Expected: "oh-my-posh", + IsWorkTree: true, + WorkingDir: "/Users/jan/Code/oh-my-posh/.git/worktrees/oh-my-posh2", + }, + { + Case: "Not in worktree", + Expected: "oh-my-posh", + IsWorkTree: false, + RealDir: "/Users/jan/Code/oh-my-posh", + }, + { + Case: "In worktree, unexpected dir", + Expected: "", + IsWorkTree: true, + WorkingDir: "/Users/jan/Code/oh-my-posh2", + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("PathSeparator").Return("/") + env.On("GOOS").Return(runtime.LINUX) + + g := &Git{ + Scm: Scm{ + repoRootDir: tc.RealDir, + mainSCMDir: tc.WorkingDir, + }, + IsWorkTree: tc.IsWorkTree, + } + g.Init(options.Map{}, env) + + got := g.repoName() + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestDisableWithJJEnabled(t *testing.T) { + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("GOOS").Return("") + env.On("IsWsl").Return(false) + // Mock .jj directory exists + env.On("HasParentFilePath", ".jj", false).Return(&runtime.FileInfo{Path: "/dir/.jj", IsDir: true}, nil) + + g := &Git{} + props := options.Map{ + DisableWithJJ: true, + } + g.Init(props, env) + + assert.False(t, g.Enabled()) +} + +func TestDisableWithJJDisabled(t *testing.T) { + fileInfo := &runtime.FileInfo{ + Path: "/dir/.git", + ParentFolder: "/dir", + IsDir: true, + } + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasCommand", "git").Return(true) + env.On("GOOS").Return("") + env.On("FileContent", "/dir/.git/HEAD").Return("") + env.MockGitCommand("/dir", "1234567890abcdef1234567890abcdef12345678", "rev-parse", "HEAD") + env.MockGitCommand("/dir", "", "describe", "--tags", "--exact-match") // Use repo root, not .git dir + env.On("IsWsl").Return(false) + // Mock .jj directory exists + env.On("HasParentFilePath", ".jj", false).Return(&runtime.FileInfo{Path: "/dir/.jj", IsDir: true}, nil) + env.On("HasParentFilePath", ".git", true).Return(fileInfo, nil) + env.On("PathSeparator").Return("/") + env.On("Home").Return(poshHome) + env.On("Getenv", poshGitEnv).Return("") + env.On("DirMatchesOneOf", testify_.Anything, testify_.Anything).Return(false) + + g := &Git{} + props := options.Map{ + DisableWithJJ: false, // Property is disabled + } + g.Init(props, env) + + assert.True(t, g.Enabled()) // Should still be enabled since disable_with_jj is false +} + +func TestDisableWithJJNoJJDirectory(t *testing.T) { + fileInfo := &runtime.FileInfo{ + Path: "/dir/.git", + ParentFolder: "/dir", + IsDir: true, + } + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasCommand", "git").Return(true) + env.On("GOOS").Return("") + env.On("FileContent", "/dir/.git/HEAD").Return("") + env.MockGitCommand("/dir", "1234567890abcdef1234567890abcdef12345678", "rev-parse", "HEAD") + env.MockGitCommand("/dir", "", "describe", "--tags", "--exact-match") // Use repo root, not .git dir + env.On("IsWsl").Return(false) + // Mock .jj directory does not exist + env.On("HasParentFilePath", ".jj", false).Return((*runtime.FileInfo)(nil), errors.New("no .jj found")) + env.On("HasParentFilePath", ".git", true).Return(fileInfo, nil) + env.On("PathSeparator").Return("/") + env.On("Home").Return(poshHome) + env.On("Getenv", poshGitEnv).Return("") + env.On("DirMatchesOneOf", testify_.Anything, testify_.Anything).Return(false) + + g := &Git{} + props := options.Map{ + DisableWithJJ: true, // Property is enabled but no .jj directory + } + g.Init(props, env) + + assert.True(t, g.Enabled()) // Should be enabled since .jj directory doesn't exist +} + +func TestPushStatusAheadAndBehind(t *testing.T) { + cases := []struct { + Case string + PushAheadCount string + PushBehindCount string + Config string + ExpectedPushAhead int + ExpectedPushBehind int + }{ + { + Case: "ahead and behind", + PushAheadCount: "3", + PushBehindCount: "5", + ExpectedPushAhead: 3, + ExpectedPushBehind: 5, + }, + { + Case: "only ahead", + PushAheadCount: "2", + PushBehindCount: "0", + ExpectedPushAhead: 2, + ExpectedPushBehind: 0, + }, + { + Case: "only behind", + PushAheadCount: "0", + PushBehindCount: "7", + ExpectedPushAhead: 0, + ExpectedPushBehind: 7, + }, + { + Case: "up to date", + PushAheadCount: "0", + PushBehindCount: "0", + ExpectedPushAhead: 0, + ExpectedPushBehind: 0, + }, + { + Case: "remote from config", + PushAheadCount: "2", + PushBehindCount: "0", + ExpectedPushAhead: 2, + ExpectedPushBehind: 0, + Config: ` + [branch "main"] + remote = origin + merge = refs/heads/main + `, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("RunCommand", "git", []string{"-C", "/dir", "--no-optional-locks", "-c", "core.quotepath=false", + "-c", "color.status=false", "config", "--get", "remote.pushDefault"}).Return("", nil) + env.On("RunCommand", "git", []string{"-C", "/dir", "--no-optional-locks", "-c", "core.quotepath=false", + "-c", "color.status=false", "rev-list", "--count", "origin/main..HEAD"}).Return(tc.PushAheadCount, nil) + env.On("RunCommand", "git", []string{"-C", "/dir", "--no-optional-locks", "-c", "core.quotepath=false", + "-c", "color.status=false", "rev-list", "--count", "HEAD..origin/main"}).Return(tc.PushBehindCount, nil) + env.On("FileContent", "/dir/.git/config").Return("") + + g := &Git{ + Scm: Scm{ + command: "git", + repoRootDir: "/dir", + scmDir: "/dir/.git", + Upstream: "origin/main", + }, + Ref: "main", + } + + props := options.Map{ + FetchPushStatus: true, + } + + g.Init(props, env) + + g.configOnce = sync.Once{} + g.configOnce.Do(func() { + if len(tc.Config) > 0 { + g.config, g.configErr = ini.Load([]byte(tc.Config)) + return + } + + g.configErr = errors.New("no config") + }) + + g.setPushStatus() + + assert.Equal(t, tc.ExpectedPushAhead, g.PushAhead, tc.Case) + assert.Equal(t, tc.ExpectedPushBehind, g.PushBehind, tc.Case) + } +} diff --git a/src/segments/git_unix.go b/src/segments/git_unix.go new file mode 100644 index 000000000000..24bb84848dfb --- /dev/null +++ b/src/segments/git_unix.go @@ -0,0 +1,14 @@ +//go:build !windows + +package segments + +import "path/filepath" + +// resolveGitPath resolves path relative to base. +func resolveGitPath(base, path string) string { + if filepath.IsAbs(path) { + return path + } + + return filepath.Join(base, path) +} diff --git a/src/segments/git_unix_test.go b/src/segments/git_unix_test.go new file mode 100644 index 000000000000..05290c33b000 --- /dev/null +++ b/src/segments/git_unix_test.go @@ -0,0 +1,36 @@ +//go:build !windows + +package segments + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const TestRootPath = "/" + +func TestResolveGitPath(t *testing.T) { + cases := []struct { + Case string + Base string + Path string + Expected string + }{ + { + Case: "relative path", + Base: "dir/", + Path: "sub", + Expected: "dir/sub", + }, + { + Case: "absolute path", + Base: "/base", + Path: "/absolute/path", + Expected: "/absolute/path", + }, + } + for _, tc := range cases { + assert.Equal(t, tc.Expected, resolveGitPath(tc.Base, tc.Path), tc.Case) + } +} diff --git a/src/segments/git_windows.go b/src/segments/git_windows.go new file mode 100644 index 000000000000..77798fd5638d --- /dev/null +++ b/src/segments/git_windows.go @@ -0,0 +1,25 @@ +package segments + +import "path/filepath" + +// resolveGitPath resolves path relative to base. +func resolveGitPath(base, path string) string { + if path == "" { + return base + } + + if filepath.IsAbs(path) { + return path + } + + // Note that git on Windows uses slashes exclusively. And it's okay + // because Windows actually accepts both directory separators. More + // importantly, however, parts of the git segment depend on those + // slashes. + if path[0] == '/' { + // path is a disk-relative path. + return filepath.VolumeName(base) + path + } + + return filepath.ToSlash(filepath.Join(base, path)) +} diff --git a/src/segments/git_windows_test.go b/src/segments/git_windows_test.go new file mode 100644 index 000000000000..436fdcd39eb6 --- /dev/null +++ b/src/segments/git_windows_test.go @@ -0,0 +1,42 @@ +//go:build windows + +package segments + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const TestRootPath = "C:/" + +func TestResolveGitPath(t *testing.T) { + cases := []struct { + Case string + Base string + Path string + Expected string + }{ + { + Case: "relative path", + Base: "dir\\", + Path: "sub", + Expected: "dir/sub", + }, + { + Case: "absolute path", + Base: "C:\\base", + Path: "C:/absolute/path", + Expected: "C:/absolute/path", + }, + { + Case: "disk-relative path", + Base: "C:\\base", + Path: "/absolute/path", + Expected: "C:/absolute/path", + }, + } + for _, tc := range cases { + assert.Equal(t, tc.Expected, resolveGitPath(tc.Base, tc.Path), tc.Case) + } +} diff --git a/src/segments/gitversion.go b/src/segments/gitversion.go new file mode 100644 index 000000000000..1e3428038331 --- /dev/null +++ b/src/segments/gitversion.go @@ -0,0 +1,68 @@ +package segments + +import ( + "encoding/json" +) + +type GitVersionInfo struct { + NuGetVersionV2 string `json:"NuGetVersionV2"` + FullSemVer string `json:"FullSemVer"` + CommitDate string `json:"CommitDate"` + AssemblySemVer string `json:"AssemblySemVer"` + PreReleaseTagWithDash string `json:"PreReleaseTagWithDash"` + PreReleaseLabel string `json:"PreReleaseLabel"` + PreReleaseLabelWithDash string `json:"PreReleaseLabelWithDash"` + AssemblySemFileVer string `json:"AssemblySemFileVer"` + CommitsSinceVersionSourcePadded string `json:"CommitsSinceVersionSourcePadded"` + VersionSourceSha string `json:"VersionSourceSha"` + BuildMetaDataPadded string `json:"BuildMetaDataPadded"` + FullBuildMetaData string `json:"FullBuildMetaData"` + MajorMinorPatch string `json:"MajorMinorPatch"` + NuGetVersion string `json:"NuGetVersion"` + LegacySemVer string `json:"LegacySemVer"` + LegacySemVerPadded string `json:"LegacySemVerPadded"` + PreReleaseTag string `json:"PreReleaseTag"` + NuGetPreReleaseTag string `json:"NuGetPreReleaseTag"` + SemVer string `json:"SemVer"` + InformationalVersion string `json:"InformationalVersion"` + BranchName string `json:"BranchName"` + EscapedBranchName string `json:"EscapedBranchName"` + Sha string `json:"Sha"` + ShortSha string `json:"ShortSha"` + NuGetPreReleaseTagV2 string `json:"NuGetPreReleaseTagV2"` + BuildMetaData int `json:"BuildMetaData"` + Major int `json:"Major"` + PreReleaseNumber int `json:"PreReleaseNumber"` + Minor int `json:"Minor"` + CommitsSinceVersionSource int `json:"CommitsSinceVersionSource"` + WeightedPreReleaseNumber int `json:"WeightedPreReleaseNumber"` + UncommittedChanges int `json:"UncommittedChanges"` + Patch int `json:"Patch"` +} + +type GitVersion struct { + Base + + GitVersionInfo +} + +func (n *GitVersion) Template() string { + return " {{ .MajorMinorPatch }} " +} + +func (n *GitVersion) Enabled() bool { + gitversion := "gitversion" + if !n.env.HasCommand(gitversion) { + return false + } + + response, err := n.env.RunCommand(gitversion, "-output", "json") + if err != nil { + return false + } + + n.GitVersionInfo = GitVersionInfo{} + err = json.Unmarshal([]byte(response), &n.GitVersionInfo) + + return err == nil +} diff --git a/src/segments/gitversion_test.go b/src/segments/gitversion_test.go new file mode 100644 index 000000000000..8a48cb66730f --- /dev/null +++ b/src/segments/gitversion_test.go @@ -0,0 +1,64 @@ +package segments + +import ( + "errors" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/alecthomas/assert" +) + +func TestGitversion(t *testing.T) { + cases := []struct { + CacheError error + CommandError error + Case string + ExpectedString string + Response string + CacheResponse string + Template string + CacheTimeout int + ExpectedEnabled bool + HasGitversion bool + }{ + {Case: "GitVersion not installed"}, + {Case: "GitVersion installed, no GitVersion.yml file", HasGitversion: true, Response: "Cannot find the .git directory"}, + { + Case: "Version", + ExpectedEnabled: true, + ExpectedString: "number", + HasGitversion: true, + Response: "{ \"FullSemVer\": \"0.1.0\", \"SemVer\": \"number\" }", + Template: "{{ .SemVer }}", + }, + { + Case: "Command Error", + HasGitversion: true, + CommandError: errors.New("error"), + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + + env.On("HasCommand", "gitversion").Return(tc.HasGitversion) + env.On("Pwd").Return("test-dir") + env.On("RunCommand", "gitversion", []string{"-output", "json"}).Return(tc.Response, tc.CommandError) + + gitversion := &GitVersion{} + gitversion.Init(options.Map{}, env) + + if tc.Template == "" { + tc.Template = gitversion.Template() + } + + enabled := gitversion.Enabled() + + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + if enabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, gitversion), tc.Case) + } + } +} diff --git a/src/segments/golang.go b/src/segments/golang.go new file mode 100644 index 000000000000..2849f4e6c780 --- /dev/null +++ b/src/segments/golang.go @@ -0,0 +1,91 @@ +package segments + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "golang.org/x/mod/modfile" +) + +type Golang struct { + Language +} + +const ( + ParseModFile options.Option = "parse_mod_file" + ParseWorkFile options.Option = "parse_work_file" +) + +func (g *Golang) Template() string { + return languageTemplate +} + +func (g *Golang) Enabled() bool { + g.extensions = []string{"*.go", "go.mod", "go.sum", "go.work", "go.work.sum"} + g.tooling = map[string]*cmd{ + "mod": { + regex: `(?P((?P[0-9]+).(?P[0-9]+)(.(?P[0-9]+))?))`, + getVersion: g.getVersion, + }, + "go": { + executable: "go", + args: []string{versionArg}, + regex: `(?:go(?P((?P[0-9]+).(?P[0-9]+)(.(?P[0-9]+))?)))`, + }, + } + g.defaultTooling = []string{"mod", "go"} + g.versionURLTemplate = "https://golang.org/doc/go{{ .Major }}.{{ .Minor }}" + + return g.Language.Enabled() +} + +// getVersion returns the version of the Go language +// It first checks if the go.mod file is present and if it is, it parses the file to get the version +// If the go.mod file is not present, it checks if the go.work file is present and if it is, it parses the file to get the version +// If neither file is present, it returns an empty string +func (g *Golang) getVersion() (string, error) { + if g.options.Bool(ParseModFile, false) { + return g.parseModFile() + } + + if g.options.Bool(ParseWorkFile, false) { + return g.parseWorkFile() + } + + return "", nil +} + +func (g *Golang) parseModFile() (string, error) { + gomod, err := g.env.HasParentFilePath("go.mod", false) + if err != nil { + return "", err + } + + contents := g.env.FileContent(gomod.Path) + file, err := modfile.Parse(gomod.Path, []byte(contents), nil) + if err != nil { + return "", err + } + + if file.Go.Version != "" { + return file.Go.Version, nil + } + + // ignore when no version is found in go.mod file + return "", nil +} + +func (g *Golang) parseWorkFile() (string, error) { + goWork, err := g.env.HasParentFilePath("go.work", false) + if err != nil { + return "", err + } + + contents := g.env.FileContent(goWork.Path) + version, _ := regex.FindStringMatch(`go (\d(\.\d{1,2})?(\.\d{1,2})?)`, contents, 1) + if len(version) > 0 { + return version, nil + } + + // ignore when no version is found in go.work file + return "", nil +} diff --git a/src/segments/golang_test.go b/src/segments/golang_test.go new file mode 100644 index 000000000000..e6cb05bbeac0 --- /dev/null +++ b/src/segments/golang_test.go @@ -0,0 +1,127 @@ +package segments + +import ( + "errors" + "fmt" + "os" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/stretchr/testify/assert" +) + +func TestGolang(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + ParseModFile bool + HasModFileInParentDir bool + InvalidModfile bool + ParseGoWorkFile bool + HasGoWorkFileInParentDir bool + InvalidGoWorkFile bool + }{ + {Case: "Go 1.15", ExpectedString: "1.15.8", Version: "go version go1.15.8 darwin/amd64"}, + {Case: "Go 1.16", ExpectedString: "1.16", Version: "go version go1.16 darwin/amd64"}, + {Case: "go.mod 1.26.0", ParseModFile: true, HasModFileInParentDir: true, ExpectedString: "1.26.0"}, + {Case: "no go.mod file fallback", ParseModFile: true, ExpectedString: "1.16", Version: "go version go1.16 darwin/amd64"}, + { + Case: "invalid go.mod file fallback", + ParseModFile: true, + HasModFileInParentDir: true, + InvalidModfile: true, + ExpectedString: "1.16", + Version: "go version go1.16 darwin/amd64", + }, + {Case: "go.work file", ParseGoWorkFile: true, HasGoWorkFileInParentDir: true, ExpectedString: "1.21"}, + { + Case: "invalid go.work file fallback", + ParseGoWorkFile: true, + HasGoWorkFileInParentDir: true, + InvalidGoWorkFile: true, + ExpectedString: "1.16", + Version: "go version go1.16 darwin/amd64", + }, + { + Case: "go.work file with go.mod file uses go.mod's version", + ParseModFile: true, + HasModFileInParentDir: true, + ParseGoWorkFile: true, + HasGoWorkFileInParentDir: true, + ExpectedString: "1.26.0", + }, + { + Case: "missing both go.mod and go.work file fallback", + ParseModFile: true, + ParseGoWorkFile: true, + ExpectedString: "1.16", + Version: "go version go1.16 darwin/amd64", + }, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "go", + versionParam: "version", + versionOutput: tc.Version, + extension: "*.go", + } + env, props := getMockedLanguageEnv(params) + + if tc.ParseModFile { + props[ParseModFile] = tc.ParseModFile + fileInfo := &runtime.FileInfo{ + Path: "../go.mod", + ParentFolder: "./", + IsDir: false, + } + + var err error + if !tc.HasModFileInParentDir { + err = errors.New("no match") + } + env.On("HasParentFilePath", "go.mod", false).Return(fileInfo, err) + + var content string + if tc.InvalidModfile { + content = "invalid go.mod file" + } else { + tmp, _ := os.ReadFile(fileInfo.Path) + content = string(tmp) + } + + env.On("FileContent", fileInfo.Path).Return(content) + } + + if tc.ParseGoWorkFile { + props[ParseWorkFile] = tc.ParseGoWorkFile + fileInfo := &runtime.FileInfo{ + Path: "../test/go.work", + ParentFolder: "./", + IsDir: false, + } + + var err error + if !tc.HasGoWorkFileInParentDir { + err = errors.New("no match") + } + + env.On("HasParentFilePath", "go.work", false).Return(fileInfo, err) + var content string + if tc.InvalidGoWorkFile { + content = "invalid go.work file" + } else { + tmp, _ := os.ReadFile(fileInfo.Path) + content = string(tmp) + } + + env.On("FileContent", fileInfo.Path).Return(content) + } + + g := &Golang{} + g.Init(props, env) + + assert.True(t, g.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, g.Template(), g), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/haskell.go b/src/segments/haskell.go new file mode 100644 index 000000000000..50cb4bf513bc --- /dev/null +++ b/src/segments/haskell.go @@ -0,0 +1,53 @@ +package segments + +import "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + +type Haskell struct { + Language + + StackGhc bool +} + +const ( + StackGhcMode options.Option = "stack_ghc_mode" +) + +func (h *Haskell) Template() string { + return languageTemplate +} + +const ghcToolName = "ghc" + +const stackToolName = "stack" + +func (h *Haskell) Enabled() bool { + h.extensions = []string{"*.hs", "*.lhs", "stack.yaml", "package.yaml", "*.cabal", "cabal.project"} + h.tooling = map[string]*cmd{ + ghcToolName: { + executable: ghcToolName, + args: []string{"--numeric-version"}, + regex: versionRegex, + }, + stackToolName: { + executable: stackToolName, + args: []string{ghcToolName, "--", "--numeric-version"}, + regex: versionRegex, + }, + } + h.defaultTooling = []string{ghcToolName} + h.versionURLTemplate = "https://www.haskell.org/ghc/download_ghc_{{ .Major }}_{{ .Minor }}_{{ .Patch }}.html" + + switch h.options.String(StackGhcMode, "never") { + case "always": + h.defaultTooling = []string{stackToolName} + h.StackGhc = true + case "package": + _, err := h.env.HasParentFilePath("stack.yaml", false) + if err == nil { + h.defaultTooling = []string{stackToolName} + h.StackGhc = true + } + } + + return h.Language.Enabled() +} diff --git a/src/segments/haskell_test.go b/src/segments/haskell_test.go new file mode 100644 index 000000000000..e72600f2ec64 --- /dev/null +++ b/src/segments/haskell_test.go @@ -0,0 +1,92 @@ +package segments + +import ( + "errors" + "fmt" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/stretchr/testify/assert" +) + +func TestHaskell(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + GhcVersion string + StackGhcVersion string + StackGhcMode string + InStackPackage bool + StackGhc bool + }{ + { + Case: "GHC 8.10.7", + ExpectedString: "8.10.7", + GhcVersion: "8.10.7", + StackGhcVersion: "9.0.2", + StackGhcMode: "never", + }, + { + Case: "Stack GHC Mode - Always", + ExpectedString: "9.0.2", + GhcVersion: "8.10.7", + StackGhcVersion: "9.0.2", + StackGhcMode: "always", + StackGhc: true, + }, + { + Case: "Stack GHC Mode - Package", + ExpectedString: "9.0.2", + GhcVersion: "8.10.7", + StackGhcVersion: "9.0.2", + StackGhcMode: "package", + InStackPackage: true, + StackGhc: true, + }, + { + Case: "Stack GHC Mode - Package no stack.yaml", + ExpectedString: "8.10.7", + GhcVersion: "8.10.7", + StackGhcVersion: "9.0.2", + StackGhcMode: "package", + }, + } + + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "ghc", + versionParam: "--numeric-version", + versionOutput: tc.GhcVersion, + extension: "*.hs", + } + env, props := getMockedLanguageEnv(params) + + if tc.StackGhcMode == "always" || (tc.StackGhcMode == "package" && tc.InStackPackage) { + env.On("HasCommand", "stack").Return(true) + env.On("RunCommand", "stack", []string{"ghc", "--", "--numeric-version"}).Return(tc.StackGhcVersion, nil) + } + + fileInfo := &runtime.FileInfo{ + Path: "../stack.yaml", + ParentFolder: "./", + IsDir: false, + } + + if tc.InStackPackage { + var err error + env.On("HasParentFilePath", "stack.yaml", false).Return(fileInfo, err) + } else { + env.On("HasParentFilePath", "stack.yaml", false).Return(fileInfo, errors.New("no match")) + } + + props[StackGhcMode] = tc.StackGhcMode + + h := &Haskell{} + h.Init(props, env) + + failMsg := fmt.Sprintf("Failed in case: %s", tc.Case) + assert.True(t, h.Enabled(), failMsg) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, h.Template(), h), failMsg) + assert.Equal(t, tc.StackGhc, h.StackGhc, failMsg) + } +} diff --git a/src/segments/helm.go b/src/segments/helm.go new file mode 100644 index 000000000000..5abf2d7a099d --- /dev/null +++ b/src/segments/helm.go @@ -0,0 +1,44 @@ +package segments + +type Helm struct { + Base + + Version string +} + +func (h *Helm) Enabled() bool { + displayMode := h.options.String(DisplayMode, DisplayModeAlways) + if displayMode != DisplayModeFiles { + return h.getVersion() + } + + inChart := false + files := []string{"Chart.yml", "Chart.yaml", "helmfile.yaml", "helmfile.yml"} + for _, file := range files { + if _, err := h.env.HasParentFilePath(file, false); err == nil { + inChart = true + break + } + } + + return inChart && h.getVersion() +} + +func (h *Helm) Template() string { + return " Helm {{.Version}}" +} + +func (h *Helm) getVersion() bool { + cmd := "helm" + if !h.env.HasCommand(cmd) { + return false + } + + result, err := h.env.RunCommand(cmd, "version", "--short", "--template={{.Version}}") + if err != nil { + return false + } + + h.Version = result[1:] + return true +} diff --git a/src/segments/helm_test.go b/src/segments/helm_test.go new file mode 100644 index 000000000000..de289352c966 --- /dev/null +++ b/src/segments/helm_test.go @@ -0,0 +1,104 @@ +package segments + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + testify_mock "github.com/stretchr/testify/mock" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +func TestHelmSegment(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Template string + DisplayMode string + ChartFile string + HelmExists bool + ExpectedEnabled bool + }{ + { + Case: "Helm not installed", + HelmExists: false, + ExpectedEnabled: false, + }, + { + Case: "DisplayMode always inside chart", + HelmExists: true, + ExpectedEnabled: true, + ExpectedString: "Helm 3.12.3", + DisplayMode: "always", + }, + { + Case: "DisplayMode always outside chart", + HelmExists: true, + ExpectedEnabled: true, + ExpectedString: "Helm 3.12.3", + DisplayMode: "always", + }, + { + Case: "DisplayMode files inside chart. Chart file Chart.yml", + HelmExists: true, + ExpectedEnabled: true, + ExpectedString: "Helm 3.12.3", + DisplayMode: "files", + ChartFile: "Chart.yml", + }, + { + Case: "DisplayMode always inside chart. Chart file Chart.yaml", + HelmExists: true, + ExpectedEnabled: true, + ExpectedString: "Helm 3.12.3", + DisplayMode: "files", + ChartFile: "Chart.yaml", + }, + { + Case: "DisplayMode always inside chart. Chart file helmfile.yaml", + HelmExists: true, + ExpectedEnabled: true, + ExpectedString: "Helm 3.12.3", + DisplayMode: "files", + ChartFile: "helmfile.yaml", + }, + { + Case: "DisplayMode always inside chart. Chart file helmfile.yml", + HelmExists: true, + ExpectedEnabled: true, + ExpectedString: "Helm 3.12.3", + DisplayMode: "files", + ChartFile: "helmfile.yml", + }, + { + Case: "DisplayMode always outside chart", + HelmExists: true, + ExpectedEnabled: false, + DisplayMode: "files", + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("HasCommand", "helm").Return(tc.HelmExists) + env.On("RunCommand", "helm", []string{"version", "--short", "--template={{.Version}}"}).Return("v3.12.3", nil) + + env.On("HasParentFilePath", tc.ChartFile, false).Return(&runtime.FileInfo{}, nil) + env.On("HasParentFilePath", testify_mock.Anything, false).Return(&runtime.FileInfo{}, errors.New("no such file or directory")) + + props := options.Map{ + DisplayMode: tc.DisplayMode, + } + + h := &Helm{} + h.Init(props, env) + + assert.Equal(t, tc.ExpectedEnabled, h.Enabled(), tc.Case) + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, h.Template(), h), tc.Case) + } + } +} diff --git a/src/segments/http.go b/src/segments/http.go new file mode 100644 index 000000000000..a31ff79f41ac --- /dev/null +++ b/src/segments/http.go @@ -0,0 +1,65 @@ +package segments + +import ( + "encoding/json" + + "net/http" + + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/template" +) + +type HTTP struct { + Base + + Body map[string]any +} + +const ( + METHOD options.Option = "method" +) + +func (h *HTTP) Template() string { + return " {{ .Body }} " +} + +func (h *HTTP) Enabled() bool { + url := h.options.String(URL, "") + if url == "" { + return false + } + + method := h.options.String(METHOD, "GET") + timeout := h.options.Int(options.HTTPTimeout, 10000) + + if resolved, err := template.Render(url, nil); err == nil { + url = resolved + } + + result, err := h.getResult(url, method, timeout) + if err != nil { + return false + } + + h.Body = result + return true +} + +func (h *HTTP) getResult(url, method string, timeout int) (map[string]any, error) { + setMethod := func(request *http.Request) { + request.Method = method + } + + resultBody, err := h.env.HTTPRequest(url, nil, timeout, setMethod) + if err != nil { + return nil, err + } + + var result map[string]any + err = json.Unmarshal(resultBody, &result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/src/segments/http_test.go b/src/segments/http_test.go new file mode 100644 index 000000000000..3db9f5c6c09c --- /dev/null +++ b/src/segments/http_test.go @@ -0,0 +1,156 @@ +package segments + +import ( + "encoding/json" + "errors" + "io" + "testing" + + runtimehttp "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +// timeoutCapturingEnv wraps the mock environment to record the timeout argument +// passed to HTTPRequest, so tests can assert it flows through correctly. +type timeoutCapturingEnv struct { + *mock.Environment + capturedTimeout int +} + +func (e *timeoutCapturingEnv) HTTPRequest(url string, body io.Reader, timeout int, modifiers ...runtimehttp.RequestModifier) ([]byte, error) { + e.capturedTimeout = timeout + return e.Environment.HTTPRequest(url, body, timeout, modifiers...) +} + +func TestHTTPSegmentEnabled(t *testing.T) { + cases := []struct { + expected any + name string + url string + method string + response string + timeout int + shouldError bool + }{ + { + name: "Valid URL with GET response", + url: "https://jsonplaceholder.typicode.com/posts/1", + method: "GET", + timeout: 0, + response: `{"id": "1"}`, + expected: "1", + shouldError: false, + }, + { + name: "Valid URL with POST response", + url: "https://jsonplaceholder.typicode.com/posts", + method: "POST", + timeout: 0, + response: `{"id": "101"}`, + expected: "101", + shouldError: false, + }, + { + name: "Valid URL with error response", + url: "https://api.example.com/data", + method: "GET", + timeout: 0, + shouldError: true, + }, + { + name: "Empty URL", + url: "", + method: "GET", + timeout: 0, + shouldError: false, + }, + { + name: "Custom timeout", + url: "https://jsonplaceholder.typicode.com/posts/1", + method: "GET", + timeout: 5000, + response: `{"id": "2"}`, + expected: "2", + shouldError: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + inner := new(mock.Environment) + props := options.Map{ + URL: tc.url, + METHOD: tc.method, + } + + if tc.timeout > 0 { + props[options.HTTPTimeout] = tc.timeout + } + + inner.On("HTTPRequest", tc.url).Return([]byte(tc.response), func() error { + if tc.shouldError { + return errors.New("error") + } + return nil + }()) + + capturing := &timeoutCapturingEnv{Environment: inner} + + cs := &HTTP{ + Base: Base{ + env: capturing, + options: props, + }, + } + + _ = cs.Enabled() + assert.Equal(t, tc.expected, cs.Body["id"], tc.name) + if tc.timeout > 0 { + assert.Equal(t, tc.timeout, capturing.capturedTimeout, "expected custom timeout to be passed to HTTPRequest") + } + }) + } +} + +func TestHTTPSegmentCache(t *testing.T) { + // Simulate what happens when caching + response := `{"version": "39.2.6", "count": 42, "enabled": true}` + + // Create and populate HTTP segment + original := &HTTP{ + Base: Base{ + Segment: &Segment{ + Text: " Electron: v39.2.6 ", + Index: 1, + }, + }, + } + + var result map[string]any + err := json.Unmarshal([]byte(response), &result) + assert.NoError(t, err) + original.Body = result + + // Marshal to JSON (like setCache does) + data, err := json.Marshal(original) + assert.NoError(t, err) + + // Unmarshal back (like restoreCache does) + restored := &HTTP{ + Base: Base{ + Segment: &Segment{}, + }, + } + + err = json.Unmarshal(data, restored) + assert.NoError(t, err) + + // Verify Body is restored correctly + assert.NotNil(t, restored.Body, "Body should not be nil") + assert.Equal(t, "39.2.6", restored.Body["version"], "version should be restored") + assert.Equal(t, float64(42), restored.Body["count"], "count should be restored") + assert.Equal(t, true, restored.Body["enabled"], "enabled should be restored") +} diff --git a/src/segments/ipify.go b/src/segments/ipify.go new file mode 100644 index 000000000000..6554cbb1d898 --- /dev/null +++ b/src/segments/ipify.go @@ -0,0 +1,92 @@ +package segments + +import ( + "net" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type ipData struct { + IP string `json:"ip"` +} + +type IPAPI interface { + Get() (*ipData, error) +} + +type ipAPI struct { + http.Request +} + +func (i *ipAPI) Get() (*ipData, error) { + url := "https://api.ipify.org?format=json" + return http.Do[*ipData](&i.Request, url, nil) +} + +type IPify struct { + Base + + api IPAPI + IP string +} + +const ( + OFFLINE = "OFFLINE" +) + +func (i *IPify) Template() string { + return " {{ .IP }} " +} + +func (i *IPify) Enabled() bool { + const key = "IP" + + if ip, ok := cache.Get[string](cache.Device, key); ok { + i.IP = ip + return true + } + + i.initAPI() + + ip, err := i.getResult() + if err != nil { + return false + } + + i.IP = ip + + duration := i.options.String(options.CacheDuration, string(cache.ONEDAY)) + cache.Set(cache.Device, key, i.IP, cache.Duration(duration)) + + return true +} + +func (i *IPify) getResult() (string, error) { + data, err := i.api.Get() + if dnsErr, OK := err.(*net.DNSError); OK && dnsErr.IsNotFound { + return OFFLINE, nil + } + + if err != nil { + return "", err + } + + return data.IP, err +} + +func (i *IPify) initAPI() { + if i.api != nil { + return + } + + request := &http.Request{ + Env: i.env, + HTTPTimeout: i.options.Int(options.HTTPTimeout, options.DefaultHTTPTimeout), + } + + i.api = &ipAPI{ + Request: *request, + } +} diff --git a/src/segments/ipify_test.go b/src/segments/ipify_test.go new file mode 100644 index 000000000000..9783a0b08b87 --- /dev/null +++ b/src/segments/ipify_test.go @@ -0,0 +1,75 @@ +package segments + +import ( + "errors" + "net" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" + testify_ "github.com/stretchr/testify/mock" +) + +type mockedipAPI struct { + testify_.Mock +} + +func (s *mockedipAPI) Get() (*ipData, error) { + args := s.Called() + return args.Get(0).(*ipData), args.Error(1) +} + +func TestIpifySegment(t *testing.T) { + cases := []struct { + Case string + IPDate *ipData + Error error + ExpectedString string + ExpectedEnabled bool + }{ + { + Case: "IP data", + IPDate: &ipData{IP: "127.0.0.1"}, + ExpectedString: "127.0.0.1", + ExpectedEnabled: true, + }, + { + Case: "Error", + Error: errors.New("network is unreachable"), + ExpectedEnabled: false, + }, + { + Case: "Offline", + ExpectedString: OFFLINE, + Error: &net.DNSError{IsNotFound: true}, + ExpectedEnabled: true, + }, + } + + for _, tc := range cases { + api := &mockedipAPI{} + api.On("Get").Return(tc.IPDate, tc.Error) + + ipify := &IPify{ + api: api, + Base: Base{ + env: &mock.Environment{}, + options: options.Map{}, + }, + } + + enabled := ipify.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + + cache.DeleteAll(cache.Device) + + if !enabled { + continue + } + + assert.Equal(t, tc.ExpectedString, renderTemplate(&mock.Environment{}, ipify.Template(), ipify), tc.Case) + } +} diff --git a/src/segments/java.go b/src/segments/java.go new file mode 100644 index 000000000000..0015ff7013d0 --- /dev/null +++ b/src/segments/java.go @@ -0,0 +1,61 @@ +package segments + +import ( + "fmt" +) + +type Java struct { + Language +} + +func (j *Java) Template() string { + return languageTemplate +} + +func (j *Java) Enabled() bool { + j.init() + + return j.Language.Enabled() +} + +const javaToolName = "java" + +func (j *Java) init() { + javaRegex := `(?: JRE)(?: \(.*\))? \((?P(?P[0-9]+)(?:\.(?P[0-9]+))?(?:\.(?P[0-9]+))?).*\),` + + j.extensions = []string{ + "pom.xml", + "build.gradle.kts", + "build.sbt", + ".java-version", + ".deps.edn", + "project.clj", + "build.boot", + "*.java", + "*.class", + "*.gradle", + "*.jar", + "*.clj", + "*.cljc", + } + + j.tooling = map[string]*cmd{ + javaToolName: { + executable: javaToolName, + args: []string{"-Xinternalversion"}, + regex: javaRegex, + }, + } + j.defaultTooling = []string{javaToolName} + + javaHome := j.env.Getenv("JAVA_HOME") + if len(javaHome) > 0 { + java := fmt.Sprintf("%s/bin/java", javaHome) + j.tooling["java_home"] = &cmd{ + executable: java, + args: []string{"-Xinternalversion"}, + regex: javaRegex, + } + j.defaultTooling = []string{"java_home", javaToolName} + } +} diff --git a/src/segment_java_test.go b/src/segments/java_test.go similarity index 69% rename from src/segment_java_test.go rename to src/segments/java_test.go index 85f0a68ef81b..91bc368001d2 100644 --- a/src/segment_java_test.go +++ b/src/segments/java_test.go @@ -1,4 +1,4 @@ -package main +package segments import ( "fmt" @@ -15,6 +15,11 @@ func TestJava(t *testing.T) { JavaHomeVersion string JavaHomeEnabled bool }{ + { + Case: "Zulu LTS", + ExpectedString: "11.0.13", + Version: "OpenJDK 64-Bit Server VM (11.0.13+8-LTS) for windows-amd64 JRE (Zulu11.52+13-CA) (11.0.13+8-LTS), built on Oct 7 2021 16:00:23 by \"zulu_re\" with MS VC++ 15.9 (VS2017)", //nolint:lll + }, { Case: "OpenJDK macOS", ExpectedString: "1.8.0", @@ -48,27 +53,25 @@ func TestJava(t *testing.T) { }, } for _, tc := range cases { - env := new(MockedEnvironment) - env.On("hasCommand", "java").Return(true) - env.On("runCommand", "java", []string{"-Xinternalversion"}).Return(tc.Version, nil) - env.On("hasFiles", "pom.xml").Return(true) - env.On("getcwd", nil).Return("/usr/home/project") - env.On("homeDir", nil).Return("/usr/home") + params := &mockedLanguageParams{ + cmd: "java", + versionParam: "-Xinternalversion", + versionOutput: tc.Version, + extension: "pom.xml", + } + env, props := getMockedLanguageEnv(params) + if tc.JavaHomeEnabled { - env.On("getenv", "JAVA_HOME").Return("/usr/java") - env.On("hasCommand", "/usr/java/bin/java").Return(true) - env.On("runCommand", "/usr/java/bin/java", []string{"-Xinternalversion"}).Return(tc.JavaHomeVersion, nil) + env.On("Getenv", "JAVA_HOME").Return("/usr/java") + env.On("HasCommand", "/usr/java/bin/java").Return(true) + env.On("RunCommand", "/usr/java/bin/java", []string{"-Xinternalversion"}).Return(tc.JavaHomeVersion, nil) } else { - env.On("getenv", "JAVA_HOME").Return("") - } - props := &properties{ - values: map[Property]interface{}{ - DisplayVersion: true, - }, + env.On("Getenv", "JAVA_HOME").Return("") } - j := &java{} - j.init(props, env) - assert.True(t, j.enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) - assert.Equal(t, tc.ExpectedString, j.string(), fmt.Sprintf("Failed in case: %s", tc.Case)) + + j := &Java{} + j.Init(props, env) + assert.True(t, j.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, j.Template(), j), fmt.Sprintf("Failed in case: %s", tc.Case)) } } diff --git a/src/segments/jujutsu.go b/src/segments/jujutsu.go new file mode 100644 index 000000000000..c9388f99aab9 --- /dev/null +++ b/src/segments/jujutsu.go @@ -0,0 +1,188 @@ +package segments + +import ( + "fmt" + "strconv" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +const ( + JUJUTSUCOMMAND = "jj" + + IgnoreWorkingCopy options.Option = "ignore_working_copy" + ChangeIDMinLen options.Option = "change_id_min_len" + FetchAhead options.Option = "fetch_ahead_counter" + AheadIcon options.Option = "ahead_icon" +) + +type JujutsuStatus struct { + ScmStatus +} + +func (s *JujutsuStatus) add(code byte) { + switch code { + case 'D': + s.Deleted++ + case 'A', 'C': // added, copied + s.Added++ + case 'M': + s.Modified++ + case 'R': // renamed + s.Moved++ + } +} + +type Jujutsu struct { + Working *JujutsuStatus + ChangeID string + Scm +} + +func (jj *Jujutsu) Template() string { + return " \uf1fa{{.ChangeID}}{{if .Working.Changed}} \uf044 {{ .Working.String }}{{ end }} " +} + +func (jj *Jujutsu) Enabled() bool { + displayStatus := jj.options.Bool(FetchStatus, false) + + if !jj.shouldDisplay(displayStatus) { + return false + } + + statusFormats := jj.options.KeyValueMap(StatusFormats, map[string]string{}) + jj.Working = &JujutsuStatus{ScmStatus: ScmStatus{Formats: statusFormats}} + + if displayStatus { + jj.setJujutsuStatus() + } + + return true +} + +func (jj *Jujutsu) CacheKey() (string, bool) { + dir, err := jj.env.HasParentFilePath(".jj", true) + if err != nil { + return "", false + } + + return dir.Path, true +} + +func (jj *Jujutsu) ClosestBookmarks() string { + statusString, err := jj.getJujutsuCommandOutput("log", "-r", "heads(::@ & bookmarks())", "--no-graph", "-T", "bookmarks") + if err != nil { + return "" + } + + line, _, _ := strings.Cut(statusString, "\n") + + if !jj.options.Bool(FetchAhead, false) || len(line) == 0 { + return line + } + + aheadIcon := jj.options.String(AheadIcon, "\u21e1") + marks := strings.Split(line, " ") + // String to return for status + var endString strings.Builder + + // Closest bookmarks are all the same distance away from the working copy + // so retrieve the distance to the first one and use it for all of them + + rangeString := strings.Trim(marks[0], "*") + "..@" + + aheadString, err := jj.getJujutsuCommandOutput("log", "--no-graph", "-T", "'.'", "-r", rangeString) + if err != nil { + return line + } + + aheadCounter := len(aheadString) + aheadCounterString := "" + + if aheadCounter != 0 { + aheadCounterString = aheadIcon + strconv.Itoa(aheadCounter) + } + + log.Debug("distance to nearest jj bookmark:" + aheadCounterString) + + // Loop through each bookmark + for index, mark := range marks { + if index > 0 { + endString.WriteString(" ") + } + + endString.WriteString(mark + aheadCounterString) + } + + return endString.String() +} + +func (jj *Jujutsu) shouldDisplay(displayStatus bool) bool { + jjdir, err := jj.env.HasParentFilePath(".jj", false) + if err != nil { + log.Debug("Jujutsu directory not found") + return false + } + + if displayStatus && !jj.hasCommand(JUJUTSUCOMMAND) { + log.Debug("Jujutsu command not found, skipping segment") + return false + } + + jj.setDir(jjdir.ParentFolder) + + jj.mainSCMDir = jjdir.Path + jj.scmDir = jjdir.Path + // convert the worktree file path to a windows one when in a WSL shared folder + jj.repoRootDir = strings.TrimSuffix(jj.convertToWindowsPath(jjdir.Path), "/.jj") + + return true +} + +func (jj *Jujutsu) setDir(dir string) { + dir = path.ReplaceHomeDirPrefixWithTilde(dir) // align with template PWD + if jj.env.GOOS() == runtime.WINDOWS { + jj.Dir = strings.TrimSuffix(dir, `\.jj`) + return + } + + jj.Dir = strings.TrimSuffix(dir, "/.jj") +} + +func (jj *Jujutsu) setJujutsuStatus() { + statusString, err := jj.getJujutsuCommandOutput("log", "-r", "@", "--no-graph", "-T", jj.logTemplate()) + if err != nil { + return + } + + lines := strings.Split(statusString, "\n") + jj.ChangeID = lines[0] + + for _, line := range lines[1:] { + if len(line) > 0 { + jj.Working.add(line[0]) + } + } +} + +func (jj *Jujutsu) logTemplate() string { + // https://jj-vcs.github.io/jj/latest/templates/#commit-keywords + return fmt.Sprintf(`change_id.shortest(%d) ++ "\n" ++ diff.summary()`, jj.options.Int(ChangeIDMinLen, 0)) +} + +func (jj *Jujutsu) getJujutsuCommandOutput(command string, args ...string) (string, error) { + cli := []string{"--repository", jj.repoRootDir, "--no-pager", "--color", "never"} + + if jj.options.Bool(IgnoreWorkingCopy, true) { + cli = append(cli, "--ignore-working-copy") + } + + cli = append(cli, command) + cli = append(cli, args...) + + return jj.env.RunCommand(jj.command, cli...) +} diff --git a/src/segments/jujutsu_test.go b/src/segments/jujutsu_test.go new file mode 100644 index 000000000000..d9a851c84521 --- /dev/null +++ b/src/segments/jujutsu_test.go @@ -0,0 +1,118 @@ +package segments + +import ( + "errors" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/stretchr/testify/assert" +) + +func TestJujutsuEnabledToolNotFound(t *testing.T) { + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasParentFilePath", ".jj", false).Return(&runtime.FileInfo{}, errors.New("not found")) + env.On("GOOS").Return("") + env.On("IsWsl").Return(false) + + jj := &Jujutsu{} + jj.Init(options.Map{}, env) + + assert.False(t, jj.Enabled()) +} + +func TestJujutsuEnabledInWorkingDirectory(t *testing.T) { + fileInfo := &runtime.FileInfo{ + Path: "/dir/hello", + ParentFolder: "/dir", + IsDir: true, + } + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasCommand", "jj").Return(true) + env.On("HasParentFilePath", ".jj", false).Return(fileInfo, nil) + env.On("GOOS").Return("") + + jj := &Jujutsu{} + jj.Init(options.Map{}, env) + + assert.True(t, jj.Enabled()) + assert.Equal(t, fileInfo.Path, jj.mainSCMDir) + assert.Equal(t, fileInfo.Path, jj.repoRootDir) +} + +func TestJujutsuGetIdInfo(t *testing.T) { + cases := []struct { + ExpectedWorking *JujutsuStatus + Case string + LogOutput string + ExpectedChangeID string + }{ + { + Case: "nochanges", + LogOutput: "a\n\n", + ExpectedChangeID: "a", + ExpectedWorking: &JujutsuStatus{ScmStatus{ + Deleted: 0, + Added: 0, + Modified: 0, + Moved: 0, + }}, + }, + { + Case: "changed", + LogOutput: `b +D deleted_file +A added_file +C {copied_file => new_file} +M modified_file +R {renamed_file => new_file} +`, + ExpectedChangeID: "b", + ExpectedWorking: &JujutsuStatus{ScmStatus{ + Deleted: 1, + Added: 2, + Modified: 1, + Moved: 1, + }}, + }, + } + + for _, tc := range cases { + fileInfo := &runtime.FileInfo{ + Path: "/dir/hello", + ParentFolder: "/dir", + IsDir: true, + } + + props := options.Map{ + FetchStatus: true, + } + + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasCommand", "jj").Return(true) + env.On("GOOS").Return("") + env.On("IsWsl").Return(false) + env.On("HasParentFilePath", ".jj", false).Return(fileInfo, nil) + env.On("PathSeparator").Return("/") + env.On("Home").Return(poshHome) + env.On("Getenv", poshGitEnv).Return("") + + jj := &Jujutsu{} + jj.Init(props, env) + env.MockJjCommand(fileInfo.Path, tc.LogOutput, "log", "-r", "@", "--no-graph", "-T", jj.logTemplate()) + + if tc.ExpectedWorking != nil { + tc.ExpectedWorking.Formats = map[string]string{} + } + + assert.True(t, jj.Enabled()) + assert.Equal(t, fileInfo.Path, jj.mainSCMDir) + assert.Equal(t, fileInfo.Path, jj.repoRootDir) + assert.Equal(t, tc.ExpectedWorking, jj.Working, tc.Case) + assert.Equal(t, tc.ExpectedChangeID, jj.ChangeID, tc.Case) + } +} diff --git a/src/segments/julia.go b/src/segments/julia.go new file mode 100644 index 000000000000..0243ff2415dc --- /dev/null +++ b/src/segments/julia.go @@ -0,0 +1,24 @@ +package segments + +type Julia struct { + Language +} + +func (j *Julia) Template() string { + return languageTemplate +} + +func (j *Julia) Enabled() bool { + j.extensions = []string{"*.jl"} + j.tooling = map[string]*cmd{ + juliaToolName: { + executable: juliaToolName, + args: []string{versionFlagArg}, + regex: `julia version ` + versionRegex, + }, + } + j.defaultTooling = []string{juliaToolName} + j.versionURLTemplate = "https://github.com/JuliaLang/julia/releases/tag/v{{ .Full }}" + + return j.Language.Enabled() +} diff --git a/src/segment_julia_test.go b/src/segments/julia_test.go similarity index 71% rename from src/segment_julia_test.go rename to src/segments/julia_test.go index 212aa16b1c0b..5d1469b53304 100644 --- a/src/segment_julia_test.go +++ b/src/segments/julia_test.go @@ -1,4 +1,4 @@ -package main +package segments import ( "fmt" @@ -24,9 +24,9 @@ func TestJulia(t *testing.T) { extension: "*.jl", } env, props := getMockedLanguageEnv(params) - j := &julia{} - j.init(props, env) - assert.True(t, j.enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) - assert.Equal(t, tc.ExpectedString, j.string(), fmt.Sprintf("Failed in case: %s", tc.Case)) + j := &Julia{} + j.Init(props, env) + assert.True(t, j.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, j.Template(), j), fmt.Sprintf("Failed in case: %s", tc.Case)) } } diff --git a/src/segments/kotlin.go b/src/segments/kotlin.go new file mode 100644 index 000000000000..578aba697481 --- /dev/null +++ b/src/segments/kotlin.go @@ -0,0 +1,26 @@ +package segments + +type Kotlin struct { + Language +} + +func (k *Kotlin) Template() string { + return languageTemplate +} + +const kotlinToolName = "kotlin" + +func (k *Kotlin) Enabled() bool { + k.extensions = []string{"*.kt", "*.kts", "*.ktm"} + k.tooling = map[string]*cmd{ + kotlinToolName: { + executable: kotlinToolName, + args: []string{versionFlagShortArg}, + regex: `Kotlin version ` + versionRegex, + }, + } + k.defaultTooling = []string{kotlinToolName} + k.versionURLTemplate = "https://github.com/JetBrains/kotlin/releases/tag/v{{ .Full }}" + + return k.Language.Enabled() +} diff --git a/src/segments/kotlin_test.go b/src/segments/kotlin_test.go new file mode 100644 index 000000000000..5ff9875247c5 --- /dev/null +++ b/src/segments/kotlin_test.go @@ -0,0 +1,32 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKotlin(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + {Case: "Kotlin 1.6.10", ExpectedString: "1.6.10", Version: "Kotlin version 1.6.10-release-923 (JRE 17.0.2+0)"}, + {Case: "Kotlin 1.6.0", ExpectedString: "1.6.0", Version: "Kotlin version 1.6.0-release-915 (JRE 17.0.2+0)"}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "kotlin", + versionParam: "-version", + versionOutput: tc.Version, + extension: "*.kt", + } + env, props := getMockedLanguageEnv(params) + k := &Kotlin{} + k.Init(props, env) + assert.True(t, k.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, k.Template(), k), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/kubectl.go b/src/segments/kubectl.go new file mode 100644 index 000000000000..b0eeed2878d0 --- /dev/null +++ b/src/segments/kubectl.go @@ -0,0 +1,169 @@ +package segments + +import ( + "path/filepath" + + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + yaml "go.yaml.in/yaml/v3" +) + +// Whether to use kubectl or read kubeconfig ourselves +const ( + ParseKubeConfig options.Option = "parse_kubeconfig" + ContextAliases options.Option = "context_aliases" + ClusterAliases options.Option = "cluster_aliases" +) + +type Kubectl struct { + Base + + KubeContext + Context string + dirty bool +} + +type KubeConfig struct { + CurrentContext string `yaml:"current-context"` + Contexts []struct { + Context *KubeContext `yaml:"context"` + Name string `yaml:"name"` + } `yaml:"contexts"` +} + +type KubeContext struct { + Cluster string `yaml:"cluster"` + User string `yaml:"user"` + Namespace string `yaml:"namespace"` +} + +func (k *Kubectl) Template() string { + return " {{ .Context }}{{ if .Namespace }} :: {{ .Namespace }}{{ end }} " +} + +func (k *Kubectl) Enabled() bool { + parseKubeConfig := k.options.Bool(ParseKubeConfig, true) + + if parseKubeConfig { + return k.doParseKubeConfig() + } + + return k.doCallKubectl() +} + +func (k *Kubectl) doParseKubeConfig() bool { + // Follow kubectl search rules (see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable) + // TL;DR: KUBECONFIG can contain a list of files. If it's empty ~/.kube/config is used. First file in list wins when merging keys. + kubeconfigs := filepath.SplitList(k.env.Getenv("KUBECONFIG")) + if len(kubeconfigs) == 0 { + kubeconfigs = []string{filepath.Join(k.env.Home(), ".kube/config")} + } + + contexts := make(map[string]*KubeContext) + k.Context = "" + + for _, kubeconfig := range kubeconfigs { + if kubeconfig == "" { + continue + } + + content := k.env.FileContent(kubeconfig) + + var config KubeConfig + err := yaml.Unmarshal([]byte(content), &config) + if err != nil { + continue + } + + for _, context := range config.Contexts { + if _, exists := contexts[context.Name]; !exists { + contexts[context.Name] = context.Context + } + } + + if k.Context == "" { + k.Context = config.CurrentContext + } + + context, exists := contexts[k.Context] + if !exists { + continue + } + + if context != nil { + k.KubeContext = *context + } + + k.SetContextAlias() + k.SetClusterAlias() + k.dirty = true + + return true + } + + displayError := k.options.Bool(options.DisplayError, false) + if !displayError { + return false + } + k.setError("KUBECONFIG ERR") + return true +} + +func (k *Kubectl) doCallKubectl() bool { + cmd := "kubectl" + if !k.env.HasCommand(cmd) { + return false + } + + result, err := k.env.RunCommand(cmd, "config", "view", "--output", "yaml", "--minify") + displayError := k.options.Bool(options.DisplayError, false) + if err != nil && displayError { + k.setError("KUBECTL ERR") + return true + } + + if err != nil { + return false + } + + var config KubeConfig + err = yaml.Unmarshal([]byte(result), &config) + if err != nil { + return false + } + + k.Context = config.CurrentContext + k.SetContextAlias() + k.dirty = true + + if len(config.Contexts) > 0 { + k.KubeContext = *config.Contexts[0].Context + k.SetClusterAlias() + } + + return true +} + +func (k *Kubectl) setError(message string) { + if k.Context == "" { + k.Context = message + } + + k.Namespace = message + k.User = message + k.Cluster = message +} + +func (k *Kubectl) SetContextAlias() { + aliases := k.options.KeyValueMap(ContextAliases, map[string]string{}) + if alias, exists := aliases[k.Context]; exists { + k.Context = alias + } +} + +func (k *Kubectl) SetClusterAlias() { + aliases := k.options.KeyValueMap(ClusterAliases, map[string]string{}) + if alias, exists := aliases[k.Cluster]; exists { + k.Cluster = alias + } +} diff --git a/src/segments/kubectl_test.go b/src/segments/kubectl_test.go new file mode 100644 index 000000000000..30c371bb4fc1 --- /dev/null +++ b/src/segments/kubectl_test.go @@ -0,0 +1,249 @@ +package segments + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +const ( + testKubectlAllInfoTemplate = "{{.Context}} :: {{.Namespace}} :: {{.User}} :: {{.Cluster}}" + contextMarker = "currentcontextmarker" +) + +func TestKubectlSegment(t *testing.T) { + standardTemplate := "{{.Context}}{{if .Namespace}} :: {{.Namespace}}{{end}}" + lsep := string(filepath.ListSeparator) + + cases := []struct { + Files map[string]string + ContextAliases map[string]string + ClusterAliases map[string]string + Cluster string + Kubeconfig string + Context string + Namespace string + UserName string + Case string + ExpectedString string + Template string + KubectlExists bool + ParseKubeConfig bool + KubectlErr bool + ExpectedEnabled bool + DisplayError bool + }{ + { + Case: "kubeconfig incomplete", + Template: testKubectlAllInfoTemplate, + ParseKubeConfig: true, + Kubeconfig: contextMarker + lsep + "contextdefinitionincomplete", + Files: testKubeConfigFiles, + ExpectedString: "ctx :: :: ::", + ExpectedEnabled: true, + }, + {Case: "disabled", Template: standardTemplate, KubectlExists: false, Context: "aaa", Namespace: "bbb", ExpectedEnabled: false}, + { + Case: "all information", + Template: testKubectlAllInfoTemplate, + KubectlExists: true, + Context: "aaa", + Namespace: "bbb", + UserName: "ccc", + Cluster: "ddd", + ExpectedString: "aaa :: bbb :: ccc :: ddd", + ExpectedEnabled: true, + }, + {Case: "no namespace", Template: standardTemplate, KubectlExists: true, Context: "aaa", ExpectedString: "aaa", ExpectedEnabled: true}, + { + Case: "kubectl context alias", + Template: standardTemplate, + KubectlExists: true, + Context: "aaa", + Namespace: "bbb", + ContextAliases: map[string]string{"aaa": "ccc"}, + ExpectedString: "ccc :: bbb", + ExpectedEnabled: true, + }, + { + Case: "kubectl error", + Template: standardTemplate, + DisplayError: true, + KubectlExists: true, + Context: "aaa", + Namespace: "bbb", + KubectlErr: true, + ExpectedString: "KUBECTL ERR :: KUBECTL ERR", + ExpectedEnabled: true, + }, + {Case: "kubectl error hidden", Template: standardTemplate, DisplayError: false, KubectlExists: true, Context: "aaa", Namespace: "bbb", KubectlErr: true, ExpectedEnabled: false}, + { + Case: "kubeconfig home", + Template: testKubectlAllInfoTemplate, + ParseKubeConfig: true, + Files: testKubeConfigFiles, + ExpectedString: "aaa :: bbb :: ccc :: ddd", + ExpectedEnabled: true, + }, + { + Case: "kubeconfig context alias", + Template: standardTemplate, + ParseKubeConfig: true, + Files: testKubeConfigFiles, + ContextAliases: map[string]string{"aaa": "ccc"}, + ExpectedString: "ccc :: bbb", + ExpectedEnabled: true, + }, + { + Case: "kubectl cluster alias", + Template: testKubectlAllInfoTemplate, + KubectlExists: true, + Context: "aaa", + Namespace: "bbb", + UserName: "ccc", + Cluster: "ddd", + ClusterAliases: map[string]string{"ddd": "production"}, + ExpectedString: "aaa :: bbb :: ccc :: production", + ExpectedEnabled: true, + }, + { + Case: "kubeconfig cluster alias", + Template: testKubectlAllInfoTemplate, + ParseKubeConfig: true, + Files: testKubeConfigFiles, + ClusterAliases: map[string]string{"ddd": "prod"}, + ExpectedString: "aaa :: bbb :: ccc :: prod", + ExpectedEnabled: true, + }, + { + Case: "kubeconfig context and cluster alias", + Template: testKubectlAllInfoTemplate, + ParseKubeConfig: true, + Files: testKubeConfigFiles, + ContextAliases: map[string]string{"aaa": "my-context"}, + ClusterAliases: map[string]string{"ddd": "my-cluster"}, + ExpectedString: "my-context :: bbb :: ccc :: my-cluster", + ExpectedEnabled: true, + }, + { + Case: "kubeconfig multiple current marker first", + Template: testKubectlAllInfoTemplate, + ParseKubeConfig: true, + Kubeconfig: "" + lsep + contextMarker + lsep + "contextdefinition" + lsep + "contextredefinition", + Files: testKubeConfigFiles, + ExpectedString: "ctx :: ns :: usr :: cl", + ExpectedEnabled: true, + }, + { + Case: "kubeconfig multiple context first", + Template: testKubectlAllInfoTemplate, ParseKubeConfig: true, + Kubeconfig: "contextdefinition" + lsep + "contextredefinition" + lsep + contextMarker + lsep, + Files: testKubeConfigFiles, + ExpectedString: "ctx :: ns :: usr :: cl", + ExpectedEnabled: true, + }, + { + Case: "kubeconfig error hidden", Template: testKubectlAllInfoTemplate, ParseKubeConfig: true, Kubeconfig: "invalid", Files: testKubeConfigFiles, ExpectedEnabled: false}, + { + Case: "kubeconfig error", + Template: testKubectlAllInfoTemplate, + ParseKubeConfig: true, + Kubeconfig: "invalid", + Files: testKubeConfigFiles, + DisplayError: true, + ExpectedString: "KUBECONFIG ERR :: KUBECONFIG ERR :: KUBECONFIG ERR :: KUBECONFIG ERR", + ExpectedEnabled: true, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("HasCommand", "kubectl").Return(tc.KubectlExists) + + var kubeconfig string + content, err := os.ReadFile("../test/kubectl.yml") + if err == nil { + kubeconfig = fmt.Sprintf(string(content), tc.Cluster, tc.UserName, tc.Namespace, tc.Context) + } + + var kubectlErr error + if tc.KubectlErr { + kubectlErr = &runtime.CommandError{ + Err: "oops", + ExitCode: 1, + } + } + + env.On("RunCommand", "kubectl", []string{"config", "view", "--output", "yaml", "--minify"}).Return(kubeconfig, kubectlErr) + env.On("Getenv", "KUBECONFIG").Return(tc.Kubeconfig) + + for path, content := range tc.Files { + env.On("FileContent", path).Return(content) + } + + env.On("Home").Return("testhome") + + props := options.Map{ + options.DisplayError: tc.DisplayError, + ParseKubeConfig: tc.ParseKubeConfig, + ContextAliases: tc.ContextAliases, + ClusterAliases: tc.ClusterAliases, + } + + k := &Kubectl{} + k.Init(props, env) + + assert.Equal(t, tc.ExpectedEnabled, k.Enabled(), tc.Case) + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, k), tc.Case) + } + } +} + +var testKubeConfigFiles = map[string]string{ + filepath.Join("testhome", ".kube/config"): ` +apiVersion: v1 +contexts: + - context: + cluster: ddd + user: ccc + namespace: bbb + name: aaa +current-context: aaa +`, + "contextdefinition": ` +apiVersion: v1 +contexts: + - context: + cluster: cl + user: usr + namespace: ns + name: ctx +`, + contextMarker: ` +apiVersion: v1 +current-context: ctx +`, + "invalid": "this is not yaml", + "contextdefinitionincomplete": ` +apiVersion: v1 +contexts: + - name: ctx +`, + "contextredefinition": ` +apiVersion: v1 +contexts: + - context: + cluster: wrongcl + user: wrongu + namespace: wrongns + name: ctx +`, +} diff --git a/src/segments/language.go b/src/segments/language.go new file mode 100644 index 000000000000..95b209ee0a19 --- /dev/null +++ b/src/segments/language.go @@ -0,0 +1,387 @@ +package segments + +import ( + "encoding/json" + "errors" + "fmt" + "path/filepath" + runtime_ "runtime" + + "slices" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/template" +) + +const ( + languageTemplate = " {{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }} " + noVersion = "NO VERSION" + + versionFlagArg = "--version" + versionFlagShortArg = "-version" + versionArg = "version" + + versionRegex = `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))` + versionRegexPrefixed = `(?:(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))` + versionRegexSemver = `(?:(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)(-(?P[a-z]+).(?P[0-9]+))?)))` + + fileName = "package.json" + + asdfToolName = "asdf" + bunToolName = "bun" + dartToolName = "dart" + denoToolName = "deno" + dotnetToolName = "dotnet" + fvmToolName = "fvm" + juliaToolName = "julia" + mojoToolName = "mojo" + nodeToolName = "node" + npmToolName = "npm" + phpToolName = "php" + pnpmToolName = "pnpm" + pythonToolName = "python" + yarnToolName = "yarn" +) + +type loadContext func() + +type inContext func() bool + +type getVersion func() (string, error) +type matchesVersionFile func() (string, bool) + +type Version struct { + Full string + Major string + Minor string + Patch string + Prerelease string + BuildMetadata string + URL string + Executable string + Expected string +} + +type cmd struct { + getVersion getVersion + executable string + regex string + versionURLTemplate string + args []string +} + +func (c *cmd) parse(versionInfo string) (*Version, error) { + values := regex.FindNamedRegexMatch(c.regex, versionInfo) + if len(values) == 0 { + return nil, errors.New("cannot parse version string") + } + + version := &Version{ + Full: values["version"], + Major: values["major"], + Minor: values["minor"], + Patch: values["patch"], + Prerelease: values["prerelease"], + BuildMetadata: values["buildmetadata"], + } + return version, nil +} + +type Language struct { + Base + + projectRoot *runtime.FileInfo + loadContext loadContext + inContext inContext + matchesVersionFile matchesVersionFile + Version + displayMode string + Error string + versionURLTemplate string + name string + commands []*cmd + tooling map[string]*cmd + defaultTooling []string + projectFiles []string + folders []string + extensions []string + exitCode int + homeEnabled bool + Mismatch bool +} + +const ( + // DisplayMode sets the display mode (always, when_in_context, never) + DisplayMode options.Option = "display_mode" + // DisplayModeAlways displays the segment always + DisplayModeAlways string = "always" + // DisplayModeFiles displays the segment when the current folder contains certain extensions + DisplayModeFiles string = "files" + // DisplayModeEnvironment displays the segment when the environment has a language's context + DisplayModeEnvironment string = "environment" + // DisplayModeContext displays the segment when the environment or files is active + DisplayModeContext string = "context" + // MissingCommandText sets the text to display when the command is not present in the system + MissingCommandText options.Option = "missing_command_text" + // HomeEnabled displays the segment in the HOME folder or not + HomeEnabled options.Option = "home_enabled" + // LanguageExtensions the list of extensions to validate + LanguageExtensions options.Option = "extensions" + // LanguageFolders the list of folders to validate + LanguageFolders options.Option = "folders" + // Tooling allows enabling additional version fetching tools + Tooling options.Option = "tooling" +) + +func (l *Language) getName() string { + _, file, _, _ := runtime_.Caller(2) + base := filepath.Base(file) + return base[:len(base)-3] +} + +func (l *Language) Enabled() bool { + l.name = l.getName() + // override default extensions if needed + l.extensions = l.options.StringArray(LanguageExtensions, l.extensions) + l.folders = l.options.StringArray(LanguageFolders, l.folders) + inHomeDir := func() bool { + return l.env.Pwd() == l.env.Home() + } + + var enabled bool + + homeEnabled := l.options.Bool(HomeEnabled, l.homeEnabled) + if inHomeDir() && !homeEnabled { + return false + } + + if len(l.projectFiles) != 0 && l.hasProjectFiles() { + enabled = true + } + + if !enabled { + // set default mode when not set + if l.displayMode == "" { + l.displayMode = l.options.String(DisplayMode, DisplayModeFiles) + } + + l.loadLanguageContext() + + switch l.displayMode { + case DisplayModeAlways: + enabled = true + case DisplayModeEnvironment: + enabled = l.inLanguageContext() + case DisplayModeFiles: + enabled = l.hasLanguageFiles() || l.hasLanguageFolders() + case DisplayModeContext: + fallthrough + default: + enabled = l.hasLanguageFiles() || l.hasLanguageFolders() || l.inLanguageContext() || l.hasProjectFiles() + } + } + + l.loadTooling() + + if !enabled || !l.options.Bool(options.FetchVersion, true) { + return enabled + } + + err := l.setVersion() + if err != nil { + l.Error = err.Error() + } + + if l.matchesVersionFile != nil { + expected, match := l.matchesVersionFile() + if !match { + l.Mismatch = true + l.Expected = expected + } + } + + return enabled +} + +// loadTooling builds the commands list from the tooling map based on the tooling configuration. +// Users can override the default tooling via the Tooling option. +// This allows specifying which tools should be used to fetch versions +// (e.g., "uv" for Python to use UV package manager). +func (l *Language) loadTooling() { + enabledTools := l.options.StringArray(Tooling, l.defaultTooling) + if len(enabledTools) == 0 { + return + } + + var commands []*cmd + for _, tool := range enabledTools { + if command, exists := l.tooling[tool]; exists { + commands = append(commands, command) + } + } + + l.commands = commands +} + +func (l *Language) hasLanguageFiles() bool { + return slices.ContainsFunc(l.extensions, l.env.HasFiles) +} + +func (l *Language) hasProjectFiles() bool { + for _, extension := range l.projectFiles { + if configPath, err := l.env.HasParentFilePath(extension, false); err == nil { + l.projectRoot = configPath + return true + } + } + + return false +} + +func (l *Language) hasLanguageFolders() bool { + return slices.ContainsFunc(l.folders, l.env.HasFolder) +} + +// setVersion parses the version string returned by the command +func (l *Language) setVersion() error { + var lastError error + + cacheKey := fmt.Sprintf("version_%s", l.name) + + if versionCache, OK := cache.Get[Version](cache.Device, cacheKey); OK { + l.Version = versionCache + return nil + } + + for _, command := range l.commands { + versionStr, err := l.runCommand(command) + if err != nil { + log.Error(err) + lastError = err + continue + } + + version, err := command.parse(versionStr) + if err != nil { + log.Error(err) + lastError = fmt.Errorf("err parsing info from %s with %s", command.executable, versionStr) + continue + } + + l.Version = *version + if command.versionURLTemplate != "" { + l.versionURLTemplate = command.versionURLTemplate + } + + l.buildVersionURL() + l.Executable = command.executable + + duration := l.options.String(options.CacheDuration, string(cache.NONE)) + cache.Set(cache.Device, cacheKey, l.Version, cache.Duration(duration)) + + return nil + } + + if lastError != nil { + return lastError + } + + return errors.New(l.options.String(MissingCommandText, "")) +} + +func (l *Language) runCommand(command *cmd) (string, error) { + if command.getVersion == nil { + if !l.env.HasCommand(command.executable) { + return "", errors.New(noVersion) + } + + versionStr, err := l.env.RunCommand(command.executable, command.args...) + if exitErr, ok := err.(*runtime.CommandError); ok { + l.exitCode = exitErr.ExitCode + return "", fmt.Errorf("err executing %s with %s", command.executable, command.args) + } + + return versionStr, nil + } + + versionStr, err := command.getVersion() + if err != nil { + return "", err + } + + if versionStr == "" { + return "", errors.New("no version found") + } + + return versionStr, nil +} + +func (l *Language) loadLanguageContext() { + if l.loadContext == nil { + return + } + l.loadContext() +} + +func (l *Language) inLanguageContext() bool { + if l.inContext == nil { + return false + } + return l.inContext() +} + +func (l *Language) buildVersionURL() { + versionURLTemplate := l.options.String(options.VersionURLTemplate, l.versionURLTemplate) + if versionURLTemplate == "" { + return + } + + url, err := template.Render(versionURLTemplate, l.Version) + if err != nil { + return + } + + l.URL = url +} + +func (l *Language) hasNodePackage(name string) bool { + packageJSON := l.env.FileContent(fileName) + + var packageData map[string]any + if err := json.Unmarshal([]byte(packageJSON), &packageData); err != nil { + return false + } + + dependencies, ok := packageData["dependencies"].(map[string]any) + if !ok { + return false + } + + if _, exists := dependencies[name]; !exists { + return false + } + + return true +} + +func (l *Language) nodePackageVersion(name string) (string, error) { + folder := filepath.Join(l.env.Pwd(), "node_modules", name) + + if !l.env.HasFilesInDir(folder, fileName) { + return "", fmt.Errorf("%s not found in %s", fileName, folder) + } + + content := l.env.FileContent(filepath.Join(folder, fileName)) + var data ProjectData + err := json.Unmarshal([]byte(content), &data) + + if err != nil { + return "", err + } + + return data.Version, nil +} diff --git a/src/segments/language_test.go b/src/segments/language_test.go new file mode 100644 index 000000000000..82b05590874c --- /dev/null +++ b/src/segments/language_test.go @@ -0,0 +1,673 @@ +package segments + +import ( + "path/filepath" + "slices" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +const ( + universion = "1.3.307" + uni = "*.uni" + corn = "*.corn" +) + +type languageArgs struct { + expectedError error + options options.Provider + matchesVersionFile matchesVersionFile + version string + versionURLTemplate string + extensions []string + enabledExtensions []string + commands []*cmd + enabledCommands []string + inHome bool +} + +func (l *languageArgs) hasvalue(value string, list []string) bool { + return slices.Contains(list, value) +} + +func bootStrapLanguageTest(args *languageArgs) *Language { + env := new(mock.Environment) + + for _, command := range args.commands { + env.On("HasCommand", command.executable).Return(args.hasvalue(command.executable, args.enabledCommands)) + env.On("RunCommand", command.executable, command.args).Return(args.version, args.expectedError) + } + + for _, extension := range args.extensions { + env.On("HasFiles", extension).Return(args.hasvalue(extension, args.enabledExtensions)) + } + + home := "/usr/home" + cwd := "/usr/home/project" + if args.inHome { + cwd = home + } + + env.On("Pwd").Return(cwd) + env.On("Home").Return(home) + + if args.options == nil { + args.options = options.Map{} + } + + l := &Language{ + extensions: args.extensions, + commands: args.commands, + versionURLTemplate: args.versionURLTemplate, + matchesVersionFile: args.matchesVersionFile, + } + l.Init(args.options, env) + + return l +} + +func TestLanguageFilesFoundButNoCommandAndVersionAndDisplayVersion(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + }, + }, + extensions: []string{uni}, + enabledExtensions: []string{uni}, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, noVersion, lang.Error, "unicorn is not available") +} + +func TestLanguageFilesFoundButNoCommandAndVersionAndDontDisplayVersion(t *testing.T) { + props := options.Map{ + options.FetchVersion: false, + } + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + }, + }, + extensions: []string{uni}, + enabledExtensions: []string{uni}, + options: props, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled(), "unicorn is not available") +} + +func TestLanguageFilesFoundButNoCommandAndNoVersion(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + }, + }, + extensions: []string{uni}, + enabledExtensions: []string{uni}, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled(), "unicorn is not available") +} + +func TestLanguageDisabledNoFiles(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + }, + }, + extensions: []string{uni}, + enabledExtensions: []string{}, + enabledCommands: []string{"unicorn"}, + } + lang := bootStrapLanguageTest(args) + assert.False(t, lang.Enabled(), "no files in the current directory") +} + +func TestLanguageEnabledOneExtensionFound(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + regex: "(?P.*)", + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni}, + enabledCommands: []string{"unicorn"}, + version: universion, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, universion, lang.Full, "unicorn is available and uni files are found") + assert.Equal(t, "unicorn", lang.Executable, "unicorn was used") +} + +func TestLanguageEnabledMismatch(t *testing.T) { + expectedVersion := "1.2.009" + + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + regex: "(?P.*)", + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni}, + enabledCommands: []string{"unicorn"}, + version: universion, + matchesVersionFile: func() (string, bool) { + return expectedVersion, false + }, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, expectedVersion, lang.Expected, "the expected unicorn version is 1.2.009") + assert.True(t, lang.Mismatch, "we require a different version of unicorn") +} + +func TestLanguageDisabledInHome(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + regex: "(?P.*)", + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni}, + enabledCommands: []string{"unicorn"}, + version: universion, + inHome: true, + } + lang := bootStrapLanguageTest(args) + assert.False(t, lang.Enabled()) +} + +func TestLanguageEnabledSecondExtensionFound(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + regex: "(?P.*)", + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{corn}, + enabledCommands: []string{"unicorn"}, + version: universion, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, universion, lang.Full, "unicorn is available and corn files are found") + assert.Equal(t, "unicorn", lang.Executable, "unicorn was used") +} + +func TestLanguageEnabledSecondCommand(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "uni", + args: []string{"--version"}, + regex: "(?P.*)", + }, + { + executable: "corn", + args: []string{"--version"}, + regex: "(?P.*)", + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{corn}, + enabledCommands: []string{"corn"}, + version: universion, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, universion, lang.Full, "unicorn is available and corn files are found") + assert.Equal(t, "corn", lang.Executable, "corn was used") +} + +func TestLanguageEnabledAllExtensionsFound(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + regex: "(?P.*)", + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni, corn}, + enabledCommands: []string{"unicorn"}, + version: universion, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, universion, lang.Full, "unicorn is available and uni and corn files are found") + assert.Equal(t, "unicorn", lang.Executable, "unicorn was used") +} + +func TestLanguageEnabledNoVersion(t *testing.T) { + props := options.Map{ + options.FetchVersion: false, + } + args := &languageArgs{ + commands: []*cmd{ + { + executable: "unicorn", + args: []string{"--version"}, + regex: "(?P.*)", + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni, corn}, + enabledCommands: []string{"unicorn"}, + version: universion, + options: props, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, "", lang.Full, "unicorn is available and uni and corn files are found") + assert.Equal(t, "", lang.Executable, "no version was found") +} + +func TestLanguageEnabledMissingCommand(t *testing.T) { + props := options.Map{ + options.FetchVersion: false, + } + args := &languageArgs{ + commands: []*cmd{}, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni, corn}, + enabledCommands: []string{"unicorn"}, + version: universion, + options: props, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, "", lang.Full, "unicorn is unavailable and uni and corn files are found") + assert.Equal(t, "", lang.Executable, "no executable was found") +} + +func TestLanguageEnabledNoVersionData(t *testing.T) { + props := options.Map{ + options.FetchVersion: true, + } + args := &languageArgs{ + commands: []*cmd{ + { + executable: "uni", + args: []string{"--version"}, + regex: `(?:Python (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni, corn}, + enabledCommands: []string{"uni"}, + version: "", + options: props, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, "", lang.Full) + assert.Equal(t, "", lang.Executable, "no version was found") +} + +func TestLanguageEnabledMissingCommandCustomText(t *testing.T) { + expected := "missing" + props := options.Map{ + MissingCommandText: expected, + } + args := &languageArgs{ + commands: []*cmd{}, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni, corn}, + enabledCommands: []string{"unicorn"}, + version: universion, + options: props, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, expected, lang.Error, "unicorn is available and uni and corn files are found") +} + +func TestLanguageEnabledMissingCommandCustomTextHideError(t *testing.T) { + props := options.Map{MissingCommandText: "missing"} + args := &languageArgs{ + commands: []*cmd{}, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni, corn}, + enabledCommands: []string{"unicorn"}, + version: universion, + options: props, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, "", lang.Full) +} + +func TestLanguageEnabledCommandExitCode(t *testing.T) { + expected := 200 + args := &languageArgs{ + commands: []*cmd{ + { + executable: "uni", + args: []string{"--version"}, + regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{uni, corn}, + enabledCommands: []string{"uni"}, + version: universion, + expectedError: &runtime.CommandError{ExitCode: expected}, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, "err executing uni with [--version]", lang.Error) + assert.Equal(t, expected, lang.exitCode) +} + +func TestLanguageHyperlinkEnabled(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "uni", + args: []string{"--version"}, + regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + }, + { + executable: "corn", + args: []string{"--version"}, + regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + }, + }, + versionURLTemplate: "https://unicor.org/doc/{{ .Full }}", + extensions: []string{uni, corn}, + enabledExtensions: []string{corn}, + enabledCommands: []string{"corn"}, + version: universion, + options: options.Map{}, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, "https://unicor.org/doc/1.3.307", lang.URL) +} + +func TestLanguageHyperlinkEnabledWrongRegex(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "uni", + args: []string{"--version"}, + regex: `wrong`, + }, + { + executable: "corn", + args: []string{"--version"}, + regex: `wrong`, + }, + }, + versionURLTemplate: "https://unicor.org/doc/{{ .Full }}", + extensions: []string{uni, corn}, + enabledExtensions: []string{corn}, + enabledCommands: []string{"corn"}, + version: universion, + options: options.Map{}, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, "err parsing info from corn with 1.3.307", lang.Error) +} + +func TestLanguageEnabledInHome(t *testing.T) { + cases := []struct { + Case string + HomeEnabled bool + ExpectedEnabled bool + }{ + {Case: "Always enabled", HomeEnabled: true, ExpectedEnabled: true}, + {Case: "Context disabled", HomeEnabled: false, ExpectedEnabled: false}, + } + for _, tc := range cases { + props := options.Map{ + HomeEnabled: tc.HomeEnabled, + } + args := &languageArgs{ + commands: []*cmd{ + { + executable: "uni", + args: []string{"--version"}, + regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + }, + }, + extensions: []string{uni, corn}, + enabledExtensions: []string{corn}, + enabledCommands: []string{"corn"}, + version: universion, + options: props, + inHome: true, + } + lang := bootStrapLanguageTest(args) + assert.Equal(t, tc.ExpectedEnabled, lang.Enabled(), tc.Case) + } +} + +func TestLanguageInnerHyperlink(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "uni", + args: []string{"--version"}, + regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + versionURLTemplate: "https://uni.org/release/{{ .Full }}", + }, + { + executable: "corn", + args: []string{"--version"}, + regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + versionURLTemplate: "https://unicor.org/doc/{{ .Full }}", + }, + }, + versionURLTemplate: "This gets replaced with inner template", + extensions: []string{uni, corn}, + enabledExtensions: []string{corn}, + enabledCommands: []string{"corn"}, + version: universion, + options: options.Map{}, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, "https://unicor.org/doc/1.3.307", lang.URL) +} + +func TestLanguageHyperlinkTemplatePropertyTakesPriority(t *testing.T) { + args := &languageArgs{ + commands: []*cmd{ + { + executable: "uni", + args: []string{"--version"}, + regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + versionURLTemplate: "https://uni.org/release/{{ .Full }}", + }, + }, + extensions: []string{uni}, + enabledExtensions: []string{uni}, + enabledCommands: []string{"uni"}, + version: universion, + options: options.Map{ + options.VersionURLTemplate: "https://custom/url/template/{{ .Major }}.{{ .Minor }}", + }, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.Enabled()) + assert.Equal(t, "https://custom/url/template/1.3", lang.URL) +} + +func TestLanguageTooling(t *testing.T) { + cases := []struct { + Case string + ExpectedFirst string + ExpectedVersion string + ToolVersion string + DefaultVersion string + Tooling []string + DefaultTooling []string + EnabledTools []string + }{ + { + Case: "Custom tooling overrides default", + Tooling: []string{"mytool"}, + DefaultTooling: []string{"unicorn"}, + EnabledTools: []string{"mytool", "unicorn"}, + ExpectedFirst: "mytool", + ExpectedVersion: "2.0.0", + ToolVersion: "2.0.0", + DefaultVersion: "1.0.0", + }, + { + Case: "Default tooling used when no override", + Tooling: nil, + DefaultTooling: []string{"unicorn"}, + EnabledTools: []string{"mytool", "unicorn"}, + ExpectedFirst: "unicorn", + ExpectedVersion: "1.0.0", + ToolVersion: "2.0.0", + DefaultVersion: "1.0.0", + }, + { + Case: "Tool not available falls back to next", + Tooling: []string{"mytool", "unicorn"}, + DefaultTooling: []string{"unicorn"}, + EnabledTools: []string{"unicorn"}, + ExpectedFirst: "mytool", + ExpectedVersion: "1.0.0", + ToolVersion: "", + DefaultVersion: "1.0.0", + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Pwd").Return("/usr/home/project") + env.On("Home").Return("/usr/home") + env.On("HasFiles", uni).Return(true) + + hasUnicorn := slices.Contains(tc.EnabledTools, "unicorn") + env.On("HasCommand", "unicorn").Return(hasUnicorn) + if hasUnicorn { + env.On("RunCommand", "unicorn", []string{"--version"}).Return(tc.DefaultVersion, nil) + } + + hasToolCommand := slices.Contains(tc.EnabledTools, "mytool") + env.On("HasCommand", "mytool").Return(hasToolCommand) + if hasToolCommand { + env.On("RunCommand", "mytool", []string{"--version"}).Return(tc.ToolVersion, nil) + } + + props := options.Map{ + options.FetchVersion: true, + } + if tc.Tooling != nil { + props[Tooling] = tc.Tooling + } + + l := &Language{ + extensions: []string{uni}, + defaultTooling: tc.DefaultTooling, + tooling: map[string]*cmd{ + "unicorn": { + executable: "unicorn", + args: []string{"--version"}, + regex: "(?P.*)", + }, + "mytool": { + executable: "mytool", + args: []string{"--version"}, + regex: "(?P.*)", + }, + }, + } + l.Init(props, env) + + assert.True(t, l.Enabled(), tc.Case) + assert.Equal(t, tc.ExpectedFirst, l.commands[0].executable, tc.Case) + assert.Equal(t, tc.ExpectedVersion, l.Full, tc.Case) + } +} + +type mockedLanguageParams struct { + cmd string + versionParam string + versionOutput string + extension string +} + +func getMockedLanguageEnv(params *mockedLanguageParams) (*mock.Environment, options.Map) { + env := new(mock.Environment) + env.On("HasCommand", params.cmd).Return(true) + env.On("RunCommand", params.cmd, []string{params.versionParam}).Return(params.versionOutput, nil) + env.On("HasFiles", params.extension).Return(true) + env.On("Pwd").Return("/usr/home/project") + env.On("Home").Return("/usr/home") + + props := options.Map{ + options.FetchVersion: true, + } + + return env, props +} + +func TestNodePackageVersion(t *testing.T) { + cases := []struct { + Case string + PackageJSON string + Version string + ShouldFail bool + NoFiles bool + }{ + {Case: "14.1.5", Version: "14.1.5", PackageJSON: "{ \"name\": \"nx\",\"version\": \"14.1.5\"}"}, + {Case: "14.0.0", Version: "14.0.0", PackageJSON: "{ \"name\": \"nx\",\"version\": \"14.0.0\"}"}, + {Case: "no files", NoFiles: true, ShouldFail: true}, + {Case: "bad data", ShouldFail: true, PackageJSON: "bad data"}, + } + + for _, tc := range cases { + var env = new(mock.Environment) + env.On("Pwd").Return("posh") + path := filepath.Join("posh", "node_modules", "nx") + env.On("HasFilesInDir", path, "package.json").Return(!tc.NoFiles) + env.On("FileContent", filepath.Join(path, "package.json")).Return(tc.PackageJSON) + + a := &Language{} + a.Init(options.Map{}, env) + got, err := a.nodePackageVersion("nx") + + if tc.ShouldFail { + assert.Error(t, err, tc.Case) + return + } + + assert.Nil(t, err, tc.Case) + assert.Equal(t, tc.Version, got, tc.Case) + } +} diff --git a/src/segments/lastfm.go b/src/segments/lastfm.go new file mode 100644 index 000000000000..e51c6c492979 --- /dev/null +++ b/src/segments/lastfm.go @@ -0,0 +1,118 @@ +package segments + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type LastFM struct { + Base + + Artist string + Track string + Full string + Icon string + Status string +} + +const ( + // LastFM username + Username options.Option = "username" +) + +type lmfDate struct { + UnixString string `json:"uts"` +} + +type lfmTrackInfo struct { + IsPlaying *string `json:"nowplaying,omitempty"` +} + +type Artist struct { + Name string `json:"#text"` +} + +type lfmTrack struct { + Artist `json:"artist"` + Name string `json:"name"` + Info *lfmTrackInfo `json:"@attr"` + Date lmfDate `json:"date"` +} + +type tracks struct { + Tracks []*lfmTrack `json:"track"` +} + +type lfmDataResponse struct { + TracksInfo tracks `json:"recenttracks"` +} + +func (d *LastFM) Enabled() bool { + err := d.setStatus() + + if err != nil { + log.Error(err) + return false + } + + return true +} + +func (d *LastFM) Template() string { + return " {{ .Icon }}{{ if ne .Status \"stopped\" }}{{ .Full }}{{ end }} " +} + +func (d *LastFM) getResult() (*lfmDataResponse, error) { + response := new(lfmDataResponse) + + apikey := d.options.Template(APIKey, ".", d) + username := d.options.Template(Username, ".", d) + httpTimeout := d.options.Int(options.HTTPTimeout, options.DefaultHTTPTimeout) + + url := fmt.Sprintf("https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&api_key=%s&user=%s&format=json&limit=1", apikey, username) + + body, err := d.env.HTTPRequest(url, nil, httpTimeout) + if err != nil { + return new(lfmDataResponse), err + } + + err = json.Unmarshal(body, &response) + if err != nil { + return new(lfmDataResponse), err + } + + return response, nil +} + +func (d *LastFM) setStatus() error { + q, err := d.getResult() + if err != nil { + return err + } + + if len(q.TracksInfo.Tracks) == 0 { + return errors.New("no data found") + } + + track := q.TracksInfo.Tracks[0] + + d.Artist = track.Artist.Name + d.Track = track.Name + d.Full = fmt.Sprintf("%s - %s", d.Artist, d.Track) + + isPlaying := track.Info != nil && track.Info.IsPlaying != nil && *track.Info.IsPlaying == "true" + + if isPlaying { + d.Icon = d.options.String(PlayingIcon, "\uE602 ") + d.Status = "playing" + } else { + d.Icon = d.options.String(StoppedIcon, "\uF04D ") + d.Status = "stopped" + } + + return nil +} diff --git a/src/segments/lastfm_test.go b/src/segments/lastfm_test.go new file mode 100644 index 000000000000..fc6fa0244f1f --- /dev/null +++ b/src/segments/lastfm_test.go @@ -0,0 +1,78 @@ +package segments + +import ( + "errors" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +const ( + LFMAPIURL = "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&api_key=key&user=KibbeWater&format=json&limit=1" +) + +func TestLFMSegmentSingle(t *testing.T) { + cases := []struct { + Error error + Case string + APIJSONResponse string + ExpectedString string + Template string + ExpectedEnabled bool + }{ + { + Case: "All Defaults", + APIJSONResponse: `{"recenttracks":{"track":[{"artist":{"#text":"C.Gambino"},"name":"Automatic","@attr":{"nowplaying":"true"}}]}}`, + ExpectedString: "\uE602 C.Gambino - Automatic", + ExpectedEnabled: true, + }, + { + Case: "Custom Template", + APIJSONResponse: `{"recenttracks":{"track":[{"artist":{"#text":"C.Gambino"},"name":"Automatic","@attr":{"nowplaying":"true"}}]}}`, + ExpectedString: "\uE602 C.Gambino - Automatic", + ExpectedEnabled: true, + Template: "{{ .Icon }}{{ if ne .Status \"stopped\" }}{{ .Full }}{{ end }}", + }, + { + Case: "Song Stopped", + APIJSONResponse: `{"recenttracks":{"track":[{"artist":{"#text":"C.Gambino"},"name":"Automatic","date":{"uts":"1699350223"}}]}}`, + ExpectedString: "\uF04D", + ExpectedEnabled: true, + Template: "{{ .Icon }}", + }, + { + Case: "Error in retrieving data", + APIJSONResponse: "nonsense", + Error: errors.New("Something went wrong"), + ExpectedEnabled: false, + }, + } + + for _, tc := range cases { + env := &mock.Environment{} + props := options.Map{ + APIKey: "key", + Username: "KibbeWater", + options.HTTPTimeout: 20000, + } + + env.On("HTTPRequest", LFMAPIURL).Return([]byte(tc.APIJSONResponse), tc.Error) + + lfm := &LastFM{} + lfm.Init(props, env) + + enabled := lfm.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + if !enabled { + continue + } + + if tc.Template == "" { + tc.Template = lfm.Template() + } + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, lfm), tc.Case) + } +} diff --git a/src/segments/lua.go b/src/segments/lua.go new file mode 100644 index 000000000000..c502a088393f --- /dev/null +++ b/src/segments/lua.go @@ -0,0 +1,36 @@ +package segments + +type Lua struct { + Language +} + +func (l *Lua) Template() string { + return languageTemplate +} + +func (l *Lua) Enabled() bool { + const ( + luaToolName = "lua" + luajitToolName = "luajit" + ) + + l.extensions = []string{"*.lua", "*.rockspec"} + l.folders = []string{"lua"} + l.tooling = map[string]*cmd{ + luaToolName: { + executable: luaToolName, + args: []string{"-v"}, + regex: `Lua (?P((?P[0-9]+).(?P[0-9]+)(.(?P[0-9]+))?))`, + versionURLTemplate: "https://www.lua.org/manual/{{ .Major }}.{{ .Minor }}/readme.html#changes", + }, + luajitToolName: { + executable: luajitToolName, + args: []string{"-v"}, + regex: `LuaJIT (?P((?P[0-9]+).(?P[0-9]+)(.(?P[0-9]+))?))`, + versionURLTemplate: "https://github.com/LuaJIT/LuaJIT/tree/v{{ .Major}}.{{ .Minor}}", + }, + } + l.defaultTooling = []string{luaToolName, luajitToolName} + + return l.Language.Enabled() +} diff --git a/src/segments/lua_test.go b/src/segments/lua_test.go new file mode 100644 index 000000000000..ee0142e965ec --- /dev/null +++ b/src/segments/lua_test.go @@ -0,0 +1,92 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/stretchr/testify/assert" +) + +func TestLua(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + ExpectedURL string + Tooling []string + HasLua bool + HasLuaJit bool + }{ + { + Case: "Lua 5.4.4 - default tooling prefers lua", + ExpectedString: "5.4.4", + Version: "Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio", + ExpectedURL: "https://www.lua.org/manual/5.4/readme.html#changes", + HasLua: true, + HasLuaJit: true, + }, + { + Case: "Lua 5.0 - tooling set to luajit but missing so fallback to lua", + ExpectedString: "5.0", + Version: "Lua 5.0 Copyright (C) 1994-2003 Tecgraf, PUC-Rio", + ExpectedURL: "https://www.lua.org/manual/5.0/readme.html#changes", + HasLua: true, + Tooling: []string{"luajit", "lua"}, + }, + { + Case: "LuaJIT 2.0.5 - tooling set to luajit first", + ExpectedString: "2.0.5", + Version: "LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/", + HasLuaJit: true, + HasLua: true, + ExpectedURL: "https://github.com/LuaJIT/LuaJIT/tree/v2.0", + Tooling: []string{"luajit"}, + }, + { + Case: "LuaJIT 2.1.0-beta3 - tooling set to lua first but missing so try luajit", + ExpectedString: "2.1.0", + Version: "LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/", + HasLuaJit: true, + ExpectedURL: "https://github.com/LuaJIT/LuaJIT/tree/v2.1", + Tooling: []string{"lua", "luajit"}, + }, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "lua", + versionParam: "-v", + versionOutput: tc.Version, + extension: "*.lua", + } + env, props := getMockedLanguageEnv(params) + + if !tc.HasLua { + env.Unset("HasCommand") + env.On("HasCommand", "lua").Return(false) + } + + env.On("HasCommand", "luajit").Return(tc.HasLuaJit) + env.On("RunCommand", "luajit", []string{"-v"}).Return(tc.Version, nil) + env.On("Shell").Return("bash") + + // Initialize template system for version URL rendering + if template.Cache == nil { + template.Cache = &cache.Template{} + } + template.Init(env, nil, nil) + + if len(tc.Tooling) > 0 { + props[Tooling] = tc.Tooling + } + + l := &Lua{} + l.Init(props, env) + + failMsg := fmt.Sprintf("Failed in case: %s", tc.Case) + assert.True(t, l.Enabled(), failMsg) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, l.Template(), l), failMsg) + assert.Equal(t, tc.ExpectedURL, l.URL, failMsg) + } +} diff --git a/src/segments/mercurial.go b/src/segments/mercurial.go new file mode 100644 index 000000000000..1456c7842953 --- /dev/null +++ b/src/segments/mercurial.go @@ -0,0 +1,174 @@ +package segments + +import ( + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" +) + +const ( + MERCURIALCOMMAND = "hg" + + hgLogTemplate = "{rev}|{node}|{branch}|{tags}|{bookmarks}" +) + +type MercurialStatus struct { + ScmStatus +} + +func (s *MercurialStatus) add(code string) { + switch code { + case "R", "!": + s.Deleted++ + case "A": + s.Added++ + case "?": + s.Untracked++ + case "M": + s.Modified++ + } +} + +type Mercurial struct { + Working *MercurialStatus + LocalCommitNumber string + ChangeSetID string + ChangeSetIDShort string + Branch string + Scm + Bookmarks []string + Tags []string + IsTip bool +} + +func (hg *Mercurial) Template() string { + return "hg {{.Branch}} {{if .LocalCommitNumber}}({{.LocalCommitNumber}}:{{.ChangeSetIDShort}}){{end}}{{range .Bookmarks }} \uf02e {{.}}{{end}}{{range .Tags}} \uf02b {{.}}{{end}}{{if .Working.Changed}} \uf044 {{ .Working.String }}{{ end }}" //nolint: lll +} + +func (hg *Mercurial) Enabled() bool { + if !hg.shouldDisplay() { + return false + } + + statusFormats := hg.options.KeyValueMap(StatusFormats, map[string]string{}) + hg.Working = &MercurialStatus{ScmStatus: ScmStatus{Formats: statusFormats}} + + displayStatus := hg.options.Bool(FetchStatus, false) + if displayStatus { + hg.setMercurialStatus() + } + + return true +} + +func (hg *Mercurial) CacheKey() (string, bool) { + dir, err := hg.env.HasParentFilePath(".hg", true) + if err != nil { + return "", false + } + + return dir.Path, true +} + +func (hg *Mercurial) shouldDisplay() bool { + if !hg.hasCommand(MERCURIALCOMMAND) { + return false + } + + hgdir, err := hg.env.HasParentFilePath(".hg", false) + if err != nil { + return false + } + + hg.setDir(hgdir.ParentFolder) + + hg.mainSCMDir = hgdir.Path + hg.scmDir = hgdir.Path + // convert the worktree file path to a windows one when in a WSL shared folder + hg.repoRootDir = strings.TrimSuffix(hg.convertToWindowsPath(hgdir.Path), "/.hg") + return true +} + +func (hg *Mercurial) setDir(dir string) { + dir = path.ReplaceHomeDirPrefixWithTilde(dir) // align with template PWD + if hg.env.GOOS() == runtime.WINDOWS { + hg.Dir = strings.TrimSuffix(dir, `\.hg`) + return + } + hg.Dir = strings.TrimSuffix(dir, "/.hg") +} + +func (hg *Mercurial) setMercurialStatus() { + hg.Branch = hg.command + + idString := hg.getHgCommandOutput("log", "-r", ".", "--template", hgLogTemplate) + if idString == "" { + return + } + + idSplit := strings.SplitN(idString, "|", 6) + if len(idSplit) != 5 { + return + } + + hg.LocalCommitNumber = idSplit[0] + hg.ChangeSetID = idSplit[1] + + if len(hg.ChangeSetID) >= 12 { + hg.ChangeSetIDShort = hg.ChangeSetID[:12] + } + hg.Branch = idSplit[2] + + hg.Tags = doSplit(idSplit[3]) + hg.Bookmarks = doSplit(idSplit[4]) + + hg.IsTip = false + tipIndex := 0 + for i, tag := range hg.Tags { + if tag == "tip" { + hg.IsTip = true + tipIndex = i + break + } + } + + if hg.IsTip { + hg.Tags = RemoveAtIndex(hg.Tags, tipIndex) + } + + statusString := hg.getHgCommandOutput("status") + + if statusString == "" { + return + } + + statusLines := strings.SplitSeq(statusString, "\n") + + for status := range statusLines { + hg.Working.add(status[:1]) + } +} + +func doSplit(s string) []string { + if s == "" { + return []string{} + } + + return strings.Split(s, " ") +} + +func RemoveAtIndex(s []string, index int) []string { + ret := make([]string, 0) + ret = append(ret, s[:index]...) + return append(ret, s[index+1:]...) +} + +func (hg *Mercurial) getHgCommandOutput(command string, args ...string) string { + args = append([]string{"-R", hg.repoRootDir, command}, args...) + val, err := hg.env.RunCommand(hg.command, args...) + if err != nil { + return "" + } + return strings.TrimSpace(val) +} diff --git a/src/segments/mercurial_test.go b/src/segments/mercurial_test.go new file mode 100644 index 000000000000..ddc8f244b7a3 --- /dev/null +++ b/src/segments/mercurial_test.go @@ -0,0 +1,173 @@ +package segments + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/stretchr/testify/assert" +) + +func TestMercurialEnabledToolNotFound(t *testing.T) { + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasCommand", "hg").Return(false) + env.On("GOOS").Return("") + env.On("IsWsl").Return(false) + + hg := &Mercurial{} + hg.Init(options.Map{}, env) + + assert.False(t, hg.Enabled()) +} + +func TestMercurialEnabledInWorkingDirectory(t *testing.T) { + fileInfo := &runtime.FileInfo{ + Path: "/dir/hello", + ParentFolder: "/dir", + IsDir: true, + } + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasCommand", "hg").Return(true) + env.On("GOOS").Return("") + env.On("IsWsl").Return(false) + env.On("HasParentFilePath", ".hg", false).Return(fileInfo, nil) + env.On("PathSeparator").Return("/") + env.On("Home").Return(poshHome) + env.On("Getenv", poshGitEnv).Return("") + + hg := &Mercurial{} + hg.Init(options.Map{}, env) + + assert.True(t, hg.Enabled()) + assert.Equal(t, fileInfo.Path, hg.mainSCMDir) + assert.Equal(t, fileInfo.Path, hg.repoRootDir) +} + +func TestMercurialGetIdInfo(t *testing.T) { + cases := []struct { + ExpectedWorking *MercurialStatus + Case string + LogOutput string + StatusOutput string + ExpectedBranch string + ExpectedChangeSetID string + ExpectedShortID string + ExpectedLocalCommitNumber string + ExpectedBookmarks []string + ExpectedTags []string + ExpectedIsTip bool + }{ + { + Case: "nochanges_tip", + LogOutput: "123|b6cb23dcb79fe5c2215f1ae8f1a85326a7fed500|branchname|tip|", + StatusOutput: "", + ExpectedWorking: &MercurialStatus{ScmStatus{ + Modified: 0, + Added: 0, + Deleted: 0, + Moved: 0, + Untracked: 0, + Conflicted: 0, + }}, + ExpectedBranch: "branchname", + ExpectedChangeSetID: "b6cb23dcb79fe5c2215f1ae8f1a85326a7fed500", + ExpectedShortID: "b6cb23dcb79f", + ExpectedLocalCommitNumber: "123", + ExpectedIsTip: true, + ExpectedBookmarks: []string{}, + ExpectedTags: []string{}, + }, + { + Case: "nochanges", + LogOutput: "123|b6cb23dcb79fe5c2215f1ae8f1a85326a7fed500|branchname||", + StatusOutput: "", + ExpectedWorking: &MercurialStatus{ScmStatus{ + Modified: 0, + Added: 0, + Deleted: 0, + Moved: 0, + Untracked: 0, + Conflicted: 0, + }}, + ExpectedBranch: "branchname", + ExpectedChangeSetID: "b6cb23dcb79fe5c2215f1ae8f1a85326a7fed500", + ExpectedShortID: "b6cb23dcb79f", + ExpectedLocalCommitNumber: "123", + ExpectedIsTip: false, + ExpectedBookmarks: []string{}, + ExpectedTags: []string{}, + }, + { + Case: "changed", + LogOutput: "3|11a953bf0288663b530dd6d65f3c8e0d5f7fddb5|default|tip mytag mytag2|bm1 bm2", + StatusOutput: ` +M Modified.File +? Untracked.File +R Removed.File +! AlsoRemoved.File +A Added.File +`, + ExpectedWorking: &MercurialStatus{ScmStatus{ + Modified: 1, + Added: 1, + Deleted: 2, + Moved: 0, + Untracked: 1, + Conflicted: 0, + }}, + ExpectedBranch: "default", + ExpectedChangeSetID: "11a953bf0288663b530dd6d65f3c8e0d5f7fddb5", + ExpectedShortID: "11a953bf0288", + ExpectedLocalCommitNumber: "3", + ExpectedIsTip: true, + ExpectedBookmarks: []string{"bm1", "bm2"}, + ExpectedTags: []string{"mytag", "mytag2"}, + }, + } + + for _, tc := range cases { + fileInfo := &runtime.FileInfo{ + Path: "/dir/hello", + ParentFolder: "/dir", + IsDir: true, + } + + props := options.Map{ + FetchStatus: true, + } + + env := new(mock.Environment) + env.On("InWSLSharedDrive").Return(false) + env.On("HasCommand", "hg").Return(true) + env.On("GOOS").Return("") + env.On("IsWsl").Return(false) + env.On("HasParentFilePath", ".hg", false).Return(fileInfo, nil) + env.On("PathSeparator").Return("/") + env.On("Home").Return(poshHome) + env.On("Getenv", poshGitEnv).Return("") + env.MockHgCommand(fileInfo.Path, tc.LogOutput, "log", "-r", ".", "--template", hgLogTemplate) + env.MockHgCommand(fileInfo.Path, tc.StatusOutput, "status") + + hg := &Mercurial{} + hg.Init(props, env) + + if tc.ExpectedWorking != nil { + tc.ExpectedWorking.Formats = map[string]string{} + } + + assert.True(t, hg.Enabled()) + assert.Equal(t, fileInfo.Path, hg.mainSCMDir) + assert.Equal(t, fileInfo.Path, hg.repoRootDir) + assert.Equal(t, tc.ExpectedWorking, hg.Working, tc.Case) + assert.Equal(t, tc.ExpectedBranch, hg.Branch, tc.Case) + assert.Equal(t, tc.ExpectedChangeSetID, hg.ChangeSetID, tc.Case) + assert.Equal(t, tc.ExpectedShortID, hg.ChangeSetIDShort, tc.Case) + assert.Equal(t, tc.ExpectedLocalCommitNumber, hg.LocalCommitNumber, tc.Case) + assert.Equal(t, tc.ExpectedIsTip, hg.IsTip, tc.Case) + assert.Equal(t, tc.ExpectedBookmarks, hg.Bookmarks, tc.Case) + assert.Equal(t, tc.ExpectedTags, hg.Tags, tc.Case) + } +} diff --git a/src/segments/mojo.go b/src/segments/mojo.go new file mode 100644 index 000000000000..5d88fd7b31be --- /dev/null +++ b/src/segments/mojo.go @@ -0,0 +1,65 @@ +package segments + +import ( + "slices" + + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +const ( + defaultVenvName = "default" +) + +type Mojo struct { + Venv string + Language +} + +func (m *Mojo) Template() string { + return " {{ if .Error }}{{ .Error }}{{ else }}{{ if .Venv }}{{ .Venv }} {{ end }}{{ .Full }}{{ end }} " +} + +func (m *Mojo) Enabled() bool { + m.extensions = []string{"*.🔥", "*.mojo", "mojoproject.toml"} + m.tooling = map[string]*cmd{ + mojoToolName: { + executable: mojoToolName, + args: []string{versionFlagArg}, + regex: `(?:mojo (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, + }, + } + m.defaultTooling = []string{mojoToolName} + m.displayMode = m.options.String(DisplayMode, DisplayModeEnvironment) + m.Language.loadContext = m.loadContext + m.Language.inContext = m.inContext + + return m.Language.Enabled() +} + +func (m *Mojo) loadContext() { + if !m.options.Bool(FetchVirtualEnv, true) { + return + } + + // Magic, the official package manager and virtual env manager, + // is built on top of pixi: https://github.com/prefix-dev/pixi + venv := m.env.Getenv("PIXI_ENVIRONMENT_NAME") + if len(venv) > 0 && m.canUseVenvName(venv) { + m.Venv = venv + } +} + +func (m *Mojo) inContext() bool { + return m.Venv != "" +} + +func (m *Mojo) canUseVenvName(name string) bool { + defaultNames := []string{defaultVenvName} + + if m.options.Bool(options.DisplayDefault, true) || + !slices.Contains(defaultNames, name) { + return true + } + + return false +} diff --git a/src/segments/mojo_test.go b/src/segments/mojo_test.go new file mode 100644 index 000000000000..908633f4582b --- /dev/null +++ b/src/segments/mojo_test.go @@ -0,0 +1,88 @@ +package segments + +import ( + "testing" + + "github.com/alecthomas/assert" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +func TestMojoTemplate(t *testing.T) { + cases := []struct { + Case string + Expected string + VirtualEnvName string + FetchVirtualEnv bool + DisplayDefault bool + FetchVersion bool + }{ + { + Case: "Virtual environment is present", + Expected: "foo 24.5.0", + VirtualEnvName: "foo", + FetchVirtualEnv: true, + DisplayDefault: true, + FetchVersion: true, + }, + { + Case: "No virtual environment present", + Expected: "24.5.0", + VirtualEnvName: "", + FetchVirtualEnv: true, + DisplayDefault: true, + FetchVersion: true, + }, + { + Case: "Hide the virtual environment, but show the version", + Expected: "24.5.0", + VirtualEnvName: "foo", + FetchVirtualEnv: false, + DisplayDefault: true, + FetchVersion: true, + }, + { + Case: "Show the virtual environment, but hide the version", + Expected: "foo", + VirtualEnvName: "foo", + FetchVirtualEnv: true, + DisplayDefault: true, + FetchVersion: false, + }, + { + Case: "Show the default virtual environment", + Expected: "default 24.5.0", + VirtualEnvName: "default", + FetchVirtualEnv: true, + DisplayDefault: true, + FetchVersion: true, + }, + { + Case: "Hide the default virtual environment", + Expected: "24.5.0", + VirtualEnvName: "default", + FetchVirtualEnv: true, + DisplayDefault: false, + FetchVersion: true, + }, + } + + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "mojo", + versionParam: "--version", + versionOutput: "mojo 24.5.0 (e8aacb95)", + extension: "*.mojo", + } + env, props := getMockedLanguageEnv(params) + env.On("Getenv", "PIXI_ENVIRONMENT_NAME").Return(tc.VirtualEnvName) + props[options.DisplayDefault] = tc.DisplayDefault + props[options.FetchVersion] = tc.FetchVersion + props[FetchVirtualEnv] = tc.FetchVirtualEnv + props[DisplayMode] = DisplayModeAlways + + mojo := &Mojo{} + mojo.Init(props, env) + assert.True(t, mojo.Enabled()) + assert.Equal(t, tc.Expected, renderTemplate(env, mojo.Template(), mojo), tc.Case) + } +} diff --git a/src/segments/mvn.go b/src/segments/mvn.go new file mode 100644 index 000000000000..9e2b1dda6729 --- /dev/null +++ b/src/segments/mvn.go @@ -0,0 +1,31 @@ +package segments + +type Mvn struct { + Language +} + +func (m *Mvn) Enabled() bool { + const mvnToolName = "mvn" + + m.extensions = []string{"pom.xml"} + m.tooling = map[string]*cmd{ + mvnToolName: { + executable: mvnToolName, + args: []string{versionFlagArg}, + regex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)(?:-(?P[a-z]+-[0-9]+))?))`, + }, + } + m.defaultTooling = []string{mvnToolName} + m.versionURLTemplate = "https://github.com/apache/maven/releases/tag/maven-{{ .Full }}" + + mvnw, err := m.env.HasParentFilePath("mvnw", false) + if err == nil { + m.tooling[mvnToolName].executable = mvnw.Path + } + + return m.Language.Enabled() +} + +func (m *Mvn) Template() string { + return languageTemplate +} diff --git a/src/segments/mvn_test.go b/src/segments/mvn_test.go new file mode 100644 index 000000000000..698f7e22b33e --- /dev/null +++ b/src/segments/mvn_test.go @@ -0,0 +1,61 @@ +package segments + +import ( + "errors" + "fmt" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + + "github.com/alecthomas/assert" +) + +func TestMvn(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + MvnVersion string + MvnwVersion string + HasMvnw bool + }{ + { + Case: "Maven version", + ExpectedString: "1.0.0", + MvnVersion: "Apache Maven 1.0.0", + HasMvnw: false, + MvnwVersion: ""}, + { + Case: "Local Maven version from wrapper", + ExpectedString: "1.1.0-beta-9", + MvnVersion: "Apache Maven 1.0.0", + HasMvnw: true, + MvnwVersion: "Apache Maven 1.1.0-beta-9"}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "mvn", + versionParam: "--version", + versionOutput: tc.MvnVersion, + extension: "pom.xml", + } + env, props := getMockedLanguageEnv(params) + + fileInfo := &runtime.FileInfo{ + Path: "../mvnw", + ParentFolder: "./", + IsDir: false, + } + var err error + if !tc.HasMvnw { + err = errors.New("no match") + } + env.On("HasParentFilePath", "mvnw", false).Return(fileInfo, err) + env.On("HasCommand", fileInfo.Path).Return(tc.HasMvnw) + env.On("RunCommand", fileInfo.Path, []string{"--version"}).Return(tc.MvnwVersion, nil) + + m := &Mvn{} + m.Init(props, env) + assert.True(t, m.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, m.Template(), m), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/nba.go b/src/segments/nba.go new file mode 100644 index 000000000000..63e889408f90 --- /dev/null +++ b/src/segments/nba.go @@ -0,0 +1,311 @@ +package segments + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +// segment struct, makes templating easier +type Nba struct { + Base + + NBAData +} + +// NBA struct contains parsed API data that care about for the segment +type NBAData struct { + HomeTeam string + AwayTeam string + Time string + GameDate string + StartTimeUTC string + GameStatus GameStatus // 1 = scheduled, 2 = in progress, 3 = finished + HomeScore int + AwayScore int + HomeTeamWins int + HomeTeamLosses int + AwayTeamWins int + AwayTeamLosses int +} + +func (nba *NBAData) HasStats() bool { + return nba.HomeTeamWins != 0 || nba.HomeTeamLosses != 0 || nba.AwayTeamWins != 0 || nba.AwayTeamLosses != 0 +} + +func (nba *NBAData) Started() bool { + return nba.GameStatus == InProgress || nba.GameStatus == Finished +} + +const ( + NBASeason options.Option = "season" + TeamName options.Option = "team" + DaysOffset options.Option = "days_offset" + + ScheduledTemplate options.Option = "scheduled_template" + InProgressTemplate options.Option = "in_progress_template" + FinishedTemplate options.Option = "finished_template" + + NBAScoreURL string = "https://cdn.nba.com/static/json/liveData/scoreboard/todaysScoreboard_00.json" + NBAScheduleURL string = "https://stats.nba.com/stats/internationalbroadcasterschedule?LeagueID=00&Season=%s&Date=%s&RegionID=1&EST=Y" + + UNKNOWN = "unknown" + + currentNBASeason = "2023" + NBADateFormat = "02/01/2006" +) + +// Custom type for GameStatus +type GameStatus int + +// Constants for GameStatus values +const ( + Scheduled GameStatus = 1 + InProgress GameStatus = 2 + Finished GameStatus = 3 + NotFound GameStatus = 4 +) + +// Int() method for GameStatus to get its integer representation +// This is a helpful method if people want to come up with their own templates +func (gs GameStatus) Int() int { + return int(gs) +} + +func (gs GameStatus) Valid() bool { + return gs == Scheduled || gs == InProgress || gs == Finished +} + +func (gs GameStatus) String() string { + switch gs { + case Scheduled: + return "Scheduled" + case InProgress: + return "In Progress" + case Finished: + return "Finished" + case NotFound: + return "Not Found" + default: + return UNKNOWN + } +} + +// All of the structs needed to retrieve data from the live score endpoint +type ScoreboardResponse struct { + Scoreboard Scoreboard `json:"scoreboard"` +} + +type Scoreboard struct { + GameDate string `json:"gameDate"` + Games []Game `json:"games"` +} + +type Game struct { + GameStatusText string `json:"gameStatusText"` + GameTimeUTC string `json:"gameTimeUTC"` + HomeTeam Team `json:"homeTeam"` + AwayTeam Team `json:"awayTeam"` + GameStatus int `json:"gameStatus"` +} + +type Team struct { + TeamTricode string `json:"teamTricode"` + Wins int `json:"wins"` + Losses int `json:"losses"` + Score int `json:"score"` +} + +// All the structs needed to get data from the schedule endpoint +type ScheduleResponse struct { + ResultSets []ResultSet `json:"resultSets"` +} + +type ResultSet struct { + CompleteGameList []ScheduledGame `json:"CompleteGameList,omitempty"` +} + +type ScheduledGame struct { + VtAbbreviation string `json:"vtAbbreviation"` + HtAbbreviation string `json:"htAbbreviation"` + Date string `json:"date"` + Time string `json:"time"` +} + +func (nba *Nba) Template() string { + return " \U000F0806 {{ .HomeTeam}}{{ if .HasStats }} ({{.HomeTeamWins}}-{{.HomeTeamLosses}}){{ end }}{{ if .Started }}:{{.HomeScore}}{{ end }} vs {{ .AwayTeam}}{{ if .HasStats }} ({{.AwayTeamWins}}-{{.AwayTeamLosses}}){{ end }}{{ if .Started }}:{{.AwayScore}}{{ end }} | {{ if not .Started }}{{.GameDate}} | {{ end }}{{.Time}} " //nolint:lll +} + +func (nba *Nba) Enabled() bool { + data, err := nba.getResult() + if err != nil || !data.GameStatus.Valid() { + return false + } + + nba.NBAData = *data + + return true +} + +// parses through a set of games from the score endpoint and looks for props.team in away or home team +func (nba *Nba) findGameScoreByTeamTricode(games []Game, teamTricode string) (*Game, error) { + for _, game := range games { + if game.HomeTeam.TeamTricode == teamTricode || game.AwayTeam.TeamTricode == teamTricode { + return &game, nil + } + } + + return nil, errors.New("no game score found for team") +} + +// parses through a set of games from the schedule endpoint and looks for props.team in away or home team +func (nba *Nba) findGameSchedulebyTeamTricode(games []ScheduledGame, teamTricode string) (*ScheduledGame, error) { + for _, game := range games { + if game.VtAbbreviation == teamTricode || game.HtAbbreviation == teamTricode { + return &game, nil + } + } + + return nil, errors.New("no scheduled game found for team") +} + +// parses the time and date from the schedule endpoint into a UTC time +func (nba *Nba) parseTimetoUTC(timeEST, date string) string { + combinedTime := date + " " + timeEST + timeUTC, err := time.Parse("01/02/2006 03:04 PM", combinedTime) + if err != nil { + return "" + } + + return timeUTC.UTC().Format("2006-01-02T15:04:05Z") +} + +// retrieves data from the score endpoint +func (nba *Nba) retrieveScoreData(teamName string, httpTimeout int) (*NBAData, error) { + body, err := nba.env.HTTPRequest(NBAScoreURL, nil, httpTimeout) + if err != nil { + return nil, err + } + + var scoreboardResponse ScoreboardResponse + err = json.Unmarshal(body, &scoreboardResponse) + if err != nil { + return nil, err + } + + gameInfo, err := nba.findGameScoreByTeamTricode(scoreboardResponse.Scoreboard.Games, teamName) + if err != nil { + return nil, err + } + + return &NBAData{ + AwayTeam: gameInfo.AwayTeam.TeamTricode, + HomeTeam: gameInfo.HomeTeam.TeamTricode, + Time: gameInfo.GameStatusText, + GameDate: scoreboardResponse.Scoreboard.GameDate, + StartTimeUTC: gameInfo.GameTimeUTC, + GameStatus: GameStatus(gameInfo.GameStatus), + HomeScore: gameInfo.HomeTeam.Score, + AwayScore: gameInfo.AwayTeam.Score, + HomeTeamWins: gameInfo.HomeTeam.Wins, + HomeTeamLosses: gameInfo.HomeTeam.Losses, + AwayTeamWins: gameInfo.AwayTeam.Wins, + AwayTeamLosses: gameInfo.AwayTeam.Losses, + }, nil +} + +// Retrieves the data from the schedule endpoint +func (nba *Nba) retrieveScheduleData(teamName string, httpTimeout int) (*NBAData, error) { + // How many days into the future should we look for a game. + numDaysToSearch := nba.options.Int(DaysOffset, 8) + nbaSeason := nba.options.String(NBASeason, currentNBASeason) + // Get the current date in America/New_York + nowNYC := time.Now().In(time.FixedZone("America/New_York", -5*60*60)) + + // Check to see if a game is scheduled while the numDaysToSearch is greater than 0 + for numDaysToSearch > 0 { + dateStr := nowNYC.Format(NBADateFormat) + urlEndpoint := fmt.Sprintf(NBAScheduleURL, nbaSeason, dateStr) + + body, err := nba.env.HTTPRequest(urlEndpoint, nil, httpTimeout) + if err != nil { + return nil, err + } + + var scheduleResponse *ScheduleResponse + err = json.Unmarshal(body, &scheduleResponse) + if err != nil { + return nil, err + } + + // Check if we can find a game for the team + gameInfo, err := nba.findGameSchedulebyTeamTricode(scheduleResponse.ResultSets[1].CompleteGameList, teamName) + if err != nil { + // We didn't find a game for the team on this day, so we need to check the next day + nowNYC = nowNYC.AddDate(0, 0, 1) + numDaysToSearch-- + continue + } + + return &NBAData{ + AwayTeam: gameInfo.VtAbbreviation, + HomeTeam: gameInfo.HtAbbreviation, + Time: gameInfo.Time + " ET", + GameDate: gameInfo.Date, + StartTimeUTC: nba.parseTimetoUTC(gameInfo.Time, gameInfo.Date), + GameStatus: Scheduled, + HomeScore: 0, + AwayScore: 0, + HomeTeamWins: 0, + HomeTeamLosses: 0, + AwayTeamWins: 0, + AwayTeamLosses: 0, + }, nil + } + + return nil, errors.New("no scheduled game found for team within DaysOffset days") +} + +// First try to get the data from the score endpoint, if that fails, try the schedule endpoint +// The score endpoint usually goes live within 12 hours of a game starting +func (nba *Nba) getAvailableGameData(teamName string, httpTimeout int) (*NBAData, error) { + // Get the info from the score endpoint + data, err := nba.retrieveScoreData(teamName, httpTimeout) + if err == nil { + return data, nil + } + + // If the score endpoint doesn't have anything get data from the schedule endpoint + data, err = nba.retrieveScheduleData(teamName, httpTimeout) + if err == nil { + return data, nil + } + + return nil, err +} + +func (nba *Nba) getResult() (*NBAData, error) { + teamName := nba.options.String(TeamName, "") + + httpTimeout := nba.options.Int(options.HTTPTimeout, options.DefaultHTTPTimeout) + + log.Debug("fetching available data for " + teamName) + + data, err := nba.getAvailableGameData(teamName, httpTimeout) + if err != nil { + log.Error(errors.Join(err, fmt.Errorf("unable to get data for team %s", teamName))) + return nil, err + } + + if !data.GameStatus.Valid() { + err := fmt.Errorf("%d is not a valid game status", data.GameStatus) + log.Error(err) + return nil, err + } + + return data, nil +} diff --git a/src/segments/nba_test.go b/src/segments/nba_test.go new file mode 100644 index 000000000000..21c2a3f711af --- /dev/null +++ b/src/segments/nba_test.go @@ -0,0 +1,105 @@ +package segments + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +func getTestData(file string) string { + content, _ := os.ReadFile(fmt.Sprintf("../test/%s", file)) + return string(content) +} + +// create Test segment for NBA segment +func TestNBASegment(t *testing.T) { + jsonScheduleData := getTestData("nba/schedule.json") + jsonScoreData := getTestData("nba/score.json") + + cases := []struct { + Error error + Case string + JSONResponse string + ExpectedString string + TeamName string + CacheTimeout int + DaysOffset int + ExpectedEnabled bool + CacheFoundFail bool + }{ + { + Case: "Team (Home Team) Scheduled Game", + JSONResponse: jsonScheduleData, + TeamName: "LAL", + ExpectedString: "󰠆 LAL vs PHX | 10/26/2023 | 10:00 PM ET", + ExpectedEnabled: true, + DaysOffset: 8, + }, + { + Case: "Team (Away Team) Scheduled Game", + JSONResponse: jsonScheduleData, + TeamName: "PHX", + ExpectedString: "󰠆 LAL vs PHX | 10/26/2023 | 10:00 PM ET", + DaysOffset: 4, + ExpectedEnabled: true, + }, + { + Case: "Team (Home Team) Live Game", + JSONResponse: jsonScoreData, + TeamName: "CHA", + ExpectedString: "󰠆 CHA (1-0):13 vs BOS (0-1):8 | Q1 8:23", + ExpectedEnabled: true, + }, + { + Case: "Team (Away Team) Live Game", + JSONResponse: jsonScoreData, + TeamName: "BOS", + ExpectedString: "󰠆 CHA (1-0):13 vs BOS (0-1):8 | Q1 8:23", + ExpectedEnabled: true, + }, + { + Case: "Team not Found", + JSONResponse: jsonScheduleData, + DaysOffset: 8, + TeamName: "INVALID", + ExpectedEnabled: false, + }, + } + + for _, tc := range cases { + env := &mock.Environment{} + props := options.Map{ + TeamName: tc.TeamName, + DaysOffset: tc.DaysOffset, + } + + env.On("HTTPRequest", NBAScoreURL).Return([]byte(tc.JSONResponse), tc.Error) + + // Add all the daysOffset to the http request responses + for i := 0; i < tc.DaysOffset; i++ { + currTime := time.Now().In(time.FixedZone("America/New_York", -5*60*60)) + // add offset days to currTime so we can query for games in the future + currTime = currTime.AddDate(0, 0, i) + dateStr := currTime.Format(NBADateFormat) + scheduleURLEndpoint := fmt.Sprintf(NBAScheduleURL, currentNBASeason, dateStr) + env.On("HTTPRequest", scheduleURLEndpoint).Return([]byte(tc.JSONResponse), tc.Error) + } + + nba := &Nba{} + nba.Init(props, env) + + enabled := nba.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + if !enabled { + continue + } + + assert.Equal(t, tc.ExpectedString, renderTemplate(env, nba.Template(), nba), tc.Case) + } +} diff --git a/src/segments/nbgv.go b/src/segments/nbgv.go new file mode 100644 index 000000000000..90decbcbf38c --- /dev/null +++ b/src/segments/nbgv.go @@ -0,0 +1,47 @@ +package segments + +import ( + "encoding/json" +) + +type Nbgv struct { + Base + + VersionInfo +} + +type VersionInfo struct { + Version string `json:"Version"` + AssemblyVersion string `json:"AssemblyVersion"` + AssemblyInformationalVersion string `json:"AssemblyInformationalVersion"` + NuGetPackageVersion string `json:"NuGetPackageVersion"` + ChocolateyPackageVersion string `json:"ChocolateyPackageVersion"` + NpmPackageVersion string `json:"NpmPackageVersion"` + SimpleVersion string `json:"SimpleVersion"` + VersionFileFound bool `json:"VersionFileFound"` +} + +func (n *Nbgv) Template() string { + return " {{ .Version }} " +} + +func (n *Nbgv) Enabled() bool { + nbgv := "nbgv" + if !n.env.HasCommand(nbgv) { + return false + } + + response, err := n.env.RunCommand(nbgv, "get-version", "--format=json") + if err != nil { + return false + } + + n.VersionInfo = VersionInfo{} + err = json.Unmarshal([]byte(response), &n.VersionInfo) + + if err != nil { + return false + } + + return n.VersionFileFound +} diff --git a/src/segment_nbgv_test.go b/src/segments/nbgv_test.go similarity index 69% rename from src/segment_nbgv_test.go rename to src/segments/nbgv_test.go index 12ce52faf280..4f83aa51eb65 100644 --- a/src/segment_nbgv_test.go +++ b/src/segments/nbgv_test.go @@ -1,21 +1,24 @@ -package main +package segments import ( "errors" "testing" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/alecthomas/assert" ) func TestNbgv(t *testing.T) { cases := []struct { + Error error Case string - ExpectedEnabled bool ExpectedString string Response string + Template string + ExpectedEnabled bool HasNbgv bool - SegmentTemplate string - Error error }{ {Case: "nbgv not installed"}, {Case: "nbgv installed, no version file", HasNbgv: true, Response: "{ \"VersionFileFound\": false }"}, @@ -26,7 +29,7 @@ func TestNbgv(t *testing.T) { ExpectedString: "invalid template text", HasNbgv: true, Response: "{ \"VersionFileFound\": true }", - SegmentTemplate: "{{ err }}", + Template: "{{ err }}", }, { Case: "command error", @@ -44,7 +47,7 @@ func TestNbgv(t *testing.T) { ExpectedString: "bump", HasNbgv: true, Response: "{ \"VersionFileFound\": true, \"Version\": \"bump\" }", - SegmentTemplate: "{{ .Version }}", + Template: "{{ .Version }}", }, { Case: "AssemblyVersion", @@ -52,26 +55,26 @@ func TestNbgv(t *testing.T) { ExpectedString: "bump", HasNbgv: true, Response: "{ \"VersionFileFound\": true, \"AssemblyVersion\": \"bump\" }", - SegmentTemplate: "{{ .AssemblyVersion }}", + Template: "{{ .AssemblyVersion }}", }, } for _, tc := range cases { - env := new(MockedEnvironment) - env.On("hasCommand", "nbgv").Return(tc.HasNbgv) - env.On("runCommand", "nbgv", []string{"get-version", "--format=json"}).Return(tc.Response, tc.Error) - nbgv := &nbgv{ - env: env, - props: &properties{ - values: map[Property]interface{}{ - SegmentTemplate: tc.SegmentTemplate, - }, - }, - } - enabled := nbgv.enabled() + env := new(mock.Environment) + env.On("HasCommand", "nbgv").Return(tc.HasNbgv) + env.On("RunCommand", "nbgv", []string{"get-version", "--format=json"}).Return(tc.Response, tc.Error) + + nbgv := &Nbgv{} + nbgv.Init(options.Map{}, env) + + enabled := nbgv.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + if tc.Template == "" { + tc.Template = nbgv.Template() + } if enabled { - assert.Equal(t, tc.ExpectedString, nbgv.string(), tc.Case) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, nbgv), tc.Case) } } } diff --git a/src/segments/nightscout.go b/src/segments/nightscout.go new file mode 100644 index 000000000000..e63cd001f677 --- /dev/null +++ b/src/segments/nightscout.go @@ -0,0 +1,158 @@ +package segments + +import ( + "encoding/json" + "errors" + "fmt" + http2 "net/http" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +// segment struct, makes templating easier +type Nightscout struct { + Base + + TrendIcon string + NightscoutData +} + +const ( + // Your complete Nightscout URL and APIKey like this + URL options.Option = "url" + Headers options.Option = "headers" + + DoubleUpIcon options.Option = "doubleup_icon" + SingleUpIcon options.Option = "singleup_icon" + FortyFiveUpIcon options.Option = "fortyfiveup_icon" + FlatIcon options.Option = "flat_icon" + FortyFiveDownIcon options.Option = "fortyfivedown_icon" + SingleDownIcon options.Option = "singledown_icon" + DoubleDownIcon options.Option = "doubledown_icon" +) + +// NightscoutData struct contains the API data +type NightscoutData struct { + DateString time.Time `json:"dateString"` + SysTime time.Time `json:"sysTime"` + ID string `json:"_id"` + Direction string `json:"direction"` + Device string `json:"device"` + Type string `json:"type"` + Sgv int `json:"sgv"` + Date int64 `json:"date"` + Trend int `json:"trend"` + UtcOffset int `json:"utcOffset"` + Mills int64 `json:"mills"` +} + +// UnmarshalJSON handles both integer and floating-point JSON numbers for the date field. +// Some Nightscout API providers (e.g. T1Pal) return the date as a float. +func (n *NightscoutData) UnmarshalJSON(data []byte) error { + type Alias NightscoutData + aux := &struct { + *Alias + Date json.Number `json:"date"` + }{ + Alias: (*Alias)(n), + } + + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + if aux.Date == "" { + return nil + } + + if i, err := aux.Date.Int64(); err == nil { + n.Date = i + return nil + } + + if f, err := aux.Date.Float64(); err == nil { + n.Date = int64(f) + return nil + } + + return fmt.Errorf("date field must be a valid number, got: %s", aux.Date) +} + +func (ns *Nightscout) Template() string { + return " {{ .Sgv }} " +} + +func (ns *Nightscout) Enabled() bool { + data, err := ns.getResult() + if err != nil { + return false + } + ns.NightscoutData = *data + ns.TrendIcon = ns.getTrendIcon() + + return true +} + +func (ns *Nightscout) getTrendIcon() string { + switch ns.Direction { + case "DoubleUp": + return ns.options.String(DoubleUpIcon, "↑↑") + case "SingleUp": + return ns.options.String(SingleUpIcon, "↑") + case "FortyFiveUp": + return ns.options.String(FortyFiveUpIcon, "↗") + case "Flat": + return ns.options.String(FlatIcon, "→") + case "FortyFiveDown": + return ns.options.String(FortyFiveDownIcon, "↘") + case "SingleDown": + return ns.options.String(SingleDownIcon, "↓") + case "DoubleDown": + return ns.options.String(DoubleDownIcon, "↓↓") + default: + return "" + } +} + +func (ns *Nightscout) getResult() (*NightscoutData, error) { + parseSingleElement := func(data []byte) (*NightscoutData, error) { + var result []*NightscoutData + err := json.Unmarshal(data, &result) + if err != nil { + return nil, err + } + if len(result) == 0 { + return nil, errors.New("no elements in the array") + } + return result[0], nil + } + + url := ns.options.Template(URL, "", ns) + httpTimeout := ns.options.Int(options.HTTPTimeout, options.DefaultHTTPTimeout) + + headers := ns.options.KeyValueMap(Headers, map[string]string{}) + modifiers := func(request *http2.Request) { + for key, value := range headers { + request.Header.Add(key, value) + } + } + + body, err := ns.env.HTTPRequest(url, nil, httpTimeout, modifiers) + if err != nil { + return nil, err + } + + var arr []*NightscoutData + err = json.Unmarshal(body, &arr) + if err != nil { + return nil, err + } + + data, err := parseSingleElement(body) + if err != nil { + return nil, err + } + + return data, nil +} diff --git a/src/segments/nightscout_test.go b/src/segments/nightscout_test.go new file mode 100644 index 000000000000..f8beec5d56b9 --- /dev/null +++ b/src/segments/nightscout_test.go @@ -0,0 +1,193 @@ +package segments + +import ( + "encoding/json" + "errors" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/template" + + "github.com/stretchr/testify/assert" +) + +const ( + FAKEAPIURL = "FAKE" +) + +func TestNSSegment(t *testing.T) { + cases := []struct { + Error error + Case string + JSONResponse string + ExpectedString string + Template string + CacheTimeout int + ExpectedEnabled bool + CacheFoundFail bool + }{ + { + Case: "Flat 150", + JSONResponse: ` + [{"_id":"619d6fa819696e8ded5b2206","sgv":150,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"Flat","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, //nolint:lll + Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}", + ExpectedString: "\ue2a1 150→", + ExpectedEnabled: true, + }, + { + Case: "DoubleDown 50", + JSONResponse: ` + [{"_id":"619d6fa819696e8ded5b2206","sgv":50,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"DoubleDown","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, //nolint:lll + Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}", + ExpectedString: "\ue2a1 50↓↓", + ExpectedEnabled: true, + }, + { + Case: "DoubleUp 250", + JSONResponse: ` + [{"_id":"619d6fa819696e8ded5b2206","sgv":250,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"DoubleUp","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, //nolint:lll + Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}", + ExpectedString: "\ue2a1 250↑↑", + ExpectedEnabled: true, + }, + { + Case: "SingleUp 130", + JSONResponse: ` + [{"_id":"619d6fa819696e8ded5b2206","sgv":130,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"SingleUp","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, //nolint:lll + Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}", + ExpectedString: "\ue2a1 130↑", + ExpectedEnabled: true, + }, + { + Case: "FortyFiveUp 174", + JSONResponse: ` + [{"_id":"619d6fa819696e8ded5b2206","sgv":174,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"FortyFiveUp","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, //nolint:lll + Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}", + ExpectedString: "\ue2a1 174↗", + ExpectedEnabled: true, + }, + { + Case: "FortyFiveDown 61", + JSONResponse: ` + [{"_id":"619d6fa819696e8ded5b2206","sgv":61,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"FortyFiveDown","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, //nolint:lll + Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}", + ExpectedString: "\ue2a1 61↘", + ExpectedEnabled: true, + }, + { + Case: "DoubleDown 50", + JSONResponse: ` + [{"_id":"619d6fa819696e8ded5b2206","sgv":50,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"DoubleDown","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, //nolint:lll + Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}", + ExpectedString: "\ue2a1 50↓↓", + ExpectedEnabled: true, + }, + { + Case: "Float date value", + JSONResponse: ` + [{"_id":"619d6fa819696e8ded5b2206","sgv":124,"date":1770512410938.386,"dateString":"2026-02-08T01:00:10.000Z","trend":4,"direction":"Flat","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2026-02-08T01:00:10.000Z","mills":1770512410000}]`, //nolint:lll + Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}", + ExpectedString: "\ue2a1 124→", + ExpectedEnabled: true, + }, + { + Case: "Error in retrieving data", + JSONResponse: "nonsense", + Error: errors.New("Something went wrong"), + ExpectedEnabled: false, + }, + { + Case: "Empty array", + JSONResponse: "[]", + ExpectedEnabled: false, + }, + { + Case: "Error parsing response", + JSONResponse: ` + 4tffgt4e4567`, + Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}", + ExpectedString: "\ue2a1 50↓↓", + ExpectedEnabled: false, + }, + { + Case: "Faulty template", + JSONResponse: ` + [{"sgv":50,"direction":"DoubleDown"}]`, + Template: "\ue2a1 {{.Sgv}}{{.Burp}}", + ExpectedString: template.IncorrectTemplate, + ExpectedEnabled: true, + }, + } + + for _, tc := range cases { + env := &mock.Environment{} + props := options.Map{ + URL: "FAKE", + Headers: map[string]string{"Fake-Header": "xxxxx"}, + } + + env.On("HTTPRequest", FAKEAPIURL).Return([]byte(tc.JSONResponse), tc.Error) + + ns := &Nightscout{} + ns.Init(props, env) + + enabled := ns.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + if !enabled { + continue + } + + if tc.Template == "" { + tc.Template = ns.Template() + } + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, ns), tc.Case) + } +} + +func TestNightscoutDataUnmarshalJSON(t *testing.T) { + cases := []struct { + Case string + JSONInput string + ExpectedDate int64 + ExpectError bool + }{ + { + Case: "Integer date value", + JSONInput: `{"date": 1637707537000}`, + ExpectedDate: 1637707537000, + }, + { + Case: "Floating-point date value", + JSONInput: `{"date": 1637707537000.5}`, + ExpectedDate: 1637707537000, + }, + { + Case: "Floating-point date with larger decimal", + JSONInput: `{"date": 1637707537123.789}`, + ExpectedDate: 1637707537123, + }, + { + Case: "Invalid date value", + JSONInput: `{"date": "not-a-number"}`, + ExpectError: true, + }, + { + Case: "Missing date field", + JSONInput: `{"sgv": 150}`, + }, + } + + for _, tc := range cases { + var data NightscoutData + err := json.Unmarshal([]byte(tc.JSONInput), &data) + + if tc.ExpectError { + assert.Error(t, err, tc.Case) + continue + } + + assert.NoError(t, err, tc.Case) + assert.Equal(t, tc.ExpectedDate, data.Date, tc.Case) + } +} diff --git a/src/segments/nim.go b/src/segments/nim.go new file mode 100644 index 000000000000..7b170036d960 --- /dev/null +++ b/src/segments/nim.go @@ -0,0 +1,25 @@ +package segments + +type Nim struct { + Language +} + +func (n *Nim) Template() string { + return languageTemplate +} + +func (n *Nim) Enabled() bool { + const nimToolName = "nim" + + n.extensions = []string{"*.nim", "*.nims"} + + n.tooling = map[string]*cmd{ + nimToolName: { + executable: nimToolName, + args: []string{versionFlagArg}, + regex: `Nim Compiler Version (?P(?P\d+)\.(?P\d+)\.(?P\d+))`, + }, + } + n.defaultTooling = []string{nimToolName} + return n.Language.Enabled() +} diff --git a/src/segments/nim_test.go b/src/segments/nim_test.go new file mode 100644 index 000000000000..0156daf8e40a --- /dev/null +++ b/src/segments/nim_test.go @@ -0,0 +1,46 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNim(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + { + Case: "Nim 2.2.0", + ExpectedString: "2.2.0", + Version: "Nim Compiler Version 2.2.0 [MacOSX: arm64]\nCompiled at 2024-11-30\nCopyright (c) 2006-2024 by Andreas Rumpf", + }, + { + Case: "Nim 1.6.12", + ExpectedString: "1.6.12", + Version: "Nim Compiler Version 1.6.12 [Linux: amd64]\nCompiled at 2023-06-15\nCopyright (c) 2006-2023 by Andreas Rumpf", + }, + { + Case: "Nim 2.0.0", + ExpectedString: "2.0.0", + Version: "Nim Compiler Version 2.0.0 [Windows: amd64]\nCompiled at 2023-12-25\nCopyright (c) 2006-2023 by Andreas Rumpf", + }, + } + + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "nim", + versionParam: "--version", + versionOutput: tc.Version, + extension: "*.nim", + } + env, props := getMockedLanguageEnv(params) + n := &Nim{} + n.Init(props, env) + assert.True(t, n.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, n.Template(), n), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/nixshell.go b/src/segments/nixshell.go new file mode 100644 index 000000000000..f10395fff2b2 --- /dev/null +++ b/src/segments/nixshell.go @@ -0,0 +1,56 @@ +package segments + +import ( + "path/filepath" + "strings" +) + +const ( + NONE = "none" +) + +type NixShell struct { + Base + + Type string +} + +func (n *NixShell) Template() string { + return "via {{ .Type }}-shell" +} + +func (n *NixShell) DetectType() string { + shellType := n.env.Getenv("IN_NIX_SHELL") + + switch shellType { + case "pure", "impure": + return shellType + default: + if n.InNewNixShell() { + return UNKNOWN + } + + return NONE + } +} + +// Hack to detect if we're in a `nix shell` (in contrast to a `nix-shell`). +// A better way to do this will be enabled by https://github.com/NixOS/nix/issues/6677 +// so we check if the PATH contains a nix store. +func (n *NixShell) InNewNixShell() bool { + paths := filepath.SplitList(n.env.Getenv("PATH")) + + for _, p := range paths { + if strings.Contains(p, "/nix/store") { + return true + } + } + + return false +} + +func (n *NixShell) Enabled() bool { + n.Type = n.DetectType() + + return n.Type != NONE +} diff --git a/src/segments/nixshell_test.go b/src/segments/nixshell_test.go new file mode 100644 index 000000000000..1c4412ae4c7a --- /dev/null +++ b/src/segments/nixshell_test.go @@ -0,0 +1,73 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/alecthomas/assert" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +const ( + nixPath = "/nix/store/zznw8fnzss1vaqfg5hmv3y79s3hkqczi-devshell-dir/bin" + defaultPath = "/users/xyz/testing" + fullNixPath = defaultPath + ":" + nixPath +) + +func TestNixShellSegment(t *testing.T) { + cases := []struct { + name string + expectedString string + shellType string + enabled bool + }{ + { + name: "Pure Nix Shell", + expectedString: "via pure-shell", + shellType: "pure", + enabled: true, + }, + { + name: "Impure Nix Shell", + expectedString: "via impure-shell", + shellType: "impure", + enabled: true, + }, + { + name: "Unknown Nix Shell", + expectedString: "via unknown-shell", + shellType: "unknown", + enabled: true, + }, + { + name: "No Nix Shell", + expectedString: "", + shellType: "", + enabled: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + env := new(mock.Environment) + env.On("Getenv", "IN_NIX_SHELL").Return(tc.shellType) + + path := defaultPath + if tc.shellType != "" { + path = fullNixPath + } + + env.On("Getenv", "PATH").Return(path) + + n := NixShell{} + n.Init(options.Map{}, env) + + assert.Equal(t, tc.enabled, n.Enabled(), fmt.Sprintf("Failed in case: %s", tc.name)) + + if tc.enabled { + assert.Equal(t, tc.expectedString, renderTemplate(env, n.Template(), n), tc.name) + } + }) + } +} diff --git a/src/segments/node.go b/src/segments/node.go new file mode 100644 index 000000000000..23a6e9f6644d --- /dev/null +++ b/src/segments/node.go @@ -0,0 +1,158 @@ +package segments + +import ( + "fmt" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type Node struct { + PackageManagerIcon string + PackageManagerName string + + Language +} + +const ( + // PnpmIcon illustrates PNPM is used + PnpmIcon options.Option = "pnpm_icon" + // YarnIcon illustrates Yarn is used + YarnIcon options.Option = "yarn_icon" + // NPMIcon illustrates NPM is used + NPMIcon options.Option = "npm_icon" + // BunIcon illustrates Bun is used + BunIcon options.Option = "bun_icon" + // FetchPackageManager shows if Bun, NPM, PNPM, or Yarn is used + FetchPackageManager options.Option = "fetch_package_manager" +) + +func (n *Node) Template() string { + return " {{ if .PackageManagerIcon }}{{ .PackageManagerIcon }} {{ end }}{{ .Full }} " +} + +func (n *Node) Enabled() bool { + n.extensions = []string{"*.js", "*.ts", fileName, ".nvmrc", "pnpm-workspace.yaml", ".pnpmfile.cjs", ".vue"} + n.tooling = map[string]*cmd{ + nodeToolName: { + executable: nodeToolName, + args: []string{versionFlagArg}, + regex: `(?:v(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, + }, + } + n.defaultTooling = []string{nodeToolName} + n.versionURLTemplate = "https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V{{ .Major }}.md#{{ .Full }}" + n.Language.matchesVersionFile = n.matchesVersionFile + n.Language.loadContext = n.loadContext + + return n.Language.Enabled() +} + +func (n *Node) loadContext() { + if !n.options.Bool(FetchPackageManager, false) { + return + } + + packageManagerDefinitions := []struct { + fileName string + name string + iconProperty options.Option + defaultIcon string + }{ + { + fileName: "pnpm-lock.yaml", + name: pnpmToolName, + iconProperty: PnpmIcon, + defaultIcon: "\ue865", + }, + { + fileName: "yarn.lock", + name: yarnToolName, + iconProperty: YarnIcon, + defaultIcon: "\ue6a7", + }, + { + fileName: "bun.lockb", + name: bunToolName, + iconProperty: BunIcon, + defaultIcon: "\ue76f", + }, + { + fileName: "bun.lock", + name: bunToolName, + iconProperty: BunIcon, + defaultIcon: "\ue76f", + }, + { + fileName: "package-lock.json", + name: npmToolName, + iconProperty: NPMIcon, + defaultIcon: "\uE71E", + }, + { + fileName: fileName, + name: npmToolName, + iconProperty: NPMIcon, + defaultIcon: "\uE71E", + }, + } + + for _, pm := range packageManagerDefinitions { + if n.env.HasFiles(pm.fileName) { + n.PackageManagerName = pm.name + n.PackageManagerIcon = n.options.String(pm.iconProperty, pm.defaultIcon) + break + } + } +} + +func (n *Node) matchesVersionFile() (string, bool) { + fileVersion := n.env.FileContent(".nvmrc") + if fileVersion == "" { + return "", true + } + + fileVersion = strings.TrimSpace(fileVersion) + + if strings.HasPrefix(fileVersion, "lts/") { + fileVersion = strings.ToLower(fileVersion) + codeName := strings.TrimPrefix(fileVersion, "lts/") + switch codeName { + case "argon": + fileVersion = "4.9.1" + case "boron": + fileVersion = "6.17.1" + case "carbon": + fileVersion = "8.17.0" + case "dubnium": + fileVersion = "10.24.1" + case "erbium": + fileVersion = "12.22.12" + case "fermium": + fileVersion = "14.21.3" + case "gallium": + fileVersion = "16.20.2" + case "hydrogen": + fileVersion = "18.20.8" + case "iron": + fileVersion = "20.19.6" + case "jod": + fileVersion = "22.21.1" + case "krypton": + fileVersion = "24.12.0" + } + } + + re := fmt.Sprintf( + `(?im)^v?%s(\.?%s)?(\.?%s)?$`, + n.Major, + n.Minor, + n.Patch, + ) + + version := strings.TrimSpace(fileVersion) + version = strings.TrimPrefix(version, "v") + + return version, regex.MatchString(re, fileVersion) +} diff --git a/src/segments/node_test.go b/src/segments/node_test.go new file mode 100644 index 000000000000..07d48d85bd92 --- /dev/null +++ b/src/segments/node_test.go @@ -0,0 +1,103 @@ +package segments + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/alecthomas/assert" +) + +func TestNodeMatchesVersionFile(t *testing.T) { + nodeVersion := Version{ + Full: "22.21.1", + Major: "22", + Minor: "21", + Patch: "1", + } + cases := []struct { + Case string + ExpectedVersion string + RCVersion string + Expected bool + }{ + {Case: "no file context", Expected: true, RCVersion: ""}, + {Case: "version match", Expected: true, ExpectedVersion: "22.21.1", RCVersion: "22.21.1"}, + {Case: "version match with newline", Expected: true, ExpectedVersion: "22.21.1", RCVersion: "22.21.1\n"}, + {Case: "version mismatch", Expected: false, ExpectedVersion: "3.2.1", RCVersion: "3.2.1"}, + {Case: "version match in other format", Expected: true, ExpectedVersion: "22.21.1", RCVersion: "v22.21.1"}, + {Case: "version match without patch", Expected: true, ExpectedVersion: "22.21", RCVersion: "22.21"}, + {Case: "version match without patch in other format", Expected: true, ExpectedVersion: "22.21", RCVersion: "v22.21"}, + {Case: "version match without minor", Expected: true, ExpectedVersion: "22", RCVersion: "22"}, + {Case: "version match without minor in other format", Expected: true, ExpectedVersion: "22", RCVersion: "v22"}, + {Case: "lts match", Expected: true, ExpectedVersion: "22.21.1", RCVersion: "lts/jod"}, + {Case: "lts match upper case", Expected: true, ExpectedVersion: "22.21.1", RCVersion: "lts/Jod"}, + {Case: "lts mismatch", Expected: false, ExpectedVersion: "8.17.0", RCVersion: "lts/carbon"}, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("FileContent", ".nvmrc").Return(tc.RCVersion) + + node := &Node{ + Language: Language{ + Version: nodeVersion, + }, + } + node.Init(options.Map{}, env) + + version, match := node.matchesVersionFile() + assert.Equal(t, tc.Expected, match, tc.Case) + assert.Equal(t, tc.ExpectedVersion, version, tc.Case) + } +} + +func TestNodeInContext(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + hasPNPM bool + hasYarn bool + hasNPM bool + hasDefault bool + hasBun bool + PkgMgrEnabled bool + }{ + {Case: "no package manager file", ExpectedString: "", PkgMgrEnabled: true}, + {Case: "pnpm", hasPNPM: true, ExpectedString: "pnpm", PkgMgrEnabled: true}, + {Case: "yarn", hasYarn: true, ExpectedString: "yarn", PkgMgrEnabled: true}, + {Case: "npm", hasNPM: true, ExpectedString: "npm", PkgMgrEnabled: true}, + {Case: "default", hasDefault: true, ExpectedString: "npm", PkgMgrEnabled: true}, + {Case: "disabled by pnpm", hasPNPM: true, ExpectedString: "", PkgMgrEnabled: false}, + {Case: "disabled by yarn", hasYarn: true, ExpectedString: "", PkgMgrEnabled: false}, + {Case: "pnpm and npm", hasPNPM: true, hasNPM: true, ExpectedString: "pnpm", PkgMgrEnabled: true}, + {Case: "yarn and npm", hasYarn: true, hasNPM: true, ExpectedString: "yarn", PkgMgrEnabled: true}, + {Case: "pnpm, yarn, and npm", hasPNPM: true, hasYarn: true, hasNPM: true, ExpectedString: "pnpm", PkgMgrEnabled: true}, + {Case: "bun", hasBun: true, ExpectedString: "bun", PkgMgrEnabled: true}, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("HasFiles", "pnpm-lock.yaml").Return(tc.hasPNPM) + env.On("HasFiles", "yarn.lock").Return(tc.hasYarn) + env.On("HasFiles", "package-lock.json").Return(tc.hasNPM) + env.On("HasFiles", "package.json").Return(tc.hasDefault) + env.On("HasFiles", "bun.lockb").Return(tc.hasBun) + env.On("HasFiles", "bun.lock").Return(tc.hasBun) + + props := options.Map{ + PnpmIcon: "pnpm", + YarnIcon: "yarn", + NPMIcon: "npm", + BunIcon: "bun", + FetchPackageManager: tc.PkgMgrEnabled, + } + + node := &Node{} + node.Init(props, env) + + node.loadContext() + assert.Equal(t, tc.ExpectedString, node.PackageManagerIcon, tc.Case) + } +} diff --git a/src/segments/npm.go b/src/segments/npm.go new file mode 100644 index 000000000000..06289f40ddb2 --- /dev/null +++ b/src/segments/npm.go @@ -0,0 +1,24 @@ +package segments + +type Npm struct { + Language +} + +func (n *Npm) Enabled() bool { + n.extensions = []string{fileName, "package-lock.json"} + n.tooling = map[string]*cmd{ + npmToolName: { + executable: npmToolName, + args: []string{versionFlagArg}, + regex: versionRegex, + }, + } + n.defaultTooling = []string{npmToolName} + n.versionURLTemplate = "https://github.com/npm/cli/releases/tag/v{{ .Full }}" + + return n.Language.Enabled() +} + +func (n *Npm) Template() string { + return " \ue71e {{.Full}} " +} diff --git a/src/segments/npm_test.go b/src/segments/npm_test.go new file mode 100644 index 000000000000..158167c6fcda --- /dev/null +++ b/src/segments/npm_test.go @@ -0,0 +1,31 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/alecthomas/assert" +) + +func TestNpm(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + {Case: "1.0.0", ExpectedString: "\ue71e 1.0.0", Version: "1.0.0"}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "npm", + versionParam: "--version", + versionOutput: tc.Version, + extension: "package.json", + } + env, props := getMockedLanguageEnv(params) + npm := &Npm{} + npm.Init(props, env) + assert.True(t, npm.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, npm.Template(), npm), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/nx.go b/src/segments/nx.go new file mode 100644 index 000000000000..11629e754c67 --- /dev/null +++ b/src/segments/nx.go @@ -0,0 +1,27 @@ +package segments + +type Nx struct { + Language +} + +func (a *Nx) Template() string { + return languageTemplate +} + +func (a *Nx) Enabled() bool { + a.extensions = []string{"workspace.json", "nx.json"} + a.tooling = map[string]*cmd{ + "nx": { + regex: versionRegexPrefixed, + getVersion: a.getVersion, + }, + } + a.defaultTooling = []string{"nx"} + a.versionURLTemplate = "https://github.com/nrwl/nx/releases/tag/{{.Full}}" + + return a.Language.Enabled() +} + +func (a *Nx) getVersion() (string, error) { + return a.nodePackageVersion("nx") +} diff --git a/src/segments/ocaml.go b/src/segments/ocaml.go new file mode 100644 index 000000000000..58cdb31868d9 --- /dev/null +++ b/src/segments/ocaml.go @@ -0,0 +1,25 @@ +package segments + +type OCaml struct { + Language +} + +func (o *OCaml) Template() string { + return languageTemplate +} + +func (o *OCaml) Enabled() bool { + const ocamlToolName = "ocaml" + + o.extensions = []string{"*.ml", "*.mli", "dune", "dune-project", "dune-workspace"} + o.tooling = map[string]*cmd{ + ocamlToolName: { + executable: ocamlToolName, + args: []string{versionFlagShortArg}, + regex: `The OCaml toplevel, version (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))(-(?P[a-z]+))?)`, + }, + } + o.defaultTooling = []string{ocamlToolName} + + return o.Language.Enabled() +} diff --git a/src/segments/ocaml_test.go b/src/segments/ocaml_test.go new file mode 100644 index 000000000000..73b2aaa69b92 --- /dev/null +++ b/src/segments/ocaml_test.go @@ -0,0 +1,33 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOCaml(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + {Case: "OCaml 4.12.0", ExpectedString: "4.12.0", Version: "The OCaml toplevel, version 4.12.0"}, + {Case: "OCaml 4.11.0", ExpectedString: "4.11.0", Version: "The OCaml toplevel, version 4.11.0"}, + {Case: "OCaml 4.13.0", ExpectedString: "4.13.0", Version: "The OCaml toplevel, version 4.13.0"}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "ocaml", + versionParam: "-version", + versionOutput: tc.Version, + extension: "*.ml", + } + env, props := getMockedLanguageEnv(params) + o := &OCaml{} + o.Init(props, env) + assert.True(t, o.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, o.Template(), o), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/options/map.go b/src/segments/options/map.go new file mode 100644 index 000000000000..09fb282fc95c --- /dev/null +++ b/src/segments/options/map.go @@ -0,0 +1,307 @@ +package options + +import ( + "encoding/gob" + "fmt" + + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/generics" + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/template" +) + +func init() { + gob.Register([]any{}) + gob.Register(map[string]any{}) + gob.Register(map[any]any{}) + gob.Register([]string{}) + gob.Register(map[string]string{}) + gob.Register([]int{}) + gob.Register([]float64{}) + gob.Register([]bool{}) + gob.Register(int64(0)) + gob.Register(uint64(0)) + gob.Register(float32(0)) + gob.Register(Map{}) + gob.Register((*Option)(nil)) + gob.Register(map[Option]any{}) +} + +type Provider interface { + Color(option Option, defaultValue color.Ansi) color.Ansi + Bool(option Option, defaultValue bool) bool + String(option Option, defaultValue string) string + Template(option Option, defaultValue string, context any) string + Float64(option Option, defaultValue float64) float64 + Int(option Option, defaultValue int) int + KeyValueMap(option Option, defaultValue map[string]string) map[string]string + StringArray(option Option, defaultValue []string) []string + Any(option Option, defaultValue any) any +} + +// Option defines one property of a segment for context +type Option string + +// general options used across Segments +const ( + // Style indicates the style to use + Style Option = "style" + // FetchVersion decides whether to fetch the version number or not + FetchVersion Option = "fetch_version" + // AlwaysEnabled decides whether or not to always display the info + AlwaysEnabled Option = "always_enabled" + // VersionURLTemplate is the template to use when building language segment hyperlink + VersionURLTemplate Option = "version_url_template" + // DisplayError decides whether to display when an error occurs or not + DisplayError Option = "display_error" + // DisplayDefault hides or shows the default + DisplayDefault Option = "display_default" + // AccessToken is the access token to use for an API + AccessToken Option = "access_token" + // RefreshToken is the refresh token to use for an API + RefreshToken Option = "refresh_token" + // HTTPTimeout timeout used when executing http request + HTTPTimeout Option = "http_timeout" + // DefaultHTTPTimeout default timeout used when executing http request + DefaultHTTPTimeout = 20 + // Files to trigger the segment on + Files Option = "files" + // Duration of the cache + CacheDuration Option = "cache_duration" +) + +type Map map[Option]any + +func (m Map) String(option Option, defaultValue string) string { + val, found := m[option] + if !found { + log.Debug(fmt.Sprintf("%s: %s", option, defaultValue)) + return defaultValue + } + value := fmt.Sprint(val) + log.Debug(fmt.Sprintf("%s: %s", option, value)) + return value +} + +// Template resolves the option value as a template and returns the resolved string. +// This allows using template syntax like {{ .Env.MY_API_KEY }} in configuration values. +// If template rendering fails, it returns the original string value. +func (m Map) Template(option Option, defaultValue string, context any) string { + value := m.String(option, defaultValue) + if value == "" { + return value + } + + resolved, err := template.Render(value, context) + if err != nil { + log.Debug(fmt.Sprintf("%s: template error, using raw value: %s", option, err)) + return value + } + + log.Debug(fmt.Sprintf("%s (template resolved): %s", option, resolved)) + return resolved +} + +func (m Map) Color(option Option, defaultValue color.Ansi) color.Ansi { + val, found := m[option] + if !found { + log.Debug(fmt.Sprintf("%s: %s", option, defaultValue)) + return defaultValue + } + + colorString := color.Ansi(fmt.Sprint(val)) + if color.IsAnsiColorName(colorString) { + log.Debug(fmt.Sprintf("%s: %s", option, colorString)) + return colorString + } + + values := regex.FindNamedRegexMatch(`(?P#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|p:.*)`, colorString.String()) + if values != nil && values["color"] != "" { + value := color.Ansi(values["color"]) + log.Debug(fmt.Sprintf("%s: %s", option, value)) + return value + } + + log.Debug(fmt.Sprintf("%s: %s", option, defaultValue)) + return defaultValue +} + +func (m Map) Bool(option Option, defaultValue bool) bool { + val, found := m[option] + if !found { + log.Debug(fmt.Sprintf("%s: %t", option, defaultValue)) + return defaultValue + } + boolValue, ok := val.(bool) + if !ok { + log.Debug(fmt.Sprintf("%s: %t", option, defaultValue)) + return defaultValue + } + log.Debug(fmt.Sprintf("%s: %t", option, boolValue)) + return boolValue +} + +func (m Map) Float64(option Option, defaultValue float64) float64 { + val, found := m[option] + if !found { + log.Debug(fmt.Sprintf("%s: %f", option, defaultValue)) + return defaultValue + } + + // Direct type conversions for common numeric types + switch v := val.(type) { + case float64: + log.Debug(fmt.Sprintf("%s: %f", option, v)) + return v + case int: + value := float64(v) + log.Debug(fmt.Sprintf("%s: %f", option, value)) + return value + case int64: + value := float64(v) + log.Debug(fmt.Sprintf("%s: %f", option, value)) + return value + case uint64: + value := float64(v) + log.Debug(fmt.Sprintf("%s: %f", option, value)) + return value + default: + log.Debug(fmt.Sprintf("%s: %f", option, defaultValue)) + return defaultValue + } +} + +func (m Map) Int(option Option, defaultValue int) int { + val, found := m[option] + if !found { + log.Debug(fmt.Sprintf("%s: %d", option, defaultValue)) + return defaultValue + } + + // Direct type conversions for common numeric types + switch v := val.(type) { + case int: + log.Debug(fmt.Sprintf("%s: %d", option, v)) + return v + case int64: + value := int(v) + log.Debug(fmt.Sprintf("%s: %d", option, value)) + return value + case uint64: + value := int(v) + log.Debug(fmt.Sprintf("%s: %d", option, value)) + return value + case float64: + value := int(v) + log.Debug(fmt.Sprintf("%s: %d", option, value)) + return value + default: + log.Debug(fmt.Sprintf("%s: %d", option, defaultValue)) + return defaultValue + } +} + +func (m Map) KeyValueMap(option Option, defaultValue map[string]string) map[string]string { + val, found := m[option] + if !found { + log.Debug(fmt.Sprintf("%s: %v", option, defaultValue)) + return defaultValue + } + + keyValues := parseKeyValueArray(val) + log.Debug(fmt.Sprintf("%s: %v", option, keyValues)) + return keyValues +} + +func (m Map) StringArray(option Option, defaultValue []string) []string { + val, found := m[option] + if !found { + log.Debug(fmt.Sprintf("%s: %v", option, defaultValue)) + return defaultValue + } + + keyValues := ParseStringArray(val) + log.Debug(fmt.Sprintf("%s: %v", option, keyValues)) + return keyValues +} + +func (m Map) Any(option Option, defaultValue any) any { + val, found := m[option] + if !found { + log.Debug(fmt.Sprintf("%s: %v", option, defaultValue)) + return defaultValue + } + + log.Debug(fmt.Sprintf("%s: %v", option, val)) + return val +} + +func ParseStringArray(param any) []string { + return generics.ParseStringSlice(param) +} + +func parseKeyValueArray(param any) map[string]string { + switch v := param.(type) { + default: + return map[string]string{} + case map[any]any: + keyValueArray := make(map[string]string) + for key, value := range v { + val := value.(string) + keyString := fmt.Sprintf("%v", key) + keyValueArray[keyString] = val + } + return keyValueArray + case map[string]any: + keyValueArray := make(map[string]string) + for key, value := range v { + val := value.(string) + keyValueArray[key] = val + } + return keyValueArray + case []any: + keyValueArray := make(map[string]string) + for _, s := range v { + l := ParseStringArray(s) + if len(l) == 2 { + key := l[0] + val := l[1] + keyValueArray[key] = val + } + } + return keyValueArray + case Map: + keyValueArray := make(map[string]string) + for key, value := range v { + val := value.(string) + keyString := fmt.Sprintf("%v", key) + keyValueArray[keyString] = val + } + return keyValueArray + case map[string]string: + return v + } +} + +// Generic functions + +type Value interface { + string | int | []string | float64 | bool +} + +func OneOf[T Value](options Provider, defaultValue T, props ...Option) T { + for _, prop := range props { + // get value on a generic get, then see if we can cast to T? + val := options.Any(prop, nil) + if val == nil { + continue + } + + if v, ok := val.(T); ok { + return v + } + } + + return defaultValue +} diff --git a/src/segments/options/map_test.go b/src/segments/options/map_test.go new file mode 100644 index 000000000000..18232dd0c985 --- /dev/null +++ b/src/segments/options/map_test.go @@ -0,0 +1,254 @@ +package options + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/color" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/stretchr/testify/assert" +) + +const ( + expected = "expected" + expectedColor = color.Ansi("#768954") + + Foo Option = "color" +) + +func TestGetString(t *testing.T) { + var options = Map{Foo: expected} + value := options.String(Foo, "err") + assert.Equal(t, expected, value) +} + +func TestGetStringNoEntry(t *testing.T) { + var options = Map{} + value := options.String(Foo, expected) + assert.Equal(t, expected, value) +} + +func TestGetStringNoTextEntry(t *testing.T) { + var options = Map{Foo: true} + value := options.String(Foo, expected) + assert.Equal(t, "true", value) +} + +func TestGetHexColor(t *testing.T) { + expected := expectedColor + var options = Map{Foo: expected} + value := options.Color(Foo, "#789123") + assert.Equal(t, expected, value) +} + +func TestGetColor(t *testing.T) { + expected := color.Ansi("yellow") + var options = Map{Foo: expected} + value := options.Color(Foo, "#789123") + assert.Equal(t, expected, value) +} + +func TestDefaultColorWithInvalidColorCode(t *testing.T) { + expected := expectedColor + var options = Map{Foo: "invalid"} + value := options.Color(Foo, expected) + assert.Equal(t, expected, value) +} + +func TestDefaultColorWithUnavailableProperty(t *testing.T) { + expected := expectedColor + var options = Map{} + value := options.Color(Foo, expected) + assert.Equal(t, expected, value) +} + +func TestGetPaletteColor(t *testing.T) { + expected := color.Ansi("p:red") + var options = Map{Foo: expected} + value := options.Color(Foo, "white") + assert.Equal(t, expected, value) +} + +func TestGetBool(t *testing.T) { + expected := true + var options = Map{Foo: expected} + value := options.Bool(Foo, false) + assert.True(t, value) +} + +func TestGetBoolPropertyNotInMap(t *testing.T) { + var options = Map{} + value := options.Bool(Foo, false) + assert.False(t, value) +} + +func TestGetBoolInvalidProperty(t *testing.T) { + var options = Map{Foo: "borked"} + value := options.Bool(Foo, false) + assert.False(t, value) +} + +func TestGetFloat64(t *testing.T) { + cases := []struct { + Input any + Case string + Expected float64 + }{ + {Case: "int", Expected: 1337, Input: 1337}, + {Case: "float64", Expected: 1337, Input: float64(1337)}, + {Case: "uint64", Expected: 1337, Input: uint64(1337)}, + {Case: "int64", Expected: 1337, Input: int64(1337)}, + {Case: "string", Expected: 9001, Input: "invalid"}, + {Case: "bool", Expected: 9001, Input: true}, + } + for _, tc := range cases { + options := Map{Foo: tc.Input} + value := options.Float64(Foo, 9001) + assert.Equal(t, tc.Expected, value, tc.Case) + } +} + +func TestGetFloat64PropertyNotInMap(t *testing.T) { + expected := float64(1337) + var options = Map{} + value := options.Float64(Foo, expected) + assert.Equal(t, expected, value) +} + +func TestOneOf(t *testing.T) { + cases := []struct { + Expected any + Map Map + Case string + DefaultValue string + Options []Option + }{ + { + Case: "one element", + Expected: "1337", + Options: []Option{Foo}, + Map: Map{ + Foo: "1337", + }, + DefaultValue: "2000", + }, + { + Case: "two elements", + Expected: "1337", + Options: []Option{Foo}, + Map: Map{ + Foo: "1337", + "Bar": "9001", + }, + DefaultValue: "2000", + }, + { + Case: "no match", + Expected: "2000", + Options: []Option{"Moo"}, + Map: Map{ + Foo: "1337", + "Bar": "9001", + }, + DefaultValue: "2000", + }, + { + Case: "incorrect type", + Expected: "2000", + Options: []Option{Foo}, + Map: Map{ + Foo: 1337, + "Bar": "9001", + }, + DefaultValue: "2000", + }, + } + for _, tc := range cases { + value := OneOf(tc.Map, tc.DefaultValue, tc.Options...) + assert.Equal(t, tc.Expected, value, tc.Case) + } +} + +func TestTemplate(t *testing.T) { + // Need to initialize template package for testing + env := &mock.Environment{} + env.On("Getenv", "MY_API_KEY").Return("secret-key-123") + env.On("Getenv", "MY_USER").Return("testuser") + env.On("Getenv", "SHLVL").Return("1") + env.On("Shell").Return("bash") + env.On("Flags").Return(&runtime.Flags{ + IsPrimary: true, + ShellVersion: "1.0.0", + PromptCount: 1, + JobCount: 0, + PSWD: "/home/test", + AbsolutePWD: "/home/test", + }) + env.On("Root").Return(false) + env.On("StatusCodes").Return(0, "0") + env.On("IsWsl").Return(false) + env.On("Pwd").Return("/home/test") + env.On("GOOS").Return(runtime.LINUX) + env.On("Platform").Return("ubuntu") + env.On("User").Return("testuser") + env.On("Host").Return("testhost", nil) + + // Initialize template package + template.Init(env, nil, nil) + + cases := []struct { + Case string + Options Map + Option Option + DefaultValue string + Context any + Expected string + }{ + { + Case: "plain string no template", + Options: Map{"key": "plain-value"}, + Option: "key", + DefaultValue: "", + Context: nil, + Expected: "plain-value", + }, + { + Case: "template with env var", + Options: Map{"key": "{{ .Env.MY_API_KEY }}"}, + Option: "key", + DefaultValue: "", + Context: nil, + Expected: "secret-key-123", + }, + { + Case: "template with multiple env vars", + Options: Map{"key": "{{ .Env.MY_USER }}/{{ .Env.MY_API_KEY }}"}, + Option: "key", + DefaultValue: "", + Context: nil, + Expected: "testuser/secret-key-123", + }, + { + Case: "empty value returns default", + Options: Map{}, + Option: "key", + DefaultValue: "default-value", + Context: nil, + Expected: "default-value", + }, + { + Case: "invalid template returns raw value", + Options: Map{"key": "{{ .Invalid }}"}, + Option: "key", + DefaultValue: "", + Context: nil, + Expected: "{{ .Invalid }}", + }, + } + + for _, tc := range cases { + value := tc.Options.Template(tc.Option, tc.DefaultValue, tc.Context) + assert.Equal(t, tc.Expected, value, tc.Case) + } +} diff --git a/src/segments/os.go b/src/segments/os.go new file mode 100644 index 000000000000..ca552e5f8019 --- /dev/null +++ b/src/segments/os.go @@ -0,0 +1,101 @@ +package segments + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type Os struct { + Base + + Icon string +} + +const ( + // MacOS the string/icon to use for MacOS + MacOS options.Option = "macos" + // Linux the string/icon to use for linux + Linux options.Option = "linux" + // Windows the string/icon to use for windows + Windows options.Option = "windows" + // Android the string/icon to use for android + Android options.Option = "android" + // DisplayDistroName display the distro name or not + DisplayDistroName options.Option = "display_distro_name" +) + +func (oi *Os) Template() string { + return " {{ if .WSL }}WSL at {{ end }}{{.Icon}} " +} + +func (oi *Os) Enabled() bool { + goos := oi.env.GOOS() + switch goos { + case runtime.WINDOWS: + oi.Icon = oi.options.String(Windows, "\uE62A") + case runtime.DARWIN: + oi.Icon = oi.options.String(MacOS, "\uF179") + case runtime.LINUX, runtime.FREEBSD: + pf := oi.env.Platform() + displayDistroName := oi.options.Bool(DisplayDistroName, false) + if displayDistroName { + oi.Icon = oi.options.String(options.Option(pf), pf) + break + } + oi.Icon = oi.getDistroIcon(pf) + case runtime.ANDROID: + oi.Icon = oi.options.String(Android, "\ue70e") + default: + oi.Icon = goos + } + return true +} + +func (oi *Os) getDistroIcon(distro string) string { + iconMap := map[string]string{ + "alma": "\uf31d", + "almalinux": "\uf31d", + "almalinux9": "\uf31d", + "alpine": "\uf300", + "android": "\ue70e", + "aosc": "\uf301", + "arch": "\uf303", + "centos": "\uf304", + "coreos": "\uf305", + "debian": "\uf306", + "deepin": "\uf321", + "devuan": "\uf307", + "elementary": "\uf309", + "endeavouros": "\uf322", + "fedora": "\uf30a", + "freebsd": "\U000f08e0", + "gentoo": "\uf30d", + "kali": "\uf327", + "mageia": "\uf310", + "manjaro": "\uf312", + "mint": "\U000f08ed", + "neon": "\uf331", + "nixos": "\uf313", + "opensuse": "\uf314", + "opensuse-tumbleweed": "\uf314", + "raspbian": "\uf315", + "redhat": "\uf316", + "rocky": "\uf32b", + "sabayon": "\uf317", + "slackware": "\uf319", + "ubuntu": "\uf31b", + "void": "\uf32e", + "zorin": "\uf32f", + } + + if icon, ok := iconMap[distro]; ok { + return oi.options.String(options.Option(distro), icon) + } + + icon := oi.options.String(options.Option(distro), "") + if len(icon) > 0 { + return icon + } + + return oi.options.String(Linux, "\uF17C") +} diff --git a/src/segments/os_test.go b/src/segments/os_test.go new file mode 100644 index 000000000000..caa0d316550d --- /dev/null +++ b/src/segments/os_test.go @@ -0,0 +1,116 @@ +package segments + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/template" + + "github.com/stretchr/testify/assert" +) + +func TestOSInfo(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + GOOS string + Platform string + Icon string + IsWSL bool + DisplayDistroName bool + }{ + { + Case: "WSL debian - icon", + ExpectedString: "WSL at \uf306", + GOOS: "linux", + IsWSL: true, + Platform: "debian", + }, + { + Case: "WSL debian - name", + ExpectedString: "WSL at debian", + GOOS: "linux", + IsWSL: true, + Platform: "debian", + DisplayDistroName: true, + }, + { + Case: "plain linux - icon", + ExpectedString: "\uf306", + GOOS: "linux", + Platform: "debian", + }, + { + Case: "plain linux - name", + ExpectedString: "debian", + GOOS: "linux", + Platform: "debian", + DisplayDistroName: true, + }, + { + Case: "windows", + ExpectedString: "windows", + GOOS: "windows", + }, + { + Case: "darwin", + ExpectedString: "darwin", + GOOS: "darwin", + }, + { + Case: "unknown", + ExpectedString: "unknown", + GOOS: "unknown", + }, + { + Case: "crazy distro, specific icon", + ExpectedString: "crazy distro", + GOOS: "linux", + Platform: "crazy", + Icon: "crazy distro", + }, + { + Case: "crazy distro, not mapped", + ExpectedString: "\uf17c", + GOOS: "linux", + Platform: "crazy", + }, + { + Case: "show distro name, mapped", + ExpectedString: "<3", + DisplayDistroName: true, + GOOS: "linux", + Icon: "<3", + Platform: "love", + }, + } + for _, tc := range cases { + env := new(mock.Environment) + env.On("GOOS").Return(tc.GOOS) + env.On("Platform").Return(tc.Platform) + + props := options.Map{ + DisplayDistroName: tc.DisplayDistroName, + Windows: "windows", + MacOS: "darwin", + } + + if len(tc.Icon) != 0 { + props[options.Option(tc.Platform)] = tc.Icon + } + + osInfo := &Os{} + osInfo.Init(props, env) + + template.Cache = &cache.Template{ + SimpleTemplate: cache.SimpleTemplate{ + WSL: tc.IsWSL, + }, + } + + _ = osInfo.Enabled() + assert.Equal(t, tc.ExpectedString, renderTemplate(env, osInfo.Template(), osInfo), tc.Case) + } +} diff --git a/src/segments/owm.go b/src/segments/owm.go new file mode 100644 index 000000000000..780ac91f737f --- /dev/null +++ b/src/segments/owm.go @@ -0,0 +1,167 @@ +package segments + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "net/url" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type Owm struct { + Base + + Weather string + URL string + units string + UnitIcon string + Temperature int +} + +const ( + // APIKey openweathermap api key + APIKey options.Option = "api_key" + // Location openweathermap location + Location options.Option = "location" + // Units openweathermap units + Units options.Option = "units" + // CacheKeyResponse key used when caching the response + CacheKeyResponse string = "owm_response" + // CacheKeyURL key used when caching the url responsible for the response + CacheKeyURL string = "owm_url" +) + +type weather struct { + ShortDescription string `json:"main"` + Description string `json:"description"` + TypeID string `json:"icon"` +} +type temperature struct { + Value float64 `json:"temp"` +} + +type owmDataResponse struct { + Data []weather `json:"weather"` + temperature `json:"main"` +} + +func (d *Owm) Enabled() bool { + err := d.setStatus() + + if err != nil { + log.Error(err) + return false + } + + return true +} + +func (d *Owm) Template() string { + return " {{ .Weather }} ({{ .Temperature }}{{ .UnitIcon }}) " +} + +func (d *Owm) getResult() (*owmDataResponse, error) { + response := new(owmDataResponse) + + apikey := d.options.Template(APIKey, "", d) + if apikey == "" { + return nil, errors.New("no api key found") + } + + location := d.options.Template(Location, "", d) + if location == "" { + return nil, errors.New("no location found") + } + + location = url.QueryEscape(location) + + units := d.options.String(Units, "standard") + httpTimeout := d.options.Int(options.HTTPTimeout, options.DefaultHTTPTimeout) + + d.URL = fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?q=%s&units=%s&appid=%s", location, units, apikey) + + body, err := d.env.HTTPRequest(d.URL, nil, httpTimeout) + if err != nil { + return new(owmDataResponse), err + } + + err = json.Unmarshal(body, &response) + if err != nil { + return new(owmDataResponse), err + } + + return response, nil +} + +func (d *Owm) setStatus() error { + units := d.options.String(Units, "standard") + + q, err := d.getResult() + if err != nil { + return err + } + + if len(q.Data) == 0 { + return errors.New("no data found") + } + + id := q.Data[0].TypeID + + d.Temperature = int(math.Round(q.Value)) + icon := "" + switch id { + case "01n": + icon = "\ue32b" + case "01d": + icon = "\ue30d" + case "02n": + icon = "\ue37e" + case "02d": + icon = "\ue302" + case "03n": + fallthrough + case "03d": + icon = "\ue33d" + case "04n": + fallthrough + case "04d": + icon = "\ue312" + case "09n": + fallthrough + case "09d": + icon = "\ue319" + case "10n": + icon = "\ue325" + case "10d": + icon = "\ue308" + case "11n": + icon = "\ue32a" + case "11d": + icon = "\ue30f" + case "13n": + fallthrough + case "13d": + icon = "\ue31a" + case "50n": + fallthrough + case "50d": + icon = "\ue313" + } + d.Weather = icon + d.units = units + d.UnitIcon = "\ue33e" + switch d.units { + case "imperial": + d.UnitIcon = "°F" // \ue341" + case "metric": + d.UnitIcon = "°C" // \ue339" + case "": + fallthrough + case "standard": + d.UnitIcon = "°K" // K" + } + return nil +} diff --git a/src/segments/owm_test.go b/src/segments/owm_test.go new file mode 100644 index 000000000000..173cd22de1c9 --- /dev/null +++ b/src/segments/owm_test.go @@ -0,0 +1,223 @@ +package segments + +import ( + "errors" + "fmt" + "net/url" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + + "github.com/stretchr/testify/assert" +) + +const ( + OWMWEATHERAPIURL = "https://api.openweathermap.org/data/2.5/weather?q=%s&units=metric&appid=key" +) + +func TestOWMSegmentSingle(t *testing.T) { + cases := []struct { + Error error + Case string + Location string + WeatherJSONResponse string + ExpectedString string + Template string + ExpectedEnabled bool + }{ + { + Case: "Sunny Display", + Location: "AMSTERDAM,NL", + WeatherJSONResponse: `{"weather":[{"icon":"01d"}],"main":{"temp":20}}`, + ExpectedString: "\ue30d (20°C)", + ExpectedEnabled: true, + }, + { + Case: "Sunny Display", + Location: "AMSTERDAM,NL", + WeatherJSONResponse: `{"weather":[{"icon":"01d"}],"main":{"temp":20}}`, + ExpectedString: "\ue30d (20°C)", + ExpectedEnabled: true, + Template: "{{.Weather}} ({{.Temperature}}{{.UnitIcon}})", + }, + { + Case: "Sunny Display", + Location: "AMSTERDAM,NL", + WeatherJSONResponse: `{"weather":[{"icon":"01d"}],"main":{"temp":20}}`, + ExpectedString: "\ue30d", + ExpectedEnabled: true, + Template: "{{.Weather}} ", + }, + { + Case: "Config Skip Geocoding Check With Location", + Location: "AMSTERDAM,NL", + WeatherJSONResponse: `{"weather":[{"icon":"01d"}],"main":{"temp":20}}`, + ExpectedString: "\ue30d (20°C)", + ExpectedEnabled: true, + }, + { + Case: "Config Skip Geocoding Check Without Location", + WeatherJSONResponse: `{"weather":[{"icon":"01d"}],"main":{"temp":20}}`, + ExpectedEnabled: false, + }, + { + Case: "Error in retrieving data", + Location: "AMSTERDAM,NL", + WeatherJSONResponse: "nonsense", + Error: errors.New("Something went wrong"), + ExpectedEnabled: false, + }, + } + + for _, tc := range cases { + env := &mock.Environment{} + props := options.Map{ + APIKey: "key", + Location: tc.Location, + Units: "metric", + } + + location := url.QueryEscape(tc.Location) + testURL := fmt.Sprintf(OWMWEATHERAPIURL, location) + env.On("HTTPRequest", testURL).Return([]byte(tc.WeatherJSONResponse), tc.Error) + + o := &Owm{} + o.Init(props, env) + + enabled := o.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + if !enabled { + continue + } + + if tc.Template == "" { + tc.Template = o.Template() + } + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, o), tc.Case) + } +} + +func TestOWMSegmentIcons(t *testing.T) { + cases := []struct { + Case string + IconID string + ExpectedIconString string + }{ + { + Case: "Sunny Display day", + IconID: "01d", + ExpectedIconString: "\ue30d", + }, + { + Case: "Light clouds Display day", + IconID: "02d", + ExpectedIconString: "\ue302", + }, + { + Case: "Cloudy Display day", + IconID: "03d", + ExpectedIconString: "\ue33d", + }, + { + Case: "Broken Clouds Display day", + IconID: "04d", + ExpectedIconString: "\ue312", + }, + { + Case: "Shower Rain Display day", + IconID: "09d", + ExpectedIconString: "\ue319", + }, + { + Case: "Rain Display day", + IconID: "10d", + ExpectedIconString: "\ue308", + }, + { + Case: "Thunderstorm Display day", + IconID: "11d", + ExpectedIconString: "\ue30f", + }, + { + Case: "Snow Display day", + IconID: "13d", + ExpectedIconString: "\ue31a", + }, + { + Case: "Fog Display day", + IconID: "50d", + ExpectedIconString: "\ue313", + }, + + { + Case: "Sunny Display night", + IconID: "01n", + ExpectedIconString: "\ue32b", + }, + { + Case: "Light clouds Display night", + IconID: "02n", + ExpectedIconString: "\ue37e", + }, + { + Case: "Cloudy Display night", + IconID: "03n", + ExpectedIconString: "\ue33d", + }, + { + Case: "Broken Clouds Display night", + IconID: "04n", + ExpectedIconString: "\ue312", + }, + { + Case: "Shower Rain Display night", + IconID: "09n", + ExpectedIconString: "\ue319", + }, + { + Case: "Rain Display night", + IconID: "10n", + ExpectedIconString: "\ue325", + }, + { + Case: "Thunderstorm Display night", + IconID: "11n", + ExpectedIconString: "\ue32a", + }, + { + Case: "Snow Display night", + IconID: "13n", + ExpectedIconString: "\ue31a", + }, + { + Case: "Fog Display night", + IconID: "50n", + ExpectedIconString: "\ue313", + }, + } + + location := url.QueryEscape("AMSTERDAM,NL") + testURL := fmt.Sprintf(OWMWEATHERAPIURL, location) + + for _, tc := range cases { + env := &mock.Environment{} + + weatherResponse := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20.3}}`, tc.IconID) + expectedString := fmt.Sprintf("%s (20°C)", tc.ExpectedIconString) + + env.On("HTTPRequest", testURL).Return([]byte(weatherResponse), nil) + + props := options.Map{ + APIKey: "key", + Location: "AMSTERDAM,NL", + Units: "metric", + } + + o := &Owm{} + o.Init(props, env) + + assert.Nil(t, o.setStatus()) + assert.Equal(t, expectedString, renderTemplate(env, o.Template(), o), tc.Case) + } +} diff --git a/src/segments/path.go b/src/segments/path.go new file mode 100644 index 000000000000..e3412c8faf11 --- /dev/null +++ b/src/segments/path.go @@ -0,0 +1,983 @@ +package segments + +import ( + "fmt" + "sort" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "github.com/jandedobbeleer/oh-my-posh/src/log" + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/text" +) + +const ( + regexPrefix = "re:" +) + +type Folder struct { + Name string + Path string + Display bool +} + +type Folders []*Folder + +func (f Folders) List() []string { + var list []string + + for _, folder := range f { + list = append(list, folder.Name) + } + + return list +} + +func (f Folders) Last() *Folder { + return f[len(f)-1] +} + +type Path struct { + Base + + mappedLocations map[string]string + root string + relative string + pwd string + Location string + pathSeparator string + Path string + Folders Folders + StackCount int + windowsPath bool + Writable bool + RootDir bool + cygPath bool +} + +const ( + // FolderSeparatorIcon the path which is split will be separated by this icon + FolderSeparatorIcon options.Option = "folder_separator_icon" + // FolderSeparatorTemplate the path which is split will be separated by this template + FolderSeparatorTemplate options.Option = "folder_separator_template" + // HomeIcon indicates the $HOME location + HomeIcon options.Option = "home_icon" + // FolderIcon identifies one folder + FolderIcon options.Option = "folder_icon" + // WindowsRegistryIcon indicates the registry location on Windows + WindowsRegistryIcon options.Option = "windows_registry_icon" + // Agnoster displays a short path with separator icon, this the default style + Agnoster string = "agnoster" + // AgnosterFull displays all the folder names with the folder_separator_icon + AgnosterFull string = "agnoster_full" + // AgnosterShort displays the folder names with one folder_separator_icon, regardless of depth + AgnosterShort string = "agnoster_short" + // Short displays a shorter path + Short string = "short" + // Full displays the full path + Full string = "full" + // FolderType displays the current folder + FolderType string = "folder" + // Mixed like agnoster, but if a folder name is short enough, it is displayed as-is + Mixed string = "mixed" + // Letter like agnoster, but with the first letter of each folder name + Letter string = "letter" + // Unique like agnoster, but with the first unique letters of each folder name + Unique string = "unique" + // AgnosterLeft like agnoster, but keeps the left side of the path + AgnosterLeft string = "agnoster_left" + // Powerlevel tries to mimic the powerlevel10k path, + // used in combination with max_width. + Powerlevel string = "powerlevel" + // MixedThreshold the threshold of the length of the path Mixed will display + MixedThreshold options.Option = "mixed_threshold" + // MappedLocations allows overriding certain location with an icon + MappedLocations options.Option = "mapped_locations" + // MappedLocationsEnabled enables overriding certain locations with an icon + MappedLocationsEnabled options.Option = "mapped_locations_enabled" + // MaxDepth Maximum path depth to display without shortening + MaxDepth options.Option = "max_depth" + // MaxWidth Maximum path width to display for powerlevel style + MaxWidth options.Option = "max_width" + // Hides the root location if it doesn't fit in max_depth. Used in Agnoster Short + HideRootLocation options.Option = "hide_root_location" + // A color override cycle + Cycle options.Option = "cycle" + // Color the path separators within the cycle + CycleFolderSeparator options.Option = "cycle_folder_separator" + // format to use on the folder names + FolderFormat options.Option = "folder_format" + // format to use on the first and last folder of the path + EdgeFormat options.Option = "edge_format" + // format to use on first folder of the path + LeftFormat options.Option = "left_format" + // format to use on the last folder of the path + RightFormat options.Option = "right_format" + // GitDirFormat format to use on the git directory + GitDirFormat options.Option = "gitdir_format" + // DisplayCygpath transforms the path to a cygpath format + DisplayCygpath options.Option = "display_cygpath" + // DisplayRoot indicates if the linux root slash should be displayed + DisplayRoot options.Option = "display_root" + // Fish displays the path in a fish-like style + Fish string = "fish" + // DirLength the length of the directory name to display in fish style + DirLength options.Option = "dir_length" + // FullLengthDirs indicates how many full length directory names should be displayed in fish style + FullLengthDirs options.Option = "full_length_dirs" +) + +func (pt *Path) Template() string { + return " {{ .Path }} " +} + +func (pt *Path) Enabled() bool { + pt.setPaths() + if pt.pwd == "" { + return false + } + + pt.setStyle() + pwd := pt.env.Pwd() + + pt.Location = pt.env.Flags().AbsolutePWD + if pt.env.GOOS() == runtime.WINDOWS { + pt.Location = strings.ReplaceAll(pt.Location, `\`, `/`) + } + + pt.StackCount = pt.env.StackCount() + pt.Writable = pt.env.DirIsWritable(pwd) + return true +} + +func (pt *Path) setPaths() { + defer func() { + pt.Folders = pt.splitPath() + }() + + displayCygpath := func() bool { + enableCygpath := pt.options.Bool(DisplayCygpath, false) + if !enableCygpath { + return false + } + + return pt.env.IsCygwin() + } + + pt.cygPath = displayCygpath() + pt.windowsPath = pt.env.GOOS() == runtime.WINDOWS && !pt.cygPath + + if pt.pathSeparator == "" { + pt.pathSeparator = path.Separator() + } + + pt.pwd = pt.env.Pwd() + if pt.env.Shell() == shell.PWSH && len(pt.env.Flags().PSWD) != 0 { + pt.pwd = pt.env.Flags().PSWD + } + + if pt.pwd == "" { + return + } + + // ensure a clean path + pt.root, pt.relative = pt.replaceMappedLocations(pt.pwd) + pt.pwd = pt.join(pt.root, pt.relative) +} + +func (pt *Path) Parent() string { + if pt.pwd == "" { + return "" + } + + folders := pt.Folders.List() + if len(folders) == 0 { + // No parent. + return "" + } + + sb := text.NewBuilder() + + folderSeparator := pt.getFolderSeparator() + + sb.WriteString(pt.root) + if !pt.endWithSeparator(pt.root) { + sb.WriteString(folderSeparator) + } + + for _, folder := range folders[:len(folders)-1] { + sb.WriteString(folder) + sb.WriteString(folderSeparator) + } + + return sb.String() +} + +func (pt *Path) Format(inputPath string) string { + separator := path.Separator() + + elements := strings.Split(inputPath, separator) + if len(elements) == 0 { + return inputPath + } + + if len(elements) == 1 { + return pt.colorizePath(elements[0], nil) + } + + return pt.colorizePath(elements[0], elements[1:]) +} + +func (pt *Path) setStyle() { + if pt.relative == "" { + root := pt.root + + // Only append a separator to a non-filesystem PSDrive root or a Windows drive root. + if (len(pt.env.Flags().PSWD) != 0 || pt.windowsPath) && strings.HasSuffix(root, ":") { + root += pt.getFolderSeparator() + } + + pt.Path = pt.colorizePath(root, nil) + return + } + + switch style := pt.options.String(options.Style, Agnoster); style { + case Agnoster: + maxWidth := pt.getMaxWidth() + pt.Path = pt.getAgnosterPath(maxWidth) + case AgnosterFull: + pt.Path = pt.getAgnosterFullPath() + case AgnosterShort: + pt.Path = pt.getAgnosterShortPath() + case Mixed: + pt.Path = pt.getMixedPath() + case Letter: + pt.Path = pt.getLetterPath() + case Unique: + pt.Path = pt.getUniqueLettersPath(0) + case AgnosterLeft: + pt.Path = pt.getAgnosterLeftPath() + case Full, Short: // "short" is a duplicate of "full", just here for backwards compatibility + pt.Path = pt.getFullPath() + case FolderType: + pt.Path = pt.getFolderPath() + case Powerlevel: + maxWidth := pt.getMaxWidth() + pt.Path = pt.getUniqueLettersPath(maxWidth) + case Fish: + pt.Path = pt.getFishPath() + default: + pt.Path = fmt.Sprintf("Path style: %s is not available", style) + } + + // make sure we resolve all templates + if txt, err := template.Render(pt.Path, pt); err == nil { + pt.Path = txt + } +} + +func (pt *Path) getMaxWidth() int { + width := pt.options.String(MaxWidth, "") + if width == "" { + return 0 + } + + txt, err := template.Render(width, pt) + if err != nil { + log.Error(err) + return 0 + } + + value, err := strconv.Atoi(txt) + if err != nil { + log.Error(err) + return 0 + } + + return value +} + +func (pt *Path) getFolderSeparator() string { + separatorTemplate := pt.options.String(FolderSeparatorTemplate, "") + if separatorTemplate == "" { + separator := pt.options.String(FolderSeparatorIcon, pt.pathSeparator) + // if empty, use the default separator + if separator == "" { + return pt.pathSeparator + } + + return separator + } + + txt, err := template.Render(separatorTemplate, pt) + if err != nil { + log.Error(err) + } + + if txt == "" { + return pt.pathSeparator + } + + return txt +} + +func (pt *Path) getMixedPath() string { + threshold := int(pt.options.Float64(MixedThreshold, 4)) + folderIcon := pt.options.String(FolderIcon, "..") + + root, folders := pt.getPaths() + + var elements []string + + for i, n := 0, len(folders); i < n; i++ { + folderName := folders[i].Name + if len(folderName) > threshold && i != n-1 && !folders[i].Display { + elements = append(elements, folderIcon) + continue + } + + elements = append(elements, folderName) + } + + return pt.colorizePath(root, elements) +} + +func (pt *Path) getAgnosterPath(maxWidth int) string { + if maxWidth > 0 { + return pt.getAgnosterMaxWidth(maxWidth) + } + + folderIcon := pt.options.String(FolderIcon, "..") + + root, folders := pt.getPaths() + + var elements []string + + for i, n := 0, len(folders); i < n; i++ { + if folders[i].Display || i == n-1 { + elements = append(elements, folders[i].Name) + continue + } + + elements = append(elements, folderIcon) + } + + return pt.colorizePath(root, elements) +} + +func (pt *Path) getAgnosterLeftPath() string { + folderIcon := pt.options.String(FolderIcon, "..") + + root, folders := pt.getPaths() + + var elements []string + if len(folders) == 0 { + return pt.colorizePath(root, elements) + } + + elements = append(elements, folders[0].Name) + for i, n := 1, len(folders); i < n; i++ { + if folders[i].Display { + elements = append(elements, folders[i].Name) + continue + } + + elements = append(elements, folderIcon) + } + + return pt.colorizePath(root, elements) +} + +func (pt *Path) findFirstLetterOrNumber(txt string) (letter string, index int) { + for i, char := range txt { + if unicode.IsLetter(char) || unicode.IsNumber(char) { + return string(char), i + } + } + + return txt, 0 +} + +func (pt *Path) getRelevantLetter(folder *Folder) string { + if folder.Display { + return folder.Name + } + + letter, index := pt.findFirstLetterOrNumber(folder.Name) + if index == 0 { + return letter + } + + // handle non-letter characters before the first found letter + return folder.Name[0:index] + letter +} + +func (pt *Path) getLetterPath() string { + root, folders := pt.getPaths() + + root = pt.getRelevantLetter(&Folder{Name: root}) + + var elements []string + for i, n := 0, len(folders); i < n; i++ { + if folders[i].Display || i == n-1 { + elements = append(elements, folders[i].Name) + continue + } + + letter := pt.getRelevantLetter(folders[i]) + elements = append(elements, letter) + } + + return pt.colorizePath(root, elements) +} + +func (pt *Path) getFishPath() string { + root, folders := pt.getPaths() + folders = append(Folders{&Folder{Name: root, Display: false}}, folders...) + + dirLength := pt.options.Int(DirLength, 1) + fullLengthDirs := max(pt.options.Int(FullLengthDirs, 1), 1) + + folderCount := len(folders) + stopAt := folderCount - fullLengthDirs + + var elements []string + for i := range folderCount { + name := folders[i].Name + runeCount := utf8.RuneCountInString(name) + if folders[i].Display || dirLength <= 0 || runeCount < dirLength || i >= stopAt { + elements = append(elements, name) + continue + } + + // Convert string to rune slice to properly handle multi-byte characters + runes := []rune(name) + elements = append(elements, string(runes[:dirLength])) + } + + if len(elements) == 1 { + return pt.colorizePath(elements[0], nil) + } + + return pt.colorizePath(elements[0], elements[1:]) +} + +func (pt *Path) getUniqueLettersPath(maxWidth int) string { + dr := pt.options.Bool(DisplayRoot, false) + log.Debugf("%t", dr) + separator := pt.getFolderSeparator() + + root, folders := pt.getPaths() + + folderNames := folders.List() + + usePowerlevelStyle := func(root, relative string) bool { + length := len(root) + len(relative) + if !pt.endWithSeparator(root) { + length += len(separator) + } + return length <= maxWidth + } + + if maxWidth > 0 { + relative := strings.Join(folderNames, separator) + if usePowerlevelStyle(root, relative) { + return pt.colorizePath(root, folderNames) + } + } + + root = pt.getRelevantLetter(&Folder{Name: root}) + + var elements []string + letters := make(map[string]bool) + letters[root] = true + + for i, n := 0, len(folders); i < n; i++ { + folderName := folderNames[i] + + if i == n-1 { + elements = append(elements, folderName) + break + } + + letter := pt.getRelevantLetter(folders[i]) + + for letters[letter] { + if letter == folderName { + break + } + letter += folderName[len(letter) : len(letter)+1] + } + + letters[letter] = true + elements = append(elements, letter) + + // only return early on maxWidth > 0 + // this enables the powerlevel10k behavior + if maxWidth > 0 { + list := elements + list = append(list, folderNames[i+1:]...) + relative := strings.Join(list, separator) + if usePowerlevelStyle(root, relative) { + return pt.colorizePath(root, list) + } + } + } + + return pt.colorizePath(root, elements) +} + +func (pt *Path) getAgnosterMaxWidth(maxWidth int) string { + separator := pt.getFolderSeparator() + folderIcon := pt.options.String(FolderIcon, "..") + + root, folders := pt.getPaths() + folderNames := append([]string{root}, folders.List()...) + + // this assumes that the root is never a single character + // except when it really is / on unix systems + if len(root) == 1 { + maxWidth++ // add one for the separator + } + + if len(folderNames) == 0 { + return pt.colorizePath(root, nil) + } + + fullPath := strings.Join(folderNames, separator) + + for i := 0; i < len(folderNames)-1 && utf8.RuneCountInString(fullPath) > maxWidth; i++ { + folderNames[i] = folderIcon + fullPath = strings.Join(folderNames, separator) + } + + for len(folderNames) > 1 && utf8.RuneCountInString(fullPath) > maxWidth { + // remove every folder until the path is short enough + folderNames = folderNames[1:] + fullPath = strings.Join(folderNames, separator) + } + + if len(folderNames) == 1 { + return pt.colorizePath(template.TruncE(maxWidth, folderNames[0]), nil) + } + + return pt.colorizePath(folderNames[0], folderNames[1:]) +} + +func (pt *Path) getAgnosterFullPath() string { + root, folders := pt.getPaths() + + return pt.colorizePath(root, folders.List()) +} + +func (pt *Path) getAgnosterShortPath() string { + root, folders := pt.getPaths() + + maxDepth := max(pt.options.Int(MaxDepth, 1), 1) + + pathDepth := len(folders) + hideRootLocation := pt.options.Bool(HideRootLocation, false) + folderIcon := pt.options.String(FolderIcon, "..") + + // No need to shorten. + if pathDepth < maxDepth || (pathDepth == maxDepth && !hideRootLocation) { + return pt.getAgnosterFullPath() + } + + elements := []string{folderIcon} + + for i := pathDepth - maxDepth; i < pathDepth; i++ { + elements = append(elements, folders[i].Name) + } + + if hideRootLocation { + return pt.colorizePath(elements[0], elements[1:]) + } + + return pt.colorizePath(root, elements) +} + +func (pt *Path) getFullPath() string { + return pt.colorizePath(pt.root, pt.Folders.List()) +} + +func (pt *Path) getFolderPath() string { + folderName := pt.Folders[len(pt.Folders)-1].Name + return pt.colorizePath(folderName, nil) +} + +func (pt *Path) join(root, relative string) string { + // this is a full replacement of the parent + if root == "" { + return relative + } + + if !pt.endWithSeparator(root) && len(relative) > 0 { + return root + pt.pathSeparator + relative + } + + return root + relative +} + +func (pt *Path) setMappedLocations() { + if pt.mappedLocations != nil { + return + } + + mappedLocations := make(map[string]string) + + // predefined mapped locations, can be disabled + if pt.options.Bool(MappedLocationsEnabled, true) { + mappedLocations["hkcu:"] = pt.options.String(WindowsRegistryIcon, "\uF013") + mappedLocations["hklm:"] = pt.options.String(WindowsRegistryIcon, "\uF013") + mappedLocations[pt.normalize(pt.env.Home())] = pt.options.String(HomeIcon, "~") + } + + // merge custom locations with mapped locations + // mapped locations can override predefined locations + keyValues := pt.options.KeyValueMap(MappedLocations, make(map[string]string)) + for key, value := range keyValues { + if key == "" { + continue + } + + location, err := template.Render(key, pt) + if err != nil { + log.Error(err) + } + + if location == "" { + continue + } + + if !strings.HasPrefix(location, regexPrefix) { + location = pt.normalize(location) + } + + // When two templates resolve to the same key, the values are compared in ascending order and the latter is taken. + if v, exist := mappedLocations[location]; exist && value <= v { + continue + } + + mappedLocations[location] = value + } + + pt.mappedLocations = mappedLocations +} + +func (pt *Path) replaceMappedLocations(inputPath string) (string, string) { + root, relative := pt.parsePath(inputPath) + if relative == "" { + pt.RootDir = true + } + + pt.setMappedLocations() + if len(pt.mappedLocations) == 0 { + return root, relative + } + + // sort map keys in reverse order + // fixes case when a subfoder and its parent are mapped + // ex /users/test and /users/test/dev + keys := make([]string, 0, len(pt.mappedLocations)) + for k := range pt.mappedLocations { + keys = append(keys, k) + } + sort.Sort(sort.Reverse(sort.StringSlice(keys))) + + rootN := pt.normalize(root) + relativeN := pt.normalize(relative) + + escape := func(path string) string { + // Escape chevron characters to avoid applying unexpected text styles. + return strings.NewReplacer("<", "<<>", ">", "<>>").Replace(path) + } + + handleRegex := func(key string) (string, bool) { + if !strings.HasPrefix(key, regexPrefix) { + return "", false + } + + input := strings.ReplaceAll(inputPath, `\`, `/`) + pattern := key[len(regexPrefix):] + + // Add (?i) at the start of the pattern for case-insensitive matching on Windows + if pt.windowsPath || (pt.env.IsWsl() && strings.HasPrefix(input, "/mnt/")) { + pattern = "(?i)" + pattern + } + + match, OK := regex.FindStringMatch(pattern, input, 1) + if !OK { + return "", false + } + + // Replace the first match with the mapped location. + input = strings.Replace(input, match, pt.mappedLocations[key], 1) + input = path.Clean(input) + + return input, true + } + + for _, key := range keys { + if input, OK := handleRegex(key); OK { + return pt.parsePath(input) + } + + keyRoot, keyRelative := pt.parsePath(key) + + matchSubFolders := strings.HasSuffix(keyRelative, pt.pathSeparator+"*") + + if matchSubFolders { + // Remove the trailing wildcard (*). + keyRelative = keyRelative[:len(keyRelative)-1] + } + + if keyRoot != rootN || !strings.HasPrefix(relativeN, keyRelative) { + continue + } + + value := pt.mappedLocations[key] + overflow := relative[len(keyRelative):] + + // exactly match the full path + if overflow == "" { + return value, "" + } + + // only match the root + if keyRelative == "" { + return value, strings.Trim(escape(relative), pt.pathSeparator) + } + + // match several prefix elements + if matchSubFolders || overflow[:1] == pt.pathSeparator { + return value, strings.Trim(escape(overflow), pt.pathSeparator) + } + } + + return escape(root), strings.Trim(escape(relative), pt.pathSeparator) +} + +// parsePath parses a clean input path into a root and a relative. +func (pt *Path) parsePath(inputPath string) (string, string) { + var root, relative string + + if inputPath == "" { + return root, relative + } + + if pt.cygPath { + cygPath, err := pt.env.RunCommand("cygpath", "-u", inputPath) + if len(cygPath) != 0 { + inputPath = cygPath + pt.pathSeparator = "/" + } + + if err != nil { + pt.cygPath = false + pt.windowsPath = true + } + } + + if pt.env.GOOS() == runtime.WINDOWS { + // Handle a UNC path, if any. + pattern := fmt.Sprintf(`^\%[1]s{2}(?P[^\%[1]s]+)\%[1]s(?P[^\%[1]s]+)(\%[1]s(?P[\s\S]*))?$`, pt.pathSeparator) + matches := regex.FindNamedRegexMatch(pattern, inputPath) + if len(matches) > 0 { + root = fmt.Sprintf(`%[1]s%[1]s%[2]s%[1]s%[3]s`, pt.pathSeparator, matches["hostname"], matches["sharename"]) + relative = matches["path"] + return root, relative + } + } + + s := strings.SplitAfterN(inputPath, pt.pathSeparator, 2) + root = s[0] + + if len(s) == 2 { + if len(root) > 1 { + root = root[:len(root)-1] + } + + relative = s[1] + } + + return root, relative +} + +func (pt *Path) getPaths() (string, Folders) { + root := pt.root + folders := pt.Folders + + isRootFS := func(inputPath string) bool { + displayRoot := pt.options.Bool(DisplayRoot, false) + if displayRoot { + return false + } + + return len(inputPath) == 1 && path.IsSeparator(inputPath[0]) + } + + if isRootFS(root) && len(folders) > 0 { + root = folders[0].Name + folders = folders[1:] + } + + return root, folders +} + +func (pt *Path) endWithSeparator(inputPath string) bool { + if inputPath == "" { + return false + } + + return path.IsSeparator(inputPath[len(inputPath)-1]) +} + +func (pt *Path) normalize(inputPath string) string { + normalized := inputPath + + if strings.HasPrefix(normalized, "~") && (len(normalized) == 1 || path.IsSeparator(normalized[1])) { + normalized = pt.env.Home() + normalized[1:] + } + + normalized = path.Clean(normalized) + + if pt.env.GOOS() == runtime.WINDOWS || pt.env.GOOS() == runtime.DARWIN { + normalized = strings.ToLower(normalized) + } + + if pt.cygPath { + return strings.ReplaceAll(normalized, `\`, "/") + } + + return normalized +} + +func (pt *Path) colorizePath(root string, elements []string) string { + cycle := pt.options.StringArray(Cycle, []string{}) + skipColorize := len(cycle) == 0 + folderSeparator := pt.getFolderSeparator() + colorSeparator := pt.options.Bool(CycleFolderSeparator, false) + folderFormat := pt.options.String(FolderFormat, "%s") + + edgeFormat := pt.options.String(EdgeFormat, folderFormat) + leftFormat := pt.options.String(LeftFormat, edgeFormat) + rightFormat := pt.options.String(RightFormat, edgeFormat) + + colorizeElement := func(element string) string { + if skipColorize || element == "" { + return element + } + + defer func() { + cycle = append(cycle[1:], cycle[0]) + }() + + return fmt.Sprintf("<%s>%s", cycle[0], element) + } + + if len(elements) == 0 { + formattedRoot := fmt.Sprintf(leftFormat, root) + return colorizeElement(formattedRoot) + } + + colorizeSeparator := func() string { + if skipColorize || !colorSeparator { + return folderSeparator + } + return fmt.Sprintf("<%s>%s", cycle[0], folderSeparator) + } + + // Pre-calculate total capacity needed + totalLen := len(root) + for _, el := range elements { + totalLen += len(el) + 20 // estimate for color codes + } + + sb := text.NewBuilder() + + sb.Grow(totalLen) + + formattedRoot := fmt.Sprintf(leftFormat, root) + sb.WriteString(colorizeElement(formattedRoot)) + + if !pt.endWithSeparator(root) { + sb.WriteString(colorizeSeparator()) + } + + for i, element := range elements { + if element == "" { + continue + } + + format := folderFormat + if i == len(elements)-1 { + format = rightFormat + } + + formattedElement := fmt.Sprintf(format, element) + sb.WriteString(colorizeElement(formattedElement)) + if i != len(elements)-1 { + sb.WriteString(colorizeSeparator()) + } + } + + return sb.String() +} + +func (pt *Path) splitPath() Folders { + folders := Folders{} + + if pt.relative == "" { + return folders + } + + elements := strings.SplitSeq(pt.relative, pt.pathSeparator) + folderFormatMap := pt.makeFolderFormatMap() + currentPath := pt.root + + if !pt.endWithSeparator(pt.root) { + currentPath += pt.pathSeparator + } + + var display bool + + for element := range elements { + currentPath += element + + if format := folderFormatMap[currentPath]; len(format) != 0 { + element = fmt.Sprintf(format, element) + display = true + } + + folders = append(folders, &Folder{Name: element, Path: currentPath, Display: display}) + + currentPath += pt.pathSeparator + + display = false + } + + return folders +} + +func (pt *Path) makeFolderFormatMap() map[string]string { + folderFormatMap := make(map[string]string) + + if gitDirFormat := pt.options.String(GitDirFormat, ""); len(gitDirFormat) != 0 { + dir, err := pt.env.HasParentFilePath(".git", false) + if err == nil && dir.IsDir { + // Make it consistent with the modified parent. + parent := pt.join(pt.replaceMappedLocations(dir.ParentFolder)) + folderFormatMap[parent] = gitDirFormat + } + } + + return folderFormatMap +} diff --git a/src/segments/path_test.go b/src/segments/path_test.go new file mode 100644 index 000000000000..864a7069a7b0 --- /dev/null +++ b/src/segments/path_test.go @@ -0,0 +1,853 @@ +package segments + +import ( + "strings" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + + "github.com/stretchr/testify/assert" + testify_ "github.com/stretchr/testify/mock" +) + +const ( + homeDir = "/home/someone" + homeDirWindows = "C:\\Users\\someone" +) + +func renderTemplateNoTrimSpace(env *mock.Environment, segmentTemplate string, context any) string { + env.On("Shell").Return("foo") + + if template.Cache == nil { + template.Cache = &cache.Template{} + } + template.Init(env, nil, nil) + + text, err := template.Render(segmentTemplate, context) + if err != nil { + return err.Error() + } + + return text +} + +func renderTemplate(env *mock.Environment, segmentTemplate string, context any) string { + return strings.TrimSpace(renderTemplateNoTrimSpace(env, segmentTemplate, context)) +} + +type testParentCase struct { + Case string + Expected string + HomePath string + Pwd string + GOOS string + PathSeparator string + FolderSeparatorIcon string +} + +func TestParent(t *testing.T) { + for _, tc := range testParentCases { + env := new(mock.Environment) + env.On("Home").Return(tc.HomePath) + env.On("Pwd").Return(tc.Pwd) + env.On("Flags").Return(&runtime.Flags{}) + env.On("Shell").Return(shell.GENERIC) + env.On("PathSeparator").Return(tc.PathSeparator) + env.On("GOOS").Return(tc.GOOS) + + props := options.Map{ + FolderSeparatorIcon: tc.FolderSeparatorIcon, + } + + path := &Path{} + path.Init(props, env) + + path.setPaths() + + got := path.Parent() + assert.EqualValues(t, tc.Expected, got, tc.Case) + } +} + +type testAgnosterPathStyleCase struct { + CygpathError error + GOOS string + Shell string + Pswd string + Pwd string + PathSeparator string + HomeIcon string + HomePath string + Style string + FolderSeparatorIcon string + Cygpath string + Expected string + MaxDepth int + MaxWidth int + HideRootLocation bool + Cygwin bool + DisplayRoot bool +} + +func TestAgnosterPathStyles(t *testing.T) { + for _, tc := range testAgnosterPathStyleCases { + env := new(mock.Environment) + env.On("PathSeparator").Return(tc.PathSeparator) + env.On("Home").Return(tc.HomePath) + env.On("Pwd").Return(tc.Pwd) + env.On("GOOS").Return(tc.GOOS) + env.On("IsCygwin").Return(tc.Cygwin) + env.On("StackCount").Return(0) + env.On("IsWsl").Return(false) + args := &runtime.Flags{ + PSWD: tc.Pswd, + } + env.On("Flags").Return(args) + + if tc.Shell == "" { + tc.Shell = shell.PWSH + } + env.On("Shell").Return(tc.Shell) + + displayCygpath := tc.Cygwin + if displayCygpath { + env.On("RunCommand", "cygpath", []string{"-u", tc.Pwd}).Return(tc.Cygpath, tc.CygpathError) + env.On("RunCommand", "cygpath", testify_.Anything).Return("brrrr", nil) + } + + props := options.Map{ + FolderSeparatorIcon: tc.FolderSeparatorIcon, + options.Style: tc.Style, + MaxDepth: tc.MaxDepth, + MaxWidth: tc.MaxWidth, + HideRootLocation: tc.HideRootLocation, + DisplayCygpath: displayCygpath, + DisplayRoot: tc.DisplayRoot, + } + + path := &Path{} + path.Init(props, env) + + path.setPaths() + path.setStyle() + got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path) + assert.Equal(t, tc.Expected, got) + } +} + +type testFullAndFolderPathCase struct { + Style string + HomePath string + FolderSeparatorIcon string + Pwd string + Pswd string + Expected string + GOOS string + PathSeparator string + Template string + StackCount int + DisableMappedLocations bool +} + +func TestFullAndFolderPath(t *testing.T) { + for _, tc := range testFullAndFolderPathCases { + env := new(mock.Environment) + if tc.PathSeparator == "" { + tc.PathSeparator = "/" + } + env.On("PathSeparator").Return(tc.PathSeparator) + if tc.GOOS == runtime.WINDOWS { + env.On("Home").Return(homeDirWindows) + } else { + env.On("Home").Return(homeDir) + } + env.On("Pwd").Return(tc.Pwd) + env.On("GOOS").Return(tc.GOOS) + env.On("StackCount").Return(tc.StackCount) + env.On("IsWsl").Return(false) + args := &runtime.Flags{ + PSWD: tc.Pswd, + } + env.On("Flags").Return(args) + env.On("Shell").Return(shell.GENERIC) + if tc.Template == "" { + tc.Template = "{{ if gt .StackCount 0 }}{{ .StackCount }} {{ end }}{{ .Path }}" + } + props := options.Map{ + options.Style: tc.Style, + } + if tc.FolderSeparatorIcon != "" { + props[FolderSeparatorIcon] = tc.FolderSeparatorIcon + } + if tc.DisableMappedLocations { + props[MappedLocationsEnabled] = false + } + + path := &Path{ + StackCount: env.StackCount(), + } + path.Init(props, env) + + path.setPaths() + path.setStyle() + got := renderTemplateNoTrimSpace(env, tc.Template, path) + assert.Equal(t, tc.Expected, got) + } +} + +type testFullPathCustomMappedLocationsCase struct { + Pwd string + MappedLocations map[string]string + GOOS string + PathSeparator string + Expected string +} + +func TestFullPathCustomMappedLocations(t *testing.T) { + for _, tc := range testFullPathCustomMappedLocationsCases { + env := new(mock.Environment) + env.On("Home").Return(homeDir) + env.On("Pwd").Return(tc.Pwd) + + if tc.GOOS == "" { + tc.GOOS = runtime.DARWIN + } + + env.On("GOOS").Return(tc.GOOS) + + if tc.PathSeparator == "" { + tc.PathSeparator = "/" + } + + env.On("PathSeparator").Return(tc.PathSeparator) + args := &runtime.Flags{ + PSWD: tc.Pwd, + } + + env.On("Flags").Return(args) + env.On("Shell").Return(shell.GENERIC) + env.On("Getenv", "HOME").Return(homeDir) + + template.Cache = new(cache.Template) + template.Init(env, nil, nil) + + props := options.Map{ + options.Style: Full, + MappedLocationsEnabled: false, + MappedLocations: tc.MappedLocations, + } + + path := &Path{} + path.Init(props, env) + + path.setPaths() + path.setStyle() + + got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path) + assert.Equal(t, tc.Expected, got) + } +} + +type testAgnosterPathCase struct { + Case string + Expected string + Home string + PWD string + GOOS string + PathSeparator string + Cycle []string + ColorSeparator bool +} + +func TestAgnosterPath(t *testing.T) { + for _, tc := range testAgnosterPathCases { + env := new(mock.Environment) + env.On("Home").Return(tc.Home) + env.On("PathSeparator").Return(tc.PathSeparator) + env.On("Pwd").Return(tc.PWD) + env.On("GOOS").Return(tc.GOOS) + args := &runtime.Flags{ + PSWD: tc.PWD, + } + env.On("Flags").Return(args) + env.On("Shell").Return(shell.PWSH) + + props := options.Map{ + options.Style: Agnoster, + FolderSeparatorIcon: " > ", + FolderIcon: "f", + HomeIcon: "~", + Cycle: tc.Cycle, + CycleFolderSeparator: tc.ColorSeparator, + } + + path := &Path{} + path.Init(props, env) + + path.setPaths() + path.setStyle() + got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path) + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +type testAgnosterLeftPathCase struct { + Case string + Expected string + Home string + PWD string + GOOS string + PathSeparator string +} + +func TestAgnosterLeftPath(t *testing.T) { + for _, tc := range testAgnosterLeftPathCases { + env := new(mock.Environment) + env.On("Home").Return(tc.Home) + env.On("PathSeparator").Return(tc.PathSeparator) + env.On("Pwd").Return(tc.PWD) + env.On("GOOS").Return(tc.GOOS) + args := &runtime.Flags{ + PSWD: tc.PWD, + } + env.On("Flags").Return(args) + env.On("Shell").Return(shell.PWSH) + + props := options.Map{ + options.Style: AgnosterLeft, + FolderSeparatorIcon: " > ", + FolderIcon: "f", + HomeIcon: "~", + } + + path := &Path{} + path.Init(props, env) + + path.setPaths() + path.setStyle() + got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path) + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestGetFolderSeparator(t *testing.T) { + cases := []struct { + Case string + FolderSeparatorIcon string + FolderSeparatorTemplate string + Expected string + }{ + {Case: "default", Expected: "/"}, + {Case: "icon - no template", FolderSeparatorIcon: "\ue5fe", Expected: "\ue5fe"}, + {Case: "template", FolderSeparatorTemplate: "{{ if eq .Shell \"bash\" }}\\{{ end }}", Expected: "\\"}, + {Case: "template empty", FolderSeparatorTemplate: "{{ if eq .Shell \"pwsh\" }}\\{{ end }}", Expected: "/"}, + {Case: "invalid template", FolderSeparatorTemplate: "{{ if eq .Shell \"pwsh\" }}", Expected: "/"}, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Shell").Return(shell.GENERIC) + + template.Cache = &cache.Template{ + SimpleTemplate: cache.SimpleTemplate{ + Shell: "bash", + }, + } + template.Init(env, nil, nil) + + props := options.Map{} + + if len(tc.FolderSeparatorTemplate) > 0 { + props[FolderSeparatorTemplate] = tc.FolderSeparatorTemplate + } + + if len(tc.FolderSeparatorIcon) > 0 { + props[FolderSeparatorIcon] = tc.FolderSeparatorIcon + } + + path := &Path{ + pathSeparator: "/", + } + path.Init(props, env) + + got := path.getFolderSeparator() + assert.Equal(t, tc.Expected, got) + } +} + +type testNormalizePathCase struct { + Case string + Input string + HomeDir string + GOOS string + PathSeparator string + Expected string + Cygwin bool +} + +func TestNormalizePath(t *testing.T) { + for _, tc := range testNormalizePathCases { + env := new(mock.Environment) + env.On("Home").Return(tc.HomeDir) + env.On("GOOS").Return(tc.GOOS) + + if tc.PathSeparator == "" { + tc.PathSeparator = "/" + } + + env.On("PathSeparator").Return(tc.PathSeparator) + + pt := &Path{cygPath: tc.Cygwin} + pt.Init(options.Map{}, env) + + got := pt.normalize(tc.Input) + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +type testSplitPathCase struct { + Case string + GOOS string + Relative string + Root string + GitDir *runtime.FileInfo + GitDirFormat string + Expected Folders +} + +func TestSplitPath(t *testing.T) { + for _, tc := range testSplitPathCases { + env := new(mock.Environment) + env.On("PathSeparator").Return("/") + env.On("Home").Return("/a/b") + env.On("HasParentFilePath", ".git", false).Return(tc.GitDir, nil) + env.On("GOOS").Return(tc.GOOS) + + props := options.Map{ + GitDirFormat: tc.GitDirFormat, + } + + path := &Path{ + root: tc.Root, + relative: tc.Relative, + pathSeparator: "/", + windowsPath: tc.GOOS == runtime.WINDOWS, + } + path.Init(props, env) + + got := path.splitPath() + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestGetMaxWidth(t *testing.T) { + cases := []struct { + MaxWidth any + Case string + Expected int + }{ + { + Case: "Nil", + Expected: 0, + }, + { + Case: "Empty string", + MaxWidth: "", + Expected: 0, + }, + { + Case: "Invalid template", + MaxWidth: "{{ .Unknown }}", + Expected: 0, + }, + { + Case: "Environment variable", + MaxWidth: "{{ .Env.MAX_WIDTH }}", + Expected: 120, + }, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("Getenv", "MAX_WIDTH").Return("120") + env.On("Shell").Return(shell.BASH) + + template.Cache = new(cache.Template) + template.Init(env, nil, nil) + + props := options.Map{ + MaxWidth: tc.MaxWidth, + } + + path := &Path{} + path.Init(props, env) + + got := path.getMaxWidth() + assert.Equal(t, tc.Expected, got, tc.Case) + } +} + +func TestAgnosterMaxWidth(t *testing.T) { + cases := []struct { + name string + pwd string + folderIcon string + separator string + expected string + goos string + maxWidth int + displayRoot bool + }{ + { + name: "path shorter than maxWidth", + pwd: "/foob/user/docs", + maxWidth: 20, + displayRoot: false, + separator: "/", + folderIcon: `..`, + expected: "foob/user/docs", + goos: runtime.LINUX, + }, + { + name: "path shorter than maxWidth, Windows", + pwd: `C:\Users\john\Documents`, + maxWidth: 20, + displayRoot: true, + folderIcon: `..`, + separator: `\`, + expected: `..\..\john\Documents`, + goos: runtime.WINDOWS, + }, + { + name: "path shorter than maxWidth, wth root", + pwd: "/foob/user/docs", + maxWidth: 20, + displayRoot: true, + folderIcon: `..`, + separator: "/", + expected: "/foob/user/docs", + goos: runtime.LINUX, + }, + { + name: "path exactly maxWidth", + pwd: "/foob/user/docs", + maxWidth: 15, + displayRoot: true, + folderIcon: `..`, + separator: "/", + expected: "/foob/user/docs", + goos: runtime.LINUX, + }, + { + name: "path longer than maxWidth with folder icons", + pwd: "/foob/user/documents/projects", + maxWidth: 15, + displayRoot: false, + folderIcon: "..", + separator: "/", + expected: "../../projects", + goos: runtime.LINUX, + }, + { + name: "very long path requiring multiple folder replacements", + pwd: "/foob/user/documents/projects/myproject/src/main", + maxWidth: 21, + displayRoot: false, + folderIcon: "..", + separator: "/", + expected: "../../../../../main", + goos: runtime.LINUX, + }, + { + name: "path requiring final folder truncation", + pwd: "/foob/verylongfoldername", + maxWidth: 15, + displayRoot: false, + separator: "/", + expected: "verylongfolder…", + goos: runtime.LINUX, + }, + { + name: "Windows path with custom separator", + pwd: `C:\Users\john\Documents`, + maxWidth: 15, + displayRoot: false, + folderIcon: "…", + separator: `\`, + expected: `…\…\…\Documents`, + goos: runtime.WINDOWS, + }, + { + name: "single folder path", + pwd: "/foob", + maxWidth: 10, + displayRoot: false, + separator: "/", + expected: "foob", + goos: runtime.LINUX, + }, + { + name: "empty relative path", + pwd: "/", + maxWidth: 10, + displayRoot: true, + separator: "/", + expected: "/", + goos: runtime.LINUX, + }, + { + name: "custom folder icon", + pwd: "/foob/user/documents/projects", + maxWidth: 15, + displayRoot: false, + folderIcon: "⋯", + separator: "/", + expected: "⋯/⋯/⋯/projects", + goos: runtime.LINUX, + }, + { + name: "maxwidth is smaller than folder name", + pwd: "/foob/user/documents/projects", + maxWidth: 2, + displayRoot: false, + folderIcon: "⋯", + separator: "/", + expected: "p…", + goos: runtime.LINUX, + }, + { + name: "maxwidth is 0", + pwd: "/foob/user/documents/projects", + maxWidth: 0, + displayRoot: false, + folderIcon: "⋯", + separator: "/", + expected: "…", + goos: runtime.LINUX, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + env := &mock.Environment{} + env.On("Pwd").Return(tc.pwd) + env.On("Home").Return("/home") + env.On("GOOS").Return(tc.goos) + env.On("Shell").Return(shell.BASH) + + path := &Path{ + Base: Base{ + env: env, + options: options.Map{ + DisplayRoot: tc.displayRoot, + FolderIcon: tc.folderIcon, + FolderSeparatorIcon: tc.separator, + }, + }, + pathSeparator: tc.separator, + } + + // Set up the path state + path.setPaths() + + got := path.getAgnosterMaxWidth(tc.maxWidth) + assert.Equal(t, tc.expected, got, tc.name) + }) + } +} + +func TestFishPath(t *testing.T) { + cases := []struct { + name string + pwd string + separator string + goos string + expected string + dirLength int + fullLengthDirs int + }{ + { + name: "default settings", + pwd: "/home/user/documents/projects", + dirLength: 1, + fullLengthDirs: 1, + expected: "h/u/d/projects", + separator: "/", + }, + { + name: "dir length 2", + pwd: "/home/user/documents/projects", + dirLength: 2, + fullLengthDirs: 1, + expected: "ho/us/do/projects", + separator: "/", + }, + { + name: "full length dirs 2", + pwd: "/home/user/documents/projects/myproject", + dirLength: 1, + fullLengthDirs: 2, + expected: "h/u/d/projects/myproject", + separator: "/", + }, + { + name: "dir length 3, full length dirs 2", + pwd: "/home/user/documents/projects/myproject", + dirLength: 3, + fullLengthDirs: 2, + expected: "hom/use/doc/projects/myproject", + separator: "/", + }, + { + name: "full length dirs 2 - Windows", + pwd: `C:\Users\Jan\Documents\Projects\Myproject`, + dirLength: 1, + fullLengthDirs: 2, + expected: `C\U\J\D\Projects\Myproject`, + separator: `\`, + }, + { + name: "dir length 3, full length dirs 2 - Windows", + pwd: `C:\Users\Jan\Documents\Projects\Myproject`, + dirLength: 3, + fullLengthDirs: 2, + expected: `C:\Use\Jan\Doc\Projects\Myproject`, + separator: `\`, + }, + { + name: "single folder", + pwd: "/home", + dirLength: 1, + fullLengthDirs: 1, + expected: "home", + separator: "/", + }, + { + name: "two folders with full length dirs 1", + pwd: "/home/user", + dirLength: 1, + fullLengthDirs: 1, + expected: "h/user", + separator: "/", + }, + { + name: "root only", + pwd: "/", + dirLength: 1, + fullLengthDirs: 1, + expected: "/", + separator: "/", + }, + { + name: "dir length 0 should disable shortening", + pwd: "/home/user/documents", + dirLength: 0, + fullLengthDirs: 1, + expected: "home/user/documents", + separator: "/", + }, + { + name: "dir length negative should disable shortening", + pwd: "/home/user/documents", + dirLength: -1, + fullLengthDirs: 1, + expected: "home/user/documents", + separator: "/", + }, + { + name: "full length dirs 0 should fallback to 1", + pwd: "/home/user/documents", + dirLength: 1, + fullLengthDirs: 0, + expected: "h/u/documents", + separator: "/", + }, + { + name: "full length dirs negative should fallback to 1", + pwd: "/home/user/documents", + dirLength: 1, + fullLengthDirs: -1, + expected: "h/u/documents", + separator: "/", + }, + { + name: "full length dirs greater than total folders", + pwd: "/home/user", + dirLength: 1, + fullLengthDirs: 5, + expected: "home/user", + separator: "/", + }, + { + name: "dir length greater than folder name", + pwd: "/a/b/c", + dirLength: 10, + fullLengthDirs: 1, + expected: "a/b/c", + separator: "/", + }, + { + name: "multi-byte unicode home icon", + pwd: "/󰋜/Downloads/test", + dirLength: 1, + fullLengthDirs: 1, + expected: "󰋜/D/test", + separator: "/", + }, + { + name: "multi-byte unicode home icon with dir length 2", + pwd: "/󰋜/Documents/Projects", + dirLength: 2, + fullLengthDirs: 1, + expected: "󰋜/Do/Projects", + separator: "/", + }, + { + name: "path with emoji folders", + pwd: "/🏠/📁/💻", + dirLength: 1, + fullLengthDirs: 1, + expected: "🏠/📁/💻", + separator: "/", + }, + { + name: "mixed multi-byte and ascii", + pwd: "/󰋜test/normal/󰨳end", + dirLength: 2, + fullLengthDirs: 1, + expected: "󰋜t/no/󰨳end", + separator: "/", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + env := &mock.Environment{} + env.On("Pwd").Return(tc.pwd) + env.On("Home").Return("/foob") + env.On("GOOS").Return(tc.goos) + env.On("Shell").Return(shell.BASH) + + path := &Path{ + Base: Base{ + env: env, + options: options.Map{ + DirLength: tc.dirLength, + FullLengthDirs: tc.fullLengthDirs, + }, + }, + pathSeparator: tc.separator, + } + + path.setPaths() + result := path.getFishPath() + + assert.Equal(t, result, tc.expected, tc.name) + }) + } +} diff --git a/src/segments/path_unix_test.go b/src/segments/path_unix_test.go new file mode 100644 index 000000000000..06445f9856a0 --- /dev/null +++ b/src/segments/path_unix_test.go @@ -0,0 +1,837 @@ +//go:build !windows + +package segments + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/stretchr/testify/assert" +) + +const ( + abc = "/abc" + abcd = "/a/b/c/d" + cdefg = "/c/d/e/f/g" +) + +var testParentCases = []testParentCase{ + { + Case: "Inside Home folder", + Expected: "~/", + HomePath: homeDir, + Pwd: homeDir + "/test", + GOOS: runtime.DARWIN, + PathSeparator: "/", + }, + { + Case: "Home folder", + HomePath: homeDir, + Pwd: homeDir, + GOOS: runtime.DARWIN, + PathSeparator: "/", + }, + { + Case: "Home folder with a trailing separator", + HomePath: homeDir, + Pwd: homeDir + "/", + GOOS: runtime.DARWIN, + PathSeparator: "/", + }, + { + Case: "Root", + HomePath: homeDir, + Pwd: "/", + GOOS: runtime.DARWIN, + PathSeparator: "/", + }, + { + Case: "Root + 1", + Expected: "/", + HomePath: homeDir, + Pwd: "/usr", + GOOS: runtime.DARWIN, + PathSeparator: "/", + }, +} + +var testAgnosterPathStyleCases = []testAgnosterPathStyleCase{ + { + Style: Unique, + Expected: "~ > a > ab > abcd", + HomePath: homeDir, + Pwd: homeDir + "/ab/abc/abcd", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Unique, + Expected: "~ > a > .a > abcd", + HomePath: homeDir, + Pwd: homeDir + "/ab/.abc/abcd", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Unique, + Expected: "~ > a > ab > abcd", + HomePath: homeDir, + Pwd: homeDir + "/ab/ab/abcd", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Unique, + Expected: "a", + HomePath: homeDir, + Pwd: "/ab", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Unique, + Expected: "/a > c > ef", + HomePath: homeDir, + Pwd: "/ab/cd/ef", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + DisplayRoot: true, + }, + + { + Style: Powerlevel, + Expected: "t > w > o > a > v > l > p > wh > we > i > wa > th > the > d > f > u > it > c > to > a > co > stream", + HomePath: homeDir, + Pwd: "/there/was/once/a/very/long/path/which/wended/its/way/through/the/dark/forest/until/it/came/to/a/cold/stream", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxWidth: 20, + }, + { + Style: Powerlevel, + Expected: "t > w > o > a > v > l > p > which > wended > its > way > through > the", + HomePath: homeDir, + Pwd: "/there/was/once/a/very/long/path/which/wended/its/way/through/the", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxWidth: 70, + }, + { + Style: Powerlevel, + Expected: "var/cache/pacman", + HomePath: homeDir, + Pwd: "/var/cache/pacman", + PathSeparator: "/", + FolderSeparatorIcon: "/", + MaxWidth: 50, + }, + { + Style: Powerlevel, + Expected: "/var/cache/pacman", + HomePath: homeDir, + Pwd: "/var/cache/pacman", + PathSeparator: "/", + FolderSeparatorIcon: "/", + MaxWidth: 50, + DisplayRoot: true, + }, + + { + Style: Letter, + Expected: "~", + HomePath: homeDir, + Pwd: homeDir, + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "~ > a > w > man", + HomePath: homeDir, + Pwd: homeDir + "/ab/whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "u > b > a > w > man", + HomePath: homeDir, + Pwd: "/usr/burp/ab/whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "u > .b > a > w > man", + HomePath: homeDir, + Pwd: "/usr/.burp/ab/whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "u > .b > a > .w > man", + HomePath: homeDir, + Pwd: "/usr/.burp/ab/.whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "u > .b > a > ._w > man", + HomePath: homeDir, + Pwd: "/usr/.burp/ab/._whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "u > .ä > ū > .w > man", + HomePath: homeDir, + Pwd: "/usr/.äufbau/ūmgebung/.whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "u > .b > 1 > .w > man", + HomePath: homeDir, + Pwd: "/usr/.burp/12345/.whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "u > .b > 1 > .w > man", + HomePath: homeDir, + Pwd: "/usr/.burp/12345abc/.whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "u > .b > __p > .w > man", + HomePath: homeDir, + Pwd: "/usr/.burp/__pycache__/.whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "➼ > .w > man", + HomePath: homeDir, + Pwd: "/➼/.whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "➼ s > .w > man", + HomePath: homeDir, + Pwd: "/➼ something/.whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "w", + HomePath: homeDir, + Pwd: "/whatever", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + + { + Style: Mixed, + Expected: "~ > .. > man", + HomePath: homeDir, + Pwd: homeDir + "/whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Mixed, + Expected: "~ > ab > .. > man", + HomePath: homeDir, + Pwd: homeDir + "/ab/whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Mixed, + Expected: "usr > foo > bar > .. > man", + HomePath: homeDir, + Pwd: "/usr/foo/bar/foobar/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: Mixed, + Expected: "whatever > .. > foo > bar", + HomePath: homeDir, + Pwd: "/whatever/foobar/foo/bar", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterFull, + Expected: "usr > location > whatever", + HomePath: homeDir, + Pwd: "/usr/location/whatever", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterFull, + Expected: "PSDRIVE: | src", + HomePath: homeDir, + Pwd: "/foo", + Pswd: "PSDRIVE:/src", + PathSeparator: "/", + FolderSeparatorIcon: " | ", + }, + + { + Style: AgnosterShort, + Expected: ".. | src | init", + HomePath: homeDir, + Pwd: "/foo", + Pswd: "PSDRIVE:/src/init", + PathSeparator: "/", + FolderSeparatorIcon: " | ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "usr > foo > bar > man", + HomePath: homeDir, + Pwd: "/usr/foo/bar/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 3, + }, + { + Style: AgnosterShort, + Expected: "PSDRIVE: | src", + HomePath: homeDir, + Pwd: "/foo", + Pswd: "PSDRIVE:/src", + PathSeparator: "/", + FolderSeparatorIcon: " | ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "~ > projects", + HomePath: homeDir, + Pwd: homeDir + "/projects", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "~", + HomePath: homeDir, + Pwd: homeDir, + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 1, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "usr > .. > bar > man", + HomePath: homeDir, + Pwd: "/usr/foo/bar/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 2, + }, + { + Style: AgnosterShort, + Expected: "~ > .. > man", + HomePath: homeDir, + Pwd: homeDir + "/whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "usr > .. > man", + HomePath: homeDir, + Pwd: "/usr/location/whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "~ > .. > bar > man", + HomePath: homeDir, + Pwd: homeDir + "/foo/bar/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 2, + }, + { + Style: AgnosterShort, + Expected: "~ > foo > bar > man", + HomePath: homeDir, + Pwd: homeDir + "/foo/bar/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 3, + }, + { + Style: AgnosterShort, + Expected: "PSDRIVE: | .. | init", + HomePath: homeDir, + Pwd: "/foo", + Pswd: "PSDRIVE:/src/init", + PathSeparator: "/", + FolderSeparatorIcon: " | ", + }, + { + Style: AgnosterShort, + Expected: ".. > foo", + HomePath: homeDir, + Pwd: homeDir + "/foo", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 1, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: ".. > bar > man", + HomePath: homeDir, + Pwd: homeDir + "/bar/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: ".. > foo > bar > man", + HomePath: homeDir, + Pwd: "/usr/foo/bar/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 3, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "~ > foo", + HomePath: homeDir, + Pwd: homeDir + "/foo", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "~ > foo > bar", + HomePath: homeDir, + Pwd: homeDir + "/foo/bar", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 3, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "C: | ", + HomePath: homeDir, + Pwd: "/mnt/c", + Pswd: "C:", + PathSeparator: "/", + FolderSeparatorIcon: " | ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "~ | space foo", + HomePath: homeDir, + Pwd: homeDir + "/space foo", + PathSeparator: "/", + FolderSeparatorIcon: " | ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: ".. | space foo", + HomePath: homeDir, + Pwd: homeDir + "/space foo", + PathSeparator: "/", + FolderSeparatorIcon: " | ", + MaxDepth: 1, + HideRootLocation: true, + }, +} + +var testAgnosterPathCases = []testAgnosterPathCase{ + { + Case: "Unix outside home", + Expected: "mnt > f > f > location", + Home: homeDir, + PWD: "/mnt/go/test/location", + PathSeparator: "/", + }, + { + Case: "Unix inside home", + Expected: "~ > f > f > location", + Home: homeDir, + PWD: homeDir + "/docs/jan/location", + PathSeparator: "/", + }, + { + Case: "Unix outside home zero levels", + Expected: "mnt > location", + Home: homeDir, + PWD: "/mnt/location", + PathSeparator: "/", + }, + { + Case: "Unix outside home one level", + Expected: "mnt > f > location", + Home: homeDir, + PWD: "/mnt/folder/location", + PathSeparator: "/", + }, + { + Case: "Unix, colorize", + Expected: "mnt > f > location", + Home: homeDir, + PWD: "/mnt/folder/location", + PathSeparator: "/", + Cycle: []string{"blue", "yellow"}, + }, + { + Case: "Unix, colorize with folder separator", + Expected: "mnt > f > location", + Home: homeDir, + PWD: "/mnt/folder/location", + PathSeparator: "/", + Cycle: []string{"blue", "yellow"}, + ColorSeparator: true, + }, + { + Case: "Unix one level", + Expected: "mnt", + Home: homeDir, + PWD: "/mnt", + PathSeparator: "/", + }, +} + +var testAgnosterLeftPathCases = []testAgnosterLeftPathCase{ + { + Case: "Unix outside home", + Expected: "mnt > go > f > f", + Home: homeDir, + PWD: "/mnt/go/test/location", + PathSeparator: "/", + }, + { + Case: "Unix inside home", + Expected: "~ > docs > f > f", + Home: homeDir, + PWD: homeDir + "/docs/jan/location", + PathSeparator: "/", + }, + { + Case: "Unix outside home zero levels", + Expected: "mnt > location", + Home: homeDir, + PWD: "/mnt/location", + PathSeparator: "/", + }, + { + Case: "Unix outside home one level", + Expected: "mnt > folder > f", + Home: homeDir, + PWD: "/mnt/folder/location", + PathSeparator: "/", + }, +} + +var testFullAndFolderPathCases = []testFullAndFolderPathCase{ + {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"}, + {Style: Full, Pwd: "/", Expected: "/"}, + {Style: Full, Pwd: homeDir, Expected: "~"}, + {Style: Full, Pwd: homeDir + abc, Expected: "~/abc"}, + {Style: Full, Pwd: homeDir + abc, Expected: homeDir + abc, DisableMappedLocations: true}, + {Style: Full, Pwd: abcd, Expected: abcd}, + + {Style: Full, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "~"}, + {Style: Full, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "/home|someone", DisableMappedLocations: true}, + {Style: Full, FolderSeparatorIcon: "|", Pwd: homeDir + abc, Expected: "~|abc"}, + {Style: Full, FolderSeparatorIcon: "|", Pwd: abcd, Expected: "/a|b|c|d"}, + + {Style: FolderType, Pwd: "/", Expected: "/"}, + {Style: FolderType, Pwd: homeDir, Expected: "~"}, + {Style: FolderType, Pwd: homeDir, Expected: "someone", DisableMappedLocations: true}, + {Style: FolderType, Pwd: homeDir + abc, Expected: "abc"}, + {Style: FolderType, Pwd: abcd, Expected: "d"}, + + {Style: FolderType, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"}, + {Style: FolderType, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "~"}, + {Style: FolderType, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "someone", DisableMappedLocations: true}, + {Style: FolderType, FolderSeparatorIcon: "|", Pwd: homeDir + abc, Expected: "abc"}, + {Style: FolderType, FolderSeparatorIcon: "|", Pwd: abcd, Expected: "d"}, + + // StackCountEnabled=true and StackCount=2 + {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCount: 2, Expected: "2 /"}, + {Style: Full, Pwd: "/", StackCount: 2, Expected: "2 /"}, + {Style: Full, Pwd: homeDir, StackCount: 2, Expected: "2 ~"}, + {Style: Full, Pwd: homeDir + abc, StackCount: 2, Expected: "2 ~/abc"}, + {Style: Full, Pwd: homeDir + abc, StackCount: 2, Expected: "2 " + homeDir + abc, DisableMappedLocations: true}, + {Style: Full, Pwd: abcd, StackCount: 2, Expected: "2 /a/b/c/d"}, + + // StackCountEnabled=false and StackCount=2 + {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Template: "{{ .Path }}", StackCount: 2, Expected: "/"}, + {Style: Full, Pwd: "/", Template: "{{ .Path }}", StackCount: 2, Expected: "/"}, + {Style: Full, Pwd: homeDir, Template: "{{ .Path }}", StackCount: 2, Expected: "~"}, + + {Style: Full, Pwd: homeDir + abc, Template: "{{ .Path }}", StackCount: 2, Expected: homeDir + abc, DisableMappedLocations: true}, + {Style: Full, Pwd: abcd, Template: "{{ .Path }}", StackCount: 2, Expected: abcd}, + + // StackCountEnabled=true and StackCount=0 + {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCount: 0, Expected: "/"}, + {Style: Full, Pwd: "/", StackCount: 0, Expected: "/"}, + {Style: Full, Pwd: homeDir, StackCount: 0, Expected: "~"}, + {Style: Full, Pwd: homeDir + abc, StackCount: 0, Expected: "~/abc"}, + {Style: Full, Pwd: homeDir + abc, StackCount: 0, Expected: homeDir + abc, DisableMappedLocations: true}, + {Style: Full, Pwd: abcd, StackCount: 0, Expected: abcd}, + + // StackCountEnabled=true and StackCount<0 + {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCount: -1, Expected: "/"}, + {Style: Full, Pwd: "/", StackCount: -1, Expected: "/"}, + {Style: Full, Pwd: homeDir, StackCount: -1, Expected: "~"}, + {Style: Full, Pwd: homeDir + abc, StackCount: -1, Expected: "~/abc"}, + {Style: Full, Pwd: homeDir + abc, StackCount: -1, Expected: homeDir + abc, DisableMappedLocations: true}, + {Style: Full, Pwd: abcd, StackCount: -1, Expected: abcd}, + + // StackCountEnabled=true and StackCount not set + {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"}, + {Style: Full, Pwd: "/", Expected: "/"}, + {Style: Full, Pwd: homeDir, Expected: "~"}, + {Style: Full, Pwd: homeDir + abc, Expected: "~/abc"}, + {Style: Full, Pwd: homeDir + abc, Expected: homeDir + abc, DisableMappedLocations: true}, + {Style: Full, Pwd: abcd, Expected: abcd}, +} + +var testFullPathCustomMappedLocationsCases = []testFullPathCustomMappedLocationsCase{ + {Pwd: homeDir + "/d", MappedLocations: map[string]string{"{{ .Env.HOME }}/d": "#"}, Expected: "#"}, + {Pwd: abcd, MappedLocations: map[string]string{abcd: "#"}, Expected: "#"}, + {Pwd: abcd, MappedLocations: map[string]string{"/a/b": "#"}, Expected: "#/c/d"}, + {Pwd: abcd, MappedLocations: map[string]string{"/a/b": "/e/f"}, Expected: "/e/f/c/d"}, + {Pwd: homeDir + abcd, MappedLocations: map[string]string{"~/a/b": "#"}, Expected: "#/c/d"}, + {Pwd: "/a" + homeDir + "/b/c/d", MappedLocations: map[string]string{"/a~": "#"}, Expected: "/a" + homeDir + "/b/c/d"}, + {Pwd: homeDir + abcd, MappedLocations: map[string]string{"/a/b": "#"}, Expected: homeDir + abcd}, +} + +var testSplitPathCases = []testSplitPathCase{ + {Case: "Root directory", Root: "/", Expected: Folders{}}, + { + Case: "Regular directory", + Root: "/", + Relative: "c/d", + GOOS: runtime.DARWIN, + Expected: Folders{ + {Name: "c", Path: "/c"}, + {Name: "d", Path: "/c/d"}, + }, + }, + { + Case: "Home directory - git folder", + Root: "~", + Relative: "c/d", + GOOS: runtime.DARWIN, + GitDir: &runtime.FileInfo{IsDir: true, ParentFolder: "/a/b/c"}, + GitDirFormat: "%s", + Expected: Folders{ + {Name: "c", Path: "~/c", Display: true}, + {Name: "d", Path: "~/c/d"}, + }, + }, +} + +var testNormalizePathCases = []testNormalizePathCase{ + { + Case: "Linux: home prefix, backslash included", + Input: "~/Bob\\Foo", + HomeDir: homeDir, + GOOS: runtime.LINUX, + Expected: homeDir + "/Bob\\Foo", + }, + { + Case: "macOS: home prefix, backslash included", + Input: "~/Bob\\Foo", + HomeDir: homeDir, + GOOS: runtime.DARWIN, + Expected: homeDir + "/bob\\foo", + }, + { + Case: "Linux: absolute", + Input: "/foo/~/bar", + HomeDir: homeDir, + GOOS: runtime.LINUX, + Expected: "/foo/~/bar", + }, + { + Case: "Linux: home prefix", + Input: "~/baz", + HomeDir: homeDir, + GOOS: runtime.LINUX, + Expected: homeDir + "/baz", + }, +} + +func TestFolderPathCustomMappedLocations(t *testing.T) { + pwd := abcd + env := new(mock.Environment) + env.On("PathSeparator").Return("/") + env.On("Home").Return(homeDir) + env.On("Pwd").Return(pwd) + env.On("GOOS").Return("") + args := &runtime.Flags{ + PSWD: pwd, + } + env.On("Flags").Return(args) + env.On("Shell").Return(shell.GENERIC) + + template.Cache = new(cache.Template) + template.Init(env, nil, nil) + + props := options.Map{ + options.Style: FolderType, + MappedLocations: map[string]string{ + abcd: "#", + }, + } + + path := &Path{} + path.Init(props, env) + + path.setPaths() + path.setStyle() + + got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path) + assert.Equal(t, "#", got) +} + +func TestReplaceMappedLocations(t *testing.T) { + cases := []struct { + Case string + Pwd string + Expected string + MappedLocationsEnabled bool + }{ + {Pwd: "/c/l/k/f", Expected: "f"}, + {Pwd: "/f/g/h", Expected: "/f/g/h"}, + {Pwd: "/f/g/h/e", Expected: "^/e"}, + {Pwd: abcd, Expected: "#"}, + {Pwd: "/a/b/c/d/e", Expected: "#/e"}, + {Pwd: "/a/b/c/D/e", Expected: "#/e"}, + {Pwd: "/a/b/k/j/e", Expected: "e"}, + {Pwd: "/a/b/k/l", Expected: "@/l"}, + {Pwd: "/a/b/k/l", MappedLocationsEnabled: true, Expected: "~/l"}, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("PathSeparator").Return("/") + env.On("Pwd").Return(tc.Pwd) + env.On("Shell").Return(shell.FISH) + env.On("GOOS").Return(runtime.DARWIN) + env.On("Home").Return("/a/b/k") + + template.Cache = new(cache.Template) + template.Init(env, nil, nil) + + props := options.Map{ + MappedLocationsEnabled: tc.MappedLocationsEnabled, + MappedLocations: map[string]string{ + abcd: "#", + "/f/g/h/*": "^", + "/c/l/k/*": "", + "~": "@", + "~/j/*": "", + }, + } + + path := &Path{} + path.Init(props, env) + + path.setPaths() + assert.Equal(t, tc.Expected, path.pwd) + } +} + +func TestGetPwd(t *testing.T) { + cases := []struct { + Pwd string + Pswd string + Expected string + MappedLocationsEnabled bool + }{ + {MappedLocationsEnabled: true, Pwd: homeDir, Expected: "~"}, + {MappedLocationsEnabled: true, Pwd: homeDir + "-test", Expected: homeDir + "-test"}, + {MappedLocationsEnabled: true}, + {MappedLocationsEnabled: true, Pwd: "/usr", Expected: "/usr"}, + {MappedLocationsEnabled: true, Pwd: homeDir + abc, Expected: "~/abc"}, + {MappedLocationsEnabled: true, Pwd: abcd, Expected: "#"}, + {MappedLocationsEnabled: true, Pwd: "/a/b/c/d/e/f/g", Expected: "#/e/f/g"}, + {MappedLocationsEnabled: true, Pwd: "/z/y/x/w", Expected: "/z/y/x/w"}, + + {MappedLocationsEnabled: false}, + {MappedLocationsEnabled: false, Pwd: homeDir + abc, Expected: homeDir + abc}, + {MappedLocationsEnabled: false, Pwd: "/a/b/c/d/e/f/g", Expected: "#/e/f/g"}, + {MappedLocationsEnabled: false, Pwd: homeDir + cdefg, Expected: homeDir + cdefg}, + {MappedLocationsEnabled: true, Pwd: homeDir + cdefg, Expected: "~/c/d/e/f/g"}, + + {MappedLocationsEnabled: true, Pwd: "/w/d/x/w", Pswd: "/z/y/x/w", Expected: "/z/y/x/w"}, + {MappedLocationsEnabled: false, Pwd: "/f/g/k/d/e/f/g", Pswd: "/a/b/c/d/e/f/g", Expected: "#/e/f/g"}, + } + + for _, tc := range cases { + env := new(mock.Environment) + env.On("PathSeparator").Return("/") + env.On("Home").Return(homeDir) + env.On("Pwd").Return(tc.Pwd) + env.On("GOOS").Return("") + args := &runtime.Flags{ + PSWD: tc.Pswd, + } + env.On("Flags").Return(args) + env.On("Shell").Return(shell.PWSH) + + template.Cache = new(cache.Template) + template.Init(env, nil, nil) + + props := options.Map{ + MappedLocationsEnabled: tc.MappedLocationsEnabled, + MappedLocations: map[string]string{ + abcd: "#", + }, + } + + path := &Path{} + path.Init(props, env) + + path.setPaths() + assert.Equal(t, tc.Expected, path.pwd) + } +} diff --git a/src/segments/path_windows_test.go b/src/segments/path_windows_test.go new file mode 100644 index 000000000000..d31f9d0b3e6b --- /dev/null +++ b/src/segments/path_windows_test.go @@ -0,0 +1,564 @@ +package segments + +import ( + "errors" + + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/shell" +) + +const ( + fooBarMan = "\\foo\\bar\\man" +) + +var testParentCases = []testParentCase{ + { + Case: "Windows Home folder", + HomePath: homeDirWindows, + Pwd: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows drive root", + HomePath: homeDirWindows, + Pwd: "C:", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows drive root with a trailing separator", + HomePath: homeDirWindows, + Pwd: "C:\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows drive root + 1", + Expected: "C:\\", + HomePath: homeDirWindows, + Pwd: "C:\\test", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "PSDrive root", + HomePath: homeDirWindows, + Pwd: "HKLM:", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, +} + +var testAgnosterPathStyleCases = []testAgnosterPathStyleCase{ + { + Style: Unique, + Expected: "C > a > ab > abcd", + HomePath: homeDirWindows, + Pwd: "C:\\ab\\ab\\abcd", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "C: > ", + HomePath: homeDirWindows, + Pwd: "C:\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "C > s > .w > man", + HomePath: homeDirWindows, + Pwd: "C:\\something\\.whatever\\man", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "~ > s > man", + HomePath: homeDirWindows, + Pwd: homeDirWindows + "\\something\\man", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: Mixed, + Expected: "C: > .. > foo > .. > man", + HomePath: homeDirWindows, + Pwd: "C:\\Users\\foo\\foobar\\man", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: Mixed, + Expected: "c > .. > foo > .. > man", + HomePath: homeDirWindows, + Pwd: "C:\\Users\\foo\\foobar\\man", + GOOS: runtime.WINDOWS, + Shell: shell.BASH, + Cygwin: true, + Cygpath: "/c/Users/foo/foobar/man", + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: Mixed, + Expected: "C: > .. > foo > .. > man", + HomePath: homeDirWindows, + Pwd: "C:\\Users\\foo\\foobar\\man", + GOOS: runtime.WINDOWS, + Shell: shell.BASH, + CygpathError: errors.New("oh no"), + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "\\\\localhost\\c$ > some", + HomePath: homeDirWindows, + Pwd: "\\\\localhost\\c$\\some", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "\\\\localhost\\c$", + HomePath: homeDirWindows, + Pwd: "\\\\localhost\\c$", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: ".. > bar > man", + HomePath: homeDirWindows, + Pwd: homeDirWindows + fooBarMan, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "C: > ", + HomePath: homeDirWindows, + Pwd: "C:", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "C: > .. > bar > man", + HomePath: homeDirWindows, + Pwd: "C:\\usr\\foo\\bar\\man", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + MaxDepth: 2, + }, + { + Style: AgnosterShort, + Expected: "C: > .. > foo > bar > man", + HomePath: homeDirWindows, + Pwd: "C:\\usr\\foo\\bar\\man", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + MaxDepth: 3, + }, + { + Style: AgnosterShort, + Expected: "~ > .. > bar > man", + HomePath: homeDirWindows, + Pwd: homeDirWindows + fooBarMan, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + MaxDepth: 2, + }, + { + Style: AgnosterShort, + Expected: "~ > foo > bar > man", + HomePath: homeDirWindows, + Pwd: homeDirWindows + fooBarMan, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + MaxDepth: 3, + }, + { + Style: AgnosterShort, + Expected: "~", + HomePath: homeDirWindows, + Pwd: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + MaxDepth: 1, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: ".. > foo", + HomePath: homeDirWindows, + Pwd: homeDirWindows + "\\foo", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + MaxDepth: 1, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "~ > foo", + HomePath: homeDirWindows, + Pwd: homeDirWindows + "\\foo", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + FolderSeparatorIcon: " > ", + MaxDepth: 2, + HideRootLocation: true, + }, +} + +var testAgnosterPathCases = []testAgnosterPathCase{ + { + Case: "Windows registry drive case sensitive", + Expected: "\uf013 > f > magnetic:TOAST", + Home: homeDirWindows, + PWD: "HKLM:\\SOFTWARE\\magnetic:TOAST\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows outside home", + Expected: "C: > f > f > location", + Home: homeDirWindows, + PWD: "C:\\Program Files\\Go\\location", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows outside home", + Expected: "~ > f > f > location", + Home: homeDirWindows, + PWD: homeDirWindows + "\\Documents\\Bill\\location", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows inside home zero levels", + Expected: "C: > location", + Home: homeDirWindows, + PWD: "C:\\location", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows inside home one level", + Expected: "C: > f > location", + Home: homeDirWindows, + PWD: "C:\\Program Files\\location", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower case drive letter", + Expected: "C: > Windows", + Home: homeDirWindows, + PWD: "C:\\Windows\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower case drive letter (other)", + Expected: "P: > Other", + Home: homeDirWindows, + PWD: "P:\\Other\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower word drive", + Expected: "some: > some", + Home: homeDirWindows, + PWD: "some:\\some\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower word drive (ending with c)", + Expected: "src: > source", + Home: homeDirWindows, + PWD: "src:\\source\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower word drive (arbitrary cases)", + Expected: "sRc: > source", + Home: homeDirWindows, + PWD: "sRc:\\source\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows registry drive", + Expected: "\uf013 > f > magnetic:test", + Home: homeDirWindows, + PWD: "HKLM:\\SOFTWARE\\magnetic:test\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, +} + +var testAgnosterLeftPathCases = []testAgnosterLeftPathCase{ + { + Case: "Windows inside home", + Expected: "~ > Documents > f > f", + Home: homeDirWindows, + PWD: homeDirWindows + "\\Documents\\Bill\\location", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows outside home", + Expected: "C: > Program Files > f > f", + Home: homeDirWindows, + PWD: "C:\\Program Files\\Go\\location", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows inside home zero levels", + Expected: "C: > location", + Home: homeDirWindows, + PWD: "C:\\location", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows inside home one level", + Expected: "C: > Program Files > f", + Home: homeDirWindows, + PWD: "C:\\Program Files\\location", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower case drive letter", + Expected: "C: > Windows", + Home: homeDirWindows, + PWD: "C:\\Windows\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower case drive letter (other)", + Expected: "P: > Other", + Home: homeDirWindows, + PWD: "P:\\Other\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower word drive", + Expected: "some: > some", + Home: homeDirWindows, + PWD: "some:\\some\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower word drive (ending with c)", + Expected: "src: > source", + Home: homeDirWindows, + PWD: "src:\\source\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows lower word drive (arbitrary cases)", + Expected: "sRc: > source", + Home: homeDirWindows, + PWD: "sRc:\\source\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows registry drive", + Expected: "\uf013 > SOFTWARE > f", + Home: homeDirWindows, + PWD: "HKLM:\\SOFTWARE\\magnetic:test\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, + { + Case: "Windows registry drive case sensitive", + Expected: "\uf013 > SOFTWARE > f", + Home: homeDirWindows, + PWD: "HKLM:\\SOFTWARE\\magnetic:TOAST\\", + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + }, +} + +var testFullAndFolderPathCases = []testFullAndFolderPathCase{ + {Style: FolderType, FolderSeparatorIcon: `\`, Pwd: "C:\\", Expected: "C:\\", PathSeparator: `\`, GOOS: runtime.WINDOWS}, + {Style: FolderType, FolderSeparatorIcon: `\`, Pwd: "\\\\localhost\\d$", Expected: "\\\\localhost\\d$", PathSeparator: `\`, GOOS: runtime.WINDOWS}, + {Style: FolderType, FolderSeparatorIcon: `\`, Pwd: homeDirWindows, Expected: "~", PathSeparator: `\`, GOOS: runtime.WINDOWS}, + {Style: Full, FolderSeparatorIcon: `\`, Pwd: homeDirWindows, Expected: "~", PathSeparator: `\`, GOOS: runtime.WINDOWS}, + {Style: Full, FolderSeparatorIcon: `\`, Pwd: homeDirWindows + "\\abc", Expected: "~\\abc", PathSeparator: `\`, GOOS: runtime.WINDOWS}, + {Style: Full, FolderSeparatorIcon: `\`, Pwd: "C:\\Users\\posh", Expected: "C:\\Users\\posh", PathSeparator: `\`, GOOS: runtime.WINDOWS}, +} + +var testFullPathCustomMappedLocationsCases = []testFullPathCustomMappedLocationsCase{ + { + Pwd: `\a\b\c\d`, + MappedLocations: map[string]string{`\a\b`: "#"}, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: `#\c\d`, + }, + { + Pwd: `\a\b\1234\d\e`, + MappedLocations: map[string]string{`re:(/a/b/[0-9]+/d).*`: "#"}, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: `#\e`, + }, + { + Pwd: `\a\b\1234\f\e`, + MappedLocations: map[string]string{`re:(/a/b/[0-9]+/d).*`: "#"}, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: `\a\b\1234\f\e`, + }, + { + Pwd: `C:\Users\taylo\Documents\github\project`, + MappedLocations: map[string]string{`re:(.*Users/taylo/Documents/GitHub).*`: "github"}, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: `github\project`, + }, +} + +var testSplitPathCases = []testSplitPathCase{ + { + Case: "Home directory - git folder on Windows", + Root: "C:", + Relative: "a/b/c/d", + GOOS: runtime.WINDOWS, + GitDir: &runtime.FileInfo{IsDir: true, ParentFolder: "C:/a/b/c"}, + GitDirFormat: "%s", + Expected: Folders{ + {Name: "a", Path: "C:/a"}, + {Name: "b", Path: "C:/a/b"}, + {Name: "c", Path: "C:/a/b/c", Display: true}, + {Name: "d", Path: "C:/a/b/c/d"}, + }, + }, +} + +var testNormalizePathCases = []testNormalizePathCase{ + { + Case: "Windows: absolute w/o drive letter, forward slash included", + Input: "/foo/~/bar", + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "\\foo\\~\\bar", + }, + { + Case: "Windows: absolute", + Input: homeDirWindows + "\\Foo", + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "c:\\users\\someone\\foo", + }, + { + Case: "Windows: home prefix", + Input: "~\\Bob\\Foo", + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "c:\\users\\someone\\bob\\foo", + }, + { + Case: "Windows: home prefix", + Input: "~/baz", + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "c:\\users\\someone\\baz", + }, + { + Case: "Windows: UNC root w/ prefix", + Input: `\\.\UNC\localhost\c$`, + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "\\\\localhost\\c$", + }, + { + Case: "Windows: UNC root w/ prefix, forward slash included", + Input: "//./UNC/localhost/c$", + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "\\\\localhost\\c$", + }, + { + Case: "Windows: UNC root", + Input: `\\localhost\c$\`, + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "\\\\localhost\\c$", + }, + { + Case: "Windows: UNC root, forward slash included", + Input: "//localhost/c$", + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "\\\\localhost\\c$", + }, + { + Case: "Windows: UNC", + Input: `\\localhost\c$\some`, + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "\\\\localhost\\c$\\some", + }, + { + Case: "Windows: UNC, forward slash included", + Input: "//localhost/c$/some", + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "\\\\localhost\\c$\\some", + }, + { + Case: "Windows: display Cygwin path", + Input: fooBarMan, + HomeDir: homeDirWindows, + GOOS: runtime.WINDOWS, + PathSeparator: `\`, + Expected: "/foo/bar/man", + Cygwin: true, + }, +} diff --git a/src/segments/perl.go b/src/segments/perl.go new file mode 100644 index 000000000000..f60f868bfe89 --- /dev/null +++ b/src/segments/perl.go @@ -0,0 +1,31 @@ +package segments + +type Perl struct { + Language +} + +func (p *Perl) Template() string { + return languageTemplate +} + +func (p *Perl) Enabled() bool { + const perlToolName = "perl" + + perlRegex := `This is perl.*v(?P(?P[0-9]+)(?:\.(?P[0-9]+))(?:\.(?P[0-9]+))?).* built for .+` + p.extensions = []string{ + ".perl-version", + "*.pl", + "*.pm", + "*.t", + } + p.tooling = map[string]*cmd{ + perlToolName: { + executable: perlToolName, + args: []string{versionFlagShortArg}, + regex: perlRegex, + }, + } + p.defaultTooling = []string{perlToolName} + + return p.Language.Enabled() +} diff --git a/src/segments/perl_test.go b/src/segments/perl_test.go new file mode 100644 index 000000000000..dc44832481dd --- /dev/null +++ b/src/segments/perl_test.go @@ -0,0 +1,43 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPerl(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + PerlHomeVersion string + PerlHomeEnabled bool + }{ + { + Case: "v5.12+", + ExpectedString: "5.32.1", + Version: "This is perl 5, version 32, subversion 1 (v5.32.1) built for MSWin32-x64-multi-thread", + }, + { + Case: "v5.6 - v5.10", + ExpectedString: "5.6.1", + Version: "This is perl, v5.6.1 built for MSWin32-x86-multi-thread", + }, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "perl", + versionParam: "-version", + versionOutput: tc.Version, + extension: ".perl-version", + } + env, props := getMockedLanguageEnv(params) + + p := &Perl{} + p.Init(props, env) + assert.True(t, p.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, p.Template(), p), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/php.go b/src/segments/php.go new file mode 100644 index 000000000000..dbaee1e8db31 --- /dev/null +++ b/src/segments/php.go @@ -0,0 +1,24 @@ +package segments + +type Php struct { + Language +} + +func (p *Php) Template() string { + return languageTemplate +} + +func (p *Php) Enabled() bool { + p.extensions = []string{"*.php", "composer.json", "composer.lock", ".php-version", "blade.php"} + p.tooling = map[string]*cmd{ + phpToolName: { + executable: phpToolName, + args: []string{versionFlagArg}, + regex: `(?:PHP (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, + }, + } + p.defaultTooling = []string{phpToolName} + p.versionURLTemplate = "https://www.php.net/ChangeLog-{{ .Major }}.php#PHP_{{ .Major }}_{{ .Minor }}" + + return p.Language.Enabled() +} diff --git a/src/segments/php_test.go b/src/segments/php_test.go new file mode 100644 index 000000000000..60a4897a957b --- /dev/null +++ b/src/segments/php_test.go @@ -0,0 +1,32 @@ +package segments + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPhp(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + Version string + }{ + {Case: "PHP 6.1.0", ExpectedString: "6.1.0", Version: "PHP 6.1.0(cli) (built: Jul 2 2021 03:59:48) ( NTS )"}, + {Case: "php 7.4.21", ExpectedString: "7.4.21", Version: "PHP 7.4.21 (cli) (built: Jul 2 2021 03:59:48) ( NTS )"}, + } + for _, tc := range cases { + params := &mockedLanguageParams{ + cmd: "php", + versionParam: "--version", + versionOutput: tc.Version, + extension: "*.php", + } + env, props := getMockedLanguageEnv(params) + j := &Php{} + j.Init(props, env) + assert.True(t, j.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, j.Template(), j), fmt.Sprintf("Failed in case: %s", tc.Case)) + } +} diff --git a/src/segments/plastic.go b/src/segments/plastic.go new file mode 100644 index 000000000000..d16e6c9a5a4c --- /dev/null +++ b/src/segments/plastic.go @@ -0,0 +1,185 @@ +package segments + +import ( + "fmt" + "strconv" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/regex" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/segments/options" +) + +type PlasticStatus struct { + ScmStatus +} + +func (s *PlasticStatus) add(code string) { + switch code { + case "LD": + s.Deleted++ + case "AD", "PR": + s.Added++ + case "LM": + s.Moved++ + case "CH", "CO": + s.Modified++ + } +} + +type Plastic struct { + Status *PlasticStatus + Selector string + plasticWorkspaceFolder string + Scm + Behind bool + MergePending bool +} + +func (p *Plastic) Init(props options.Provider, env runtime.Environment) { + p.options = props + p.env = env +} + +func (p *Plastic) Template() string { + return " {{ .Selector }} " +} + +func (p *Plastic) Enabled() bool { + if !p.env.HasCommand("cm") { + return false + } + + wkdir, err := p.env.HasParentFilePath(".plastic", false) + if err != nil { + return false + } + + if !wkdir.IsDir { + return false + } + + p.plasticWorkspaceFolder = wkdir.ParentFolder + displayStatus := p.options.Bool(FetchStatus, false) + p.setSelector() + if displayStatus { + p.setPlasticStatus() + } + return true +} + +func (p *Plastic) CacheKey() (string, bool) { + dir, err := p.env.HasParentFilePath(".plastic", true) + if err != nil { + return "", false + } + + return dir.Path, true +} + +func (p *Plastic) setPlasticStatus() { + output := p.getCmCommandOutput("status", "--all", "--machinereadable") + splittedOutput := strings.Split(output, "\n") + // compare to head + currentChangeset := p.parseStatusChangeset(splittedOutput[0]) + headChangeset := p.getHeadChangeset() + p.Behind = headChangeset > currentChangeset + + statusFormats := p.options.KeyValueMap(StatusFormats, map[string]string{}) + p.Status = &PlasticStatus{ScmStatus: ScmStatus{Formats: statusFormats}} + + // parse file state + p.MergePending = false + p.parseFilesStatus(splittedOutput) +} + +func (p *Plastic) parseFilesStatus(output []string) { + if len(output) <= 1 { + return + } + for _, line := range output[1:] { + if len(line) < 3 { + continue + } + + if strings.Contains(line, "NO_MERGES") { + p.Status.Unmerged++ + continue + } + + p.MergePending = p.MergePending || regex.MatchString(`(?i)\smerge\s+from\s+[0-9]+\s*$`, line) + + code := line[:2] + p.Status.add(code) + } +} + +func (p *Plastic) parseStringPattern(output, pattern, name string) string { + match := regex.FindNamedRegexMatch(pattern, output) + if sValue, ok := match[name]; ok { + return sValue + } + return "" +} + +func (p *Plastic) parseIntPattern(output, pattern, name string, defValue int) int { + sValue := p.parseStringPattern(output, pattern, name) + if len(sValue) > 0 { + iValue, _ := strconv.Atoi(sValue) + return iValue + } + return defValue +} + +func (p *Plastic) parseStatusChangeset(status string) int { + return p.parseIntPattern(status, `STATUS\s+(?P[0-9]+?)\s`, "cs", 0) +} + +func (p *Plastic) getHeadChangeset() int { + output := p.getCmCommandOutput("status", "--head", "--machinereadable") + return p.parseIntPattern(output, `\bcs:(?P[0-9]+?)\s`, "cs", 0) +} + +func (p *Plastic) setSelector() { + var ref string + selector := p.fileContent(p.plasticWorkspaceFolder+"/.plastic/", "plastic.selector") + + // changeset + ref = p.parseChangesetSelector(selector) + if len(ref) > 0 { + p.Selector = fmt.Sprintf("%s%s", p.options.String(CommitIcon, "\uF417"), ref) + return + } + + // fallback to label + ref = p.parseLabelSelector(selector) + if len(ref) > 0 { + p.Selector = fmt.Sprintf("%s%s", p.options.String(TagIcon, "\uF412"), ref) + return + } + + // fallback to branch/smartbranch + ref = p.parseBranchSelector(selector) + if len(ref) > 0 { + ref = p.formatBranch(ref) + } + + p.Selector = fmt.Sprintf("%s%s", p.options.String(BranchIcon, "\uE0A0"), ref) +} + +func (p *Plastic) parseChangesetSelector(selector string) string { + return p.parseStringPattern(selector, `\bchangeset "(?P[0-9]+?)"`, "cs") +} + +func (p *Plastic) parseLabelSelector(selector string) string { + return p.parseStringPattern(selector, `label "(?P