Something went wrong. Try again.
Maps Linux distribution source packages to their upstream repositories, and through them to each other
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450package opensuse
import ( "strconv" "strings" "unicode")
// expand performs a textual expansion of RPM macros in s using the parser's macro table:// %{name}, %name, %{?name}, %{?name:text}, %{!?name:text}, %{nil}, %{defined x}, %{with x}.// Unknown macros, shell expansions %(...) and %{lua:...}-style bodies are left verbatim so the// caller can still recognise them. Expansion iterates to a fixed point (bounded).func (p *specParser) expand(s string) string { for i := 0; i < 8; i++ { n := expandOnce(p.spec.Macros, s, 0) if n == s { return n } s = n } return s}
func isMacroStart(r rune) bool { return r == '_' || unicode.IsLetter(r) }func isMacroChar(r rune) bool { return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) }
func expandOnce(macros map[string]string, s string, depth int) string { if depth > 16 || !strings.Contains(s, "%") { return s } var b strings.Builder i := 0 for i < len(s) { c := s[i] if c != '%' { b.WriteByte(c) i++ continue } if i+1 >= len(s) { b.WriteByte(c) i++ continue } switch s[i+1] { case '%': b.WriteString("%%") i += 2 case '{': end := matchBrace(s, i+1, '{', '}') if end < 0 { b.WriteString(s[i:]) return b.String() } body := s[i+2 : end] out, ok := expandBody(macros, body, depth) if ok { b.WriteString(out) } else { b.WriteString(s[i : end+1]) } i = end + 1 case '(': end := matchBrace(s, i+1, '(', ')') if end < 0 { b.WriteString(s[i:]) return b.String() } b.WriteString(s[i : end+1]) i = end + 1 default: j := i + 1 if j < len(s) && s[j] == '?' { j++ } k := j for k < len(s) && (isMacroChar(rune(s[k])) && (k > j || isMacroStart(rune(s[k])))) { k++ } if k == j { b.WriteByte(c) i++ continue } name := s[j:k] opt := s[i+1] == '?' if v, ok := macros[name]; ok { b.WriteString(expandOnce(macros, v, depth+1)) } else if opt { // %?name undefined -> empty } else { b.WriteString(s[i:k]) } i = k } } return b.String()}
// matchBrace returns the index of the bracket closing the one at s[open], or -1.func matchBrace(s string, open int, o, c byte) int { depth := 0 for i := open; i < len(s); i++ { switch s[i] { case o: depth++ case c: depth-- if depth == 0 { return i } } } return -1}
// expandBody expands the inside of %{...}. ok is false when the body is not understood and// should be kept verbatim.func expandBody(macros map[string]string, body string, depth int) (string, bool) { body = strings.TrimSpace(body) if body == "" { return "", false } // %{?name}, %{?name:text}, %{!?name:text}, %{?!name:text} if strings.HasPrefix(body, "?") || strings.HasPrefix(body, "!?") || strings.HasPrefix(body, "?!") { neg := strings.HasPrefix(body, "!") || strings.HasPrefix(body, "?!") rest := strings.TrimLeft(body, "?!") name, text, hasText := strings.Cut(rest, ":") name = strings.TrimSpace(name) if name == "" || !validMacroName(name) { return "", false } v, defined := macros[name] if neg { defined = !defined } if hasText { if defined { return expandOnce(macros, text, depth+1), true } return "", true } if defined && !neg { return expandOnce(macros, v, depth+1), true } return "", true } if body == "nil" { return "", true } if kw, arg, ok := strings.Cut(body, " "); ok { arg = strings.TrimSpace(arg) switch kw { case "defined", "undefined": _, d := macros[arg] if kw == "undefined" { d = !d } return boolStr(d), true case "with", "without": _, d := macros["with_"+arg] if kw == "without" { d = !d } return boolStr(d), true } return "", false } if strings.Contains(body, ":") { return "", false // %{expand:...}, %{lua:...}, %{basename:...} } if !validMacroName(body) { return "", false } if v, ok := macros[body]; ok { return expandOnce(macros, v, depth+1), true } return "", false}
func boolStr(b bool) string { if b { return "1" } return "0"}
func validMacroName(s string) bool { for i, r := range s { if i == 0 && !isMacroStart(r) || i > 0 && !isMacroChar(r) { return false } } return s != ""}
// eval evaluates a %if expression. Anything it cannot decide (unexpanded macros, %ifarch// style tests, syntax it does not know) is condUnknown.func (p *specParser) eval(expr string) condState { e := strings.TrimSpace(p.expand(expr)) if e == "" || strings.Contains(e, "%") { return condUnknown } toks, ok := tokenize(e) if !ok { return condUnknown } ev := &evaluator{toks: toks} v, ok := ev.parseOr() if !ok || ev.pos != len(ev.toks) { return condUnknown } if v.unknown { return condUnknown } if v.truthy() { return condTrue } return condFalse}
type token struct { kind string // "str", "num", "word", "op" val string}
func tokenize(s string) ([]token, bool) { var toks []token i := 0 for i < len(s) { c := s[i] switch { case c == ' ' || c == '\t': i++ case c == '"': j := strings.IndexByte(s[i+1:], '"') if j < 0 { return nil, false } toks = append(toks, token{"str", s[i+1 : i+1+j]}) i += j + 2 case strings.HasPrefix(s[i:], "==") || strings.HasPrefix(s[i:], "!=") || strings.HasPrefix(s[i:], ">=") || strings.HasPrefix(s[i:], "<=") || strings.HasPrefix(s[i:], "&&") || strings.HasPrefix(s[i:], "||"): toks = append(toks, token{"op", s[i : i+2]}) i += 2 case c == '>' || c == '<' || c == '!' || c == '(' || c == ')': toks = append(toks, token{"op", string(c)}) i++ default: j := i for j < len(s) && s[j] != ' ' && s[j] != '\t' && s[j] != '"' && s[j] != '(' && s[j] != ')' && !strings.HasPrefix(s[j:], "==") && !strings.HasPrefix(s[j:], "!=") && !strings.HasPrefix(s[j:], ">=") && !strings.HasPrefix(s[j:], "<=") && !strings.HasPrefix(s[j:], "&&") && !strings.HasPrefix(s[j:], "||") && s[j] != '>' && s[j] != '<' { j++ } if j == i { return nil, false } w := s[i:j] if _, err := strconv.ParseInt(w, 10, 64); err == nil { toks = append(toks, token{"num", w}) } else { toks = append(toks, token{"word", w}) } i = j } } return toks, true}
type value struct { num int64 str string isNum bool unknown bool}
func (v value) truthy() bool { if v.isNum { return v.num != 0 } return v.str != ""}
type evaluator struct { toks []token pos int}
func (e *evaluator) peek() *token { if e.pos < len(e.toks) { return &e.toks[e.pos] } return nil}
func (e *evaluator) parseOr() (value, bool) { l, ok := e.parseAnd() if !ok { return l, false } for t := e.peek(); t != nil && t.kind == "op" && t.val == "||"; t = e.peek() { e.pos++ r, ok := e.parseAnd() if !ok { return r, false } l = boolValue(l.truthy() || r.truthy(), l.unknown || r.unknown) } return l, true}
func (e *evaluator) parseAnd() (value, bool) { l, ok := e.parseNot() if !ok { return l, false } for t := e.peek(); t != nil && t.kind == "op" && t.val == "&&"; t = e.peek() { e.pos++ r, ok := e.parseNot() if !ok { return r, false } l = boolValue(l.truthy() && r.truthy(), l.unknown || r.unknown) } return l, true}
func (e *evaluator) parseNot() (value, bool) { if t := e.peek(); t != nil && t.kind == "op" && t.val == "!" { e.pos++ v, ok := e.parseNot() if !ok { return v, false } return boolValue(!v.truthy(), v.unknown), true } return e.parseCmp()}
func (e *evaluator) parseCmp() (value, bool) { l, ok := e.parsePrimary() if !ok { return l, false } t := e.peek() if t == nil || t.kind != "op" || t.val == "&&" || t.val == "||" || t.val == ")" { return l, true } op := t.val e.pos++ r, ok := e.parsePrimary() if !ok { return r, false } if l.unknown || r.unknown { return value{unknown: true}, true } var res bool if l.isNum && r.isNum { switch op { case "==": res = l.num == r.num case "!=": res = l.num != r.num case ">": res = l.num > r.num case "<": res = l.num < r.num case ">=": res = l.num >= r.num case "<=": res = l.num <= r.num default: return value{}, false } } else { ls, rs := l.str, r.str if l.isNum { ls = strconv.FormatInt(l.num, 10) } if r.isNum { rs = strconv.FormatInt(r.num, 10) } switch op { case "==": res = ls == rs case "!=": res = ls != rs case ">": res = ls > rs case "<": res = ls < rs case ">=": res = ls >= rs case "<=": res = ls <= rs default: return value{}, false } } return boolValue(res, false), true}
func (e *evaluator) parsePrimary() (value, bool) { t := e.peek() if t == nil { return value{}, false } switch t.kind { case "op": if t.val != "(" { return value{}, false } e.pos++ v, ok := e.parseOr() if !ok { return v, false } if c := e.peek(); c == nil || c.kind != "op" || c.val != ")" { return value{}, false } e.pos++ return v, true case "num": e.pos++ n, _ := strconv.ParseInt(t.val, 10, 64) return value{num: n, isNum: true}, true case "str": e.pos++ if n, err := strconv.ParseInt(strings.TrimSpace(t.val), 10, 64); err == nil { return value{num: n, isNum: true, str: t.val}, true } return value{str: t.val}, true default: // A bare word (an unexpanded value or an arch name): we cannot know. e.pos++ return value{str: t.val, unknown: true}, true }}
func boolValue(b, unknown bool) value { v := value{isNum: true, unknown: unknown} if b { v.num = 1 } return v}