name.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. package model
  2. import (
  3. "cmp"
  4. "errors"
  5. "fmt"
  6. "hash/maphash"
  7. "io"
  8. "log/slog"
  9. "path"
  10. "path/filepath"
  11. "slices"
  12. "strings"
  13. "sync"
  14. "github.com/ollama/ollama/types/structs"
  15. )
  16. // Errors
  17. var (
  18. // ErrInvalidName, ErrIncompleteName, and ErrInvalidDigest are not
  19. // used by this package, but are exported so that other packages can
  20. // use them, instead of defining their own errors for them.
  21. ErrInvalidName = errors.New("invalid model name")
  22. ErrIncompleteName = errors.New("incomplete model name")
  23. ErrInvalidDigest = errors.New("invalid digest")
  24. )
  25. // Defaults
  26. const (
  27. // MaskDefault is the default mask used by [Name.DisplayShortest].
  28. MaskDefault = "registry.ollama.ai/library/?:latest"
  29. // MaskNothing is a mask that masks nothing.
  30. MaskNothing = "?/?/?:?"
  31. // DefaultFill is the default fill used by [ParseName].
  32. FillDefault = "registry.ollama.ai/library/?:latest+Q4_0"
  33. // FillNothing is a fill that fills nothing.
  34. FillNothing = "?/?/?:?+?"
  35. )
  36. const MaxNamePartLen = 128
  37. type PartKind int
  38. // Levels of concreteness
  39. const (
  40. // Each value aligns with its index in the Name.parts array.
  41. PartHost PartKind = iota
  42. PartNamespace
  43. PartModel
  44. PartTag
  45. PartBuild
  46. PartDigest
  47. // NumParts is the number of parts in a Name. In this list, it must
  48. // follow the final part.
  49. NumParts
  50. PartExtraneous = -1
  51. )
  52. var kindNames = map[PartKind]string{
  53. PartHost: "Host",
  54. PartNamespace: "Namespace",
  55. PartModel: "Name",
  56. PartTag: "Tag",
  57. PartBuild: "Build",
  58. PartDigest: "Digest",
  59. }
  60. func (k PartKind) String() string {
  61. return cmp.Or(kindNames[k], "Unknown")
  62. }
  63. // Name is an opaque reference to a model. It holds the parts of a model
  64. // with the case preserved, but is not directly comparable with other Names
  65. // since model names can be represented with different casing depending on
  66. // the use case. For instance, "Mistral" and "mistral" are the same model
  67. // but each version may have come from different sources (e.g. copied from a
  68. // Web page, or from a file path).
  69. //
  70. // Valid Names can ONLY be constructed by calling [ParseName].
  71. //
  72. // A Name is valid if and only if is have a valid Model part. The other parts
  73. // are optional.
  74. //
  75. // A Name is considered "complete" if it has all parts present. To check if a
  76. // Name is complete, use [Name.IsComplete].
  77. //
  78. // To compare two names in a case-insensitive manner, use [Name.EqualFold].
  79. //
  80. // The parts of a Name are:
  81. //
  82. // - Host: the domain of the model (optional)
  83. // - Namespace: the namespace of the model (optional)
  84. // - Model: the name of the model (required)
  85. // - Tag: the tag of the model (optional)
  86. // - Build: the build of the model; usually the quantization or "file type" (optional)
  87. //
  88. // The parts can be obtained in their original form by calling [Name.Parts].
  89. //
  90. // To check if a Name has at minimum a valid model part, use [Name.IsValid].
  91. type Name struct {
  92. _ structs.Incomparable
  93. parts [NumParts]string // host, namespace, model, tag, build, digest
  94. // TODO(bmizerany): track offsets and hold s (raw string) here? We
  95. // could pack the offsets all into a single uint64 since the first
  96. // parts take less bits since their max offset is less than the max
  97. // offset of the next part. This would save a ton of bytes per Name
  98. // and mean zero allocations for String.
  99. }
  100. // ParseName parses s into a Name, and returns the result of filling it with
  101. // defaults. The input string must be a valid string
  102. // representation of a model name in the form:
  103. //
  104. // [host/][namespace/]<model>[:tag][+build][@<digest-type>-<digest>]
  105. //
  106. // The name part is required, all others are optional. If a part is missing,
  107. // it is left empty in the returned Name. If a part is invalid, the zero Ref
  108. // value is returned.
  109. //
  110. // The build part is normalized to uppercase.
  111. //
  112. // Examples of valid paths:
  113. //
  114. // "example.com/library/mistral:7b+x"
  115. // "example.com/eva/mistral:7b+Q4_0"
  116. // "mistral:7b+x"
  117. // "example.com/mike/mistral:latest+Q4_0"
  118. // "example.com/bruce/mistral:latest"
  119. // "example.com/pdevine/thisisfine:7b+Q4_0@sha256-1234567890abcdef"
  120. //
  121. // Examples of invalid paths:
  122. //
  123. // "example.com/mistral:7b+"
  124. // "example.com/mistral:7b+Q4_0+"
  125. // "x/y/z/z:8n+I"
  126. // ""
  127. //
  128. // It returns the zero value if any part is invalid.
  129. //
  130. // # Fills
  131. //
  132. // For any valid s, the fill string is used to fill in missing parts of the
  133. // Name. The fill string must be a valid Name with the exception that any part
  134. // may be the string ("?"), which will not be considered for filling.
  135. func ParseNameFill(s, fill string) Name {
  136. var r Name
  137. parts(s)(func(kind PartKind, part string) bool {
  138. if kind == PartDigest && !ParseDigest(part).IsValid() {
  139. r = Name{}
  140. return false
  141. }
  142. if kind == PartExtraneous || !IsValidNamePart(kind, part) {
  143. r = Name{}
  144. return false
  145. }
  146. r.parts[kind] = part
  147. return true
  148. })
  149. if r.IsValid() || r.IsResolved() {
  150. return fillName(r, fill)
  151. }
  152. return Name{}
  153. }
  154. // ParseName parses s into a Name, and returns the result of filling it
  155. // with FillDefault. The input string must be a valid string representation
  156. // of a model
  157. func ParseName(s string) Name {
  158. return ParseNameFill(s, "")
  159. }
  160. func parseMask(s string) Name {
  161. var r Name
  162. parts(s)(func(kind PartKind, part string) bool {
  163. if part == "?" {
  164. // mask part; treat as empty but valid
  165. return true
  166. }
  167. if !IsValidNamePart(kind, part) {
  168. panic(fmt.Errorf("invalid mask part %s: %q", kind, part))
  169. }
  170. r.parts[kind] = part
  171. return true
  172. })
  173. return r
  174. }
  175. func MustParseName(s, fill string) Name {
  176. r := ParseNameFill(s, fill)
  177. if !r.IsValid() {
  178. panic("invalid Name: " + s)
  179. }
  180. return r
  181. }
  182. // fillName fills in the missing parts of dst with the parts of src.
  183. //
  184. // The returned Name will only be valid if dst is valid.
  185. //
  186. // It skips fill parts that are "?".
  187. func fillName(r Name, fill string) Name {
  188. fill = cmp.Or(fill, FillDefault)
  189. f := parseMask(fill)
  190. if fill != FillNothing && f.IsZero() {
  191. panic("invalid fill")
  192. }
  193. for i := range r.parts {
  194. if f.parts[i] == "?" {
  195. continue
  196. }
  197. r.parts[i] = cmp.Or(r.parts[i], f.parts[i])
  198. }
  199. return r
  200. }
  201. // WithBuild returns a copy of r with the build set to the given string.
  202. func (r Name) WithBuild(build string) Name {
  203. r.parts[PartBuild] = build
  204. return r
  205. }
  206. func (r Name) WithDigest(digest Digest) Name {
  207. r.parts[PartDigest] = digest.String()
  208. return r
  209. }
  210. var mapHashSeed = maphash.MakeSeed()
  211. // MapHash returns a case insensitive hash for use in maps and equality
  212. // checks. For a convenient way to compare names, use [Name.EqualFold].
  213. //
  214. //nolint:errcheck
  215. func (r Name) MapHash() uint64 {
  216. // correctly hash the parts with case insensitive comparison
  217. var h maphash.Hash
  218. h.SetSeed(mapHashSeed)
  219. for _, part := range r.parts {
  220. // downcase the part for hashing
  221. for i := range part {
  222. c := part[i]
  223. if c >= 'A' && c <= 'Z' {
  224. c = c - 'A' + 'a'
  225. }
  226. h.WriteByte(c)
  227. }
  228. }
  229. return h.Sum64()
  230. }
  231. func (r Name) slice(from, to PartKind) Name {
  232. var v Name
  233. copy(v.parts[from:to+1], r.parts[from:to+1])
  234. return v
  235. }
  236. // DisplayShortest returns the shortest possible, masked display string in form:
  237. //
  238. // [host/][<namespace>/]<model>[:<tag>]
  239. //
  240. // # Masks
  241. //
  242. // The mask is a string that specifies which parts of the name to omit based
  243. // on case-insensitive comparison. [Name.DisplayShortest] omits parts of the name
  244. // that are the same as the mask, moving from left to right until the first
  245. // unequal part is found. It then moves right to left until the first unequal
  246. // part is found. The result is the shortest possible display string.
  247. //
  248. // Unlike a [Name] the mask can contain "?" characters which are treated as
  249. // wildcards. A "?" will never match a part of the name, since a valid name
  250. // can never contain a "?" character.
  251. //
  252. // For example: Given a Name ("registry.ollama.ai/library/mistral:latest") masked
  253. // with ("registry.ollama.ai/library/?:latest") will produce the display string
  254. // ("mistral").
  255. //
  256. // If mask is the empty string, then [MaskDefault] is used.
  257. //
  258. // DisplayShortest panics if the mask is not the empty string, MaskNothing, and
  259. // invalid.
  260. //
  261. // # Builds
  262. //
  263. // For now, DisplayShortest does consider the build or return one in the
  264. // result. We can lift this restriction when needed.
  265. func (r Name) DisplayShortest(mask string) string {
  266. mask = cmp.Or(mask, MaskDefault)
  267. d := parseMask(mask)
  268. if mask != MaskNothing && r.IsZero() {
  269. panic("invalid Name")
  270. }
  271. for i := range PartTag {
  272. if !strings.EqualFold(r.parts[i], d.parts[i]) {
  273. break
  274. }
  275. r.parts[i] = ""
  276. }
  277. for i := PartTag; i >= 0; i-- {
  278. if !strings.EqualFold(r.parts[i], d.parts[i]) {
  279. break
  280. }
  281. r.parts[i] = ""
  282. }
  283. return r.slice(PartHost, PartTag).DisplayLong()
  284. }
  285. // DisplayLongest returns the result of r.DisplayShortest(MaskNothing).
  286. func (r Name) DisplayLongest() string {
  287. return r.DisplayShortest(MaskNothing)
  288. }
  289. var seps = [...]string{
  290. PartHost: "/",
  291. PartNamespace: "/",
  292. PartModel: ":",
  293. PartTag: "+",
  294. PartBuild: "@",
  295. PartDigest: "",
  296. }
  297. // WriteTo implements io.WriterTo. It writes the fullest possible display
  298. // string in form:
  299. //
  300. // <host>/<namespace>/<model>:<tag>+<build>@<digest-type>-<digest>
  301. //
  302. // Missing parts and their separators are not written.
  303. //
  304. // The full digest is always prefixed with "@". That is if [Name.IsValid]
  305. // reports false and [Name.IsResolved] reports true, then the string is
  306. // returned as "@<digest-type>-<digest>".
  307. func (r Name) writeTo(w io.StringWriter) error {
  308. var partsWritten int
  309. for i := range r.parts {
  310. if r.parts[i] == "" {
  311. continue
  312. }
  313. if partsWritten > 0 || i == int(PartDigest) {
  314. if _, err := w.WriteString(seps[i-1]); err != nil {
  315. return err
  316. }
  317. }
  318. if _, err := w.WriteString(r.parts[i]); err != nil {
  319. return err
  320. }
  321. partsWritten++
  322. }
  323. return nil
  324. }
  325. var builderPool = sync.Pool{
  326. New: func() interface{} {
  327. return &strings.Builder{}
  328. },
  329. }
  330. // DisplayLong returns the fullest possible display string in form:
  331. //
  332. // <host>/<namespace>/<model>:<tag>+<build>
  333. //
  334. // If any part is missing, it is omitted from the display string.
  335. func (r Name) DisplayLong() string {
  336. b := builderPool.Get().(*strings.Builder)
  337. defer builderPool.Put(b)
  338. b.Reset()
  339. b.Grow(50) // arbitrarily long enough for most names
  340. _ = r.writeTo(b)
  341. return b.String()
  342. }
  343. // GoString implements fmt.GoStringer. It returns a string suitable for
  344. // debugging and logging. It is similar to [Name.DisplayLong] but it always
  345. // returns a string that includes all parts of the Name, with missing parts
  346. // replaced with a ("?").
  347. func (r Name) GoString() string {
  348. for i := range r.parts {
  349. r.parts[i] = cmp.Or(r.parts[i], "?")
  350. }
  351. return r.DisplayLong()
  352. }
  353. // LogValue implements slog.Valuer.
  354. func (r Name) LogValue() slog.Value {
  355. return slog.StringValue(r.GoString())
  356. }
  357. // IsComplete reports whether the Name is fully qualified. That is it has a
  358. // domain, namespace, name, tag, and build.
  359. func (r Name) IsComplete() bool {
  360. return !slices.Contains(r.parts[:PartDigest], "")
  361. }
  362. // IsCompleteNoBuild is like [Name.IsComplete] but it does not require the
  363. // build part to be present.
  364. func (r Name) IsCompleteNoBuild() bool {
  365. return !slices.Contains(r.parts[:PartBuild], "")
  366. }
  367. // IsResolved reports true if the Name has a valid digest.
  368. //
  369. // It is possible to have a valid Name, or a complete Name that is not
  370. // resolved.
  371. func (r Name) IsResolved() bool {
  372. return r.Digest().IsValid()
  373. }
  374. // Digest returns the digest part of the Name, if any.
  375. //
  376. // If Digest returns a non-empty string, then [Name.IsResolved] will return
  377. // true, and digest is considered valid.
  378. func (r Name) Digest() Digest {
  379. // This was already validated by ParseName, so we can just return it.
  380. return Digest{r.parts[PartDigest]}
  381. }
  382. // EqualFold reports whether r and o are equivalent model names, ignoring
  383. // case.
  384. func (r Name) EqualFold(o Name) bool {
  385. return r.CompareFold(o) == 0
  386. }
  387. // CompareFold performs a case-insensitive cmp.Compare on r and o.
  388. //
  389. // This can be used with [slices.SortFunc].
  390. //
  391. // For simple equality checks, use [Name.EqualFold].
  392. func (r Name) CompareFold(o Name) int {
  393. return slices.CompareFunc(r.parts[:], o.parts[:], compareFold)
  394. }
  395. func compareFold(a, b string) int {
  396. return slices.CompareFunc([]rune(a), []rune(b), func(a, b rune) int {
  397. return cmp.Compare(downcase(a), downcase(b))
  398. })
  399. }
  400. func downcase(r rune) rune {
  401. if r >= 'A' && r <= 'Z' {
  402. return r - 'A' + 'a'
  403. }
  404. return r
  405. }
  406. func (r Name) Host() string { return r.parts[PartHost] }
  407. func (r Name) Namespace() string { return r.parts[PartNamespace] }
  408. func (r Name) Model() string { return r.parts[PartModel] }
  409. func (r Name) Build() string { return r.parts[PartBuild] }
  410. func (r Name) Tag() string { return r.parts[PartTag] }
  411. // iter_Seq2 is a iter.Seq2 defined here to avoid the current build
  412. // restrictions in the go1.22 iter package requiring the
  413. // goexperiment.rangefunc tag to be set via the GOEXPERIMENT=rangefunc flag,
  414. // which we are not yet ready to support.
  415. //
  416. // Once we are ready to support rangefunc, this can be removed and replaced
  417. // with the iter.Seq2 type.
  418. type iter_Seq2[A, B any] func(func(A, B) bool)
  419. // Parts returns a sequence of the parts of a Name string from most specific
  420. // to least specific.
  421. //
  422. // It normalizes the input string by removing "http://" and "https://" only.
  423. // No other normalizations are performed.
  424. func parts(s string) iter_Seq2[PartKind, string] {
  425. return func(yield func(PartKind, string) bool) {
  426. if strings.HasPrefix(s, "http://") {
  427. s = strings.TrimPrefix(s, "http://")
  428. } else {
  429. s = strings.TrimPrefix(s, "https://")
  430. }
  431. if len(s) > MaxNamePartLen || len(s) == 0 {
  432. return
  433. }
  434. partLen := 0
  435. state, j := PartDigest, len(s)
  436. for i := len(s) - 1; i >= 0; i-- {
  437. if partLen++; partLen > MaxNamePartLen {
  438. // catch a part that is too long early, so
  439. // we don't keep spinning on it, waiting for
  440. // an isInValidPart check which would scan
  441. // over it again.
  442. yield(state, s[i+1:j])
  443. return
  444. }
  445. switch s[i] {
  446. case '@':
  447. switch state {
  448. case PartDigest:
  449. if !yield(PartDigest, s[i+1:j]) {
  450. return
  451. }
  452. if i == 0 {
  453. // This is the form
  454. // "@<digest>" which is valid.
  455. //
  456. // We're done.
  457. return
  458. }
  459. state, j, partLen = PartBuild, i, 0
  460. default:
  461. yield(PartExtraneous, s[i+1:j])
  462. return
  463. }
  464. case '+':
  465. switch state {
  466. case PartBuild, PartDigest:
  467. if !yield(PartBuild, s[i+1:j]) {
  468. return
  469. }
  470. state, j, partLen = PartTag, i, 0
  471. default:
  472. yield(PartExtraneous, s[i+1:j])
  473. return
  474. }
  475. case ':':
  476. switch state {
  477. case PartTag, PartBuild, PartDigest:
  478. if !yield(PartTag, s[i+1:j]) {
  479. return
  480. }
  481. state, j, partLen = PartModel, i, 0
  482. case PartHost:
  483. // noop: support for host:port
  484. default:
  485. yield(PartExtraneous, s[i+1:j])
  486. return
  487. }
  488. case '/':
  489. switch state {
  490. case PartModel, PartTag, PartBuild, PartDigest:
  491. if !yield(PartModel, s[i+1:j]) {
  492. return
  493. }
  494. state, j = PartNamespace, i
  495. case PartNamespace:
  496. if !yield(PartNamespace, s[i+1:j]) {
  497. return
  498. }
  499. state, j, partLen = PartHost, i, 0
  500. default:
  501. yield(PartExtraneous, s[i+1:j])
  502. return
  503. }
  504. }
  505. }
  506. if state <= PartNamespace {
  507. yield(state, s[:j])
  508. } else {
  509. yield(PartModel, s[:j])
  510. }
  511. }
  512. }
  513. func (r Name) IsZero() bool {
  514. return r.parts == [NumParts]string{}
  515. }
  516. // IsValid reports if a model has at minimum a valid model part.
  517. func (r Name) IsValid() bool {
  518. // Parts ensures we only have valid parts, so no need to validate
  519. // them here, only check if we have a name or not.
  520. return r.parts[PartModel] != ""
  521. }
  522. // ParseNameFromURLPath parses forms of a URL path into a Name. Specifically,
  523. // it trims any leading "/" and then calls [ParseName] with fill.
  524. func ParseNameFromURLPath(s, fill string) Name {
  525. s = strings.TrimPrefix(s, "/")
  526. return ParseNameFill(s, fill)
  527. }
  528. func ParseNameFromURLPathFill(s, fill string) Name {
  529. return ParseNameFill(s, fill)
  530. }
  531. // URLPath returns a complete, canonicalized, relative URL path using the parts of a
  532. // complete Name.
  533. //
  534. // The parts maintain their original case.
  535. //
  536. // Example:
  537. //
  538. // ParseName("example.com/namespace/model:tag+build").URLPath() // returns "/example.com/namespace/model:tag"
  539. func (r Name) DisplayURLPath() string {
  540. return r.DisplayShortest(MaskNothing)
  541. }
  542. // URLPath returns a complete, canonicalized, relative URL path using the parts of a
  543. // complete Name in the form:
  544. //
  545. // <host>/<namespace>/<model>/<tag>
  546. //
  547. // The parts are downcased.
  548. func (r Name) URLPath() string {
  549. return strings.ToLower(path.Join(r.parts[:PartBuild]...))
  550. }
  551. // ParseNameFromFilepath parses a file path into a Name. The input string must be a
  552. // valid file path representation of a model name in the form:
  553. //
  554. // host/namespace/model/tag/build
  555. //
  556. // The zero valid is returned if s does not contain all path elements
  557. // leading up to the model part, or if any path element is an invalid part
  558. // for the its corresponding part kind.
  559. //
  560. // The fill string is used to fill in missing parts of any constructed Name.
  561. // See [ParseName] for more information on the fill string.
  562. func ParseNameFromFilepath(s, fill string) Name {
  563. var r Name
  564. for i := range PartBuild + 1 {
  565. part, rest, _ := strings.Cut(s, string(filepath.Separator))
  566. if !IsValidNamePart(i, part) {
  567. return Name{}
  568. }
  569. r.parts[i] = part
  570. s = rest
  571. if s == "" {
  572. break
  573. }
  574. }
  575. if s != "" {
  576. return Name{}
  577. }
  578. if !r.IsValid() {
  579. return Name{}
  580. }
  581. return fillName(r, fill)
  582. }
  583. // Filepath returns a complete, canonicalized, relative file path using the
  584. // parts of a complete Name.
  585. //
  586. // Each parts is downcased, except for the build part which is upcased.
  587. //
  588. // Example:
  589. //
  590. // ParseName("example.com/namespace/model:tag+build").Filepath() // returns "example.com/namespace/model/tag/BUILD"
  591. func (r Name) Filepath() string {
  592. for i := range r.parts {
  593. if PartKind(i) == PartBuild {
  594. r.parts[i] = strings.ToUpper(r.parts[i])
  595. } else {
  596. r.parts[i] = strings.ToLower(r.parts[i])
  597. }
  598. }
  599. return filepath.Join(r.parts[:]...)
  600. }
  601. // FilepathNoBuild returns a complete, canonicalized, relative file path using
  602. // the parts of a complete Name, but without the build part.
  603. func (r Name) FilepathNoBuild() string {
  604. for i := range PartBuild {
  605. r.parts[i] = strings.ToLower(r.parts[i])
  606. }
  607. return filepath.Join(r.parts[:PartBuild]...)
  608. }
  609. // IsValidNamePart reports if s contains all valid characters for the given
  610. // part kind and is under MaxNamePartLen bytes.
  611. func IsValidNamePart(kind PartKind, s string) bool {
  612. if len(s) > MaxNamePartLen {
  613. return false
  614. }
  615. if s == "" {
  616. return false
  617. }
  618. var consecutiveDots int
  619. for i, c := range []byte(s) {
  620. if i == 0 && !isAlphaNumeric(c) {
  621. return false
  622. }
  623. if c == '.' {
  624. if consecutiveDots++; consecutiveDots >= 2 {
  625. return false
  626. }
  627. } else {
  628. consecutiveDots = 0
  629. }
  630. if !isValidByteFor(kind, c) {
  631. return false
  632. }
  633. }
  634. return true
  635. }
  636. func isAlphaNumeric(c byte) bool {
  637. return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
  638. }
  639. func isValidByteFor(kind PartKind, c byte) bool {
  640. if kind == PartNamespace && c == '.' {
  641. return false
  642. }
  643. if kind == PartHost && c == ':' {
  644. return true
  645. }
  646. if c == '.' || c == '-' {
  647. return true
  648. }
  649. if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' {
  650. return true
  651. }
  652. return false
  653. }