nkb boundary

Give the limit a field states and get the values that test it: nothing, one under, exactly at and one over, in characters that weigh differently in bytes and UTF-16 units.

nkb boundary [--max-length N] [--min-length N]
             [--unit graphemes|code-points|utf16|bytes]
             [--format json|csv|lines] [--escaped|--raw] [--base64]

What does it make?

For a field that takes at most 50 characters: the empty value, and then values of 49, 50 and 51 in each of six fills. With --min-length it adds the lengths around the least the field takes, and a length that two bounds share comes once. Every value says how long it is in four units, and whether its length is inside the limit.

The packs hold lists of odd characters. This command answers the one sentence a specification gives you about a field.

Why six fills?

Because “50 characters” is one of four numbers, and you rarely know which one the application counts:

Fifty letters a are fifty in every unit, so they cannot tell these apart. Fifty of a two-byte letter are fifty characters and a hundred bytes, fifty emoji are a hundred UTF-16 units, and fifty family emoji are fifty characters to the eye and two hundred and fifty code points. A field that takes fifty letters a and refuses or cuts one of the others has told you what it counts.

None of the characters changes under a Unicode normal form, so a field that normalises its text still gets the length you asked for.

Examples

One value per line, to paste from or to feed a test:

$ nkb boundary --max-length 50 --format lines > limit-50.txt
nkb boundary: 19 values, 1500 code points, 4350 bytes

CSV, with the four sizes of every value side by side:

$ nkb boundary --max-length 50 --format csv
nkb boundary: 19 values, 1500 code points, 4350 bytes
id,name,value,fill,length,within,graphemes,code_points,utf16_units,bytes
empty,Empty value,,,0,true,0,0,0,0
letters-49,49 x a,aaaaa...,letters,49,true,49,49,49,49
letters-50,50 x a,aaaaa...,letters,50,true,50,50,50,50
letters-51,51 x a,aaaaa...,letters,51,false,51,51,51,51
...
two-byte-50,50 x U+0142,...,two-byte,50,true,50,50,50,100
four-byte-50,50 x U+1F600,...,four-byte,50,true,50,50,100,200
cluster-50,50 x U+1F468 U+200D U+1F469 U+200D U+1F467,...,cluster,50,true,50,250,400,900

A limit counted in bytes. Every value has exactly the bytes its row says, with the letter a in front where whole characters do not add up:

$ nkb boundary --max-length 50 --unit bytes --format csv
...
two-byte-49,1 x a + 24 x U+0142,...,two-byte,49,true,25,25,25,49
two-byte-50,25 x U+0142,...,two-byte,50,true,25,25,25,50
two-byte-51,1 x a + 25 x U+0142,...,two-byte,51,false,26,26,26,51

Which unit should I ask for?

Start without --unit. The values are then counted the way a person counts, and the application’s answer to the heavier fills shows what it counts instead.

Then run again in that unit. A value of 51 bytes made of a two-byte letter is one a and twenty-five letters, so a field that cuts at 50 bytes cuts through the middle of the last letter. In utf16 the same happens to an emoji, and what is left is half a character: text that no longer displays, a save that fails, or an error far from the field. A value made of whole characters up to the limit never provokes this, which is why the padding stands in front.

How do I read a counterstring?

Every star is preceded by the number of its own place. This is the counterstring of 51:

*3*5*7*9*12*15*18*21*24*27*30*33*36*39*42*45*48*51*

If the field kept ...42*45*48*51, the last star stands at 48 and two characters follow it, so 50 arrived. You read the cut from the field itself, without counting and without a second tool.

A counterstring is digits and stars. A field that takes letters only refuses it for that reason, and that is what the letters fill is for. A field that renders Markdown may swallow the stars.

What does “within” promise?

That the length of the value is inside the limit you gave, in the unit you gave, and nothing more. A required field still refuses the empty value, and a field that takes letters only still refuses an emoji. When a value that is within the limit is refused for its length, either the application counts in another unit or the limit is not the one the specification states. Both are worth a report.

Options

--max-length N

The most the field takes, from 1 to 100000

--min-length N

The least the field takes, from 1 to 100000

--unit graphemes|code-points|utf16|bytes

What the limit is counted in (default: graphemes)

--format json|csv|lines

One of: json, csv, lines. The formats below say what each one writes.

--escaped

Print values as a pack file stores them

--raw

Print values literally

--base64

Encode values, for channels that damage text

-h, --help

Show this help and exit with 0

Fills

letters

The letter a

counterstring

Numbers and stars, as in *3*5*7*9*12*. Each number is the place of the star after it, so a field that cut the value shows where it cut

two-byte

A letter of two bytes (U+0142)

three-byte

A sign of three bytes (U+20AC)

four-byte

An emoji of four bytes and two UTF-16 units (U+1F600)

cluster

A family emoji: one character to the eye, five code points

Units

graphemes

Characters as a person counts them. The default

code-points

Unicode code points

utf16

UTF-16 units, which a browser's maxlength and JavaScript count

bytes

Bytes of UTF-8

Formats

json

Both forms of every value, with its fill, its length, whether it is within the limit and its size in all four units. The default

csv

One row per value, with the same columns. Escaped by default

lines

One value per line. The empty value is an empty line

Exit codes

This command's help names no exit codes of its own. The numbers mean the same for every command: Exit codes

The help, as the program prints it

What nkb boundary --help prints, from nkb 0.3.0.

nkb boundary - make the values that test the length limit of a field

Usage:
  nkb boundary [--max-length N] [--min-length N]
               [--unit graphemes|code-points|utf16|bytes]
               [--format json|csv|lines] [--escaped|--raw] [--base64]

Give the limit the field states and get the values worth trying: nothing,
then one under, exactly at and one over each bound. Every length comes in
six fills, because "50 characters" is a different number in each unit.

Fills:
  letters        The letter a
  counterstring  Numbers and stars, as in *3*5*7*9*12*. Each number is the
                 place of the star after it, so a field that cut the value
                 shows where it cut
  two-byte       A letter of two bytes (U+0142)
  three-byte     A sign of three bytes (U+20AC)
  four-byte      An emoji of four bytes and two UTF-16 units (U+1F600)
  cluster        A family emoji: one character to the eye, five code points

Units:
  graphemes    Characters as a person counts them. The default
  code-points  Unicode code points
  utf16        UTF-16 units, which a browser's maxlength and JavaScript count
  bytes        Bytes of UTF-8

Counted in graphemes, 50 two-byte letters are 100 bytes and 50 emoji are
100 UTF-16 units, so a field that refuses or cuts them counts something
else. Run again in that unit: a value then has exactly that many units,
padded with the letter a in front, and the limit falls inside the last
character of the value one over it.

Values go to standard output and the account of what came out goes to
standard error. "within" says whether the length of a value is inside the
limit and nothing more: a required field still refuses the empty value.

Formats:
  json     Both forms of every value, with its fill, its length, whether it
           is within the limit and its size in all four units. The default
  csv      One row per value, with the same columns. Escaped by default
  lines    One value per line. The empty value is an empty line

Options:
      --max-length N  The most the field takes, from 1 to 100000
      --min-length N  The least the field takes, from 1 to 100000
      --unit          What the limit is counted in (default: graphemes)
      --escaped       Print values as a pack file stores them
      --raw           Print values literally
      --base64        Encode values, for channels that damage text
  -h, --help          Show this help and exit with 0