├── .gitignore ├── .tx └── config ├── CHANGELOG ├── INSTALL ├── LICENSE ├── Makefile.in ├── README.md ├── TODO ├── cmd ├── kcp │ ├── commands.go │ ├── consts.go │ ├── flags.go │ └── kcp.go └── pckcp │ ├── checkers.go │ ├── commands.go │ ├── consts.go │ ├── flags.go │ └── pckcp.go ├── color └── color.go ├── common ├── consts.go ├── consts_all.go ├── funcs.go ├── init.go └── kcp.conf ├── conf └── conf.go ├── configure ├── database ├── consts.go ├── database.go ├── doc.go ├── package.go ├── repository.go └── util.go ├── excluded.json ├── flag ├── consts.go ├── doc.go ├── flag.go ├── parser.go └── properties.go ├── generate_pot.elv ├── go.mod ├── go.sum ├── pkgbuild ├── atom │ ├── atom.go │ ├── doc.go │ └── info.go ├── format │ ├── doc.go │ ├── format.go │ └── order.go ├── pkgbuild.go ├── scanner │ ├── consts.go │ ├── doc.go │ ├── scanner.go │ └── token.go └── standard │ ├── doc.go │ ├── function.go │ └── variable.go ├── position └── position.go ├── resources ├── completion │ ├── kcp.bash │ ├── kcp.fish │ └── kcp.zsh ├── conf │ ├── PKGBUILD.commented.kaos.proto │ ├── exceptions │ └── kcp.conf ├── i18n │ ├── bg.po │ ├── ca.po │ ├── da.po │ ├── de.po │ ├── es.po │ ├── es_AR.po │ ├── fr.po │ ├── hi_IN.po │ ├── it.po │ ├── kcp.pot │ ├── nl_NL.po │ └── si.po └── man │ ├── kcp.1 │ └── pckcp.1 └── runes ├── consts.go ├── delimiter.go ├── doc.go └── runes.go /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | build 3 | pkg 4 | tmp 5 | cache 6 | vendor 7 | common/consts_dev.go 8 | cmd/pckcp/pckcp 9 | cmd/kcp/kcp 10 | resources/man/*.md 11 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [o:kaos:p:kaos:r:kcppot] 5 | file_filter = resources/i18n/.po 6 | source_file = resources/i18n/kcp.pot 7 | source_lang = en 8 | type = PO 9 | 10 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | kcp 1.2.4 (released 2024-04-12): 2 | - Adjust exceptions files + use it at kcp updates 3 | kcp 1.2.3 (released 2024-04-08): 4 | - Always update event if --force-update is not typed 5 | kcp 1.2.2 (released 2023-03-03): 6 | - Fix thread issue (2) 7 | 8 | kcp 1.2.1 (released 2023-03-03): 9 | - Fix thread issue 10 | 11 | kcp 1.2.0 (released 2223-03-01): 12 | - Optimization of the db update’s process 13 | 14 | kcp 1.1.0 (released 2022-09-26): 15 | - Add broken informations (needed for use of kcp to refresh the future KCP-center database) 16 | - Add Bulgarian translation 17 | 18 | kcp 1.0.9 (released 2022-07-07): 19 | - Add Sinhala translation 20 | 21 | kcp 1.0.8 (released 2022-05-12): 22 | - Update gotext dependencies 23 | - Add italian translation 24 | 25 | kcp 1.0.7 (releasd 2021-09-13): 26 | - Correct error parsing 27 | - Better format for errors 28 | 29 | kcp 1.0.6 (2021-09-08): 30 | - Add danish translation 31 | - Correct version number with epoch 32 | 33 | kcp 1.0.5 (released 2021-04-07): 34 | - Typo 35 | - Update Transifex 36 | 37 | kcp 1.0.4 (released 2021-03-24): 38 | - Correct missing translations 39 | 40 | kcp 1.0.3 (released 2021-03-08): 41 | - Trying to guess the locale if malformed, fallback to en 42 | - Correct panic error on PKGBUILD parsing 43 | 44 | kcp 1.0.2 (released 2021-03-04): 45 | - trying to fix the localisation 46 | 47 | kcp 1.0.1 (released 2021-02-27): 48 | ---------------- 49 | - fix the clone url in kcp database 50 | - minor changes in the Makefile.in 51 | 52 | kcp 1.0.0 (released 2021-02-27): 53 | -------------------------------- 54 | 55 | * Common: 56 | - Complete refactoring of the code 57 | - Update requirement Go ≥ 1.16 58 | - Use standard go.mod 59 | - Add an option to configure kcp/pckcp (system to /etc/kcp/kcp.conf 60 | which can be overloaded by user config in $HOME/.config/kcp/kcp.conf) 61 | - Add the hability to change the language of the CLI (trough an option) 62 | 63 | * kcp: 64 | - Move the database to $HOME/.config/kcp 65 | - Notice: the old database is incompatible with the new database 66 | - Add the hability to change the temporary dir (default to /tmp) and the locker file (through options) 67 | - Add the hability to use a personalized organization (default to KaOS-Community-Packages) 68 | - Add the hability to customize repos to ignore 69 | - Add the hability to change the clone method (ssh or https, default to https) 70 | - Add the hability to use a custom OAuth provider 71 | - Rewrite the manpage 72 | 73 | * pckp: 74 | - Rewrite the logic of the PKGBUILD parser 75 | - Add install vs. hook checker 76 | - Add the hability to change the exceptions per user 77 | - Add the hability to change the suffix of the new cleaned PKGBUILD 78 | - Add the hability to use a custom PKGBUILD template 79 | - Add a manpage 80 | 81 | kcp 0.91.0: 82 | ----------- 83 | ... 84 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Installing kcp is a pretty straighforward task. 2 | 3 | 1. DEPENDS 4 | Ensure the needed depends are installed on your system: 5 | - Go ≥ 1.16 (for building the apps) 6 | - gettext (for translation files generation) 7 | - git (to make the compiled app work) 8 | 9 | 2. PREPARE 10 | Launch configure script 11 | To get help about options, type: ./configure --help 12 | 13 | 2. COMPILATION 14 | Just launch the following command: 15 | make 16 | 17 | 3. INSTALL 18 | Launch the following command: 19 | make DESTDIR= install 20 | As result: 21 | - the binary file will be installed on /usr/bin 22 | - the man files will be installed on /usr/share/man/man1 23 | - the completion files will be installed 24 | - the translation files will be installed on the path provided by the --locale-dir flag (/usr/share/locale by default) 25 | 26 | 4. ENJOY! 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | # Configuration options 2 | VERSION := %version% 3 | DATE := $(shell date) 4 | CONFIGDIR := %configdir% 5 | CONFIGFILE := %configfile% 6 | LOCALEDIR := %localedir% 7 | DOMAIN := %domain% 8 | ORGANIZATION := %organization% 9 | USER := %user% 10 | PASSWORD := %password% 11 | 12 | DESTDIR ?= 13 | WD = build 14 | 15 | # Flags associated to the configuration 16 | cflag = $(if $(1),-X "github.com/bvaudour/kcp/common.$(2)=$(1)",) 17 | 18 | LDFLAGS := $(call cflag,$(VERSION),Version) 19 | LDFLAGS += $(call cflag,$(DATE),BuildTime) 20 | LDFLAGS += $(call cflag,$(CONFIGDIR),ConfigBaseDir) 21 | LDFLAGS += $(call cflag,$(CONFIGFILE),ConfigFile) 22 | LDFLAGS += $(call cflag,$(LOCALEDIR),LocaleBaseDir) 23 | LDFLAGS += $(call cflag,$(DOMAIN),LocaleDomain) 24 | LDFLAGS += $(call cflag,$(ORGANIZATION),Organization) 25 | LDFLAGS += $(call cflag,$(USER),User) 26 | LDFLAGS += $(call cflag,$(PASSWORD),Password) 27 | 28 | 29 | # Go build variables 30 | GOFLAGS := -v -trimpath -mod=readonly -modcacherw 31 | GOCMD := go build $(GOFLAGS) -ldflags='-s -w $(LDFLAGS)' 32 | BINARIES := kcp \ 33 | pckcp 34 | 35 | # Locale build variables 36 | LOCALESRCDIR := resources/i18n 37 | POFILES := $(wildcard $(LOCALESRCDIR)/*.po) 38 | LANGS := $(notdir $(basename $(POFILES))) 39 | MOFILES := $(addprefix $(WD)/,$(addsuffix .mo,$(LANGS))) 40 | 41 | # Working directory 42 | 43 | .PHONY: default build install clean gobuild uninstall 44 | 45 | default: build 46 | 47 | preparebuild: 48 | mkdir -p $(WD) 49 | 50 | ${WD}/%.mo: ${LOCALESRCDIR}/%.po 51 | msgfmt $< -o $@ 52 | 53 | gobuild: 54 | for b in $(BINARIES); do \ 55 | $(GOCMD) -o $(WD)/$${b} ./cmd/$${b}; \ 56 | done 57 | 58 | clean: 59 | rm -rf $(WD) 60 | 61 | build: preparebuild gobuild $(MOFILES) 62 | 63 | install: build 64 | for b in $(BINARIES); do \ 65 | install -Dm755 $(WD)/$${b} $(DESTDIR)/usr/bin/$${b}; \ 66 | install -Dm644 resources/man/$${b}.1 $(DESTDIR)/usr/share/man1/$${b}.1; \ 67 | done 68 | for l in $(LANGS); do \ 69 | install -Dm644 $(WD)/$${l}.mo $(DESTDIR)$(LOCALEDIR)/$${l}/LC_MESSAGES/$(DOMAIN).mo; \ 70 | done 71 | install -Dm644 resources/completion/kcp.bash $(DESTDIR)/etc/bash-completion.d/kcp 72 | install -Dm644 resources/completion/kcp.zsh $(DESTDIR)/usr/share/zsh/site-functions/_kcp 73 | install -Dm644 resources/completion/kcp.fish $(DESTDIR)/usr/share/fish/vendor_completions.d/kcp.fish 74 | install -Dm644 resources/conf/kcp.conf $(DESTDIR)$(CONFIGDIR)/$(CONFIGFILE) 75 | install -Dm644 resources/conf/exceptions $(DESTDIR)$(CONFIGDIR)/exceptions 76 | install -Dm644 resources/conf/PKGBUILD.commented.kaos.proto $(DESTDIR)$(CONFIGDIR)/PKGBUILD.commented.kaos.proto 77 | 78 | uninstall: 79 | for b in $(BINARIES); do \ 80 | rm -f $(DESTDIR)/usr/bin/$${b}; \ 81 | rm -f $(DESTDIR)/usr/share/man1/$${b}.1; \ 82 | done 83 | for l in $(LANGS); do \ 84 | rm -f $(DESTDIR)$(LOCALEDIR)/$${l}/LC_MESSAGES/$(DOMAIN).mo; \ 85 | done 86 | rm -f $(DESTDIR)/etc/bash-completion.d/kcp 87 | rm -f $(DESTDIR)/usr/share/zsh/site-functions/_kcp 88 | rm -f $(DESTDIR)/usr/share/fish/vendor_completions.d/kcp.fish 89 | rm -rf $(DESTDIR)$(CONFIGDIR) 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kcp 2 | === 3 | 4 | A command-line tool for KaOS Community Packages 5 | 6 | For more informations, see: https://github.com/KaOS-Community-Packages 7 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO : 2 | - Improve the json parser work at search (done) 3 | - Add the possibility of editing PKGBUILD (done) 4 | - Add version in the -s listing (done) 5 | - Create a Makefile (done) 6 | - Create bash completion (done) 7 | - Create fish completion (done) 8 | - Create zsh completion (partially done) 9 | - Implement app access token for github api (done) 10 | - Implement internationalization (done) 11 | -------------------------------------------------------------------------------- /cmd/kcp/commands.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "path/filepath" 8 | "strings" 9 | "syscall" 10 | 11 | "github.com/bvaudour/kcp/color" 12 | . "github.com/bvaudour/kcp/common" 13 | "github.com/bvaudour/kcp/database" 14 | "github.com/leonelquinteros/gotext" 15 | ) 16 | 17 | func getIgnore() []string { 18 | return strings.Fields(Config.Get("kcp.ignore")) 19 | } 20 | 21 | func getDbPath() string { 22 | return JoinIfRelative(UserBaseDir, Config.Get("kcp.dbFile")) 23 | } 24 | 25 | func useSsh() bool { 26 | return Config.Get("kcp.cloneMethod") == "ssh" 27 | } 28 | 29 | func getDb() (db database.Database, err error) { 30 | fpath, ignore := getDbPath(), getIgnore() 31 | return database.Load(fpath, ignore...) 32 | } 33 | 34 | func updateDb(db *database.Database, debug bool) (counters database.Counter, err error) { 35 | return db.Update(Organization, debug, User, Password) 36 | } 37 | 38 | func loadDb(debug, forceUpdate bool) database.Database { 39 | db, err := getDb() 40 | if debug { 41 | fmt.Fprintln(os.Stderr, "Trying to open db") 42 | } 43 | if err != nil || forceUpdate { 44 | if debug { 45 | fmt.Fprintln(os.Stderr, "Trying to update db") 46 | } 47 | updateDb(&db, debug) 48 | } 49 | return db 50 | } 51 | 52 | func saveDb(db database.Database) error { 53 | return database.Save(getDbPath(), db) 54 | } 55 | 56 | func filter(debug, forceUpdate, onlyName bool, f []database.FilterFunc, s []database.SorterFunc) { 57 | db := loadDb(debug, true) 58 | saveDb(db) 59 | l := db.Filter(f...).Sort(s...) 60 | if len(l) == 0 { 61 | PrintWarning(Tr(errNoPackage)) 62 | return 63 | } 64 | if onlyName { 65 | names := l.Names() 66 | fmt.Println(strings.Join(names, "\n")) 67 | return 68 | } 69 | fmt.Println(l) 70 | } 71 | 72 | func getFilters(onlyStarred, onlyInstalled, onlyOutDated bool) []database.FilterFunc { 73 | var filters []database.FilterFunc 74 | if onlyStarred { 75 | filters = append(filters, database.FilterStarred) 76 | } 77 | if onlyOutDated { 78 | filters = append(filters, database.FilterOutdated) 79 | } else if onlyInstalled { 80 | filters = append(filters, database.FilterInstalled) 81 | } 82 | return filters 83 | } 84 | 85 | func getFiltersSearch(search string) []database.FilterFunc { 86 | var filters []database.FilterFunc 87 | if len(search) > 0 { 88 | search = strings.ToLower(search) 89 | filters = append(filters, func(p database.Package) bool { 90 | name := strings.ToLower(p.Name) 91 | if strings.Contains(name, search) { 92 | return true 93 | } 94 | description := strings.ToLower(p.Description) 95 | return strings.Contains(description, search) 96 | }) 97 | } 98 | return filters 99 | } 100 | 101 | func getSorters(sortByStar bool) []database.SorterFunc { 102 | var sorters []database.SorterFunc 103 | if sortByStar { 104 | sorters = append(sorters, database.SortByStar) 105 | } 106 | sorters = append(sorters, database.SortByName) 107 | return sorters 108 | } 109 | 110 | func update(debug bool) { 111 | if debug { 112 | fmt.Fprintln(os.Stderr, "Open db") 113 | } 114 | db, _ := getDb() 115 | if debug { 116 | fmt.Fprintln(os.Stderr, "Trying to update db") 117 | } 118 | counters, err := updateDb(&db, debug) 119 | if err != nil { 120 | PrintError(err) 121 | os.Exit(1) 122 | } 123 | if err = saveDb(db); err != nil { 124 | PrintError(err) 125 | os.Exit(1) 126 | } 127 | fmt.Println() 128 | fmt.Println(color.Yellow.Colorize(counters)) 129 | } 130 | 131 | func list( 132 | debug, 133 | forceUpdate, 134 | onlyName, 135 | onlyStarred, 136 | onlyInstalled, 137 | onlyOutDated, 138 | sortByStar bool, 139 | ) { 140 | filters := getFilters(onlyStarred, onlyInstalled, onlyOutDated) 141 | sorters := getSorters(sortByStar) 142 | filter(debug, forceUpdate, onlyName, filters, sorters) 143 | } 144 | 145 | func search( 146 | debug, 147 | forceUpdate, 148 | onlyName, 149 | onlyStarred, 150 | onlyInstalled, 151 | onlyOutDated, 152 | sortByStar bool, 153 | substr string, 154 | ) { 155 | filters := getFilters(onlyStarred, onlyInstalled, onlyOutDated) 156 | filters = append(filters, getFiltersSearch(substr)...) 157 | sorters := getSorters(sortByStar) 158 | filter(debug, forceUpdate, onlyName, filters, sorters) 159 | } 160 | 161 | func info(debug bool, app string) { 162 | db := loadDb(debug, false) 163 | p, ok := db.Get(app) 164 | if !ok { 165 | PrintWarning(Tr(errNoPackage)) 166 | os.Exit(1) 167 | } 168 | fmt.Println(p.Detail()) 169 | } 170 | 171 | func get(debug bool, app string) { 172 | db := loadDb(debug, false) 173 | p, ok := db.Get(app) 174 | if !ok { 175 | PrintWarning(Tr(errNoPackageOrNeedUpdate)) 176 | os.Exit(1) 177 | } 178 | wd, _ := os.Getwd() 179 | fullDir, err := p.Clone(wd, useSsh()) 180 | if err != nil { 181 | PrintError(err) 182 | os.Exit(1) 183 | } 184 | fmt.Println(Tr(msgCloned, app, fullDir)) 185 | } 186 | 187 | func install(debug bool, app string, asdep bool) { 188 | db := loadDb(debug, false) 189 | p, ok := db.Get(app) 190 | if !ok { 191 | PrintWarning(Tr(errNoPackageOrNeedUpdate)) 192 | os.Exit(1) 193 | } 194 | wd := Config.Get("kcp.tmpDir") 195 | if err := os.MkdirAll(wd, 0755); err != nil { 196 | PrintError(err) 197 | os.Exit(1) 198 | } 199 | locker := JoinIfRelative(wd, Config.Get("kcp.lockerFile")) 200 | if _, err := os.Open(locker); err == nil { 201 | PrintError(Tr(errOnlyOneInstance)) 202 | os.Exit(1) 203 | } 204 | _, err := os.Create(locker) 205 | if err != nil { 206 | PrintError(Tr(errFailedCreateLocker)) 207 | os.Exit(1) 208 | } 209 | installDir, err := p.Clone(wd, useSsh()) 210 | remove := func() { 211 | os.Remove(locker) 212 | os.RemoveAll(installDir) 213 | } 214 | c := make(chan os.Signal, 1) 215 | signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGQUIT, syscall.SIGABRT, syscall.SIGHUP) 216 | go func() { 217 | <-c 218 | remove() 219 | PrintError(Tr(errInterrupt)) 220 | os.Exit(1) 221 | }() 222 | defer remove() 223 | if err != nil { 224 | remove() 225 | PrintError(err) 226 | os.Exit(1) 227 | } 228 | os.Chdir(installDir) 229 | if QuestionYN(Tr(msgEdit), true) { 230 | if err = EditFile("PKGBUILD"); err != nil { 231 | remove() 232 | PrintError(err) 233 | os.Exit(1) 234 | } 235 | } 236 | m, _ := filepath.Glob("*.install") 237 | for _, i := range m { 238 | if QuestionYN(Tr(msgEditInstall, i), false) { 239 | if err := EditFile(i); err != nil { 240 | remove() 241 | PrintError(err) 242 | os.Exit(1) 243 | } 244 | } 245 | } 246 | args := []string{"-si"} 247 | if asdep { 248 | args = append(args, "--asdeps") 249 | } 250 | if err := LaunchCommand("makepkg", args...); err != nil { 251 | remove() 252 | PrintError(err) 253 | os.Exit(1) 254 | } 255 | p.LocalVersion = p.GetLocaleVersion() 256 | saveDb(db) 257 | } 258 | 259 | func debugLocales() { 260 | b, d, l := gotext.GetLibrary(), gotext.GetDomain(), gotext.GetLanguage() 261 | f := filepath.Join(b, l, "LC_MESSAGES", d+".mo") 262 | fmt.Println("Debug locale configuration:") 263 | fmt.Println("- Base path:", b) 264 | fmt.Println("- Domain:", d) 265 | fmt.Println("- Language used:", l) 266 | fmt.Println("- Mo file:", f) 267 | fmt.Println("- Mo file exists:", FileExists(f)) 268 | } 269 | -------------------------------------------------------------------------------- /cmd/kcp/consts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //Flags informations 4 | const ( 5 | appLongDescription = `Provides a tool to make use of KaOS Community Packages. 6 | 7 | With this tool, you can search, get and install a package from KaOS Community Packages.` 8 | appDescription = "Tool in command-line for KaOS Community Packages" 9 | synopsis = "(-h|-v|-u|(-l|-s ) [-fxNSIO]|-i [-d]|-g |-V )" 10 | dHelp = "Print this help" 11 | dVersion = "Print version" 12 | dList = "Display all packages of KCP" 13 | dUpdate = "Refresh the local database" 14 | dSearch = "Search packages in KCP and display them" 15 | dGet = "Download needed files to build a package" 16 | dInstall = "Install a package from KCP" 17 | dFast = "On display action, don't print KCP version" 18 | dSort = "On display action, sort packages by stars descending" 19 | dAsDeps = "On install action, install as a dependence" 20 | dInstalled = "On list action, display only installed packages" 21 | dComplete = "On refreshing database action, force complete update" 22 | dForceUpdate = "On display action, force refreshing local database" 23 | dOnlyName = "On display action, display only the name of the package" 24 | dOnlystarred = "On display action, display only packages with at least one star" 25 | dOnlyInstalled = "On display action, display only installed packages" 26 | dOnlyOutdated = "On display action, display only outdated packages" 27 | dInformation = "Display informations about a package" 28 | dValueName = "" 29 | ) 30 | 31 | //Messages 32 | const ( 33 | errNoRoot = "Don't launch this program as root!" 34 | errNoPackage = "No package found" 35 | errNoPackageOrNeedUpdate = "No package found. Check if the database is updated." 36 | errOnlyOneInstance = "Another instance of kcp is running!" 37 | errFailedCreateLocker = "Failed to create locker file!" 38 | errInterrupt = "Interrupt by user…" 39 | 40 | msgCloned = "Package %s cloned in %s." 41 | msgEdit = "Do you want to edit PKGBUILD?" 42 | msgEditInstall = "Do you want to edit %s?" 43 | ) 44 | -------------------------------------------------------------------------------- /cmd/kcp/flags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | . "github.com/bvaudour/kcp/common" 7 | "github.com/bvaudour/kcp/flag" 8 | ) 9 | 10 | var ( 11 | flags *flag.Parser 12 | fHelp, fVersion, fList, fUpdate *bool 13 | fSearch, fGet, fInstall, fInfo *string 14 | fSorted, fOnlyName, fOnlyStar, fOnlyInstalled, fOnlyOutdated *bool 15 | fForceUpdate, fAsDepend, fDebug *bool 16 | ) 17 | 18 | func initFlags() { 19 | flags = flag.NewParser(Tr(appDescription), Version) 20 | flags.Set(flag.Synopsis, Tr(synopsis)) 21 | flags.Set(flag.LongDescription, Tr(appLongDescription)) 22 | 23 | fHelp, _ = flags.Bool("-h", "--help", Tr(dHelp)) 24 | fVersion, _ = flags.Bool("-v", "--version", Tr(dVersion)) 25 | fList, _ = flags.Bool("-l", "--list", Tr(dList)) 26 | fUpdate, _ = flags.Bool("-u", "--update-database", Tr(dUpdate)) 27 | fSearch, _ = flags.String("-s", "--search", Tr(dSearch), Tr(dValueName), "") 28 | fGet, _ = flags.String("-g", "--get", Tr(dGet), Tr(dValueName), "") 29 | fInstall, _ = flags.String("-i", "--install", Tr(dInstall), Tr(dValueName), "") 30 | fSorted, _ = flags.Bool("-x", "--sort", Tr(dSort)) 31 | fForceUpdate, _ = flags.Bool("-f", "--force-update", Tr(dForceUpdate)) 32 | fOnlyName, _ = flags.Bool("-N", "--only-name", Tr(dOnlyName)) 33 | fOnlyStar, _ = flags.Bool("-S", "--only-starred", Tr(dOnlystarred)) 34 | fOnlyInstalled, _ = flags.Bool("-I", "--only-installed", Tr(dOnlyInstalled)) 35 | fOnlyOutdated, _ = flags.Bool("-O", "--only-outdated", Tr(dOnlyOutdated)) 36 | fAsDepend, _ = flags.Bool("-d", "--asdeps", Tr(dAsDeps)) 37 | fInfo, _ = flags.String("-V", "--information", Tr(dInformation), Tr(dValueName), "") 38 | fDebug, _ = flags.Bool("", "--debug", "") 39 | 40 | flags.Group("-h", "-v", "-l", "-s", "-g", "-i", "-u", "--information") 41 | flags.Require("--sort", "-l", "-s") 42 | flags.Require("--force-update", "-l", "-s") 43 | flags.Require("--only-name", "-l", "-s") 44 | flags.Require("--only-starred", "-l", "-s") 45 | flags.Require("--only-installed", "-l", "-s") 46 | flags.Require("--only-outdated", "-l", "-s") 47 | flags.Require("--asdeps", "-i") 48 | flags.GetFlag("--debug").Set(flag.Hidden, true) 49 | } 50 | 51 | func parseFlags() { 52 | if err := flags.Parse(os.Args); err != nil { 53 | PrintError(err) 54 | flags.PrintHelp() 55 | os.Exit(1) 56 | } 57 | } 58 | 59 | func checkUser() { 60 | if e := os.Geteuid(); e == 0 { 61 | PrintError(Tr(errNoRoot)) 62 | os.Exit(1) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /cmd/kcp/kcp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | checkUser() 5 | initFlags() 6 | parseFlags() 7 | switch { 8 | case *fHelp: 9 | if *fDebug { 10 | debugLocales() 11 | } 12 | flags.PrintHelp() 13 | case *fVersion: 14 | flags.PrintVersion() 15 | case *fUpdate: 16 | update(*fDebug) 17 | case *fList: 18 | list(*fDebug, *fForceUpdate, *fOnlyName, *fOnlyStar, *fOnlyInstalled, *fOnlyOutdated, *fSorted) 19 | case *fSearch != "": 20 | search(*fDebug, *fForceUpdate, *fOnlyName, *fOnlyStar, *fOnlyInstalled, *fOnlyOutdated, *fSorted, *fSearch) 21 | case *fInfo != "": 22 | info(*fDebug, *fInfo) 23 | case *fGet != "": 24 | get(*fDebug, *fGet) 25 | case *fInstall != "": 26 | install(*fDebug, *fInstall, *fAsDepend) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cmd/pckcp/checkers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/bvaudour/kcp/color" 10 | . "github.com/bvaudour/kcp/common" 11 | "github.com/bvaudour/kcp/pkgbuild" 12 | "github.com/bvaudour/kcp/pkgbuild/atom" 13 | "github.com/bvaudour/kcp/pkgbuild/standard" 14 | ) 15 | 16 | const ( 17 | urlHook = "https://jlk.fjfi.cvut.cz/arch/manpages/man/alpm-hooks.5" 18 | ) 19 | 20 | var ( 21 | colorType = map[string]color.Color{ 22 | typeError: color.Red, 23 | typeWarning: color.Yellow, 24 | typeInfo: color.Green, 25 | } 26 | ) 27 | 28 | func message(t string, l1, l2 int, msg string) { 29 | var position string 30 | if l1 > 0 { 31 | if l1 == l2 { 32 | position = fmt.Sprintf("(L.%d)", l1) 33 | } else { 34 | position = fmt.Sprintf("(L.%d-%d)", l1, l2) 35 | } 36 | } 37 | fmt.Printf( 38 | "%s:\n %s\n", 39 | colorType[t].Format("%s %s", t, position), 40 | msg, 41 | ) 42 | } 43 | 44 | func loadExceptions() (exceptions map[string]bool) { 45 | exceptions = make(map[string]bool) 46 | fp := Config.Get("pckcp.exceptionsFile") 47 | if fp == "" { 48 | return 49 | } 50 | fp = JoinIfRelative(ConfigBaseDir, fp) 51 | f, err := os.Open(fp) 52 | if err != nil { 53 | return 54 | } 55 | defer f.Close() 56 | sc := bufio.NewScanner(f) 57 | for sc.Scan() { 58 | for _, e := range strings.Fields(sc.Text()) { 59 | exceptions[e] = true 60 | } 61 | } 62 | return 63 | } 64 | 65 | func formatPackage(v string) string { 66 | for _, sep := range []string{">", "<", "=", ":"} { 67 | i := strings.Index(v, sep) 68 | if i >= 0 { 69 | v = v[:i] 70 | } 71 | } 72 | return v 73 | } 74 | 75 | func isPackageInRepo(v string) bool { 76 | result, _ := GetOutputCommand("pacman", "-Si", v) 77 | if len(result) > 0 { 78 | return true 79 | } 80 | result, _ = GetOutputCommand("kcp", "-Ns", v) 81 | for _, e := range strings.Fields(string(result)) { 82 | if e == v { 83 | return true 84 | } 85 | } 86 | return false 87 | } 88 | 89 | func checkHeader(p *pkgbuild.PKGBUILD, edit bool) { 90 | l := p.Len() 91 | bh, eh := -1, -1 92 | for i := 0; i < l; i++ { 93 | info, isBlank, _ := p.GetIndex(i) 94 | if info != nil { 95 | break 96 | } 97 | if i == 0 && isBlank { 98 | continue 99 | } 100 | eh = i 101 | if bh < 0 { 102 | bh = i 103 | } 104 | } 105 | if bh < 0 { 106 | message(typeInfo, 0, 0, Tr(infoHeader)) 107 | return 108 | } 109 | 110 | message(typeWarning, bh+1, eh+1, Tr(warnHeader)) 111 | if edit && QuestionYN(Tr(questionHeader), true) { 112 | for i := bh; i <= eh; i++ { 113 | p.RemoveIndex(bh) 114 | } 115 | } 116 | } 117 | 118 | func checkDuplicates(p *pkgbuild.PKGBUILD, edit bool) { 119 | names := make(map[string]int) 120 | var infoNames [][]*atom.Info 121 | infos := p.GetInfos() 122 | for _, info := range infos { 123 | name := info.Name() 124 | if i, exists := names[name]; exists { 125 | infoNames[i] = append(infoNames[i], info) 126 | } else { 127 | names[name] = len(infoNames) 128 | infoNames = append(infoNames, []*atom.Info{info}) 129 | } 130 | } 131 | 132 | var duplicates [][]*atom.Info 133 | for _, l := range infoNames { 134 | if len(l) > 1 { 135 | duplicates = append(duplicates, l) 136 | } 137 | } 138 | 139 | if len(duplicates) == 0 { 140 | message(typeInfo, 0, 0, Tr(infoDuplicate)) 141 | return 142 | } 143 | 144 | message(typeWarning, 0, 0, Tr(warnDuplicate)) 145 | for _, d := range duplicates { 146 | name := d[0].Name() 147 | lines := make([]string, len(d)) 148 | for i, info := range d { 149 | b, e := info.GetPositions() 150 | t := "V" 151 | if info.IsFunc() { 152 | t = "F" 153 | } 154 | if b.Line == e.Line { 155 | lines[i] = fmt.Sprintf("L.%d (%s)", b.Line, t) 156 | } else { 157 | lines[i] = fmt.Sprintf("L.%d-%d (%s)", b.Line, e.Line, t) 158 | } 159 | } 160 | fmt.Printf(" - '%s': %s\n", name, strings.Join(lines, ", ")) 161 | } 162 | if edit && QuestionYN(Tr(questionDuplicate), true) { 163 | for _, d := range duplicates { 164 | for _, i := range d[1:] { 165 | p.RemoveInfo(i) 166 | } 167 | } 168 | } 169 | } 170 | 171 | func checkMissingVars(p *pkgbuild.PKGBUILD, edit bool) { 172 | var missings []string 173 | for _, v := range standard.GetVariables() { 174 | if !standard.IsRequiredVariable(v) { 175 | continue 176 | } 177 | if !p.ContainsVariable(v) { 178 | missings = append(missings, v) 179 | } 180 | } 181 | if len(missings) == 0 { 182 | message(typeInfo, 0, 0, Tr(infoMissingVar)) 183 | return 184 | } 185 | for _, v := range missings { 186 | message(typeError, 0, 0, Tr(errMissingVar, v)) 187 | if !(edit && QuestionYN(Tr(questionMissingVar, v), true)) { 188 | continue 189 | } 190 | r := strings.TrimSpace(Question(Tr(questionAddValue, v))) 191 | if len(r) == 0 { 192 | continue 193 | } 194 | isArray := standard.IsArrayVariable(v) 195 | var values []string 196 | if isArray { 197 | values = strings.Split(r, " ") 198 | } else { 199 | values = append(values, r) 200 | } 201 | p.AddVariable(v, isArray, values...) 202 | } 203 | } 204 | 205 | func checkMissingFuncs(p *pkgbuild.PKGBUILD, edit bool) { 206 | var missings []string 207 | for _, f := range standard.GetFunctions() { 208 | if !standard.IsRequiredFunction(f) { 209 | continue 210 | } 211 | if !p.ContainsFunction(f) { 212 | missings = append(missings, f) 213 | } 214 | } 215 | if len(missings) == 0 { 216 | message(typeInfo, 0, 0, Tr(infoMissingFunc)) 217 | return 218 | } 219 | for _, f := range missings { 220 | message(typeError, 0, 0, Tr(errMissingFunc, f)) 221 | fmt.Printf(" %s\n", commentAddManually) 222 | } 223 | } 224 | 225 | func checkInfoTypes(p *pkgbuild.PKGBUILD, edit bool) { 226 | infos := p.GetInfos() 227 | clean := true 228 | for _, info := range infos { 229 | name := info.Name() 230 | p0, p1 := info.GetPositions() 231 | var actualType, neededType string 232 | var isBad bool 233 | if standard.IsStandardFunction(name) { 234 | if isBad = !info.IsFunc(); isBad { 235 | actualType, neededType = commentVariable, commentFunction 236 | } 237 | } else if standard.IsStandardVariable(name) { 238 | if isBad = !info.IsVar(); isBad { 239 | actualType, neededType = commentFunction, commentVariable 240 | } else { 241 | actualType, neededType = commentStringVar, commentStringVar 242 | if info.IsArrayVar() { 243 | actualType = commentArrayVar 244 | } 245 | if standard.IsArrayVariable(name) { 246 | neededType = commentArrayVar 247 | } 248 | isBad = actualType != neededType 249 | } 250 | } 251 | if isBad { 252 | clean = false 253 | message(typeWarning, p0.Line, p1.Line, Tr(warnBadType, name, Tr(actualType), Tr(neededType))) 254 | } 255 | } 256 | if clean { 257 | message(typeInfo, 0, 0, Tr(infoBadType)) 258 | } 259 | } 260 | 261 | func checkEmpty(p *pkgbuild.PKGBUILD, edit bool) { 262 | infos := p.GetInfos(atom.VarArray, atom.VarString) 263 | clean := true 264 | for _, info := range infos { 265 | values := info.ArrayValue() 266 | if len(values) == 0 || (len(values) == 1 && values[0] == "") { 267 | clean = false 268 | name := info.Name() 269 | p0, p1 := info.GetPositions() 270 | message(typeWarning, p0.Line, p1.Line, Tr(warnEmpty, name)) 271 | if edit && QuestionYN(Tr(questionRemoveEmpty, name), true) { 272 | p.RemoveIndex(info.Index()) 273 | } 274 | } 275 | } 276 | if clean { 277 | message(typeInfo, 0, 0, Tr(infoEmpty)) 278 | } 279 | } 280 | 281 | func checkPkgrel(p *pkgbuild.PKGBUILD, edit bool) { 282 | info, ok := p.GetInfo(standard.PKGREL, atom.VarString) 283 | if !ok { 284 | return 285 | } 286 | p0, p1 := info.GetPositions() 287 | ok, t, m := true, typeInfo, Tr(infoVarClean, standard.PKGREL) 288 | if info.StringValue() != "1" { 289 | ok, t, m = false, typeWarning, Tr(warnPkgrel) 290 | } 291 | message(t, p0.Line, p1.Line, m) 292 | if !ok && edit && QuestionYN(Tr(questionPkgrel), false) { 293 | p.SetValue(info, "1") 294 | } 295 | } 296 | 297 | func checkArch(p *pkgbuild.PKGBUILD, edit bool) { 298 | info, ok := p.GetInfo(standard.ARCH, atom.VarString, atom.VarArray) 299 | if !ok { 300 | return 301 | } 302 | p0, p1 := info.GetPositions() 303 | ok, t, m := true, typeInfo, Tr(infoVarClean, standard.ARCH) 304 | values := info.ArrayValue() 305 | if len(values) != 1 || values[0] != "x86_64" { 306 | ok, t, m = false, typeWarning, Tr(warnArch) 307 | } 308 | message(t, p0.Line, p1.Line, m) 309 | if !ok && edit && QuestionYN(Tr(questionArch), true) { 310 | if !info.IsArrayVar() { 311 | p.SetArrayVar(info) 312 | } 313 | p.SetValue(info, "x86_64") 314 | } 315 | } 316 | 317 | func checkDep(p *pkgbuild.PKGBUILD, edit bool, info *atom.Info, pkgname string, exceptions map[string]bool) { 318 | errors := make(map[string]int) 319 | values := info.ArrayValue() 320 | for i, v := range values { 321 | v = formatPackage(v) 322 | values[i] = v 323 | if v == pkgname { 324 | errors[v] = 1 325 | } else if !exceptions[v] && !isPackageInRepo(v) { 326 | errors[v] = 2 327 | } 328 | } 329 | name := info.Name() 330 | p0, p1 := info.GetPositions() 331 | if len(errors) == 0 { 332 | message(typeInfo, p0.Line, p1.Line, Tr(infoVarClean, name)) 333 | return 334 | } 335 | message(typeWarning, p0.Line, p1.Line, Tr(warnDepends, name)) 336 | var newValues []string 337 | for _, v := range values { 338 | switch errors[v] { 339 | case 1: 340 | fmt.Printf(" %s\n", Tr(warnPackageIsName, v)) 341 | if edit && QuestionYN(" "+Tr(questionDepend, v), true) { 342 | v = Question(" " + Tr(questionTypeDepend)) 343 | } 344 | case 2: 345 | fmt.Printf(" %s\n", Tr(warnPackageNotInRepo, v)) 346 | if edit && QuestionYN(" "+Tr(questionDepend, v), true) { 347 | v = Question(" " + Tr(questionTypeDepend)) 348 | } 349 | } 350 | if v != "" { 351 | newValues = append(newValues, v) 352 | } 353 | } 354 | if edit { 355 | p.SetValue(info, newValues...) 356 | } 357 | } 358 | 359 | func checkDepends(p *pkgbuild.PKGBUILD, edit bool) { 360 | var hasDepend bool 361 | pkgname := p.GetValue(standard.PKGNAME) 362 | names := map[string]bool{ 363 | standard.CONFLICTS: true, 364 | standard.PROVIDES: true, 365 | standard.REPLACES: true, 366 | standard.DEPENDS: true, 367 | standard.MAKEDEPENDS: true, 368 | standard.OPTDEPENDS: true, 369 | standard.CHECKDEPENDS: true, 370 | } 371 | nameDepends := map[string]bool{ 372 | standard.DEPENDS: true, 373 | standard.MAKEDEPENDS: true, 374 | } 375 | exceptions := loadExceptions() 376 | infos := p.GetInfos(atom.VarArray) 377 | for _, info := range infos { 378 | name := info.Name() 379 | if !names[name] || len(info.ArrayValue()) == 0 { 380 | continue 381 | } 382 | hasDepend = hasDepend || nameDepends[name] 383 | checkDep(p, edit, info, pkgname, exceptions) 384 | } 385 | if !hasDepend { 386 | message(typeWarning, 0, 0, Tr(warnMissingDepends)) 387 | } 388 | } 389 | 390 | func checkInstalls(p *pkgbuild.PKGBUILD, edit bool) { 391 | info, ok := p.GetInfo(standard.INSTALL, atom.VarString) 392 | if !ok { 393 | return 394 | } 395 | p0, p1 := info.GetPositions() 396 | file := info.StringValue() 397 | if FileExists(file) { 398 | message(typeInfo, p0.Line, p1.Line, Tr(infoVarClean, standard.INSTALL)) 399 | fmt.Printf(" %s\n", Tr(commentInstall, urlHook)) 400 | return 401 | } 402 | message(typeWarning, p0.Line, p1.Line, Tr(warnInstall, file)) 403 | if !(edit && QuestionYN(Tr(questionInstall, file), true)) { 404 | return 405 | } 406 | newFile := Question(Tr(questionInstall2)) 407 | if newFile == "" { 408 | p.RemoveInfo(info) 409 | } else { 410 | p.SetValue(info, newFile) 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /cmd/pckcp/commands.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | . "github.com/bvaudour/kcp/common" 8 | "github.com/bvaudour/kcp/pkgbuild" 9 | ) 10 | 11 | func generate(clean, debug bool) { 12 | fp := JoinIfRelative(UserBaseDir, Config.Get("pckcp.protoFile")) 13 | if !FileExists(fp) { 14 | fp = JoinIfRelative(ConfigBaseDir, Config.Get("pckcp.protoFile")) 15 | if !FileExists(fp) { 16 | PrintError(Tr(errFileNotExist, fp)) 17 | os.Exit(1) 18 | } 19 | } 20 | proto, err := os.Open(fp) 21 | if err != nil { 22 | PrintError(err) 23 | os.Exit(1) 24 | } 25 | defer proto.Close() 26 | p, err := pkgbuild.Decode(proto) 27 | if err != nil { 28 | PrintError(err) 29 | os.Exit(1) 30 | } 31 | if clean { 32 | p.Format() 33 | } 34 | if debug { 35 | fmt.Print(p.String()) 36 | return 37 | } 38 | dest, err := os.Create("PKGBUILD") 39 | if err != nil { 40 | PrintError(err) 41 | os.Exit(1) 42 | } 43 | defer dest.Close() 44 | if _, err := p.Encode(dest); err != nil { 45 | PrintError(err) 46 | os.Exit(1) 47 | } 48 | } 49 | 50 | func check(edit, debug bool) { 51 | f, err := os.Open("PKGBUILD") 52 | if err != nil { 53 | PrintError(err) 54 | os.Exit(1) 55 | } 56 | defer f.Close() 57 | p, err := pkgbuild.Decode(f) 58 | if err != nil { 59 | PrintError(err) 60 | os.Exit(1) 61 | } 62 | 63 | // Checkers 64 | checkHeader(p, edit) 65 | checkDuplicates(p, edit) 66 | checkMissingFuncs(p, edit) 67 | checkMissingFuncs(p, edit) 68 | checkInfoTypes(p, edit) 69 | checkEmpty(p, edit) 70 | checkPkgrel(p, edit) 71 | checkArch(p, edit) 72 | checkDepends(p, edit) 73 | checkInstalls(p, edit) 74 | if edit && QuestionYN(Tr(questionFormat), true) { 75 | p.Format() 76 | } 77 | 78 | if debug { 79 | fmt.Println() 80 | fmt.Print(p.String()) 81 | return 82 | } 83 | if !edit { 84 | return 85 | } 86 | destname := fmt.Sprintf("PKGBUILD%s", Config.Get("pckcp.suffixNewPKGBUILD")) 87 | dest, err := os.Create(destname) 88 | if err != nil { 89 | PrintError(err) 90 | os.Exit(1) 91 | } 92 | defer dest.Close() 93 | if _, err := p.Encode(dest); err != nil { 94 | PrintError(err) 95 | os.Exit(1) 96 | } 97 | PrintWarning(Tr(warnSaved, destname)) 98 | } 99 | -------------------------------------------------------------------------------- /cmd/pckcp/consts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //Flags informations 4 | const ( 5 | appLongDescription = `Provides a tool to check the validity of a PKGBUILD according to the KCP standards. 6 | 7 | If flag -e is used, the common errors can be checked and a (potentially) valid PKGBUILD.new is created.` 8 | appDescription = "Tool in command-line to manage common PKGBUILD errors" 9 | synopsis = "[-h|-e|-v|-g[-c]]" 10 | help = "Print this help" 11 | version = "Print version" 12 | interactiveEdit = "Interactive edition" 13 | generatePrototype = "Generate a prototype of PKGBUILD" 14 | cleanUseless = "Removes the useless comments and blanks of the prototype" 15 | ) 16 | 17 | //Messages’ templates 18 | const ( 19 | typeError = "Error" 20 | typeWarning = "Warning" 21 | typeInfo = "Info" 22 | 23 | errFileNotExist = "File %s does not exist." 24 | errMissingVar = "Variable '%s' is missing." 25 | errMissingFunc = "Function '%s' is missing." 26 | 27 | warnSaved = "Modifications saved in %s!" 28 | warnHeader = "Header was found. Do not use names of maintainers or contributors in PKGBUILD, anyone can contribute, keep the header clean from this." 29 | warnDuplicate = "Some duplicates found:" 30 | warnBadType = "Bad type declaration: '%s' is %s but it should be %s." 31 | warnEmpty = "Variable '%s' is empty." 32 | warnPkgrel = "pkgrel is different from 1. It should be the case only if build instructions are edited but not pkgver." 33 | warnArch = "arch is different from 'x86_64'. Since KaOS only supports this architecture, no other arch would be added here." 34 | warnInstall = "install: file '%s' doesn’t exist." 35 | warnDepends = "Variable '%s' contains bad packages." 36 | warnPackageIsName = "'%s' is the name of the package. It is useless." 37 | warnPackageNotInRepo = "'%s' isn't in repo neither in kcp." 38 | warnMissingDepends = "Variables 'depends' and 'makedepends' are empty. You should manually check if it is not a missing." 39 | 40 | infoHeader = "Header is clean." 41 | infoDuplicate = "There aren’t duplicates." 42 | infoMissingVar = "There aren’t missing variables." 43 | infoMissingFunc = "There aren’t missing functions." 44 | infoBadType = "Declarations have the good type." 45 | infoEmpty = "There aren’t empty variables." 46 | infoVarClean = "Variable '%s' is clean." 47 | 48 | questionHeader = "Remove header?" 49 | questionDuplicate = "Remove duplicates?" 50 | questionMissingVar = "Add variable '%s'?" 51 | questionAddValue = "Set variable '%s' with (leave blank to ignore):" 52 | questionRemoveEmpty = "Remove variable '%s'?" 53 | questionPkgrel = "Reset pkgrel to 1?" 54 | questionArch = "Reset arch to x86_64?" 55 | questionInstall = "Modify name of '%s' file?" 56 | questionInstall2 = "Type the new name (leave blank to remove install variable):" 57 | questionDepend = "Modify '%s'?" 58 | questionTypeDepend = "Type the new value (leave blank to remove it):" 59 | questionFormat = "Format the PKGBUILD?" 60 | 61 | commentAddManually = "You should add it manually." 62 | commentFunction = "a function" 63 | commentVariable = "a variable" 64 | commentStringVar = "a string variable" 65 | commentArrayVar = "an array variable" 66 | commentInstall = "Note that hooks provide similar functionnalities and are more powerful. For more informations: %s" 67 | ) 68 | -------------------------------------------------------------------------------- /cmd/pckcp/flags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | . "github.com/bvaudour/kcp/common" 7 | "github.com/bvaudour/kcp/flag" 8 | ) 9 | 10 | var ( 11 | flags *flag.Parser 12 | fHelp, fVersion, fEdit, fDebug, fGenerate, fClean *bool 13 | ) 14 | 15 | func initFlags() { 16 | flags = flag.NewParser(Tr(appDescription), Version) 17 | flags.Set(flag.Synopsis, Tr(synopsis)) 18 | flags.Set(flag.LongDescription, Tr(appLongDescription)) 19 | 20 | fHelp, _ = flags.Bool("-h", "--help", Tr(help)) 21 | fVersion, _ = flags.Bool("-v", "--version", Tr(version)) 22 | fEdit, _ = flags.Bool("-e", "--edit", Tr(interactiveEdit)) 23 | fGenerate, _ = flags.Bool("-g", "--generate", Tr(generatePrototype)) 24 | 25 | fClean, _ = flags.Bool("-c", "--clean", Tr(cleanUseless)) 26 | flags.Require("-c", "-g") 27 | 28 | fDebug, _ = flags.Bool("-d", "--debug", "") 29 | flags.GetFlag("--debug").Set(flag.Hidden, true) 30 | flags.Group("-h", "-e", "-v", "-g") 31 | } 32 | 33 | func parseFlags() { 34 | if err := flags.Parse(os.Args); err != nil { 35 | PrintError(err) 36 | flags.PrintHelp() 37 | os.Exit(1) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cmd/pckcp/pckcp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | initFlags() 5 | parseFlags() 6 | 7 | if *fHelp { 8 | flags.PrintHelp() 9 | return 10 | } 11 | if *fVersion { 12 | flags.PrintVersion() 13 | return 14 | } 15 | if *fGenerate { 16 | generate(*fClean, *fDebug) 17 | return 18 | } 19 | check(*fEdit, *fDebug) 20 | } 21 | -------------------------------------------------------------------------------- /color/color.go: -------------------------------------------------------------------------------- 1 | package color 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Color int 8 | 9 | const ( 10 | NoColor Color = iota 11 | Red 12 | Green 13 | Yellow 14 | Blue 15 | Magenta 16 | Cyan 17 | ) 18 | 19 | func (c Color) Light() string { 20 | if c == NoColor { 21 | return "\033[1m" 22 | } 23 | return fmt.Sprintf("\033[1;3%dm", c) 24 | } 25 | 26 | func (c Color) Dark() string { 27 | if c == NoColor { 28 | return "\033[m" 29 | } 30 | return fmt.Sprintf("\033[3%dm", c) 31 | } 32 | 33 | func (c Color) String() string { 34 | if c == NoColor { 35 | return c.Dark() 36 | } 37 | return c.Light() 38 | } 39 | 40 | func (c Color) Format(f string, args ...interface{}) string { 41 | return fmt.Sprint(c, fmt.Sprintf(f, args...), NoColor) 42 | } 43 | 44 | func (c Color) FormatDark(f string, args ...interface{}) string { 45 | return fmt.Sprint(c.Dark(), fmt.Sprintf(f, args...), NoColor) 46 | } 47 | 48 | func (c Color) Colorize(args ...interface{}) string { 49 | l := len(args) 50 | elts := make([]interface{}, l+2) 51 | elts[0] = c.Light() 52 | copy(elts[1:l+1], args) 53 | elts[l+1] = NoColor 54 | return fmt.Sprint(elts...) 55 | } 56 | 57 | func (c Color) ColorizeDark(args ...interface{}) string { 58 | l := len(args) 59 | elts := make([]interface{}, l+2) 60 | elts[0] = c.Dark() 61 | copy(elts[1:l+1], args) 62 | elts[l+1] = NoColor 63 | return fmt.Sprint(elts...) 64 | } 65 | -------------------------------------------------------------------------------- /common/consts.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | fbDefaultEditor = "vim" 5 | 6 | cDefaultYes = "[Y/n]" 7 | cDefaultNo = "[y/N]" 8 | 9 | Yes = "yes" 10 | No = "no" 11 | ) 12 | -------------------------------------------------------------------------------- /common/consts_all.go: -------------------------------------------------------------------------------- 1 | // +build !dev 2 | 3 | package common 4 | 5 | const ( 6 | fbVersion = "" 7 | fbLocaleBaseDir = "/usr/share/locale" 8 | fbLocaleDomain = "kcp" 9 | fbConfigBaseDir = "/etc/kcp" 10 | fbConfigUserDir = "" 11 | fbConfigFile = "kcp.conf" 12 | fbOrganization = "KaOS-Community-Packages" 13 | ) 14 | -------------------------------------------------------------------------------- /common/funcs.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "path" 9 | "strings" 10 | "time" 11 | 12 | "github.com/bvaudour/kcp/color" 13 | "github.com/leonelquinteros/gotext" 14 | ) 15 | 16 | //Tr returns the translated string. 17 | func Tr(msg string, vars ...interface{}) string { 18 | return gotext.Get(msg, vars...) 19 | } 20 | 21 | //LaunchCommand launches a system command. 22 | func LaunchCommand(name string, args ...string) error { 23 | cmd := exec.Command(name, args...) 24 | cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr 25 | return cmd.Run() 26 | } 27 | 28 | //GetOuptutCommand returns the redirected output of a system command. 29 | func GetOutputCommand(name string, args ...string) ([]byte, error) { 30 | cmd := exec.Command(name, args...) 31 | return cmd.Output() 32 | } 33 | 34 | //Edit lets the user edit the given file. 35 | func EditFile(f string) error { 36 | return LaunchCommand(DefaultEditor, f) 37 | } 38 | 39 | //InstalledVersion returns the installed version of a package. 40 | func InstalledVersion(app string) string { 41 | if b, e := GetOutputCommand("pacman", "-Q", app); e == nil { 42 | f := strings.Fields(string(b)) 43 | if len(f) >= 2 { 44 | return f[1] 45 | } 46 | } 47 | return "" 48 | } 49 | 50 | //Question displays a question to the output and returns the response given by the user. 51 | func Question(msg string) string { 52 | fmt.Print(msg + " ") 53 | sc := bufio.NewScanner(os.Stdin) 54 | sc.Scan() 55 | return strings.TrimSpace(sc.Text()) 56 | } 57 | 58 | //QuestionYN displays a question to the output and returns the boolean response given by the user. 59 | func QuestionYN(msg string, defaultResponse bool) bool { 60 | defstr, resp := Tr(cDefaultYes), "" 61 | if !defaultResponse { 62 | defstr = Tr(cDefaultNo) 63 | } 64 | fmt.Print(color.Yellow.Format("%s %s ", msg, Tr(defstr))) 65 | if _, e := fmt.Scanf("%v", &resp); e != nil || len(resp) == 0 { 66 | return defaultResponse 67 | } 68 | yes, no := strings.ToLower(Tr(Yes)), strings.ToLower(Tr(No)) 69 | resp = strings.ToLower(resp) 70 | switch resp[0] { 71 | case yes[0]: 72 | return true 73 | case no[0]: 74 | return false 75 | case Yes[0]: 76 | return true 77 | case No[0]: 78 | return false 79 | } 80 | return defaultResponse 81 | } 82 | 83 | //PrintError print a red message in the stderr. 84 | func PrintError(e interface{}) { 85 | fmt.Fprintln(os.Stderr, color.Red.Colorize(e)) 86 | } 87 | 88 | //PrintWarning print a yellow message in the stderr. 89 | func PrintWarning(e interface{}) { 90 | fmt.Fprintln(os.Stderr, color.Yellow.Colorize(e)) 91 | } 92 | 93 | //Now returns the UNIX timestamp from the current time 94 | func Now() int64 { 95 | return time.Now().UTC().Unix() 96 | } 97 | 98 | //StrToTimeStamp converts a formatted string date to a timestamp. 99 | func StrToTimestamp(date string) int64 { 100 | if date == "" { 101 | return 0 102 | } 103 | utc, _ := time.LoadLocation("") 104 | d, _ := time.ParseInLocation(time.RFC3339, date, utc) 105 | return d.Unix() 106 | } 107 | 108 | //TimestampTostring convertis an UNIX timestamp to a formatted string. 109 | func TimestampToString(unix int64) string { 110 | if unix == 0 { 111 | return "" 112 | } 113 | return time.Unix(unix, 0).UTC().Format(time.RFC3339) 114 | } 115 | 116 | //FilExists check if the given file or directory exists on the system. 117 | func FileExists(path string) bool { 118 | _, err := os.Stat(path) 119 | return !os.IsNotExist(err) 120 | } 121 | 122 | //JoinRelative returns the complete path of the file. 123 | //- If the path of the file is absolute, it returns it. 124 | //- If it is relative, it returns the absolute path from the base. 125 | func JoinIfRelative(base, file string) string { 126 | if len(file) > 0 && file[0] == '/' { 127 | return file 128 | } 129 | return path.Join(base, file) 130 | } 131 | -------------------------------------------------------------------------------- /common/init.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | _ "embed" 7 | "fmt" 8 | "os" 9 | "path" 10 | "strings" 11 | 12 | "github.com/bvaudour/kcp/conf" 13 | "github.com/leonelquinteros/gotext" 14 | ) 15 | 16 | //go:embed kcp.conf 17 | var embedConf []byte 18 | 19 | // Initialized at build time 20 | var ( 21 | Version string 22 | BuildTime string 23 | 24 | LocaleBaseDir string 25 | LocaleDomain string 26 | 27 | ConfigBaseDir string 28 | ConfigFile string 29 | 30 | Organization string 31 | User string 32 | Password string 33 | ) 34 | 35 | // Initialized at execution time 36 | var ( 37 | Language string 38 | DefaultEditor string 39 | UserBaseDir string 40 | Config *conf.Configuration 41 | Exceptions = make(map[string]bool) 42 | ) 43 | 44 | func setIfZero(v *string, d string) { 45 | if len(*v) == 0 { 46 | *v = d 47 | } 48 | } 49 | 50 | func initLanguage() { 51 | if Language = Config.Get("main.language"); Language != "" && checkLanguage() { 52 | return 53 | } 54 | if Language = os.Getenv("LANGUAGE"); Language != "" && checkLanguage() { 55 | return 56 | } 57 | if Language = os.Getenv("LANG"); Language != "" && checkLanguage() { 58 | return 59 | } 60 | Language = "en" 61 | } 62 | 63 | func languageFileExist() bool { 64 | p := path.Join(LocaleBaseDir, Language, "LC_MESSAGES", LocaleDomain+".mo") 65 | return FileExists(p) 66 | } 67 | 68 | func checkLanguage() bool { 69 | // No modification 70 | if languageFileExist() { 71 | return true 72 | } 73 | // Trying to replace fr_FR.UTF-8 by fr_FR, for example 74 | if i := strings.Index(Language, "."); i > 0 { 75 | Language = Language[:i] 76 | if languageFileExist() { 77 | return true 78 | } 79 | } 80 | // Trying base: fr_FR by fr, for example 81 | if i := strings.Index(Language, "_"); i > 0 { 82 | if languageFileExist() { 83 | return true 84 | } 85 | } 86 | // Trying complete base: fr by fr_FR 87 | cl := Language 88 | Language = fmt.Sprintf("%s_%s", Language, strings.ToUpper(Language)) 89 | if languageFileExist() { 90 | return true 91 | } 92 | Language = cl 93 | //@TODO: Trying to search in similar locale (ie. if there is one fr_*) 94 | return false 95 | } 96 | 97 | func init() { 98 | // Init buildInfo 99 | setIfZero(&Version, fbVersion) 100 | setIfZero(&BuildTime, TimestampToString(Now())) 101 | 102 | // Init gettext 103 | setIfZero(&LocaleBaseDir, fbLocaleBaseDir) 104 | setIfZero(&LocaleDomain, fbLocaleDomain) 105 | 106 | // Init config 107 | setIfZero(&ConfigBaseDir, fbConfigBaseDir) 108 | setIfZero(&ConfigFile, fbConfigFile) 109 | setIfZero(&UserBaseDir, fbConfigUserDir) 110 | setIfZero(&Organization, fbOrganization) 111 | 112 | // Init runtime 113 | DefaultEditor = os.Getenv("EDITOR") 114 | setIfZero(&UserBaseDir, os.Getenv("XDG_CONFIG_HOME")) 115 | 116 | // Default values if empty 117 | setIfZero(&DefaultEditor, fbDefaultEditor) 118 | setIfZero(&UserBaseDir, path.Join(os.Getenv("HOME"), ".config")) 119 | 120 | // Load the default config 121 | Config = conf.Parse(bytes.NewReader(embedConf)) 122 | 123 | // Load the system config 124 | fp := path.Join(ConfigBaseDir, ConfigFile) 125 | if cs, err := conf.Load(fp); err == nil { 126 | Config.Fusion(cs) 127 | } 128 | 129 | // Load the user config 130 | userDir := Config.Get("main.configDir") 131 | setIfZero(&userDir, fbLocaleDomain) 132 | UserBaseDir = JoinIfRelative(UserBaseDir, userDir) 133 | if !FileExists(UserBaseDir) { 134 | os.MkdirAll(UserBaseDir, 0755) 135 | } 136 | userfp := path.Join(UserBaseDir, ConfigFile) 137 | if cu, err := conf.Load(userfp); err == nil { 138 | Config.Fusion(cu) 139 | } 140 | conf.Save(userfp, Config) 141 | 142 | // Load locales 143 | initLanguage() 144 | gotext.Configure(LocaleBaseDir, Language, LocaleDomain) 145 | 146 | // Load custom github config 147 | org, user, passwd := Config.Get("github.organization"), Config.Get("github.user"), Config.Get("github.password") 148 | if org != "" { 149 | Organization = org 150 | } 151 | if user != "" && passwd != "" { 152 | User, Password = user, passwd 153 | } 154 | 155 | // Load exceptions 156 | exceptionsFile := Config.Get("pckcp.exceptionsFile") 157 | if exceptionsFile != "" { 158 | p := path.Join(userDir, exceptionsFile) 159 | if f, err := os.ReadFile(p); err == nil { 160 | sc := bufio.NewScanner(bytes.NewReader(f)) 161 | for sc.Scan() { 162 | Exceptions[sc.Text()] = true 163 | } 164 | } 165 | p = path.Join(ConfigBaseDir, exceptionsFile) 166 | if f, err := os.ReadFile(p); err == nil { 167 | sc := bufio.NewScanner(bytes.NewReader(f)) 168 | for sc.Scan() { 169 | Exceptions[sc.Text()] = true 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /common/kcp.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | ;; Base directory of the specific user configuration 3 | ;; If not an absolute path, it is relative to the $XDG_CONFIG_HOME 4 | ;; (or $HOME/.config if $XDG_CONFIG_HOME is not set). 5 | configDir = kcp 6 | 7 | ;; Language code 8 | ;; Leave it empty to use the system’s default 9 | language = 10 | 11 | [kcp] 12 | ;; Temporary dir 13 | ;; Temporary dir is used during a package installation through kcp. 14 | tmpDir = /tmp/kcp 15 | 16 | ;; Name of the file locker 17 | ;; This file is created during a KCP package installation 18 | ;; in order to prevent the launching of multiple kcp instances. 19 | ;; It is removed once the installation is finished or if 20 | ;; the program is killed. 21 | lockerFile = locked 22 | 23 | ;; Name of Database file 24 | ;; This file is created/updated with the kcp -u command 25 | ;; or when you install a package with the kcp -i command. 26 | dbFile = kcp.json 27 | 28 | ;; Repos to ignore during update 29 | ;; The names must be separated by spaces 30 | ignore = KaOS-Community-Packages.github.io 31 | 32 | ;; Clone method to use 33 | ;; Available values: https (default) or ssh 34 | ;; Warning: if you choose ssh, you need to create a key 35 | ;; and upload it in your git profile. For more explanation: 36 | ;; https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh 37 | cloneMethod = https 38 | 39 | [pckcp] 40 | ;; Name of exceptions file 41 | ;; The listed exceptions define the depends to ignore 42 | ;; at checking a PKGBUILD 43 | exceptionsFile = exceptions 44 | 45 | ;; Name of the PKGBUILD prototype 46 | protoFile = PKGBUILD.commented.kaos.proto 47 | 48 | ;; Suffix of the newly created PKGBUILDs when launching 49 | ;; the interactive edition 50 | suffixNewPKGBUILD = .new 51 | 52 | [github] 53 | ;; Github Organization where repos are. 54 | ;; Leave blank to use the default system. 55 | organization = 56 | 57 | ;; User/Password to use a custom authentification to connect to the API 58 | ;; Leave Blank to use the system. 59 | user = 60 | password = 61 | 62 | -------------------------------------------------------------------------------- /conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "os" 9 | "sort" 10 | "strings" 11 | ) 12 | 13 | //Configuration is a parser of .conf file. 14 | //A configuration file is of the form: 15 | // 16 | // [section1] 17 | // value1A = … 18 | // ;comment 19 | // #another comment 20 | // [section2] 21 | // value2A = … 22 | // value2B = … 23 | // 24 | //Each value can be accessed by the concatenation 25 | //of the section and its name (for ex.: section1.value1A, section2.value2B, etc.). 26 | type Configuration struct { 27 | t []string 28 | m map[string]string 29 | i map[string]int 30 | } 31 | 32 | //New returns an empty configuration 33 | func New() *Configuration { 34 | return &Configuration{ 35 | m: make(map[string]string), 36 | i: make(map[string]int), 37 | } 38 | } 39 | 40 | //Keys returns the list of the available values’ keys. 41 | func (c *Configuration) Keys() []string { 42 | out := make([]string, 0, len(c.i)) 43 | for i := range c.i { 44 | out = append(out, i) 45 | } 46 | sort.Slice(out, func(i, j int) bool { 47 | return c.i[out[i]] < c.i[out[j]] 48 | }) 49 | return out 50 | } 51 | 52 | //Position returns the index where the key is defined on the file. 53 | //index = line - 1. 54 | func (c *Configuration) Position(key string) int { 55 | if p, ok := c.i[key]; ok { 56 | return p 57 | } 58 | return -1 59 | } 60 | 61 | //Set modifies the given key with the given value. 62 | func (c *Configuration) Set(key string, value string) (ok bool) { 63 | if _, ok = c.m[key]; ok { 64 | c.m[key] = value 65 | } 66 | return 67 | } 68 | 69 | //Contains checks if the given key is defined. 70 | func (c *Configuration) Contains(key string) bool { 71 | _, ok := c.i[key] 72 | return ok 73 | } 74 | 75 | //Get returns the value of the given keys. 76 | //If the key is not defined, it returns an empty string. 77 | func (c *Configuration) Get(key string) string { 78 | return c.m[key] 79 | } 80 | 81 | //Read parses a .conf files into the Configuration object. 82 | func (c *Configuration) Read(r io.Reader) { 83 | b := bufio.NewScanner(r) 84 | var section string 85 | i := -1 86 | for b.Scan() { 87 | i++ 88 | line := b.Text() 89 | c.t = append(c.t, line) 90 | line = strings.TrimSpace(line) 91 | l := len(line) 92 | // Line is comment or blank line 93 | if l == 0 || line[0] == '#' || line[0] == ';' { 94 | continue 95 | } 96 | // line is section header 97 | if line[0] == '[' && line[l-1] == ']' { 98 | section = line[1 : l-1] 99 | continue 100 | } 101 | if idx := strings.Index(line, "="); idx > 0 { 102 | key, value := strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+1:]) 103 | k := section + "." + key 104 | c.m[k] = value 105 | c.i[k] = i 106 | } 107 | } 108 | } 109 | 110 | //Write encodes the Configuration object into the given file. 111 | func (c *Configuration) Write(w io.Writer) error { 112 | lines := make([]string, len(c.t)) 113 | copy(lines, c.t) 114 | for k, v := range c.m { 115 | i := c.Position(k) 116 | if i < 0 { 117 | continue 118 | } 119 | l := lines[i] 120 | idx := strings.Index(l, "=") 121 | v := strings.TrimSpace(v) 122 | lines[i] = fmt.Sprintf("%s= %s", l[:idx], v) 123 | } 124 | for _, l := range lines { 125 | if _, err := fmt.Fprintln(w, l); err != nil { 126 | return err 127 | } 128 | } 129 | return nil 130 | } 131 | 132 | func (c *Configuration) Debug() string { 133 | out := make(map[string]map[string]interface{}) 134 | for k, v := range c.m { 135 | out[k] = map[string]interface{}{ 136 | "Value": v, 137 | "Interface": c.Position(k), 138 | } 139 | } 140 | b, _ := json.MarshalIndent(out, "", " ") 141 | return string(b) 142 | } 143 | 144 | //Fusion sets all keys both defined in c & c2 with 145 | //the values of c2 into c. 146 | func (c *Configuration) Fusion(c2 *Configuration) { 147 | for _, k := range c.Keys() { 148 | if c2.Contains(k) { 149 | c.Set(k, c2.Get(k)) 150 | } 151 | } 152 | } 153 | 154 | //Parse decodes a .conf file and returns the decoded result. 155 | func Parse(r io.Reader) *Configuration { 156 | c := New() 157 | c.Read(r) 158 | return c 159 | } 160 | 161 | //Load parses the file located at the given path and returns 162 | //the decoded result. 163 | func Load(filepath string) (c *Configuration, err error) { 164 | var f *os.File 165 | if f, err = os.Open(filepath); err != nil { 166 | return 167 | } 168 | defer f.Close() 169 | c = Parse(f) 170 | return 171 | } 172 | 173 | //Save encodes the configuration on the file at the given path. 174 | func Save(filepath string, c *Configuration) (err error) { 175 | var f *os.File 176 | if f, err = os.Create(filepath); err != nil { 177 | return 178 | } 179 | defer f.Close() 180 | err = c.Write(f) 181 | return 182 | } 183 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION= 4 | CONFIGDIR=/etc/kcp 5 | CONFIGFILE=kcp.conf 6 | LOCALEDIR=/usr/share/locale 7 | DOMAIN=kcp 8 | ORGANIZATION=KaOS-Community-Packages 9 | USER= 10 | PASSWORD= 11 | 12 | _help() { 13 | echo "Usage: $0 [options]" 14 | echo "Options:" 15 | echo -e "\t-h, --help\tPrint this help" 16 | echo -e "\t--version\tVersion of the application" 17 | echo -e "\t--config-dir\tFinal configuration directory [/etc/kcp]" 18 | echo -e "\t--config-file\tName of the configuration file [kcp.conf]" 19 | echo -e "\t--locale-dir\tFinal locale directory [/usr/share/locale]" 20 | echo -e "\t--locale-domain\tDomain name of the locales [kcp]" 21 | echo -e "\t--organization\tGithub organization [KaOS-Community-Packages]" 22 | echo -e "\t--user\tUser to use for the github API requests" 23 | echo -e "\t--password\tPassword to use for the github API requests" 24 | } 25 | 26 | _parseArgs() { 27 | while (( $# > 0 )); do 28 | local flag=$1 29 | local arg=$2 30 | shift 31 | shift 32 | case $flag in 33 | -h|--help) _help; exit;; 34 | --version) VERSION=$arg;; 35 | --config-dir) CONFIGDIR=$arg;; 36 | --config-file) CONFIGFILE=$arg;; 37 | --locale-dir) LOCALEDIR=$arg;; 38 | --locale-domain) DOMAIN=$arg;; 39 | --organization) ORGANIZATION=$arg;; 40 | --user) USER=$arg;; 41 | --password) PASSWORD=$arg;; 42 | *) echo "Invalid flag: '${flag}'. Type $0 -h for help."; exit 1;; 43 | esac 44 | done 45 | } 46 | 47 | _checkVersion () { 48 | if [[ -z ${VERSION} ]]; then 49 | if [[ -e .git ]]; then 50 | VERSION=$(git rev-parse HEAD) 51 | else 52 | VERSION='' 53 | fi 54 | fi 55 | } 56 | 57 | _makeMakefile() { 58 | cp -f Makefile.in Makefile 59 | sed -i "s|%version%|${VERSION}|" Makefile 60 | sed -i "s|%configdir%|${CONFIGDIR}|" Makefile 61 | sed -i "s|%configfile%|${CONFIGFILE}|" Makefile 62 | sed -i "s|%localedir%|${LOCALEDIR}|" Makefile 63 | sed -i "s|%domain%|${DOMAIN}|" Makefile 64 | sed -i "s|%organization%|${ORGANIZATION}|" Makefile 65 | sed -i "s|%user%|${USER}|" Makefile 66 | sed -i "s|%password%|${PASSWORD}|" Makefile 67 | } 68 | 69 | _parseArgs $@ 70 | _checkVersion 71 | _makeMakefile 72 | -------------------------------------------------------------------------------- /database/consts.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | const ( 4 | labelInstalled = "[installed]" 5 | labelInstalledVersion = "[installed: %s]" 6 | labelName = "Name" 7 | labelVersion = "Version" 8 | labelDescription = "Description" 9 | labelArch = "Architecture" 10 | labelUrl = "URL" 11 | labelLicenses = "Licenses" 12 | labelProvides = "Provides" 13 | labelDepends = "Depends on" 14 | labelMakeDepends = "Depends on (make)" 15 | labelOptDepends = "Optional Deps" 16 | labelConflicts = "Conflicts With" 17 | labelReplaces = "Replaces" 18 | labelInstall = "Install Script" 19 | labelValidatedBy = "Validated By" 20 | labelYes = "Yes" 21 | labelNo = "No" 22 | 23 | errPathExists = "Dir %s already exists!" 24 | 25 | msgAdded = "%d entries added!" 26 | msgDeleted = "%d entries deleted!" 27 | msgUpdated = "%d entries updated!" 28 | 29 | baseUrl = "https://api.github.com/orgs" 30 | baseRawURL = "https://raw.githubusercontent.com/%s/%s/%s/PKGBUILD" 31 | baseOrganizationURL = baseUrl + "/%s" 32 | baseReposURL = baseOrganizationURL + "/repos?page=%d&per_page=%d" 33 | acceptHeader = "application/vnd.github.v3+json" 34 | 35 | defaultLimit = 100 36 | defaultRoutines = 150 37 | ) 38 | -------------------------------------------------------------------------------- /database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "os" 8 | "sync" 9 | "time" 10 | 11 | "github.com/bvaudour/kcp/color" 12 | ) 13 | 14 | // Database is the decoded structure 15 | // of a json database of packages. 16 | type Database struct { 17 | LastUpdate time.Time `json:"last_update"` 18 | IgnoreRepos []string `json:"ignore_repos"` 19 | BrokenDepends []string `json:"broken_depends"` 20 | Packages `json:"packages"` 21 | } 22 | 23 | // New returns a new empty database initialized 24 | // by repositories of the organzation to ignore. 25 | func New(ignored ...string) Database { 26 | return Database{ 27 | IgnoreRepos: ignored, 28 | } 29 | } 30 | 31 | // Decode decodes the given file to the database 32 | func (db *Database) Decode(r io.Reader) error { 33 | dec := json.NewDecoder(r) 34 | 35 | return dec.Decode(db) 36 | } 37 | 38 | // Encode encodes the database to json 39 | // and write it to the given file. 40 | func (db Database) Encode(w io.Writer) error { 41 | enc := json.NewEncoder(w) 42 | 43 | return enc.Encode(db) 44 | } 45 | 46 | // Load decodes the file in the given path and 47 | // returns the decoded database. 48 | func Load(fpath string, ignored ...string) (db Database, err error) { 49 | var f *os.File 50 | db = New(ignored...) 51 | 52 | if f, err = os.Open(fpath); err != nil { 53 | return 54 | } 55 | defer f.Close() 56 | 57 | err = db.Decode(f) 58 | db.IgnoreRepos = ignored 59 | 60 | return 61 | } 62 | 63 | // Save writes the database into the file on the given path. 64 | func Save(fpath string, db Database) (err error) { 65 | var f *os.File 66 | 67 | if f, err = os.Create(fpath); err != nil { 68 | return 69 | } 70 | defer f.Close() 71 | 72 | return db.Encode(f) 73 | } 74 | 75 | // UpdateBroken updates the broken depends. 76 | func (db *Database) UpdateBroken() { 77 | db.BrokenDepends = db.Packages.SearchBroken() 78 | } 79 | 80 | // UpdateRemote updates the database from a github organization. 81 | // If optional user and password are given, requests are done 82 | // with authentification in order to have a better rate limit. 83 | func (db *Database) UpdateRemote(organization string, debug bool, opt ...string) (counter Counter, err error) { 84 | limit, routines := defaultLimit, defaultRoutines 85 | repo := NewRepository(organization, opt...) 86 | 87 | var nbPages, nbRepos int 88 | if nbPages, nbRepos, err = repo.CountPages(limit); err != nil { 89 | fmt.Fprintf( 90 | os.Stderr, 91 | "%s %s\n", 92 | color.Red.Format("[Error: %s]", err), 93 | "Failed to count the pages of the repository list", 94 | ) 95 | return 96 | } 97 | 98 | if debug { 99 | fmt.Fprintln( 100 | os.Stderr, 101 | color.Magenta.Format("%d pages, %d repos", nbPages, nbRepos), 102 | ) 103 | } 104 | 105 | var newPackages Packages 106 | ignored := sliceToSet(db.IgnoreRepos) 107 | lastUpdate, newUpdate := db.LastUpdate, time.Now() 108 | 109 | packages := make(chan Package, (nbPages-1)*limit+1) 110 | buffer := make(chan Package, nbRepos) 111 | quit := make(chan bool) 112 | var wgPackages, wgPages, wgBuffer sync.WaitGroup 113 | 114 | wgBuffer.Add(1) 115 | go (func() { 116 | defer wgBuffer.Done() 117 | for { 118 | p, ok := <-buffer 119 | if !ok { 120 | quit <- true 121 | return 122 | } 123 | newPackages.Push(p) 124 | } 125 | })() 126 | 127 | wgPackages.Add(routines) 128 | for i := 0; i < routines; i++ { 129 | go (func() { 130 | defer wgPackages.Done() 131 | for { 132 | p, ok := <-packages 133 | if !ok { 134 | return 135 | } 136 | if ignored[p.Name] { 137 | continue 138 | } 139 | 140 | p.PkgbuildUrl = fmt.Sprintf(baseRawURL, repo.organization, p.Name, p.Branch) 141 | p.LocalVersion = p.GetLocaleVersion() 142 | if p.noChange = p.UpdatedAt.Before(lastUpdate); !p.noChange { 143 | if file, e := p.GetPKGBUID(debug); e == nil { 144 | p.updateFromPKGBUILD(file) 145 | } 146 | } 147 | buffer <- p 148 | } 149 | })() 150 | } 151 | 152 | wgPages.Add(nbPages) 153 | for i := 1; i <= nbPages; i++ { 154 | go (func(i int) { 155 | defer wgPages.Done() 156 | page, e := repo.GetPage(i, limit, debug) 157 | if e != nil { 158 | err = e 159 | return 160 | } 161 | for _, p := range page { 162 | packages <- p 163 | } 164 | })(i) 165 | } 166 | 167 | wgPages.Wait() 168 | 169 | close(packages) 170 | wgPackages.Wait() 171 | 172 | close(buffer) 173 | <-quit 174 | wgBuffer.Wait() 175 | 176 | if err != nil { 177 | fmt.Fprintf( 178 | os.Stderr, 179 | "%s %s\n", 180 | color.Red.Format("[Error: %s]", err), 181 | "Failed to retrieve the remote packages list", 182 | ) 183 | return 184 | } 185 | psOld := db.Packages.ToSet() 186 | 187 | for i, p := range newPackages { 188 | p0, ok := psOld[p.Name] 189 | if !ok { 190 | counter.Added++ 191 | } else if !p.noChange { 192 | counter.Updated++ 193 | } else { 194 | newPackages[i].updateFromPackage(p0) 195 | } 196 | } 197 | 198 | psNew := newPackages.ToSet() 199 | 200 | for n := range psOld { 201 | if _, ok := psNew[n]; !ok { 202 | counter.Deleted++ 203 | } 204 | } 205 | 206 | db.Packages, db.LastUpdate = newPackages, newUpdate 207 | 208 | return 209 | } 210 | 211 | // Update checks if updates are available in the database. 212 | func (db *Database) Update(organization string, debug bool, opt ...string) (counter Counter, err error) { 213 | if counter, err = db.UpdateRemote(organization, debug, opt...); err == nil { 214 | db.UpdateBroken() 215 | } 216 | 217 | return 218 | } 219 | -------------------------------------------------------------------------------- /database/doc.go: -------------------------------------------------------------------------------- 1 | // Package database provides utilities 2 | // to manage community repository database. 3 | // Only Github organizatons are supported. 4 | package database 5 | -------------------------------------------------------------------------------- /database/package.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path" 9 | "sort" 10 | "strings" 11 | "sync" 12 | "time" 13 | "unicode/utf8" 14 | 15 | "github.com/bvaudour/kcp/color" 16 | "github.com/bvaudour/kcp/common" 17 | "github.com/bvaudour/kcp/pkgbuild" 18 | "github.com/bvaudour/kcp/pkgbuild/standard" 19 | ) 20 | 21 | // Package stores informations about a package. 22 | type Package struct { 23 | Name string `json:"name"` 24 | Description string `json:"description"` 25 | CreatedAt time.Time `json:"created_at"` 26 | UpdatedAt time.Time `json:"updated_at"` 27 | PushedAt time.Time `json:"pushed_at"` 28 | RepoUrl string `json:"html_url"` 29 | CloneUrl string `json:"clone_url"` 30 | SshUrl string `json:"ssh_url"` 31 | PkgbuildUrl string `json:"pkgbuild_url"` 32 | Stars int `json:"stargazers_count"` 33 | Branch string `json:"default_branch"` 34 | LocalVersion string `json:"local_version"` 35 | RepoVersion string `json:"remote_version"` 36 | Arch []string `json:"architectures"` 37 | Url string `json:"upstream_url"` 38 | Provides []string `json:"provides"` 39 | Depends []string `json:"depends"` 40 | OptDepends []string `json:"opt_depends"` 41 | MakeDepends []string `json:"make_depends"` 42 | Conflicts []string `json:"conflicts"` 43 | Replaces []string `json:"replaces"` 44 | Licenses []string `json:"licenses"` 45 | ValidatedBy string `json:"validated_by"` 46 | HasInstallScript bool `json:"has_install_script"` 47 | noChange bool 48 | } 49 | 50 | // GetLocaleVersion searches the installed version of 51 | // the package. If the package is not installed 52 | // it returns an empty string. 53 | func (p Package) GetLocaleVersion() string { 54 | return common.InstalledVersion(p.Name) 55 | } 56 | 57 | // GetPKGBUILD reads and parses the remote PKGBUILD 58 | // from the github organization URL. 59 | func (p Package) GetPKGBUID(debug ...bool) (pkg *pkgbuild.PKGBUILD, err error) { 60 | url := p.PkgbuildUrl 61 | printDebug := len(debug) > 0 && debug[0] 62 | var buf io.Reader 63 | 64 | if buf, err = execRequest(url, ctx{}); err != nil { 65 | if printDebug { 66 | fmt.Fprintf(os.Stderr, "%s %s\n", color.Red.Format("[Error: %s]", err), url) 67 | } 68 | return 69 | } 70 | 71 | return pkgbuild.DecodeVars(buf) 72 | } 73 | 74 | func (p *Package) updateFromPKGBUILD(file *pkgbuild.PKGBUILD) { 75 | p.RepoVersion = file.GetFullVersion() 76 | p.HasInstallScript = false 77 | 78 | for n, v := range file.GetArrayValues() { 79 | switch n { 80 | case standard.ARCH: 81 | p.Arch = v 82 | case standard.URL: 83 | p.Url = "" 84 | if len(v) > 0 { 85 | p.Url = v[0] 86 | } 87 | case standard.PROVIDES: 88 | p.Provides = v 89 | case standard.DEPENDS: 90 | p.Depends = v 91 | case standard.OPTDEPENDS: 92 | p.OptDepends = v 93 | case standard.MAKEDEPENDS: 94 | p.MakeDepends = v 95 | case standard.CONFLICTS: 96 | p.Conflicts = v 97 | case standard.REPLACES: 98 | p.Replaces = v 99 | case standard.MD5SUMS: 100 | p.ValidatedBy = "MD5" 101 | case standard.SHA1SUMS: 102 | p.ValidatedBy = "SHA-1" 103 | case standard.SHA256SUMS: 104 | p.ValidatedBy = "SHA-256" 105 | case standard.LICENSE: 106 | p.Licenses = v 107 | case standard.INSTALL: 108 | p.HasInstallScript = true 109 | } 110 | } 111 | } 112 | 113 | func (p *Package) updateFromPackage(p2 Package) { 114 | p.RepoVersion = p2.RepoVersion 115 | p.Arch = p2.Arch 116 | p.Url = p2.Url 117 | p.Provides = p2.Provides 118 | p.Depends = p2.Depends 119 | p.OptDepends = p2.OptDepends 120 | p.MakeDepends = p2.MakeDepends 121 | p.Conflicts = p2.Conflicts 122 | p.Replaces = p2.Replaces 123 | p.ValidatedBy = p2.ValidatedBy 124 | p.HasInstallScript = p2.HasInstallScript 125 | p.Licenses = p2.Licenses 126 | } 127 | 128 | // String returns the string representation of a package. 129 | func (p Package) String() string { 130 | var w strings.Builder 131 | fmt.Fprint( 132 | &w, 133 | color.Magenta.Colorize("kcp/"), 134 | color.NoColor.Colorize(p.Name), 135 | " ", 136 | color.Green.Colorize(p.RepoVersion), 137 | ) 138 | 139 | if p.LocalVersion != "" { 140 | fmt.Fprint(&w, " ") 141 | if p.LocalVersion == p.RepoVersion { 142 | fmt.Fprint(&w, color.Cyan.Colorize(common.Tr(labelInstalled))) 143 | } else { 144 | fmt.Fprint(&w, color.Cyan.Format(common.Tr(labelInstalledVersion), p.LocalVersion)) 145 | } 146 | } 147 | 148 | fmt.Fprintln(&w, color.Blue.Format(" (%d)", p.Stars)) 149 | fmt.Fprint(&w, "\t", p.Description) 150 | 151 | return w.String() 152 | } 153 | 154 | // Detail returns detailled informations of the package. 155 | func (p Package) Detail() string { 156 | labels, values := make([]string, 14), make([]string, 14) 157 | 158 | labels[0], values[0] = common.Tr(labelName), p.Name 159 | labels[1], values[1] = common.Tr(labelVersion), p.RepoVersion 160 | labels[2], values[2] = common.Tr(labelDescription), p.Description 161 | labels[3], values[3] = common.Tr(labelArch), strings.Join(p.Arch, " ") 162 | labels[4], values[4] = common.Tr(labelUrl), p.Url 163 | labels[5], values[5] = common.Tr(labelLicenses), strings.Join(p.Licenses, " ") 164 | labels[6], values[6] = common.Tr(labelProvides), strings.Join(p.Provides, " ") 165 | labels[7], values[7] = common.Tr(labelDepends), strings.Join(p.Depends, " ") 166 | labels[8], values[8] = common.Tr(labelMakeDepends), strings.Join(p.MakeDepends, " ") 167 | labels[9], values[9] = common.Tr(labelOptDepends), strings.Join(p.OptDepends, " ") 168 | labels[10], values[10] = common.Tr(labelConflicts), strings.Join(p.Conflicts, " ") 169 | labels[11], values[11] = common.Tr(labelReplaces), strings.Join(p.Replaces, " ") 170 | labels[12], values[12] = common.Tr(labelInstall), common.Tr(labelNo) 171 | 172 | if p.HasInstallScript { 173 | values[12] = common.Tr(labelYes) 174 | } 175 | labels[13], values[13] = common.Tr(labelValidatedBy), p.ValidatedBy 176 | 177 | s := 0 178 | for _, l := range labels { 179 | sl := utf8.RuneCountInString(l) 180 | if sl > s { 181 | s = sl 182 | } 183 | } 184 | 185 | result := make([]string, len(labels)) 186 | for i, l := range labels { 187 | v := values[i] 188 | if v == "" { 189 | v = "--" 190 | } 191 | sep := strings.Repeat(" ", s-utf8.RuneCountInString(l)) 192 | result[i] = fmt.Sprintf("%s%s : %s", l, sep, v) 193 | } 194 | 195 | return strings.Join(result, "\n") 196 | } 197 | 198 | // Clone clone the git repo corresponding to the package 199 | // on the given dir. 200 | // if ssh it clones using ssh. 201 | func (p Package) Clone(dir string, ssh bool) (fullDir string, err error) { 202 | fullDir = path.Join(dir, p.Name) 203 | if common.FileExists(fullDir) { 204 | err = errors.New(common.Tr(errPathExists, fullDir)) 205 | return 206 | } 207 | if err = os.Chdir(dir); err != nil { 208 | return 209 | } 210 | 211 | url := p.CloneUrl 212 | if ssh { 213 | url = p.SshUrl 214 | } 215 | 216 | err = common.LaunchCommand("git", "clone", url) 217 | 218 | return 219 | } 220 | 221 | type FilterFunc func(Package) bool 222 | type SorterFunc func(Package, Package) int 223 | 224 | // NewFilter aggregates multiple filter funcs in one filter func. 225 | func NewFilter(filters ...FilterFunc) FilterFunc { 226 | return func(p Package) bool { 227 | for _, f := range filters { 228 | if !f(p) { 229 | return false 230 | } 231 | } 232 | return true 233 | } 234 | } 235 | 236 | // NewSorter aggregates multiple sort funcs in one sort func. 237 | func NewSorter(sorters ...SorterFunc) SorterFunc { 238 | return func(p1, p2 Package) int { 239 | for _, s := range sorters { 240 | if c := s(p1, p2); c != 0 { 241 | return c 242 | } 243 | } 244 | return 0 245 | } 246 | } 247 | 248 | // Packages is a list of packages. 249 | type Packages []Package 250 | 251 | // ToSet returns a set of the list. 252 | func (pl Packages) ToSet() PackageSet { 253 | ps := make(PackageSet) 254 | 255 | for _, p := range pl { 256 | ps[p.Name] = p 257 | } 258 | 259 | return ps 260 | } 261 | 262 | // Push append the given entries to the list. 263 | func (pl *Packages) Push(packages ...Package) { 264 | *pl = append(*pl, packages...) 265 | } 266 | 267 | // Remove removes the given entries from the list. 268 | func (pl *Packages) Remove(packages ...Package) { 269 | ps := Packages(packages).ToSet() 270 | np := pl.Filter(func(p Package) bool { return !ps.Contains(p.Name) }) 271 | *pl = np 272 | } 273 | 274 | // Filter returns a list which contains all packages 275 | // matching the filters. 276 | func (pl Packages) Filter(filters ...FilterFunc) (result Packages) { 277 | f := NewFilter(filters...) 278 | 279 | for _, p := range pl { 280 | if f(p) { 281 | result.Push(p) 282 | } 283 | } 284 | 285 | return result 286 | } 287 | 288 | // Sort sorts the list using the given criterias 289 | // and returns it. 290 | func (pl Packages) Sort(sorters ...SorterFunc) Packages { 291 | s := NewSorter(sorters...) 292 | less := func(i, j int) bool { 293 | return s(pl[i], pl[j]) <= 0 294 | } 295 | 296 | sort.Slice(pl, less) 297 | 298 | return pl 299 | } 300 | 301 | // Get returns the package with the given name. 302 | // If no package found, ok is false. 303 | func (pl Packages) Get(name string) (result Package, ok bool) { 304 | for _, p := range pl { 305 | if ok = p.Name == name; ok { 306 | return p, ok 307 | } 308 | } 309 | 310 | return 311 | } 312 | 313 | // SearchBroken returns packages which have at least 314 | // one depend missing on the offical repo or on KCP. 315 | func (pl Packages) SearchBroken() (broken []string) { 316 | available := make(map[string]bool) 317 | for _, p := range pl { 318 | available[p.Name] = true 319 | } 320 | for e := range common.Exceptions { 321 | available[e] = true 322 | } 323 | 324 | cleanDep := func(d string) string { 325 | for _, s := range []string{">", "<", "=", ":"} { 326 | if i := strings.Index(d, s); i > 0 { 327 | d = d[:i] 328 | } 329 | } 330 | return strings.TrimSpace(d) 331 | } 332 | 333 | checkBroken := func(d string) { 334 | if available[d] { 335 | return 336 | } 337 | result, _ := common.GetOutputCommand("pacman", "-Si", d) 338 | if len(result) == 0 { 339 | broken = append(broken, d) 340 | } 341 | } 342 | 343 | routines := defaultRoutines 344 | done := make(map[string]bool) 345 | buffer := make(chan string, len(pl)) 346 | var wg sync.WaitGroup 347 | wg.Add(routines) 348 | 349 | for i := 0; i < routines; i++ { 350 | go (func() { 351 | defer wg.Done() 352 | for { 353 | d, ok := <-buffer 354 | if !ok { 355 | return 356 | } 357 | checkBroken(d) 358 | } 359 | })() 360 | } 361 | 362 | for _, p := range pl { 363 | for _, depends := range [][]string{p.Depends, p.OptDepends, p.MakeDepends} { 364 | for _, d := range depends { 365 | d = cleanDep(d) 366 | if !done[d] && len(d) > 0 { 367 | done[d] = true 368 | buffer <- d 369 | } 370 | } 371 | } 372 | } 373 | 374 | close(buffer) 375 | wg.Wait() 376 | 377 | return 378 | } 379 | 380 | // Names returns the list of the packages’ names. 381 | func (pl Packages) Names() []string { 382 | names := make([]string, len(pl)) 383 | 384 | for i, p := range pl { 385 | names[i] = p.Name 386 | } 387 | 388 | return names 389 | } 390 | 391 | // String returns the string representation of the list. 392 | func (pl Packages) String() string { 393 | out := make([]string, len(pl)) 394 | 395 | for i, p := range pl { 396 | out[i] = p.String() 397 | } 398 | 399 | return strings.Join(out, "\n") 400 | } 401 | 402 | // PackageSet is list of packages mapped by name 403 | type PackageSet map[string]Package 404 | 405 | // Contains checks if the set contains a package with the given name. 406 | func (ps PackageSet) Contains(name string) (ok bool) { 407 | _, ok = ps[name] 408 | 409 | return 410 | } 411 | 412 | // Push adds all given packages to the set. 413 | func (ps PackageSet) Push(packages ...Package) { 414 | for _, p := range packages { 415 | ps[p.Name] = p 416 | } 417 | } 418 | 419 | // Remove removes all packages with the given names. 420 | func (ps PackageSet) Remove(names ...string) { 421 | for _, n := range names { 422 | delete(ps, n) 423 | } 424 | } 425 | 426 | // ToList converts the set to a list of packages. 427 | func (ps PackageSet) ToList() (pl Packages) { 428 | for _, p := range ps { 429 | pl.Push(p) 430 | } 431 | 432 | return 433 | } 434 | 435 | // Filter returns a list which contains all packages 436 | // matching the filters. 437 | func (ps PackageSet) Filter(filters ...FilterFunc) (pl Packages) { 438 | f := NewFilter(filters...) 439 | 440 | for _, p := range ps { 441 | if f(p) { 442 | pl.Push(p) 443 | } 444 | } 445 | 446 | return 447 | } 448 | 449 | // Sort sorts the packages using the given criterias 450 | // and returns them. 451 | func (ps PackageSet) Sort(sorters ...SorterFunc) Packages { 452 | return ps.ToList().Sort(sorters...) 453 | } 454 | 455 | // Names returns the list of the packages’ names. 456 | func (ps PackageSet) Names() []string { 457 | var names []string 458 | 459 | for n := range ps { 460 | names = append(names, n) 461 | } 462 | 463 | return names 464 | } 465 | 466 | func SortByName(p1, p2 Package) int { 467 | return strings.Compare(p1.Name, p2.Name) 468 | } 469 | 470 | func SortByStar(p1, p2 Package) int { 471 | c := p2.Stars - p1.Stars 472 | 473 | if c < 0 { 474 | return -1 475 | } else if c > 0 { 476 | return 1 477 | } 478 | return c 479 | } 480 | 481 | func FilterInstalled(p Package) bool { 482 | return p.LocalVersion != "" 483 | } 484 | 485 | func FilterOutdated(p Package) bool { 486 | return FilterInstalled(p) && p.LocalVersion != p.RepoVersion 487 | } 488 | 489 | func FilterStarred(p Package) bool { 490 | return p.Stars > 0 491 | } 492 | -------------------------------------------------------------------------------- /database/repository.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "os" 8 | 9 | "github.com/bvaudour/kcp/color" 10 | ) 11 | 12 | // Repository is a connector to access to the repos infos 13 | // of a github organization. 14 | type Repository struct { 15 | organization string 16 | ctx 17 | } 18 | 19 | // NewRepository creates a connector to an organization. 20 | // If optional user and password are given, requests are done 21 | // with authentification in order to have a better rate limit. 22 | func NewRepository(organization string, opt ...string) Repository { 23 | var user, password string 24 | if len(opt) >= 2 { 25 | user, password = opt[0], opt[1] 26 | } 27 | return Repository{ 28 | organization: organization, 29 | ctx: ctx{ 30 | username: user, 31 | password: password, 32 | accept: acceptHeader, 33 | }, 34 | } 35 | } 36 | 37 | // CountPublicRepos returns the number of repositories 38 | // owned by the organization. 39 | func (r Repository) CountPublicRepos() (nb int, err error) { 40 | var buf io.Reader 41 | 42 | if buf, err = execRequest(baseOrganizationURL, r.ctx, r.organization); err == nil { 43 | var result struct { 44 | PublicRepos int `json:"public_repos"` 45 | } 46 | dec := json.NewDecoder(buf) 47 | if err = dec.Decode(&result); err == nil { 48 | nb = result.PublicRepos 49 | } 50 | } 51 | 52 | return 53 | } 54 | 55 | // CountPages gives the number of pages of repos and of repos in the organization 56 | func (r Repository) CountPages(limit int) (pages, repos int, err error) { 57 | if limit == 0 { 58 | panic("Limit should be > 0") 59 | } 60 | 61 | if repos, err = r.CountPublicRepos(); err == nil && repos > 0 { 62 | pages = (repos-1)/limit + 1 63 | } 64 | 65 | return 66 | } 67 | 68 | // GetPage returns the remote packages’ infos on 69 | // the repositories list page of the organization. 70 | func (r *Repository) GetPage(page, limit int, debug ...bool) (packages Packages, err error) { 71 | var buf io.Reader 72 | 73 | buf, err = execRequest(baseReposURL, r.ctx, r.organization, page, limit) 74 | if err == nil { 75 | dec := json.NewDecoder(buf) 76 | err = dec.Decode(&packages) 77 | } 78 | 79 | if len(debug) > 0 && debug[0] { 80 | var t string 81 | if err == nil { 82 | t = color.Green.Colorize("[Success]") 83 | } else { 84 | t = color.Red.Format("[Error: %s]", err) 85 | } 86 | fmt.Fprintf( 87 | os.Stderr, 88 | "%s "+baseReposURL+"\n", 89 | t, 90 | r.organization, 91 | page, 92 | limit, 93 | ) 94 | } 95 | 96 | return 97 | } 98 | -------------------------------------------------------------------------------- /database/util.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/bvaudour/kcp/common" 11 | ) 12 | 13 | type ctx struct { 14 | username string 15 | password string 16 | accept string 17 | } 18 | 19 | func execRequest(url string, opt ctx, args ...any) (io.Reader, error) { 20 | request, err := http.NewRequest("GET", fmt.Sprintf(url, args...), nil) 21 | if err != nil { 22 | return nil, err 23 | } 24 | if opt.username != "" && opt.password != "" { 25 | request.SetBasicAuth(opt.username, opt.password) 26 | } 27 | if opt.accept != "" { 28 | request.Header.Set("Accept", opt.accept) 29 | } 30 | response, err := new(http.Client).Do(request) 31 | if err != nil { 32 | return nil, err 33 | } 34 | defer response.Body.Close() 35 | b, err := io.ReadAll(response.Body) 36 | if err == nil { 37 | return bytes.NewBuffer(b), nil 38 | } 39 | return bytes.NewBuffer([]byte{}), nil 40 | } 41 | 42 | func sliceToSet(sl []string) map[string]bool { 43 | out := make(map[string]bool) 44 | for _, e := range sl { 45 | out[e] = true 46 | } 47 | 48 | return out 49 | } 50 | 51 | // Counter is a counter of updated packages. 52 | type Counter struct { 53 | Updated int 54 | Deleted int 55 | Added int 56 | } 57 | 58 | // String returns the string representation of the counter 59 | func (c Counter) String() string { 60 | var out []string 61 | if c.Added > 0 { 62 | out = append(out, common.Tr(msgAdded, c.Added)) 63 | } 64 | if c.Deleted > 0 { 65 | out = append(out, common.Tr(msgDeleted, c.Deleted)) 66 | } 67 | if c.Updated > 0 { 68 | out = append(out, common.Tr(msgUpdated, c.Updated)) 69 | } 70 | 71 | return strings.Join(out, "\n") 72 | } 73 | -------------------------------------------------------------------------------- /excluded.json: -------------------------------------------------------------------------------- 1 | { 2 | "excludedStrings": [ 3 | "", 4 | "PKGBUILD", 5 | "kcp", 6 | "\u001b[1;3%dm", 7 | "\u001b[m", 8 | " (L.%d)", 9 | " (L.%d-%d)", 10 | "%s%s%s%s:\n %s\n", 11 | "[-h|-e|-v|-g[-c]" 12 | ], 13 | "excludedRegexps" : [ 14 | "^\\-\\-", 15 | "^\\-\\w$", 16 | "^.$" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /flag/consts.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "github.com/bvaudour/kcp/common" 5 | ) 6 | 7 | //Templates for errors' messages. 8 | const ( 9 | errInvalidFlag = "Invalid %s flag '%s': %s!" 10 | errInvalidValue = "Invalid value '%v': Must be a %s!" 11 | errMustBeAlternative = "Flag '%s' cannot be used with '%s'" 12 | errNeedRequirment = "Flag '%s' needs one of these flags: %v" 13 | errNoMultipleAllowed = "Flag '%s' doesn't allow multiple arguments!" 14 | errNotAllowed = "%s not allowed!" 15 | errNotEnoughArg = "Arg needed!" 16 | errUnexpectedArg = "Unexpected arg: %s" 17 | errUnexpectedChoiceArg = "Arg '%s' not in %v!" 18 | errUnexpectedFlag = "Flag '%s' is already set!" 19 | errUnknownProperty = "Unknown property: %d" 20 | errUnsupportedFlag = "Unsupported flag '%s'" 21 | 22 | typeString = "string" 23 | typeBool = "bool" 24 | 25 | flagShort = "short" 26 | flagLong = "long" 27 | flagMustBegin = "must begin with '%s'" 28 | flagTooShort = "too short" 29 | flagTooLong = "too long" 30 | 31 | usage = "Usage:" 32 | ) 33 | 34 | type Error string 35 | 36 | func (e Error) Error() string { 37 | return string(e) 38 | } 39 | 40 | func NewError(err string, args ...interface{}) error { 41 | return Error(common.Tr(err, args...)) 42 | } 43 | -------------------------------------------------------------------------------- /flag/doc.go: -------------------------------------------------------------------------------- 1 | //Package flag provides tools to parse arguments of a CLI. 2 | package flag 3 | -------------------------------------------------------------------------------- /flag/flag.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | //Flag is a descriptor of a parsing element. 10 | type Flag struct { 11 | p Properties 12 | f func(string) error 13 | used string 14 | } 15 | 16 | //Internal checkers 17 | func contains(a []string, s string) bool { 18 | for _, e := range a { 19 | if e == s { 20 | return true 21 | } 22 | } 23 | return false 24 | } 25 | func stringOf(v interface{}) (string, error) { 26 | if s, ok := v.(string); ok { 27 | return s, nil 28 | } 29 | return "", NewError(errInvalidValue, v, typeString) 30 | } 31 | func boolOf(v interface{}) (bool, error) { 32 | if b, ok := v.(bool); ok { 33 | return b, nil 34 | } 35 | return false, NewError(errInvalidValue, v, typeBool) 36 | } 37 | func isShortFlag(v interface{}) error { 38 | n, e := stringOf(v) 39 | if e != nil { 40 | return e 41 | } 42 | switch { 43 | case len(n) == 0: 44 | return nil 45 | case len(n) == 1: 46 | return NewError(errInvalidFlag, flagShort, n, flagTooShort) 47 | case len(n) > 2: 48 | return NewError(errInvalidFlag, flagShort, n, flagTooLong) 49 | case n[0] != '-' || n[1] == '-': 50 | return NewError(errInvalidFlag, flagShort, n, fmt.Sprintf(flagMustBegin, "-")) 51 | } 52 | return nil 53 | } 54 | func isLongFlag(v interface{}) error { 55 | n, e := stringOf(v) 56 | if e != nil { 57 | return e 58 | } 59 | switch { 60 | case len(n) == 0: 61 | return nil 62 | case len(n) < 3: 63 | return NewError(errInvalidFlag, flagLong, n, flagTooShort) 64 | case n[0:2] != "--": 65 | return NewError(errInvalidFlag, flagLong, n, fmt.Sprintf(flagMustBegin, "--")) 66 | default: 67 | return nil 68 | } 69 | } 70 | 71 | //Set modifies the given property with the given value. 72 | func (f *Flag) Set(k PropertyType, v interface{}) error { 73 | switch k { 74 | case Short: 75 | if e := isShortFlag(v); e != nil { 76 | return e 77 | } 78 | case Long: 79 | if e := isLongFlag(v); e != nil { 80 | return e 81 | } 82 | } 83 | return f.p.Set(k, v) 84 | } 85 | 86 | //Get returns the value of the needed property. 87 | func (f *Flag) Get(k PropertyType) interface{} { 88 | return f.p.Value(k) 89 | } 90 | 91 | //GetString returns the string representation of the needed property. 92 | func (f *Flag) GetString(k PropertyType) string { 93 | return f.p.ValueString(k) 94 | } 95 | 96 | //GetBool returns the boolean representation of the needed property. 97 | func (f *Flag) GetBool(k PropertyType) bool { 98 | return f.p.ValueBool(k) 99 | } 100 | 101 | //Short returns the value of the short flag. 102 | func (f *Flag) Short() string { 103 | return f.GetString(Short) 104 | } 105 | 106 | //Long returns the value of the long flag. 107 | func (f *Flag) Long() string { 108 | return f.GetString(Long) 109 | } 110 | 111 | //Description returns the description of the flag. 112 | func (f *Flag) Description() string { 113 | return f.GetString(Description) 114 | } 115 | 116 | //ValueName returns the value' name of the flag. 117 | func (f *Flag) ValueName() string { 118 | return f.GetString(ValueName) 119 | } 120 | 121 | //DefaultValue returns the default value of the flag. 122 | func (f *Flag) DefaultValue() string { 123 | return f.GetString(DefaultValue) 124 | } 125 | 126 | //AllowMultipleValues returns true if the flag accepts one or more values. 127 | func (f *Flag) AllowMultipleValues() bool { 128 | return f.GetBool(MultipleValues) 129 | } 130 | 131 | //Hidden returns true if the flag shouldn't appear in the help. 132 | func (f *Flag) Hidden() bool { 133 | return f.GetBool(Hidden) 134 | } 135 | 136 | //Parse functions 137 | func parseBool(v *bool) func(string) error { 138 | return func(s string) error { 139 | if s != "" { 140 | return NewError(errUnexpectedArg, s) 141 | } 142 | *v = true 143 | return nil 144 | } 145 | } 146 | func parseString(v *string) func(string) error { 147 | return func(s string) error { 148 | if s == "" { 149 | return NewError(errNotEnoughArg) 150 | } 151 | *v = s 152 | return nil 153 | } 154 | } 155 | func parseChoice(v *string, c []string) func(string) error { 156 | return func(s string) error { 157 | switch { 158 | case s == "": 159 | return NewError(errNotEnoughArg) 160 | case !contains(c, s): 161 | return NewError(errUnexpectedChoiceArg, s, c) 162 | default: 163 | *v = s 164 | return nil 165 | } 166 | } 167 | } 168 | func parseArray(v *[]string) func(string) error { 169 | return func(s string) error { 170 | if s != "" { 171 | *v = append(*v, s) 172 | } 173 | return nil 174 | } 175 | } 176 | func parseInt(v *int) func(string) error { 177 | return func(s string) error { 178 | var e error 179 | *v, e = strconv.Atoi(s) 180 | return e 181 | } 182 | } 183 | 184 | //Flag initialization 185 | func initFlag(s, l, desc string, cb func(string) error) (f *Flag, e error) { 186 | f = new(Flag) 187 | f.p = FlagProps() 188 | if e = f.Set(Short, s); e != nil { 189 | return 190 | } 191 | if e = f.Set(Long, l); e != nil { 192 | return 193 | } 194 | f.Set(Description, desc) 195 | f.f = cb 196 | return 197 | } 198 | 199 | //NewBoolFlag returns a new flag which doesn't accept args and a pointer to its value. 200 | func NewBoolFlag(s, l, desc string) (f *Flag, v *bool, e error) { 201 | v = new(bool) 202 | f, e = initFlag(s, l, desc, parseBool(v)) 203 | return 204 | } 205 | 206 | //NewStringFlag returns a new flag which accepts a string arg and a pointer to its value. 207 | func NewStringFlag(s, l, desc, vn, df string) (f *Flag, v *string, e error) { 208 | v = new(string) 209 | f, e = initFlag(s, l, desc, parseString(v)) 210 | if e != nil { 211 | return 212 | } 213 | if vn == "" { 214 | vn = "ARG" 215 | } 216 | f.Set(ValueName, vn) 217 | f.Set(DefaultValue, df) 218 | return 219 | } 220 | 221 | //NewChoiceFlag returns a new flag which accepts arg among a list of choices, and a pointer to its value. 222 | func NewChoiceFlag(s, l, desc, df string, choices []string) (f *Flag, v *string, e error) { 223 | if df != "" && !contains(choices, df) { 224 | e = NewError(errUnexpectedChoiceArg, df, choices) 225 | return 226 | } 227 | v = new(string) 228 | f, e = initFlag(s, l, desc, parseChoice(v, choices)) 229 | if e != nil { 230 | return 231 | } 232 | f.Set(ValueName, "["+strings.Join(choices, "|")+"]") 233 | f.Set(DefaultValue, df) 234 | return 235 | } 236 | 237 | //NewArrayFlag returns a new flag which accepts multiple string args, and a pointer to its value. 238 | func NewArrayFlag(s, l, desc, vn string) (f *Flag, v *[]string, e error) { 239 | *v = make([]string, 0, 1) 240 | f, e = initFlag(s, l, desc, parseArray(v)) 241 | if e != nil { 242 | return 243 | } 244 | if vn == "" { 245 | vn = "ARG..." 246 | } 247 | f.Set(ValueName, vn) 248 | return 249 | } 250 | 251 | //NewIntFlag returns a new flag which accepts an int arg and a pointer to its value. 252 | func NewIntFlag(s, l, desc, vn string, df int) (f *Flag, v *int, e error) { 253 | v = new(int) 254 | *v = df 255 | f, e = initFlag(s, l, desc, parseInt(v)) 256 | if e != nil { 257 | return 258 | } 259 | if vn == "" { 260 | vn = "ARG" 261 | } 262 | f.Set(ValueName, vn) 263 | return 264 | } 265 | 266 | // Group represents a selected flag among a group of flags. 267 | type Group struct{ selected string } 268 | -------------------------------------------------------------------------------- /flag/parser.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "strings" 8 | "time" 9 | "unicode/utf8" 10 | ) 11 | 12 | //Parser is a structure containing definitions to parse arguments of a CLI. 13 | type Parser struct { 14 | p Properties 15 | flags []*Flag 16 | args map[string]*Flag 17 | groups map[*Group][]*Flag 18 | fgroups map[*Flag][]*Group 19 | requires map[*Flag][]*Flag 20 | pre []string 21 | post []string 22 | } 23 | 24 | //Set modifies the given property with the given value. 25 | func (p *Parser) Set(k PropertyType, v interface{}) error { 26 | return p.p.Set(k, v) 27 | } 28 | 29 | //GetString returns the string representation of the needed property 30 | func (p *Parser) GetString(k PropertyType) string { 31 | return p.p.ValueString(k) 32 | } 33 | 34 | //GetBool returns the boolean representation of the needed property. 35 | func (p *Parser) GetBool(k PropertyType) bool { 36 | return p.p.ValueBool(k) 37 | } 38 | 39 | //Name returns the name of the application. 40 | func (p *Parser) Name() string { 41 | return p.GetString(Name) 42 | } 43 | 44 | //Description returns the description of the application. 45 | func (p *Parser) Description() string { 46 | return p.GetString(Description) 47 | } 48 | 49 | //LongDescription returns the long description of the application. 50 | func (p *Parser) LongDescription() string { 51 | return p.GetString(LongDescription) 52 | } 53 | 54 | //Synopsis returns the synopsis of the application. 55 | func (p *Parser) Synopsis() string { 56 | return p.GetString(Synopsis) 57 | } 58 | 59 | //Author returns the author's name of the application. 60 | func (p *Parser) Author() string { 61 | return p.GetString(Author) 62 | } 63 | 64 | //Version returns the version of the application. 65 | func (p *Parser) Version() string { 66 | return p.GetString(Version) 67 | } 68 | 69 | //AllowPreArgs returns true if the parser accepts anonymous args before flags. 70 | func (p *Parser) AllowPreArgs() bool { 71 | return p.GetBool(AllowPreArs) 72 | } 73 | 74 | //AllowPostArgs returns true if the parser accepts anonymous args after flags. 75 | func (p *Parser) AllowPostArgs() bool { 76 | return p.GetBool(AllowPostArgs) 77 | } 78 | 79 | //GetFlag returns the flag where the given name is defined. 80 | func (p *Parser) GetFlag(name string) *Flag { 81 | f, _ := p.args[name] 82 | return f 83 | } 84 | 85 | //ContainsFlag returns true if a flag with the given name is defined. 86 | func (p *Parser) ContainsFlag(name string) bool { 87 | _, ok := p.args[name] 88 | return ok 89 | } 90 | 91 | //Add appends a flags to the parser. 92 | //If a flag with same name(s) is (are) defined, return a non nil error. 93 | func (p *Parser) Add(f *Flag) error { 94 | long, short := f.Long(), f.Short() 95 | if p.ContainsFlag(long) { 96 | return NewError(errUnexpectedFlag, long) 97 | } 98 | if p.ContainsFlag(short) { 99 | return NewError(errUnexpectedFlag, short) 100 | } 101 | p.flags = append(p.flags, f) 102 | p.fgroups[f] = make([]*Group, 0) 103 | if long != "" { 104 | p.args[long] = f 105 | } 106 | if short != "" { 107 | p.args[short] = f 108 | } 109 | return nil 110 | } 111 | 112 | //AddAll appends given flags to the parser. 113 | //If a flag cannot be added, return an error. 114 | func (p *Parser) AddAll(flags ...*Flag) (err []error) { 115 | for _, f := range flags { 116 | if e := p.Add(f); e != nil { 117 | err = append(err, e) 118 | } 119 | } 120 | return 121 | } 122 | 123 | //NewParser returns an new parser initialized with the description and the version of the application. 124 | func NewParser(description, version string) *Parser { 125 | p := &Parser{ 126 | p: ParserProps(), 127 | args: make(map[string]*Flag), 128 | groups: make(map[*Group][]*Flag), 129 | fgroups: make(map[*Flag][]*Group), 130 | requires: make(map[*Flag][]*Flag), 131 | } 132 | p.Set(Description, description) 133 | p.Set(Version, version) 134 | return p 135 | } 136 | 137 | //Bool appends a new boolean flag and returns a pointer to its value. 138 | func (p *Parser) Bool(s, l, desc string) (*bool, error) { 139 | f, v, e := NewBoolFlag(s, l, desc) 140 | if e == nil { 141 | e = p.Add(f) 142 | } 143 | return v, e 144 | } 145 | 146 | //String appends a new string flag and returns a pointer to its value. 147 | func (p *Parser) String(s, l, desc, vn, df string) (*string, error) { 148 | f, v, e := NewStringFlag(s, l, desc, vn, df) 149 | if e == nil { 150 | e = p.Add(f) 151 | } 152 | return v, e 153 | } 154 | 155 | //Choice appends a new choice flag and returns a pointer to its value. 156 | func (p *Parser) Choice(s, l, desc, df string, choices []string) (*string, error) { 157 | f, v, e := NewChoiceFlag(s, l, desc, df, choices) 158 | if e == nil { 159 | e = p.Add(f) 160 | } 161 | return v, e 162 | } 163 | 164 | //Array appends a new multiple string flag and returns a pointer to its value. 165 | func (p *Parser) Array(s, l, desc, vn string) (*[]string, error) { 166 | f, v, e := NewArrayFlag(s, l, desc, vn) 167 | if e == nil { 168 | e = p.Add(f) 169 | } 170 | return v, e 171 | } 172 | 173 | //Int appends a new multiple int flag and returns a pointer to its value. 174 | func (p *Parser) Int(s, l, desc, vn string, df int) (*int, error) { 175 | f, v, e := NewIntFlag(s, l, desc, vn, df) 176 | if e == nil { 177 | e = p.Add(f) 178 | } 179 | return v, e 180 | } 181 | 182 | //Group groups all flags with given names and returns the number of flags in the group. 183 | //Only one flag of a group can be used at same time. 184 | func (p *Parser) Group(names ...string) int { 185 | g := new(Group) 186 | var flags []*Flag 187 | for _, n := range names { 188 | f := p.GetFlag(n) 189 | if f != nil { 190 | flags = append(flags, f) 191 | p.fgroups[f] = append(p.fgroups[f], g) 192 | } 193 | } 194 | p.groups[g] = flags 195 | return len(flags) 196 | } 197 | 198 | //Require defines all flags required by the flag with name name0 and returns the number of required flags. 199 | func (p *Parser) Require(name0 string, names ...string) int { 200 | f0 := p.GetFlag(name0) 201 | if f0 == nil { 202 | return 0 203 | } 204 | flags := make([]*Flag, 0) 205 | for _, n := range names { 206 | f := p.GetFlag(n) 207 | if f != nil { 208 | flags = append(flags, f) 209 | } 210 | } 211 | p.requires[f0] = flags 212 | return len(flags) 213 | } 214 | 215 | func format(args []string) []string { 216 | out := make([]string, 0, len(args)) 217 | for _, a := range args { 218 | a = strings.TrimSpace(a) 219 | a = strings.Trim(a, "=") 220 | l := len(a) 221 | switch { 222 | case l == 0: 223 | continue 224 | case l > 2 && a[0] == '-': 225 | if a[1] == '-' { 226 | i := strings.Index(a, "=") 227 | if i < 0 { 228 | out = append(out, a) 229 | } else { 230 | out = append(out, a[:i], a[i+1:]) 231 | } 232 | } else { 233 | for _, c := range a[1:] { 234 | out = append(out, fmt.Sprintf("-%c", c)) 235 | } 236 | } 237 | default: 238 | out = append(out, a) 239 | } 240 | } 241 | return out 242 | } 243 | 244 | //Parse parses the givens arguments according to the definition of the parser. 245 | func (p *Parser) Parse(args []string) error { 246 | if len(args) == 0 { 247 | return NewError(errNotEnoughArg) 248 | } 249 | _, app := path.Split(args[0]) 250 | p.Set(Name, app) 251 | args = format(args[1:]) 252 | if contains(args, "--create-manpage") { 253 | p.PrintMan() 254 | os.Exit(0) 255 | } 256 | if len(args) == 0 { 257 | return nil 258 | } 259 | 260 | // Get Flags 261 | l := len(args) 262 | idxF, flags := make([]int, 0, l), make([]*Flag, 0, l) 263 | for i, a := range args { 264 | switch { 265 | case a[0] != '-': 266 | continue 267 | case !p.ContainsFlag(a): 268 | return NewError(errUnsupportedFlag, a) 269 | default: 270 | f := p.GetFlag(a) 271 | f.used = a 272 | flags, idxF = append(flags, f), append(idxF, i) 273 | } 274 | } 275 | 276 | // Extract Pre-args 277 | switch { 278 | case len(idxF) == 0: 279 | switch { 280 | case p.AllowPreArgs(): 281 | p.pre = args 282 | case p.AllowPostArgs(): 283 | p.post = args 284 | default: 285 | return NewError(errNotAllowed, "Pre-args") 286 | } 287 | case p.AllowPreArgs(): 288 | p.pre = args[:idxF[0]] 289 | case idxF[0] == 0: 290 | default: 291 | return NewError(errNotAllowed, "Pre-args") 292 | } 293 | 294 | // Parse args 295 | l = len(idxF) - 1 296 | for i, f := range flags { 297 | a := args[idxF[i]] 298 | if grps, ok := p.fgroups[f]; ok { 299 | for _, g := range grps { 300 | if g.selected != "" && g.selected != f.Short() && g.selected != f.Long() { 301 | return NewError(errMustBeAlternative, a, g.selected) 302 | } 303 | g.selected = a 304 | } 305 | } 306 | i1 := idxF[i] + 1 307 | i2 := len(args) 308 | if i != l { 309 | i2 = idxF[i+1] 310 | } 311 | switch { 312 | case i1 == i2: 313 | if e := f.f(f.DefaultValue()); e != nil { 314 | return e 315 | } 316 | case i1+1 == i2: 317 | if e := f.f(args[i1]); e != nil { 318 | return e 319 | } 320 | case f.AllowMultipleValues(): 321 | for _, an := range args[i1:i2] { 322 | if e := f.f(an); e != nil { 323 | return e 324 | } 325 | } 326 | case i != l: 327 | return NewError(errNoMultipleAllowed, a) 328 | default: 329 | if e := f.f(args[i1]); e != nil { 330 | return e 331 | } 332 | if p.AllowPostArgs() { 333 | p.post = args[i1+1:] 334 | } else { 335 | return NewError(errNotAllowed, "Post-args") 336 | } 337 | } 338 | } 339 | 340 | // Check Requirements 341 | for f0, req := range p.requires { 342 | if f0.used == "" { 343 | continue 344 | } 345 | ok := false 346 | str := make([]string, 0, len(req)*2) 347 | for _, r := range req { 348 | if r.used != "" { 349 | ok = true 350 | break 351 | } 352 | str = append(str, r.Short(), r.Long()) 353 | } 354 | if !ok { 355 | return NewError(errNeedRequirment, f0.used, str) 356 | } 357 | } 358 | return nil 359 | } 360 | 361 | //GetPreArgs returns anonymous args before flags. 362 | func (p *Parser) GetPreArgs() []string { 363 | return p.pre 364 | } 365 | 366 | //GetPostArgs returns anonymous args after flags. 367 | func (p *Parser) GetPostArgs() []string { 368 | return p.post 369 | } 370 | 371 | //Manpage creation 372 | func (f *Flag) man() string { 373 | b := new(strings.Builder) 374 | short, long := f.Short(), f.Long() 375 | switch { 376 | case short == "": 377 | fmt.Fprintf(b, `\-\-%s`, long[2:]) 378 | case long == "": 379 | fmt.Fprintf(b, `\-%s`, short[1:]) 380 | default: 381 | fmt.Fprintf(b, `\-%s|\-\-%s`, short[1:], long[2:]) 382 | } 383 | if vn := f.ValueName(); vn != "" { 384 | if dv := f.DefaultValue(); dv == "" { 385 | fmt.Fprintf(b, " [%s]", vn) 386 | } else { 387 | fmt.Fprintf(b, " %s", vn) 388 | } 389 | } 390 | return b.String() 391 | } 392 | func (p *Parser) synopsis() string { 393 | b := new(strings.Builder) 394 | if p.AllowPreArgs() { 395 | fmt.Fprint(b, " [BARGS...]") 396 | } 397 | for _, f := range p.flags { 398 | fmt.Fprintf(b, " [%s]", f.man()) 399 | } 400 | if p.AllowPostArgs() { 401 | fmt.Fprint(b, " [EARGS...]") 402 | } 403 | return b.String() 404 | } 405 | func (p *Parser) description() string { 406 | b := new(strings.Builder) 407 | for _, l := range strings.Split(p.LongDescription(), "\n") { 408 | if l == "" { 409 | fmt.Fprintln(b, ".PP") 410 | } else { 411 | fmt.Fprintln(b, l) 412 | } 413 | } 414 | return b.String() 415 | } 416 | 417 | //PrinMan prints the Manpage to the standard output according to the parser's definition. 418 | func (p *Parser) PrintMan() { 419 | app, version, summ, author := p.Name(), p.Version(), p.Description(), p.Author() 420 | fmt.Printf(`.TH "%s" 1 "%s" "%s" ""`, app, time.Now().Format("January 2, 2006"), version) 421 | fmt.Println() 422 | fmt.Println(".SH NAME") 423 | fmt.Println(app, `\-`, summ) 424 | fmt.Println(".SH SYNOPSIS") 425 | fmt.Println(app, p.synopsis()) 426 | fmt.Println(".SH DESCRIPTION") 427 | fmt.Println(p.description()) 428 | fmt.Println(".SH OPTIONS") 429 | for _, f := range p.flags { 430 | if f.Hidden() { 431 | continue 432 | } 433 | fmt.Println(".TP") 434 | fmt.Println(f.man()) 435 | fmt.Printf("\t%s\n", f.Description()) 436 | } 437 | if author != "" { 438 | fmt.Printf(".SH AUTHOR\n%s\n", author) 439 | } 440 | } 441 | 442 | //PrintVersion displays the version of the application. 443 | func (p *Parser) PrintVersion() { fmt.Println(p.Name(), p.Version()) } 444 | 445 | //PrintHelp displays the help of the application. 446 | func (p *Parser) PrintHelp() { 447 | // Usage 448 | a, s := p.Name(), p.Synopsis() 449 | if s == "" { 450 | s = "[OPTIONS] [ARGS]" 451 | if p.AllowPreArgs() { 452 | s = fmt.Sprintf("[BARGS...] %s", s) 453 | } 454 | if p.AllowPostArgs() { 455 | s = fmt.Sprintf("%s [EARGS...]", s) 456 | } 457 | } 458 | fmt.Println(usage, a, s) 459 | fmt.Println(p.Description()) 460 | fmt.Println("\n\033[1;31mOptions\033[m") 461 | for _, f := range p.flags { 462 | if f.Hidden() { 463 | continue 464 | } 465 | sf, lf, vn, dv, d := f.Short(), f.Long(), f.ValueName(), f.DefaultValue(), f.Description() 466 | var flags, args string 467 | switch { 468 | case lf == "": 469 | flags = fmt.Sprintf("%s ", sf) 470 | case sf == "": 471 | flags = fmt.Sprintf(" %s", lf) 472 | default: 473 | flags = fmt.Sprintf("%s, %s", sf, lf) 474 | } 475 | if vn != "" { 476 | args = vn 477 | if dv != "" { 478 | args = fmt.Sprintf("[%s]", args) 479 | } 480 | if lf != "" { 481 | args = fmt.Sprintf(" %s", args) 482 | } 483 | args = fmt.Sprintf("%s ", args) 484 | } 485 | i := 2 + utf8.RuneCountInString(flags) + utf8.RuneCountInString(args) 486 | if i > 28 { 487 | d = "\n" + d 488 | } else { 489 | d = strings.Repeat(" ", 30-i) + d 490 | } 491 | fmt.Printf(" \033[1m%s\033[1;33m%s\033[1;34m%s\033[m\n", flags, args, d) 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /flag/properties.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | //Property is a type of property. 9 | type PropertyType int 10 | 11 | const ( 12 | Unknown PropertyType = iota 13 | Name 14 | Short 15 | Long 16 | Description 17 | LongDescription 18 | Synopsis 19 | Author 20 | Version 21 | AllowPreArs 22 | AllowPostArgs 23 | ValueName 24 | DefaultValue 25 | MultipleValues 26 | Hidden 27 | ) 28 | 29 | var ( 30 | // Excepted property's type following key (true if boolean, false if string) 31 | kType = map[PropertyType]bool{ 32 | Name: false, 33 | Short: false, 34 | Long: false, 35 | Description: false, 36 | LongDescription: false, 37 | Synopsis: false, 38 | Author: false, 39 | Version: false, 40 | AllowPreArs: true, 41 | AllowPostArgs: true, 42 | ValueName: false, 43 | DefaultValue: false, 44 | MultipleValues: true, 45 | Hidden: true, 46 | } 47 | 48 | kParser = []PropertyType{ 49 | Name, 50 | Description, 51 | LongDescription, 52 | Synopsis, 53 | Author, 54 | Version, 55 | AllowPreArs, 56 | AllowPostArgs, 57 | } 58 | kFlag = []PropertyType{ 59 | Short, 60 | Long, 61 | Description, 62 | ValueName, 63 | DefaultValue, 64 | MultipleValues, 65 | Hidden, 66 | } 67 | ) 68 | 69 | //Property represents an internal property of a flag or a flags’ parser. 70 | type Property struct { 71 | t PropertyType 72 | vbool bool 73 | vstr string 74 | } 75 | 76 | func newProperty(t PropertyType) *Property { 77 | return &Property{t: t} 78 | } 79 | 80 | //Type returns the property’s type. 81 | func (p *Property) Type() PropertyType { 82 | return p.t 83 | } 84 | 85 | //IsBool returns true if the property’s value accept only booleans. 86 | func (p *Property) IsBool() bool { 87 | return kType[p.t] 88 | } 89 | 90 | //IsString returns true if the property’s value accept only strings. 91 | func (p *Property) IsString() bool { 92 | return !p.IsBool() 93 | } 94 | 95 | //ValueBool returns the boolean value of the property. 96 | //It returns false if the property is not boolean. 97 | func (p *Property) ValueBool() bool { 98 | return p.vbool 99 | } 100 | 101 | //ValueString returns the string value of the property. 102 | //It returns an empty string if the property is not a string. 103 | func (p *Property) ValueString() string { 104 | return p.vstr 105 | } 106 | 107 | //Value returns the value of the property which is either 108 | //boolean or string depending of the type of the property. 109 | func (p *Property) Value() interface{} { 110 | if p.IsBool() { 111 | return p.ValueBool() 112 | } 113 | return p.ValueString() 114 | } 115 | 116 | //String returns the string representation of the value’s property. 117 | func (p *Property) String() string { 118 | if p.IsString() { 119 | return p.ValueString() 120 | } 121 | return fmt.Sprint(p.ValueBool()) 122 | } 123 | 124 | //Set sets the property to the given value. 125 | //It returns an error if the given value has not the good type. 126 | func (p *Property) Set(v interface{}) (err error) { 127 | if p.IsBool() { 128 | p.vbool, err = boolOf(v) 129 | } else { 130 | p.vstr, err = stringOf(v) 131 | } 132 | return 133 | } 134 | 135 | //Properties is a set of properties 136 | type Properties map[PropertyType]*Property 137 | 138 | func newProperties(keys []PropertyType) Properties { 139 | p := make(Properties) 140 | for _, k := range keys { 141 | p[k] = newProperty(k) 142 | } 143 | return p 144 | } 145 | 146 | //ParserProps returns all properties supported by 147 | //a flags’ parser. 148 | func ParserProps() Properties { 149 | return newProperties(kParser) 150 | } 151 | 152 | //FlagProps returns all propertis supported by 153 | //a flag. 154 | func FlagProps() Properties { 155 | return newProperties(kFlag) 156 | } 157 | 158 | //Set set the given property to the given value. 159 | //It returns an error if the type of value isn’t supported 160 | //by the property’s type. 161 | func (l Properties) Set(k PropertyType, v interface{}) error { 162 | if p, ok := l[k]; ok { 163 | return p.Set(v) 164 | } 165 | return NewError(errUnknownProperty, k) 166 | } 167 | 168 | //Value returns a string or a boolan of the given property 169 | //depending of the property’s type. 170 | //It returns nil if the property doesn’t exist. 171 | func (l Properties) Value(k PropertyType) interface{} { 172 | if p, ok := l[k]; ok { 173 | return p.Value() 174 | } 175 | return nil 176 | } 177 | 178 | //ValueBool returns the boolan value of the given property. 179 | //It returns false if the property doesn’t exist or is not a boolean. 180 | func (l Properties) ValueBool(k PropertyType) bool { 181 | if p, ok := l[k]; ok { 182 | return p.ValueBool() 183 | } 184 | return false 185 | } 186 | 187 | //ValueString returns the string value of the given property. 188 | //It returns an empty string if the property doesn’t exist or is not a string. 189 | func (l Properties) ValueString(k PropertyType) string { 190 | if p, ok := l[k]; ok { 191 | return p.ValueString() 192 | } 193 | return "" 194 | } 195 | 196 | //String returns the string representation af the properties’ set. 197 | func (l Properties) String() string { 198 | m := make(map[PropertyType]string) 199 | for k, p := range l { 200 | m[k] = p.String() 201 | } 202 | json, _ := json.MarshalIndent(m, "", "\t") 203 | return string(json) 204 | } 205 | -------------------------------------------------------------------------------- /generate_pot.elv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env elvish 2 | 3 | local:workdir = cache 4 | local:alldir = all 5 | local:transdir = kcp 6 | 7 | if ?(test -e $workdir) { 8 | rm -rf $workdir 9 | } 10 | mkdir $workdir 11 | 12 | local:ignored = "^(vendor|resources|cache|build|"$workdir")" 13 | 14 | fn extract_all []{ 15 | i18n4go -c extract-strings -v --po -d . -r -o $workdir/$alldir --ignore-regexp $ignored -output-match-package 16 | } 17 | 18 | fn extract_trans []{ 19 | put **/consts.go | each [file]{ 20 | local:d = $workdir/$transdir/(dirname $file) 21 | i18n4go -c extract-strings -v --po -f $file -o $d --ignore-regexp $ignored 22 | } 23 | } 24 | 25 | fn merge_json [d]{ 26 | i18n4go -c merge-strings -d $workdir/$d -r -source-language en 27 | local:json = [] 28 | put $workdir/$d/**/en.all.json | each [f]{ 29 | local:j = (cat $f | from-json) 30 | @j = (all $j | each [e]{ dissoc $e modified }) 31 | @json = $@json $@j 32 | } 33 | put $json | to-json > $workdir/$d/kcp.json 34 | } 35 | 36 | fn convert_json [d]{ 37 | json2po -P $workdir/$d/kcp.{json,pot} 38 | } 39 | 40 | fn merge_po [d]{ 41 | xgettext -o $workdir/$d/kcp.pot $workdir/$d/**.po 42 | } 43 | 44 | if (> (count $args) 0) { 45 | local:e = $args[0] 46 | if (is $e --all) { 47 | extract_all 48 | merge_json $alldir 49 | merge_po $alldir 50 | } 51 | } 52 | 53 | extract_trans 54 | merge_po $transdir 55 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bvaudour/kcp 2 | 3 | go 1.19 4 | 5 | require github.com/leonelquinteros/gotext v1.5.2 6 | 7 | require golang.org/x/text v0.7.0 // indirect 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/leonelquinteros/gotext v1.5.0 h1:ODY7LzLpZWWSJdAHnzhreOr6cwLXTAmc914FOauSkBM= 2 | github.com/leonelquinteros/gotext v1.5.0/go.mod h1:OCiUVHuhP9LGFBQ1oAmdtNCHJCiHiQA8lf4nAifHkr0= 3 | github.com/leonelquinteros/gotext v1.5.2 h1:T2y6ebHli+rMBCjcJlHTXyUrgXqsKBhl/ormgvt7lPo= 4 | github.com/leonelquinteros/gotext v1.5.2/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= 5 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 6 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 7 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 8 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 9 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 10 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 11 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 12 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 13 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 14 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 15 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 16 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 17 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 18 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 19 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 20 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 21 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 22 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 24 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 25 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 26 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 27 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 28 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 29 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 30 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 31 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 32 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 33 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 34 | golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 35 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 36 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 37 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 38 | -------------------------------------------------------------------------------- /pkgbuild/atom/doc.go: -------------------------------------------------------------------------------- 1 | //Package atom provides primitives to describe and get the properties 2 | //of an atomic part of a PKGBUILD file. 3 | // 4 | //A PKGBUILD can contain: 5 | //- Blank lines, 6 | //- Comment lines, 7 | //- Variable declarations (with distinction between string & array variables) 8 | // containing themselves a name and a list of values and inner comments, 9 | //- Function declarations containing themselves a name and a body, 10 | //- Trailing comments after variables/functions declarations. 11 | // 12 | //There should be only one root atom on a line of the file. 13 | //For this reason, declarations and trailing comments are grouped in an atom 14 | //of type group. 15 | package atom 16 | -------------------------------------------------------------------------------- /pkgbuild/atom/info.go: -------------------------------------------------------------------------------- 1 | package atom 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/bvaudour/kcp/position" 7 | ) 8 | 9 | //GetNamed returns the named atom of the given atom if possible. 10 | //If the atom is a named atom, it returns it. 11 | //If the atom is a group, it returns the first named atom child and put 12 | //isParent to true. 13 | //Otherwise, exists is false. 14 | func GetNamed(a Atom) (n AtomNamed, exists bool, isParent bool) { 15 | switch a.GetType() { 16 | case Function: 17 | n, exists = a.(*AtomFunc) 18 | case VarArray, VarString: 19 | n, exists = a.(*AtomVar) 20 | case Group: 21 | { 22 | var e *AtomGroup 23 | if e, isParent = a.(*AtomGroup); isParent { 24 | for _, c := range e.Childs { 25 | if n, exists, _ = GetNamed(c); exists { 26 | break 27 | } 28 | } 29 | } 30 | } 31 | } 32 | return 33 | } 34 | 35 | //Info packs properties of a named atom. 36 | type Info struct { 37 | AtomNamed 38 | index int 39 | parent Atom 40 | values []string 41 | } 42 | 43 | //NewInfo returns the infos of the given atom. 44 | //If the atom is not a named atom or doesn’t contain 45 | //a named atom, it returns false. 46 | func NewInfo(a Atom) (info *Info, ok bool) { 47 | var n AtomNamed 48 | var isParent bool 49 | if n, ok, isParent = GetNamed(a); ok { 50 | info = &Info{AtomNamed: n} 51 | if isParent { 52 | info.parent = a 53 | } 54 | } 55 | return 56 | } 57 | 58 | //InfoList is a list of named infos in a slice of atoms. 59 | type InfoList struct { 60 | infos map[Atom]*Info 61 | values map[string]string 62 | } 63 | 64 | //NewInfoList returns an empty info list. 65 | func NewInfoList() *InfoList { 66 | return &InfoList{ 67 | infos: make(map[Atom]*Info), 68 | values: make(map[string]string), 69 | } 70 | } 71 | 72 | //Keys returns the ilst of atoms where infos are found. 73 | func (l *InfoList) Keys() Slice { 74 | var keys Slice 75 | for n := range l.infos { 76 | keys.Push(n) 77 | } 78 | sort.Slice(keys, func(i, j int) bool { 79 | ki, kj := keys[i], keys[j] 80 | return l.infos[ki].index < l.infos[kj].index 81 | }) 82 | return keys 83 | } 84 | 85 | //Filter returns the infos of the atoms which pass le callback. 86 | func (l *InfoList) Filter(cb NamedCheckerFunc) []*Info { 87 | var infos []*Info 88 | for _, n := range l.Keys() { 89 | info := l.infos[n] 90 | if cb == nil || cb(info.AtomNamed) { 91 | infos = append(infos, info) 92 | } 93 | } 94 | return infos 95 | } 96 | 97 | //FilterFirst returns the first atom which pass the callback 98 | //or false if no atom was found. 99 | func (l *InfoList) FilterFirst(cb NamedCheckerFunc) (info *Info, exists bool) { 100 | for _, n := range l.Keys() { 101 | i := l.infos[n] 102 | if cb == nil || cb(i.AtomNamed) { 103 | return i, true 104 | } 105 | } 106 | return 107 | } 108 | 109 | //Variables returns all infos of variable atoms. 110 | func (l *InfoList) Variables() []*Info { 111 | return l.Filter(NewNameMatcher(VarArray, VarString)) 112 | } 113 | 114 | //Functions returns all infos of function atoms. 115 | func (l *InfoList) Functions() []*Info { 116 | return l.Filter(NewNameMatcher(Function)) 117 | } 118 | 119 | //GetNamed returns the info of the given named atom 120 | //or false if not found. 121 | func (l *InfoList) GetNamed(n AtomNamed) (info *Info, exists bool) { 122 | for _, i := range l.infos { 123 | if n == i.AtomNamed { 124 | return i, true 125 | } 126 | } 127 | return 128 | } 129 | 130 | //Get is same as GetNamed but checks the container. 131 | func (l *InfoList) Get(a Atom) (info *Info, exists bool) { 132 | info, exists = l.infos[a] 133 | return 134 | } 135 | 136 | //GetDeep returns the info of the atom wheter it is 137 | //a named atom or a container, or false 138 | //if not found. 139 | func (l *InfoList) GetDeep(a Atom) (info *Info, exists bool) { 140 | if info, exists = l.Get(a); !exists { 141 | if n, ok, _ := GetNamed(a); ok { 142 | info, exists = l.GetNamed(n) 143 | } 144 | } 145 | return 146 | } 147 | 148 | //GetValues returns the list of the variable values 149 | //indexed by the name of the variables. 150 | func (l *InfoList) GetValues() map[string]string { 151 | out := make(map[string]string) 152 | for k, v := range l.values { 153 | out[k] = v 154 | } 155 | return out 156 | } 157 | 158 | //GetValue returns the value of the given variable name 159 | //or an empty string if not found. 160 | func (l *InfoList) GetValue(name string) string { 161 | return l.values[name] 162 | } 163 | 164 | //GetByIndex returns the info on the given index. 165 | func (l *InfoList) GetByIndex(idx int) (info *Info, exists bool) { 166 | for _, k := range l.Keys() { 167 | i := l.infos[k] 168 | if exists = i.index == idx; exists { 169 | info = i 170 | return 171 | } 172 | } 173 | return 174 | } 175 | 176 | //HasValue returns true if the name is a variable name 177 | //and if it has a value. 178 | func (l *InfoList) HasValue(name string) bool { 179 | _, ok := l.values[name] 180 | return ok 181 | } 182 | 183 | //Update updates the info of the given atom with the specified index 184 | //and returns a rune code specific to the type of update: 185 | //- 'A' if the info didn’t exist and was added, 186 | //- 'D' if the info existed and was deleted, 187 | //- 'U' if the info was found and some properties were modified, 188 | //- 'O' if there was no change. 189 | func (l *InfoList) Update(a Atom, i int) (t rune) { 190 | infoNew, isInfo := NewInfo(a) 191 | info, exists := l.Get(a) 192 | if !isInfo { 193 | if exists { 194 | info.index = -1 195 | delete(l.infos, a) 196 | return 'D' 197 | } 198 | return 'O' 199 | } 200 | if !exists { 201 | info, exists = l.GetNamed(infoNew.AtomNamed) 202 | } 203 | if !exists { 204 | infoNew.index = i 205 | l.infos[a] = infoNew 206 | return 'A' 207 | } 208 | c := false 209 | old := Slice{info.AtomNamed, info.parent} 210 | if info.index != i { 211 | info.index = i 212 | c = true 213 | } 214 | if info.AtomNamed != infoNew.AtomNamed { 215 | info.AtomNamed = infoNew.AtomNamed 216 | info.values = info.values[:0] 217 | c = true 218 | } 219 | if info.parent != infoNew.parent { 220 | info.parent = infoNew.parent 221 | c = true 222 | } 223 | if !c { 224 | return 'O' 225 | } 226 | for _, e := range old { 227 | if e != nil { 228 | delete(l.infos, e) 229 | } 230 | } 231 | l.infos[a] = info 232 | return 'U' 233 | } 234 | 235 | //UpdateAll update the infos with the slice of atoms. 236 | func (l *InfoList) UpdateAll(atoms Slice) { 237 | done := make(map[Atom]bool) 238 | for i, a := range atoms { 239 | l.Update(a, i) 240 | done[a] = true 241 | if n, ok, ip := GetNamed(a); ok && ip { 242 | done[n] = true 243 | } 244 | } 245 | for k, i := range l.infos { 246 | if !done[k] { 247 | i.index = -1 248 | i.values = nil 249 | delete(l.infos, k) 250 | } 251 | } 252 | } 253 | 254 | //RecomputeValues recomputes all values of all 255 | //variable atoms. 256 | func (l *InfoList) RecomputeValues() { 257 | l.values = make(map[string]string) 258 | for _, k := range l.Keys() { 259 | info := l.infos[k] 260 | if v, ok := info.Variable(); ok { 261 | info.values = v.GetArrayParsed(l.values) 262 | if len(info.values) > 0 { 263 | l.values[info.GetName()] = info.values[0] 264 | } 265 | } 266 | } 267 | } 268 | 269 | //Begin returns the begin position of the named atom 270 | //or the container. 271 | func (i *Info) Begin() position.Position { 272 | return GetBegin(i) 273 | } 274 | 275 | //End returns the end position of the named atom 276 | //or the container. 277 | func (i *Info) End() position.Position { 278 | return GetEnd(i) 279 | } 280 | 281 | //Index returns the index position of the described 282 | //atom in the slice. 283 | func (i *Info) Index() int { 284 | return i.index 285 | } 286 | 287 | //Name returns the name of the named atom. 288 | func (i *Info) Name() string { 289 | return i.GetName() 290 | } 291 | 292 | //Variable returns the variable atom described 293 | //by the info, or false if it’s not a variable atom. 294 | func (i *Info) Variable() (n *AtomVar, ok bool) { 295 | n, ok = i.AtomNamed.(*AtomVar) 296 | return 297 | } 298 | 299 | func (i *Info) v() *AtomVar { 300 | n, _ := i.Variable() 301 | return n 302 | } 303 | 304 | //Function returns the functon atom described 305 | //by the info, or false if it’s not a function atom. 306 | func (i *Info) Function() (n *AtomFunc, ok bool) { 307 | n, ok = i.AtomNamed.(*AtomFunc) 308 | return 309 | } 310 | 311 | func (i *Info) f() *AtomFunc { 312 | n, _ := i.Function() 313 | return n 314 | } 315 | 316 | //IsArrayVar returns true if the info concerns 317 | //a variable of type array. 318 | func (i *Info) IsArrayVar() bool { 319 | return i.GetType() == VarArray 320 | } 321 | 322 | //IsStringVar returns true if the info concerns 323 | //a variable of type string. 324 | func (i *Info) IsStringVar() bool { 325 | return i.GetType() == VarString 326 | } 327 | 328 | //IsSVar returns true if the info concerns 329 | //a variable. 330 | func (i *Info) IsVar() bool { 331 | return i.IsArrayVar() || i.IsStringVar() 332 | } 333 | 334 | //IsFunction returns true if the info concerns 335 | //a function. 336 | func (i *Info) IsFunc() bool { 337 | return i.GetType() == Function 338 | } 339 | 340 | //Raw returns the raw string of the described 341 | //named atom. 342 | func (i *Info) Raw() string { 343 | return i.GetRaw() 344 | } 345 | 346 | //StringValue returns the first value of the 347 | //described atom or an empty string if it’s not 348 | //a variable. 349 | func (i *Info) StringValue() string { 350 | if len(i.values) > 0 { 351 | return i.values[0] 352 | } 353 | return "" 354 | } 355 | 356 | //ArrayValue returns the values of the 357 | //described atom or an empty array if it’s not 358 | //a variable. 359 | func (i *Info) ArrayValue() []string { 360 | return i.values 361 | } 362 | 363 | //Body returns the raw content of the body function 364 | //or an empty string if it’s not a function. 365 | func (i *Info) Body() string { 366 | if i.IsFunc() { 367 | return i.f().GetBody() 368 | } 369 | return "" 370 | } 371 | 372 | //StringRawValue returns the first raw value of the 373 | //described atom or an empty string if it’s not 374 | //a variable. 375 | func (i *Info) StringRawValue() string { 376 | if i.IsVar() { 377 | return i.v().GetStringValue() 378 | } 379 | return "" 380 | } 381 | 382 | //ArrayRawValue returns the raw values of the 383 | //described atom or an empty array if it’s not 384 | //a variable. 385 | func (i *Info) ArrayRawValue() []string { 386 | if i.IsVar() { 387 | return i.v().GetArrayValue() 388 | } 389 | return nil 390 | } 391 | -------------------------------------------------------------------------------- /pkgbuild/format/doc.go: -------------------------------------------------------------------------------- 1 | //Package format provides utilities 2 | //to clean up a list of atoms. 3 | package format 4 | -------------------------------------------------------------------------------- /pkgbuild/format/format.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/bvaudour/kcp/pkgbuild/atom" 7 | "github.com/bvaudour/kcp/pkgbuild/standard" 8 | ) 9 | 10 | //MapFunc is a function which apply 11 | //transformations to a slice of atoms 12 | //and returns the result of these transformations. 13 | type MapFunc func(atom.Slice) atom.Slice 14 | 15 | //Formatter is an interface 16 | //which implements a transformation method 17 | type Formatter interface { 18 | Map(atom.Slice) atom.Slice 19 | } 20 | 21 | type StdFormatter int 22 | 23 | const ( 24 | RemoveExtraSpaces StdFormatter = iota 25 | RemoveBlankLinesExceptFirst 26 | RemoveAllBlankLines 27 | RemoveAdjacentDuplicateBlankLines 28 | RemoveCommentLines 29 | RemoveTrailingComments 30 | RemoveVariableComments 31 | RemoveDuplicateVariables 32 | RemoveDuplicateFunctions 33 | FormatValues 34 | BeautifulValues 35 | ReorderFuncsAndVars 36 | AddBlankLineBeforeVariables 37 | AddBlankLineBeforeFunctions 38 | ) 39 | 40 | var mstd = map[StdFormatter]MapFunc{ 41 | RemoveExtraSpaces: func(in atom.Slice) (out atom.Slice) { 42 | cb := func(a atom.Atom) atom.Atom { 43 | switch a.GetType() { 44 | case atom.Function: 45 | a.(*atom.AtomFunc).FormatSpaces(true) 46 | case atom.VarArray, atom.VarString: 47 | a.(*atom.AtomVar).FormatSpaces(true) 48 | case atom.Group: 49 | a.(*atom.AtomGroup).FormatSpaces(true) 50 | default: 51 | raw := strings.TrimSpace(a.GetRaw()) 52 | a.SetRaw(raw) 53 | atom.RecomputePosition(a) 54 | } 55 | return a 56 | } 57 | return in.Map(cb) 58 | }, 59 | RemoveBlankLinesExceptFirst: func(in atom.Slice) (out atom.Slice) { 60 | check := atom.NewMatcher(atom.Blank) 61 | for i, a := range in { 62 | if i == 0 || !check(a) { 63 | out.Push(a) 64 | } 65 | } 66 | return 67 | }, 68 | RemoveAllBlankLines: func(in atom.Slice) (out atom.Slice) { 69 | check := atom.NewRevMatcher(atom.Blank) 70 | return in.Search(check) 71 | }, 72 | RemoveAdjacentDuplicateBlankLines: func(in atom.Slice) (out atom.Slice) { 73 | check := atom.NewMatcher(atom.Blank) 74 | for i, a := range in { 75 | if !(i > 0 && check(a) && check(in[i-1])) { 76 | out.Push(a) 77 | } 78 | } 79 | return 80 | }, 81 | RemoveCommentLines: func(in atom.Slice) (out atom.Slice) { 82 | check := atom.NewRevMatcher(atom.Comment) 83 | return in.Search(check) 84 | }, 85 | RemoveTrailingComments: func(in atom.Slice) (out atom.Slice) { 86 | check := atom.NewMatcher(atom.Group) 87 | cc := atom.NewMatcher(atom.Comment) 88 | for _, a := range in { 89 | if check(a) { 90 | e := a.(*atom.AtomGroup) 91 | last := len(e.Childs) - 1 92 | for last >= 0 && cc(e.Childs[last]) { 93 | e.Childs.Pop() 94 | last-- 95 | } 96 | if last < 0 { 97 | continue 98 | } else if last == 0 { 99 | out.Push(e.Childs[0]) 100 | continue 101 | } 102 | } 103 | out.Push(a) 104 | } 105 | return 106 | }, 107 | RemoveVariableComments: func(in atom.Slice) (out atom.Slice) { 108 | cb := func(a atom.Atom) { 109 | if e, ok := a.(*atom.AtomVar); ok { 110 | e.RemoveComments(true) 111 | } 112 | } 113 | out = make(atom.Slice, len(in)) 114 | for i, a := range in { 115 | if info, ok := atom.NewInfo(a); ok && info.IsVar() { 116 | cb(info.AtomNamed) 117 | } 118 | out[i] = a 119 | } 120 | return 121 | }, 122 | RemoveDuplicateVariables: func(in atom.Slice) (out atom.Slice) { 123 | done := make(map[string]bool) 124 | check := func(a atom.Atom) bool { 125 | info, ok := atom.NewInfo(a) 126 | if ok && info.IsVar() { 127 | if name := info.Name(); done[name] { 128 | return false 129 | } else { 130 | done[name] = true 131 | } 132 | } 133 | return true 134 | } 135 | out = in.Search(check) 136 | return 137 | }, 138 | RemoveDuplicateFunctions: func(in atom.Slice) (out atom.Slice) { 139 | done := make(map[string]bool) 140 | check := func(a atom.Atom) bool { 141 | info, ok := atom.NewInfo(a) 142 | if ok && info.IsFunc() { 143 | if name := info.Name(); done[name] { 144 | return false 145 | } else { 146 | done[name] = true 147 | } 148 | } 149 | return true 150 | } 151 | out = in.Search(check) 152 | return 153 | }, 154 | FormatValues: func(in atom.Slice) (out atom.Slice) { 155 | cb := func(a atom.Atom) atom.Atom { 156 | info, ok := atom.NewInfo(a) 157 | if !ok { 158 | return a 159 | } 160 | e, ok := info.Variable() 161 | if !ok { 162 | return a 163 | } 164 | name := info.Name() 165 | qn := standard.IsQuotedVariable(name) 166 | var t []atom.AtomType 167 | if standard.IsStandardVariable(name) { 168 | if standard.IsArrayVariable(name) { 169 | t = append(t, atom.VarArray) 170 | } else { 171 | t = append(t, atom.VarString) 172 | } 173 | } 174 | e.FormatVariables(true, qn, t...) 175 | return a 176 | } 177 | out = in.Map(cb) 178 | return 179 | }, 180 | BeautifulValues: func(in atom.Slice) (out atom.Slice) { 181 | cb := func(a atom.Atom) atom.Atom { 182 | info, ok := atom.NewInfo(a) 183 | if !ok { 184 | return a 185 | } 186 | e, ok := info.Variable() 187 | if !ok { 188 | return a 189 | } 190 | name := info.Name() 191 | qn := standard.IsQuotedVariable(name) 192 | var t []atom.AtomType 193 | if standard.IsStandardVariable(name) { 194 | if standard.IsArrayVariable(name) { 195 | t = append(t, atom.VarArray) 196 | } else { 197 | t = append(t, atom.VarString) 198 | } 199 | } 200 | e.RemoveComments(false) 201 | e.FormatVariables(false, qn, t...) 202 | e.FormatSpaces(true) 203 | return a 204 | } 205 | out = in.Map(cb) 206 | return 207 | }, 208 | ReorderFuncsAndVars: func(in atom.Slice) (out atom.Slice) { 209 | var header, block atom.Slice 210 | fo, vo := make(fOrder), make(vOrder) 211 | for _, a := range in { 212 | info, ok := atom.NewInfo(a) 213 | if !ok { 214 | block.Push(a) 215 | continue 216 | } 217 | if len(header) == 0 && len(fo) == 0 && len(vo) == 0 { 218 | header.Push(block...) 219 | block = nil 220 | } 221 | block.Push(a) 222 | name := info.Name() 223 | if info.IsFunc() { 224 | f := fo[name] 225 | f.Push(block...) 226 | fo[name] = f 227 | } else { 228 | v := vo[name] 229 | vo[name] = append(v, newVBlock(info, block)) 230 | } 231 | block = nil 232 | } 233 | out.Push(header...) 234 | out.Push(vo.order()...) 235 | out.Push(fo.order()...) 236 | out.Push(block...) 237 | return 238 | }, 239 | AddBlankLineBeforeVariables: func(in atom.Slice) (out atom.Slice) { 240 | cl := atom.NewMatcher(atom.Blank) 241 | check := func(a atom.Atom) bool { 242 | info, ok := atom.NewInfo(a) 243 | return ok && info.IsVar() 244 | } 245 | for i, a := range in { 246 | if i > 0 && !cl(in[i-1]) && check(a) { 247 | out.Push(atom.NewBlank()) 248 | } 249 | out.Push(a) 250 | } 251 | return 252 | }, 253 | AddBlankLineBeforeFunctions: func(in atom.Slice) (out atom.Slice) { 254 | cl := atom.NewMatcher(atom.Blank) 255 | check := func(a atom.Atom) bool { 256 | info, ok := atom.NewInfo(a) 257 | return ok && info.IsFunc() 258 | } 259 | for i, a := range in { 260 | if i > 0 && !cl(in[i-1]) && check(a) { 261 | out.Push(atom.NewBlank()) 262 | } 263 | out.Push(a) 264 | } 265 | return 266 | }, 267 | } 268 | 269 | func (f StdFormatter) Map(in atom.Slice) atom.Slice { 270 | if cb, ok := mstd[f]; ok { 271 | return cb(in) 272 | } 273 | return in 274 | } 275 | 276 | func Format(formats ...Formatter) MapFunc { 277 | return func(atoms atom.Slice) atom.Slice { 278 | for _, f := range formats { 279 | atoms = f.Map(atoms) 280 | } 281 | return atoms 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /pkgbuild/format/order.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "sort" 5 | "strings" 6 | 7 | "github.com/bvaudour/kcp/pkgbuild/atom" 8 | "github.com/bvaudour/kcp/pkgbuild/standard" 9 | ) 10 | 11 | type ntree struct { 12 | name string 13 | childs []*ntree 14 | } 15 | 16 | type fOrder map[string]atom.Slice 17 | 18 | func (o fOrder) keys() (keys []string) { 19 | for k := range o { 20 | keys = append(keys, k) 21 | } 22 | fs := make(map[string]int) 23 | for i, f := range standard.GetFunctions() { 24 | fs[f] = i 25 | } 26 | less := func(i, j int) bool { 27 | ki, kj := keys[i], keys[j] 28 | ii, ei := fs[ki] 29 | ij, ej := fs[kj] 30 | switch { 31 | case ei: 32 | if ej { 33 | return ii < ij 34 | } 35 | return true 36 | case ej: 37 | return false 38 | } 39 | return strings.Compare(ki, kj) < 0 40 | } 41 | sort.Slice(keys, less) 42 | return 43 | } 44 | 45 | func (o fOrder) order() (atoms atom.Slice) { 46 | for _, k := range o.keys() { 47 | atoms.Push(o[k]...) 48 | } 49 | return 50 | } 51 | 52 | type vBlock struct { 53 | name string 54 | depends map[string]bool 55 | block atom.Slice 56 | } 57 | 58 | func orderVkeys(keys []string) { 59 | vs := make(map[string]int) 60 | for i, v := range standard.GetVariables() { 61 | vs[v] = i 62 | } 63 | less := func(i, j int) bool { 64 | ki, kj := keys[i], keys[j] 65 | ii, ei := vs[ki] 66 | ij, ej := vs[kj] 67 | switch { 68 | case ei: 69 | if ej { 70 | return ii < ij 71 | } 72 | return true 73 | case ej: 74 | return false 75 | } 76 | return strings.Compare(ki, kj) < 0 77 | } 78 | sort.Slice(keys, less) 79 | } 80 | 81 | func newVBlock(info *atom.Info, block atom.Slice) *vBlock { 82 | out := vBlock{ 83 | name: info.Name(), 84 | block: block, 85 | depends: make(map[string]bool), 86 | } 87 | vv, _ := info.Variable() 88 | for d := range vv.GetDepends() { 89 | out.depends[d] = true 90 | } 91 | return &out 92 | } 93 | 94 | func (v *vBlock) dependList() (depends []string) { 95 | for d, ok := range v.depends { 96 | if ok { 97 | depends = append(depends, d) 98 | } 99 | } 100 | orderVkeys(depends) 101 | return 102 | } 103 | 104 | type vOrder map[string][]*vBlock 105 | 106 | func (o vOrder) keys() (keys []string) { 107 | for k := range o { 108 | keys = append(keys, k) 109 | } 110 | orderVkeys(keys) 111 | return 112 | } 113 | 114 | func (o vOrder) first(name string) *vBlock { 115 | return o[name][0] 116 | } 117 | 118 | func (o vOrder) pop(name string) bool { 119 | if len(o[name]) < 2 { 120 | delete(o, name) 121 | return true 122 | } 123 | o[name] = o[name][1:] 124 | return false 125 | } 126 | 127 | func (o vOrder) sequence(name string, done map[string]bool) (seq []string) { 128 | done[name] = true 129 | b := o.first(name) 130 | for d := range done { 131 | delete(b.depends, d) 132 | } 133 | if len(b.depends) == 0 { 134 | return []string{name} 135 | } 136 | for _, d := range b.dependList() { 137 | if done[d] { 138 | continue 139 | } 140 | if _, ok := o[d]; !ok { 141 | continue 142 | } 143 | seq = append(seq, o.sequence(d, done)...) 144 | } 145 | seq = append(seq, name) 146 | return 147 | } 148 | 149 | func (o vOrder) order() (atoms atom.Slice) { 150 | keys, done := o.keys(), make(map[string]bool) 151 | for len(keys) > 0 { 152 | seq := o.sequence(keys[0], done) 153 | for _, k := range seq { 154 | b := o.first(k) 155 | atoms = append(atoms, b.block...) 156 | if o.pop(k) { 157 | idx := -1 158 | for i, ki := range keys { 159 | if ki == k { 160 | idx = i 161 | break 162 | } 163 | } 164 | if idx >= 0 { 165 | ktmp := make([]string, len(keys)-1) 166 | copy(ktmp[:idx], keys[:idx]) 167 | copy(ktmp[idx:], keys[idx+1:]) 168 | keys = ktmp 169 | } 170 | } 171 | } 172 | } 173 | return 174 | } 175 | -------------------------------------------------------------------------------- /pkgbuild/scanner/consts.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | const ( 4 | errorAt = "Error at %s: %s\n%s" 5 | ) 6 | -------------------------------------------------------------------------------- /pkgbuild/scanner/doc.go: -------------------------------------------------------------------------------- 1 | //Package scanner provides 2 | //all needed to scan and parse a PKGBUILD. 3 | package scanner 4 | -------------------------------------------------------------------------------- /pkgbuild/scanner/scanner.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "strings" 8 | 9 | "github.com/bvaudour/kcp/pkgbuild/atom" 10 | "github.com/bvaudour/kcp/position" 11 | "github.com/bvaudour/kcp/runes" 12 | ) 13 | 14 | //Scanner is an interface which wrap 15 | //needed methods to scan a PKGBUILD: 16 | //- Scan() analyzes a part of a PKGBUILD and returns false is the analyze failed 17 | //- Error() returns the error after a scan failed 18 | //- Atom() returns the last scanned atom 19 | type Scanner interface { 20 | Scan() bool 21 | Error() error 22 | Atom() atom.Atom 23 | } 24 | 25 | type scbase struct { 26 | token 27 | pos position.Position 28 | buffer strings.Builder 29 | element atom.Atom 30 | } 31 | 32 | func (sc *scbase) raw() string { 33 | return sc.buffer.String() 34 | } 35 | 36 | func (sc *scbase) write(s string) position.Position { 37 | sc.buffer.WriteString(s) 38 | sc.pos = sc.pos.NextString(s) 39 | return sc.pos 40 | } 41 | 42 | func (sc *scbase) writeRune(r rune) position.Position { 43 | sc.buffer.WriteRune(r) 44 | sc.pos = sc.pos.Next(r) 45 | return sc.pos 46 | } 47 | 48 | func (sc *scbase) writeLast() position.Position { 49 | return sc.writeRune(sc.last) 50 | } 51 | 52 | func (sc *scbase) nextToken() (s string, tt tokenType, ok bool) { 53 | s, tt, ok = sc.text() 54 | sc.write(s) 55 | return 56 | } 57 | 58 | func (sc *scbase) setAtom(a atom.Atom, begin position.Position) bool { 59 | a.SetRaw(sc.raw()) 60 | a.SetPositions(begin, sc.pos) 61 | sc.element = a 62 | return true 63 | } 64 | 65 | func (sc *scbase) setTrailing(a atom.Atom, begin position.Position) bool { 66 | if sc.state != tsTrailing { 67 | return sc.setAtom(a, begin) 68 | } 69 | raw := sc.raw() 70 | end, bc := sc.pos, sc.pos 71 | s, tt, ok := sc.nextToken() 72 | switch { 73 | case !ok: 74 | return false 75 | case sc.state != tsTrailing: 76 | return sc.setAtom(a, begin) 77 | case tt == ttSpace: 78 | bc = sc.pos 79 | if s, _, ok = sc.nextToken(); !ok { 80 | return false 81 | } 82 | } 83 | 84 | a.SetRaw(raw) 85 | a.SetPositions(begin, end) 86 | 87 | c := atom.NewComment() 88 | c.SetRaw(s) 89 | c.SetPositions(bc, sc.pos) 90 | 91 | g := atom.NewGroup() 92 | g.Childs = atom.Slice{a, c} 93 | return sc.setAtom(g, begin) 94 | } 95 | 96 | func (sc *scbase) getFunction(bn position.Position, name string) (a *atom.AtomFunc, ok bool) { 97 | var s string 98 | en := sc.pos 99 | if s, _, ok = sc.nextToken(); !ok { 100 | return 101 | } 102 | bb := sc.pos 103 | if s, _, ok = sc.nextToken(); ok { 104 | a = atom.NewFunction() 105 | a.SetName(name) 106 | a.SetPositions(bn, en) 107 | a.SetBody(s) 108 | a.SetBodyPositions(bb, sc.pos) 109 | } 110 | return 111 | } 112 | 113 | func (sc *scbase) passFunction() (ok bool) { 114 | if _, _, ok = sc.nextToken(); !ok { 115 | return 116 | } 117 | if _, _, ok = sc.nextToken(); !ok { 118 | return 119 | } 120 | var s string 121 | s, ok = sc.nextLine() 122 | sc.write(s) 123 | return 124 | } 125 | 126 | func (sc *scbase) getVariable(bn position.Position, name string) (a *atom.AtomVar, ok bool) { 127 | var s string 128 | en := sc.pos 129 | if s, _, ok = sc.nextToken(); !ok { 130 | return 131 | } 132 | bv := sc.pos 133 | var values atom.Slice 134 | if sc.state == tsVarSingle { 135 | a = atom.NewStringVar() 136 | if s, _, ok = sc.nextToken(); !ok { 137 | return 138 | } 139 | var f atom.ValueFormatter 140 | if f, ok = atom.NewValueFormatter(s); !ok { 141 | return 142 | } 143 | v := atom.NewValue() 144 | v.SetRaw(s) 145 | v.SetFormat(f) 146 | v.SetPositions(bv, sc.pos) 147 | values.Push(v) 148 | } else { 149 | a = atom.NewArrayVar() 150 | var tt tokenType 151 | s, tt, ok = sc.nextToken() 152 | for ok && tt != ttVarEnd { 153 | switch tt { 154 | case ttComment: 155 | v := atom.NewComment() 156 | v.SetRaw(s) 157 | v.SetPositions(bv, sc.pos) 158 | values.Push(v) 159 | case ttValue: 160 | var f atom.ValueFormatter 161 | if f, ok = atom.NewValueFormatter(s); !ok { 162 | return 163 | } 164 | v := atom.NewValue() 165 | v.SetRaw(s) 166 | v.SetFormat(f) 167 | v.SetPositions(bv, sc.pos) 168 | values.Push(v) 169 | } 170 | bv = sc.pos 171 | s, tt, ok = sc.nextToken() 172 | } 173 | if !ok { 174 | return 175 | } 176 | } 177 | a.SetName(name) 178 | a.SetNamePositions(bn, en) 179 | a.SetValues(values...) 180 | return 181 | } 182 | 183 | func (sc *scbase) passVariable() (ok bool) { 184 | if _, _, ok = sc.nextToken(); !ok { 185 | return 186 | } 187 | if sc.state == tsVarSingle { 188 | if _, _, ok = sc.nextToken(); !ok { 189 | return 190 | } 191 | } else { 192 | var tt tokenType 193 | _, tt, ok = sc.nextToken() 194 | for ok && tt != ttVarEnd { 195 | _, tt, ok = sc.nextToken() 196 | } 197 | if !ok { 198 | return 199 | } 200 | } 201 | var s string 202 | s, ok = sc.nextLine() 203 | sc.write(s) 204 | return 205 | } 206 | 207 | func (sc *scbase) Error() error { 208 | switch sc.err { 209 | case io.EOF, nil: 210 | return nil 211 | case runes.ErrInvalidToken: 212 | sc.writeLast() 213 | case runes.ErrUnendedToken: 214 | default: 215 | return sc.err 216 | } 217 | return fmt.Errorf(errorAt, sc.pos, sc.err, sc.buffer.String()) 218 | } 219 | 220 | func (sc *scbase) Atom() atom.Atom { 221 | return sc.element 222 | } 223 | 224 | type scFull struct { 225 | scbase 226 | } 227 | 228 | func (sc *scFull) Scan() (ok bool) { 229 | sc.element = nil 230 | sc.buffer.Reset() 231 | var prefix, s string 232 | if prefix, ok = sc.init(); !ok { 233 | return 234 | } 235 | begin := sc.pos 236 | p := sc.write(prefix) 237 | var tt tokenType 238 | if s, tt, ok = sc.nextToken(); !ok { 239 | return 240 | } 241 | switch tt { 242 | case ttSpace: 243 | ok = sc.setAtom(atom.NewBlank(), begin) 244 | case ttComment: 245 | ok = sc.setAtom(atom.NewComment(), begin) 246 | case ttName: 247 | var a atom.Atom 248 | if sc.state == tsFunc { 249 | a, ok = sc.getFunction(p, s) 250 | } else { 251 | a, ok = sc.getVariable(p, s) 252 | } 253 | if ok { 254 | ok = sc.setTrailing(a, begin) 255 | } 256 | } 257 | if sc.state == tsUnknown { 258 | sc.writeRune('\n') 259 | } 260 | return 261 | } 262 | 263 | type scSlim struct { 264 | scbase 265 | } 266 | 267 | func (sc *scSlim) Scan() (ok bool) { 268 | sc.element = nil 269 | sc.buffer.Reset() 270 | var prefix, s string 271 | if prefix, ok = sc.init(); !ok { 272 | return 273 | } 274 | begin := sc.pos 275 | p := sc.write(prefix) 276 | var tt tokenType 277 | if s, tt, ok = sc.nextToken(); !ok { 278 | return 279 | } 280 | if tt != ttName { 281 | if sc.state == tsUnknown { 282 | sc.writeRune('\n') 283 | } 284 | return sc.Scan() 285 | } 286 | var a atom.Atom 287 | if sc.state == tsFunc { 288 | a, ok = sc.getFunction(p, s) 289 | } else { 290 | a, ok = sc.getVariable(p, s) 291 | } 292 | if ok { 293 | sc.setAtom(a, begin) 294 | s, ok = sc.nextLine() 295 | sc.write(s) 296 | } 297 | if sc.state == tsUnknown { 298 | sc.writeRune('\n') 299 | } 300 | return 301 | } 302 | 303 | type scVar struct { 304 | scbase 305 | } 306 | 307 | func (sc *scVar) Scan() (ok bool) { 308 | sc.element = nil 309 | sc.buffer.Reset() 310 | var prefix, s string 311 | if prefix, ok = sc.init(); !ok { 312 | return 313 | } 314 | begin := sc.pos 315 | p := sc.write(prefix) 316 | var tt tokenType 317 | if s, tt, ok = sc.nextToken(); !ok { 318 | return 319 | } 320 | if tt != ttName { 321 | if sc.state == tsUnknown { 322 | sc.writeRune('\n') 323 | } 324 | return sc.Scan() 325 | } 326 | var a atom.Atom 327 | if sc.state == tsFunc { 328 | if ok = sc.passFunction(); !ok { 329 | return 330 | } 331 | if sc.state == tsUnknown { 332 | sc.writeRune('\n') 333 | } 334 | return sc.Scan() 335 | } else { 336 | a, ok = sc.getVariable(p, s) 337 | } 338 | if ok { 339 | sc.setAtom(a, begin) 340 | s, ok = sc.nextLine() 341 | sc.write(s) 342 | } 343 | if sc.state == tsUnknown { 344 | sc.writeRune('\n') 345 | } 346 | return 347 | } 348 | 349 | type scFunc struct { 350 | scbase 351 | } 352 | 353 | func (sc *scFunc) Scan() (ok bool) { 354 | sc.element = nil 355 | sc.buffer.Reset() 356 | var prefix, s string 357 | if prefix, ok = sc.init(); !ok { 358 | return 359 | } 360 | begin := sc.pos 361 | p := sc.write(prefix) 362 | var tt tokenType 363 | if s, tt, ok = sc.nextToken(); !ok { 364 | return 365 | } 366 | if tt != ttName { 367 | if sc.state == tsUnknown { 368 | sc.writeRune('\n') 369 | } 370 | return sc.Scan() 371 | } 372 | var a atom.Atom 373 | if sc.state == tsVar { 374 | if ok = sc.passVariable(); !ok { 375 | return 376 | } 377 | if sc.state == tsUnknown { 378 | sc.writeRune('\n') 379 | } 380 | return sc.Scan() 381 | } else { 382 | a, ok = sc.getFunction(p, s) 383 | } 384 | if ok { 385 | sc.setAtom(a, begin) 386 | s, ok = sc.nextLine() 387 | sc.write(s) 388 | } 389 | if sc.state == tsUnknown { 390 | sc.writeRune('\n') 391 | } 392 | return 393 | } 394 | 395 | func (sc *scbase) new(r io.Reader, initialPos ...position.Position) { 396 | sc.source = bufio.NewReader(r) 397 | if len(initialPos) > 0 { 398 | sc.pos = initialPos[0] 399 | } else { 400 | sc.pos.Line++ 401 | } 402 | } 403 | 404 | //New returns a full scanner of the given reader 405 | //If a position is provided, the scan position start here. 406 | //Otherwise the position is the initial position (line 1, column/offset 0). 407 | func New(r io.Reader, initialPos ...position.Position) Scanner { 408 | sc := new(scFull) 409 | sc.new(r, initialPos...) 410 | return sc 411 | } 412 | 413 | //NewFastScanner is a scanner which ignores blank lines and comments. 414 | func NewFastScanner(r io.Reader, initialPos ...position.Position) Scanner { 415 | sc := new(scSlim) 416 | sc.new(r, initialPos...) 417 | return sc 418 | } 419 | 420 | //NewVarScanner is a scanner which keeps only variable atoms. 421 | func NewVarScanner(r io.Reader, initialPos ...position.Position) Scanner { 422 | sc := new(scVar) 423 | sc.new(r, initialPos...) 424 | return sc 425 | } 426 | 427 | //NewFuncScanner is a scanner which keeps only function atoms. 428 | func NewFuncScanner(r io.Reader, initialPos ...position.Position) Scanner { 429 | sc := new(scFunc) 430 | sc.new(r, initialPos...) 431 | return sc 432 | } 433 | 434 | //ScanAll apply the Scan method of the scanner up to the end 435 | //or until an error occurs. 436 | //It returns the scanned atoms or an eventual error if scan failed. 437 | func ScanAll(sc Scanner) (atoms atom.Slice, err error) { 438 | for sc.Scan() { 439 | atoms.Push(sc.Atom()) 440 | } 441 | err = sc.Error() 442 | return 443 | } 444 | -------------------------------------------------------------------------------- /pkgbuild/scanner/token.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | 7 | "github.com/bvaudour/kcp/runes" 8 | ) 9 | 10 | type tokenState int 11 | type tokenType int 12 | 13 | const ( 14 | tsUnknown tokenState = iota 15 | tsEnd 16 | tsVar 17 | tsVarSingle 18 | tsVarMultiple 19 | tsFunc 20 | tsFuncBody 21 | tsTrailing 22 | 23 | ttUnknown tokenType = iota 24 | ttSpace 25 | ttComment 26 | ttName 27 | ttNameType 28 | ttBody 29 | ttValue 30 | ttVarEnd 31 | ) 32 | 33 | type token struct { 34 | state tokenState 35 | last rune 36 | source io.RuneReader 37 | err error 38 | } 39 | 40 | func (t *token) next() bool { 41 | if t.err == nil { 42 | t.last, _, t.err = t.source.ReadRune() 43 | } 44 | if t.err != nil { 45 | t.state = tsEnd 46 | } 47 | return t.err == nil 48 | } 49 | 50 | func (t *token) nextString(cb runes.BlockCheckerFunc, ignoreFirst ...bool) (string, bool) { 51 | var buffer strings.Builder 52 | ok := true 53 | if len(ignoreFirst) > 0 && ignoreFirst[0] { 54 | buffer.WriteRune(t.last) 55 | ok = t.next() 56 | } 57 | for ok && t.err == nil { 58 | if ok, t.err = cb(t.last); ok { 59 | buffer.WriteRune(t.last) 60 | ok = t.next() 61 | } 62 | } 63 | if t.state != tsEnd && t.err != nil { 64 | t.state = tsEnd 65 | } 66 | ok = t.err == nil || t.err == io.EOF 67 | return buffer.String(), ok 68 | } 69 | 70 | func (t *token) nextSpace() (string, bool) { 71 | cb := runes.Checker2Block(runes.IsSpace) 72 | return t.nextString(cb) 73 | } 74 | 75 | func (t *token) nextBlank() (string, bool) { 76 | cb := runes.Checker2Block(runes.IsBlank) 77 | return t.nextString(cb) 78 | } 79 | 80 | func (t *token) nextLine() (string, bool) { 81 | cb := runes.RChecker2Block(runes.IsNL) 82 | return t.nextString(cb) 83 | } 84 | 85 | func (t *token) nextDelimited(endChar runes.CheckerFunc) (s string, ok bool) { 86 | d, cb := runes.NewDelimiterChecker(endChar) 87 | if s, ok = t.nextString(cb); ok { 88 | if ok = d.IsClosed(); !ok { 89 | t.err, t.state = runes.ErrUnendedToken, tsEnd 90 | } 91 | } 92 | return 93 | } 94 | 95 | func (t *token) init() (s string, ok bool) { 96 | if ok = t.next(); ok { 97 | if s, ok = t.nextSpace(); ok { 98 | t.state = tsUnknown 99 | } 100 | } 101 | return 102 | } 103 | 104 | func (t *token) readUnknown() (s string, tt tokenType, ok bool) { 105 | switch { 106 | case runes.IsNL(t.last) || t.err == io.EOF: 107 | tt, ok = ttSpace, true 108 | if t.err != nil { 109 | t.state = tsEnd 110 | } 111 | case runes.IsComment(t.last): 112 | if s, ok = t.nextLine(); ok { 113 | tt = ttComment 114 | } 115 | case runes.IsAlpha(t.last): 116 | cb := runes.Checker2Block(runes.IsAlphaNum) 117 | if s, ok = t.nextString(cb); !ok { 118 | return 119 | } 120 | if ok = t.err == nil; !ok { 121 | t.err, t.state = runes.ErrUnendedToken, tsEnd 122 | return 123 | } 124 | tt = ttName 125 | if runes.IsAffectation(t.last) { 126 | t.state = tsVar 127 | } else { 128 | t.state = tsFunc 129 | } 130 | default: 131 | t.err, t.state = runes.ErrInvalidToken, tsEnd 132 | } 133 | return 134 | } 135 | 136 | func (t *token) readVarType() (s string, tt tokenType, ok bool) { 137 | s, tt, ok = "=(", ttNameType, true 138 | t.state = tsVarMultiple 139 | if !t.next() || t.last != '(' { 140 | s, t.state = "=", tsVarSingle 141 | } else if !t.next() { 142 | if t.err == io.EOF { 143 | t.err = runes.ErrUnendedToken 144 | } 145 | tt = ttUnknown 146 | } 147 | return 148 | } 149 | 150 | func (t *token) readVarSingle() (s string, tt tokenType, ok bool) { 151 | tt, ok = ttValue, true 152 | switch { 153 | case t.err == io.EOF: 154 | t.state = tsEnd 155 | case runes.IsNL(t.last): 156 | t.state = tsUnknown 157 | case runes.IsSpace(t.last) || runes.IsComment(t.last): 158 | t.state = tsTrailing 159 | default: 160 | endChar := func(r rune) bool { return runes.IsComment(r) || runes.IsBlank(r) } 161 | if s, ok = t.nextDelimited(endChar); !ok { 162 | tt = ttUnknown 163 | } else { 164 | t.readVarSingle() 165 | } 166 | } 167 | return 168 | } 169 | 170 | func (t *token) readVarMultiple() (s string, tt tokenType, ok bool) { 171 | switch { 172 | case t.last == ')': 173 | s, tt, ok = ")", ttVarEnd, true 174 | if t.next() { 175 | if runes.IsNL(t.last) { 176 | t.state = tsUnknown 177 | } else { 178 | t.state = tsTrailing 179 | } 180 | } 181 | return 182 | case runes.IsComment(t.last): 183 | if s, ok = t.nextLine(); ok { 184 | tt = ttComment 185 | } 186 | case runes.IsBlank(t.last): 187 | if s, ok = t.nextBlank(); ok { 188 | tt = ttSpace 189 | } 190 | default: 191 | endChar := func(r rune) bool { return r == ')' || runes.IsComment(r) || runes.IsBlank(r) } 192 | if s, ok = t.nextDelimited(endChar); ok { 193 | tt = ttValue 194 | } 195 | } 196 | if ok { 197 | if ok = t.err == nil; !ok { 198 | tt = ttUnknown 199 | t.err = runes.ErrUnendedToken 200 | } 201 | } 202 | return 203 | } 204 | 205 | func (t *token) readFuncType() (s string, tt tokenType, ok bool) { 206 | last := rune(' ') 207 | cb := func(r rune) (ok bool, err error) { 208 | switch { 209 | case runes.IsBlank(r): 210 | ok = true 211 | case r == '(': 212 | if ok = last == ' '; ok { 213 | last = r 214 | } else { 215 | err = runes.ErrInvalidToken 216 | } 217 | case r == ')': 218 | if ok = last == '('; ok { 219 | last = r 220 | } else { 221 | err = runes.ErrInvalidToken 222 | } 223 | case r == '{': 224 | if last != ')' { 225 | err = runes.ErrInvalidToken 226 | } 227 | default: 228 | err = runes.ErrInvalidToken 229 | } 230 | return 231 | } 232 | if s, ok = t.nextString(cb); ok { 233 | if ok = t.err == nil; ok { 234 | tt, t.state = ttSpace, tsFuncBody 235 | } else { 236 | t.err = runes.ErrUnendedToken 237 | } 238 | } 239 | return 240 | } 241 | 242 | func (t *token) readFuncBody() (s string, tt tokenType, ok bool) { 243 | endChar := func(r rune) bool { return runes.IsComment(r) || runes.IsBlank(r) } 244 | if s, ok = t.nextDelimited(endChar); ok { 245 | tt = ttBody 246 | switch { 247 | case t.err == io.EOF: 248 | t.state = tsEnd 249 | case runes.IsNL(t.last): 250 | t.state = tsUnknown 251 | case runes.IsSpace(t.last) || runes.IsComment(t.last): 252 | t.state = tsTrailing 253 | } 254 | } 255 | return 256 | } 257 | 258 | func (t *token) readTrailing() (s string, tt tokenType, ok bool) { 259 | switch { 260 | case runes.IsSpace(t.last): 261 | if s, ok = t.nextSpace(); ok { 262 | tt = ttSpace 263 | } 264 | case runes.IsComment(t.last): 265 | if s, ok = t.nextLine(); ok { 266 | tt = ttComment 267 | } 268 | default: 269 | t.err, t.state = runes.ErrInvalidToken, tsEnd 270 | } 271 | if t.err == nil && runes.IsNL(t.last) { 272 | t.state = tsUnknown 273 | } 274 | return 275 | } 276 | 277 | func (t *token) text() (s string, tt tokenType, ok bool) { 278 | switch t.state { 279 | case tsUnknown: 280 | return t.readUnknown() 281 | case tsVar: 282 | return t.readVarType() 283 | case tsVarSingle: 284 | return t.readVarSingle() 285 | case tsVarMultiple: 286 | return t.readVarMultiple() 287 | case tsFunc: 288 | return t.readFuncType() 289 | case tsFuncBody: 290 | return t.readFuncBody() 291 | case tsTrailing: 292 | return t.readTrailing() 293 | } 294 | return 295 | } 296 | -------------------------------------------------------------------------------- /pkgbuild/standard/doc.go: -------------------------------------------------------------------------------- 1 | //Package standard provides utilities 2 | //to check and get the informations of 3 | //the well-known declarations. 4 | package standard 5 | -------------------------------------------------------------------------------- /pkgbuild/standard/function.go: -------------------------------------------------------------------------------- 1 | package standard 2 | 3 | const ( 4 | PREPARE = "prepare" 5 | BUILD = "build" 6 | CHECK = "check" 7 | PACKAGE = "package" 8 | ) 9 | 10 | func listToSet(l ...string) map[string]bool { 11 | m := make(map[string]bool) 12 | for _, e := range l { 13 | m[e] = true 14 | } 15 | return m 16 | } 17 | 18 | var ( 19 | flist = []string{ 20 | PREPARE, 21 | BUILD, 22 | CHECK, 23 | PACKAGE, 24 | } 25 | fset = listToSet(flist...) 26 | frequired = listToSet( 27 | PACKAGE, 28 | ) 29 | ) 30 | 31 | //GetFunctions returns the list of standard function names. 32 | func GetFunctions() []string { 33 | out := make([]string, len(flist)) 34 | copy(out, flist) 35 | return out 36 | } 37 | 38 | //IsStandardFunction returns true if the name is a well-known function. 39 | func IsStandardFunction(name string) bool { 40 | return fset[name] 41 | } 42 | 43 | //IsRequiredFunction returns true if the name is a required function. 44 | func IsRequiredFunction(name string) bool { 45 | return frequired[name] 46 | } 47 | -------------------------------------------------------------------------------- /pkgbuild/standard/variable.go: -------------------------------------------------------------------------------- 1 | package standard 2 | 3 | const ( 4 | PKGBASE = "pkgbase" 5 | PKGNAME = "pkgname" 6 | PKGVER = "pkgver" 7 | PKGREL = "pkgrel" 8 | EPOCH = "epoch" 9 | PKGDESC = "pkgdesc" 10 | ARCH = "arch" 11 | URL = "url" 12 | LICENSE = "license" 13 | GROUPS = "groups" 14 | DEPENDS = "depends" 15 | MAKEDEPENDS = "makedepends" 16 | CHECKDEPENDS = "checkdepends" 17 | OPTDEPENDS = "optdepends" 18 | PROVIDES = "provides" 19 | CONFLICTS = "conflicts" 20 | REPLACES = "replaces" 21 | BACKUP = "backup" 22 | OPTIONS = "options" 23 | INSTALL = "install" 24 | CHANGELOG = "changelog" 25 | SOURCE = "source" 26 | NOEXTRACT = "noextract" 27 | MD5SUMS = "md5sums" 28 | SHA1SUMS = "sha1sums" 29 | SHA256SUMS = "sha256sums" 30 | ) 31 | 32 | var ( 33 | vlist = []string{ 34 | PKGBASE, 35 | PKGNAME, 36 | PKGVER, 37 | PKGREL, 38 | EPOCH, 39 | PKGDESC, 40 | ARCH, 41 | URL, 42 | LICENSE, 43 | GROUPS, 44 | DEPENDS, 45 | MAKEDEPENDS, 46 | CHECKDEPENDS, 47 | OPTDEPENDS, 48 | PROVIDES, 49 | CONFLICTS, 50 | REPLACES, 51 | BACKUP, 52 | OPTIONS, 53 | INSTALL, 54 | CHANGELOG, 55 | SOURCE, 56 | NOEXTRACT, 57 | MD5SUMS, 58 | SHA1SUMS, 59 | SHA256SUMS, 60 | } 61 | 62 | vset = listToSet(vlist...) 63 | vrequired = listToSet( 64 | PKGNAME, 65 | PKGVER, 66 | PKGREL, 67 | PKGDESC, 68 | ARCH, 69 | URL, 70 | LICENSE, 71 | ) 72 | varray = listToSet( 73 | PKGBASE, 74 | ARCH, 75 | LICENSE, 76 | GROUPS, 77 | DEPENDS, 78 | MAKEDEPENDS, 79 | CHECKDEPENDS, 80 | OPTDEPENDS, 81 | PROVIDES, 82 | CONFLICTS, 83 | REPLACES, 84 | BACKUP, 85 | OPTIONS, 86 | SOURCE, 87 | NOEXTRACT, 88 | MD5SUMS, 89 | SHA1SUMS, 90 | SHA256SUMS, 91 | ) 92 | vquoted = listToSet( 93 | PKGDESC, 94 | ARCH, 95 | URL, 96 | LICENSE, 97 | GROUPS, 98 | DEPENDS, 99 | MAKEDEPENDS, 100 | CHECKDEPENDS, 101 | OPTDEPENDS, 102 | PROVIDES, 103 | CONFLICTS, 104 | REPLACES, 105 | BACKUP, 106 | INSTALL, 107 | CHANGELOG, 108 | SOURCE, 109 | NOEXTRACT, 110 | MD5SUMS, 111 | SHA1SUMS, 112 | SHA256SUMS, 113 | ) 114 | ) 115 | 116 | //GetVariables returns the list of the well-known variables. 117 | func GetVariables() []string { 118 | out := make([]string, len(vlist)) 119 | copy(out, vlist) 120 | return out 121 | } 122 | 123 | //IsStandardVariable returns true if the name is a well-known variable. 124 | func IsStandardVariable(name string) bool { 125 | return vset[name] 126 | } 127 | 128 | //IsRequiredVariable returns true if the name is a required variable. 129 | func IsRequiredVariable(name string) bool { 130 | return vrequired[name] 131 | } 132 | 133 | //IsRequiredVariable returns true if the name is a variable of type array. 134 | func IsArrayVariable(name string) bool { 135 | return varray[name] 136 | } 137 | 138 | //IsQuotedVariable returns true if the value(s) of named variable should be quoted. 139 | func IsQuotedVariable(name string) bool { 140 | return vquoted[name] || !vset[name] 141 | } 142 | -------------------------------------------------------------------------------- /position/position.go: -------------------------------------------------------------------------------- 1 | package position 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode/utf8" 7 | ) 8 | 9 | //Position represents the position of a character in a file. 10 | type Position struct { 11 | Offset int 12 | Line int 13 | Column int 14 | } 15 | 16 | //Increment is an incrementer of positions 17 | type Increment = Position 18 | 19 | //IsValid returns true if the position is valid. 20 | //To be valid, a position should have lines > 0 and columns/offset ≥ 0. 21 | func (p Position) IsValid() bool { 22 | return p.Line > 0 && p.Column >= 0 && p.Offset >= 0 23 | } 24 | 25 | //Next retuns the position next to the given character. 26 | func (p Position) Next(r rune) Position { 27 | w := utf8.RuneLen(r) 28 | if w <= 0 { 29 | return p 30 | } 31 | p.Offset += w 32 | if r == '\n' { 33 | p.Line++ 34 | p.Column = 0 35 | } else { 36 | p.Column++ 37 | } 38 | return p 39 | } 40 | 41 | //Prev returns the position before the given character. 42 | func (p Position) Prev(r rune) Position { 43 | w := utf8.RuneLen(r) 44 | if r == '\n' || w <= 0 { 45 | return p 46 | } 47 | p.Offset -= w 48 | p.Column-- 49 | return p 50 | } 51 | 52 | //NextString returns the cursor position next to the given string. 53 | func (p Position) NextString(s string) Position { 54 | for _, r := range s { 55 | p = p.Next(r) 56 | } 57 | return p 58 | } 59 | 60 | //String returns a string representation of the position 61 | //on the form L{line},C{column} 62 | func (p Position) String() string { 63 | return fmt.Sprintf("L%d,C%d", p.Line, p.Column) 64 | } 65 | 66 | //Increment returns a new position with line, column and offset 67 | //incremented by the given increment. 68 | func (p Position) Increment(inc Increment) Position { 69 | p.Line += inc.Line 70 | p.Column += inc.Column 71 | p.Offset += inc.Offset 72 | return p 73 | } 74 | 75 | //IncrementPosition returns a new position with line, column and offset 76 | //incremented by the given increments. 77 | func (p Position) IncrementPosition(incLine, incColumn, incOffset int) Position { 78 | return p.Increment(New(incLine, incColumn, incOffset)) 79 | } 80 | 81 | //New returns a position with the given line, column and offset. 82 | func New(line, column, offset int) Position { 83 | return Position{ 84 | Line: line, 85 | Column: column, 86 | Offset: offset, 87 | } 88 | } 89 | 90 | //Cmp compares 2 positions. It returns : 91 | //- -1 if p1 before p2 92 | //- 1 if p1 after p2 93 | //- 0 otherwise 94 | func (p1 Position) Cmp(p2 Position) int { 95 | switch { 96 | case p1.Line < p2.Line: 97 | return -1 98 | case p1.Line > p2.Line: 99 | return 1 100 | case p1.Column < p2.Column: 101 | return -1 102 | case p1.Column > p2.Column: 103 | return 1 104 | } 105 | return 0 106 | } 107 | 108 | func (p1 Position) Eq(p2 Position) bool { 109 | return p1.Cmp(p2) == 0 110 | } 111 | 112 | func (p1 Position) Ne(p2 Position) bool { 113 | return p1.Cmp(p2) != 0 114 | } 115 | 116 | func (p1 Position) Ge(p2 Position) bool { 117 | return p1.Cmp(p2) >= 0 118 | } 119 | 120 | func (p1 Position) Le(p2 Position) bool { 121 | return p1.Cmp(p2) <= 0 122 | } 123 | 124 | func (p1 Position) Gt(p2 Position) bool { 125 | return p1.Cmp(p2) > 0 126 | } 127 | 128 | func (p1 Position) Lt(p2 Position) bool { 129 | return p1.Cmp(p2) < 0 130 | } 131 | 132 | func (p Position) Between(p1, p2 Position) bool { 133 | return p.Ge(p1) && p.Le(p2) 134 | } 135 | 136 | //Diff returns p1 - p2 137 | func (p1 Position) Diff(p2 Position) Increment { 138 | return New( 139 | p1.Line-p2.Line, 140 | p1.Column-p2.Column, 141 | p1.Offset-p2.Offset, 142 | ) 143 | } 144 | 145 | //Blank returns a string corresponding to the space 146 | //between p1 and p2. 147 | func (p1 Position) Blank(p2 Position) string { 148 | inc := p2.Diff(p1) 149 | if inc.Line == 0 && inc.Column > 0 { 150 | return strings.Repeat(" ", inc.Column) 151 | } else if inc.Line > 0 { 152 | s := strings.Repeat("\n", inc.Line) 153 | if p2.Column > 0 { 154 | s += strings.Repeat(" ", p2.Column) 155 | } 156 | return s 157 | } 158 | return "" 159 | } 160 | -------------------------------------------------------------------------------- /resources/completion/kcp.bash: -------------------------------------------------------------------------------- 1 | # kcp completion 2 | 3 | _kcpMatch() { 4 | for i in ${@:2}; do 5 | case $1 in 6 | --$i) return 0;; 7 | --*) ;; 8 | -*$i*) return 0;; 9 | *) ;; 10 | esac 11 | done 12 | return 1 13 | } 14 | 15 | _kcpMatchLast() { 16 | for i in ${@:2}; do 17 | case $1 in 18 | --$i) return 0;; 19 | --*) ;; 20 | -*$i) return 0;; 21 | *) ;; 22 | esac 23 | done 24 | return 1 25 | } 26 | 27 | _kcpContains() { 28 | for e in ${@:3}; do 29 | _kcpMatch $e $1 $2 && return 0 30 | done 31 | return 1 32 | } 33 | 34 | _kcpContainsLast() { 35 | for e in ${@:3}; do 36 | _kcpMatchLast $e $1 $2 && return 0 37 | done 38 | return 1 39 | } 40 | 41 | _kcpIsInstall() { 42 | _kcpContainsLast i install $@ && return 0 43 | return 1 44 | } 45 | 46 | _kcpIsUpdate() { 47 | _kcpContains u update-database $@ && return 0 48 | return 1 49 | } 50 | 51 | _kcpIsSearch() { 52 | _kcpContainsLast s search $@ && return 0 53 | return 1 54 | } 55 | 56 | _kcpIsList() { 57 | _kcpContains l list $@ && return 0 58 | return 1 59 | } 60 | 61 | _kcpIsInfo() { 62 | _kcpContains V information $@ && return 0 63 | return 1 64 | } 65 | 66 | _kcp() { 67 | local cur prev words cword pprev opts lst 68 | _init_completion || return 69 | pprev="${COMP_WORDS[COMP_CWORD-2]}" 70 | opts='--help --version --list --update-database --search --get --install 71 | --only-name --only-starred --only-installed --only-outdated 72 | --sort --force-update --asdeps --information 73 | -h -v -l -u -s -g -i -V 74 | -lN -lS -lI -lO 75 | -lx -lf -di' 76 | lst=() 77 | 78 | _kcpMatch $prev h help v version && return 0 79 | if [[ $prev == "kcp" ]]; then 80 | lst=($opts) 81 | elif _kcpMatchLast $prev s search g get i install V information; then 82 | lst=( $( kcp -lN | sort ) ) 83 | elif _kcpIsInstall ${COMP_WORDS[@]}; then 84 | _kcpContains d asdeps ${COMP_WORDS[@]} || lst=(--asdeps) 85 | elif _kcpIsSearch ${COMP_WORDS[@]}; then 86 | _kcpContains x sort ${COMP_WORDS[@]} || lst=(${lst[@]} --sort) 87 | _kcpContains N only-name ${COMP_WORDS[@]} || lst=(${lst[@]} --only-name) 88 | _kcpContains S only-starred ${COMP_WORDS[@]} || lst=(${lst[@]} --only-starred) 89 | _kcpContains I only-installed ${COMP_WORDS[@]} || lst=(${lst[@]} --only-installed) 90 | _kcpContains O only-outdated ${COMP_WORDS[@]} || lst=(${lst[@]} --only-outdated) 91 | _kcpContains f force-update ${COMP_WORDS[@]} || lst=(${lst[@]} --force-update) 92 | elif _kcpIsList ${COMP_WORDS[@]}; then 93 | _kcpContains x sort ${COMP_WORDS[@]} || lst=(${lst[@]} --sort) 94 | _kcpContains N only-name ${COMP_WORDS[@]} || lst=(${lst[@]} --only-name) 95 | _kcpContains S only-starred ${COMP_WORDS[@]} || lst=(${lst[@]} --only-starred) 96 | _kcpContains I only-installed ${COMP_WORDS[@]} || lst=(${lst[@]} --only-installed) 97 | _kcpContains O only-outdated ${COMP_WORDS[@]} || lst=(${lst[@]} --only-outdated) 98 | _kcpContains f force-update ${COMP_WORDS[@]} || lst=(${lst[@]} --force-update) 99 | elif _kcpContains f force-update ${COMP_WORDS[@]}; then 100 | lst=(--list --search) 101 | _kcpContains x sort ${COMP_WORDS[@]} || lst=(${lst[@]} --sort) 102 | _kcpContains N only-name ${COMP_WORDS[@]} || lst=(${lst[@]} --only-name) 103 | _kcpContains S only-starred ${COMP_WORDS[@]} || lst=(${lst[@]} --only-starred) 104 | _kcpContains I only-installed ${COMP_WORDS[@]} || lst=(${lst[@]} --only-installed) 105 | _kcpContains O only-outdated ${COMP_WORDS[@]} || lst=(${lst[@]} --only-outdated) 106 | _kcpContains f force-update ${COMP_WORDS[@]} || lst=(${lst[@]} --force-update) 107 | fi 108 | 109 | lst="${lst[@]}" 110 | [[ $lst == "" ]] && return 0 111 | COMPREPLY=( $(compgen -W "$lst" -- "$cur") ) 112 | } 113 | 114 | complete -F _kcp kcp 115 | -------------------------------------------------------------------------------- /resources/completion/kcp.fish: -------------------------------------------------------------------------------- 1 | # fish completion for kcp 2 | # Use 'command kcp' to avoid interactions for aliases from kcp to (e.g.) hub 3 | 4 | function __fish_kcp_match 5 | for e in $argv[2..(count $argv)] 6 | switch $argv[1] 7 | case "--$e" 8 | return 0 9 | case "--*" 10 | continue 11 | case "-*$e*" 12 | return 0 13 | end 14 | end 15 | return 1 16 | end 17 | 18 | function __fish_kcp_match_last 19 | for e in $argv[2..(count $argv)] 20 | switch $argv[1] 21 | case "--$e" 22 | return 0 23 | case "--*" 24 | continue 25 | case "-*$e" 26 | return 0 27 | end 28 | end 29 | return 1 30 | end 31 | 32 | function __fish_kcp_contains 33 | for e in $argv[3..(count $argv)] 34 | if __fish_kcp_match $e $argv[1..2] 35 | return 0 36 | end 37 | end 38 | return 1 39 | end 40 | 41 | function __fish_kcp_contains_last 42 | for e in $argv[3..(count $argv)] 43 | if __fish_kcp_match_last $e $argv[1..2] 44 | return 0 45 | end 46 | end 47 | return 1 48 | end 49 | 50 | function __fish_kcp_needs_arg 51 | set cmd (commandline -opc) 52 | if __fish_kcp_match_last $cmd[(count $cmd)] s i g V search install get information 53 | return 0 54 | end 55 | return 1 56 | end 57 | 58 | function __fish_kcp_empty 59 | set cmd (commandline -opc) 60 | if [ (count $cmd) -eq 1 -a $cmd[1] = 'kcp' ] 61 | return 0 62 | end 63 | return 1 64 | end 65 | 66 | function __fish_kcp_needs_command 67 | if __fish_kcp_needs_arg 68 | return 1 69 | end 70 | set cmd (commandline -opc) 71 | if __fish_kcp_contains $argv $cmd 72 | return 1 73 | end 74 | switch $argv[1] 75 | case l list 76 | if __fish_kcp_contains s search $cmd 77 | return 1 78 | end 79 | if __fish_kcp_contains u update-database $cmd 80 | return 1 81 | end 82 | if __fish_kcp_contains N only-name $cmd 83 | return 0 84 | end 85 | if __fish_kcp_contains S only-starred $cmd 86 | return 0 87 | end 88 | if __fish_kcp_contains I only-installed $cmd 89 | return 0 90 | end 91 | if __fish_kcp_contains O only-outdated $cmd 92 | return 0 93 | end 94 | if __fish_kcp_contains x sort $cmd 95 | return 0 96 | end 97 | if __fish_kcp_contains f force-update $cmd 98 | return 0 99 | end 100 | case u update-database 101 | if __fish_kcp_contains l list $cmd 102 | return 1 103 | end 104 | case s search 105 | if __fish_kcp_contains l list $cmd 106 | return 1 107 | end 108 | if __fish_kcp_contains N only-name $cmd 109 | return 0 110 | end 111 | if __fish_kcp_contains S only-starred $cmd 112 | return 0 113 | end 114 | if __fish_kcp_contains I only-installed $cmd 115 | return 0 116 | end 117 | if __fish_kcp_contains O only-outdated $cmd 118 | return 0 119 | end 120 | if __fish_kcp_contains x sort $cmd 121 | return 0 122 | end 123 | if __fish_kcp_contains f force-update $cmd 124 | return 0 125 | end 126 | case i install 127 | if __fish_kcp_contains d asdeps $cmd 128 | return 0 129 | end 130 | case N only-name S only-starred I only-installed O only-outdated x sort 131 | if __fish_kcp_contains l list $cmd 132 | return 0 133 | end 134 | if __fish_kcp_contains s search $cmd 135 | return 0 136 | end 137 | if __fish_kcp_contains N only-name $cmd 138 | return 0 139 | end 140 | if __fish_kcp_contains S only-starred $cmd 141 | return 0 142 | end 143 | if __fish_kcp_contains I only-installed $cmd 144 | return 0 145 | end 146 | if __fish_kcp_contains O only-outdated $cmd 147 | return 0 148 | end 149 | if __fish_kcp_contains x sort $cmd 150 | return 0 151 | end 152 | if __fish_kcp_contains f force-update $cmd 153 | return 0 154 | end 155 | case f force-update 156 | if __fish_kcp_contains u update-database $cmd 157 | return 1 158 | end 159 | if __fish_kcp_contains s search $cmd 160 | return 0 161 | end 162 | if __fish_kcp_contains l list $cmd 163 | return 0 164 | end 165 | if __fish_kcp_contains N only-name $cmd 166 | return 0 167 | end 168 | if __fish_kcp_contains S only-starred $cmd 169 | return 0 170 | end 171 | if __fish_kcp_contains I only-installed $cmd 172 | return 0 173 | end 174 | if __fish_kcp_contains O only-outdated $cmd 175 | return 0 176 | end 177 | if __fish_kcp_contains x sort $cmd 178 | return 0 179 | end 180 | case d asdeps 181 | if __fish_kcp_contains i install $cmd 182 | return 0 183 | end 184 | end 185 | return 1 186 | end 187 | 188 | function __fish_kcp_listall 189 | command kcp -lN | sort 190 | end 191 | 192 | # Initialization 193 | complete -fA -c kcp -n '__fish_kcp_empty' -a '-h --help' -d 'Display help' 194 | complete -fA -c kcp -n '__fish_kcp_empty' -a '-v --version' -d 'Display version' 195 | complete -f -c kcp -n '__fish_kcp_empty' -a '-l --list' -d 'List all available packages' 196 | complete -f -c kcp -n '__fish_kcp_empty' -a '-u --update-database' -d 'Refresh the local database' 197 | complete -f -c kcp -n '__fish_kcp_empty' -a '-s --search' -d 'Search a package' 198 | complete -f -c kcp -n '__fish_kcp_empty' -a '-g --get' -d 'Download a package' 199 | complete -f -c kcp -n '__fish_kcp_empty' -a '-i --install' -d 'Install a package' 200 | complete -f -c kcp -n '__fish_kcp_empty' -a '-V --information' -d 'Information about a package' 201 | complete -f -c kcp -n '__fish_kcp_empty' -a '-lN --only-name' -d 'Display only the packages name' 202 | complete -f -c kcp -n '__fish_kcp_empty' -a '-lS --only-starred' -d 'Display only the popular packages' 203 | complete -f -c kcp -n '__fish_kcp_empty' -a '-lI --only-installed' -d 'Display only the installed packages' 204 | complete -f -c kcp -n '__fish_kcp_empty' -a '-lO --only-outdated' -d 'Display only the outdated packages' 205 | complete -f -c kcp -n '__fish_kcp_empty' -a '-lx --sort' -d 'Sort results by popularity' 206 | complete -f -c kcp -n '__fish_kcp_empty' -a '-lf --force-update' -d 'Force refreshing database' 207 | complete -f -c kcp -n '__fish_kcp_empty' -a '-di --asdeps' -d 'install as dependence' 208 | 209 | 210 | # Options 211 | complete -f -c kcp -n '__fish_kcp_needs_command l list' -a '-l --list' -d 'Display all available packages' 212 | complete -f -c kcp -n '__fish_kcp_needs_command u update-database' -a '-u --update-database' -d 'Refresh the local database' 213 | complete -f -c kcp -n '__fish_kcp_needs_command s search' -a '-s --search' -d 'Search a package' 214 | complete -f -c kcp -n '__fish_kcp_needs_command i install' -a '-i --install' -d 'Install a package' 215 | complete -f -c kcp -n '__fish_kcp_needs_command N only-name' -a '-N --only-name' -d 'Display only the packages name' 216 | complete -f -c kcp -n '__fish_kcp_needs_command S only-starred' -a '-S --only-starred' -d 'Display only the popular packages' 217 | complete -f -c kcp -n '__fish_kcp_needs_command I only-installed' -a '-I --only-installed' -d 'Display only the installed packages' 218 | complete -f -c kcp -n '__fish_kcp_needs_command O only-outdated' -a '-O --only-outdated' -d 'Display only the outdated packages' 219 | complete -f -c kcp -n '__fish_kcp_needs_command x sort' -a '-x --sort' -d 'Sort results by popularity' 220 | complete -f -c kcp -n '__fish_kcp_needs_command f force-update' -a '-f --force-update' -d 'Force refreshing database' 221 | complete -f -c kcp -n '__fish_kcp_needs_command d asdeps' -a '-d --asdeps' -d 'install as dependence' 222 | 223 | # Available packages 224 | complete -f -c kcp -n '__fish_kcp_needs_arg' -a '(__fish_kcp_listall)' -d 'Available packages' 225 | -------------------------------------------------------------------------------- /resources/completion/kcp.zsh: -------------------------------------------------------------------------------- 1 | #compdef kcp 2 | 3 | #typeset -A opt_args 4 | #setopt extendedglob 5 | 6 | _kcp_help=( '(-h,--help)'{-h,--help}'[Display usage]' ) 7 | _kcp_version=( '(-v,--version)'{-v,--version}'[Display version]' ) 8 | _kcp_update=( '(-u,--update-database)'{-u,--update-database}'[Update database]' ) 9 | _kcp_get=( '(-g,--get)'{-g,--get}'[Get needed files to build the package]:package:_kcpApps' ) 10 | _kcp_information=( '(-V,--information)'{-V,--information}'[Get details about a package]:package:_kcpApps' ) 11 | _kcp_list=( '(-l,--list)'{-l,--list}'[List packages]' ) 12 | _kcp_search=( '(-s,--search)'{-s,--search}'[Search package]:package:_kcpApps' ) 13 | _kcp_install=( '(-i,--install)'{-i,--install}'[Install package]:package:_kcpApps' ) 14 | _kcp_sort=( '(-x,--sort)'{-x,--sort}'[Sort by popularity]' ) 15 | _kcp_force=( '(-f,--force-update)'{-f,--force-update}'[Force update]' ) 16 | _kcp_name=( '(-N,--only-name)'{-N,--only-name}'[Display only names]' ) 17 | _kcp_starred=( '(-S,--only-starred)'{-S,--only-starred}'[Display only popular packages]' ) 18 | _kcp_installed=( '(-I,--only-installed)'{-I,--only-installed}'[Display only installed packages]' ) 19 | _kcp_outdated=( '(-O,--only-outdated)'{-O,--only-outdated}'[Display only outdated packages]' ) 20 | _kcp_asdeps=( '(-D,--asdeps)'{-D,--asdeps}'[Install as a depend]' ) 21 | 22 | _kcpApps() { 23 | for a in $(kcp -lN | sort); do 24 | compadd $a 25 | done 26 | } 27 | 28 | _kcp() { 29 | _arguments -s : \ 30 | - '(help)' \ 31 | "$_kcp_help[@]" \ 32 | - '(version)' \ 33 | "$_kcp_version[@]" \ 34 | - '(update)' \ 35 | "$_kcp_update[@]" \ 36 | - '(get)' \ 37 | "$_kcp_get[@]" \ 38 | - '(information)' \ 39 | "$_kcp_information[@]" \ 40 | - list \ 41 | "$_kcp_list[@]" \ 42 | "$_kcp_sort[@]" \ 43 | "$_kcp_force[@]" \ 44 | "$_kcp_name[@]" \ 45 | "$_kcp_starred[@]" \ 46 | "$_kcp_installed[@]" \ 47 | "$_kcp_outdated[@]" \ 48 | - search \ 49 | "$_kcp_search[@]" \ 50 | "$_kcp_sort[@]" \ 51 | "$_kcp_force[@]" \ 52 | "$_kcp_name[@]" \ 53 | "$_kcp_starred[@]" \ 54 | "$_kcp_installed[@]" \ 55 | "$_kcp_outdated[@]" \ 56 | - install \ 57 | "$_kcp_install[@]" \ 58 | "$_kcp_asdeps[@]" 59 | } 60 | 61 | _kcp "$@" 62 | -------------------------------------------------------------------------------- /resources/conf/PKGBUILD.commented.kaos.proto: -------------------------------------------------------------------------------- 1 | # This is an example PKGBUILD file for KaOS Linux. Use this as a start to creating your own, 2 | # and remove these comments. For more information, see 'man PKGBUILD'. 3 | # (you can use sed -i 's/#.*$//;/^$/d' /this/PKGBUILD) 4 | 5 | ### Formatting standards for KCP: 6 | # 7 | # To get to a reasonable standard in submitting, maintaining and contributing packages to KCP, 8 | # use the following guidelines to get to a uniform way of providing PKGBUILD files, readme info and common courtesy in KCP. 9 | # 10 | # Do not use names of maintainers or contributors in PKGBUILD, anyone can contribute, keep the header clean from this 11 | # Every package, application, support package or needed library has its own Github repository 12 | # KaOS has no i686, reflect that in arch=(‘x86_64′) line 13 | # Avoid code with “if” for x86_64 and i686 sources/md5sum, just use the x86_64 version 14 | # Discuss in the issue section what you want to contribute or change to an existing package 15 | # Use the “star” button to vote for a package, packages with some votes will be moved to the KaOS repositories 16 | # Provide relative links in the readme to packages needed for your applications, which are not in the repositories, 17 | # But need to be build from KCP also. And show the URL for the package. 18 | # 19 | # Example for readme.md code, providing relative links: 20 | # 21 | # owncloud-client 22 | # =============== 23 | # Description: 24 | # ownCloud is a software system for what is commonly termed cloud storage, for your personal Server. 25 | # http://owncloud.org/ 26 | # Needed packages from KCP: 27 | # Download and run makepkg -si in the package directory (with Dolphin and F4): 28 | # [qtkeychain](../../../qtkeychain) 29 | # [iniparser](../../../iniparser) 30 | # [ocsync](../../../ocsync) 31 | 32 | ### Commented PKGBUILD variables: 33 | 34 | #The name(s) of the package(s). 35 | # This should consist of lowercase alphanumerics and any of the following characters: @, ., _, +, - 36 | # (at symbol, dot, underscore, plus, hyphen). 37 | # Names are not allowed to start with hyphens. 38 | #For the sake of consistency, pkgname should match the name of the source tarball of the software 39 | pkgname=PKGNAME 40 | 41 | # The version of the package. This should be the same as the version released by the author of the package. 42 | pkgver=0.1 43 | 44 | # Release number: this value allows users to differentiate between consecutive builds of the same version of a package. 45 | pkgrel=1 46 | 47 | # Only used to force the package to be seen as newer than any previous version with a lower epoch 48 | # epoch= 49 | 50 | # The description of the package. This is recommended to be 80 characters or less and should not include the package name 51 | # in a self-referencing way, unless the application name differs from the package name. 52 | # For example, use pkgdesc="Text editor for X11" instead of pkgdesc="Nedit is a text editor for X11". 53 | pkgdesc="Commented PKGBUILD example for KaOS Linux" 54 | 55 | # KaOS only support x86_64 so next line shouldn't be changed 56 | arch=('x86_64') 57 | 58 | # The URL of the official site of the software being packaged. 59 | url="https://kaosx.us" 60 | 61 | # The license under which the software is distributed. If it's unknown, then put 'unknown'. 62 | license=('GPL') 63 | 64 | # The group the package belongs in. Optional. 65 | # groups=() 66 | 67 | # An array of packages that must be installed before the software can be run. 68 | # Version restrictions can be specified with comparison operators, e.g. depends=('foobar>=1.8.0') 69 | depends=() 70 | 71 | # An array of packages that are only required to build the software. 72 | makedepends=() 73 | 74 | # An array of packages that the software depends on to run its test suite, but are not needed at runtime. 75 | checkdepends=() 76 | 77 | # An array of packages that are not needed for the software to function, but provide additional features. 78 | optdepends=() 79 | 80 | # An array of files that can contain user-made changes and should be preserved during upgrade or removal. 81 | backup=() 82 | 83 | # The name of the .install script to be included in the package. 84 | # This should be the same as pkgname. 85 | install=${pkgname}.install 86 | 87 | # The name of the package changelog. If any... 88 | #changelog= 89 | 90 | # An array of files needed to build the package. It must contain the location of the software source, 91 | # which in most cases is a full HTTP or FTP URL. The previously set variables pkgname and pkgver can be used here. 92 | source=("http://yoursources.com/${pkgname}-${pkgver}.tar.gz") 93 | 94 | # This variable is array whose items are checksum strings that will be used to verify the integrity 95 | # of the respective files in the source array. 96 | sha512sums=('') 97 | 98 | # Classical configure/make/make install with patch 99 | prepare() { 100 | cd "${pkgname}-${pkgver}" 101 | patch -p1 -i "$srcdir/${pkgname}-${pkgver}.patch" 102 | } 103 | 104 | build() { 105 | cd "${pkgname}-${pkgver}" 106 | ./configure --prefix=/usr 107 | make 108 | } 109 | 110 | check() { 111 | cd "${pkgname}-${pkgver}" 112 | make -k check 113 | } 114 | 115 | package() { 116 | cd "${pkgname}-${pkgver}" 117 | make DESTDIR="${pkgdir}/" install 118 | } 119 | -------------------------------------------------------------------------------- /resources/conf/exceptions: -------------------------------------------------------------------------------- 1 | java-runtime 2 | java-environment 3 | libgl 4 | libjpeg 5 | libreoffice-en-US 6 | libreoffice-langpack 7 | phonon-backend 8 | phonon-backend-qt5 9 | sh 10 | -------------------------------------------------------------------------------- /resources/conf/kcp.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | ;; Base directory of the specific user configuration 3 | ;; If not an absolute path, it is relative to the $XDG_CONFIG_HOME 4 | ;; (or $HOME/.config if $XDG_CONFIG_HOME is not set). 5 | configDir = kcp 6 | 7 | ;; Language code 8 | ;; Leave it empty to use the system’s default 9 | language = 10 | 11 | [kcp] 12 | ;; Temporary dir 13 | ;; Temporary dir is used during a package installation through kcp. 14 | tmpDir = /tmp/kcp 15 | 16 | ;; Name of the file locker 17 | ;; This file is created during a KCP package installation 18 | ;; in order to prevent the launching of multiple kcp instances. 19 | ;; It is removed once the installation is finished or if 20 | ;; the program is killed. 21 | lockerFile = locked 22 | 23 | ;; Name of Database file 24 | ;; This file is created/updated with the kcp -u command 25 | ;; or when you install a package with the kcp -i command. 26 | dbFile = kcp.json 27 | 28 | ;; Repos to ignore during update 29 | ;; The names must be separated by spaces 30 | ignore = KaOS-Community-Packages.github.io 31 | 32 | ;; Clone method to use 33 | ;; Available values: https (default) or ssh 34 | ;; Warning: if you choose ssh, you need to create a key 35 | ;; and upload it in your git profile. For more explanation: 36 | ;; https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh 37 | cloneMethod = https 38 | 39 | [pckcp] 40 | ;; Name of exceptions file 41 | ;; The listed exceptions define the depends to ignore 42 | ;; at checking a PKGBUILD 43 | exceptionsFile = exceptions 44 | 45 | ;; Name of the PKGBUILD prototype 46 | protoFile = PKGBUILD.commented.kaos.proto 47 | 48 | ;; Suffix of the newly created PKGBUILDs when launching 49 | ;; the interactive edition 50 | suffixNewPKGBUILD = .new 51 | 52 | [github] 53 | ;; Github Organization where repos are. 54 | ;; Leave blank to use the default system. 55 | organization = 56 | 57 | ;; User/Password to use a custom authentification to connect to the API 58 | ;; Leave Blank to use the system. 59 | user = 60 | password = 61 | 62 | -------------------------------------------------------------------------------- /resources/i18n/hi_IN.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # 5 | # Translators: 6 | # Cellix , 2021 7 | # Panwar108 , 2018 8 | # Panwar108 , 2018-2020 9 | msgid "" 10 | msgstr "" 11 | "Project-Id-Version: KaOS\n" 12 | "Report-Msgid-Bugs-To: \n" 13 | "POT-Creation-Date: 2020-08-06 12:01+0200\n" 14 | "PO-Revision-Date: 2021-04-06 07:55+0000\n" 15 | "Last-Translator: Panwar108 \n" 16 | "Language-Team: Hindi (India) (http://www.transifex.com/kaos/kaos/language/hi_IN/)\n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | "Language: hi_IN\n" 21 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 22 | 23 | # filename: cmd/kcp/consts.go, offset: 460, line: 12, column: 19 24 | msgid "Display all packages of KCP" 25 | msgstr "केसीपी के सभी पैकेज दिखाएँ" 26 | 27 | # filename: cmd/kcp/consts.go, offset: 1435, line: 27, column: 19 28 | msgid "Display informations about a package" 29 | msgstr "पैकेज जानकारी दिखाएँ" 30 | 31 | # filename: cmd/kcp/consts.go, offset: 508, line: 13, column: 19 32 | msgid "Refresh the local database" 33 | msgstr "लोकल डाटाबेस रिफ्रेश करें" 34 | 35 | # filename: cmd/kcp/consts.go, offset: 990, line: 21, column: 19 36 | msgid "On refreshing database action, force complete update" 37 | msgstr "डेटाबेस रिफ्रेश करते समय, पूर्ण अपडेट हेतु बाध्य करें" 38 | 39 | # filename: cmd/kcp/consts.go, offset: 234, line: 8, column: 19 40 | msgid "Tool in command-line for KaOS Community Packages" 41 | msgstr "कैओस सामुदायिक पैकेज हेतु कमांड-लाइन साधन" 42 | 43 | # filename: cmd/kcp/consts.go, offset: 555, line: 14, column: 19 44 | msgid "Search packages in KCP and display them" 45 | msgstr "केसीपी में पैकेज खोजें व दिखाएँ" 46 | 47 | # filename: cmd/kcp/consts.go, offset: 859, line: 19, column: 19 48 | msgid "On install action, install as a dependence" 49 | msgstr "इंस्टॉल करते समय, आश्रित पैकेज के रूप में इंस्टॉल करें" 50 | 51 | # filename: cmd/kcp/consts.go, offset: 922, line: 20, column: 19 52 | msgid "On list action, display only installed packages" 53 | msgstr "सूचीबद्ध रूप में, केवल इंस्टॉल पैकेज ही दिखाएँ" 54 | 55 | # filename: cmd/kcp/consts.go, offset: 1867, line: 38, column: 29 56 | msgid "Interrupt by user…" 57 | msgstr "उपयोक्ता द्वारा अवरुद्ध ..." 58 | 59 | # filename: cmd/kcp/consts.go, offset: 303, line: 9, column: 19 60 | msgid "(-h|-v|-u|(-l|-s ) [-fxNSIO]|-i [-d]|-g |-V )" 61 | msgstr "(-h|-v|-u|(-l|-s ) [-fxNSIO]|-i [-d]|-g |-V )" 62 | 63 | # filename: cmd/kcp/consts.go, offset: 615, line: 15, column: 19 64 | msgid "Download needed files to build a package" 65 | msgstr "पैकेज इंस्टॉल हेतु आवश्यक फ़ाइलें डाउनलोड करें" 66 | 67 | # filename: cmd/kcp/consts.go, offset: 1550, line: 33, column: 29 68 | msgid "Don't launch this program as root!" 69 | msgstr "यह प्रोग्राम रुट उपयोक्ता के रूप में उपयोग न करें!" 70 | 71 | # filename: cmd/kcp/consts.go, offset: 2004, line: 42, column: 19 72 | msgid "Do you want to edit %s?" 73 | msgstr "क्या आप %s संपादित करना चाहते है?" 74 | 75 | # filename: cmd/kcp/consts.go, offset: 65, line: 5, column: 23 76 | msgid "" 77 | "Provides a tool to make the use of KaOS Community Packages.\n" 78 | "\n" 79 | "With this tool, you can search, get and install a package from KaOS Community Packages." 80 | msgstr "KaOS के सामुदायिक पैकेज उपयोग हेतु साधन है।\n\nइसके द्वारा आप KaOS के सामुदायिक पैकेज में उपलब्ध किसी भी पैकेज को खोज कर, इंस्टॉल कर सकते हैं।" 81 | 82 | # filename: cmd/kcp/consts.go, offset: 676, line: 16, column: 19 83 | msgid "Install a package from KCP" 84 | msgstr "केसीपी से पैकेज इंस्टॉल करें" 85 | 86 | # filename: cmd/kcp/consts.go, offset: 723, line: 17, column: 19 87 | msgid "On display action, don't print KCP version" 88 | msgstr "दिखाते समय, केसीपी संस्करण न दिखाएँ" 89 | 90 | # filename: cmd/kcp/consts.go, offset: 1210, line: 24, column: 19 91 | msgid "On display action, display only packages with at least one star" 92 | msgstr "दिखाते समय, केवल कम-से-कम एक स्टार रेटिंग वाले पैकेज दिखाएँ" 93 | 94 | # filename: cmd/kcp/consts.go, offset: 1492, line: 28, column: 19 95 | msgid "" 96 | msgstr "" 97 | 98 | # filename: cmd/kcp/consts.go, offset: 786, line: 18, column: 19 99 | msgid "On display action, sort packages by stars descending" 100 | msgstr "दिखाते समय, पैकेज को स्टार रेटिंग के घटते क्रम में दिखाएँ" 101 | 102 | # filename: cmd/kcp/consts.go, offset: 1365, line: 26, column: 19 103 | msgid "On display action, display only outdated packages" 104 | msgstr "दिखाते समय, केवल पुराने पैकेज दिखाएँ" 105 | 106 | # filename: cmd/kcp/consts.go, offset: 1741, line: 36, column: 29 107 | msgid "Another instance of kcp is running!" 108 | msgstr "केसीपी की अन्य आवृत्ति कार्यरत है!" 109 | 110 | # filename: cmd/kcp/consts.go, offset: 1807, line: 37, column: 29 111 | msgid "Failed to create locker file!" 112 | msgstr "लॉक फाइल बनाना विफल!" 113 | 114 | # filename: cmd/kcp/consts.go, offset: 426, line: 11, column: 19 115 | # filename: cmd/pckcp/consts.go, offset: 434, line: 11, column: 22 116 | msgid "Print version" 117 | msgstr "संस्करण देखें" 118 | 119 | # filename: cmd/kcp/consts.go, offset: 1063, line: 22, column: 19 120 | msgid "On display action, force refreshing local database" 121 | msgstr "दिखाते समय, लोकल डाटाबेस रिफ्रेश हेतु बाध्य करें" 122 | 123 | # filename: cmd/kcp/consts.go, offset: 1134, line: 23, column: 19 124 | msgid "On display action, display only the name of the package" 125 | msgstr "दिखाते समय, केवल पैकेज नाम दिखाएँ" 126 | 127 | # filename: cmd/kcp/consts.go, offset: 1615, line: 34, column: 29 128 | msgid "No package found" 129 | msgstr "कोई पैकेज नहीं मिला" 130 | 131 | # filename: cmd/kcp/consts.go, offset: 1954, line: 41, column: 19 132 | msgid "Do you want to edit PKGBUILD?" 133 | msgstr "क्या आप PKGBUILD संपादित करना चाहते है?" 134 | 135 | # filename: cmd/kcp/consts.go, offset: 390, line: 10, column: 19 136 | # filename: cmd/pckcp/consts.go, offset: 395, line: 10, column: 22 137 | msgid "Print this help" 138 | msgstr "मदद देखें" 139 | 140 | # filename: cmd/kcp/consts.go, offset: 1294, line: 25, column: 19 141 | msgid "On display action, display only installed packages" 142 | msgstr "दिखाते समय, केवल इंस्टॉल पैकेज ही दिखाएँ" 143 | 144 | # filename: cmd/kcp/consts.go, offset: 1662, line: 35, column: 29 145 | msgid "No package found. Check if the database is updated." 146 | msgstr "कोई पैकेज नहीं मिला। सुनिश्चित करें कि डेटाबेस अपडेट है।" 147 | 148 | # filename: cmd/kcp/consts.go, offset: 1909, line: 40, column: 19 149 | msgid "Package %s cloned in %s." 150 | msgstr "%s पैकेज %s में प्रतिरूपित।" 151 | 152 | # filename: cmd/pckcp/consts.go, offset: 799, line: 24, column: 20 153 | msgid "Variable '%s' is missing." 154 | msgstr "अनुपलब्ध मापदंड '%s'।" 155 | 156 | # filename: cmd/pckcp/consts.go, offset: 1622, line: 35, column: 25 157 | msgid "Variable '%s' contains bad packages." 158 | msgstr "विकृत पैकेज युक्त मापदंड '%s'।" 159 | 160 | # filename: cmd/pckcp/consts.go, offset: 2137, line: 44, column: 20 161 | msgid "Declarations have the good type." 162 | msgstr "प्रोग्राम घोषणा प्रकार उचित है।" 163 | 164 | # filename: cmd/pckcp/consts.go, offset: 2684, line: 56, column: 24 165 | msgid "Type the new name (leave blank to remove install variable):" 166 | msgstr "नया नाम दर्ज करें (इंस्टॉल मापदंड हटाने हेतु रिक्त छोड़ें) :" 167 | 168 | # filename: cmd/pckcp/consts.go, offset: 2931, line: 61, column: 23 169 | msgid "a function" 170 | msgstr "प्रोग्राम फंक्शन" 171 | 172 | # filename: cmd/pckcp/consts.go, offset: 65, line: 5, column: 23 173 | msgid "" 174 | "Provides a tool to check the validity of a PKGBUILD according to the KCP standards.\n" 175 | "\n" 176 | "If flag -e is used, the common errors can be checked and a (potentially) valid PKGBUILD.new is created." 177 | msgstr "केसीपी मानकों के अनुसार, PKGBUILD की वैधता की जाँचने हेतु साधन है।\n\nअगर flag -e का उपयोग किया गया है, तो आम त्रुटियों की जांच की जा सकती है व एक (संभावित) मान्य PKGBUILD.new बनाई जाती है।" 178 | 179 | # filename: cmd/pckcp/consts.go, offset: 1242, line: 31, column: 25 180 | msgid "Variable '%s' is empty." 181 | msgstr "रिक्त मापदंड '%s'।" 182 | 183 | # filename: cmd/pckcp/consts.go, offset: 1979, line: 41, column: 20 184 | msgid "There aren’t duplicates." 185 | msgstr "कोई प्रतिलिपि नहीं है।" 186 | 187 | # filename: cmd/pckcp/consts.go, offset: 2027, line: 42, column: 20 188 | msgid "There aren’t missing variables." 189 | msgstr "कोई मापदंड अनुपलब्ध नहीं है।" 190 | 191 | # filename: cmd/pckcp/consts.go, offset: 2966, line: 62, column: 23 192 | msgid "a variable" 193 | msgstr "मापदंड" 194 | 195 | # filename: cmd/pckcp/consts.go, offset: 952, line: 28, column: 25 196 | msgid "" 197 | "Header was found. Do not use names of maintainers or contributors in " 198 | "PKGBUILD, anyone can contribute, keep the header clean from this." 199 | msgstr "हैडर मिल गया। PKGBUILD में रखरखाव करने वालों व योगदानकर्ताओं के नाम उपयोग न करें, इसमें कोई भी योगदान दे सकता है, हैडर में ये सब शामिल न करें।" 200 | 201 | # filename: cmd/pckcp/consts.go, offset: 679, line: 19, column: 16 202 | msgid "Error" 203 | msgstr "त्रुटि" 204 | 205 | # filename: cmd/pckcp/consts.go, offset: 1685, line: 36, column: 25 206 | msgid "'%s' is the name of the package. It is useless." 207 | msgstr "'%s' पैकेज नाम है। यह अनुपयोगी है।" 208 | 209 | # filename: cmd/pckcp/consts.go, offset: 1759, line: 37, column: 25 210 | msgid "'%s' isn't in repo neither in kcp." 211 | msgstr "'%s' पैकेज-संग्रह व केसीपी दोनों में ही नहीं है।" 212 | 213 | # filename: cmd/pckcp/consts.go, offset: 2378, line: 50, column: 24 214 | msgid "Add variable '%s'?" 215 | msgstr "मापदंड '%s' जोड़ें?" 216 | 217 | # filename: cmd/pckcp/consts.go, offset: 2769, line: 57, column: 24 218 | msgid "Modify '%s'?" 219 | msgstr "'%s' को बदलें?" 220 | 221 | # filename: cmd/pckcp/consts.go, offset: 2879, line: 60, column: 23 222 | msgid "You should add it manually." 223 | msgstr "इसे स्वयं ही जोड़ें।" 224 | 225 | # filename: cmd/pckcp/consts.go, offset: 471, line: 12, column: 22 226 | msgid "Interactive edition" 227 | msgstr "संवादात्मक संस्करण" 228 | 229 | # filename: cmd/pckcp/consts.go, offset: 570, line: 14, column: 22 230 | msgid "Removes the useless comments and blanks of the prototype" 231 | msgstr "व्यर्थ टिप्पणियों व रिक्त स्थानों को प्रतिमान​ से हटाए" 232 | 233 | # filename: cmd/pckcp/consts.go, offset: 1113, line: 29, column: 25 234 | msgid "Some duplicates found:" 235 | msgstr "कुछ प्रतिरूप मिले :" 236 | 237 | # filename: cmd/pckcp/consts.go, offset: 1820, line: 38, column: 25 238 | msgid "" 239 | "Variables 'depends' and 'makedepends' are empty. You should manually check " 240 | "if it is not a missing." 241 | msgstr "मापदंड 'depends' व 'makedepends' रिक्त हैं। आप स्वयं इनकी उपलब्धता जाँचें।" 242 | 243 | # filename: cmd/pckcp/consts.go, offset: 2082, line: 43, column: 20 244 | msgid "There aren’t missing functions." 245 | msgstr "कोई फंक्शन अनुपलब्ध नहीं है।" 246 | 247 | # filename: cmd/pckcp/consts.go, offset: 2191, line: 45, column: 20 248 | msgid "There aren’t empty variables." 249 | msgstr "कोई मापदंड रिक्त नहीं है।" 250 | 251 | # filename: cmd/pckcp/consts.go, offset: 2294, line: 48, column: 24 252 | msgid "Remove header?" 253 | msgstr "हैडर हटाएँ?" 254 | 255 | # filename: cmd/pckcp/consts.go, offset: 2495, line: 52, column: 24 256 | msgid "Remove variable '%s'?" 257 | msgstr "मापदंड '%s' हटाएँ?" 258 | 259 | # filename: cmd/pckcp/consts.go, offset: 846, line: 25, column: 20 260 | msgid "Function '%s' is missing." 261 | msgstr "अनुपलब्ध फंक्शन '%s'।" 262 | 263 | # filename: cmd/pckcp/consts.go, offset: 1560, line: 34, column: 25 264 | msgid "install: file '%s' doesn’t exist." 265 | msgstr "इंस्टॉल : फाइल '%s' अनुपलब्ध।" 266 | 267 | # filename: cmd/pckcp/consts.go, offset: 2334, line: 49, column: 24 268 | msgid "Remove duplicates?" 269 | msgstr "प्रतिरूप हटाएँ?" 270 | 271 | # filename: cmd/pckcp/consts.go, offset: 514, line: 13, column: 22 272 | msgid "Generate a prototype of PKGBUILD" 273 | msgstr "PKGBUILD प्रतिमान बनाएँ" 274 | 275 | # filename: cmd/pckcp/consts.go, offset: 1941, line: 40, column: 20 276 | msgid "Header is clean." 277 | msgstr "उचित हैडर।" 278 | 279 | # filename: cmd/pckcp/consts.go, offset: 1292, line: 32, column: 25 280 | msgid "" 281 | "pkgrel is different from 1. It should be the case only if build instructions" 282 | " are edited but not pkgver." 283 | msgstr "pkgrel, 1 से अलग है। यह स्थिति केवल तभी होनी चाहिए, जब build निर्देश संपादित किए हो लेकिन pkgver नहीं।" 284 | 285 | # filename: cmd/pckcp/consts.go, offset: 2542, line: 53, column: 24 286 | msgid "Reset pkgrel to 1?" 287 | msgstr "pkgrel को 1 पुनः सेट करें?" 288 | 289 | # filename: cmd/pckcp/consts.go, offset: 2633, line: 55, column: 24 290 | msgid "Modify name of '%s' file?" 291 | msgstr "'%s' फ़ाइल का नाम बदलें?" 292 | 293 | # filename: cmd/pckcp/consts.go, offset: 3043, line: 64, column: 23 294 | msgid "an array variable" 295 | msgstr "श्रृंखला मापदंड" 296 | 297 | # filename: cmd/pckcp/consts.go, offset: 3085, line: 65, column: 23 298 | msgid "" 299 | "Note that hooks provide similar functionnalities and are more powerful. For " 300 | "more informations: %s" 301 | msgstr "ध्यान दें कि संपादन स्थान अधिक प्रभावी होने के साथ ही समान कार्यक्षमता प्रदान करते हैं। अधिक जानकारी : %s" 302 | 303 | # filename: cmd/pckcp/consts.go, offset: 754, line: 23, column: 20 304 | msgid "File %s does not exist." 305 | msgstr "फाइल %s अनुपलब्ध।" 306 | 307 | # filename: cmd/pckcp/consts.go, offset: 354, line: 9, column: 22 308 | msgid "[-h|-e|-v|-g[-c]]" 309 | msgstr "[-h|-e|-v|-g[-c]]" 310 | 311 | # filename: cmd/pckcp/consts.go, offset: 702, line: 20, column: 16 312 | msgid "Warning" 313 | msgstr "चेतावनी" 314 | 315 | # filename: cmd/pckcp/consts.go, offset: 727, line: 21, column: 16 316 | msgid "Info" 317 | msgstr "जानकारी" 318 | 319 | # filename: cmd/pckcp/consts.go, offset: 899, line: 27, column: 25 320 | msgid "Modifications saved in %s!" 321 | msgstr "बदलाव %s में संचित किये गए।" 322 | 323 | # filename: cmd/pckcp/consts.go, offset: 1162, line: 30, column: 25 324 | msgid "Bad type declaration: '%s' is %s but it should be %s." 325 | msgstr "प्रोग्राम घोषणा प्रकार गलत : '%s' %s है परंतु इसे %s होना चाहिए।" 326 | 327 | # filename: cmd/pckcp/consts.go, offset: 1422, line: 33, column: 25 328 | msgid "" 329 | "arch is different from 'x86_64'. Since KaOS only supports this architecture," 330 | " no other arch would be added here." 331 | msgstr "यह संरचना 'x86_64' से भिन्न है। कैओस हेतु केवल यही संरचना समर्थित है, अतः कोई अन्य संरचना जोड़ी नहीं जाएगी।" 332 | 333 | # filename: cmd/pckcp/consts.go, offset: 277, line: 8, column: 22 334 | msgid "Tool in command-line to manage common PKGBUILD errors" 335 | msgstr "सामान्य PKGBUILD त्रुटि प्रबंधन हेतु कमांड-लाइन साधन" 336 | 337 | # filename: cmd/pckcp/consts.go, offset: 2422, line: 51, column: 24 338 | msgid "Set variable '%s' with (leave blank to ignore):" 339 | msgstr "इसके साथ मापदंड '%s' सेट करें (अनदेखा करने हेतु रिक्त छोड़ें) :" 340 | 341 | # filename: cmd/pckcp/consts.go, offset: 2586, line: 54, column: 24 342 | msgid "Reset arch to x86_64?" 343 | msgstr "संरचना x86_64 पर पुनः सेट करें?" 344 | 345 | # filename: cmd/pckcp/consts.go, offset: 2807, line: 58, column: 24 346 | msgid "Type the new value (leave blank to remove it):" 347 | msgstr "नया मान दर्ज करें (हटाने हेतु रिक्त छोड़ें) :" 348 | 349 | # filename: cmd/pckcp/consts.go, offset: 3001, line: 63, column: 23 350 | msgid "a string variable" 351 | msgstr "वाक्यांश मापदंड" 352 | 353 | # filename: cmd/pckcp/consts.go, offset: 2244, line: 46, column: 20 354 | msgid "Variable '%s' is clean." 355 | msgstr "उचित मापदंड '%s'।" 356 | 357 | # filename: common/consts.go, offset: 88, line: 7, column: 16 358 | msgid "[y/N]" 359 | msgstr "[y/N]" 360 | 361 | # filename: common/consts.go, offset: 104, line: 9, column: 8 362 | msgid "yes" 363 | msgstr "हाँ" 364 | 365 | # filename: common/consts.go, offset: 117, line: 10, column: 8 366 | msgid "no" 367 | msgstr "नहीं" 368 | 369 | # filename: common/consts.go, offset: 43, line: 4, column: 20 370 | msgid "vim" 371 | msgstr "vim" 372 | 373 | # filename: common/consts.go, offset: 65, line: 6, column: 16 374 | msgid "[Y/n]" 375 | msgstr "[Y/n]" 376 | 377 | # filename: database/consts.go, offset: 180, line: 8, column: 22 378 | msgid "Description" 379 | msgstr "विवरण" 380 | 381 | # filename: database/consts.go, offset: 342, line: 13, column: 22 382 | msgid "Depends on" 383 | msgstr "पर निर्भर" 384 | 385 | # filename: database/consts.go, offset: 492, line: 17, column: 22 386 | msgid "Replaces" 387 | msgstr "की जगह लेगा" 388 | 389 | # filename: database/consts.go, offset: 758, line: 27, column: 15 390 | msgid "%d entries updated!" 391 | msgstr "%d प्रविष्टियाँ अपडेट हुई!" 392 | 393 | # filename: database/consts.go, offset: 121, line: 6, column: 22 394 | msgid "Name" 395 | msgstr "नाम" 396 | 397 | # filename: database/consts.go, offset: 562, line: 19, column: 22 398 | msgid "Validated By" 399 | msgstr "द्वारा प्रमाणित" 400 | 401 | # filename: database/consts.go, offset: 598, line: 20, column: 22 402 | msgid "Yes" 403 | msgstr "हाँ" 404 | 405 | # filename: database/consts.go, offset: 648, line: 23, column: 18 406 | msgid "Dir %s already exists!" 407 | msgstr "डायरेक्टरी %s पहले से मौजूद है!" 408 | 409 | # filename: database/consts.go, offset: 688, line: 25, column: 15 410 | msgid "%d entries added!" 411 | msgstr "%d प्रविष्टियाँ जोड़ी गई!" 412 | 413 | # filename: database/consts.go, offset: 722, line: 26, column: 15 414 | msgid "%d entries deleted!" 415 | msgstr "%d प्रविष्टियाँ हटाई गई!" 416 | 417 | # filename: database/consts.go, offset: 47, line: 4, column: 22 418 | msgid "[installed]" 419 | msgstr "[इंस्टॉल है]" 420 | 421 | # filename: database/consts.go, offset: 149, line: 7, column: 22 422 | msgid "Version" 423 | msgstr "संस्करण" 424 | 425 | # filename: database/consts.go, offset: 278, line: 11, column: 22 426 | msgid "Licenses" 427 | msgstr "लाइसेंस" 428 | 429 | # filename: database/consts.go, offset: 417, line: 15, column: 22 430 | msgid "Optional Deps" 431 | msgstr "वैकल्पिक निर्भरताएँ" 432 | 433 | # filename: database/consts.go, offset: 454, line: 16, column: 22 434 | msgid "Conflicts With" 435 | msgstr "के साथ विरोध में" 436 | 437 | # filename: database/consts.go, offset: 524, line: 18, column: 22 438 | msgid "Install Script" 439 | msgstr "इंस्टॉल स्क्रिप्ट" 440 | 441 | # filename: database/consts.go, offset: 625, line: 21, column: 22 442 | msgid "No" 443 | msgstr "नहीं" 444 | 445 | # filename: database/consts.go, offset: 82, line: 5, column: 22 446 | msgid "[installed: %s]" 447 | msgstr "[इंस्टॉल है: %s]" 448 | 449 | # filename: database/consts.go, offset: 215, line: 9, column: 22 450 | msgid "Architecture" 451 | msgstr "स्थापत्य" 452 | 453 | # filename: database/consts.go, offset: 251, line: 10, column: 22 454 | msgid "URL" 455 | msgstr "यूआरएल" 456 | 457 | # filename: database/consts.go, offset: 310, line: 12, column: 22 458 | msgid "Provides" 459 | msgstr "प्रदान करेगा" 460 | 461 | # filename: database/consts.go, offset: 376, line: 14, column: 22 462 | msgid "Depends on (make)" 463 | msgstr "(make) पर निर्भर" 464 | 465 | # filename: flag/consts.go, offset: 182, line: 10, column: 27 466 | msgid "Invalid value '%v': Must be a %s!" 467 | msgstr "अमान्य मान '%v': %s होना चाहिए!" 468 | 469 | # filename: flag/consts.go, offset: 244, line: 11, column: 27 470 | msgid "Flag '%s' cannot be used with '%s'" 471 | msgstr "Flag '%s' के साथ '%s' उपयोग नहीं किया जा सकता" 472 | 473 | # filename: flag/consts.go, offset: 530, line: 16, column: 27 474 | msgid "Unexpected arg: %s" 475 | msgstr "अनपेक्षित argument: %s" 476 | 477 | # filename: flag/consts.go, offset: 767, line: 22, column: 15 478 | msgid "string" 479 | msgstr "वाक्यांश" 480 | 481 | # filename: flag/consts.go, offset: 904, line: 28, column: 18 482 | msgid "too short" 483 | msgstr "अति लघु" 484 | 485 | # filename: flag/consts.go, offset: 933, line: 29, column: 18 486 | msgid "too long" 487 | msgstr "अति दीर्घ" 488 | 489 | # filename: flag/consts.go, offset: 954, line: 31, column: 10 490 | msgid "Usage:" 491 | msgstr "उपयोग :" 492 | 493 | # filename: flag/consts.go, offset: 128, line: 9, column: 27 494 | msgid "Invalid %s flag '%s': %s!" 495 | msgstr "अमान्य %s फ्लैग '%s': %s!" 496 | 497 | # filename: flag/consts.go, offset: 490, line: 15, column: 27 498 | msgid "Arg needed!" 499 | msgstr "Argument चाहिए!" 500 | 501 | # filename: flag/consts.go, offset: 815, line: 25, column: 18 502 | msgid "short" 503 | msgstr "लघु" 504 | 505 | # filename: flag/consts.go, offset: 840, line: 26, column: 18 506 | msgid "long" 507 | msgstr "दीर्घ" 508 | 509 | # filename: flag/consts.go, offset: 307, line: 12, column: 27 510 | msgid "Flag '%s' needs one of these flags: %v" 511 | msgstr "इनमें से कोई फ्लैग '%s' हेतु आवश्यक : %v" 512 | 513 | # filename: flag/consts.go, offset: 625, line: 18, column: 27 514 | msgid "Flag '%s' is already set!" 515 | msgstr "फ्लैग '%s' पहले से ही सेट है!" 516 | 517 | # filename: flag/consts.go, offset: 679, line: 19, column: 27 518 | msgid "Unknown property: %d" 519 | msgstr "अज्ञात गुण : %d" 520 | 521 | # filename: flag/consts.go, offset: 864, line: 27, column: 18 522 | msgid "must begin with '%s'" 523 | msgstr "'%s' से आरंभ होना आवश्यक" 524 | 525 | # filename: flag/consts.go, offset: 374, line: 13, column: 27 526 | msgid "Flag '%s' doesn't allow multiple arguments!" 527 | msgstr "Flag '%s' एकाधिक arguments की अनुमति नहीं देता!" 528 | 529 | # filename: flag/consts.go, offset: 577, line: 17, column: 27 530 | msgid "Arg '%s' not in %v!" 531 | msgstr "Argument '%s' %v में नहीं है!" 532 | 533 | # filename: flag/consts.go, offset: 728, line: 20, column: 27 534 | msgid "Unsupported flag '%s'" 535 | msgstr "असमर्थित फ्लैग '%s'" 536 | 537 | # filename: flag/consts.go, offset: 790, line: 23, column: 15 538 | msgid "bool" 539 | msgstr "bool" 540 | 541 | # filename: flag/consts.go, offset: 446, line: 14, column: 27 542 | msgid "%s not allowed!" 543 | msgstr "%sकी अनुमति नहीं हैं!" 544 | 545 | # filename: pkgbuild/scanner/consts.go, offset: 36, line: 4, column: 12 546 | msgid "" 547 | "Error at %s: %s\n" 548 | "%s" 549 | msgstr "%s पर त्रुटि : %s\n%s" 550 | 551 | # filename: runes/consts.go, offset: 113, line: 10, column: 26 552 | msgid "Invalid token" 553 | msgstr "अमान्य टोकन" 554 | 555 | # filename: runes/consts.go, offset: 154, line: 11, column: 26 556 | msgid "Unended token" 557 | msgstr "अंतहीन टोकन" 558 | -------------------------------------------------------------------------------- /resources/man/kcp.1: -------------------------------------------------------------------------------- 1 | .\" Automatically generated by Pandoc 2.11.3.1 2 | .\" 3 | .TH "kcp" "1" "2021-01-23" "" "" 4 | .hy 5 | .SH NAME 6 | .PP 7 | kcp - Tool in command-line for KaOS Community Packages 8 | .SH SYNOPSIS 9 | .PP 10 | kcp (-hvlusgicxfNSIOdDV) [] 11 | .SH DESCRIPTION 12 | .PP 13 | kcp provides a tool to make the use of KaOS Community Packages easier. 14 | With this tool, you can: 15 | .IP \[bu] 2 16 | list all packages of KaOS Community Packages, 17 | .IP \[bu] 2 18 | search a package in KaOS Community Packages, 19 | .IP \[bu] 2 20 | download needed files to install a package from KaOS Community Packages, 21 | .IP \[bu] 2 22 | install a package of KaOS Community Packages. 23 | .SH COMMAND OPTIONS 24 | .TP 25 | \f[B]-h, --help\f[R] 26 | Display the list of the available options 27 | .TP 28 | \f[B]-v, --version\f[R] 29 | Display the current version of kcp 30 | .TP 31 | \f[B]-u, --update-database\f[R] 32 | Refresh the local database. 33 | The database is a file (see CREATED FILES chapter) which contains the 34 | following informations: 35 | .IP \[bu] 2 36 | The name of a package, 37 | .IP \[bu] 2 38 | Its description, 39 | .IP \[bu] 2 40 | Its installed version (if installed), 41 | .IP \[bu] 2 42 | Its latest version in KaOS Community Packages, 43 | .IP \[bu] 2 44 | Its popularity. 45 | .TP 46 | \f[B]-l, --list\f[R] 47 | Display the list of all packages. 48 | By default, it displays all informations contained in the database 49 | (name, remote/local version, popularity & description) 50 | .TP 51 | \f[B]-s, --search \f[R] 52 | Display the list of all packages which contain the keyword in the 53 | name or the description. 54 | .TP 55 | \f[B]-g, --get \f[R] 56 | Download the package from KaOS Community Packages in the current 57 | directory. 58 | .TP 59 | \f[B]-i, --install \f[R] 60 | Download, compile and install the package from KaOS Community 61 | Packages. 62 | .TP 63 | \f[B]-V, --information \f[R] 64 | Display information on a given package. 65 | .SH SPECIFIC OPTIONS 66 | .TP 67 | \f[B]-f, --force-update\f[R] 68 | By default, displaying list of packages uses the informations from the 69 | local database. 70 | With this option, the database is refreshed before displaying the list. 71 | It can be used only if -l or -s option is used. 72 | .TP 73 | \f[B]-x, --sort\f[R] 74 | On packages\[cq] display operation, sort the results by popularity, 75 | then, by name. 76 | This option can be used only with -l or -s options. 77 | .TP 78 | \f[B]-N, --only-name\f[R] 79 | On packages\[cq] display operation, display only the name of the 80 | packages. 81 | This option can be used only with -l or -s options. 82 | .TP 83 | \f[B]-S, --only-starred\f[R] 84 | On packages\[cq] display operation, display only the packages which are 85 | popular. 86 | This option can be used only with -l or -s options. 87 | .TP 88 | \f[B]-I, --only-installed\f[R] 89 | On packages\[cq] display operation, display only the packages which are 90 | installed on the system. 91 | This option can be used only with -l or -s options. 92 | .TP 93 | \f[B]-O, --only-outdated\f[R] 94 | On packages\[cq] display operation, display only the packages which are 95 | installed on the system and whose installed version isn\[cq]t the same 96 | as KaOS Community Packages version. 97 | This option can be used only with -l or -s options. 98 | .TP 99 | \f[B]-d, --asdeps\f[R] 100 | Install packages non-explicitly; in other words, fake their install 101 | reason to be installed as a dependency. 102 | This is useful to install dependencies before building the package. 103 | .TP 104 | \f[B]--debug\f[R] 105 | For internal use only. 106 | Display useful logtraces, in order to identify a potential problem. 107 | .SH CONFIGURATION 108 | .PP 109 | The program can be set through a .conf file in INI format. 110 | By default The \f[B]/etc/kcp/kcp.conf\f[R] is used for all users of the 111 | system. 112 | If you want override some settings or all settings for a specific user, 113 | you need to copy this file to the \f[B]$XDG_CONFIG_HOME/kcp/\f[R] dir. 114 | If the environnment variable $XDG_CONFIG_HOME is not set, it uses 115 | \f[B]$HOME/.config/kcp/\f[R] instead. 116 | .PP 117 | All parameters are commented in /etc/kcp/kcp.conf. 118 | .SH CREDITS 119 | .TP 120 | This is free and unencumbered software released into the public domain. 121 | http://unlicense.org/ for more information. 122 | .SH SEE ALSO 123 | .PP 124 | For more information regarding kcp, see the following: 125 | .TP 126 | The official code repository at 127 | 128 | .TP 129 | You can contact the project by emailing 130 | 131 | .SH REPORTING BUGS 132 | .TP 133 | Bugs in kcp may be reported to the issue-tracker at 134 | 135 | -------------------------------------------------------------------------------- /resources/man/pckcp.1: -------------------------------------------------------------------------------- 1 | .\" Automatically generated by Pandoc 2.11.3.1 2 | .\" 3 | .TH "pckcp" "1" "2021-01-23" "" "" 4 | .hy 5 | .SH NAME 6 | .PP 7 | pckcp - Tool in command-line to manage common PKGBUILD errors 8 | .SH SYNOPSIS 9 | .PP 10 | pckcp (-hvegc) 11 | .SH DESCRIPTION 12 | .PP 13 | Provides a tool to check the validity of a PKGBUILD according to the KCP 14 | standards. 15 | .PP 16 | If flag -e is used, the common errors can be checked and a (potentially) 17 | valid PKGBUILD.new is created. 18 | .SH OPTIONS 19 | .TP 20 | \f[B]-h, --help\f[R] 21 | Print this help 22 | .TP 23 | \f[B]-v, --version\f[R] 24 | Print version 25 | .TP 26 | \f[B]-e, --edit\f[R] 27 | Interactive edition. 28 | With this option the program suggests you to correct detected errors 29 | during checking. 30 | .TP 31 | \f[B]-g, --generate\f[R] 32 | Generate a prototype of PKGBUILD. 33 | It is based on the /etc/kcp/PKGBUILD.commented.kaos.proto by default. 34 | You can change the default prototype in the kcp configuration. 35 | .TP 36 | \f[B]-c, \[en]clean\f[R] 37 | Used with generate, it generates a prototype of PKGBUILD cleaned from 38 | any comment. 39 | .SH CREDITS 40 | .TP 41 | This is free and unencumbered software released into the public domain. 42 | http://unlicense.org/ for more information. 43 | .SH SEE ALSO 44 | .PP 45 | For more information regarding kcp, see the following: 46 | .TP 47 | The official code repository at 48 | 49 | .TP 50 | You can contact the project by emailing 51 | 52 | .SH REPORTING BUGS 53 | .TP 54 | Bugs in kcp may be reported to the issue-tracker at 55 | 56 | -------------------------------------------------------------------------------- /runes/consts.go: -------------------------------------------------------------------------------- 1 | package runes 2 | 3 | import ( 4 | "github.com/bvaudour/kcp/common" 5 | ) 6 | 7 | type Error string 8 | 9 | const ( 10 | ErrInvalidToken Error = "Invalid token" 11 | ErrUnendedToken Error = "Unended token" 12 | ) 13 | 14 | func (e Error) Error() string { 15 | return common.Tr(string(e)) 16 | } 17 | -------------------------------------------------------------------------------- /runes/delimiter.go: -------------------------------------------------------------------------------- 1 | package runes 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var ( 8 | closer = map[rune]rune{ 9 | '(': ')', 10 | '[': ']', 11 | '{': '}', 12 | '\'': '\'', 13 | '"': '"', 14 | } 15 | opener = (func(m map[rune]rune) map[rune]rune { 16 | out := make(map[rune]rune) 17 | for k, v := range m { 18 | out[v] = k 19 | } 20 | return out 21 | })(closer) 22 | ) 23 | 24 | //Delimiter is a structure which 25 | //stores the state of the analyze 26 | //of a string rune-by-rune. 27 | type Delimiter struct { 28 | escape bool 29 | variable bool 30 | longVariable bool 31 | comment bool 32 | variableName []rune 33 | quote rune 34 | openDelim []rune 35 | } 36 | 37 | //IsEscapeOpen returns true if the 38 | //last analyzed rune is the escape rune (\). 39 | func (d Delimiter) IsEscapeOpen() bool { 40 | return d.escape 41 | } 42 | 43 | //IsDelimOpen returns true if 44 | //there are not closed delimiters ('(', '{' or '['). 45 | func (d Delimiter) IsDelimOpen() bool { 46 | return len(d.openDelim) > 0 47 | } 48 | 49 | //IsQuoteOpen returns true 50 | //if previous open quote (' or ") was not closed. 51 | func (d Delimiter) IsQuoteOpen() bool { 52 | return IsQuote(d.quote) 53 | } 54 | 55 | //IsCommentOpen returns true if comment (#) is not ended. 56 | func (d Delimiter) IsCommentOpen() bool { 57 | return d.comment 58 | } 59 | 60 | //IsShortVariableOpen returns true 61 | //if a variable ($name) began to be parsed 62 | //but was not flushed. 63 | func (d Delimiter) IsShortVariableOpen() bool { 64 | return d.variable && !d.longVariable 65 | } 66 | 67 | //IsSLongVariableOpen returns true 68 | //if a variable (${name}) began to be parsed 69 | //but was not flushed. 70 | func (d Delimiter) IsLongVariablOpen() bool { 71 | return d.longVariable 72 | } 73 | 74 | //IsVariableOpen returns true 75 | //if a variable began to be parsed 76 | //but was not flushed. 77 | func (d Delimiter) IsVariableOpen() bool { 78 | return d.variable 79 | } 80 | 81 | //IsUnended returns true 82 | //if the parsign was not finished. 83 | func (d Delimiter) IsUnended() bool { 84 | return d.IsEscapeOpen() || d.IsDelimOpen() || d.IsQuoteOpen() || d.IsLongVariablOpen() 85 | } 86 | 87 | //IsClosed returns true 88 | //if the string stopping parsing returns 89 | //a valid string 90 | func (d Delimiter) IsClosed() bool { 91 | return !d.IsUnended() 92 | } 93 | 94 | //IsFullyClosed is same as IsClosed 95 | //but checks also variable and comment are flushed. 96 | func (d Delimiter) IsFullyClosed() bool { 97 | return d.IsClosed() && !d.variable && !d.comment 98 | } 99 | 100 | //GetVariableName returns the current value of 101 | //the parsed variable name. 102 | func (d Delimiter) GetVariableName() string { 103 | return string(d.variableName) 104 | } 105 | 106 | //Parse reads the given rune and change the state of the delimiter. 107 | //It flushed parsed runes, distinguishing variable name part and 108 | //raw string part, and ok is false if parse failed. 109 | func (d *Delimiter) Parse(r rune) (vname, out []rune, ok bool) { 110 | ok = true 111 | var needReparse, needCleanVar bool 112 | switch { 113 | case d.comment: 114 | d.comment = !IsNL(r) 115 | case d.variable: 116 | switch { 117 | case IsAlphaNum(r): 118 | d.variableName = append(d.variableName, r) 119 | case IsComment(r) && d.longVariable: 120 | d.variableName = append(d.variableName, r) 121 | case r == '{' && len(d.variableName) == 0: 122 | d.longVariable = true 123 | case len(d.variableName) == 0: 124 | if d.longVariable { 125 | out = []rune("${") 126 | if !d.IsQuoteOpen() { 127 | d.openDelim = append(d.openDelim, '{') 128 | } 129 | } else { 130 | out = []rune{'$'} 131 | } 132 | out = append(out, d.variableName...) 133 | needReparse, needCleanVar = true, true 134 | case !d.longVariable: 135 | needReparse = true 136 | fallthrough 137 | case r == '}': 138 | vname = append(vname, d.variableName...) 139 | needCleanVar = true 140 | default: 141 | out = []rune("${") 142 | out = append(out, d.variableName...) 143 | if !d.IsQuoteOpen() { 144 | d.openDelim = append(d.openDelim, '{') 145 | } 146 | needReparse, needCleanVar = true, true 147 | } 148 | case d.escape: 149 | d.escape = false 150 | if IsEscapable(r, d.quote) { 151 | out = []rune{r} 152 | } else { 153 | out = []rune{'\\', r} 154 | } 155 | case IsEscape(r): 156 | d.escape = true 157 | case d.IsQuoteOpen(): 158 | switch { 159 | case r == d.quote: 160 | d.quote = 0 161 | case d.quote == '"' && IsVariable(r): 162 | d.variable = true 163 | default: 164 | out = []rune{r} 165 | } 166 | case IsQuote(r): 167 | d.quote = r 168 | case IsComment(r): 169 | d.comment = true 170 | case IsVariable(r): 171 | d.variable = true 172 | case IsOpen(r): 173 | d.openDelim = append(d.openDelim, r) 174 | out = []rune{r} 175 | case IsClose(r): 176 | i := len(d.openDelim) - 1 177 | if i < 0 || r != closer[d.openDelim[i]] { 178 | ok = false 179 | } else { 180 | d.openDelim = d.openDelim[:i] 181 | out = []rune{r} 182 | } 183 | default: 184 | out = []rune{r} 185 | } 186 | if needCleanVar { 187 | d.variable, d.longVariable = false, false 188 | d.variableName = d.variableName[:0] 189 | } 190 | if needReparse { 191 | var nextOut []rune 192 | _, nextOut, ok = d.Parse(r) 193 | out = append(out, nextOut...) 194 | } 195 | if !ok { 196 | vname, out = nil, nil 197 | } 198 | return 199 | } 200 | 201 | //Next is same as parses but returns only the (no-)failure of 202 | //the parsing. 203 | func (d *Delimiter) Next(r rune) bool { 204 | _, _, ok := d.Parse(r) 205 | return ok 206 | } 207 | 208 | //String returns the string representation of the delimiter state 209 | //(for debug only). 210 | func (d *Delimiter) String() string { 211 | v := "" 212 | if d.variable { 213 | v = "$" 214 | if d.longVariable { 215 | v = "${" 216 | } 217 | } 218 | v += string(d.variableName) 219 | return fmt.Sprintf( 220 | `D{ 221 | escaped: %v, 222 | comment: %v, 223 | varOpen: %v, 224 | longOpen: %v, 225 | variable: "%s", 226 | quote: '%c', 227 | brackets: "%c", 228 | closed: %v, 229 | }`, 230 | d.escape, 231 | d.comment, 232 | d.variable, 233 | d.longVariable, 234 | v, 235 | d.quote, 236 | d.openDelim, 237 | d.IsClosed(), 238 | ) 239 | } 240 | 241 | //NewDelimiterChecker returns a new Delimiter plus 242 | //an associated blockchecker callback. 243 | //endChar gives a required condition to stop the parsing 244 | //(the other condition is the delimiter is closed). 245 | func NewDelimiterChecker(endChar CheckerFunc) (d *Delimiter, f BlockCheckerFunc) { 246 | d = new(Delimiter) 247 | f = func(r rune) (ok bool, err error) { 248 | if endChar(r) && d.IsClosed() { 249 | return 250 | } 251 | ok = d.Next(r) 252 | if !ok { 253 | err = ErrInvalidToken 254 | } 255 | return 256 | } 257 | return 258 | } 259 | -------------------------------------------------------------------------------- /runes/doc.go: -------------------------------------------------------------------------------- 1 | //Package runes provides utilities 2 | //to analyze runes and parse strings. 3 | package runes 4 | -------------------------------------------------------------------------------- /runes/runes.go: -------------------------------------------------------------------------------- 1 | package runes 2 | 3 | func IsSpace(r rune) bool { 4 | return r == ' ' || r == '\t' 5 | } 6 | 7 | func IsNL(r rune) bool { 8 | return r == '\n' 9 | } 10 | 11 | func IsBlank(r rune) bool { 12 | return IsSpace(r) || IsNL(r) 13 | } 14 | 15 | func IsEscape(r rune) bool { 16 | return r == '\\' 17 | } 18 | 19 | func IsQuote(r rune) bool { 20 | return r == '\'' || r == '"' 21 | } 22 | 23 | func IsOpen(r rune) bool { 24 | return r == '(' || r == '[' || r == '{' 25 | } 26 | 27 | func IsClose(r rune) bool { 28 | return r == ')' || r == ']' || r == '}' 29 | } 30 | 31 | func IsDelimiter(r rune) bool { 32 | return IsOpen(r) || IsClose(r) 33 | } 34 | 35 | func IsDigit(r rune) bool { 36 | return r >= '0' && r <= '9' 37 | } 38 | 39 | func IsLowCase(r rune) bool { 40 | return r >= 'a' && r <= 'z' 41 | } 42 | 43 | func IsUpCase(r rune) bool { 44 | return r >= 'A' && r <= 'Z' 45 | } 46 | 47 | func IsLetter(r rune) bool { 48 | return IsLowCase(r) || IsUpCase(r) 49 | } 50 | 51 | func IsAlpha(r rune) bool { 52 | return IsLetter(r) || r == '_' 53 | } 54 | 55 | func IsAlphaNum(r rune) bool { 56 | return IsAlpha(r) || IsDigit(r) 57 | } 58 | 59 | func IsComment(r rune) bool { 60 | return r == '#' 61 | } 62 | 63 | func IsVariable(r rune) bool { 64 | return r == '$' 65 | } 66 | 67 | func IsAffectation(r rune) bool { 68 | return r == '=' 69 | } 70 | 71 | //IsEscapable returns true if the given 72 | //rune should be escaped according to the given quote. 73 | func IsEscapable(r rune, quote rune) bool { 74 | if IsEscape(r) { 75 | return true 76 | } 77 | switch quote { 78 | case '\'': 79 | return r == quote 80 | case '"': 81 | return r == quote || IsVariable(r) || IsDelimiter(r) 82 | } 83 | return IsQuote(r) || IsBlank(r) || IsDelimiter(r) || IsComment(r) || IsVariable(r) 84 | } 85 | 86 | //CheckerFunc is a function which checks a rune. 87 | type CheckerFunc func(rune) bool 88 | 89 | //BlockCheckerFunc is a function which checks/blocks a rune. 90 | type BlockCheckerFunc func(rune) (bool, error) 91 | 92 | //ReverseChecker returns the negative checker 93 | //of the given callback. 94 | func ReverseChecker(cb CheckerFunc) CheckerFunc { 95 | return func(r rune) bool { return !cb(r) } 96 | } 97 | 98 | func Checker2Block(cb CheckerFunc) BlockCheckerFunc { 99 | return func(r rune) (bool, error) { return cb(r), nil } 100 | } 101 | 102 | func RChecker2Block(cb CheckerFunc) BlockCheckerFunc { 103 | return Checker2Block(ReverseChecker(cb)) 104 | } 105 | 106 | //CheckString checks if all runes of the string 107 | //pass the given checker func. 108 | func CheckString(s string, cb CheckerFunc) bool { 109 | for _, r := range s { 110 | if !cb(r) { 111 | return false 112 | } 113 | } 114 | return true 115 | } 116 | --------------------------------------------------------------------------------