Remixware report: shopfront

Code in this project that matches third-party code, the licence that code came with, and what that licence asks.

Project
testdata/projects/shopfront
Project licence
MIT (from LICENSE)
Compared with
upstream.rmx (index: 10 components, 10 files)
Scanned
11 source files, 474 lines
Notice files
THIRD_PARTY_NOTICES.md
Made
2026-10-26T14:06:45Z by remixware 0.2.0
2 CONFLICT1 NO LICENSE5 ATTRIBUTION MISSING1 ATTRIBUTED

Findings

CONFLICTinternal/files/order.go lines 7-46RW-06c2e1f5
Matches example.org/gplsort, natural.go lines 14-53. Licence GPL-3.0-or-later (from COPYING). Copied with names changed (98% match).

GPL-3.0-or-later code can't go into an MIT project unless the whole program is released under GPL-3.0-or-later. Remove it, rewrite it, or relicense.

Your code

7func lessNatural(left, right string) bool {
8    p, q := 0, 0
9    for p < len(left) && q < len(right) {
10        x, y := left[p], right[q]
11        if digit(x) && digit(y) {
12            startP := p
13            for p < len(left) && digit(left[p]) {
14                p++
15            }
16            startQ := q
17            for q < len(right) && digit(right[q]) {
18                q++
19            }
20            numL := dropZeros(left[startP:p])
21            numR := dropZeros(right[startQ:q])
22            if len(numL) != len(numR) {
23                return len(numL) < len(numR)
24            }
25            if numL != numR {
26                return numL < numR
27            }
28            continue
29        }
30        if x != y {
31            return x < y
32        }
33        p++
34        q++
35    }
36    return len(left)-p < len(right)-q
37}
38
39func digit(c byte) bool { return c >= '0' && c <= '9' }
40
41func dropZeros(s string) string {
42    for len(s) > 1 && s[0] == '0' {
43        s = s[1:]
44    }
45    return s
46}

Their code

14func NaturalLess(a, b string) bool {
15    i, j := 0, 0
16    for i < len(a) && j < len(b) {
17        ca, cb := a[i], b[j]
18        if isDigit(ca) && isDigit(cb) {
19            si := i
20            for i < len(a) && isDigit(a[i]) {
21                i++
22            }
23            sj := j
24            for j < len(b) && isDigit(b[j]) {
25                j++
26            }
27            na := trimZeros(a[si:i])
28            nb := trimZeros(b[sj:j])
29            if len(na) != len(nb) {
30                return len(na) < len(nb)
31            }
32            if na != nb {
33                return na < nb
34            }
35            continue
36        }
37        if ca != cb {
38            return ca < cb
39        }
40        i++
41        j++
42    }
43    return len(a)-i < len(b)-j
44}
45
46func isDigit(c byte) bool { return c >= '0' && c <= '9' }
47
48func trimZeros(s string) string {
49    for len(s) > 1 && s[0] == '0' {
50        s = s[1:]
51    }
52    return s
53}
CONFLICTscripts/slug.py lines 1-36RW-a64e598d
Matches agplwiki 0.4.0, agplwiki/text.py lines 1-36. Licence AGPL-3.0-or-later (from LICENSE). Copied as is (100% match).

AGPL-3.0-or-later code can't go into an MIT project unless the whole program is released under AGPL-3.0-or-later, which also means offering the source to everyone who uses it over a network.

Your code

1"""Build URL slugs for product pages."""
2
3import re
4import unicodedata
5
6_STRIP = re.compile(r"[^\w\s-]")
7_DASHES = re.compile(r"[-\s]+")
8
9
10def slugify(title, max_length=60):
11    """Turn a page title into a URL path segment."""
12    value = unicodedata.normalize("NFKD", title)
13    value = value.encode("ascii", "ignore").decode("ascii")
14    value = _STRIP.sub("", value).strip().lower()
15    value = _DASHES.sub("-", value)
16    if len(value) > max_length:
17        cut = value.rfind("-", 0, max_length)
18        value = value[: cut if cut > 0 else max_length]
19    return value or "page"
20
21
22def escape_html(text, quote=True):
23    """Escape the characters that matter in HTML."""
24    out = []
25    for ch in text:
26        if ch == "&":
27            out.append("&amp;")
28        elif ch == "<":
29            out.append("&lt;")
30        elif ch == ">":
31            out.append("&gt;")
32        elif quote and ch == '"':
33            out.append("&quot;")
34        else:
35            out.append(ch)
36    return "".join(out)

Their code

1"""Text helpers for page titles and markup."""
2
3import re
4import unicodedata
5
6_STRIP = re.compile(r"[^\w\s-]")
7_DASHES = re.compile(r"[-\s]+")
8
9
10def slugify(title, max_length=60):
11    """Turn a page title into a URL path segment."""
12    value = unicodedata.normalize("NFKD", title)
13    value = value.encode("ascii", "ignore").decode("ascii")
14    value = _STRIP.sub("", value).strip().lower()
15    value = _DASHES.sub("-", value)
16    if len(value) > max_length:
17        cut = value.rfind("-", 0, max_length)
18        value = value[: cut if cut > 0 else max_length]
19    return value or "page"
20
21
22def escape_html(text, quote=True):
23    """Escape the characters that matter in HTML."""
24    out = []
25    for ch in text:
26        if ch == "&":
27            out.append("&amp;")
28        elif ch == "<":
29            out.append("&lt;")
30        elif ch == ">":
31            out.append("&gt;")
32        elif quote and ch == '"':
33            out.append("&quot;")
34        else:
35            out.append(ch)
36    return "".join(out)
NO LICENSEinternal/format/bytes.go lines 2-24RW-00095597
Matches upstream, snippets/human.go lines 1-23. Licence NONE. Copied with some edits (100% match).

No licence was found, so nobody has given permission to copy this code. Remove it or ask the author.

Your code

2package format
3
4import (
5    "fmt"
6    "strings"
7)
8
9// Size formats a file size with binary units.
10func Size(n int64) string {
11    const unit = 1024
12    if n < unit {
13        return fmt.Sprintf("%d B", n)
14    }
15    div, exp := int64(unit), 0
16    for m := n / unit; m >= unit; m /= unit {
17        div *= unit
18        exp++
19    }
20    value := float64(n) / float64(div)
21    text := fmt.Sprintf("%.1f", value)
22    text = strings.TrimSuffix(text, ".0")
23    return text + " " + string("KMGTPE"[exp]) + "iB"
24}

Their code

1package snippets
2
3import (
4    "fmt"
5    "strings"
6)
7
8// HumanBytes formats a byte count with binary units: 1536 -> "1.5 KiB".
9func HumanBytes(n int64) string {
10    const unit = 1024
11    if n < unit {
12        return fmt.Sprintf("%d B", n)
13    }
14    div, exp := int64(unit), 0
15    for m := n / unit; m >= unit; m /= unit {
16        div *= unit
17        exp++
18    }
19    value := float64(n) / float64(div)
20    text := fmt.Sprintf("%.1f", value)
21    text = strings.TrimSuffix(text, ".0")
22    return text + " " + string("KMGTPE"[exp]) + "iB"
23}
ATTRIBUTION MISSINGinternal/cache/lru.go lines 2-52RW-abb1da01
Matches example.net/mplcache, lru.go lines 6-57. Licence MPL-2.0 (from LICENSE). Copied with some edits (100% match).

MPL-2.0 applies file by file: keep the copied code in its own file under MPL-2.0, keep its notice, and publish your changes to that file. Its copyright line and licence text also have to ship with your copy: until your notices carry them, this counts as attribution missing.

Your code

2package cache
3
4import "container/list"
5
6type item struct {
7    key   string
8    value any
9}
10
11// Products keeps at most size products and drops the least recently used.
12type Products struct {
13    size  int
14    order *list.List
15    items map[string]*list.Element
16    evict func(key string, value any)
17}
18
19// NewProducts returns an empty cache.
20func NewProducts(size int, evict func(key string, value any)) *Products {
21    return &Products{size: size, order: list.New(), items: make(map[string]*list.Element), evict: evict}
22}
23
24// Get returns the value for key and marks it as recently used.
25func (c *Products) Get(key string) (any, bool) {
26    el, ok := c.items[key]
27    if !ok {
28        return nil, false
29    }
30    c.order.MoveToFront(el)
31    return el.Value.(*item).value, true
32}
33
34// Put stores value under key, dropping the oldest entry when full.
35func (c *Products) Put(key string, value any) {
36    if el, ok := c.items[key]; ok {
37        el.Value.(*item).value = value
38        c.order.MoveToFront(el)
39        return
40    }
41    el := c.order.PushFront(&item{key: key, value: value})
42    c.items[key] = el
43    if c.order.Len() > c.size {
44        last := c.order.Back()
45        c.order.Remove(last)
46        e := last.Value.(*item)
47        delete(c.items, e.key)
48        if c.evict != nil {
49            c.evict(e.key, e.value)
50        }
51    }
52}

Their code

6package mplcache
7
8import "container/list"
9
10type entry struct {
11    key   string
12    value any
13}
14
15// LRU keeps at most Size entries and drops the least recently used one.
16type LRU struct {
17    size  int
18    order *list.List
19    items map[string]*list.Element
20    evict func(key string, value any)
21}
22
23// New returns an empty cache for size entries. evict, if set, is called
24// for every entry the cache drops.
25func New(size int, evict func(key string, value any)) *LRU {
26    return &LRU{size: size, order: list.New(), items: make(map[string]*list.Element), evict: evict}
27}
28
29// Get returns the value for key and marks it as recently used.
30func (c *LRU) Get(key string) (any, bool) {
31    el, ok := c.items[key]
32    if !ok {
33        return nil, false
34    }
35    c.order.MoveToFront(el)
36    return el.Value.(*entry).value, true
37}
38
39// Put stores value under key, dropping the oldest entry when the cache is full.
40func (c *LRU) Put(key string, value any) {
41    if el, ok := c.items[key]; ok {
42        el.Value.(*entry).value = value
43        c.order.MoveToFront(el)
44        return
45    }
46    el := c.order.PushFront(&entry{key: key, value: value})
47    c.items[key] = el
48    if c.order.Len() > c.size {
49        last := c.order.Back()
50        c.order.Remove(last)
51        e := last.Value.(*entry)
52        delete(c.items, e.key)
53        if c.evict != nil {
54            c.evict(e.key, e.value)
55        }
56    }
57}
ATTRIBUTION MISSINGinternal/csvio/split.go lines 2-45RW-116557ad
Matches example.com/fastcsv, fastcsv.go lines 2-45. Licence MIT (from LICENSE). Copied as is (100% match).

Keep its copyright line and the MIT licence text with your copy, for example in a third-party notices file.

Your code

2package csvio
3
4import (
5    "errors"
6    "strings"
7)
8
9// ErrQuote is returned for a field with an unterminated quote.
10var ErrQuote = errors.New("fastcsv: unterminated quote")
11
12// SplitRecord splits one line into fields. Fields may be quoted with double
13// quotes; a doubled quote inside a quoted field stands for one quote.
14func SplitRecord(line string, sep rune) ([]string, error) {
15    var fields []string
16    var field strings.Builder
17    inQuotes := false
18    runes := []rune(line)
19    for i := 0; i < len(runes); i++ {
20        r := runes[i]
21        switch {
22        case inQuotes && r == '"':
23            if i+1 < len(runes) && runes[i+1] == '"' {
24                field.WriteRune('"')
25                i++
26            } else {
27                inQuotes = false
28            }
29        case inQuotes:
30            field.WriteRune(r)
31        case r == '"' && field.Len() == 0:
32            inQuotes = true
33        case r == sep:
34            fields = append(fields, field.String())
35            field.Reset()
36        default:
37            field.WriteRune(r)
38        }
39    }
40    if inQuotes {
41        return nil, ErrQuote
42    }
43    fields = append(fields, field.String())
44    return fields, nil
45}

Their code

2package fastcsv
3
4import (
5    "errors"
6    "strings"
7)
8
9// ErrQuote is returned for a field with an unterminated quote.
10var ErrQuote = errors.New("fastcsv: unterminated quote")
11
12// SplitRecord splits one line into fields. Fields may be quoted with double
13// quotes; a doubled quote inside a quoted field stands for one quote.
14func SplitRecord(line string, sep rune) ([]string, error) {
15    var fields []string
16    var field strings.Builder
17    inQuotes := false
18    runes := []rune(line)
19    for i := 0; i < len(runes); i++ {
20        r := runes[i]
21        switch {
22        case inQuotes && r == '"':
23            if i+1 < len(runes) && runes[i+1] == '"' {
24                field.WriteRune('"')
25                i++
26            } else {
27                inQuotes = false
28            }
29        case inQuotes:
30            field.WriteRune(r)
31        case r == '"' && field.Len() == 0:
32            inQuotes = true
33        case r == sep:
34            fields = append(fields, field.String())
35            field.Reset()
36        default:
37            field.WriteRune(r)
38        }
39    }
40    if inQuotes {
41        return nil, ErrQuote
42    }
43    fields = append(fields, field.String())
44    return fields, nil
45}
ATTRIBUTION MISSINGtools/prettyjson.py lines 1-32RW-91be51f9
Matches tinyjson 1.2.0, __init__.py lines 1-32. Licence MIT (from tinyjson-1.2.0.dist-info/METADATA). Copied with names changed (100% match).

Keep its copyright line and the MIT licence text with your copy, for example in a third-party notices file.

Your code

1"""Print the catalog as readable JSON."""
2
3
4def to_text(obj, width=2, depth=0):
5    """Format obj as indented JSON text."""
6    pad = " " * (width * depth)
7    inner = " " * (width * (depth + 1))
8    if isinstance(obj, dict):
9        if not obj:
10            return "{}"
11        parts = []
12        for name in sorted(obj):
13            text = to_text(obj[name], width, depth + 1)
14            parts.append('%s"%s": %s' % (inner, quote(str(name)), text))
15        return "{\n" + ",\n".join(parts) + "\n" + pad + "}"
16    if isinstance(obj, (list, tuple)):
17        if not obj:
18            return "[]"
19        parts = [inner + to_text(v, width, depth + 1) for v in obj]
20        return "[\n" + ",\n".join(parts) + "\n" + pad + "]"
21    if obj is None:
22        return "null"
23    if obj is True or obj is False:
24        return "true" if obj else "false"
25    if isinstance(obj, (int, float)):
26        return repr(obj)
27    return '"%s"' % quote(str(obj))
28
29
30def quote(text):
31    """Escape a string for JSON."""
32    return text.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")

Their code

1"""A very small JSON pretty printer."""
2
3
4def pretty(value, indent=2, level=0):
5    """Format value as indented JSON text."""
6    pad = " " * (indent * level)
7    inner = " " * (indent * (level + 1))
8    if isinstance(value, dict):
9        if not value:
10            return "{}"
11        parts = []
12        for key in sorted(value):
13            text = pretty(value[key], indent, level + 1)
14            parts.append('%s"%s": %s' % (inner, escape(str(key)), text))
15        return "{\n" + ",\n".join(parts) + "\n" + pad + "}"
16    if isinstance(value, (list, tuple)):
17        if not value:
18            return "[]"
19        parts = [inner + pretty(v, indent, level + 1) for v in value]
20        return "[\n" + ",\n".join(parts) + "\n" + pad + "]"
21    if value is None:
22        return "null"
23    if value is True or value is False:
24        return "true" if value else "false"
25    if isinstance(value, (int, float)):
26        return repr(value)
27    return '"%s"' % escape(str(value))
28
29
30def escape(text):
31    """Escape a string for JSON."""
32    return text.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
ATTRIBUTION MISSINGweb/pad.js lines 1-28RW-cb1eaaf4
Matches leftpadx 1.0.4, index.js lines 1-28. Licence ISC (from package.json). Whole file, copied as is (100% match).

Keep its copyright line and the ISC licence text with your copy, for example in a third-party notices file.

Your code

1'use strict';
2
3// Pads str on the left with ch until it is len characters long.
4function leftPad(str, len, ch) {
5  str = String(str);
6  if (ch === undefined || ch === null || ch === '') {
7    ch = ' ';
8  } else {
9    ch = String(ch);
10  }
11  let pad = len - str.length;
12  if (pad <= 0) {
13    return str;
14  }
15  let out = '';
16  while (pad > 0) {
17    if (pad & 1) {
18      out += ch;
19    }
20    pad >>= 1;
21    if (pad) {
22      ch += ch;
23    }
24  }
25  return out + str;
26}
27
28module.exports = leftPad;

Their code

1'use strict';
2
3// Pads str on the left with ch until it is len characters long.
4function leftPad(str, len, ch) {
5  str = String(str);
6  if (ch === undefined || ch === null || ch === '') {
7    ch = ' ';
8  } else {
9    ch = String(ch);
10  }
11  let pad = len - str.length;
12  if (pad <= 0) {
13    return str;
14  }
15  let out = '';
16  while (pad > 0) {
17    if (pad & 1) {
18      out += ch;
19    }
20    pad >>= 1;
21    if (pad) {
22      ch += ch;
23    }
24  }
25  return out + str;
26}
27
28module.exports = leftPad;
ATTRIBUTION MISSINGweb/util.js lines 3-25RW-c857c2bb
Matches jsutil 3.1.0, index.js lines 6-28. Licence BSD-3-Clause (from package.json). Copied with names changed (100% match).

Keep its copyright line and the BSD-3-Clause licence text with your copy, for example in a third-party notices file.

Your code

3function debounce(callback, delay, leading) {
4  let handle = null;
5  let pending = null;
6  function fire() {
7    handle = null;
8    if (!leading && pending) {
9      callback.apply(null, pending);
10      pending = null;
11    }
12  }
13  return function wrapped(...params) {
14    const runNow = leading && handle === null;
15    pending = params;
16    if (handle !== null) {
17      clearTimeout(handle);
18    }
19    handle = setTimeout(fire, delay);
20    if (runNow) {
21      callback.apply(null, params);
22      pending = null;
23    }
24  };
25}

Their code

6function debounce(fn, wait, immediate) {
7  let timer = null;
8  let lastArgs = null;
9  function later() {
10    timer = null;
11    if (!immediate && lastArgs) {
12      fn.apply(null, lastArgs);
13      lastArgs = null;
14    }
15  }
16  return function debounced(...args) {
17    const callNow = immediate && timer === null;
18    lastArgs = args;
19    if (timer !== null) {
20      clearTimeout(timer);
21    }
22    timer = setTimeout(later, wait);
23    if (callNow) {
24      fn.apply(null, args);
25      lastArgs = null;
26    }
27  };
28}
ATTRIBUTEDinternal/net/retry.go lines 13-46RW-8591310b
Matches example.com/retrykit, retry.go lines 22-70. Licence Apache-2.0 (from LICENSE). Copied with some edits (93% match).

Credited in the project's notices. Keep its copyright line, the Apache-2.0 licence text and any NOTICE text the project ships, and mark files you changed.

Your code

13func backoff(n int, base, max time.Duration, jitter float64, rnd func() float64) time.Duration {
14    d := base
15    for i := 0; i < n && d < max; i++ {
16        d *= 2
17    }
18    if d > max {
19        d = max
20    }
21    if jitter > 0 {
22        spread := float64(d) * jitter
23        d = time.Duration(float64(d) - spread + 2*spread*rnd())
24    }
25    return d
26}
27
28// retry calls fn until it succeeds, the context ends, or attempts run out.
29func retry(ctx context.Context, attempts int, base, max time.Duration, fn func() error) error {
30    var err error
31    for n := 0; n < attempts; n++ {
32        if err = fn(); err == nil {
33            return nil
34        }
35        if errors.Is(err, context.Canceled) {
36            return err
37        }
38        wait := backoff(n, base, max, 0.2, rand.Float64)
39        select {
40        case <-ctx.Done():
41            return ctx.Err()
42        case <-time.After(wait):
43        }
44    }
45    return fmt.Errorf("shopfront: service unavailable after %d attempts: %w", attempts, err)
46}

Their code

22func Backoff(n int, base, max time.Duration, jitter float64, rnd func() float64) time.Duration {
23    d := base
24    for i := 0; i < n && d < max; i++ {
25        d *= 2
26    }
27    if d > max {
28        d = max
29    }
30    if jitter > 0 {
31        spread := float64(d) * jitter
32        d = time.Duration(float64(d) - spread + 2*spread*rnd())
33    }
34    return d
35}
36
37// Retryable reports whether an error is worth another attempt.
38func Retryable(err error) bool {
39    if err == nil {
40        return false
41    }
42    if errors.Is(err, ErrPermanent) || errors.Is(err, context.Canceled) {
43        return false
44    }
45    var t interface{ Temporary() bool }
46    if errors.As(err, &t) {
47        return t.Temporary()
48    }
49    return true
50}
51
52// Do calls fn until it succeeds, the context ends, or attempts run out.
53func Do(ctx context.Context, attempts int, base, max time.Duration, fn func() error) error {
54    var err error
55    for n := 0; n < attempts; n++ {
56        if err = fn(); err == nil {
57            return nil
58        }
59        if !Retryable(err) {
60            return err
61        }
62        wait := Backoff(n, base, max, 0.2, rand.Float64)
63        select {
64        case <-ctx.Done():
65            return ctx.Err()
66        case <-time.After(wait):
67        }
68    }
69    return fmt.Errorf("retrykit: gave up after %d attempts: %w", attempts, err)
70}